Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/SDK-8 #27

Merged
merged 37 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
81fff51
feat: Added secp256r1 key to createIdentifier() method
zoemaas Mar 29, 2024
89968e5
chore: Associated the keys with the verification relationship
zoemaas Apr 3, 2024
b988892
refactor: Add trailing new line to types.ts
zoemaas Apr 3, 2024
e2b485b
refactor: Prettier
zoemaas Apr 3, 2024
d97e1eb
chore: Added default verification relationships
zoemaas Apr 3, 2024
0b44f54
chore: Added tests and run prettier
zoemaas Apr 3, 2024
29e4f59
refactor: Simplified structures, extracted more methods and run prettier
zoemaas Apr 4, 2024
99e8f39
Merge branch 'develop' of github.com:Sphereon-Opensource/SSI-SDK-cryp…
zoemaas Apr 5, 2024
b0ac3b5
feat: Implemented conversion of public keys, rpc service and document…
zoemaas Apr 5, 2024
36dc9b7
chore: Implemented the getDidDocument and listDids methods
zoemaas Apr 9, 2024
3c1ef0d
feat: Implemented integration of the ebsi rpc service with the ebsi d…
zoemaas Apr 11, 2024
1d2e04d
fix: Bugfix creating eth transactions
zoemaas Apr 12, 2024
35f1f6b
refactor: Added missing notBefore and notAfter properties to options
zoemaas Apr 17, 2024
32f3572
Merge branch 'develop' of github.com:Sphereon-Opensource/SSI-SDK-cryp…
zoemaas May 1, 2024
c91fdf2
refactor: Fixed merging issues
zoemaas May 1, 2024
34b8d43
refactor: Made token an argument of rpc functions
zoemaas May 1, 2024
5d672b2
refactor: Refactored the formatEbsiPublicKey function
zoemaas May 1, 2024
12c1a14
refactor: Refactored how the RPC url is built
zoemaas May 1, 2024
9ccacbf
refactor: Extracted a method from to build/populate fetch options
zoemaas May 1, 2024
74a1589
refactor: Extracted nested string interpolation into ternary
zoemaas May 1, 2024
317ac2d
refactor: Extracted multiple if clauses to build the query string
zoemaas May 1, 2024
137504f
refactor: Added query url to getUrls method
zoemaas May 1, 2024
725dc37
refactor: Run prettier
zoemaas May 1, 2024
773726f
refactor: Removed the jose dependency
zoemaas May 1, 2024
7b141f8
refactor: updated pnpm-lock.yaml
zoemaas May 1, 2024
dace576
refactor: Added comment about the conversion of JWK in hex string.
zoemaas May 10, 2024
615eb48
refactor: Renamed function
zoemaas May 10, 2024
a81ebfe
refactor: extracted interface from params of method createEbsiDid
zoemaas May 10, 2024
f2efdcd
refactor: extracted interface from params of method updateIdentifier
zoemaas May 10, 2024
9b2b5b7
refactor: extracted constant baseDocument
zoemaas May 10, 2024
b2d685e
refactor: removed the implementation of updateIdentifier method and t…
zoemaas May 10, 2024
bb5e60f
refactor: added new keyword to Errors that might be thrown
zoemaas May 10, 2024
38a486f
refactor: moved Veramo unrelated methods to functions.ts
zoemaas May 10, 2024
60602c4
refactor: Renamed checkPresent function to assertPresent
zoemaas May 10, 2024
1c7680b
refactor: Refactored the EBSI RPC methods
zoemaas May 10, 2024
dbb51ef
refactor: Removed unused dependency
zoemaas May 10, 2024
07d320a
fix: Fixed broken tests
zoemaas May 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/did-provider-ebsi/__tests__/EbsiDidProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const ebsiDidProvider = new EbsiDidProvider({
defaultKms: 'mem',
})

jest.mock('../src/services/EbsiRPCService', () => ({
...jest.requireActual('../src/services/EbsiRPCService'),
callRpcMethod: jest.fn().mockResolvedValue({ result: { r: '', s: '', v: '' } }),
}))

const agent = createAgent<IKeyManager & IDIDManager>({
plugins: [
new SphereonKeyManager({
Expand Down
5 changes: 4 additions & 1 deletion packages/did-provider-ebsi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
},
"dependencies": {
"@ethersproject/random": "^5.7.0",
"@sphereon/ssi-sdk-ext.did-resolver-ebsi": "workspace:*",
"@sphereon/ssi-sdk-ext.did-resolver-ebsi": "workspace:^",
"@sphereon/ssi-sdk-ext.key-utils": "workspace:^",
"@veramo/core": "4.2.0",
"@veramo/did-manager": "4.2.0",
"@veramo/did-provider-key": "4.2.0",
"cross-fetch": "^4.0.0",
"debug": "^4.3.4",
"did-resolver": "^4.1.0",
"ethers": "^6.11.1",
"multiformats": "9.9.0",
"uint8arrays": "3.1.1"
},
Expand Down
147 changes: 40 additions & 107 deletions packages/did-provider-ebsi/src/EbsiDidProvider.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
import { IAgentContext, IIdentifier, IKeyManager, MinimalImportableKey } from '@veramo/core'
import { IAgentContext, IDIDManager, IIdentifier, IKeyManager } 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, generateMethodSpecificId } from './functions'
import { ApiOpts, ebsiDIDSpecInfo, IContext, ICreateIdentifierArgs, UpdateIdentifierParams } from './types'
import { createEbsiDid, generateEbsiKeyPair, generateMethodSpecificId } from './functions'

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

export class EbsiDidProvider extends AbstractIdentifierProvider {
private readonly defaultKms?: string
private readonly apiOpts?: ApiOpts

constructor(options: { defaultKms?: string }) {
constructor(options: { defaultKms?: string; apiOpts?: ApiOpts }) {
super()
this.defaultKms = options.defaultKms
this.apiOpts = options.apiOpts
}

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(
const secp256k1ManagedKeyInfo = await generateEbsiKeyPair(
{
keyOpts: options?.secp256k1Key,
keyType: 'Secp256k1',
kms,
kms: kms ?? this.defaultKms,
},
context
)
const secp256r1ManagedKeyInfo = await this.generateEbsiKeyPair(
const secp256r1ManagedKeyInfo = await generateEbsiKeyPair(
{
keyOpts: options?.secp256r1Key,
keyType: 'Secp256r1',
kms,
kms: kms ?? this.defaultKms,
},
context
)
Expand All @@ -45,28 +46,32 @@ 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 createEbsiDid(
{
identifier,
secp256k1ManagedKeyInfo,
secp256r1ManagedKeyInfo,
id,
from: options.from,
notBefore: options.notBefore,
notAfter: options.notAfter,
apiOpts: options.apiOpts ?? this.apiOpts,
},
context
)

debug('Created', identifier.did)
return identifier
} else if (type === ebsiDIDSpecInfo.KEY) {
throw Error(`Type ${type} not supported. Please use @sphereon/ssi-sdk-ext.did-provider-key for Natural Person EBSI DIDs`)
}
throw Error(`Type ${type} not supported`)
}

private async generateEbsiKeyPair(args: { keyOpts?: IKeyOpts; keyType: EbsiKeyType; kms?: string }, context: IAgentContext<IKeyManager>) {
const { keyOpts, keyType, kms } = args
let privateKeyHex = generateEbsiPrivateKeyHex(
ebsiDIDSpecInfo.V1,
keyOpts?.privateKeyHex ? u8a.fromString(keyOpts.privateKeyHex, 'base16') : undefined
)
if (privateKeyHex.startsWith('0x')) {
privateKeyHex = privateKeyHex.substring(2)
throw new Error(`Type ${type} not supported. Please use @sphereon/ssi-sdk-ext.did-provider-key for Natural Person EBSI DIDs`)
}
if (!privateKeyHex || privateKeyHex.length !== 64) {
throw Error('Private key should be 32 bytes / 64 chars hex')
}
const importableKey = this.assertedKey({ key: { ...keyOpts, privateKeyHex }, type: keyType, kms })
return await context.agent.keyManagerImport(importableKey)
throw new Error(`Type ${type} not supported`)
}

addKey(
Expand All @@ -77,7 +82,7 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
},
context: IAgentContext<IKeyManager>
): Promise<any> {
throw Error(`Not (yet) implemented for the EBSI did provider`)
throw new Error(`Not (yet) implemented for the EBSI did provider`)
}

addService(
Expand All @@ -88,7 +93,7 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
},
context: IAgentContext<IKeyManager>
): Promise<any> {
throw Error(`Not (yet) implemented for the EBSI did provider`)
throw new Error(`Not (yet) implemented for the EBSI did provider`)
}

deleteIdentifier(args: IIdentifier, context: IAgentContext<IKeyManager>): Promise<boolean> {
Expand All @@ -103,7 +108,7 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
},
context: IAgentContext<IKeyManager>
): Promise<any> {
throw Error(`Not (yet) implemented for the EBSI did provider`)
throw new Error(`Not (yet) implemented for the EBSI did provider`)
}

removeService(
Expand All @@ -114,83 +119,11 @@ export class EbsiDidProvider extends AbstractIdentifierProvider {
},
context: IAgentContext<IKeyManager>
): Promise<any> {
throw Error(`Not (yet) implemented for the EBSI did provider`)
}

updateIdentifier(
args: {
did: string
document: Partial<DIDDocument>
options?: { [p: string]: any }
},
context: IAgentContext<IKeyManager>
): Promise<IIdentifier> {
throw Error(`Not (yet) implemented for the EBSI did provider`)
}

private assertedKey = (args: { key?: IKeyOpts; type: EbsiKeyType; kms?: string }): MinimalImportableKey => {
const { key, type, kms } = args
const minimalImportableKey: Partial<MinimalImportableKey> = { ...key } ?? {}
minimalImportableKey.kms = this.assertedKms(kms)
minimalImportableKey.type = this.setDefaultKeyType({ key, type })
minimalImportableKey.meta = { purposes: this.assertedPurposes({ key }) ?? this.setDefaultPurposes({ key, type }) }
return minimalImportableKey as MinimalImportableKey
}

private assertedKms(kms?: string) {
const result = kms ?? this.defaultKms
if (!!result) {
return result
}
throw Error('no KMS supplied')
}

private setDefaultKeyType = (args: { key?: IKeyOpts; type: EbsiKeyType }): EbsiKeyType => {
if (!args.key?.type) {
return args.type
}
return args.key.type
}

private assertedPurposes = (args: { key?: IKeyOpts }): EbsiPublicKeyPurpose[] | undefined => {
const { key } = args
if (key?.purposes && key.purposes.length > 0) {
switch (key.type) {
case 'Secp256k1': {
if (key?.purposes && key.purposes.length > 0 && key.purposes?.includes(EbsiPublicKeyPurpose.CapabilityInvocation)) {
return key.purposes
}
throw new Error(`Secp256k1 key requires ${EbsiPublicKeyPurpose.CapabilityInvocation} purpose`)
}
case 'Secp256r1': {
if (
key?.purposes &&
key.purposes.length > 0 &&
key.purposes.every((purpose) => [EbsiPublicKeyPurpose.AssertionMethod, EbsiPublicKeyPurpose.Authentication].includes(purpose))
) {
return key.purposes
}
throw new Error(`Secp256r1 key requires ${[EbsiPublicKeyPurpose.AssertionMethod, EbsiPublicKeyPurpose.Authentication].join(', ')} purposes`)
}
default:
throw new Error(`Unsupported key type: ${key.type}`)
}
}
return key?.purposes
throw new Error(`Not (yet) implemented for the EBSI did provider`)
}

private setDefaultPurposes = (args: { key?: IKeyOpts; type: EbsiKeyType }): EbsiPublicKeyPurpose[] => {
const { key, type } = args
if (!key?.purposes || key.purposes.length === 0) {
switch (type) {
case 'Secp256k1':
return [EbsiPublicKeyPurpose.CapabilityInvocation]
case 'Secp256r1':
return [EbsiPublicKeyPurpose.AssertionMethod, EbsiPublicKeyPurpose.Authentication]
default:
throw new Error(`Unsupported key type: ${key?.type}`)
}
}
return key.purposes
// TODO How does it work? Not inferable from the api: https://hub.ebsi.eu/apis/pilot/did-registry/v5/post-jsonrpc#updatebasedocument
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with not inferable? Of course I know what the did is. Also this is an optional method to implement once you want to work with passing in updated (partial) did documents. The regular way would be the add/remove service endpoint and add/remove keys methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will actually change in the did? That's the request body:

{
  "jsonrpc": "2.0",
  "method": "updateBaseDocument",
  "params": [
    {
      "from": "0xc9765eC58E49e6E386549CEAA34FB0d8bB69C320",
      "did": "did:ebsi:z23eve8qDNzYuNVqfSaErpuJ",
      "baseDocument": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/suites/jws-2020/v1\"]}"
    }
  ],
  "id": 524
}

And there is no did document.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thenit is likely this method should not have been implemented to begin with and throw an error instead. Veramo uses add and delete services and keys methods to manage updates to the identifier and did document. It recently added a method to pass in a new (partial) did document. If however a did provider cannot work with that it should throw an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just throwing an error then

async updateIdentifier(args: UpdateIdentifierParams, context: IAgentContext<IKeyManager & IDIDManager>): Promise<IIdentifier> {
throw new Error(`Not (yet) implemented for the EBSI did provider`)
}
}
Loading