Skip to content

Commit ff142b0

Browse files
committed
feat: use uncompressed keys by default
Signed-off-by: Timo Glastra <[email protected]>
1 parent 807f0d5 commit ff142b0

34 files changed

+503
-247
lines changed

packages/askar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"tsyringe": "^4.8.0"
3535
},
3636
"devDependencies": {
37-
"@animo-id/expo-secure-environment": "^0.1.0-alpha.11",
37+
"@animo-id/expo-secure-environment": "^0.1.0-alpha.12",
3838
"@hyperledger/aries-askar-nodejs": "^0.2.3",
3939
"@hyperledger/aries-askar-shared": "^0.2.3",
4040
"@types/bn.js": "^5.1.0",

packages/askar/src/secureEnvironment/secureEnvironment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export function importSecureEnvironment(): {
22
sign: (id: string, message: Uint8Array) => Promise<Uint8Array>
3-
getPublicBytesForKeyId: (id: string) => Uint8Array
4-
generateKeypair: (id: string) => void
3+
getPublicBytesForKeyId: (id: string) => Uint8Array | Promise<Uint8Array>
4+
generateKeypair: (id: string) => void | Promise<Uint8Array>
55
} {
66
throw new Error(
77
'@animo-id/expo-secure-environment cannot be imported in Node.js. Currently, there is no hardware key support for node.js'

packages/askar/src/wallet/AskarBaseWallet.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
KeyBackend,
2929
KeyType,
3030
utils,
31+
expandIfPossible,
3132
} from '@credo-ts/core'
3233
import {
3334
CryptoBox,
@@ -181,7 +182,7 @@ export abstract class AskarBaseWallet implements Wallet {
181182
// This will be fixed once we use the new 'using' syntax
182183
key = _key
183184

184-
const keyPublicBytes = key.publicBytes
185+
const keyPublicBytes = expandIfPossible(key.publicBytes, keyType)
185186

186187
// Store key
187188
await this.withSession((session) =>
@@ -206,7 +207,9 @@ export abstract class AskarBaseWallet implements Wallet {
206207

207208
// Generate a hardware-backed P-256 keypair
208209
await secureEnvironment.generateKeypair(kid)
209-
const publicKeyBytes = await secureEnvironment.getPublicBytesForKeyId(kid)
210+
const compressedPublicKeyBytes = await secureEnvironment.getPublicBytesForKeyId(kid)
211+
212+
const publicKeyBytes = expandIfPossible(compressedPublicKeyBytes, keyType)
210213
const publicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyBytes)
211214

212215
await this.storeSecureEnvironmentKeyById({
@@ -349,7 +352,9 @@ export abstract class AskarBaseWallet implements Wallet {
349352
if (!isError(error)) {
350353
throw new CredoError('Attempted to throw error, but it was not of type Error', { cause: error })
351354
}
352-
throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}. ${error.message}`, { cause: error })
355+
throw new WalletError(`Error signing data with key associated with ${key.publicKeyBase58}. ${error.message}`, {
356+
cause: error,
357+
})
353358
} finally {
354359
askarKey?.handle.free()
355360
}

packages/bbs-signatures/tests/bbs-signing-provider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describeSkipNode18('BBS Signing Provider', () => {
5252
key,
5353
})
5454
).rejects.toThrow(
55-
'Error signing data with verkey AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2'
55+
'Error signing data with key associated with AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2'
5656
)
5757
})
5858

packages/core/src/crypto/JwsService.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import type { Key } from './Key'
99
import type { Jwk } from './jose/jwk'
1010
import type { JwkJson } from './jose/jwk/Jwk'
1111
import type { AgentContext } from '../agent'
12-
import type { Buffer } from '../utils'
1312

1413
import { CredoError } from '../error'
1514
import { EncodedX509Certificate, X509ModuleConfig } from '../modules/x509'
1615
import { injectable } from '../plugins'
17-
import { isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils'
16+
import { Buffer, isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils'
1817
import { WalletError } from '../wallet/error'
1918

2019
import { X509Service } from './../modules/x509/X509Service'
@@ -33,14 +32,18 @@ export class JwsService {
3332
const certificate = X509Service.getLeafCertificate(agentContext, { certificateChain: x5c })
3433
if (
3534
certificate.publicKey.keyType !== options.key.keyType ||
36-
!certificate.publicKey.publicKey.equals(options.key.publicKey)
35+
!Buffer.from(certificate.publicKey.publicKey).equals(Buffer.from(options.key.publicKey))
3736
) {
3837
throw new CredoError(`Protected header x5c does not match key for signing.`)
3938
}
4039
}
4140

4241
// Make sure the options.key and jwk from protectedHeader are the same.
43-
if (jwk && (jwk.key.keyType !== options.key.keyType || !jwk.key.publicKey.equals(options.key.publicKey))) {
42+
if (
43+
jwk &&
44+
(jwk.key.keyType !== options.key.keyType ||
45+
!Buffer.from(jwk.key.publicKey).equals(Buffer.from(options.key.publicKey)))
46+
) {
4447
throw new CredoError(`Protected header JWK does not match key for signing.`)
4548
}
4649

packages/core/src/crypto/Key.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import type { KeyType } from './KeyType'
22

3-
import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils'
3+
import { MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils'
44

5+
import { compressIfPossible, expandIfPossible } from './jose/jwk/ecCompression'
56
import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './keyUtils'
67
import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey'
78

89
export class Key {
9-
public readonly publicKey: Buffer
10+
public readonly publicKey: Uint8Array
1011
public readonly keyType: KeyType
1112

1213
public constructor(publicKey: Uint8Array, keyType: KeyType) {
13-
this.publicKey = Buffer.from(publicKey)
14+
this.publicKey = expandIfPossible(publicKey, keyType)
1415
this.keyType = keyType
1516
}
1617

18+
public get compressedPublicKey() {
19+
return compressIfPossible(this.publicKey, this.keyType)
20+
}
21+
1722
public static fromPublicKey(publicKey: Uint8Array, keyType: KeyType) {
18-
return new Key(Buffer.from(publicKey), keyType)
23+
return new Key(publicKey, keyType)
1924
}
2025

2126
public static fromPublicKeyBase58(publicKey: string, keyType: KeyType) {
22-
const publicKeyBytes = TypedArrayEncoder.fromBase58(publicKey)
27+
const publicKeyBytes = Uint8Array.from(TypedArrayEncoder.fromBase58(publicKey))
2328

2429
return Key.fromPublicKey(publicKeyBytes, keyType)
2530
}
@@ -28,7 +33,7 @@ export class Key {
2833
const { data } = MultiBaseEncoder.decode(fingerprint)
2934
const [code, byteLength] = VarintEncoder.decode(data)
3035

31-
const publicKey = Buffer.from(data.slice(byteLength))
36+
const publicKey = data.slice(byteLength)
3237
const keyType = getKeyTypeByMultiCodecPrefix(code)
3338

3439
return new Key(publicKey, keyType)
@@ -40,8 +45,11 @@ export class Key {
4045
// Create Buffer with length of the prefix bytes, then use varint to fill the prefix bytes
4146
const prefixBytes = VarintEncoder.encode(multiCodecPrefix)
4247

48+
// Multicodec requires compressable keys to be compressed
49+
const possiblyCompressedKey = compressIfPossible(this.publicKey, this.keyType)
50+
4351
// Combine prefix with public key
44-
return Buffer.concat([prefixBytes, this.publicKey])
52+
return new Uint8Array([...prefixBytes, ...possiblyCompressedKey])
4553
}
4654

4755
public get fingerprint() {

packages/core/src/crypto/__tests__/JwsService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ describe('JwsService', () => {
147147
jwsService.verifyJws(agentContext, {
148148
jws: { signatures: [], payload: '' },
149149
})
150-
).rejects.toThrowError('Unable to verify JWS, no signatures present in JWS.')
150+
).rejects.toThrow('Unable to verify JWS, no signatures present in JWS.')
151151
})
152152
})
153153
})

packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { JwkJson } from './Jwk'
2-
import type { Buffer } from '../../../utils'
32
import type { JwaEncryptionAlgorithm } from '../jwa/alg'
43

54
import { TypedArrayEncoder } from '../../../utils'
@@ -15,12 +14,16 @@ export class Ed25519Jwk extends Jwk {
1514
public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.EdDSA]
1615
public static readonly keyType = KeyType.Ed25519
1716

18-
public readonly x: string
17+
private readonly _x: Uint8Array
1918

20-
public constructor({ x }: { x: string }) {
19+
public constructor({ x }: { x: string | Uint8Array }) {
2120
super()
2221

23-
this.x = x
22+
this._x = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x
23+
}
24+
25+
public get x() {
26+
return TypedArrayEncoder.toBase64URL(this._x)
2427
}
2528

2629
public get kty() {
@@ -32,7 +35,7 @@ export class Ed25519Jwk extends Jwk {
3235
}
3336

3437
public get publicKey() {
35-
return TypedArrayEncoder.fromBase64(this.x)
38+
return this._x
3639
}
3740

3841
public get keyType() {
@@ -65,10 +68,8 @@ export class Ed25519Jwk extends Jwk {
6568
})
6669
}
6770

68-
public static fromPublicKey(publicKey: Buffer) {
69-
return new Ed25519Jwk({
70-
x: TypedArrayEncoder.toBase64URL(publicKey),
71-
})
71+
public static fromPublicKey(publicKey: Uint8Array) {
72+
return new Ed25519Jwk({ x: publicKey })
7273
}
7374
}
7475

packages/core/src/crypto/jose/jwk/Jwk.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Buffer } from '../../../utils'
21
import type { KeyType } from '../../KeyType'
32
import type { JwaKeyType, JwaEncryptionAlgorithm, JwaSignatureAlgorithm } from '../jwa'
43

@@ -11,7 +10,7 @@ export interface JwkJson {
1110
}
1211

1312
export abstract class Jwk {
14-
public abstract publicKey: Buffer
13+
public abstract publicKey: Uint8Array
1514
public abstract supportedSignatureAlgorithms: JwaSignatureAlgorithm[]
1615
public abstract supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[]
1716

packages/core/src/crypto/jose/jwk/K256Jwk.ts

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
11
import type { JwkJson } from './Jwk'
22
import type { JwaEncryptionAlgorithm } from '../jwa/alg'
33

4-
import { TypedArrayEncoder, Buffer } from '../../../utils'
4+
import { CredoError } from '../../../error'
5+
import { TypedArrayEncoder } from '../../../utils'
56
import { KeyType } from '../../KeyType'
67
import { JwaCurve, JwaKeyType } from '../jwa'
78
import { JwaSignatureAlgorithm } from '../jwa/alg'
89

910
import { Jwk } from './Jwk'
10-
import { compress, expand } from './ecCompression'
11+
import {
12+
compressECPoint,
13+
expand,
14+
isValidCompressedPublicKey,
15+
isValidUncompressedPublicKey,
16+
PREFIX_UNCOMPRESSED,
17+
} from './ecCompression'
1118
import { hasKty, hasCrv, hasX, hasY, hasValidUse } from './validate'
1219

1320
export class K256Jwk extends Jwk {
1421
public static readonly supportedEncryptionAlgorithms: JwaEncryptionAlgorithm[] = []
1522
public static readonly supportedSignatureAlgorithms: JwaSignatureAlgorithm[] = [JwaSignatureAlgorithm.ES256K]
1623
public static readonly keyType = KeyType.K256
1724

18-
public readonly x: string
19-
public readonly y: string
25+
private readonly _x: Uint8Array
26+
private readonly _y: Uint8Array
2027

21-
public constructor({ x, y }: { x: string; y: string }) {
28+
public constructor({ x, y }: { x: string | Uint8Array; y: string | Uint8Array }) {
2229
super()
2330

24-
this.x = x
25-
this.y = y
31+
const xAsBytes = typeof x === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(x)) : x
32+
const yAsBytes = typeof y === 'string' ? Uint8Array.from(TypedArrayEncoder.fromBase64(y)) : y
33+
34+
this._x = xAsBytes
35+
this._y = yAsBytes
2636
}
2737

2838
public get kty() {
@@ -33,17 +43,26 @@ export class K256Jwk extends Jwk {
3343
return JwaCurve.Secp256k1 as const
3444
}
3545

46+
public get x() {
47+
return TypedArrayEncoder.toBase64URL(this._x)
48+
}
49+
50+
public get y() {
51+
return TypedArrayEncoder.toBase64URL(this._y)
52+
}
53+
3654
/**
37-
* Returns the public key of the K-256 JWK.
38-
*
39-
* NOTE: this is the compressed variant. We still need to add support for the
40-
* uncompressed variant.
55+
* Returns the uncompressed public key of the P-256 JWK.
4156
*/
4257
public get publicKey() {
43-
const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(this.x), TypedArrayEncoder.fromBase64(this.y)])
44-
const compressedPublicKey = compress(publicKeyBuffer)
58+
return new Uint8Array([PREFIX_UNCOMPRESSED, ...this._x, ...this._y])
59+
}
4560

46-
return Buffer.from(compressedPublicKey)
61+
/**
62+
* Returns the compressed public key of the K-256 JWK.
63+
*/
64+
public get publicKeyCompressed() {
65+
return compressECPoint(this._x, this._y)
4766
}
4867

4968
public get keyType() {
@@ -78,15 +97,25 @@ export class K256Jwk extends Jwk {
7897
})
7998
}
8099

81-
public static fromPublicKey(publicKey: Buffer) {
82-
const expanded = expand(publicKey, JwaCurve.Secp256k1)
83-
const x = expanded.slice(0, expanded.length / 2)
84-
const y = expanded.slice(expanded.length / 2)
100+
public static fromPublicKey(publicKey: Uint8Array) {
101+
if (isValidCompressedPublicKey(publicKey, this.keyType)) {
102+
const expanded = expand(publicKey, this.keyType)
103+
const x = expanded.slice(1, expanded.length / 2 + 1)
104+
const y = expanded.slice(expanded.length / 2 + 1)
85105

86-
return new K256Jwk({
87-
x: TypedArrayEncoder.toBase64URL(x),
88-
y: TypedArrayEncoder.toBase64URL(y),
89-
})
106+
return new K256Jwk({ x, y })
107+
}
108+
109+
if (isValidUncompressedPublicKey(publicKey, this.keyType)) {
110+
const x = publicKey.slice(1, publicKey.length / 2 + 1)
111+
const y = publicKey.slice(publicKey.length / 2 + 1)
112+
113+
return new K256Jwk({ x, y })
114+
}
115+
116+
throw new CredoError(
117+
`${this.keyType} public key is neither a valid compressed or uncompressed key. Key prefix '${publicKey[0]}', key length '${publicKey.length}'`
118+
)
90119
}
91120
}
92121

0 commit comments

Comments
 (0)