Skip to content

Commit

Permalink
feat: add Multikey support (#1316)
Browse files Browse the repository at this point in the history
* feat: add support for Multikey

* upgrade did:peer provider
* upgrade did-jwt to v8
* simplify key extraction, and key comparison code
* Multikey / JsonWebKey2020 processing is done by did-jwt
  • Loading branch information
mirceanis authored Jan 19, 2024
1 parent ade95c7 commit 165de35
Show file tree
Hide file tree
Showing 20 changed files with 5,096 additions and 14,182 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
"caip": "1.1.0",
"credential-status": "2.0.6",
"cross-env": "7.0.3",
"did-jwt": "7.4.2",
"did-jwt-vc": "3.2.11",
"did-jwt": "8.0.0",
"did-jwt-vc": "4.0.0",
"did-resolver": "4.1.0",
"ethers": "6.9.0",
"ethr-did-resolver": "10.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dependencies": {
"credential-status": "^2.0.5",
"debug": "^4.3.3",
"did-jwt-vc": "^3.2.10",
"did-jwt-vc": "^4.0.0",
"did-resolver": "^4.1.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/credential-ld/src/suites/Ed25519Signature2020.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class VeramoEd25519Signature2020 extends VeramoLdSignature {
;(vm as any)['@context'] = 'https://w3id.org/security/suites/ed25519-2020/v1'
// publicKeyMultibase is required by this suite
if (!vm.publicKeyMultibase) {
const publicKeyHex = extractPublicKeyHex(vm)
const { publicKeyHex } = extractPublicKeyHex(vm)
vm.publicKeyMultibase = bytesToMultibase(hexToBytes(publicKeyHex), 'base58btc', 'ed25519-pub')
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/credential-status/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@veramo/core-types": "workspace:^",
"@veramo/utils": "workspace:^",
"credential-status": "^2.0.5",
"did-jwt": "^7.4.1",
"did-jwt": "^8.0.0",
"did-resolver": "^4.1.0"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/credential-w3c/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@veramo/utils": "workspace:^",
"canonicalize": "^2.0.0",
"debug": "^4.3.3",
"did-jwt": "^7.4.1",
"did-jwt-vc": "^3.2.10",
"did-jwt": "^8.0.0",
"did-jwt-vc": "^4.0.0",
"did-resolver": "^4.1.0",
"uuid": "^9.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/data-store-json/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@veramo/key-manager": "workspace:^",
"@veramo/utils": "workspace:^",
"debug": "^4.3.3",
"did-jwt-vc": "^3.2.10",
"did-jwt-vc": "^4.0.0",
"uuid": "^9.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/data-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@veramo/key-manager": "workspace:^",
"@veramo/utils": "workspace:^",
"debug": "^4.3.3",
"did-jwt-vc": "^3.2.10",
"did-jwt-vc": "^4.0.0",
"typeorm": "^0.3.17",
"uuid": "^9.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/did-comm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@veramo/mediation-manager": "workspace:^",
"cross-fetch": "^4.0.0",
"debug": "^4.3.3",
"did-jwt": "^7.4.1",
"did-jwt": "^8.0.0",
"did-resolver": "^4.1.0",
"isomorphic-webcrypto": "^2.3.8",
"uuid": "^9.0.0"
Expand Down
14 changes: 12 additions & 2 deletions packages/did-comm/src/didcomm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
decodeJoseBlob,
dereferenceDidKeys,
encodeJoseBlob,
extractPublicKeyHex,
hexToBytes,
isDefined,
mapIdentifierKeysToDoc,
Expand Down Expand Up @@ -132,7 +133,6 @@ export interface DIDCommConfig<T extends IDIDCommTransport = DIDCommHttpTranspor
transports?: T[]
}


/**
* DID Comm plugin for {@link @veramo/core#Agent}
*
Expand Down Expand Up @@ -303,6 +303,7 @@ export class DIDComm implements IAgentPlugin {
interface IRecipient {
kid: string
publicKeyBytes: Uint8Array
keyType: string
}

let recipients: IRecipient[] = []
Expand All @@ -324,7 +325,16 @@ export class DIDComm implements IAgentPlugin {

// 2.3 get public key bytes and key IDs for supported recipient keys
const tempRecipients = keyAgreementKeys
.map((pk) => ({ kid: pk.id, publicKeyBytes: hexToBytes(pk.publicKeyHex!) }))
.map((pk) => {
// FIXME: only supporting X25519 keys for now. Add support for P-256 and P-384 & others
const { publicKeyHex, keyType } = extractPublicKeyHex(pk, true)
if (keyType === 'X25519') {
return { kid: pk.id, publicKeyBytes: hexToBytes(publicKeyHex), keyType: pk.type }
} else {
debug(`not_supported: key agreement key type ${pk.type} is not supported for encryption`)
return null
}
})
.filter(isDefined)

if (tempRecipients.length === 0) {
Expand Down
12 changes: 2 additions & 10 deletions packages/did-comm/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,10 @@ export async function extractSenderEncryptionKey(
didUrl: protectedHeader.skid,
section: 'keyAgreement',
})) as _ExtendedVerificationMethod
if (
![
'Ed25519VerificationKey2018',
'X25519KeyAgreementKey2019',
'JsonWebKey2020',
'Ed25519VerificationKey2020',
'X25519KeyAgreementKey2020',
].includes(sKey.type)
) {
let { publicKeyHex, keyType } = extractPublicKeyHex(sKey, true)
if (keyType !== 'X25519') {
throw new Error(`not_supported: sender key of type ${sKey.type} is not supported`)
}
let publicKeyHex = extractPublicKeyHex(sKey, true)
senderKey = hexToBytes(publicKeyHex)
}
return senderKey
Expand Down
2 changes: 1 addition & 1 deletion packages/did-jwt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@veramo/core-types": "workspace:^",
"@veramo/message-handler": "workspace:^",
"debug": "^4.3.3",
"did-jwt": "^7.4.1",
"did-jwt": "^8.0.0",
"did-resolver": "^4.1.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/did-provider-peer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"extract-api": "node ../cli/bin/veramo.js dev extract-api"
},
"dependencies": {
"@aviarytech/did-peer": "^0.0.21",
"@aviarytech/did-peer": "^0.0.22",
"@veramo/core-types": "workspace:^",
"@veramo/did-manager": "workspace:^",
"@veramo/utils": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/key-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@veramo/core-types": "workspace:^",
"@veramo/utils": "workspace:^",
"debug": "^4.3.4",
"did-jwt": "^7.4.1",
"did-jwt": "^8.0.0",
"ethers": "^6.9.0",
"uint8arrays": "^4.0.6",
"uuid": "^9.0.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/kms-local/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@veramo/key-manager": "workspace:^",
"@veramo/utils": "workspace:^",
"debug": "^4.3.3",
"did-jwt": "^7.4.1",
"did-jwt": "^8.0.0",
"ethers": "^6.9.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/selective-disclosure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@veramo/message-handler": "workspace:^",
"@veramo/utils": "workspace:^",
"debug": "^4.3.3",
"did-jwt": "^7.4.1",
"did-jwt": "^8.0.0",
"uuid": "^9.0.0"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"credential-status": "^2.0.5",
"cross-fetch": "^4.0.0",
"debug": "^4.3.3",
"did-jwt": "^7.4.1",
"did-jwt-vc": "^3.2.10",
"did-jwt": "^8.0.0",
"did-jwt-vc": "^4.0.0",
"did-resolver": "^4.1.0",
"ethers": "^6.9.0",
"ipfs-unixfs": "^11.1.0",
Expand Down
14 changes: 7 additions & 7 deletions packages/utils/src/__tests__/did-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,41 +75,41 @@ describe('@veramo/utils did utils', () => {
// let expectedMultibase = `z8FRmkyRH9xAsLCk51yXN2Qy6uq4eN4iAesa3v3Hv889v`;

// // multibase + multicodec
let expectedMultibase = `z6MkmhgpMDfiVVfLShamhYVCsWX6jQLVmwxXLtUykKFw3LwJ`;
let expectedMultibase = `z6MkmhgpMDfiVVfLShamhYVCsWX6jQLVmwxXLtUykKFw3LwJ`

expect(computedMultibase).toEqual(expectedMultibase)

const computedHex = extractPublicKeyHex({
const computed = extractPublicKeyHex({
publicKeyMultibase: expectedMultibase,
type: 'Ed25519VerificationKey2020',
id: 'dummy key',
controller: 'dummy controller',
})

expect(computedHex).toEqual(publicKeyHex)
expect(computed).toEqual({ publicKeyHex, keyType: 'Ed25519' })
})

it('should convert to multibase and back', async () => {
const publicKeyHex = '6bb3f30242ac89bb6baa169fd5d1fea5adb61ce5b3cfee9e157e699a51983869'
const computedMultibase = bytesToMultibase(hexToBytes(publicKeyHex), 'base58btc', 'ed25519-pub')

// // multibase + multicodec
let expectedMultibase = `z6MkmhgpMDfiVVfLShamhYVCsWX6jQLVmwxXLtUykKFw3LwJ`;
let expectedMultibase = `z6MkmhgpMDfiVVfLShamhYVCsWX6jQLVmwxXLtUykKFw3LwJ`

expect(computedMultibase).toEqual(expectedMultibase)

const computedXMultibase = bytesToMultibase(hexToBytes(publicKeyHex), 'base58btc', 'x25519-pub')
let expectedXMultibase = `z6LSivbwHHE9FQtcRb7qYd3KM1Bakybm4ftKXrHjQVwSqVvg`;
let expectedXMultibase = `z6LSivbwHHE9FQtcRb7qYd3KM1Bakybm4ftKXrHjQVwSqVvg`

expect(computedXMultibase).toEqual(expectedXMultibase)

const computedHex = extractPublicKeyHex({
const computed = extractPublicKeyHex({
publicKeyMultibase: expectedMultibase,
type: 'Ed25519VerificationKey2020',
id: 'dummy key',
controller: 'dummy controller',
})

expect(computedHex).toEqual(publicKeyHex)
expect(computed).toEqual({ publicKeyHex, keyType: 'Ed25519' })
})
})
67 changes: 29 additions & 38 deletions packages/utils/src/did-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function convertEd25519PrivateKeyToX25519(privateKey: Uint8Array): Uint8A
*
* @param identifier - the identifier with keys
*
* @returns the array of converted keys filtered to contain only those usable for encryption.
* @returns the array of converted keys filtered to contain ONLY X25519 keys usable for encryption.
*
* @beta This API may change without a BREAKING CHANGE notice.
*/
Expand Down Expand Up @@ -109,21 +109,13 @@ export function compressIdentifierSecp256k1Keys(identifier: IIdentifier): IKey[]
* @beta This API may change without a BREAKING CHANGE notice.
*/
function compareBlockchainAccountId(localKey: IKey, verificationMethod: VerificationMethod): boolean {
if (
!(
verificationMethod.type === 'EcdsaSecp256k1RecoveryMethod2020' ||
verificationMethod.type === 'EcdsaSecp256k1VerificationKey2019' ||
(verificationMethod.type === 'JsonWebKey2020' &&
verificationMethod.publicKeyJwk &&
verificationMethod.publicKeyJwk.crv === 'secp256k1') ||
localKey.type === 'Secp256k1'
)
) {
if (localKey.type !== 'Secp256k1') {
return false
}
let vmEthAddr = getEthereumAddress(verificationMethod)
const localAccount = localKey.meta?.account ?? localKey.meta?.ethereumAddress
if (localKey.meta?.account) {
return vmEthAddr === localKey.meta?.account.toLowerCase()
return vmEthAddr === localAccount.toLowerCase()
}
const computedAddr = computeAddress('0x' + localKey.publicKeyHex).toLowerCase()
return computedAddr === vmEthAddr
Expand All @@ -146,14 +138,12 @@ export function getEthereumAddress(verificationMethod: VerificationMethod): stri
vmEthAddr = verificationMethod.blockchainAccountId?.split('@eip155')[0].toLowerCase()
} else if (verificationMethod.blockchainAccountId?.startsWith('eip155')) {
vmEthAddr = verificationMethod.blockchainAccountId.split(':')[2]?.toLowerCase()
} else if (
verificationMethod.publicKeyHex ||
verificationMethod.publicKeyBase58 ||
verificationMethod.publicKeyBase64 ||
verificationMethod.publicKeyJwk
) {
const pbBytes = extractPublicKeyBytes(verificationMethod)
const pbHex = SigningKey.computePublicKey(pbBytes, false)
} else {
const { keyBytes, keyType } = extractPublicKeyBytes(verificationMethod)
if (keyType !== 'Secp256k1') {
return undefined
}
const pbHex = SigningKey.computePublicKey(keyBytes, false)

vmEthAddr = computeAddress(pbHex).toLowerCase()
}
Expand Down Expand Up @@ -302,7 +292,7 @@ export async function dereferenceDidKeys(
)
.filter(isDefined)
.map((key) => {
const hexKey = extractPublicKeyHex(key, convert)
const { publicKeyHex: hexKey, keyType } = extractPublicKeyHex(key, convert)
const {
publicKeyHex,
publicKeyBase58,
Expand All @@ -315,10 +305,12 @@ export async function dereferenceDidKeys(

// With a JWK `key`, `newKey` does not have information about crv (Ed25519 vs X25519)
// Should type of `newKey` change?
if (convert && 'Ed25519VerificationKey2018' === newKey.type) {
newKey.type = 'X25519KeyAgreementKey2019'
} else if (convert && ['Ed25519VerificationKey2020', 'JsonWebKey2020'].includes(newKey.type)) {
newKey.type = 'X25519KeyAgreementKey2020'
if (convert) {
if ('Ed25519VerificationKey2018' === newKey.type) {
newKey.type = 'X25519KeyAgreementKey2019'
} else if ('Ed25519VerificationKey2020' === newKey.type || 'X25519' === keyType) {
newKey.type = 'X25519KeyAgreementKey2020'
}
}

return newKey
Expand All @@ -330,24 +322,23 @@ export async function dereferenceDidKeys(
*
* @param pk - the VerificationMethod to be converted
* @param convert - when this flag is set to true, Ed25519 keys are converted to their X25519 pairs
* @returns the hex encoding of the public key
* @returns the hex encoding of the public key along with the inferred key type
*
* @beta This API may change without a BREAKING CHANGE notice.
*/
export function extractPublicKeyHex(pk: _ExtendedVerificationMethod, convert: boolean = false): string {
let keyBytes = extractPublicKeyBytes(pk)
export function extractPublicKeyHex(
pk: _ExtendedVerificationMethod,
convert: boolean = false,
): {
publicKeyHex: string
keyType: string | undefined
} {
let { keyBytes, keyType } = extractPublicKeyBytes(pk)
if (convert) {
if (
['Ed25519', 'Ed25519VerificationKey2018', 'Ed25519VerificationKey2020'].includes(pk.type) ||
(pk.type === 'JsonWebKey2020' && pk.publicKeyJwk?.crv === 'Ed25519')
) {
if (keyType === 'Ed25519') {
keyBytes = convertEd25519PublicKeyToX25519(keyBytes)
} else if (
!['X25519', 'X25519KeyAgreementKey2019', 'X25519KeyAgreementKey2020'].includes(pk.type) &&
!(pk.type === 'JsonWebKey2020' && pk.publicKeyJwk?.crv === 'X25519')
) {
return ''
keyType = 'X25519'
}
}
return bytesToHex(keyBytes)
return { publicKeyHex: bytesToHex(keyBytes), keyType }
}
3 changes: 2 additions & 1 deletion packages/utils/src/jwk-did-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,6 @@ export function generateJwkFromVerificationMethod(
key: VerificationMethod,
keyUse?: KeyUse,
) {
return createJWK(keyType, extractPublicKeyHex(key), keyUse)
const { publicKeyHex, keyType: extractedType } = extractPublicKeyHex(key)
return createJWK(keyType, publicKeyHex, keyUse)
}
Loading

0 comments on commit 165de35

Please sign in to comment.