Skip to content

Commit

Permalink
feat: Implemented integration of the ebsi rpc service with the ebsi d…
Browse files Browse the repository at this point in the history
…id provider
  • Loading branch information
Maikel Maas committed Apr 11, 2024
1 parent 36dc9b7 commit 3c1ef0d
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 33 deletions.
1 change: 1 addition & 0 deletions packages/did-provider-ebsi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"debug": "^4.3.4",
"did-resolver": "^4.1.0",
"ethers": "^6.11.1",
"jose": "^4.14.6",
"multiformats": "9.9.0",
"uint8arrays": "3.1.1"
},
Expand Down
143 changes: 138 additions & 5 deletions packages/did-provider-ebsi/src/EbsiDidProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { IAgentContext, IIdentifier, IKeyManager, MinimalImportableKey } from '@veramo/core'
import { IAgentContext, IDIDManager, IIdentifier, IKeyManager, ManagedKeyInfo, MinimalImportableKey } from '@veramo/core'
import Debug from 'debug'
import { AbstractIdentifierProvider } from '@veramo/did-manager/build/abstract-identifier-provider'
import { DIDDocument } from 'did-resolver'
import { IKey, IService } from '@veramo/core/build/types/IIdentifier'
import * as u8a from 'uint8arrays'
import { ebsiDIDSpecInfo, EbsiKeyType, EbsiPublicKeyPurpose, IContext, ICreateIdentifierArgs, IKeyOpts } from './types'
import { generateEbsiPrivateKeyHex, toMethodSpecificId } from './functions'
import { ebsiDIDSpecInfo, EbsiKeyType, EbsiPublicKeyPurpose, IContext, ICreateIdentifierArgs, IKeyOpts, Response, Response200 } from './types'
import { formatEbsiPublicKey, generateEbsiPrivateKeyHex, toMethodSpecificId } from './functions'
import {
addVerificationMethod,
addVerificationMethodRelationship,
insertDidDocument,
sendSignedTransaction,
updateBaseDocument,
} from './services/EbsiRPCService'
import { toJwk } from '@sphereon/ssi-sdk-ext.key-utils'
import { calculateJwkThumbprint } from 'jose'
import { Transaction } from 'ethers'

const debug = Debug('sphereon:did-provider-ebsi')

Expand All @@ -19,6 +29,7 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {

async createIdentifier(args: ICreateIdentifierArgs, context: IContext): Promise<Omit<IIdentifier, 'provider'>> {
const { type, options, kms, alias } = { ...args }

if (!type || type === ebsiDIDSpecInfo.V1) {
const secp256k1ManagedKeyInfo = await this.generateEbsiKeyPair(
{
Expand All @@ -45,6 +56,14 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
alias,
services: [],
}
const id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)

if (options === undefined) {
throw new Error(`Options must be provided ${JSON.stringify(options)}`)
}

await this.createEbsiDid({ identifier, secp256k1ManagedKeyInfo, secp256r1ManagedKeyInfo, id, from: options.from }, context)

debug('Created', identifier.did)
return identifier
} else if (type === ebsiDIDSpecInfo.KEY) {
Expand All @@ -53,6 +72,106 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
throw Error(`Type ${type} not supported`)
}

async createEbsiDid(
args: {
identifier: Omit<IIdentifier, 'provider'>
secp256k1ManagedKeyInfo: ManagedKeyInfo
secp256r1ManagedKeyInfo: ManagedKeyInfo
id: number
from: string
baseDocument?: string
},
context: IContext
): Promise<void> {
const insertDidDocTransaction = await insertDidDocument({
params: [
{
from: args.from,
did: args.identifier.did,
baseDocument:
args.baseDocument ?? JSON.stringify({ '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'] }),
vMethoddId: await calculateJwkThumbprint(toJwk(args.secp256k1ManagedKeyInfo.publicKeyHex, 'Secp256k1')),
isSecp256k1: true,
publicKey: formatEbsiPublicKey({ key: args.secp256k1ManagedKeyInfo, type: 'Secp256k1' }),
notBefore: 1,
notAfter: 1,
},
],
id: args.id,
})

await this.sendTransaction({ docTransactionResponse: insertDidDocTransaction, kid: args.secp256r1ManagedKeyInfo.kid, id: args.id }, context)

const addVerificationMethodTransaction = await addVerificationMethod({
params: [
{
from: args.from, // required
did: args.identifier.did,
isSecp256k1: true,
vMethoddId: await calculateJwkThumbprint(toJwk(args.secp256k1ManagedKeyInfo.publicKeyHex, 'Secp256k1')),
publicKey: formatEbsiPublicKey({ key: args.secp256k1ManagedKeyInfo, type: 'Secp256k1' }),
},
],
id: args.id,
})

await this.sendTransaction(
{ docTransactionResponse: addVerificationMethodTransaction, kid: args.secp256r1ManagedKeyInfo.kid, id: args.id },
context
)

const addVerificationMethodRelationshipTransaction = await addVerificationMethodRelationship({
params: [
{
from: args?.from,
did: args.identifier.did,
vMethoddId: await calculateJwkThumbprint(toJwk(args.secp256r1ManagedKeyInfo.publicKeyHex, 'Secp256r1')),
name: 'assertionMethod',
notAfter: 1,
notBefore: 1,
},
],
id: args.id,
})

await this.sendTransaction(
{ docTransactionResponse: addVerificationMethodRelationshipTransaction, kid: args.secp256r1ManagedKeyInfo.kid, id: args.id },
context
)
}

private sendTransaction = async (args: { docTransactionResponse: Response; kid: string; id: number }, context: IContext) => {
if ('status' in args.docTransactionResponse) {
throw new Error(JSON.stringify(args.docTransactionResponse, null, 2))
}
const unsignedTransaction = (args.docTransactionResponse as Response200).result

const signedRawTransaction = await context.agent.keyManagerSignEthTX({
kid: args.kid,
transaction: unsignedTransaction,
})

const { r, s, v } = Transaction.from(signedRawTransaction).signature!

const sTResponse = await sendSignedTransaction({
params: [
{
protocol: 'eth',
unsignedTransaction: unsignedTransaction,
r,
s,
v: v.toString(),
signedRawTransaction,
},
],
id: args.id,
})

if ('status' in sTResponse) {
throw new Error(JSON.stringify(sTResponse, null, 2))
}
}

private async generateEbsiKeyPair(args: { keyOpts?: IKeyOpts; keyType: EbsiKeyType; kms?: string }, context: IAgentContext<IKeyManager>) {
const { keyOpts, keyType, kms } = args
let privateKeyHex = generateEbsiPrivateKeyHex(
Expand Down Expand Up @@ -117,14 +236,28 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
throw Error(`Not (yet) implemented for the EBSI did provider`)
}

updateIdentifier(
// TODO How does it work? Not inferable from the api: https://hub.ebsi.eu/apis/pilot/did-registry/v5/post-jsonrpc#updatebasedocument
async updateIdentifier(
args: {
did: string
document: Partial<DIDDocument>
options?: { [p: string]: any }
},
context: IAgentContext<IKeyManager>
context: IAgentContext<IKeyManager & IDIDManager>
): Promise<IIdentifier> {
const id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
await updateBaseDocument({
params: [
{
from: args.options?.from ?? 'eth',
did: args.did,
baseDocument:
args.options?.baseDocument ??
JSON.stringify({ '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'] }),
},
],
id,
})
throw Error(`Not (yet) implemented for the EBSI did provider`)
}

Expand Down
23 changes: 10 additions & 13 deletions packages/did-provider-ebsi/src/services/EbsiRPCService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Headers } from 'cross-fetch'
import {
AddVerificationMethodParams,
AddVerificationMethodRelationshipParams, GetDidDocumentParams, GetDidDocumentsParams,
AddVerificationMethodRelationshipParams,
GetDidDocumentParams,
GetDidDocumentsParams,
InsertDidDocumentParams,
SendSignedTransactionParams,
UpdateBaseDocumentParams,
Response, GetDidDocumentsResponse
Response,
GetDidDocumentsResponse,
} from '../types'
import {DIDDocument} from "did-resolver";
import { DIDDocument } from 'did-resolver'

/**
* @constant {string} jsonrpc
Expand Down Expand Up @@ -43,7 +46,7 @@ export const insertDidDocument = async (args: { params: InsertDidDocumentParams[
* "didr_write" scope.
* @param {{ params: UpdateBaseDocumentParams[], id:number }} args
*/
export const updateBaseDocument = async (args: { params: UpdateBaseDocumentParams; id: number }): Promise<Response> => {
export const updateBaseDocument = async (args: { params: UpdateBaseDocumentParams[]; id: number }): Promise<Response> => {
const { params, id } = args
const options = {
method: 'POST',
Expand Down Expand Up @@ -147,14 +150,8 @@ export const getDidDocument = async (args: GetDidDocumentParams): Promise<DIDDoc
*/
export const listDidDocuments = async (args: GetDidDocumentsParams): Promise<GetDidDocumentsResponse> => {
const { offset, size, controller } = args
const query = `?${[
offset && `page[after]=${offset}`,
size && `page[size]=${size}`,
controller && `controller=${controller}`]
.filter(Boolean)
.join('&')}`
const query = `?${[offset && `page[after]=${offset}`, size && `page[size]=${size}`, controller && `controller=${controller}`]
.filter(Boolean)
.join('&')}`
return await (await fetch(`https://api-pilot.ebsi.eu/did-registry/v5/identifiers${query}`)).json()
}

// Fixed the args vs params clash
// Key rotation?
30 changes: 16 additions & 14 deletions packages/did-provider-ebsi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export interface ICreateIdentifierArgs {
methodSpecificId?: string
secp256k1Key?: IKeyOpts
secp256r1Key?: IKeyOpts
from: string
baseDocument?: string
}
}

Expand Down Expand Up @@ -242,7 +244,7 @@ export type ResponseNot200 = {
* @property {string} validAt
*/
export type GetDidDocumentParams = {
did: string;
did: string
validAt?: string
}

Expand All @@ -254,9 +256,9 @@ export type GetDidDocumentParams = {
* @property {string} controller Filter by controller DID.
*/
export type GetDidDocumentsParams = {
offset?: string;
size?: number;
controller?: string;
offset?: string
size?: number
controller?: string
}

/**
Expand All @@ -281,10 +283,10 @@ export type Item = {
* @property {string} last - The link to the last page
*/
export type Links = {
first: string;
prev: string;
next: string;
last: string;
first: string
prev: string
next: string
last: string
}

/**
Expand All @@ -297,11 +299,11 @@ export type Links = {
* @property {Links} links - The links related to pagination
*/
export type GetDidDocumentsResponse = {
self: string;
items: Item[];
total: number;
pageSize: number;
links: Links;
self: string
items: Item[]
total: number
pageSize: number
links: Links
}

export type Response = Response200 | ResponseNot200 | GetDidDocumentsResponse;
export type Response = Response200 | ResponseNot200 | GetDidDocumentsResponse
4 changes: 3 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3c1ef0d

Please sign in to comment.