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: default to uncompressed keys #2165

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
9 changes: 9 additions & 0 deletions .changeset/cyan-parents-relax.md
Original file line number Diff line number Diff line change
@@ -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)
Comment on lines +8 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two should be marked as minor (due to breaking changes)

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 0 additions & 2 deletions packages/anoncreds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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',
Expand Down
9 changes: 5 additions & 4 deletions packages/anoncreds/src/utils/credential.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | number>

Expand Down Expand Up @@ -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 = (
Expand Down
5 changes: 3 additions & 2 deletions packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
4 changes: 1 addition & 3 deletions packages/askar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,15 @@
},
"dependencies": {
"@credo-ts/core": "workspace:*",
"bn.js": "^5.2.1",
"class-transformer": "0.5.1",
"class-validator": "0.14.1",
"rxjs": "^7.8.0",
"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",
"@types/ref-array-di": "^1.2.6",
"@types/ref-struct-di": "^1.1.10",
"reflect-metadata": "^0.1.13",
Expand Down
4 changes: 2 additions & 2 deletions packages/askar/src/secureEnvironment/secureEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function importSecureEnvironment(): {
sign: (id: string, message: Uint8Array) => Promise<Uint8Array>
getPublicBytesForKeyId: (id: string) => Uint8Array
generateKeypair: (id: string) => void
getPublicBytesForKeyId: (id: string) => Uint8Array | Promise<Uint8Array>
generateKeypair: (id: string) => void | Promise<Uint8Array>
} {
throw new Error(
'@animo-id/expo-secure-environment cannot be imported in Node.js. Currently, there is no hardware key support for node.js'
Expand Down
17 changes: 12 additions & 5 deletions packages/askar/src/wallet/AskarBaseWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
KeyBackend,
KeyType,
utils,
expandPublicKeyIfPossible,
} from '@credo-ts/core'
import {
CryptoBox,
Expand All @@ -38,7 +39,6 @@ import {
KeyAlgs,
Jwk,
} from '@hyperledger/aries-askar-shared'
import BigNumber from 'bn.js'

import { importSecureEnvironment } from '../secureEnvironment'
import {
Expand Down Expand Up @@ -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 = key.publicBytes
const keyPublicBytes = expandPublicKeyIfPossible(key.publicBytes, keyType)

// Store key
await this.withSession((session) =>
Expand All @@ -206,7 +206,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 = expandPublicKeyIfPossible(compressedPublicKeyBytes, keyType)
const publicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyBytes)

await this.storeSecureEnvironmentKeyById({
Expand Down Expand Up @@ -349,7 +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 verkey ${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()
}
Expand Down Expand Up @@ -592,7 +599,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 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 publicKeyBase58 AeAihfn5UFf7y9oesemKE1oLmTwKMRv7fafTepespr3qceF4RUMggAbogkoC8n6rXgtJytq4oGy59DsVHxmNj9WGWwkiRnP3Sz2r924RLVbc2NdP4T7yEPsSFZPsWmLjgnP1vXHpj4bVXNcTmkUmF6mSXinF3HehnQVip14vRFuMzYVxMUh28ofTJzbtUqxMWZQRu. Unsupported keyType: bls12381g1g2'
)
})

Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 7 additions & 4 deletions packages/core/src/crypto/JwsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 type { 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'
Expand All @@ -33,14 +32,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.`)
}

Expand Down
22 changes: 15 additions & 7 deletions packages/core/src/crypto/Key.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import type { KeyType } from './KeyType'

import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils'
import { MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils'

import { compressIfPossible, expandPublicKeyIfPossible } from './jose/jwk/ecCompression'
import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './keyUtils'
import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey'

export class Key {
public readonly publicKey: Buffer
public readonly publicKey: Uint8Array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite a big breaking change. Can you add the needed changeset entry?

public readonly keyType: KeyType

public constructor(publicKey: Uint8Array, keyType: KeyType) {
this.publicKey = Buffer.from(publicKey)
this.publicKey = expandPublicKeyIfPossible(publicKey, keyType)
this.keyType = keyType
}

public get compressedPublicKey() {
return compressIfPossible(this.publicKey, this.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) {
const publicKeyBytes = TypedArrayEncoder.fromBase58(publicKey)
const publicKeyBytes = Uint8Array.from(TypedArrayEncoder.fromBase58(publicKey))

return Key.fromPublicKey(publicKeyBytes, keyType)
}
Expand All @@ -28,7 +33,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)
Expand All @@ -40,8 +45,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() {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/crypto/__tests__/JwsService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.')
})
})
})
19 changes: 10 additions & 9 deletions packages/core/src/crypto/jose/jwk/Ed25519Jwk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { JwkJson } from './Jwk'
import type { Buffer } from '../../../utils'
import type { JwaEncryptionAlgorithm } from '../jwa/alg'

import { TypedArrayEncoder } from '../../../utils'
Expand All @@ -15,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() {
Expand All @@ -32,7 +35,7 @@ export class Ed25519Jwk extends Jwk {
}

public get publicKey() {
return TypedArrayEncoder.fromBase64(this.x)
return this._x
}

public get keyType() {
Expand Down Expand Up @@ -65,10 +68,8 @@ export class Ed25519Jwk extends Jwk {
})
}

public static fromPublicKey(publicKey: Buffer) {
return new Ed25519Jwk({
x: TypedArrayEncoder.toBase64URL(publicKey),
})
public static fromPublicKey(publicKey: Uint8Array) {
return new Ed25519Jwk({ x: publicKey })
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/crypto/jose/jwk/Jwk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Buffer } from '../../../utils'
import type { KeyType } from '../../KeyType'
import type { JwaKeyType, JwaEncryptionAlgorithm, JwaSignatureAlgorithm } from '../jwa'

Expand All @@ -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[]

Expand Down
Loading
Loading