Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for key type k256 #1722

Merged
merged 13 commits into from
Jan 31, 2024
1 change: 1 addition & 0 deletions packages/askar/src/utils/askarKeyTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const keyTypeToAskarAlg = {
[KeyType.Bls12381g2]: KeyAlgs.Bls12381G2,
[KeyType.Bls12381g1g2]: KeyAlgs.Bls12381G1G2,
[KeyType.P256]: KeyAlgs.EcSecp256r1,
[KeyType.K256]: KeyAlgs.EcSecp256k1,
} as const

export const isKeyTypeSupportedByAskar = (keyType: KeyType) => keyType in keyTypeToAskarAlg
Expand Down
1 change: 1 addition & 0 deletions packages/askar/src/wallet/__tests__/AskarWallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('AskarWallet basic operations', () => {
KeyType.Bls12381g2,
KeyType.Bls12381g1g2,
KeyType.P256,
KeyType.K256,
])
})

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/crypto/KeyType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export enum KeyType {
P256 = 'p256',
P384 = 'p384',
P521 = 'p521',
K256 = 'k256',
}
1 change: 1 addition & 0 deletions packages/core/src/crypto/jose/jwa/crv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export enum JwaCurve {
P521 = 'P-521',
Ed25519 = 'Ed25519',
X25519 = 'X25519',
SECP256K1 = 'secp256k1',
sairanjit marked this conversation as resolved.
Show resolved Hide resolved
}
112 changes: 112 additions & 0 deletions packages/core/src/crypto/jose/jwk/K256Jwk.ts
Original file line number Diff line number Diff line change
@@ -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.ES256]
sairanjit marked this conversation as resolved.
Show resolved Hide resolved
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,
})
)
}
26 changes: 24 additions & 2 deletions packages/core/src/crypto/jose/jwk/ecCompression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ const curveToPointLength = {
'P-256': 64,
sairanjit marked this conversation as resolved.
Show resolved Hide resolved
'P-384': 96,
'P-521': 132,
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') {
Expand Down Expand Up @@ -43,6 +44,21 @@ function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521') {
pIdent = prime.add(1).divide(4)
}

if (curve === 'secp256k1') {
sairanjit marked this conversation as resolved.
Show resolved Hide resolved
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}`)
}
Expand Down Expand Up @@ -80,14 +96,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
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/crypto/jose/jwk/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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'
Expand All @@ -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}'.`)
Expand All @@ -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}'.`)
}

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/crypto/keyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/crypto/multiCodecKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const multiCodecPrefixMap: Record<string, KeyType> = {
4608: KeyType.P256,
4609: KeyType.P384,
4610: KeyType.P521,
231: KeyType.K256,
}

export function getKeyTypeByMultiCodecPrefix(multiCodecPrefix: number): KeyType {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const keyDidMapping: Record<KeyType, KeyDidMapping> = {
[KeyType.P256]: keyDidJsonWebKey,
[KeyType.P384]: keyDidJsonWebKey,
[KeyType.P521]: keyDidJsonWebKey,
[KeyType.K256]: keyDidJsonWebKey,
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/modules/dids/domain/keyDidDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const didDocumentKeyTypeMapping: Record<KeyType, (did: string, key: Key) => DidD
[KeyType.P256]: getJsonWebKey2020DidDocument,
[KeyType.P384]: getJsonWebKey2020DidDocument,
[KeyType.P521]: getJsonWebKey2020DidDocument,
[KeyType.K256]: getJsonWebKey2020DidDocument,
}

export function getDidDocumentForKey(did: string, key: Key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -21,6 +22,7 @@ describe('DidKey', () => {
didKeyP256,
didKeyP384,
didKeyP521,
didKeyK256,
]

for (const documentType of documentTypes) {
Expand Down
Loading