Skip to content

Commit

Permalink
Merge pull request #58 from Sphereon-Opensource/develop
Browse files Browse the repository at this point in the history
New release
  • Loading branch information
nklomp authored Dec 5, 2024
2 parents 347d6ca + ae9e285 commit 940bd4e
Show file tree
Hide file tree
Showing 114 changed files with 8,183 additions and 4,823 deletions.
5 changes: 3 additions & 2 deletions packages/did-provider-jwk/src/jwk-did-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const debug = Debug('sphereon:did-provider-jwk')
* @public
*/
export class JwkDIDProvider extends AbstractIdentifierProvider {
private readonly defaultKms: string
private readonly defaultKms?: string

constructor(options: { defaultKms: string }) {
constructor(options: { defaultKms?: string }) {
super()
this.defaultKms = options.defaultKms
}
Expand All @@ -23,6 +23,7 @@ export class JwkDIDProvider extends AbstractIdentifierProvider {
async createIdentifier(args: ICreateIdentifierArgs, context: IRequiredContext): Promise<Omit<IIdentifier, 'provider'>> {
const key = await importProvidedOrGeneratedKey(
{
// @ts-ignore
kms: args.kms ?? this.defaultKms,
alias: args.alias,
options: args.options,
Expand Down
61 changes: 59 additions & 2 deletions packages/did-provider-key/__tests__/key-did-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,73 @@ describe('@sphereon/did-provider-key', () => {
expect(identifier.keys.length).toBe(1)
})

it('should create consistent identifier with provided key', async () => {
it('should create identifier without provided key Ed25519', async () => {
const options = {
type: Key.Ed25519,
}
const identifier: IIdentifier = await agent.didManagerCreate({ options })

expect(identifier).toBeDefined()
expect(identifier.did).toBeDefined()
})

it('should create identifier without provided key Secp256r1', async () => {
const options = {
type: Key.Secp256r1,
}
const identifier: IIdentifier = await agent.didManagerCreate({ options })

expect(identifier).toBeDefined()
expect(identifier.did).toBeDefined()
})

it('should create identifier without provided key Secp256k1', async () => {
const options = {
type: Key.Secp256k1,
}
const identifier: IIdentifier = await agent.didManagerCreate({ options })

expect(identifier).toBeDefined()
expect(identifier.did).toBeDefined()
})

it('should create consistent identifier with provided key ed25519', async () => {
const options = {
type: Key.Ed25519,
key: {
privateKeyHex: PRIVATE_KEY_HEX + PRIVATE_KEY_HEX,
},
}
const identifier: IIdentifier = await agent.didManagerCreate({ options })

expect(identifier).toBeDefined()
expect(identifier.did).toBe('did:key:z6MknvX3iMSuMSMCebC4Z7Cve4u7p7VdfTShx93b8nCff3c6')
})

it('should create consistent identifier with provided key Secp256r1', async () => {
const options = {
type: Key.Secp256r1,
key: {
privateKeyHex: PRIVATE_KEY_HEX,
},
}
const identifier: IIdentifier = await agent.didManagerCreate({ options })

expect(identifier).toBeDefined()
expect(identifier.did).toBe('did:key:zDnaeZqjqUtYuYakaWXGb9VRSukEn5rcAuFfteLgzumPNNZfN')
})

it('should create consistent identifier with provided key Secp256k1', async () => {
const options = {
type: Key.Secp256k1,
key: {
privateKeyHex: PRIVATE_KEY_HEX,
},
}
const identifier: IIdentifier = await agent.didManagerCreate({ options })

expect(identifier).toBeDefined()
expect(identifier.did).toBe('did:key:zQ3shqZQs23rWENxtomyw4BNz1p23AkbjzwdeYg6DpmhWDDE6')
expect(identifier.did).toBe('did:key:zQ3shqZQs23rWENxtomyw4BNz1p23AkbjzwdeYg6DpmhWDDE6')
})

it('should remove identifier', async () => {
Expand Down
26 changes: 19 additions & 7 deletions packages/did-provider-key/src/SphereonKeyDidProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
JwkKeyUse,
TKeyType,
toJwk,
toRawCompressedHexPublicKey,
} from '@sphereon/ssi-sdk-ext.key-utils'
import { IAgentContext, IIdentifier, IKey, IKeyManager, IService } from '@veramo/core'
import { AbstractIdentifierProvider } from '@veramo/did-manager'
Expand All @@ -29,9 +30,9 @@ const keyCodecs = {
} as const

export class SphereonKeyDidProvider extends AbstractIdentifierProvider {
private readonly kms: string
private readonly kms?: string

constructor(options: { defaultKms: string }) {
constructor(options: { defaultKms?: string }) {
super()
this.kms = options.defaultKms
}
Expand All @@ -48,6 +49,7 @@ export class SphereonKeyDidProvider extends AbstractIdentifierProvider {
type?: TKeyType
codecName?: 'EBSI' | 'jwk_jcs-pub' | Multicodec.CodecName
key?: {
type?: Exclude<TKeyType, 'Secp384r1' | 'Secp521r1'>
privateKeyHex: string
}
}
Expand All @@ -57,18 +59,26 @@ export class SphereonKeyDidProvider extends AbstractIdentifierProvider {
let codecName = (options?.codecName?.toUpperCase() === 'EBSI' ? (JWK_JCS_PUB_NAME as Multicodec.CodecName) : options?.codecName) as
| CodeNameType
| undefined
const keyType: TKeyType = options?.type ?? (codecName === JWK_JCS_PUB_NAME ? 'Secp256r1' : 'Secp256k1')
const keyType = (options?.type ?? options?.key?.type ?? (codecName === JWK_JCS_PUB_NAME ? 'Secp256r1' : 'Secp256k1')) as Exclude<
TKeyType,
'Secp384r1' | 'Secp521r1'
>
// console.log(`keytype: ${keyType}, codecName: ${codecName}`)

const key = await importProvidedOrGeneratedKey({
const key = await importProvidedOrGeneratedKey(
{
// @ts-ignore
kms: kms ?? this.kms,
alias: alias,
options: { ...options, type: keyType },
},
context,
context
)

let methodSpecificId: string | undefined

// did:key uses compressed pub keys
const compressedPublicKeyHex = toRawCompressedHexPublicKey(u8a.fromString(key.publicKeyHex, 'hex'), key.type)
if (codecName === JWK_JCS_PUB_NAME) {
const jwk = toJwk(key.publicKeyHex, keyType, { use: JwkKeyUse.Signature, key, noKidThumbprint: true })
// console.log(`FIXME JWK: ${JSON.stringify(toJwk(privateKeyHex, keyType, { use: JwkKeyUse.Signature, key, isPrivateKey: true }), null, 2)}`)
Expand All @@ -77,15 +87,17 @@ export class SphereonKeyDidProvider extends AbstractIdentifierProvider {
)
} else if (codecName) {
methodSpecificId = u8a.toString(
Multibase.encode('base58btc', Multicodec.addPrefix(codecName as Multicodec.CodecName, u8a.fromString(key.publicKeyHex, 'hex')))
Multibase.encode('base58btc', Multicodec.addPrefix(codecName as Multicodec.CodecName, u8a.fromString(compressedPublicKeyHex, 'hex')))
)
} else {
codecName = keyCodecs[keyType]

if (codecName) {
// methodSpecificId = bytesToMultibase({bytes: u8a.fromString(key.publicKeyHex, 'hex'), codecName})
methodSpecificId = u8a
.toString(Multibase.encode('base58btc', Multicodec.addPrefix(codecName as Multicodec.CodecName, u8a.fromString(key.publicKeyHex, 'hex'))))
.toString(
Multibase.encode('base58btc', Multicodec.addPrefix(codecName as Multicodec.CodecName, u8a.fromString(compressedPublicKeyHex, 'hex')))
)
.toString()
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/did-provider-oyd/src/oyd-did-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ type IContext = IAgentContext<IKeyManager>
* @public
*/
export class OydDIDProvider extends AbstractIdentifierProvider {
private defaultKms: string
private defaultKms?: string

constructor(options: { defaultKms: string }) {
constructor(options: { defaultKms?: string }) {
super()
this.defaultKms = options.defaultKms
}
Expand Down Expand Up @@ -48,6 +48,7 @@ export class OydDIDProvider extends AbstractIdentifierProvider {
const keyType: OydDidSupportedKeyTypes = options?.keyType || 'Ed25519'
const key = await this.holdKeys(
{
// @ts-ignore
kms: kms || this.defaultKms,
options: {
keyType,
Expand Down Expand Up @@ -102,6 +103,7 @@ export class OydDIDProvider extends AbstractIdentifierProvider {
private async holdKeys(args: OydDidHoldKeysArgs, context: IContext): Promise<IKey> {
if (args.options.privateKeyHex) {
return context.agent.keyManagerImport({
// @ts-ignore
kms: args.kms || this.defaultKms,
type: args.options.keyType,
kid: args.options.kid,
Expand All @@ -113,6 +115,7 @@ export class OydDIDProvider extends AbstractIdentifierProvider {
}
return context.agent.keyManagerCreate({
type: args.options.keyType,
// @ts-ignore
kms: args.kms || this.defaultKms,
meta: {
algorithms: ['Ed25519'],
Expand Down
76 changes: 38 additions & 38 deletions packages/did-utils/src/did-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getKms,
JwkKeyUse,
keyTypeFromCryptographicSuite,
sanitizedJwk,
signatureAlgorithmFromKey,
TKeyType,
toJwk,
Expand Down Expand Up @@ -204,12 +205,12 @@ export const getPrimaryIdentifier = async (context: IAgentContext<IDIDManager>,
}

export const createIdentifier = async (context: IAgentContext<IDIDManager>, opts?: CreateIdentifierOpts): Promise<IIdentifier> => {
return await context.agent.didManagerCreate({
kms: await getKms(context, opts?.createOpts?.kms),
...(opts?.method && {provider: `${DID_PREFIX}${opts?.method}`}),
alias: opts?.createOpts?.alias ?? `${IdentifierAliasEnum.PRIMARY}-${opts?.method}-${opts?.createOpts?.options?.type}-${new Date().toUTCString()}`,
options: opts?.createOpts?.options,
})
return await context.agent.didManagerCreate({
kms: await getKms(context, opts?.createOpts?.kms),
...(opts?.method && { provider: `${DID_PREFIX}${opts?.method}` }),
alias: opts?.createOpts?.alias ?? `${IdentifierAliasEnum.PRIMARY}-${opts?.method}-${opts?.createOpts?.options?.type}-${new Date().getTime()}`,
options: opts?.createOpts?.options,
})
}

export const getFirstKeyWithRelationFromDIDDoc = async (
Expand Down Expand Up @@ -344,7 +345,7 @@ export function jwkTtoPublicKeyHex(jwk: JWK): string {
// todo: Hacky way to convert this to a VM. Should extract the logic from the below methods
// @ts-ignore
const vm: _ExtendedVerificationMethod = {
publicKeyJwk: jwk,
publicKeyJwk: sanitizedJwk(jwk),
}
return extractPublicKeyHexWithJwkSupport(vm)
}
Expand All @@ -360,24 +361,28 @@ export function jwkTtoPublicKeyHex(jwk: JWK): string {
*/
export function extractPublicKeyHexWithJwkSupport(pk: _ExtendedVerificationMethod, convert = false): string {
if (pk.publicKeyJwk) {
if (pk.publicKeyJwk.kty === 'EC') {
const curve = pk.publicKeyJwk.crv ? toEcLibCurve(pk.publicKeyJwk.crv): 'p256'
const ec = new elliptic.ec(curve)

const xHex = base64ToHex(pk.publicKeyJwk.x!, 'base64url')
const yHex = base64ToHex(pk.publicKeyJwk.y!, 'base64url')
const jwk = sanitizedJwk(pk.publicKeyJwk)
if (jwk.kty === 'EC') {
const curve = jwk.crv ? toEcLibCurve(jwk.crv) : 'p256'
const xHex = base64ToHex(jwk.x!, 'base64url')
const yHex = base64ToHex(jwk.y!, 'base64url')
const prefix = '04' // isEven(yHex) ? '02' : '03'
// Uncompressed Hex format: 04<x><y>
// Compressed Hex format: 02<x> (for even y) or 03<x> (for uneven y)
const hex = `${prefix}${xHex}${yHex}`
// We return directly as we don't want to convert the result back into Uint8Array and then convert again to hex as the elliptic lib already returns hex strings
const publicKeyHex = ec.keyFromPublic(hex, 'hex').getPublic(true, 'hex')
// This returns a short form (x) with 02 or 03 prefix
return publicKeyHex
} else if (pk.publicKeyJwk.crv === 'Ed25519') {
return u8a.toString(u8a.fromString(pk.publicKeyJwk.x!, 'base64url'), 'base16')
} else if (pk.publicKeyJwk.kty === 'RSA') {
return hexKeyFromPEMBasedJwk(pk.publicKeyJwk, 'public')
try {
const ec = new elliptic.ec(curve)
// We return directly as we don't want to convert the result back into Uint8Array and then convert again to hex as the elliptic lib already returns hex strings
const publicKeyHex = ec.keyFromPublic(hex, 'hex').getPublic(true, 'hex')
// This returns a short form (x) with 02 or 03 prefix
return publicKeyHex
} catch (error: any) {
console.error(`Error converting EC with elliptic lib curve ${curve} from JWK to hex. x: ${jwk.x}, y: ${jwk.y}, error: ${error}`, error)
}
} else if (jwk.crv === 'Ed25519') {
return u8a.toString(u8a.fromString(jwk.x!, 'base64url'), 'base16')
} else if (jwk.kty === 'RSA') {
return hexKeyFromPEMBasedJwk(jwk, 'public')
}
}
// delegate the other types to the original Veramo function
Expand All @@ -404,15 +409,16 @@ interface LegacyVerificationMethod extends VerificationMethod {
*/
export function extractPublicKeyHex(pk: _ExtendedVerificationMethod, convert: boolean = false): string {
let keyBytes = extractPublicKeyBytes(pk)
const jwk = pk.publicKeyJwk ? sanitizedJwk(pk.publicKeyJwk) : undefined
if (convert) {
if (
['Ed25519', 'Ed25519VerificationKey2018', 'Ed25519VerificationKey2020'].includes(pk.type) ||
(pk.type === 'JsonWebKey2020' && pk.publicKeyJwk?.crv === 'Ed25519')
(pk.type === 'JsonWebKey2020' && jwk?.crv === 'Ed25519')
) {
keyBytes = convertPublicKeyToX25519(keyBytes)
} else if (
!['X25519', 'X25519KeyAgreementKey2019', 'X25519KeyAgreementKey2020'].includes(pk.type) &&
!(pk.type === 'JsonWebKey2020' && pk.publicKeyJwk?.crv === 'X25519')
!(pk.type === 'JsonWebKey2020' && jwk?.crv === 'X25519')
) {
return ''
}
Expand All @@ -421,7 +427,7 @@ export function extractPublicKeyHex(pk: _ExtendedVerificationMethod, convert: bo
}

function toEcLibCurve(input: string) {
return input.toLowerCase().replace("-", "").replace("_", "")
return input.toLowerCase().replace('-', '').replace('_', '')
}

function extractPublicKeyBytes(pk: VerificationMethod): Uint8Array {
Expand All @@ -434,14 +440,8 @@ function extractPublicKeyBytes(pk: VerificationMethod): Uint8Array {
} else if (pk.publicKeyHex) {
return hexToBytes(pk.publicKeyHex)
} else if (pk.publicKeyJwk?.crv && pk.publicKeyJwk.x && pk.publicKeyJwk.y) {
const secp = new elliptic.ec(toEcLibCurve(pk.publicKeyJwk.crv))
return hexToBytes(
secp
.keyFromPublic({
x: base64ToHex(pk.publicKeyJwk.x, 'base64url'),
y: base64ToHex(pk.publicKeyJwk.y, 'base64url'),
})
.getPublic('hex')
extractPublicKeyHexWithJwkSupport(pk)
)
} else if (pk.publicKeyJwk && (pk.publicKeyJwk.crv === 'Ed25519' || pk.publicKeyJwk.crv === 'X25519') && pk.publicKeyJwk.x) {
return base64ToBytes(pk.publicKeyJwk.x)
Expand All @@ -453,25 +453,25 @@ export function verificationMethodToJwk(vm: VerificationMethod): JWK {
let jwk: JWK | undefined = vm.publicKeyJwk as JWK
if (!jwk) {
let publicKeyHex = vm.publicKeyHex ?? u8a.toString(extractPublicKeyBytes(vm), 'hex')
jwk = toJwk(publicKeyHex, keyTypeFromCryptographicSuite({suite: vm.type}))
jwk = toJwk(publicKeyHex, keyTypeFromCryptographicSuite({crv: vm.type}))
}
if (!jwk) {
throw Error(`Could not convert verification method to jwk`)
}
jwk.kid = vm.id
return jwk
return sanitizedJwk(jwk)
}

function didDocumentSectionToJwks(
didDocumentSection: DIDDocumentSection,
searchForVerificationMethods?: (VerificationMethod | string)[],
verificationMethods?: VerificationMethod[]
) {
const jwks = (searchForVerificationMethods ?? [])
const jwks = new Set((searchForVerificationMethods ?? [])
.map((vmOrId) => (typeof vmOrId === 'object' ? vmOrId : verificationMethods?.find((vm) => vm.id === vmOrId)))
.filter(isDefined)
.map((vm) => verificationMethodToJwk(vm))
return {didDocumentSection, jwks: jwks}
.map((vm) => verificationMethodToJwk(vm)))
return {didDocumentSection, jwks: Array.from(jwks)}
}

export type DidDocumentJwks = Record<Exclude<DIDDocumentSection, 'publicKey' | 'service'>, Array<JWK>>
Expand Down Expand Up @@ -542,8 +542,8 @@ export async function mapIdentifierKeysToDocWithJwkSupport(
const extendedKeys: _ExtendedIKey[] = documentKeys
.map((verificationMethod) => {
/*if (verificationMethod.type !== 'JsonWebKey2020') {
return null
}*/
return null
}*/
const localKey = localKeys.find(
(localKey) =>
localKey.publicKeyHex === verificationMethod.publicKeyHex ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const setup = async (): Promise<boolean> => {
defaultProvider: DID_METHOD,
store: new MemoryDIDStore(),
}),
new IdentifierResolution({ crypto: global.crypto }),
new IdentifierResolution(),
],
})
agent = localAgent
Expand Down
2 changes: 1 addition & 1 deletion packages/identifier-resolution/__tests__/restAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const setup = async (): Promise<boolean> => {
defaultProvider: DID_METHOD,
store: new MemoryDIDStore(),
}),
new IdentifierResolution({ crypto: global.crypto }),
new IdentifierResolution(),
],
})

Expand Down
Loading

0 comments on commit 940bd4e

Please sign in to comment.