diff --git a/src/ConditionalAlgorithm.ts b/src/ConditionalAlgorithm.ts index 02b9fb53..08201c23 100644 --- a/src/ConditionalAlgorithm.ts +++ b/src/ConditionalAlgorithm.ts @@ -137,7 +137,7 @@ async function verifyConditionDelegated( } } else { try { - foundSigner = await verifyJWTDecoded({ header, payload, data, signature }, delegatedAuthenticator) + foundSigner = verifyJWTDecoded({ header, payload, data, signature }, delegatedAuthenticator) } catch (e) { if (!(e as Error).message.startsWith('invalid_signature:')) throw e } diff --git a/src/JWT.ts b/src/JWT.ts index 2978979f..7c4e8879 100644 --- a/src/JWT.ts +++ b/src/JWT.ts @@ -1,7 +1,7 @@ import canonicalizeData from 'canonicalize' import { DIDDocument, DIDResolutionResult, parse, ParsedDID, Resolvable, VerificationMethod } from 'did-resolver' import SignerAlg from './SignerAlgorithm.js' -import { decodeBase64url, EcdsaSignature, encodeBase64url } from './util.js' +import { decodeBase64url, EcdsaSignature, encodeBase64url, KNOWN_JWA, SUPPORTED_PUBLIC_KEY_TYPES } from './util.js' import VerifierAlgorithm from './VerifierAlgorithm.js' import { JWT_ERROR } from './Errors.js' import { verifyProof } from './ConditionalAlgorithm.js' @@ -141,79 +141,13 @@ export interface JWTVerified { policies?: JWTVerifyPolicies } -export interface PublicKeyTypes { - [name: string]: string[] -} - -export const SUPPORTED_PUBLIC_KEY_TYPES: PublicKeyTypes = { - ES256: ['JsonWebKey2020'], - ES256K: [ - 'EcdsaSecp256k1VerificationKey2019', - /** - * Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress - */ - 'EcdsaSecp256k1RecoveryMethod2020', - /** - * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is - * not an ethereumAddress - */ - 'Secp256k1VerificationKey2018', - /** - * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is - * not an ethereumAddress - */ - 'Secp256k1SignatureVerificationKey2018', - /** - * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is - * not an ethereumAddress - */ - 'EcdsaPublicKeySecp256k1', - /** - * TODO - support R1 key as well - * 'ConditionalProof2022', - */ - 'JsonWebKey2020', - ], - 'ES256K-R': [ - 'EcdsaSecp256k1VerificationKey2019', - /** - * Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress - */ - 'EcdsaSecp256k1RecoveryMethod2020', - /** - * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is - * not an ethereumAddress - */ - 'Secp256k1VerificationKey2018', - /** - * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is - * not an ethereumAddress - */ - 'Secp256k1SignatureVerificationKey2018', - /** - * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is - * not an ethereumAddress - */ - 'EcdsaPublicKeySecp256k1', - 'ConditionalProof2022', - 'JsonWebKey2020', - ], - Ed25519: [ - 'ED25519SignatureVerification', - 'Ed25519VerificationKey2018', - 'Ed25519VerificationKey2020', - 'JsonWebKey2020', - ], - EdDSA: ['ED25519SignatureVerification', 'Ed25519VerificationKey2018', 'Ed25519VerificationKey2020', 'JsonWebKey2020'], -} - export const SELF_ISSUED_V2 = 'https://self-issued.me/v2' export const SELF_ISSUED_V2_VC_INTEROP = 'https://self-issued.me/v2/openid-vc' // https://identity.foundation/jwt-vc-presentation-profile/#id-token-validation export const SELF_ISSUED_V0_1 = 'https://self-issued.me' type LegacyVerificationMethod = { publicKey?: string } -const defaultAlg = 'ES256K' +const defaultAlg: KNOWN_JWA = 'ES256K' const DID_JSON = 'application/did+json' // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -347,8 +281,8 @@ export async function createJWT( } /** - * Creates a multi-signature signed JWT given multiple issuers and their corresponding signers, and a payload for which the signature is - * over. + * Creates a multi-signature signed JWT given multiple issuers and their corresponding signers, and a payload for + * which the signature is over. * * @example * const signer = ES256KSigner(process.env.PRIVATE_KEY) @@ -388,7 +322,8 @@ export async function createMultisignatureJWT( // Create nested JWT // See Point 5 of https://www.rfc-editor.org/rfc/rfc7519#section-7.1 - // After the first JWT is created (the first JWS), the next JWT is created by inputting the previous JWT as the payload + // After the first JWT is created (the first JWS), the next JWT is created by inputting the previous JWT as the + // payload if (i !== 0) { header.cty = 'JWT' } @@ -646,7 +581,7 @@ export async function resolveAuthenticator( issuer: string, proofPurpose?: ProofPurposeTypes ): Promise { - const types: string[] = SUPPORTED_PUBLIC_KEY_TYPES[alg] + const types: string[] = SUPPORTED_PUBLIC_KEY_TYPES[alg as KNOWN_JWA] if (!types || types.length === 0) { throw new Error(`${JWT_ERROR.NOT_SUPPORTED}: No supported signature types for algorithm ${alg}`) } diff --git a/src/VerifierAlgorithm.ts b/src/VerifierAlgorithm.ts index 35ebe865..e9d1e09b 100644 --- a/src/VerifierAlgorithm.ts +++ b/src/VerifierAlgorithm.ts @@ -1,14 +1,12 @@ import { sha256, toEthereumAddress } from './Digest.js' import type { VerificationMethod } from 'did-resolver' import { - base58ToBytes, base64ToBytes, - bytesToBigInt, bytesToHex, EcdsaSignature, ECDSASignature, - hexToBytes, - multibaseToBytes, + extractPublicKeyBytes, + KNOWN_JWA, stringToBytes, } from './util.js' import { verifyBlockchainAccountId } from './blockchains/index.js' @@ -42,36 +40,6 @@ export function toSignatureObject2(signature: string, recoverable = false): ECDS } } -export function extractPublicKeyBytes(pk: VerificationMethod): Uint8Array { - if (pk.publicKeyBase58) { - return base58ToBytes(pk.publicKeyBase58) - } else if (pk.publicKeyBase64) { - return base64ToBytes(pk.publicKeyBase64) - } else if (pk.publicKeyHex) { - return hexToBytes(pk.publicKeyHex) - } else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'secp256k1' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) { - return secp256k1.ProjectivePoint.fromAffine({ - x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)), - y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y)), - }).toRawBytes(false) - } else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'P-256' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) { - return p256.ProjectivePoint.fromAffine({ - x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)), - y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y)), - }).toRawBytes(false) - } else if ( - pk.publicKeyJwk && - pk.publicKeyJwk.kty === 'OKP' && - ['Ed25519', 'X25519'].includes(pk.publicKeyJwk.crv ?? '') && - pk.publicKeyJwk.x - ) { - return base64ToBytes(pk.publicKeyJwk.x) - } else if (pk.publicKeyMultibase) { - return multibaseToBytes(pk.publicKeyMultibase) - } - return new Uint8Array() -} - export function verifyES256(data: string, signature: string, authenticators: VerificationMethod[]): VerificationMethod { const hash = sha256(data) const sig = p256.Signature.fromCompact(toSignatureObject2(signature).compact) @@ -79,8 +47,8 @@ export function verifyES256(data: string, signature: string, authenticators: Ver const signer: VerificationMethod | undefined = fullPublicKeys.find((pk: VerificationMethod) => { try { - const pubBytes = extractPublicKeyBytes(pk) - return p256.verify(sig, hash, pubBytes) + const { keyBytes } = extractPublicKeyBytes(pk) + return p256.verify(sig, hash, keyBytes) } catch (err) { return false } @@ -106,8 +74,8 @@ export function verifyES256K( let signer: VerificationMethod | undefined = fullPublicKeys.find((pk: VerificationMethod) => { try { - const pubBytes = extractPublicKeyBytes(pk) - return secp256k1.verify(signatureNormalized, hash, pubBytes) + const { keyBytes } = extractPublicKeyBytes(pk) + return secp256k1.verify(signatureNormalized, hash, keyBytes) } catch (err) { return false } @@ -144,7 +112,8 @@ export function verifyRecoverableES256K( const recoveredCompressedPublicKeyHex = recoveredPublicKey.toHex(true) return authenticators.find((a: VerificationMethod) => { - const keyHex = bytesToHex(extractPublicKeyBytes(a)) + const { keyBytes } = extractPublicKeyBytes(a) + const keyHex = bytesToHex(keyBytes) return ( keyHex === recoveredPublicKeyHex || keyHex === recoveredCompressedPublicKeyHex || @@ -172,7 +141,12 @@ export function verifyEd25519( const clear = stringToBytes(data) const signatureBytes = base64ToBytes(signature) const signer = authenticators.find((a: VerificationMethod) => { - return ed25519.verify(signatureBytes, clear, extractPublicKeyBytes(a)) + const { keyBytes, keyType } = extractPublicKeyBytes(a) + if (keyType === 'Ed25519') { + return ed25519.verify(signatureBytes, clear, keyBytes) + } else { + return false + } }) if (!signer) throw new Error('invalid_signature: Signature invalid for JWT') return signer @@ -180,9 +154,7 @@ export function verifyEd25519( type Verifier = (data: string, signature: string, authenticators: VerificationMethod[]) => VerificationMethod -interface Algorithms { - [name: string]: Verifier -} +type Algorithms = Record const algorithms: Algorithms = { ES256: verifyES256, @@ -197,7 +169,7 @@ const algorithms: Algorithms = { } function VerifierAlgorithm(alg: string): Verifier { - const impl: Verifier = algorithms[alg] + const impl: Verifier = algorithms[alg as KNOWN_JWA] if (!impl) throw new Error(`not_supported: Unsupported algorithm ${alg}`) return impl } diff --git a/src/__tests__/JWT.test.ts b/src/__tests__/JWT.test.ts index c0593a62..36ccadca 100644 --- a/src/__tests__/JWT.test.ts +++ b/src/__tests__/JWT.test.ts @@ -430,7 +430,7 @@ describe('verifyJWT() for ES256', () => { verificationMethod: [ { id: `${did}#keys-1`, - type: 'JsonWebKey2020', + type: 'EcdsaSecp256r1VerificationKey2019', controller: did, publicKeyHex: publicKey, }, diff --git a/src/__tests__/VerifierAlgorithm.test.ts b/src/__tests__/VerifierAlgorithm.test.ts index 2d3ab8ca..c4f7c953 100644 --- a/src/__tests__/VerifierAlgorithm.test.ts +++ b/src/__tests__/VerifierAlgorithm.test.ts @@ -62,7 +62,8 @@ describe('ES256', () => { y: bytesToBase64url(hexToBytes(kp.y.toString(16))), } const signer = ES256Signer(privateKey) - const publicKeyMultibase = bytesToMultibase(hexToBytes(publicKey), 'base58btc') + const publicKeyMultibase = bytesToMultibase(hexToBytes(compressedPublicKey), 'base58btc', 'p256-pub') + const publicKeyMultibaseNoCodec = bytesToMultibase(hexToBytes(compressedPublicKey), 'base58btc') const ecKey1 = { id: `${did}#keys-1`, @@ -79,6 +80,20 @@ describe('ES256', () => { publicKeyHex: publicKey, } + const ecKey3 = { + id: `${did}#keys-3`, + type: 'Multikey', + controller: did, + publicKeyMultibase, + } + + const ecKey4 = { + id: `${did}#keys-4`, + type: 'Multikey', + controller: did, + publicKeyMultibase: publicKeyMultibaseNoCodec, + } + const compressedKey = { id: `${did}#keys-4`, type: 'JsonWebKey2020', @@ -155,7 +170,27 @@ describe('ES256', () => { expect.assertions(1) const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }, { alg: 'ES256' }) const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) - const pubkey = Object.assign({ publicKeyMultibase }, ecKey2) + const pubkey = ecKey3 + // @ts-ignore + return expect(verifier(parts[1], parts[2], [pubkey])).toEqual(pubkey) + }) + + it('validates with publicKeyMultibase Multikey', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }, { alg: 'ES256' }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + const pubkey = ecKey3 + // @ts-ignore + delete pubkey.publicKeyHex + // @ts-ignore + return expect(verifier(parts[1], parts[2], [pubkey])).toEqual(pubkey) + }) + + it('validates with publicKeyMultibase Multikey without codec', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }, { alg: 'ES256' }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + const pubkey = ecKey4 // @ts-ignore delete pubkey.publicKeyHex // @ts-ignore diff --git a/src/__tests__/xc20pEncryption.test.ts b/src/__tests__/xc20pEncryption.test.ts index c425b53f..5b768843 100644 --- a/src/__tests__/xc20pEncryption.test.ts +++ b/src/__tests__/xc20pEncryption.test.ts @@ -199,10 +199,10 @@ describe('xc20pEncryption', () => { 'resolver_error: Could not resolve did:test:3' ) await expect(resolveX25519Encrypters([did4], resolver)).rejects.toThrowError( - 'no_suitable_keys: Could not find x25519 key for did:test:4' + 'no_suitable_keys: Could not find X25519 key for did:test:4' ) await expect(resolveX25519Encrypters([did7], resolver)).rejects.toThrowError( - 'no_suitable_keys: Could not find x25519 key for did:test:7' + 'no_suitable_keys: Could not find X25519 key for did:test:7' ) }) diff --git a/src/encryption/xc20pEncryption.ts b/src/encryption/xc20pEncryption.ts index 1b2152f8..1e04ecfe 100644 --- a/src/encryption/xc20pEncryption.ts +++ b/src/encryption/xc20pEncryption.ts @@ -10,11 +10,10 @@ import type { Recipient, WrappingResult, } from './types.js' -import { base64ToBytes, toSealed } from '../util.js' +import { base64ToBytes, extractPublicKeyBytes, isDefined, toSealed } from '../util.js' import { xc20pDirDecrypter, xc20pDirEncrypter, xc20pEncrypter } from './xc20pDir.js' import { computeX25519Ecdh1PUv3Kek, createX25519Ecdh1PUv3Kek } from './X25519-ECDH-1PU.js' import { computeX25519EcdhEsKek, createX25519EcdhEsKek } from './X25519-ECDH-ES.js' -import { extractPublicKeyBytes } from '../VerifierAlgorithm.js' import { createFullEncrypter } from './createEncrypter.js' /** @@ -175,12 +174,22 @@ export async function resolveX25519Encrypters(dids: string[], resolver: Resolvab }) ?.filter((key) => typeof key !== 'undefined') as VerificationMethod[] const pks = - agreementKeys?.filter((key) => { - return key.type === 'X25519KeyAgreementKey2019' || key.type === 'X25519KeyAgreementKey2020' - }) || [] + agreementKeys?.filter((key) => + ['X25519KeyAgreementKey2019', 'X25519KeyAgreementKey2020', 'JsonWebKey2020', 'Multikey'].includes(key.type) + ) ?? [] if (!pks.length && !controllerEncrypters.length) - throw new Error(`no_suitable_keys: Could not find x25519 key for ${did}`) - return pks.map((pk) => x25519Encrypter(extractPublicKeyBytes(pk), pk.id)).concat(...controllerEncrypters) + throw new Error(`no_suitable_keys: Could not find X25519 key for ${did}`) + return pks + .map((pk) => { + const { keyBytes, keyType } = extractPublicKeyBytes(pk) + if (keyType === 'X25519') { + return x25519Encrypter(keyBytes, pk.id) + } else { + return null + } + }) + .filter(isDefined) + .concat(...controllerEncrypters) } const encrypterPromises = dids.map((did) => encryptersForDID(did)) diff --git a/src/index.ts b/src/index.ts index 6391f4e3..b446e999 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,8 +72,7 @@ export { multibaseToBytes, bytesToMultibase, supportedCodecs, + extractPublicKeyBytes, } from './util.js' -export { extractPublicKeyBytes } from './VerifierAlgorithm.js' - export * from './Errors.js' diff --git a/src/util.ts b/src/util.ts index 2648a383..90ed7fe1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,6 +3,9 @@ import { x25519 } from '@noble/curves/ed25519' import type { EphemeralKeyPair } from './encryption/types.js' import { varint } from 'multiformats' import { BaseName, decode, encode } from 'multibase' +import type { VerificationMethod } from 'did-resolver' +import { secp256k1 } from '@noble/curves/secp256k1' +import { p256 } from '@noble/curves/p256' const u8a = { toString, fromString, concat } @@ -53,8 +56,126 @@ export function bytesToBase58(b: Uint8Array): string { return u8a.toString(b, 'base58btc') } +export type KNOWN_JWA = 'ES256' | 'ES256K' | 'ES256K-R' | 'Ed25519' | 'EdDSA' + +export type KNOWN_VERIFICATION_METHOD = + | 'JsonWebKey2020' + | 'Multikey' + | 'Secp256k1SignatureVerificationKey2018' // deprecated in favor of EcdsaSecp256k1VerificationKey2019 + | 'Secp256k1VerificationKey2018' // deprecated in favor of EcdsaSecp256k1VerificationKey2019 + | 'EcdsaSecp256k1VerificationKey2019' // ES256K / ES256K-R + | 'EcdsaPublicKeySecp256k1' // deprecated in favor of EcdsaSecp256k1VerificationKey2019 + | 'EcdsaSecp256k1RecoveryMethod2020' // ES256K-R (ES256K also supported with 1 less bit of security) + | 'EcdsaSecp256r1VerificationKey2019' // ES256 / P-256 + | 'Ed25519VerificationKey2018' + | 'Ed25519VerificationKey2020' + | 'ED25519SignatureVerification' // deprecated + | 'ConditionalProof2022' + | 'X25519KeyAgreementKey2019' // deprecated + | 'X25519KeyAgreementKey2020' + +export type KNOWN_KEY_TYPE = 'Secp256k1' | 'Ed25519' | 'X25519' | 'Bls12381G1' | 'Bls12381G2' | 'P-256' + +export type PublicKeyTypes = Record + +export const SUPPORTED_PUBLIC_KEY_TYPES: PublicKeyTypes = { + ES256: ['JsonWebKey2020', 'Multikey', 'EcdsaSecp256r1VerificationKey2019'], + ES256K: [ + 'EcdsaSecp256k1VerificationKey2019', + /** + * Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress + */ + 'EcdsaSecp256k1RecoveryMethod2020', + /** + * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is + * not an ethereumAddress + */ + 'Secp256k1VerificationKey2018', + /** + * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is + * not an ethereumAddress + */ + 'Secp256k1SignatureVerificationKey2018', + /** + * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is + * not an ethereumAddress + */ + 'EcdsaPublicKeySecp256k1', + /** + * TODO - support R1 key as well + * 'ConditionalProof2022', + */ + 'JsonWebKey2020', + 'Multikey', + ], + 'ES256K-R': [ + 'EcdsaSecp256k1VerificationKey2019', + /** + * Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress + */ + 'EcdsaSecp256k1RecoveryMethod2020', + /** + * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is + * not an ethereumAddress + */ + 'Secp256k1VerificationKey2018', + /** + * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is + * not an ethereumAddress + */ + 'Secp256k1SignatureVerificationKey2018', + /** + * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is + * not an ethereumAddress + */ + 'EcdsaPublicKeySecp256k1', + 'ConditionalProof2022', + 'JsonWebKey2020', + 'Multikey', + ], + Ed25519: [ + 'ED25519SignatureVerification', + 'Ed25519VerificationKey2018', + 'Ed25519VerificationKey2020', + 'JsonWebKey2020', + 'Multikey', + ], + EdDSA: [ + 'ED25519SignatureVerification', + 'Ed25519VerificationKey2018', + 'Ed25519VerificationKey2020', + 'JsonWebKey2020', + 'Multikey', + ], +} + +export const VM_TO_KEY_TYPE: Record = { + Secp256k1SignatureVerificationKey2018: 'Secp256k1', + Secp256k1VerificationKey2018: 'Secp256k1', + EcdsaSecp256k1VerificationKey2019: 'Secp256k1', + EcdsaPublicKeySecp256k1: 'Secp256k1', + EcdsaSecp256k1RecoveryMethod2020: 'Secp256k1', + EcdsaSecp256r1VerificationKey2019: 'P-256', + Ed25519VerificationKey2018: 'Ed25519', + Ed25519VerificationKey2020: 'Ed25519', + ED25519SignatureVerification: 'Ed25519', + X25519KeyAgreementKey2019: 'X25519', + X25519KeyAgreementKey2020: 'X25519', + ConditionalProof2022: undefined, + JsonWebKey2020: undefined, // key type must be specified in the JWK + Multikey: undefined, // key type must be extracted from the multicodec +} + +export type KNOWN_CODECS = + | 'ed25519-pub' + | 'x25519-pub' + | 'secp256k1-pub' + | 'bls12_381-g1-pub' + | 'bls12_381-g2-pub' + | 'p256-pub' + // this is from the multicodec table https://github.com/multiformats/multicodec/blob/master/table.csv -export const supportedCodecs = { +export const supportedCodecs: Record = { 'ed25519-pub': 0xed, 'x25519-pub': 0xec, 'secp256k1-pub': 0xe7, @@ -63,6 +184,63 @@ export const supportedCodecs = { 'p256-pub': 0x1200, } +export const CODEC_TO_KEY_TYPE: Record = { + 'bls12_381-g1-pub': 'Bls12381G1', + 'bls12_381-g2-pub': 'Bls12381G2', + 'ed25519-pub': 'Ed25519', + 'p256-pub': 'P-256', + 'secp256k1-pub': 'Secp256k1', + 'x25519-pub': 'X25519', +} + +/** + * Extracts the raw byte representation of a public key from a VerificationMethod along with an inferred key type + * @param pk a VerificationMethod entry from a DIDDocument + * @return an object containing the `keyBytes` of the public key and an inferred `keyType` + */ +export function extractPublicKeyBytes(pk: VerificationMethod): { keyBytes: Uint8Array; keyType?: KNOWN_KEY_TYPE } { + if (pk.publicKeyBase58) { + return { + keyBytes: base58ToBytes(pk.publicKeyBase58), + keyType: VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD], + } + } else if (pk.publicKeyBase64) { + return { + keyBytes: base64ToBytes(pk.publicKeyBase64), + keyType: VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD], + } + } else if (pk.publicKeyHex) { + return { keyBytes: hexToBytes(pk.publicKeyHex), keyType: VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD] } + } else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'secp256k1' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) { + return { + keyBytes: secp256k1.ProjectivePoint.fromAffine({ + x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)), + y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y)), + }).toRawBytes(false), + keyType: 'Secp256k1', + } + } else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'P-256' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) { + return { + keyBytes: p256.ProjectivePoint.fromAffine({ + x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)), + y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y)), + }).toRawBytes(false), + keyType: 'P-256', + } + } else if ( + pk.publicKeyJwk && + pk.publicKeyJwk.kty === 'OKP' && + ['Ed25519', 'X25519'].includes(pk.publicKeyJwk.crv ?? '') && + pk.publicKeyJwk.x + ) { + return { keyBytes: base64ToBytes(pk.publicKeyJwk.x), keyType: pk.publicKeyJwk.crv as KNOWN_KEY_TYPE } + } else if (pk.publicKeyMultibase) { + const { keyBytes, keyType } = multibaseToBytes(pk.publicKeyMultibase) + return { keyBytes, keyType: keyType ?? VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD] } + } + return { keyBytes: new Uint8Array() } +} + /** * Encodes the given byte array to a multibase string (defaulting to base58btc). * If a codec is provided, the corresponding multicodec prefix will be added. @@ -102,23 +280,25 @@ export function bytesToMultibase( * * @public */ -export function multibaseToBytes(s: string): Uint8Array { +export function multibaseToBytes(s: string): { keyBytes: Uint8Array; keyType?: KNOWN_KEY_TYPE } { const bytes = decode(s) // look for known key lengths first // Ed25519/X25519, secp256k1/P256 compressed or not, BLS12-381 G1/G2 compressed if ([32, 33, 48, 64, 65, 96].includes(bytes.length)) { - return bytes + return { keyBytes: bytes } } // then assume multicodec, otherwise return the bytes try { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [codec, length] = varint.decode(bytes) - return bytes.slice(length) + const possibleCodec: string | undefined = + Object.entries(supportedCodecs).filter(([, code]) => code === codec)?.[0][0] ?? '' + return { keyBytes: bytes.slice(length), keyType: CODEC_TO_KEY_TYPE[possibleCodec as KNOWN_CODECS] } } catch (e) { // not a multicodec, return the bytes - return bytes + return { keyBytes: bytes } } } @@ -226,3 +406,15 @@ export function genX25519EphemeralKeyPair(): EphemeralKeyPair { secretKey: epk.secretKey, } } + +/** + * Checks if a variable is defined and not null. + * After this check, typescript sees the variable as defined. + * + * @param arg - The input to be verified + * + * @returns true if the input variable is defined. + */ +export function isDefined(arg: T): arg is Exclude { + return arg !== null && typeof arg !== 'undefined' +}