diff --git a/packages/askar/src/utils/askarKeyTypes.ts b/packages/askar/src/utils/askarKeyTypes.ts index cbbf3713d2..5288ccc565 100644 --- a/packages/askar/src/utils/askarKeyTypes.ts +++ b/packages/askar/src/utils/askarKeyTypes.ts @@ -29,7 +29,11 @@ const keyTypeToAskarAlg = { }, [KeyType.P256]: { keyAlg: KeyAlgs.EcSecp256r1, - purposes: [AskarKeyTypePurpose.KeyManagement], + purposes: [AskarKeyTypePurpose.KeyManagement, AskarKeyTypePurpose.Signing], + }, + [KeyType.K256]: { + keyAlg: KeyAlgs.EcSecp256k1, + purposes: [AskarKeyTypePurpose.KeyManagement, AskarKeyTypePurpose.Signing], }, } diff --git a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts index a07c6a1e6a..ecc1e15b9d 100644 --- a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts +++ b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts @@ -59,6 +59,7 @@ describe('AskarWallet basic operations', () => { KeyType.Bls12381g2, KeyType.Bls12381g1g2, KeyType.P256, + KeyType.K256, ]) }) diff --git a/packages/core/src/crypto/KeyType.ts b/packages/core/src/crypto/KeyType.ts index d378e4bffb..cb85ab608d 100644 --- a/packages/core/src/crypto/KeyType.ts +++ b/packages/core/src/crypto/KeyType.ts @@ -7,4 +7,5 @@ export enum KeyType { P256 = 'p256', P384 = 'p384', P521 = 'p521', + K256 = 'k256', } diff --git a/packages/core/src/crypto/jose/jwa/alg.ts b/packages/core/src/crypto/jose/jwa/alg.ts index ea2ba50c8d..07e32d98da 100644 --- a/packages/core/src/crypto/jose/jwa/alg.ts +++ b/packages/core/src/crypto/jose/jwa/alg.ts @@ -12,6 +12,7 @@ export enum JwaSignatureAlgorithm { PS384 = 'PS384', PS512 = 'PS512', EdDSA = 'EdDSA', + ES256K = 'ES256K', None = 'none', } diff --git a/packages/core/src/crypto/jose/jwa/crv.ts b/packages/core/src/crypto/jose/jwa/crv.ts index bdf2c4a010..d663c2ebb4 100644 --- a/packages/core/src/crypto/jose/jwa/crv.ts +++ b/packages/core/src/crypto/jose/jwa/crv.ts @@ -4,4 +4,5 @@ export enum JwaCurve { P521 = 'P-521', Ed25519 = 'Ed25519', X25519 = 'X25519', + Secp256k1 = 'secp256k1', } diff --git a/packages/core/src/crypto/jose/jwk/K256Jwk.ts b/packages/core/src/crypto/jose/jwk/K256Jwk.ts new file mode 100644 index 0000000000..898c1c23b9 --- /dev/null +++ b/packages/core/src/crypto/jose/jwk/K256Jwk.ts @@ -0,0 +1,112 @@ +import type { JwkJson } from './Jwk' +import type { JwaEncryptionAlgorithm } from '../jwa/alg' + +import { TypedArrayEncoder, Buffer } 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 { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate' + +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 + + public constructor({ x, y }: { x: string; y: string }) { + super() + + this.x = x + this.y = y + } + + public get kty() { + return JwaKeyType.EC as const + } + + public get crv() { + return JwaCurve.Secp256k1 as const + } + + /** + * 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. + */ + public get publicKey() { + const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)]) + const compressedPublicKey = compress(publicKeyBuffer) + + return Buffer.from(compressedPublicKey) + } + + public get keyType() { + return K256Jwk.keyType + } + + public get supportedEncryptionAlgorithms() { + return K256Jwk.supportedEncryptionAlgorithms + } + + public get supportedSignatureAlgorithms() { + return K256Jwk.supportedSignatureAlgorithms + } + + public toJson() { + return { + ...super.toJson(), + crv: this.crv, + x: this.x, + y: this.y, + } as K256JwkJson + } + + public static fromJson(jwkJson: JwkJson) { + if (!isValidP256JwkPublicKey(jwkJson)) { + throw new Error("Invalid 'K-256' JWK.") + } + + return new K256Jwk({ + x: jwkJson.x, + y: jwkJson.y, + }) + } + + 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) + + return new K256Jwk({ + x: TypedArrayEncoder.toBase64URL(x), + y: TypedArrayEncoder.toBase64URL(y), + }) + } +} + +export interface K256JwkJson extends JwkJson { + kty: JwaKeyType.EC + crv: JwaCurve.Secp256k1 + x: string + y: string + use?: 'sig' | 'enc' +} + +export function isValidP256JwkPublicKey(jwk: JwkJson): jwk is K256JwkJson { + return ( + hasKty(jwk, JwaKeyType.EC) && + hasCrv(jwk, JwaCurve.Secp256k1) && + hasX(jwk) && + hasY(jwk) && + hasValidUse(jwk, { + supportsEncrypting: true, + supportsSigning: true, + }) + ) +} diff --git a/packages/core/src/crypto/jose/jwk/ecCompression.ts b/packages/core/src/crypto/jose/jwk/ecCompression.ts index cfde82d11d..f602191e8d 100644 --- a/packages/core/src/crypto/jose/jwk/ecCompression.ts +++ b/packages/core/src/crypto/jose/jwk/ecCompression.ts @@ -6,14 +6,16 @@ import bigInt from 'big-integer' import { Buffer } from '../../../utils/buffer' +import { JwaCurve } from '../jwa' const curveToPointLength = { - 'P-256': 64, - 'P-384': 96, - 'P-521': 132, + [JwaCurve.P256]: 64, + [JwaCurve.P384]: 96, + [JwaCurve.P521]: 132, + [JwaCurve.Secp256k1]: 64, } -function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521') { +function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1') { let two, prime, b, pIdent if (curve === 'P-256') { @@ -43,6 +45,24 @@ function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521') { pIdent = prime.add(1).divide(4) } + // 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) { + 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) + } + if (!prime || !b || !pIdent) { throw new Error(`Unsupported curve ${curve}`) } @@ -80,14 +100,20 @@ export function compress(publicKey: Uint8Array): Uint8Array { return compressECPoint(xOctet, yOctet) } -export function expand(publicKey: Uint8Array, curve: 'P-256' | 'P-384' | 'P-521'): Uint8Array { +export function expand(publicKey: Uint8Array, curve: 'P-256' | 'P-384' | 'P-521' | 'secp256k1'): 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) + // y^2 = x^3 - 3x + b let y = x.pow(3).subtract(x.multiply(3)).add(b).modPow(pIdent, prime) + if (curve === 'secp256k1') { + // y^2 = x^3 + 7 + y = x.pow(3).add(7).modPow(pIdent, prime) + } + // If the parity doesn't match it's the *other* root if (y.mod(2).toJSNumber() !== signY) { // y = prime - y diff --git a/packages/core/src/crypto/jose/jwk/transform.ts b/packages/core/src/crypto/jose/jwk/transform.ts index ed9e2b968a..145029984f 100644 --- a/packages/core/src/crypto/jose/jwk/transform.ts +++ b/packages/core/src/crypto/jose/jwk/transform.ts @@ -7,13 +7,14 @@ import { KeyType } from '../../KeyType' import { JwaCurve, JwaKeyType } from '../jwa' import { Ed25519Jwk } from './Ed25519Jwk' +import { K256Jwk } from './K256Jwk' import { P256Jwk } from './P256Jwk' import { P384Jwk } from './P384Jwk' import { P521Jwk } from './P521Jwk' import { X25519Jwk } from './X25519Jwk' import { hasCrv } from './validate' -const JwkClasses = [Ed25519Jwk, P256Jwk, P384Jwk, P521Jwk, X25519Jwk] as const +const JwkClasses = [Ed25519Jwk, P256Jwk, P384Jwk, P521Jwk, X25519Jwk, K256Jwk] as const export function getJwkFromJson(jwkJson: JwkJson): Jwk { if (jwkJson.kty === JwaKeyType.OKP) { @@ -25,6 +26,7 @@ export function getJwkFromJson(jwkJson: JwkJson): Jwk { if (hasCrv(jwkJson, JwaCurve.P256)) return P256Jwk.fromJson(jwkJson) if (hasCrv(jwkJson, JwaCurve.P384)) return P384Jwk.fromJson(jwkJson) if (hasCrv(jwkJson, JwaCurve.P521)) return P521Jwk.fromJson(jwkJson) + if (hasCrv(jwkJson, JwaCurve.Secp256k1)) return K256Jwk.fromJson(jwkJson) } throw new Error(`Cannot create JWK from JSON. Unsupported JWK with kty '${jwkJson.kty}'.`) @@ -38,6 +40,8 @@ export function getJwkFromKey(key: Key) { if (key.keyType === KeyType.P384) return P384Jwk.fromPublicKey(key.publicKey) if (key.keyType === KeyType.P521) return P521Jwk.fromPublicKey(key.publicKey) + if (key.keyType === KeyType.K256) return K256Jwk.fromPublicKey(key.publicKey) + throw new AriesFrameworkError(`Cannot create JWK from key. Unsupported key with type '${key.keyType}'.`) } diff --git a/packages/core/src/crypto/keyUtils.ts b/packages/core/src/crypto/keyUtils.ts index 7b667f7aa7..14b229fc8d 100644 --- a/packages/core/src/crypto/keyUtils.ts +++ b/packages/core/src/crypto/keyUtils.ts @@ -12,6 +12,7 @@ export function isValidSeed(seed: Buffer, keyType: KeyType): boolean { [KeyType.P256]: 64, [KeyType.P384]: 64, [KeyType.P521]: 64, + [KeyType.K256]: 64, } as const return Buffer.isBuffer(seed) && seed.length >= minimumSeedLength[keyType] @@ -27,6 +28,7 @@ export function isValidPrivateKey(privateKey: Buffer, keyType: KeyType): boolean [KeyType.P256]: 32, [KeyType.P384]: 48, [KeyType.P521]: 66, + [KeyType.K256]: 32, } as const return Buffer.isBuffer(privateKey) && privateKey.length === privateKeyLength[keyType] @@ -42,6 +44,7 @@ export function isSigningSupportedForKeyType(keyType: KeyType): boolean { [KeyType.Bls12381g1]: true, [KeyType.Bls12381g2]: true, [KeyType.Bls12381g1g2]: true, + [KeyType.K256]: true, } as const return keyTypeSigningSupportedMapping[keyType] @@ -57,6 +60,7 @@ export function isEncryptionSupportedForKeyType(keyType: KeyType): boolean { [KeyType.Bls12381g1]: false, [KeyType.Bls12381g2]: false, [KeyType.Bls12381g1g2]: false, + [KeyType.K256]: true, } as const return keyTypeEncryptionSupportedMapping[keyType] diff --git a/packages/core/src/crypto/multiCodecKey.ts b/packages/core/src/crypto/multiCodecKey.ts index 1ebcbd5ca9..249978a4d3 100644 --- a/packages/core/src/crypto/multiCodecKey.ts +++ b/packages/core/src/crypto/multiCodecKey.ts @@ -10,6 +10,7 @@ const multiCodecPrefixMap: Record = { 4608: KeyType.P256, 4609: KeyType.P384, 4610: KeyType.P521, + 231: KeyType.K256, } export function getKeyTypeByMultiCodecPrefix(multiCodecPrefix: number): KeyType { diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyK256.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyK256.json new file mode 100644 index 0000000000..aae86c5876 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyK256.json @@ -0,0 +1,29 @@ +{ + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], + "id": "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp", + "verificationMethod": [ + { + "id": "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp#zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp", + "type": "JsonWebKey2020", + "controller": "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp", + "publicKeyJwk": { + "kty": "EC", + "crv": "secp256k1", + "x": "RwiZITTa2Dcmq-V1j-5tgPUshOLO31FbsnhVS-7lskc", + "y": "3o1-UCc3ABh757P58gDISSc4hOj9qyfSGl3SGGA7xdc" + } + } + ], + "authentication": [ + "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp#zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp" + ], + "assertionMethod": [ + "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp#zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp" + ], + "capabilityInvocation": [ + "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp#zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp" + ], + "capabilityDelegation": [ + "did:key:zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp#zQ3shjRPgHQQbTtXyofk1ygghRJ75RZpXmWBMY1BKnhyz7zKp" + ] +} diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts index dfd277e60c..e8dced5ea7 100644 --- a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts +++ b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts @@ -34,6 +34,7 @@ const keyDidMapping: Record = { [KeyType.P256]: keyDidJsonWebKey, [KeyType.P384]: keyDidJsonWebKey, [KeyType.P521]: keyDidJsonWebKey, + [KeyType.K256]: keyDidJsonWebKey, } /** diff --git a/packages/core/src/modules/dids/domain/keyDidDocument.ts b/packages/core/src/modules/dids/domain/keyDidDocument.ts index 73204d94a0..609b786071 100644 --- a/packages/core/src/modules/dids/domain/keyDidDocument.ts +++ b/packages/core/src/modules/dids/domain/keyDidDocument.ts @@ -27,6 +27,7 @@ const didDocumentKeyTypeMapping: Record DidD [KeyType.P256]: getJsonWebKey2020DidDocument, [KeyType.P384]: getJsonWebKey2020DidDocument, [KeyType.P521]: getJsonWebKey2020DidDocument, + [KeyType.K256]: getJsonWebKey2020DidDocument, } export function getDidDocumentForKey(did: string, key: Key) { diff --git a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts index a9a854cb1a..5994e3baeb 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts @@ -4,6 +4,7 @@ import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.j import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' +import didKeyK256 from '../../../__tests__/__fixtures__/didKeyK256.json' import didKeyP256 from '../../../__tests__/__fixtures__/didKeyP256.json' import didKeyP384 from '../../../__tests__/__fixtures__/didKeyP384.json' import didKeyP521 from '../../../__tests__/__fixtures__/didKeyP521.json' @@ -21,6 +22,7 @@ describe('DidKey', () => { didKeyP256, didKeyP384, didKeyP521, + didKeyK256, ] for (const documentType of documentTypes) {