From 042e1843abfa33b1c83926d578d49d8eedc91a0e Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 30 Jan 2025 14:58:36 +0100 Subject: [PATCH 1/9] feat: default to uncompressed keys Signed-off-by: Berend Sliedrecht --- packages/core/src/crypto/JwsService.ts | 10 +- packages/core/src/crypto/Key.ts | 18 ++-- .../core/src/crypto/jose/jwk/Ed25519Jwk.ts | 3 +- packages/core/src/crypto/jose/jwk/Jwk.ts | 3 +- packages/core/src/crypto/jose/jwk/K256Jwk.ts | 73 +++++++++---- packages/core/src/crypto/jose/jwk/P256Jwk.ts | 73 +++++++++---- packages/core/src/crypto/jose/jwk/P384Jwk.ts | 73 +++++++++---- packages/core/src/crypto/jose/jwk/P521Jwk.ts | 73 +++++++++---- .../core/src/crypto/jose/jwk/X25519Jwk.ts | 3 +- .../jose/jwk/__tests__/K_256Jwk.test.ts | 57 ++++++++++ .../jose/jwk/__tests__/P_256Jwk.test.ts | 39 ++++--- .../jose/jwk/__tests__/P_384Jwk.test.ts | 38 ++++--- .../jose/jwk/__tests__/P_521Jwk.test.ts | 38 ++++--- .../core/src/crypto/jose/jwk/ecCompression.ts | 101 ++++++++++++++---- .../key-type/__tests__/bls12381g1.test.ts | 4 +- .../key-type/__tests__/bls12381g1g2.test.ts | 4 +- .../key-type/__tests__/bls12381g2.test.ts | 2 +- .../domain/key-type/__tests__/ed25519.test.ts | 4 +- .../domain/key-type/__tests__/jwk.test.ts | 2 +- .../domain/key-type/__tests__/x25519.test.ts | 2 +- .../transport/TransportDecorator.test.ts | 2 +- 21 files changed, 436 insertions(+), 186 deletions(-) create mode 100644 packages/core/src/crypto/jose/jwk/__tests__/K_256Jwk.test.ts diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 31f04163a5..431730df97 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -9,7 +9,7 @@ import type { Key } from './Key' import type { Jwk } from './jose/jwk' import type { JwkJson } from './jose/jwk/Jwk' import type { AgentContext } from '../agent' -import type { Buffer } from '../utils' +import { Buffer } from '../utils' import { CredoError } from '../error' import { EncodedX509Certificate, X509ModuleConfig } from '../modules/x509' @@ -33,14 +33,18 @@ export class JwsService { const certificate = X509Service.getLeafCertificate(agentContext, { certificateChain: x5c }) if ( certificate.publicKey.keyType !== options.key.keyType || - !certificate.publicKey.publicKey.equals(options.key.publicKey) + !Buffer.from(certificate.publicKey.publicKey).equals(Buffer.from(options.key.publicKey)) ) { throw new CredoError(`Protected header x5c does not match key for signing.`) } } // Make sure the options.key and jwk from protectedHeader are the same. - if (jwk && (jwk.key.keyType !== options.key.keyType || !jwk.key.publicKey.equals(options.key.publicKey))) { + if ( + jwk && + (jwk.key.keyType !== options.key.keyType || + !Buffer.from(jwk.key.publicKey).equals(Buffer.from(options.key.publicKey))) + ) { throw new CredoError(`Protected header JWK does not match key for signing.`) } diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index ec44eead24..e5751813d5 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -1,21 +1,22 @@ import type { KeyType } from './KeyType' -import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' +import { MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './keyUtils' import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey' +import { compressIfPossible, expandIfPossible } from './jose/jwk/ecCompression' export class Key { - public readonly publicKey: Buffer + public readonly publicKey: Uint8Array public readonly keyType: KeyType public constructor(publicKey: Uint8Array, keyType: KeyType) { - this.publicKey = Buffer.from(publicKey) + this.publicKey = expandIfPossible(publicKey, keyType) this.keyType = keyType } public static fromPublicKey(publicKey: Uint8Array, keyType: KeyType) { - return new Key(Buffer.from(publicKey), keyType) + return new Key(publicKey, keyType) } public static fromPublicKeyBase58(publicKey: string, keyType: KeyType) { @@ -28,7 +29,7 @@ export class Key { const { data } = MultiBaseEncoder.decode(fingerprint) const [code, byteLength] = VarintEncoder.decode(data) - const publicKey = Buffer.from(data.slice(byteLength)) + const publicKey = data.slice(byteLength) const keyType = getKeyTypeByMultiCodecPrefix(code) return new Key(publicKey, keyType) @@ -40,8 +41,11 @@ export class Key { // Create Buffer with length of the prefix bytes, then use varint to fill the prefix bytes const prefixBytes = VarintEncoder.encode(multiCodecPrefix) + // Multicodec requires compressable keys to be compressed + const possiblyCompressedKey = compressIfPossible(this.publicKey, this.keyType) + // Combine prefix with public key - return Buffer.concat([prefixBytes, this.publicKey]) + return new Uint8Array([...prefixBytes, ...possiblyCompressedKey]) } public get fingerprint() { @@ -62,7 +66,7 @@ export class Key { // We return an object structure based on the key, so that when this object is // serialized to JSON it will be nicely formatted instead of the bytes printed - private toJSON() { + public toJSON() { return { keyType: this.keyType, publicKeyBase58: this.publicKeyBase58, diff --git a/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts b/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts index 30966cea64..86e33687e3 100644 --- a/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts @@ -1,5 +1,4 @@ import type { JwkJson } from './Jwk' -import type { Buffer } from '../../../utils' import type { JwaEncryptionAlgorithm } from '../jwa/alg' import { TypedArrayEncoder } from '../../../utils' @@ -65,7 +64,7 @@ export class Ed25519Jwk extends Jwk { }) } - public static fromPublicKey(publicKey: Buffer) { + public static fromPublicKey(publicKey: Uint8Array) { return new Ed25519Jwk({ x: TypedArrayEncoder.toBase64URL(publicKey), }) diff --git a/packages/core/src/crypto/jose/jwk/Jwk.ts b/packages/core/src/crypto/jose/jwk/Jwk.ts index a38b384ba9..b9cc10ccc5 100644 --- a/packages/core/src/crypto/jose/jwk/Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/Jwk.ts @@ -1,4 +1,3 @@ -import type { Buffer } from '../../../utils' import type { KeyType } from '../../KeyType' import type { JwaKeyType, JwaEncryptionAlgorithm, JwaSignatureAlgorithm } from '../jwa' @@ -11,7 +10,7 @@ export interface JwkJson { } export abstract class Jwk { - public abstract publicKey: Buffer + public abstract publicKey: Uint8Array public abstract supportedSignatureAlgorithms: JwaSignatureAlgorithm[] public abstract supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] diff --git a/packages/core/src/crypto/jose/jwk/K256Jwk.ts b/packages/core/src/crypto/jose/jwk/K256Jwk.ts index 914b940d86..584a545728 100644 --- a/packages/core/src/crypto/jose/jwk/K256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/K256Jwk.ts @@ -1,28 +1,38 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' -import { TypedArrayEncoder, Buffer } from '../../../utils' +import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' import { JwaSignatureAlgorithm } from '../jwa/alg' import { Jwk } from './Jwk' -import { compress, expand } from './ecCompression' +import { + compressECPoint, + expand, + isValidCompressedPublicKey, + isValidUncompressedPublicKey, + PREFIX_UNCOMPRESSED, +} from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' +import { CredoError } from '../../../error' export class K256Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.ES256K] public static readonly keyType = KeyType.K256 - public readonly x: string - public readonly y: string + private readonly _x: Uint8Array + private readonly _y: Uint8Array - public constructor({ x, y }: { x: string; y: string }) { + public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - this.x = x - this.y = y + const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x + const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + + this._x = xAsBytes + this._y = yAsBytes } public get kty() { @@ -33,17 +43,26 @@ export class K256Jwk extends Jwk { return JwaCurve.Secp256k1 as const } + public get x() { + return TypedArrayEncoder.toBase64URL(this._x) + } + + public get y() { + return TypedArrayEncoder.toBase64URL(this._y) + } + /** - * Returns the public key of the K-256 JWK. - * - * NOTE: this is the compressed variant. We still need to add support for the - * uncompressed variant. + * Returns the uncompressed public key of the P-256 JWK. */ public get publicKey() { - const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) - const compressedPublicKey = compress(publicKeyBuffer) + return new Uint8Array([PREFIX_UNCOMPRESSED, ...this._x, ...this._y]) + } - return Buffer.from(compressedPublicKey) + /** + * Returns the compressed public key of the K-256 JWK. + */ + public get publicKeyCompressed() { + return compressECPoint(this._x, this._y) } public get keyType() { @@ -78,15 +97,25 @@ export class K256Jwk extends Jwk { }) } - public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.Secp256k1) - const x = expanded.slice(0, expanded.length / 2) - const y = expanded.slice(expanded.length / 2) + public static fromPublicKey(publicKey: Uint8Array) { + if (isValidCompressedPublicKey(publicKey, this.keyType)) { + const expanded = expand(publicKey, this.keyType) + const x = expanded.slice(1, expanded.length / 2 + 1) + const y = expanded.slice(expanded.length / 2 + 1) - return new K256Jwk({ - x: TypedArrayEncoder.toBase64URL(x), - y: TypedArrayEncoder.toBase64URL(y), - }) + return new K256Jwk({ x, y }) + } + + if (isValidUncompressedPublicKey(publicKey, this.keyType)) { + const x = publicKey.slice(1, publicKey.length / 2 + 1) + const y = publicKey.slice(publicKey.length / 2 + 1) + + return new K256Jwk({ x, y }) + } + + throw new CredoError( + `${this.keyType} public key is neither a valid compressed or uncompressed key. Key prefix '${publicKey[0]}', key length '${publicKey.length}'` + ) } } diff --git a/packages/core/src/crypto/jose/jwk/P256Jwk.ts b/packages/core/src/crypto/jose/jwk/P256Jwk.ts index 68427ad9d7..526511563a 100644 --- a/packages/core/src/crypto/jose/jwk/P256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P256Jwk.ts @@ -1,28 +1,38 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' -import { TypedArrayEncoder, Buffer } from '../../../utils' +import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' import { JwaSignatureAlgorithm } from '../jwa/alg' import { Jwk } from './Jwk' -import { compress, expand } from './ecCompression' +import { + compressECPoint, + expand, + isValidCompressedPublicKey, + isValidUncompressedPublicKey, + PREFIX_UNCOMPRESSED, +} from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' +import { CredoError } from '../../../error' export class P256Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.ES256] public static readonly keyType = KeyType.P256 - public readonly x: string - public readonly y: string + private readonly _x: Uint8Array + private readonly _y: Uint8Array - public constructor({ x, y }: { x: string; y: string }) { + public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - this.x = x - this.y = y + const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x + const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + + this._x = xAsBytes + this._y = yAsBytes } public get kty() { @@ -33,17 +43,26 @@ export class P256Jwk extends Jwk { return JwaCurve.P256 as const } + public get x() { + return TypedArrayEncoder.toBase64URL(this._x) + } + + public get y() { + return TypedArrayEncoder.toBase64URL(this._y) + } + /** - * Returns the public key of the P-256 JWK. - * - * NOTE: this is the compressed variant. We still need to add support for the - * uncompressed variant. + * Returns the uncompressed public key of the P-256 JWK. */ public get publicKey() { - const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) - const compressedPublicKey = compress(publicKeyBuffer) + return new Uint8Array([PREFIX_UNCOMPRESSED, ...this._x, ...this._y]) + } - return Buffer.from(compressedPublicKey) + /** + * Returns the compressed public key of the P-256 JWK. + */ + public get publicKeyCompressed() { + return compressECPoint(this._x, this._y) } public get keyType() { @@ -78,15 +97,25 @@ export class P256Jwk extends Jwk { }) } - public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.P256) - const x = expanded.slice(0, expanded.length / 2) - const y = expanded.slice(expanded.length / 2) + public static fromPublicKey(publicKey: Uint8Array) { + if (isValidCompressedPublicKey(publicKey, this.keyType)) { + const expanded = expand(publicKey, this.keyType) + const x = expanded.slice(1, expanded.length / 2 + 1) + const y = expanded.slice(expanded.length / 2 + 1) - return new P256Jwk({ - x: TypedArrayEncoder.toBase64URL(x), - y: TypedArrayEncoder.toBase64URL(y), - }) + return new P256Jwk({ x, y }) + } + + if (isValidUncompressedPublicKey(publicKey, this.keyType)) { + const x = publicKey.slice(1, publicKey.length / 2 + 1) + const y = publicKey.slice(publicKey.length / 2 + 1) + + return new P256Jwk({ x, y }) + } + + throw new CredoError( + `${this.keyType} public key is neither a valid compressed or uncompressed key. Key prefix '${publicKey[0]}', key length '${publicKey.length}'` + ) } } diff --git a/packages/core/src/crypto/jose/jwk/P384Jwk.ts b/packages/core/src/crypto/jose/jwk/P384Jwk.ts index b6f30c15c5..53e594689b 100644 --- a/packages/core/src/crypto/jose/jwk/P384Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P384Jwk.ts @@ -1,28 +1,38 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' -import { TypedArrayEncoder, Buffer } from '../../../utils' +import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' import { JwaSignatureAlgorithm } from '../jwa/alg' import { Jwk } from './Jwk' -import { compress, expand } from './ecCompression' +import { + compressECPoint, + expand, + isValidCompressedPublicKey, + isValidUncompressedPublicKey, + PREFIX_UNCOMPRESSED, +} from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' +import { CredoError } from '../../../error' export class P384Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.ES384] public static readonly keyType = KeyType.P384 - public readonly x: string - public readonly y: string + private readonly _x: Uint8Array + private readonly _y: Uint8Array - public constructor({ x, y }: { x: string; y: string }) { + public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - this.x = x - this.y = y + const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x + const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + + this._x = xAsBytes + this._y = yAsBytes } public get kty() { @@ -45,17 +55,26 @@ export class P384Jwk extends Jwk { return P384Jwk.supportedSignatureAlgorithms } + public get x() { + return TypedArrayEncoder.toBase64URL(this._x) + } + + public get y() { + return TypedArrayEncoder.toBase64URL(this._y) + } + /** - * Returns the public key of the P-384 JWK. - * - * NOTE: this is the compressed variant. We still need to add support for the - * uncompressed variant. + * Returns the uncompressed public key of the P-384 JWK. */ public get publicKey() { - const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) - const compressedPublicKey = compress(publicKeyBuffer) + return new Uint8Array([PREFIX_UNCOMPRESSED, ...this._x, ...this._y]) + } - return Buffer.from(compressedPublicKey) + /** + * Returns the compressed public key of the P-384 JWK. + */ + public get publicKeyCompressed() { + return compressECPoint(this._x, this._y) } public toJson() { @@ -78,15 +97,25 @@ export class P384Jwk extends Jwk { }) } - public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.P384) - const x = expanded.slice(0, expanded.length / 2) - const y = expanded.slice(expanded.length / 2) + public static fromPublicKey(publicKey: Uint8Array) { + if (isValidCompressedPublicKey(publicKey, this.keyType)) { + const expanded = expand(publicKey, this.keyType) + const x = expanded.slice(1, expanded.length / 2 + 1) + const y = expanded.slice(expanded.length / 2 + 1) - return new P384Jwk({ - x: TypedArrayEncoder.toBase64URL(x), - y: TypedArrayEncoder.toBase64URL(y), - }) + return new P384Jwk({ x, y }) + } + + if (isValidUncompressedPublicKey(publicKey, this.keyType)) { + const x = publicKey.slice(1, publicKey.length / 2 + 1) + const y = publicKey.slice(publicKey.length / 2 + 1) + + return new P384Jwk({ x, y }) + } + + throw new CredoError( + `${this.keyType} public key is neither a valid compressed or uncompressed key. Key prefix '${publicKey[0]}', key length '${publicKey.length}'` + ) } } diff --git a/packages/core/src/crypto/jose/jwk/P521Jwk.ts b/packages/core/src/crypto/jose/jwk/P521Jwk.ts index 5b7998eff7..d72b363e93 100644 --- a/packages/core/src/crypto/jose/jwk/P521Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P521Jwk.ts @@ -1,28 +1,38 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' -import { TypedArrayEncoder, Buffer } from '../../../utils' +import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' import { JwaSignatureAlgorithm } from '../jwa/alg' import { Jwk } from './Jwk' -import { compress, expand } from './ecCompression' +import { + compressECPoint, + expand, + isValidCompressedPublicKey, + isValidUncompressedPublicKey, + PREFIX_UNCOMPRESSED, +} from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' +import { CredoError } from '../../../error' export class P521Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.ES512] public static readonly keyType = KeyType.P521 - public readonly x: string - public readonly y: string + private readonly _x: Uint8Array + private readonly _y: Uint8Array - public constructor({ x, y }: { x: string; y: string }) { + public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - this.x = x - this.y = y + const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x + const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + + this._x = xAsBytes + this._y = yAsBytes } public get kty() { @@ -45,17 +55,26 @@ export class P521Jwk extends Jwk { return P521Jwk.supportedSignatureAlgorithms } + public get x() { + return TypedArrayEncoder.toBase64URL(this._x) + } + + public get y() { + return TypedArrayEncoder.toBase64URL(this._y) + } + /** - * Returns the public key of the P-521 JWK. - * - * NOTE: this is the compressed variant. We still need to add support for the - * uncompressed variant. + * Returns the uncompressed public key of the P-521 JWK. */ public get publicKey() { - const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) - const compressedPublicKey = compress(publicKeyBuffer) + return new Uint8Array([PREFIX_UNCOMPRESSED, ...this._x, ...this._y]) + } - return Buffer.from(compressedPublicKey) + /** + * Returns the compressed public key of the P-521 JWK. + */ + public get publicKeyCompressed() { + return compressECPoint(this._x, this._y) } public toJson() { @@ -78,15 +97,25 @@ export class P521Jwk extends Jwk { }) } - public static fromPublicKey(publicKey: Buffer) { - const expanded = expand(publicKey, JwaCurve.P521) - const x = expanded.slice(0, expanded.length / 2) - const y = expanded.slice(expanded.length / 2) + public static fromPublicKey(publicKey: Uint8Array) { + if (isValidCompressedPublicKey(publicKey, this.keyType)) { + const expanded = expand(publicKey, this.keyType) + const x = expanded.slice(1, expanded.length / 2 + 1) + const y = expanded.slice(expanded.length / 2 + 1) - return new P521Jwk({ - x: TypedArrayEncoder.toBase64URL(x), - y: TypedArrayEncoder.toBase64URL(y), - }) + return new P521Jwk({ x, y }) + } + + if (isValidUncompressedPublicKey(publicKey, this.keyType)) { + const x = publicKey.slice(1, publicKey.length / 2 + 1) + const y = publicKey.slice(publicKey.length / 2 + 1) + + return new P521Jwk({ x, y }) + } + + throw new CredoError( + `${this.keyType} public key is neither a valid compressed or uncompressed key. Key prefix '${publicKey[0]}', key length '${publicKey.length}'` + ) } } diff --git a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts index 6d1ada04ce..64cf7bab83 100644 --- a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts @@ -1,5 +1,4 @@ import type { JwkJson } from './Jwk' -import type { Buffer } from '../../../utils' import type { JwaSignatureAlgorithm } from '../jwa' import { TypedArrayEncoder } from '../../../utils' @@ -69,7 +68,7 @@ export class X25519Jwk extends Jwk { }) } - public static fromPublicKey(publicKey: Buffer) { + public static fromPublicKey(publicKey: Uint8Array) { return new X25519Jwk({ x: TypedArrayEncoder.toBase64URL(publicKey), }) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/K_256Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/K_256Jwk.test.ts new file mode 100644 index 0000000000..ad2475db4a --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/__tests__/K_256Jwk.test.ts @@ -0,0 +1,57 @@ +import { TypedArrayEncoder } from '../../../../utils' +import { KeyType } from '../../../KeyType' +import { K256Jwk } from '../K256Jwk' +import { compress } from '../ecCompression' + +// Generated with https://mkjwk.org +const jwkJson = { + kty: 'EC', + crv: 'secp256k1', + x: '0CtFvFuEzkEhPOTKHi3k2OvEgJmQ1dH-IXXme3JBzVY', + y: 'vIr8423MqTswmAebHhCaOoiYdp1kyOiduZinD3JBXxU', +} + +const uncompressedPublicKey = new Uint8Array([ + 0x04, + ...TypedArrayEncoder.fromBase64(jwkJson.x), + ...TypedArrayEncoder.fromBase64(jwkJson.y), +]) +const compressedPublicKey = compress(uncompressedPublicKey) + +describe('K_256JWk', () => { + test('has correct properties', () => { + const jwk = new K256Jwk({ x: jwkJson.x, y: jwkJson.y }) + + expect(jwk.kty).toEqual('EC') + expect(jwk.crv).toEqual('secp256k1') + expect(jwk.keyType).toEqual(KeyType.K256) + expect(jwk.supportedEncryptionAlgorithms).toEqual([]) + expect(jwk.supportedSignatureAlgorithms).toEqual(['ES256K']) + expect(jwk.key.keyType).toEqual(KeyType.K256) + expect(jwk.toJson()).toEqual(jwkJson) + + expect(jwk.publicKey).toEqual(uncompressedPublicKey) + expect(jwk.publicKey.length).toEqual(65) + expect(jwk.publicKeyCompressed.length).toEqual(33) + }) + + test('fromJson', () => { + const jwk = K256Jwk.fromJson(jwkJson) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + + expect(() => K256Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrow("Invalid 'K-256' JWK.") + }) + + test('fromUncompressedPublicKey', () => { + const jwk = K256Jwk.fromPublicKey(uncompressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) + + test('fromCompressedPublicKey', () => { + const jwk = K256Jwk.fromPublicKey(compressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) +}) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts index 1250d031d9..926ab5d123 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_256Jwk.test.ts @@ -1,15 +1,23 @@ -import { TypedArrayEncoder, Buffer } from '../../../../utils' +import { TypedArrayEncoder } from '../../../../utils' import { KeyType } from '../../../KeyType' import { P256Jwk } from '../P256Jwk' import { compress } from '../ecCompression' +// Generated with https://mkjwk.org const jwkJson = { kty: 'EC', crv: 'P-256', - x: 'igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns', - y: 'efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM', + x: 'YKIJKqnGI22osL86OZUIGmwW7Bh0ZsUpTVBLVRNyThQ', + y: 'booCsoNXVs1W8GBt9V7DvEktjyWPUV2NFvDrW2aqMfI', } +const uncompressedPublicKey = new Uint8Array([ + 0x04, + ...TypedArrayEncoder.fromBase64(jwkJson.x), + ...TypedArrayEncoder.fromBase64(jwkJson.y), +]) +const compressedPublicKey = compress(uncompressedPublicKey) + describe('P_256JWk', () => { test('has correct properties', () => { const jwk = new P256Jwk({ x: jwkJson.x, y: jwkJson.y }) @@ -17,17 +25,14 @@ describe('P_256JWk', () => { expect(jwk.kty).toEqual('EC') expect(jwk.crv).toEqual('P-256') expect(jwk.keyType).toEqual(KeyType.P256) - - const publicKeyBuffer = Buffer.concat([ - TypedArrayEncoder.fromBase64(jwkJson.x), - TypedArrayEncoder.fromBase64(jwkJson.y), - ]) - const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) - expect(jwk.publicKey).toEqual(compressedPublicKey) expect(jwk.supportedEncryptionAlgorithms).toEqual([]) expect(jwk.supportedSignatureAlgorithms).toEqual(['ES256']) expect(jwk.key.keyType).toEqual(KeyType.P256) expect(jwk.toJson()).toEqual(jwkJson) + + expect(jwk.publicKey).toEqual(uncompressedPublicKey) + expect(jwk.publicKey.length).toEqual(65) + expect(jwk.publicKeyCompressed.length).toEqual(33) }) test('fromJson', () => { @@ -35,16 +40,16 @@ describe('P_256JWk', () => { expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) - expect(() => P256Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-256' JWK.") + expect(() => P256Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrow("Invalid 'P-256' JWK.") }) - test('fromPublicKey', () => { - const publicKeyBuffer = Buffer.concat([ - TypedArrayEncoder.fromBase64(jwkJson.x), - TypedArrayEncoder.fromBase64(jwkJson.y), - ]) - const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + test('fromUncompressedPublicKey', () => { + const jwk = P256Jwk.fromPublicKey(uncompressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) + test('fromCompressedPublicKey', () => { const jwk = P256Jwk.fromPublicKey(compressedPublicKey) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts index 0f409ed878..bf23d4d39b 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_384Jwk.test.ts @@ -1,15 +1,23 @@ -import { TypedArrayEncoder, Buffer } from '../../../../utils' +import { TypedArrayEncoder } from '../../../../utils' import { KeyType } from '../../../KeyType' import { P384Jwk } from '../P384Jwk' import { compress } from '../ecCompression' +// Generated with https://mkjwk.org const jwkJson = { kty: 'EC', crv: 'P-384', - x: 'lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc', - y: 'y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv', + x: 'Rl0BbVOvE0zcytPVSGgM39tihXnlYjuaLin3SjhD6cLRL_IK-3tHTCljCiJBbSX9', + y: '282rUQMBuCkLb0t9PbReApadoP7Jo-sVcZDNGglYg4iMsqNPvyq-WIzxSUb1USpc', } +const uncompressedPublicKey = new Uint8Array([ + 0x04, + ...TypedArrayEncoder.fromBase64(jwkJson.x), + ...TypedArrayEncoder.fromBase64(jwkJson.y), +]) +const compressedPublicKey = compress(uncompressedPublicKey) + describe('P_384JWk', () => { test('has correct properties', () => { const jwk = new P384Jwk({ x: jwkJson.x, y: jwkJson.y }) @@ -17,16 +25,14 @@ describe('P_384JWk', () => { expect(jwk.kty).toEqual('EC') expect(jwk.crv).toEqual('P-384') expect(jwk.keyType).toEqual(KeyType.P384) - const publicKeyBuffer = Buffer.concat([ - TypedArrayEncoder.fromBase64(jwkJson.x), - TypedArrayEncoder.fromBase64(jwkJson.y), - ]) - const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) - expect(jwk.publicKey).toEqual(compressedPublicKey) expect(jwk.supportedEncryptionAlgorithms).toEqual([]) expect(jwk.supportedSignatureAlgorithms).toEqual(['ES384']) expect(jwk.key.keyType).toEqual(KeyType.P384) expect(jwk.toJson()).toEqual(jwkJson) + + expect(jwk.publicKey).toEqual(uncompressedPublicKey) + expect(jwk.publicKey.length).toEqual(97) + expect(jwk.publicKeyCompressed.length).toEqual(49) }) test('fromJson', () => { @@ -34,16 +40,16 @@ describe('P_384JWk', () => { expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) - expect(() => P384Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-384' JWK.") + expect(() => P384Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrow("Invalid 'P-384' JWK.") }) - test('fromPublicKey', () => { - const publicKeyBuffer = Buffer.concat([ - TypedArrayEncoder.fromBase64(jwkJson.x), - TypedArrayEncoder.fromBase64(jwkJson.y), - ]) - const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + test('fromUncompressedPublicKey', () => { + const jwk = P384Jwk.fromPublicKey(uncompressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) + test('fromCompressedPublicKey', () => { const jwk = P384Jwk.fromPublicKey(compressedPublicKey) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) diff --git a/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts index 662fb5ee7b..4a283e58ac 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/P_521Jwk.test.ts @@ -1,15 +1,23 @@ -import { TypedArrayEncoder, Buffer } from '../../../../utils' +import { TypedArrayEncoder } from '../../../../utils' import { KeyType } from '../../../KeyType' import { P521Jwk } from '../P521Jwk' import { compress } from '../ecCompression' +// Generated with https://mkjwk.org const jwkJson = { kty: 'EC', crv: 'P-521', - x: 'ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS', - y: 'AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC', + x: 'AAyV8qWafv5UPexMB3ohAPSFuz_zFdaHAjb-XlzO8qBkx-lZtN1PN1E9AHipP6esSNBPilGOAkiZYnQ48hPJgJQG', + y: 'AccbmJnVXJhxJ8vFS4GcG1eM27XtSOjKz1dX52wbJ0YN6U5KEOPQ-3krxvLAqlFG2BCbZkpnrfateEdervmp3Q3G', } +const uncompressedPublicKey = new Uint8Array([ + 0x04, + ...TypedArrayEncoder.fromBase64(jwkJson.x), + ...TypedArrayEncoder.fromBase64(jwkJson.y), +]) +const compressedPublicKey = compress(uncompressedPublicKey) + describe('P_521JWk', () => { test('has correct properties', () => { const jwk = new P521Jwk({ x: jwkJson.x, y: jwkJson.y }) @@ -17,16 +25,14 @@ describe('P_521JWk', () => { expect(jwk.kty).toEqual('EC') expect(jwk.crv).toEqual('P-521') expect(jwk.keyType).toEqual(KeyType.P521) - const publicKeyBuffer = Buffer.concat([ - TypedArrayEncoder.fromBase64(jwkJson.x), - TypedArrayEncoder.fromBase64(jwkJson.y), - ]) - const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) - expect(jwk.publicKey).toEqual(compressedPublicKey) expect(jwk.supportedEncryptionAlgorithms).toEqual([]) expect(jwk.supportedSignatureAlgorithms).toEqual(['ES512']) expect(jwk.key.keyType).toEqual(KeyType.P521) expect(jwk.toJson()).toEqual(jwkJson) + + expect(jwk.publicKey).toEqual(uncompressedPublicKey) + expect(jwk.publicKey.length).toEqual(133) + expect(jwk.publicKeyCompressed.length).toEqual(67) }) test('fromJson', () => { @@ -34,16 +40,16 @@ describe('P_521JWk', () => { expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) - expect(() => P521Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'P-521' JWK.") + expect(() => P521Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrow("Invalid 'P-521' JWK.") }) - test('fromPublicKey', () => { - const publicKeyBuffer = Buffer.concat([ - TypedArrayEncoder.fromBase64(jwkJson.x), - TypedArrayEncoder.fromBase64(jwkJson.y), - ]) - const compressedPublicKey = Buffer.from(compress(publicKeyBuffer)) + test('fromUncompressedPublicKey', () => { + const jwk = P521Jwk.fromPublicKey(uncompressedPublicKey) + expect(jwk.x).toEqual(jwkJson.x) + expect(jwk.y).toEqual(jwkJson.y) + }) + test('fromCompressedPublicKey', () => { const jwk = P521Jwk.fromPublicKey(compressedPublicKey) expect(jwk.x).toEqual(jwkJson.x) expect(jwk.y).toEqual(jwkJson.y) diff --git a/packages/core/src/crypto/jose/jwk/ecCompression.ts b/packages/core/src/crypto/jose/jwk/ecCompression.ts index f602191e8d..686bbf70f0 100644 --- a/packages/core/src/crypto/jose/jwk/ecCompression.ts +++ b/packages/core/src/crypto/jose/jwk/ecCompression.ts @@ -2,23 +2,26 @@ * Based on https://github.com/transmute-industries/verifiable-data/blob/main/packages/web-crypto-key-pair/src/compression/ec-compression.ts */ +// TODO(crypto): can remove this? // native BigInteger is only supported in React Native 0.70+, so we use big-integer for now. import bigInt from 'big-integer' import { Buffer } from '../../../utils/buffer' -import { JwaCurve } from '../jwa' +import { KeyType } from '../../KeyType' -const curveToPointLength = { - [JwaCurve.P256]: 64, - [JwaCurve.P384]: 96, - [JwaCurve.P521]: 132, - [JwaCurve.Secp256k1]: 64, +type CompressableKey = KeyType.K256 | KeyType.P256 | KeyType.P384 | KeyType.P521 + +const curveToPointLength: Record = { + [KeyType.K256]: 64, + [KeyType.P256]: 64, + [KeyType.P384]: 96, + [KeyType.P521]: 132, } -function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1') { +function getConstantsForCurve(curve: KeyType) { let two, prime, b, pIdent - if (curve === 'P-256') { + if (curve === KeyType.P256) { two = bigInt(2) prime = two.pow(256).subtract(two.pow(224)).add(two.pow(192)).add(two.pow(96)).subtract(1) @@ -27,7 +30,7 @@ function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1') b = bigInt('5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16) } - if (curve === 'P-384') { + if (curve === KeyType.P384) { two = bigInt(2) prime = two.pow(384).subtract(two.pow(128)).subtract(two.pow(96)).add(two.pow(32)).subtract(1) @@ -35,7 +38,7 @@ function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1') b = bigInt('b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16) } - if (curve === 'P-521') { + if (curve === KeyType.P521) { two = bigInt(2) prime = two.pow(521).subtract(1) b = bigInt( @@ -48,7 +51,7 @@ function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1') // https://en.bitcoin.it/wiki/Secp256k1 // p = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F // P = 2256 - 232 - 29 - 28 - 27 - 26 - 24 - 1 - if (curve === JwaCurve.Secp256k1) { + if (curve === KeyType.K256) { two = bigInt(2) prime = two .pow(256) @@ -76,7 +79,7 @@ function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1') * Point compress elliptic curve key * @return Compressed representation */ -function compressECPoint(x: Uint8Array, y: Uint8Array): Uint8Array { +export function compressECPoint(x: Uint8Array, y: Uint8Array): Uint8Array { const out = new Uint8Array(x.length + 1) out[0] = 2 + (y[y.length - 1] & 1) out.set(x, 1) @@ -92,15 +95,20 @@ function padWithZeroes(number: number | string, length: number) { } export function compress(publicKey: Uint8Array): Uint8Array { - const publicKeyHex = Buffer.from(publicKey).toString('hex') - const xHex = publicKeyHex.slice(0, publicKeyHex.length / 2) - const yHex = publicKeyHex.slice(publicKeyHex.length / 2, publicKeyHex.length) - const xOctet = Uint8Array.from(Buffer.from(xHex, 'hex')) - const yOctet = Uint8Array.from(Buffer.from(yHex, 'hex')) - return compressECPoint(xOctet, yOctet) + const x = publicKey.slice(1, publicKey.length / 2 + 1) + const y = publicKey.slice(publicKey.length / 2 + 1) + return compressECPoint(x, y) +} + +export function compressIfPossible(publicKey: Uint8Array, keyType: KeyType): Uint8Array { + return isValidUncompressedPublicKey(publicKey, keyType) ? compress(publicKey) : publicKey +} + +export function expandIfPossible(publicKey: Uint8Array, keyType: KeyType): Uint8Array { + return isValidCompressedPublicKey(publicKey, keyType) ? expand(publicKey, keyType as CompressableKey) : publicKey } -export function expand(publicKey: Uint8Array, curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1'): Uint8Array { +export function expand(publicKey: Uint8Array, curve: CompressableKey): Uint8Array { const publicKeyComponent = Buffer.from(publicKey).toString('hex') const { prime, b, pIdent } = getConstantsForCurve(curve) const signY = new Number(publicKeyComponent[1]).valueOf() - 2 @@ -109,7 +117,7 @@ export function expand(publicKey: Uint8Array, curve: 'P-256' | 'P-384' | 'P-521' // y^2 = x^3 - 3x + b let y = x.pow(3).subtract(x.multiply(3)).add(b).modPow(pIdent, prime) - if (curve === 'secp256k1') { + if (curve === KeyType.K256) { // y^2 = x^3 + 7 y = x.pow(3).add(7).modPow(pIdent, prime) } @@ -120,8 +128,55 @@ export function expand(publicKey: Uint8Array, curve: 'P-256' | 'P-384' | 'P-521' y = prime.subtract(y) } - return Buffer.from( - padWithZeroes(x.toString(16), curveToPointLength[curve]) + padWithZeroes(y.toString(16), curveToPointLength[curve]), - 'hex' + return Uint8Array.from([ + 0x04, + ...Buffer.from( + padWithZeroes(x.toString(16), curveToPointLength[curve]) + + padWithZeroes(y.toString(16), curveToPointLength[curve]), + 'hex' + ), + ]) +} + +export const PREFIX_UNCOMPRESSED = 0x04 +export const PREFIX_COMPRESSED_Y_IS_ODD = 0x03 +export const PREFIX_COMPRESSED_Y_IS_EVEN = 0x02 + +function isCompressedKeyValidLength(length: number, keyType: KeyType) { + switch (keyType) { + case KeyType.K256: + case KeyType.P256: + return length === 33 + case KeyType.P384: + return length === 49 + case KeyType.P521: + return length === 67 || length === 66 + default: + return false + } +} + +function isUncompressedKeyValidLength(length: number, keyType: KeyType) { + switch (keyType) { + case KeyType.K256: + case KeyType.P256: + return length === 65 + case KeyType.P384: + return length === 97 + case KeyType.P521: + return length === 133 || length === 131 + default: + return false + } +} + +export function isValidCompressedPublicKey(publicKey: Uint8Array, keyType: KeyType) { + return ( + isCompressedKeyValidLength(publicKey.length, keyType) && + (publicKey[0] === PREFIX_COMPRESSED_Y_IS_ODD || publicKey[0] === PREFIX_COMPRESSED_Y_IS_EVEN) ) } + +export function isValidUncompressedPublicKey(publicKey: Uint8Array, keyType: KeyType) { + return isUncompressedKeyValidLength(publicKey.length, keyType) && publicKey[0] === PREFIX_UNCOMPRESSED +} diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts index fe2b3f0c03..36db30bb81 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts @@ -41,7 +41,7 @@ describe('bls12381g1', () => { expect(key.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) expect(key.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY)) expect(key.keyType).toBe(KeyType.Bls12381g1) - expect(key.prefixedPublicKey.equals(TEST_BLS12381G1_PREFIX_BYTES)).toBe(true) + expect(Buffer.from(key.prefixedPublicKey).equals(TEST_BLS12381G1_PREFIX_BYTES)).toBe(true) }) it('should return a valid verification method', async () => { @@ -68,7 +68,7 @@ describe('bls12381g1', () => { verificationMethod.type = 'SomeRandomType' - expect(() => keyDidBls12381g1.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + expect(() => keyDidBls12381g1.getKeyFromVerificationMethod(verificationMethod)).toThrow( "Verification method with type 'SomeRandomType' not supported for key type 'bls12381g1'" ) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts index 442422f2cb..aee12f75b9 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts @@ -52,7 +52,7 @@ describe('bls12381g1g2', () => { expect(key.publicKeyBase58).toBe(TEST_BLS12381G1G2_BASE58_KEY) expect(key.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY)) expect(key.keyType).toBe(KeyType.Bls12381g1g2) - expect(key.prefixedPublicKey.equals(TEST_BLS12381G1G2_PREFIX_BYTES)).toBe(true) + expect(Buffer.from(key.prefixedPublicKey).equals(TEST_BLS12381G1G2_PREFIX_BYTES)).toBe(true) }) it('should return a valid verification method', async () => { @@ -73,7 +73,7 @@ describe('bls12381g1g2', () => { VerificationMethod ) - expect(() => keyDidBls12381g1g2.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + expect(() => keyDidBls12381g1g2.getKeyFromVerificationMethod(verificationMethod)).toThrow( 'Not supported for bls12381g1g2 key' ) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts index 14ab8d9fbd..9c5a156a10 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts @@ -43,7 +43,7 @@ describe('bls12381g2', () => { expect(key.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) expect(key.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY)) expect(key.keyType).toBe(KeyType.Bls12381g2) - expect(key.prefixedPublicKey.equals(TEST_BLS12381G2_PREFIX_BYTES)).toBe(true) + expect(Buffer.from(key.prefixedPublicKey).equals(TEST_BLS12381G2_PREFIX_BYTES)).toBe(true) }) it('should return a valid verification method', async () => { diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts index f57600a3c0..9c806b656d 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts @@ -41,7 +41,7 @@ describe('ed25519', () => { expect(didKey.publicKeyBase58).toBe(TEST_ED25519_BASE58_KEY) expect(didKey.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_ED25519_BASE58_KEY)) expect(didKey.keyType).toBe(KeyType.Ed25519) - expect(didKey.prefixedPublicKey.equals(TEST_ED25519_PREFIX_BYTES)).toBe(true) + expect(Buffer.from(didKey.prefixedPublicKey).equals(TEST_ED25519_PREFIX_BYTES)).toBe(true) }) it('should return a valid verification method', async () => { @@ -89,7 +89,7 @@ describe('ed25519', () => { verificationMethod.type = 'SomeRandomType' - expect(() => keyDidEd25519.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + expect(() => keyDidEd25519.getKeyFromVerificationMethod(verificationMethod)).toThrow( "Verification method with type 'SomeRandomType' not supported for key type 'ed25519'" ) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts index aa186aef56..9e529c26b8 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts @@ -35,7 +35,7 @@ describe('keyDidJsonWebKey', () => { verificationMethod.type = 'SomeRandomType' - expect(() => keyDidJsonWebKey.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + expect(() => keyDidJsonWebKey.getKeyFromVerificationMethod(verificationMethod)).toThrow( 'Invalid verification method passed' ) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts index 5fb6490e43..aa8c878aac 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts @@ -41,7 +41,7 @@ describe('x25519', () => { expect(didKey.publicKeyBase58).toBe(TEST_X25519_BASE58_KEY) expect(didKey.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_X25519_BASE58_KEY)) expect(didKey.keyType).toBe(KeyType.X25519) - expect(didKey.prefixedPublicKey.equals(TEST_X25519_PREFIX_BYTES)).toBe(true) + expect(Buffer.from(didKey.prefixedPublicKey).equals(TEST_X25519_PREFIX_BYTES)).toBe(true) }) it('should return a valid verification method', async () => { diff --git a/packages/didcomm/src/decorators/transport/TransportDecorator.test.ts b/packages/didcomm/src/decorators/transport/TransportDecorator.test.ts index 5c2956f6f8..f0f587ed6c 100644 --- a/packages/didcomm/src/decorators/transport/TransportDecorator.test.ts +++ b/packages/didcomm/src/decorators/transport/TransportDecorator.test.ts @@ -6,7 +6,7 @@ const validTransport = (transportJson: Record) => MessageValidator.validateSync(JsonTransformer.fromJSON(transportJson, TransportDecorator)) const expectValid = (transportJson: Record) => expect(validTransport(transportJson)).toBeUndefined() const expectInvalid = (transportJson: Record) => - expect(() => validTransport(transportJson)).toThrowError(ClassValidationError) + expect(() => validTransport(transportJson)).toThrow(ClassValidationError) const valid = { all: { From d06d931031d36a25300c6c821aaff0bf50eeede0 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 30 Jan 2025 15:15:05 +0100 Subject: [PATCH 2/9] fix(x509): parse compressed and uncompressed correctly Signed-off-by: Berend Sliedrecht --- packages/core/src/crypto/Key.ts | 4 ++++ packages/core/src/crypto/jose/jwk/ecCompression.ts | 2 +- packages/core/src/modules/x509/X509Certificate.ts | 14 ++------------ .../src/modules/x509/__tests__/X509Service.test.ts | 13 ++++++++----- tests/InMemoryWallet.ts | 6 ++++-- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index e5751813d5..457f8b1504 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -15,6 +15,10 @@ export class Key { this.keyType = keyType } + public get compressedPublicKey() { + return compressIfPossible(this.publicKey, this.keyType) + } + public static fromPublicKey(publicKey: Uint8Array, keyType: KeyType) { return new Key(publicKey, keyType) } diff --git a/packages/core/src/crypto/jose/jwk/ecCompression.ts b/packages/core/src/crypto/jose/jwk/ecCompression.ts index 686bbf70f0..6d06da3138 100644 --- a/packages/core/src/crypto/jose/jwk/ecCompression.ts +++ b/packages/core/src/crypto/jose/jwk/ecCompression.ts @@ -9,7 +9,7 @@ import bigInt from 'big-integer' import { Buffer } from '../../../utils/buffer' import { KeyType } from '../../KeyType' -type CompressableKey = KeyType.K256 | KeyType.P256 | KeyType.P384 | KeyType.P521 +export type CompressableKey = KeyType.K256 | KeyType.P256 | KeyType.P384 | KeyType.P521 const curveToPointLength: Record = { [KeyType.K256]: 64, diff --git a/packages/core/src/modules/x509/X509Certificate.ts b/packages/core/src/modules/x509/X509Certificate.ts index 0a6433556b..e42d30b770 100644 --- a/packages/core/src/modules/x509/X509Certificate.ts +++ b/packages/core/src/modules/x509/X509Certificate.ts @@ -85,19 +85,9 @@ export class X509Certificate { const privateKey = certificate.privateKey ? new Uint8Array(certificate.privateKey.rawData) : undefined const keyType = spkiAlgorithmIntoCredoKeyType(publicKey.algorithm) + const publicKeyBytes = new Uint8Array(publicKey.subjectPublicKey) - // TODO(crypto): Currently this only does point-compression for P256. - // We should either store all keys as uncompressed, or we should compress all supported keys here correctly - let keyBytes = new Uint8Array(publicKey.subjectPublicKey) - if (publicKey.subjectPublicKey.byteLength === 65 && keyType === KeyType.P256) { - if (keyBytes[0] !== 0x04) { - throw new X509Error('Received P256 key with 65 bytes, but key did not start with 0x04. Invalid key') - } - // TODO(crypto): the compress method is bugged because it does not expect the required `0x04` prefix. Here we strip that and receive the expected result - keyBytes = compress(keyBytes.slice(1)) - } - - const key = new Key(keyBytes, keyType) + const key = new Key(publicKeyBytes, keyType) const extensions = certificate.extensions .map((e) => { diff --git a/packages/core/src/modules/x509/__tests__/X509Service.test.ts b/packages/core/src/modules/x509/__tests__/X509Service.test.ts index f47e37c6f3..fea2fa6c4e 100644 --- a/packages/core/src/modules/x509/__tests__/X509Service.test.ts +++ b/packages/core/src/modules/x509/__tests__/X509Service.test.ts @@ -128,13 +128,16 @@ describe('X509Service', () => { const x509Certificate = X509Service.parseCertificate(agentContext, { encodedCertificate }) - expect(x509Certificate.publicKey.publicKey.length).toStrictEqual(33) - expect(x509Certificate.publicKey.publicKeyBase58).toStrictEqual('23vfBuUJkWXTC3zZWMh1TR57CTubFjFfhm4CGfgszRMHU') + expect(x509Certificate.publicKey.keyType).toStrictEqual(KeyType.P256) + expect(x509Certificate.publicKey.publicKey.length).toStrictEqual(65) + expect(x509Certificate.publicKey.publicKeyBase58).toStrictEqual( + 'QDaLvg9KroUnpuviZ9W7Q3DauqAuKiJN4sKC6cLo4HtxnpJCwwayNBLzRpsCHfHsLJsiKDeTCV8LqmCBSPkmiJNe' + ) const jwk = getJwkFromKey(x509Certificate.publicKey) expect(jwk).toBeInstanceOf(P256Jwk) - expect(jwk).toMatchObject({ + expect(jwk.toJson()).toMatchObject({ x: 'iTwtg0eQbcbNabf2Nq9L_VM_lhhPCq2s0Qgw2kRx29s', y: 'YKwXDRz8U0-uLZ3NSI93R_35eNkl6jHp6Qg8OCup7VM', }) @@ -164,7 +167,7 @@ describe('X509Service', () => { sanUriNames: expect.arrayContaining(['animo.id']), }) - expect(x509Certificate.publicKey.publicKey.length).toStrictEqual(33) + expect(x509Certificate.publicKey.publicKey.length).toStrictEqual(65) }) it('should correctly parse x5c chain provided as a test-vector', async () => { @@ -206,7 +209,7 @@ describe('X509Service', () => { keyUsage: [KeyUsage.DigitalSignature, KeyUsage.KeyCertSign], }) - expect(x509Certificate.publicKey.publicKey.length).toStrictEqual(33) + expect(x509Certificate.publicKey.publicKey.length).toStrictEqual(65) }) it('should validate a valid certificate chain', async () => { diff --git a/tests/InMemoryWallet.ts b/tests/InMemoryWallet.ts index eb16f3eaa3..492e7f6942 100644 --- a/tests/InMemoryWallet.ts +++ b/tests/InMemoryWallet.ts @@ -28,6 +28,7 @@ import { TypedArrayEncoder, KeyBackend, } from '@credo-ts/core' +import { expandIfPossible } from '../packages/core/src/crypto/jose/jwk/ecCompression' const inMemoryWallets: InMemoryWallets = {} @@ -183,8 +184,9 @@ export class InMemoryWallet implements Wallet { : AskarKey.generate(algorithm, convertToAskarKeyBackend(keyBackend)) const keyPublicBytes = key.publicBytes + // Store key - this.getInMemoryKeys()[TypedArrayEncoder.toBase58(keyPublicBytes)] = { + this.getInMemoryKeys()[TypedArrayEncoder.toBase58(expandIfPossible(keyPublicBytes, keyType))] = { publicKeyBytes: keyPublicBytes, secretKeyBytes: key.secretBytes, keyType, @@ -260,7 +262,7 @@ export class InMemoryWallet implements Wallet { try { askarKey = AskarKey.fromPublicBytes({ algorithm: keyAlgFromString(key.keyType), - publicKey: key.publicKey, + publicKey: key.compressedPublicKey, }) return askarKey.verifySignature({ message: data as Buffer, signature }) } finally { From 87d47a78a66cdb4dd5e9db690ba3ab1f1bae3b04 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 30 Jan 2025 15:25:26 +0100 Subject: [PATCH 3/9] fix: tests compare uint8array with uint8array Signed-off-by: Berend Sliedrecht --- packages/askar/package.json | 2 +- .../src/secureEnvironment/secureEnvironment.ts | 4 ++-- packages/askar/src/wallet/AskarBaseWallet.ts | 5 ++++- .../domain/key-type/__tests__/bls12381g1.test.ts | 2 +- .../domain/key-type/__tests__/bls12381g1g2.test.ts | 6 +++--- .../domain/key-type/__tests__/bls12381g2.test.ts | 2 +- pnpm-lock.yaml | 13 ++----------- 7 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/askar/package.json b/packages/askar/package.json index 48730d5638..7d9174564a 100644 --- a/packages/askar/package.json +++ b/packages/askar/package.json @@ -34,7 +34,7 @@ "tsyringe": "^4.8.0" }, "devDependencies": { - "@animo-id/expo-secure-environment": "^0.1.0-alpha.11", + "@animo-id/expo-secure-environment": "^0.1.0-alpha.12", "@hyperledger/aries-askar-nodejs": "^0.2.3", "@hyperledger/aries-askar-shared": "^0.2.3", "@types/bn.js": "^5.1.0", diff --git a/packages/askar/src/secureEnvironment/secureEnvironment.ts b/packages/askar/src/secureEnvironment/secureEnvironment.ts index 5c94febd7a..42bcfa7db3 100644 --- a/packages/askar/src/secureEnvironment/secureEnvironment.ts +++ b/packages/askar/src/secureEnvironment/secureEnvironment.ts @@ -1,7 +1,7 @@ export function importSecureEnvironment(): { sign: (id: string, message: Uint8Array) => Promise - getPublicBytesForKeyId: (id: string) => Uint8Array - generateKeypair: (id: string) => void + getPublicBytesForKeyId: (id: string) => Uint8Array | Promise + generateKeypair: (id: string) => void | Promise } { throw new Error( '@animo-id/expo-secure-environment cannot be imported in Node.js. Currently, there is no hardware key support for node.js' diff --git a/packages/askar/src/wallet/AskarBaseWallet.ts b/packages/askar/src/wallet/AskarBaseWallet.ts index 5ae967425b..75e032f59b 100644 --- a/packages/askar/src/wallet/AskarBaseWallet.ts +++ b/packages/askar/src/wallet/AskarBaseWallet.ts @@ -50,6 +50,7 @@ import { } from '../utils' import { didcommV1Pack, didcommV1Unpack } from './didcommV1' +import { compressIfPossible, expandIfPossible } from 'packages/core/src/crypto/jose/jwk/ecCompression' const isError = (error: unknown): error is Error => error instanceof Error @@ -206,7 +207,9 @@ export abstract class AskarBaseWallet implements Wallet { // Generate a hardware-backed P-256 keypair await secureEnvironment.generateKeypair(kid) - const publicKeyBytes = await secureEnvironment.getPublicBytesForKeyId(kid) + const compressedPublicKeyBytes = await secureEnvironment.getPublicBytesForKeyId(kid) + + const publicKeyBytes = expandIfPossible(compressedPublicKeyBytes, keyType) const publicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyBytes) await this.storeSecureEnvironmentKeyById({ diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts index 36db30bb81..fc05105baa 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts @@ -39,7 +39,7 @@ describe('bls12381g1', () => { expect(key.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) expect(key.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) - expect(key.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY)) + expect(key.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY))) expect(key.keyType).toBe(KeyType.Bls12381g1) expect(Buffer.from(key.prefixedPublicKey).equals(TEST_BLS12381G1_PREFIX_BYTES)).toBe(true) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts index aee12f75b9..85ac115900 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts @@ -50,7 +50,7 @@ describe('bls12381g1g2', () => { expect(key.fingerprint).toBe(TEST_BLS12381G1G2_FINGERPRINT) expect(key.publicKeyBase58).toBe(TEST_BLS12381G1G2_BASE58_KEY) - expect(key.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY)) + expect(key.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY))) expect(key.keyType).toBe(KeyType.Bls12381g1g2) expect(Buffer.from(key.prefixedPublicKey).equals(TEST_BLS12381G1G2_PREFIX_BYTES)).toBe(true) }) @@ -86,7 +86,7 @@ describe('bls12381g1g2', () => { expect(g1DidKey.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) expect(g1DidKey.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) - expect(g1DidKey.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY)) + expect(g1DidKey.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY))) expect(g1DidKey.keyType).toBe(KeyType.Bls12381g1) }) @@ -98,7 +98,7 @@ describe('bls12381g1g2', () => { expect(g2DidKey.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) expect(g2DidKey.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) - expect(g2DidKey.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY)) + expect(g2DidKey.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY))) expect(g2DidKey.keyType).toBe(KeyType.Bls12381g2) }) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts index 9c5a156a10..254455e1ae 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts @@ -41,7 +41,7 @@ describe('bls12381g2', () => { expect(key.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) expect(key.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) - expect(key.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY)) + expect(key.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY))) expect(key.keyType).toBe(KeyType.Bls12381g2) expect(Buffer.from(key.prefixedPublicKey).equals(TEST_BLS12381G2_PREFIX_BYTES)).toBe(true) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d3528d15b..f654cbb659 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -339,7 +339,7 @@ importers: version: 4.8.0 devDependencies: '@animo-id/expo-secure-environment': - specifier: ^0.1.0-alpha.11 + specifier: ^0.1.0-alpha.12 version: 0.1.0-alpha.12(expo@51.0.14(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.71.19(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(react@18.3.1))(react@18.3.1) '@hyperledger/aries-askar-nodejs': specifier: ^0.2.3 @@ -2756,9 +2756,6 @@ packages: '@peculiar/asn1-schema@2.3.13': resolution: {integrity: sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==} - '@peculiar/asn1-schema@2.3.8': - resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} - '@peculiar/asn1-x509-attr@2.3.13': resolution: {integrity: sha512-WpEos6CcnUzJ6o2Qb68Z7Dz5rSjRGv/DtXITCNBtjZIRWRV12yFVci76SVfOX8sisL61QWMhpLKQibrG8pi2Pw==} @@ -8376,7 +8373,7 @@ snapshots: '@animo-id/expo-secure-environment@0.1.0-alpha.12(expo@51.0.14(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.71.19(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(react@18.3.1))(react@18.3.1)': dependencies: '@peculiar/asn1-ecc': 2.3.14 - '@peculiar/asn1-schema': 2.3.8 + '@peculiar/asn1-schema': 2.3.13 '@peculiar/asn1-x509': 2.3.13 expo: 51.0.14(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)) react: 18.3.1 @@ -11085,12 +11082,6 @@ snapshots: pvtsutils: 1.3.5 tslib: 2.8.1 - '@peculiar/asn1-schema@2.3.8': - dependencies: - asn1js: 3.0.5 - pvtsutils: 1.3.5 - tslib: 2.6.3 - '@peculiar/asn1-x509-attr@2.3.13': dependencies: '@peculiar/asn1-schema': 2.3.13 From b52ec3d0a8b8d6af08bdae51bd4367799cb381f1 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 30 Jan 2025 16:02:05 +0100 Subject: [PATCH 4/9] chore: run linter Signed-off-by: Berend Sliedrecht --- packages/askar/src/wallet/AskarBaseWallet.ts | 2 +- packages/core/src/crypto/JwsService.ts | 3 +-- packages/core/src/crypto/Key.ts | 2 +- packages/core/src/crypto/jose/jwk/K256Jwk.ts | 2 +- packages/core/src/crypto/jose/jwk/P256Jwk.ts | 2 +- packages/core/src/crypto/jose/jwk/P384Jwk.ts | 2 +- packages/core/src/crypto/jose/jwk/P521Jwk.ts | 2 +- packages/core/src/crypto/jose/jwk/index.ts | 1 + packages/core/src/modules/x509/X509Certificate.ts | 2 -- tests/InMemoryWallet.ts | 2 +- 10 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/askar/src/wallet/AskarBaseWallet.ts b/packages/askar/src/wallet/AskarBaseWallet.ts index 75e032f59b..e26a2f7d83 100644 --- a/packages/askar/src/wallet/AskarBaseWallet.ts +++ b/packages/askar/src/wallet/AskarBaseWallet.ts @@ -28,6 +28,7 @@ import { KeyBackend, KeyType, utils, + expandIfPossible, } from '@credo-ts/core' import { CryptoBox, @@ -50,7 +51,6 @@ import { } from '../utils' import { didcommV1Pack, didcommV1Unpack } from './didcommV1' -import { compressIfPossible, expandIfPossible } from 'packages/core/src/crypto/jose/jwk/ecCompression' const isError = (error: unknown): error is Error => error instanceof Error diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 431730df97..055c3754f8 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -9,12 +9,11 @@ import type { Key } from './Key' import type { Jwk } from './jose/jwk' import type { JwkJson } from './jose/jwk/Jwk' import type { AgentContext } from '../agent' -import { Buffer } from '../utils' import { CredoError } from '../error' import { EncodedX509Certificate, X509ModuleConfig } from '../modules/x509' import { injectable } from '../plugins' -import { isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils' +import { Buffer, isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils' import { WalletError } from '../wallet/error' import { X509Service } from './../modules/x509/X509Service' diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index 457f8b1504..e92a802281 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -2,9 +2,9 @@ import type { KeyType } from './KeyType' import { MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' +import { compressIfPossible, expandIfPossible } from './jose/jwk/ecCompression' import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './keyUtils' import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey' -import { compressIfPossible, expandIfPossible } from './jose/jwk/ecCompression' export class Key { public readonly publicKey: Uint8Array diff --git a/packages/core/src/crypto/jose/jwk/K256Jwk.ts b/packages/core/src/crypto/jose/jwk/K256Jwk.ts index 584a545728..162b747c03 100644 --- a/packages/core/src/crypto/jose/jwk/K256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/K256Jwk.ts @@ -1,6 +1,7 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' +import { CredoError } from '../../../error' import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' @@ -15,7 +16,6 @@ import { PREFIX_UNCOMPRESSED, } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -import { CredoError } from '../../../error' export class K256Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] diff --git a/packages/core/src/crypto/jose/jwk/P256Jwk.ts b/packages/core/src/crypto/jose/jwk/P256Jwk.ts index 526511563a..f1b02693dd 100644 --- a/packages/core/src/crypto/jose/jwk/P256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P256Jwk.ts @@ -1,6 +1,7 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' +import { CredoError } from '../../../error' import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' @@ -15,7 +16,6 @@ import { PREFIX_UNCOMPRESSED, } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -import { CredoError } from '../../../error' export class P256Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] diff --git a/packages/core/src/crypto/jose/jwk/P384Jwk.ts b/packages/core/src/crypto/jose/jwk/P384Jwk.ts index 53e594689b..848bb821ce 100644 --- a/packages/core/src/crypto/jose/jwk/P384Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P384Jwk.ts @@ -1,6 +1,7 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' +import { CredoError } from '../../../error' import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' @@ -15,7 +16,6 @@ import { PREFIX_UNCOMPRESSED, } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -import { CredoError } from '../../../error' export class P384Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] diff --git a/packages/core/src/crypto/jose/jwk/P521Jwk.ts b/packages/core/src/crypto/jose/jwk/P521Jwk.ts index d72b363e93..7ab0783a2e 100644 --- a/packages/core/src/crypto/jose/jwk/P521Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P521Jwk.ts @@ -1,6 +1,7 @@ import type { JwkJson } from './Jwk' import type { JwaEncryptionAlgorithm } from '../jwa/alg' +import { CredoError } from '../../../error' import { TypedArrayEncoder } from '../../../utils' import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' @@ -15,7 +16,6 @@ import { PREFIX_UNCOMPRESSED, } from './ecCompression' import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' -import { CredoError } from '../../../error' export class P521Jwk extends Jwk { public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = [] diff --git a/packages/core/src/crypto/jose/jwk/index.ts b/packages/core/src/crypto/jose/jwk/index.ts index 7579a74778..10eb3e4450 100644 --- a/packages/core/src/crypto/jose/jwk/index.ts +++ b/packages/core/src/crypto/jose/jwk/index.ts @@ -1,4 +1,5 @@ export * from './transform' +export * from './ecCompression' export { Ed25519Jwk } from './Ed25519Jwk' export { X25519Jwk } from './X25519Jwk' export { P256Jwk } from './P256Jwk' diff --git a/packages/core/src/modules/x509/X509Certificate.ts b/packages/core/src/modules/x509/X509Certificate.ts index e42d30b770..7a5a07191e 100644 --- a/packages/core/src/modules/x509/X509Certificate.ts +++ b/packages/core/src/modules/x509/X509Certificate.ts @@ -12,8 +12,6 @@ import { import * as x509 from '@peculiar/x509' import { Key } from '../../crypto/Key' -import { KeyType } from '../../crypto/KeyType' -import { compress } from '../../crypto/jose/jwk/ecCompression' import { CredoWebCrypto, CredoWebCryptoKey } from '../../crypto/webcrypto' import { credoKeyTypeIntoCryptoKeyAlgorithm, spkiAlgorithmIntoCredoKeyType } from '../../crypto/webcrypto/utils' import { TypedArrayEncoder } from '../../utils' diff --git a/tests/InMemoryWallet.ts b/tests/InMemoryWallet.ts index 492e7f6942..9b9453aa02 100644 --- a/tests/InMemoryWallet.ts +++ b/tests/InMemoryWallet.ts @@ -13,6 +13,7 @@ import BigNumber from 'bn.js' import { convertToAskarKeyBackend } from '../packages/askar/src/utils/askarKeyBackend' import { didcommV1Pack, didcommV1Unpack } from '../packages/askar/src/wallet/didcommV1' +import { expandIfPossible } from '../packages/core/src/crypto/jose/jwk/ecCompression' import { JsonEncoder, @@ -28,7 +29,6 @@ import { TypedArrayEncoder, KeyBackend, } from '@credo-ts/core' -import { expandIfPossible } from '../packages/core/src/crypto/jose/jwk/ecCompression' const inMemoryWallets: InMemoryWallets = {} From c499267e31a224d59941fcd1ad77070e2421dc79 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 30 Jan 2025 16:29:38 +0100 Subject: [PATCH 5/9] chore: return uint8array for coordinates in JWK Signed-off-by: Berend Sliedrecht --- .../core/src/crypto/__tests__/JwsService.test.ts | 2 +- packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts | 16 +++++++++------- packages/core/src/crypto/jose/jwk/K256Jwk.ts | 4 ++-- packages/core/src/crypto/jose/jwk/P256Jwk.ts | 4 ++-- packages/core/src/crypto/jose/jwk/P384Jwk.ts | 4 ++-- packages/core/src/crypto/jose/jwk/P521Jwk.ts | 4 ++-- packages/core/src/crypto/jose/jwk/X25519Jwk.ts | 16 +++++++++------- .../domain/key-type/__tests__/ed25519.test.ts | 2 +- .../domain/key-type/__tests__/x25519.test.ts | 2 +- packages/core/src/wallet/Wallet.ts | 4 ++-- 10 files changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index d6654aaa0d..036837f19d 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -147,7 +147,7 @@ describe('JwsService', () => { jwsService.verifyJws(agentContext, { jws: { signatures: [], payload: '' }, }) - ).rejects.toThrowError('Unable to verify JWS, no signatures present in JWS.') + ).rejects.toThrow('Unable to verify JWS, no signatures present in JWS.') }) }) }) diff --git a/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts b/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts index 86e33687e3..56d7cf5ceb 100644 --- a/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts @@ -14,12 +14,16 @@ export class Ed25519Jwk extends Jwk { public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.EdDSA] public static readonly keyType = KeyType.Ed25519 - public readonly x: string + private readonly _x: Uint8Array - public constructor({ x }: { x: string }) { + public constructor({ x }: { x: string | Uint8Array }) { super() - this.x = x + this._x = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x + } + + public get x() { + return TypedArrayEncoder.toBase64URL(this._x) } public get kty() { @@ -31,7 +35,7 @@ export class Ed25519Jwk extends Jwk { } public get publicKey() { - return TypedArrayEncoder.fromBase64(this.x) + return this._x } public get keyType() { @@ -65,9 +69,7 @@ export class Ed25519Jwk extends Jwk { } public static fromPublicKey(publicKey: Uint8Array) { - return new Ed25519Jwk({ - x: TypedArrayEncoder.toBase64URL(publicKey), - }) + return new Ed25519Jwk({ x: publicKey }) } } diff --git a/packages/core/src/crypto/jose/jwk/K256Jwk.ts b/packages/core/src/crypto/jose/jwk/K256Jwk.ts index 162b747c03..3c32308907 100644 --- a/packages/core/src/crypto/jose/jwk/K256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/K256Jwk.ts @@ -28,8 +28,8 @@ export class K256Jwk extends Jwk { public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x - const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + const xAsBytes = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x + const yAsBytes = typeof y === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(y)) : y this._x = xAsBytes this._y = yAsBytes diff --git a/packages/core/src/crypto/jose/jwk/P256Jwk.ts b/packages/core/src/crypto/jose/jwk/P256Jwk.ts index f1b02693dd..42423d176e 100644 --- a/packages/core/src/crypto/jose/jwk/P256Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P256Jwk.ts @@ -28,8 +28,8 @@ export class P256Jwk extends Jwk { public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x - const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + const xAsBytes = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x + const yAsBytes = typeof y === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(y)) : y this._x = xAsBytes this._y = yAsBytes diff --git a/packages/core/src/crypto/jose/jwk/P384Jwk.ts b/packages/core/src/crypto/jose/jwk/P384Jwk.ts index 848bb821ce..39053f3b97 100644 --- a/packages/core/src/crypto/jose/jwk/P384Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P384Jwk.ts @@ -28,8 +28,8 @@ export class P384Jwk extends Jwk { public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x - const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + const xAsBytes = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x + const yAsBytes = typeof y === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(y)) : y this._x = xAsBytes this._y = yAsBytes diff --git a/packages/core/src/crypto/jose/jwk/P521Jwk.ts b/packages/core/src/crypto/jose/jwk/P521Jwk.ts index 7ab0783a2e..e59433a997 100644 --- a/packages/core/src/crypto/jose/jwk/P521Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/P521Jwk.ts @@ -28,8 +28,8 @@ export class P521Jwk extends Jwk { public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) { super() - const xAsBytes = typeof x === 'string' ? TypedArrayEncoder.fromBase64(x) : x - const yAsBytes = typeof y === 'string' ? TypedArrayEncoder.fromBase64(y) : y + const xAsBytes = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x + const yAsBytes = typeof y === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(y)) : y this._x = xAsBytes this._y = yAsBytes diff --git a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts index 64cf7bab83..78c22a1a72 100644 --- a/packages/core/src/crypto/jose/jwk/X25519Jwk.ts +++ b/packages/core/src/crypto/jose/jwk/X25519Jwk.ts @@ -18,12 +18,16 @@ export class X25519Jwk extends Jwk { public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [] public static readonly keyType = KeyType.X25519 - public readonly x: string + private readonly _x: Uint8Array - public constructor({ x }: { x: string }) { + public constructor({ x }: { x: string | Uint8Array }) { super() - this.x = x + this._x = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x + } + + public get x() { + return TypedArrayEncoder.toBase64URL(this._x) } public get kty() { @@ -47,7 +51,7 @@ export class X25519Jwk extends Jwk { } public get publicKey() { - return TypedArrayEncoder.fromBase64(this.x) + return this._x } public toJson() { @@ -69,9 +73,7 @@ export class X25519Jwk extends Jwk { } public static fromPublicKey(publicKey: Uint8Array) { - return new X25519Jwk({ - x: TypedArrayEncoder.toBase64URL(publicKey), - }) + return new X25519Jwk({ x: publicKey }) } } diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts index 9c806b656d..cf86cc533d 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts @@ -39,7 +39,7 @@ describe('ed25519', () => { expect(didKey.fingerprint).toBe(TEST_ED25519_FINGERPRINT) expect(didKey.publicKeyBase58).toBe(TEST_ED25519_BASE58_KEY) - expect(didKey.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_ED25519_BASE58_KEY)) + expect(didKey.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_ED25519_BASE58_KEY))) expect(didKey.keyType).toBe(KeyType.Ed25519) expect(Buffer.from(didKey.prefixedPublicKey).equals(TEST_ED25519_PREFIX_BYTES)).toBe(true) }) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts index aa8c878aac..7055e34b32 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts @@ -39,7 +39,7 @@ describe('x25519', () => { expect(didKey.fingerprint).toBe(TEST_X25519_FINGERPRINT) expect(didKey.publicKeyBase58).toBe(TEST_X25519_BASE58_KEY) - expect(didKey.publicKey).toEqual(TypedArrayEncoder.fromBase58(TEST_X25519_BASE58_KEY)) + expect(didKey.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase58(TEST_X25519_BASE58_KEY))) expect(didKey.keyType).toBe(KeyType.X25519) expect(Buffer.from(didKey.prefixedPublicKey).equals(TEST_X25519_PREFIX_BYTES)).toBe(true) }) diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index dcd2a46863..bfa10125e4 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -97,8 +97,8 @@ export interface Wallet extends Disposable { export interface WalletCreateKeyOptions { keyType: KeyType - seed?: Buffer - privateKey?: Buffer + seed?: Uint8Array + privateKey?: Uint8Array keyBackend?: KeyBackend keyId?: string } From 9ff76c6dbd8f7ee923a50415c6abd3217c322ce2 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 30 Jan 2025 16:42:26 +0100 Subject: [PATCH 6/9] chore: convert buffer into uint8array Signed-off-by: Berend Sliedrecht --- packages/askar/src/wallet/AskarBaseWallet.ts | 6 ++++-- packages/bbs-signatures/tests/bbs-signing-provider.test.ts | 2 +- packages/core/src/crypto/Key.ts | 4 ++-- .../core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts | 4 ++-- .../core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts | 4 ++-- packages/core/src/wallet/Wallet.ts | 4 ++-- .../services/__tests__/MediationRecipientService.test.ts | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/askar/src/wallet/AskarBaseWallet.ts b/packages/askar/src/wallet/AskarBaseWallet.ts index e26a2f7d83..6e64244625 100644 --- a/packages/askar/src/wallet/AskarBaseWallet.ts +++ b/packages/askar/src/wallet/AskarBaseWallet.ts @@ -182,7 +182,7 @@ export abstract class AskarBaseWallet implements Wallet { // This will be fixed once we use the new 'using' syntax key = _key - const keyPublicBytes = key.publicBytes + const keyPublicBytes = expandIfPossible(key.publicBytes, keyType) // Store key await this.withSession((session) => @@ -352,7 +352,9 @@ export abstract class AskarBaseWallet implements Wallet { if (!isError(error)) { throw new CredoError('Attempted to throw error, but it was not of type Error', { cause: error }) } - throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}. ${error.message}`, { cause: error }) + throw new WalletError(`Error signing data with key associated with ${key.publicKeyBase58}. ${error.message}`, { + cause: error, + }) } finally { askarKey?.handle.free() } diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.test.ts index 21acb9988f..7870473b55 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.test.ts @@ -52,7 +52,7 @@ describeSkipNode18('BBS Signing Provider', () => { key, }) ).rejects.toThrow( - 'Error signing data with verkey AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2' + 'Error signing data with key associated with AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2' ) }) diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index e92a802281..997f5f8c46 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -24,7 +24,7 @@ export class Key { } public static fromPublicKeyBase58(publicKey: string, keyType: KeyType) { - const publicKeyBytes = TypedArrayEncoder.fromBase58(publicKey) + const publicKeyBytes = Uint8Array.from(TypedArrayEncoder.fromBase58(publicKey)) return Key.fromPublicKey(publicKeyBytes, keyType) } @@ -70,7 +70,7 @@ export class Key { // We return an object structure based on the key, so that when this object is // serialized to JSON it will be nicely formatted instead of the bytes printed - public toJSON() { + private toJSON() { return { keyType: this.keyType, publicKeyBase58: this.publicKeyBase58, diff --git a/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts index a2f07ecc7f..e5903063a6 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/Ed25519Jwk.test.ts @@ -15,7 +15,7 @@ describe('Ed25519JWk', () => { expect(jwk.kty).toEqual('OKP') expect(jwk.crv).toEqual('Ed25519') expect(jwk.keyType).toEqual(KeyType.Ed25519) - expect(jwk.publicKey).toEqual(TypedArrayEncoder.fromBase64(jwkJson.x)) + expect(jwk.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase64(jwkJson.x))) expect(jwk.supportedEncryptionAlgorithms).toEqual([]) expect(jwk.supportedSignatureAlgorithms).toEqual(['EdDSA']) expect(jwk.key.keyType).toEqual(KeyType.Ed25519) @@ -26,7 +26,7 @@ describe('Ed25519JWk', () => { const jwk = Ed25519Jwk.fromJson(jwkJson) expect(jwk.x).toEqual(jwkJson.x) - expect(() => Ed25519Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'Ed25519' JWK.") + expect(() => Ed25519Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrow("Invalid 'Ed25519' JWK.") }) test('fromPublicKey', () => { diff --git a/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts b/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts index 138e3f59b4..c582a63ca2 100644 --- a/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts +++ b/packages/core/src/crypto/jose/jwk/__tests__/X25519Jwk.test.ts @@ -15,7 +15,7 @@ describe('X25519JWk', () => { expect(jwk.kty).toEqual('OKP') expect(jwk.crv).toEqual('X25519') expect(jwk.keyType).toEqual(KeyType.X25519) - expect(jwk.publicKey).toEqual(TypedArrayEncoder.fromBase64(jwkJson.x)) + expect(jwk.publicKey).toEqual(Uint8Array.from(TypedArrayEncoder.fromBase64(jwkJson.x))) expect(jwk.supportedEncryptionAlgorithms).toEqual(['ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW', 'ECDH-ES']) expect(jwk.supportedSignatureAlgorithms).toEqual([]) expect(jwk.key.keyType).toEqual(KeyType.X25519) @@ -26,7 +26,7 @@ describe('X25519JWk', () => { const jwk = X25519Jwk.fromJson(jwkJson) expect(jwk.x).toEqual(jwkJson.x) - expect(() => X25519Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrowError("Invalid 'X25519' JWK.") + expect(() => X25519Jwk.fromJson({ ...jwkJson, kty: 'test' })).toThrow("Invalid 'X25519' JWK.") }) test('fromPublicKey', () => { diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index bfa10125e4..dcd2a46863 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -97,8 +97,8 @@ export interface Wallet extends Disposable { export interface WalletCreateKeyOptions { keyType: KeyType - seed?: Uint8Array - privateKey?: Uint8Array + seed?: Buffer + privateKey?: Buffer keyBackend?: KeyBackend keyId?: string } diff --git a/packages/didcomm/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/didcomm/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 20075ad282..611f62cd11 100644 --- a/packages/didcomm/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/didcomm/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -223,7 +223,7 @@ describe('MediationRecipientService', () => { expect(extendedRouting).toMatchObject({ endpoints: ['https://a-mediator-endpoint.com'], - routingKeys: [routingKey], + routingKeys: routing.routingKeys, }) expect(mediationRepository.findSingleByQuery).toHaveBeenCalledWith(agentContext, { default: true }) }) From d037fbcd0e5239380197a9ba4141bb592b57294f Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 6 Feb 2025 13:00:32 +0100 Subject: [PATCH 7/9] chore: remove usage of bn libraries Signed-off-by: Berend Sliedrecht --- package.json | 2 - packages/anoncreds/package.json | 2 - .../AnonCredsDataIntegrityService.ts | 3 +- packages/anoncreds/src/utils/credential.ts | 9 +- .../tests/InMemoryAnonCredsRegistry.ts | 5 +- packages/askar/package.json | 2 - packages/askar/src/wallet/AskarBaseWallet.ts | 3 +- packages/core/package.json | 1 - .../core/src/crypto/jose/jwk/ecCompression.ts | 122 +++++++++--------- pnpm-lock.yaml | 28 ---- tests/InMemoryWallet.ts | 3 +- 11 files changed, 74 insertions(+), 106 deletions(-) diff --git a/package.json b/package.json index f1d2ebef5b..e7d09d5e9c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "@changesets/cli": "^2.27.5", "@hyperledger/aries-askar-nodejs": "^0.2.3", "@jest/types": "^29.6.3", - "@types/bn.js": "^5.1.5", "@types/cors": "^2.8.10", "@types/eslint": "^8.21.2", "@types/express": "^4.17.13", @@ -49,7 +48,6 @@ "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", - "bn.js": "^5.2.1", "cors": "^2.8.5", "eslint": "^8.36.0", "eslint-config-prettier": "^8.3.0", diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json index 7bee830493..f5aa4ef404 100644 --- a/packages/anoncreds/package.json +++ b/packages/anoncreds/package.json @@ -30,8 +30,6 @@ "@credo-ts/core": "workspace:*", "@credo-ts/didcomm": "workspace:*", "@sphereon/pex-models": "^2.3.1", - "big-integer": "^1.6.51", - "bn.js": "^5.2.1", "class-transformer": "0.5.1", "class-validator": "0.14.1", "reflect-metadata": "^0.1.13" diff --git a/packages/anoncreds/src/anoncreds-rs/AnonCredsDataIntegrityService.ts b/packages/anoncreds/src/anoncreds-rs/AnonCredsDataIntegrityService.ts index 9894f41702..ee1f6fa808 100644 --- a/packages/anoncreds/src/anoncreds-rs/AnonCredsDataIntegrityService.ts +++ b/packages/anoncreds/src/anoncreds-rs/AnonCredsDataIntegrityService.ts @@ -24,7 +24,6 @@ import { injectable, ClaimFormat, } from '@credo-ts/core' -import BigNumber from 'bn.js' import { AnonCredsHolderServiceSymbol, AnonCredsVerifierServiceSymbol } from '../services' import { fetchCredentialDefinitions, fetchSchemas } from '../utils/anonCredsObjects' @@ -163,7 +162,7 @@ export class AnonCredsDataIntegrityService implements IAnonCredsDataIntegritySer const credentialsWithMetadata: CredentialWithRevocationMetadata[] = [] const hash = Hasher.hash(TypedArrayEncoder.fromString(challenge), 'sha-256') - const nonce = new BigNumber(hash).toString().slice(0, 20) + const nonce = Buffer.from(hash).toString().slice(0, 20) const anonCredsProofRequest: AnonCredsProofRequest = { version: '1.0', diff --git a/packages/anoncreds/src/utils/credential.ts b/packages/anoncreds/src/utils/credential.ts index e015ec26aa..9963b3f219 100644 --- a/packages/anoncreds/src/utils/credential.ts +++ b/packages/anoncreds/src/utils/credential.ts @@ -1,9 +1,8 @@ import type { AnonCredsSchema, AnonCredsCredentialValues } from '../models' import type { CredentialPreviewAttributeOptions, LinkedAttachment } from '@credo-ts/didcomm' -import { Buffer, CredoError, Hasher, TypedArrayEncoder } from '@credo-ts/core' +import { CredoError, Hasher, TypedArrayEncoder } from '@credo-ts/core' import { encodeAttachment } from '@credo-ts/didcomm' -import bigInt from 'big-integer' export type AnonCredsClaimRecord = Record @@ -66,9 +65,11 @@ export function encodeCredentialValue(value: unknown) { const buffer = TypedArrayEncoder.fromString(String(value)) const hash = Hasher.hash(buffer, 'sha-256') - const hex = Buffer.from(hash).toString('hex') - return bigInt(hex, 16).toString() + return hash + .slice(1) + .reduce((acc, byte) => (acc << 8n) | BigInt(byte), 0n) + .toString() } export const mapAttributeRawValuesToAnonCredsCredentialValues = ( diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index fa397be680..c58034bae0 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -20,7 +20,6 @@ import type { import type { AgentContext } from '@credo-ts/core' import { Hasher, utils } from '@credo-ts/core' -import BigNumber from 'bn.js' import { getDidIndyCredentialDefinitionId, @@ -377,7 +376,9 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { * Does this by hashing the schema id, transforming the hash to a number and taking the first 6 digits. */ function getSeqNoFromSchemaId(schemaId: string) { - const seqNo = Number(new BigNumber(Hasher.hash(schemaId, 'sha-256')).toString().slice(0, 5)) + const hash = Hasher.hash(schemaId, 'sha-256') + const hashNumber = hash.reduce((acc, byte) => (acc << 8n) | BigInt(byte), 0n) + const seqNo = Number(hashNumber.toString().slice(0, 5)) return seqNo } diff --git a/packages/askar/package.json b/packages/askar/package.json index 7d9174564a..8fcc93cac3 100644 --- a/packages/askar/package.json +++ b/packages/askar/package.json @@ -27,7 +27,6 @@ }, "dependencies": { "@credo-ts/core": "workspace:*", - "bn.js": "^5.2.1", "class-transformer": "0.5.1", "class-validator": "0.14.1", "rxjs": "^7.8.0", @@ -37,7 +36,6 @@ "@animo-id/expo-secure-environment": "^0.1.0-alpha.12", "@hyperledger/aries-askar-nodejs": "^0.2.3", "@hyperledger/aries-askar-shared": "^0.2.3", - "@types/bn.js": "^5.1.0", "@types/ref-array-di": "^1.2.6", "@types/ref-struct-di": "^1.1.10", "reflect-metadata": "^0.1.13", diff --git a/packages/askar/src/wallet/AskarBaseWallet.ts b/packages/askar/src/wallet/AskarBaseWallet.ts index 6e64244625..cd329fbba4 100644 --- a/packages/askar/src/wallet/AskarBaseWallet.ts +++ b/packages/askar/src/wallet/AskarBaseWallet.ts @@ -39,7 +39,6 @@ import { KeyAlgs, Jwk, } from '@hyperledger/aries-askar-shared' -import BigNumber from 'bn.js' import { importSecureEnvironment } from '../secureEnvironment' import { @@ -597,7 +596,7 @@ export abstract class AskarBaseWallet implements Wallet { try { // generate an 80-bit nonce suitable for AnonCreds proofs const nonce = CryptoBox.randomNonce().slice(0, 10) - return new BigNumber(nonce).toString() + return nonce.reduce((acc, byte) => (acc << 8n) | BigInt(byte), 0n).toString() } catch (error) { if (!isError(error)) { throw new CredoError('Attempted to throw error, but it was not of type Error', { cause: error }) diff --git a/packages/core/package.json b/packages/core/package.json index 2117d99997..e1ed759baf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,7 +47,6 @@ "@sphereon/ssi-types": "0.30.2-next.135", "@stablelib/ed25519": "^1.0.2", "@types/ws": "^8.5.4", - "big-integer": "^1.6.51", "borc": "^3.0.0", "buffer": "^6.0.3", "class-transformer": "0.5.1", diff --git a/packages/core/src/crypto/jose/jwk/ecCompression.ts b/packages/core/src/crypto/jose/jwk/ecCompression.ts index 6d06da3138..d92aca7f20 100644 --- a/packages/core/src/crypto/jose/jwk/ecCompression.ts +++ b/packages/core/src/crypto/jose/jwk/ecCompression.ts @@ -1,11 +1,3 @@ -/** - * Based on https://github.com/transmute-industries/verifiable-data/blob/main/packages/web-crypto-key-pair/src/compression/ec-compression.ts - */ - -// TODO(crypto): can remove this? -// native BigInteger is only supported in React Native 0.70+, so we use big-integer for now. -import bigInt from 'big-integer' - import { Buffer } from '../../../utils/buffer' import { KeyType } from '../../KeyType' @@ -18,59 +10,53 @@ const curveToPointLength: Record = { [KeyType.P521]: 132, } +/** + * + * Get constants for a specified curve + * + * pIdent = (p + 1) / 4 but precomputed for performance + * + */ function getConstantsForCurve(curve: KeyType) { - let two, prime, b, pIdent + let p, b, pIdent + // https://neuromancer.sk/std/nist/P-256 if (curve === KeyType.P256) { - two = bigInt(2) - prime = two.pow(256).subtract(two.pow(224)).add(two.pow(192)).add(two.pow(96)).subtract(1) - - pIdent = prime.add(1).divide(4) - - b = bigInt('5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16) + p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn + pIdent = 0x3fffffffc0000000400000000000000000000000400000000000000000000000n + b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn } + // https://neuromancer.sk/std/nist/P-384 if (curve === KeyType.P384) { - two = bigInt(2) - prime = two.pow(384).subtract(two.pow(128)).subtract(two.pow(96)).add(two.pow(32)).subtract(1) - - pIdent = prime.add(1).divide(4) - b = bigInt('b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16) + p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn + pIdent = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffffffc00000000000000040000000n + b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aefn } + // https://neuromancer.sk/std/nist/P-521 if (curve === KeyType.P521) { - two = bigInt(2) - prime = two.pow(521).subtract(1) - b = bigInt( - '00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00', - 16 - ) - pIdent = prime.add(1).divide(4) + p = + 6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151n + p = + 0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn + pIdent = + 0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n + b = + 0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00n } - // https://en.bitcoin.it/wiki/Secp256k1 - // p = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F - // P = 2256 - 232 - 29 - 28 - 27 - 26 - 24 - 1 if (curve === KeyType.K256) { - two = bigInt(2) - prime = two - .pow(256) - .subtract(two.pow(32)) - .subtract(two.pow(9)) - .subtract(two.pow(8)) - .subtract(two.pow(7)) - .subtract(two.pow(6)) - .subtract(two.pow(4)) - .subtract(1) - b = bigInt(7) - pIdent = prime.add(1).divide(4) + p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn + pIdent = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0cn + b = 0x7n } - if (!prime || !b || !pIdent) { + if (!p || !b || !pIdent) { throw new Error(`Unsupported curve ${curve}`) } - return { prime, b, pIdent } + return { p, b, pIdent } } // see https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression @@ -109,27 +95,20 @@ export function expandIfPossible(publicKey: Uint8Array, keyType: KeyType): Uint8 } export function expand(publicKey: Uint8Array, curve: CompressableKey): Uint8Array { - const publicKeyComponent = Buffer.from(publicKey).toString('hex') - const { prime, b, pIdent } = getConstantsForCurve(curve) - const signY = new Number(publicKeyComponent[1]).valueOf() - 2 - const x = bigInt(publicKeyComponent.substring(2), 16) + const { p, b, pIdent } = getConstantsForCurve(curve) - // y^2 = x^3 - 3x + b - let y = x.pow(3).subtract(x.multiply(3)).add(b).modPow(pIdent, prime) + const signY = publicKey[0] + const x = publicKey.slice(1).reduce((acc, byte) => (acc << 8n) | BigInt(byte), 0n) - if (curve === KeyType.K256) { - // y^2 = x^3 + 7 - y = x.pow(3).add(7).modPow(pIdent, prime) - } + let y = curve === KeyType.K256 ? x ** 3n + 7n : x ** 3n - x * 3n + b + y = modPow(y, pIdent, p) - // If the parity doesn't match it's the *other* root - if (y.mod(2).toJSNumber() !== signY) { - // y = prime - y - y = prime.subtract(y) + if (signY % 2 === 1) { + y = p - y } return Uint8Array.from([ - 0x04, + PREFIX_UNCOMPRESSED, ...Buffer.from( padWithZeroes(x.toString(16), curveToPointLength[curve]) + padWithZeroes(y.toString(16), curveToPointLength[curve]), @@ -180,3 +159,28 @@ export function isValidCompressedPublicKey(publicKey: Uint8Array, keyType: KeyTy export function isValidUncompressedPublicKey(publicKey: Uint8Array, keyType: KeyType) { return isUncompressedKeyValidLength(publicKey.length, keyType) && publicKey[0] === PREFIX_UNCOMPRESSED } + +/** + * + * Modular exponentiation optimalization + * + * Equivalent to: base ** exponent % modulus + * However, because base ** exponent can be very large, this optimalization has to be done + * to keep the number managable for the BigInt type in JS + * + */ +function modPow(base: bigint, exponent: bigint, modulus: bigint) { + if (modulus === 1n) return 0n + + let result = 1n + base = ((base % modulus) + modulus) % modulus + + while (exponent > 0n) { + if (exponent & 1n) { + result = (result * base) % modulus + } + base = (base * base) % modulus + exponent >>= 1n + } + return result +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f654cbb659..42432fdaee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,9 +27,6 @@ importers: '@jest/types': specifier: ^29.6.3 version: 29.6.3 - '@types/bn.js': - specifier: ^5.1.5 - version: 5.1.5 '@types/cors': specifier: ^2.8.10 version: 2.8.17 @@ -63,9 +60,6 @@ importers: '@typescript-eslint/parser': specifier: ^7.14.1 version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) - bn.js: - specifier: ^5.2.1 - version: 5.2.1 cors: specifier: ^2.8.5 version: 2.8.5 @@ -282,12 +276,6 @@ importers: '@sphereon/pex-models': specifier: ^2.3.1 version: 2.3.2 - big-integer: - specifier: ^1.6.51 - version: 1.6.52 - bn.js: - specifier: ^5.2.1 - version: 5.2.1 class-transformer: specifier: 0.5.1 version: 0.5.1 @@ -322,9 +310,6 @@ importers: '@credo-ts/core': specifier: workspace:* version: link:../core - bn.js: - specifier: ^5.2.1 - version: 5.2.1 class-transformer: specifier: 0.5.1 version: 0.5.1 @@ -347,9 +332,6 @@ importers: '@hyperledger/aries-askar-shared': specifier: ^0.2.3 version: 0.2.3 - '@types/bn.js': - specifier: ^5.1.0 - version: 5.1.5 '@types/ref-array-di': specifier: ^1.2.6 version: 1.2.8 @@ -514,9 +496,6 @@ importers: '@types/ws': specifier: ^8.5.4 version: 8.5.10 - big-integer: - specifier: ^1.6.51 - version: 1.6.52 borc: specifier: ^3.0.0 version: 3.0.0 @@ -3116,9 +3095,6 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/bn.js@5.1.5': - resolution: {integrity: sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==} - '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} @@ -11996,10 +11972,6 @@ snapshots: dependencies: '@babel/types': 7.24.7 - '@types/bn.js@5.1.5': - dependencies: - '@types/node': 18.18.8 - '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 diff --git a/tests/InMemoryWallet.ts b/tests/InMemoryWallet.ts index 9b9453aa02..a0ee071581 100644 --- a/tests/InMemoryWallet.ts +++ b/tests/InMemoryWallet.ts @@ -9,7 +9,6 @@ import type { } from '@credo-ts/core' import { CryptoBox, Store, Key as AskarKey, keyAlgFromString } from '@hyperledger/aries-askar-nodejs' -import BigNumber from 'bn.js' import { convertToAskarKeyBackend } from '../packages/askar/src/utils/askarKeyBackend' import { didcommV1Pack, didcommV1Unpack } from '../packages/askar/src/wallet/didcommV1' @@ -340,7 +339,7 @@ export class InMemoryWallet implements Wallet { try { // generate an 80-bit nonce suitable for AnonCreds proofs const nonce = CryptoBox.randomNonce().slice(0, 10) - return new BigNumber(nonce).toString() + return nonce.reduce((acc, byte) => (acc << 8n) | BigInt(byte), 0n).toString() } catch (error) { if (!isError(error)) { throw new CredoError('Attempted to throw error, but it was not of type Error', { cause: error }) From 4758e8bf0dbf13c6856d2bcb39d83b7a3aa637f8 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 6 Feb 2025 14:02:28 +0100 Subject: [PATCH 8/9] chore: resolve feedback Signed-off-by: Berend Sliedrecht --- packages/askar/src/wallet/AskarBaseWallet.ts | 15 +++++++++------ .../tests/bbs-signing-provider.test.ts | 2 +- packages/core/src/crypto/Key.ts | 4 ++-- .../core/src/crypto/jose/jwk/ecCompression.ts | 2 +- tests/InMemoryWallet.ts | 4 ++-- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/askar/src/wallet/AskarBaseWallet.ts b/packages/askar/src/wallet/AskarBaseWallet.ts index cd329fbba4..72b3f0678b 100644 --- a/packages/askar/src/wallet/AskarBaseWallet.ts +++ b/packages/askar/src/wallet/AskarBaseWallet.ts @@ -28,7 +28,7 @@ import { KeyBackend, KeyType, utils, - expandIfPossible, + expandPublicKeyIfPossible, } from '@credo-ts/core' import { CryptoBox, @@ -181,7 +181,7 @@ export abstract class AskarBaseWallet implements Wallet { // This will be fixed once we use the new 'using' syntax key = _key - const keyPublicBytes = expandIfPossible(key.publicBytes, keyType) + const keyPublicBytes = expandPublicKeyIfPossible(key.publicBytes, keyType) // Store key await this.withSession((session) => @@ -208,7 +208,7 @@ export abstract class AskarBaseWallet implements Wallet { await secureEnvironment.generateKeypair(kid) const compressedPublicKeyBytes = await secureEnvironment.getPublicBytesForKeyId(kid) - const publicKeyBytes = expandIfPossible(compressedPublicKeyBytes, keyType) + const publicKeyBytes = expandPublicKeyIfPossible(compressedPublicKeyBytes, keyType) const publicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyBytes) await this.storeSecureEnvironmentKeyById({ @@ -351,9 +351,12 @@ export abstract class AskarBaseWallet implements Wallet { if (!isError(error)) { throw new CredoError('Attempted to throw error, but it was not of type Error', { cause: error }) } - throw new WalletError(`Error signing data with key associated with ${key.publicKeyBase58}. ${error.message}`, { - cause: error, - }) + throw new WalletError( + `Error signing data with key associated with publicKeyBase58 ${key.publicKeyBase58}. ${error.message}`, + { + cause: error, + } + ) } finally { askarKey?.handle.free() } diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.test.ts index 7870473b55..a63d59b84d 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.test.ts @@ -52,7 +52,7 @@ describeSkipNode18('BBS Signing Provider', () => { key, }) ).rejects.toThrow( - 'Error signing data with key associated with AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2' + 'Error signing data with key associated with publicKeyBase58 AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2' ) }) diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index 997f5f8c46..4f5ff950dc 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -2,7 +2,7 @@ import type { KeyType } from './KeyType' import { MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' -import { compressIfPossible, expandIfPossible } from './jose/jwk/ecCompression' +import { compressIfPossible, expandPublicKeyIfPossible } from './jose/jwk/ecCompression' import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './keyUtils' import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey' @@ -11,7 +11,7 @@ export class Key { public readonly keyType: KeyType public constructor(publicKey: Uint8Array, keyType: KeyType) { - this.publicKey = expandIfPossible(publicKey, keyType) + this.publicKey = expandPublicKeyIfPossible(publicKey, keyType) this.keyType = keyType } diff --git a/packages/core/src/crypto/jose/jwk/ecCompression.ts b/packages/core/src/crypto/jose/jwk/ecCompression.ts index d92aca7f20..811eb1cb68 100644 --- a/packages/core/src/crypto/jose/jwk/ecCompression.ts +++ b/packages/core/src/crypto/jose/jwk/ecCompression.ts @@ -90,7 +90,7 @@ export function compressIfPossible(publicKey: Uint8Array, keyType: KeyType): Uin return isValidUncompressedPublicKey(publicKey, keyType) ? compress(publicKey) : publicKey } -export function expandIfPossible(publicKey: Uint8Array, keyType: KeyType): Uint8Array { +export function expandPublicKeyIfPossible(publicKey: Uint8Array, keyType: KeyType): Uint8Array { return isValidCompressedPublicKey(publicKey, keyType) ? expand(publicKey, keyType as CompressableKey) : publicKey } diff --git a/tests/InMemoryWallet.ts b/tests/InMemoryWallet.ts index a0ee071581..8990e3f252 100644 --- a/tests/InMemoryWallet.ts +++ b/tests/InMemoryWallet.ts @@ -12,7 +12,7 @@ import { CryptoBox, Store, Key as AskarKey, keyAlgFromString } from '@hyperledge import { convertToAskarKeyBackend } from '../packages/askar/src/utils/askarKeyBackend' import { didcommV1Pack, didcommV1Unpack } from '../packages/askar/src/wallet/didcommV1' -import { expandIfPossible } from '../packages/core/src/crypto/jose/jwk/ecCompression' +import { expandPublicKeyIfPossible } from '../packages/core' import { JsonEncoder, @@ -185,7 +185,7 @@ export class InMemoryWallet implements Wallet { const keyPublicBytes = key.publicBytes // Store key - this.getInMemoryKeys()[TypedArrayEncoder.toBase58(expandIfPossible(keyPublicBytes, keyType))] = { + this.getInMemoryKeys()[TypedArrayEncoder.toBase58(expandPublicKeyIfPossible(keyPublicBytes, keyType))] = { publicKeyBytes: keyPublicBytes, secretKeyBytes: key.secretBytes, keyType, From 30ff14a1751406eeb22edbf58a8b4b61f152cb30 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 6 Feb 2025 14:05:45 +0100 Subject: [PATCH 9/9] docs(changeset): - Remove usage of Big Number libraries and rely on native implementations - By default rely on uncompressed keys instead of compressed (for P256, P384, P521 and K256) - Utilze Uint8Array more instead of Buffer (i.e. for internally representing a key) Signed-off-by: Berend Sliedrecht --- .changeset/cyan-parents-relax.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/cyan-parents-relax.md diff --git a/.changeset/cyan-parents-relax.md b/.changeset/cyan-parents-relax.md new file mode 100644 index 0000000000..0b97d5b06e --- /dev/null +++ b/.changeset/cyan-parents-relax.md @@ -0,0 +1,9 @@ +--- +'@credo-ts/anoncreds': patch +'@credo-ts/askar': patch +'@credo-ts/core': patch +--- + +- Remove usage of Big Number libraries and rely on native implementations +- By default rely on uncompressed keys instead of compressed (for P256, P384, P521 and K256) +- Utilze Uint8Array more instead of Buffer (i.e. for internally representing a key)