Skip to content

Commit 7588468

Browse files
authored
KMS provider improvements (#222)
Migrate secp256k1 provider
1 parent 66c2a72 commit 7588468

File tree

13 files changed

+406
-497
lines changed

13 files changed

+406
-497
lines changed

package-lock.json

+210-399
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@0xpolygonid/js-sdk",
3-
"version": "1.10.4",
3+
"version": "1.11.0",
44
"description": "SDK to work with Polygon ID",
55
"main": "dist/node/cjs/index.js",
66
"module": "dist/node/esm/index.js",
@@ -25,7 +25,7 @@
2525
"build": "npm run clean && npm run build:node && npm run build:browser",
2626
"build:node": "npm run build:tsc && npm run build:esm",
2727
"build:esm": "tsc --outDir dist/node/esm --declaration --declarationDir dist/types",
28-
"build:browser": "rollup -c rollup.config.mjs --failAfterWarnings",
28+
"build:browser": "rollup -c rollup.config.mjs --failAfterWarnings --filterLogs \"/* @__PURE__ */\"",
2929
"build:tsc": "tsc --module commonjs --outDir dist/node/cjs",
3030
"doc:extract": "ts-node ./scripts/doc-extract.ts",
3131
"doc:documenter": "ts-node ./scripts/doc-documenter.ts",
@@ -53,18 +53,17 @@
5353
"@iden3/eslint-config": "https://github.com/iden3/eslint-config",
5454
"@microsoft/api-documenter": "^7.8.20",
5555
"@microsoft/api-extractor": "^7.9.0",
56-
"@rollup/plugin-commonjs": "^25.0.4",
57-
"@rollup/plugin-json": "^6.0.1",
58-
"@rollup/plugin-node-resolve": "^15.2.1",
59-
"@rollup/plugin-replace": "^5.0.3",
56+
"@rollup/plugin-commonjs": "^25.0.7",
57+
"@rollup/plugin-json": "^6.1.0",
58+
"@rollup/plugin-node-resolve": "^15.2.3",
59+
"@rollup/plugin-replace": "^5.0.5",
6060
"@rollup/plugin-terser": "^0.4.4",
61-
"@rollup/plugin-typescript": "^11.1.4",
61+
"@rollup/plugin-typescript": "^11.1.6",
6262
"@rollup/plugin-virtual": "^3.0.2",
6363
"@typechain/ethers-v6": "^0.5.1",
6464
"@types/chai": "^4.3.9",
6565
"@types/chai-as-promised": "^7.1.7",
6666
"@types/chai-spies": "^1.0.5",
67-
"@types/elliptic": "^6.4.16",
6867
"@types/fs-extra": "^11.0.1",
6968
"@types/jsonld": "^1.5.11",
7069
"@types/mocha": "^10.0.3",
@@ -81,7 +80,7 @@
8180
"mocha": "10.2.0",
8281
"prettier": "^2.7.1",
8382
"rimraf": "^5.0.5",
84-
"rollup": "^4.2.0",
83+
"rollup": "^4.14.3",
8584
"ts-loader": "^9.4.1",
8685
"ts-node": "^10.9.1",
8786
"typescript": "^4.8.4"
@@ -100,9 +99,8 @@
10099
"@noble/curves": "^1.4.0",
101100
"ajv": "8.12.0",
102101
"ajv-formats": "2.1.1",
103-
"did-jwt": "6.11.6",
102+
"did-jwt": "8.0.4",
104103
"did-resolver": "4.1.0",
105-
"elliptic": "6.5.4",
106104
"ethers": "6.8.0",
107105
"idb-keyval": "6.2.0",
108106
"js-sha3": "0.9.3",

src/iden3comm/packers/jws.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { keyPath, KMS } from '../../kms/';
55

66
import { verifyJWS } from 'did-jwt';
77
import { DIDDocument, Resolvable, parse } from 'did-resolver';
8+
import { bytesToBase64url } from '../../utils/encoding';
89
import {
910
byteDecoder,
1011
byteEncoder,
@@ -163,8 +164,8 @@ export class JWSPacker implements IPacker {
163164
const signingInputBytes = byteEncoder.encode(signingInput);
164165
let signatureBase64: string;
165166
if (params.signer) {
166-
const signerFn = params.signer(vm, signingInputBytes);
167-
signatureBase64 = (await signerFn(signingInput)).toString();
167+
const signature = await params.signer(vm, signingInputBytes);
168+
signatureBase64 = bytesToBase64url(signature);
168169
} else {
169170
if (!publicKeyBytes) {
170171
throw new Error('No public key found');
@@ -176,10 +177,11 @@ export class JWSPacker implements IPacker {
176177

177178
const signatureBytes = await this._kms.sign(
178179
{ type: kmsKeyType, id: keyPath(kmsKeyType, bytesToHex(publicKeyBytes)) },
179-
signingInputBytes
180+
signingInputBytes,
181+
{ alg: params.alg }
180182
);
181183

182-
signatureBase64 = byteDecoder.decode(signatureBytes);
184+
signatureBase64 = bytesToBase64url(signatureBytes);
183185
}
184186

185187
return byteEncoder.encode(`${signingInput}.${signatureBase64}`);

src/iden3comm/types/packer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { ProvingMethodAlg } from '@iden3/js-jwz';
44
import { CircuitId } from '../../circuits';
55
import { MediaType } from '../constants';
66
import { DIDDocument, VerificationMethod } from 'did-resolver';
7-
import { Signer } from 'did-jwt';
87
/**
98
* Protocol message type
109
*/
@@ -50,8 +49,9 @@ export type ZKPPackerParams = PackerParams & {
5049

5150
/**
5251
* SignerFn Is function to sign data with a verification method
52+
* @returns Promise of signature bytes;
5353
*/
54-
export type SignerFn = (vm: VerificationMethod, data: Uint8Array) => Signer;
54+
export type SignerFn = (vm: VerificationMethod, dataToSign: Uint8Array) => Promise<Uint8Array>;
5555

5656
/**
5757
* JWSPackerParams are parameters for JWS packer

src/iden3comm/utils/did.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SUPPORTED_PUBLIC_KEY_TYPES } from '../constants';
2-
import elliptic from 'elliptic';
32
import { DIDDocument, VerificationMethod } from 'did-resolver';
3+
import { secp256k1 as sec } from '@noble/curves/secp256k1';
44

55
import { KmsKeyType } from '../../kms';
66
import { base58ToBytes, base64UrlToBytes, bytesToHex, hexToBytes } from '../../utils';
@@ -29,8 +29,6 @@ export const resolveVerificationMethods = (didDocument: DIDDocument): Verificati
2929
return sortedVerificationMethods;
3030
};
3131

32-
const secp256k1 = new elliptic.ec('secp256k1');
33-
3432
export const extractPublicKeyBytes = (
3533
vm: VerificationMethod
3634
): { publicKeyBytes: Uint8Array | null; kmsKeyType?: KmsKeyType } => {
@@ -55,15 +53,17 @@ export const extractPublicKeyBytes = (
5553
vm.publicKeyJwk.x &&
5654
vm.publicKeyJwk.y
5755
) {
56+
const [xHex, yHex] = [
57+
base64UrlToBytes(vm.publicKeyJwk.x),
58+
base64UrlToBytes(vm.publicKeyJwk.y)
59+
].map(bytesToHex);
60+
const x = xHex.includes('0x') ? BigInt(xHex) : BigInt(`0x${xHex}`);
61+
const y = yHex.includes('0x') ? BigInt(yHex) : BigInt(`0x${yHex}`);
5862
return {
59-
publicKeyBytes: hexToBytes(
60-
secp256k1
61-
.keyFromPublic({
62-
x: bytesToHex(base64UrlToBytes(vm.publicKeyJwk.x)),
63-
y: bytesToHex(base64UrlToBytes(vm.publicKeyJwk.y))
64-
})
65-
.getPublic('hex')
66-
),
63+
publicKeyBytes: sec.ProjectivePoint.fromAffine({
64+
x,
65+
y
66+
}).toRawBytes(false),
6767
kmsKeyType: KmsKeyType.Secp256k1
6868
};
6969
}

src/kms/key-providers/bjj-provider.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Hex, PrivateKey } from '@iden3/js-crypto';
1+
import { Hex, PrivateKey, PublicKey, Signature } from '@iden3/js-crypto';
22
import { BytesHelper, checkBigIntInField } from '@iden3/js-iden3-core';
33
import { IKeyProvider } from '../kms';
44
import { AbstractPrivateKeyStore, KmsKeyId, KmsKeyType } from '../store';
55

66
import * as providerHelpers from '../provider-helpers';
7+
import { hexToBytes } from '../../utils';
78

89
/**
910
* Provider for Baby Jub Jub keys
@@ -24,6 +25,9 @@ export class BjjProvider implements IKeyProvider {
2425
* @param {AbstractPrivateKeyStore} keyStore - key store for kms
2526
*/
2627
constructor(keyType: KmsKeyType, keyStore: AbstractPrivateKeyStore) {
28+
if (keyType !== KmsKeyType.BabyJubJub) {
29+
throw new Error('Key type must be BabyJubJub');
30+
}
2731
this.keyType = keyType;
2832
this.keyStore = keyStore;
2933
}
@@ -88,4 +92,13 @@ export class BjjProvider implements IKeyProvider {
8892

8993
return new PrivateKey(Hex.decodeString(privateKeyHex));
9094
}
95+
96+
async verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
97+
const publicKey = await this.publicKey(keyId);
98+
99+
return PublicKey.newFromCompressed(hexToBytes(publicKey)).verifyPoseidon(
100+
BytesHelper.bytesToInt(message),
101+
Signature.newFromCompressed(hexToBytes(signatureHex))
102+
);
103+
}
91104
}

src/kms/key-providers/ed25519-provider.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AbstractPrivateKeyStore, KmsKeyId, KmsKeyType } from '../store';
33
import * as providerHelpers from '../provider-helpers';
44
import { ed25519 } from '@noble/curves/ed25519';
55
import { bytesToHex } from '../../utils';
6+
import { sha256 } from '@iden3/js-crypto';
67

78
/**
89
* Provider for Ed25519 keys
@@ -65,7 +66,19 @@ export class Ed25519Provider implements IKeyProvider {
6566
*/
6667
async sign(keyId: KmsKeyId, data: Uint8Array): Promise<Uint8Array> {
6768
const privateKeyHex = await this.privateKey(keyId);
68-
return ed25519.sign(data, privateKeyHex);
69+
return ed25519.sign(sha256(data), privateKeyHex);
70+
}
71+
72+
/**
73+
* Verifies a signature for the given message and key identifier.
74+
* @param message - The message to verify the signature against.
75+
* @param signatureHex - The signature to verify, as a hexadecimal string.
76+
* @param keyId - The key identifier to use for verification.
77+
* @returns A Promise that resolves to a boolean indicating whether the signature is valid.
78+
*/
79+
async verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
80+
const publicKeyHex = await this.publicKey(keyId);
81+
return ed25519.verify(signatureHex, sha256(message), publicKeyHex);
6982
}
7083

7184
/**

src/kms/key-providers/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export * from './bjj-provider';
22
export * from './ed25519-provider';
3-
export * from './sec256k1-provider';
3+
export * from './secp256k1-provider';

src/kms/key-providers/sec256k1-provider.ts src/kms/key-providers/secp256k1-provider.ts

+36-24
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { IKeyProvider } from '../kms';
22
import { AbstractPrivateKeyStore, KmsKeyId, KmsKeyType } from '../store';
3-
import Elliptic from 'elliptic';
43
import * as providerHelpers from '../provider-helpers';
5-
import { ES256KSigner } from 'did-jwt';
6-
import { base64UrlToBytes, byteEncoder, bytesToHex, hexToBytes } from '../../utils';
4+
import { base64UrlToBytes, bytesToHex } from '../../utils';
5+
import { secp256k1 } from '@noble/curves/secp256k1';
6+
import { sha256 } from '@iden3/js-crypto';
7+
import { ES256KSigner, hexToBytes } from 'did-jwt';
78

89
/**
9-
* Provider for Sec256p1 keys256p1
10+
* Provider for Secp256k1
1011
* @public
11-
* @class Sec256p1Provider
12+
* @class Secp256k1Provider
1213
* @implements implements IKeyProvider interface
1314
*/
1415
export class Sec256k1Provider implements IKeyProvider {
@@ -19,16 +20,17 @@ export class Sec256k1Provider implements IKeyProvider {
1920
keyType: KmsKeyType;
2021
private _keyStore: AbstractPrivateKeyStore;
2122

22-
private readonly _ec;
2323
/**
2424
* Creates an instance of BjjProvider.
2525
* @param {KmsKeyType} keyType - kms key type
2626
* @param {AbstractPrivateKeyStore} keyStore - key store for kms
2727
*/
2828
constructor(keyType: KmsKeyType, keyStore: AbstractPrivateKeyStore) {
29+
if (keyType !== KmsKeyType.Secp256k1) {
30+
throw new Error('Key type must be Secp256k1');
31+
}
2932
this.keyType = keyType;
3033
this._keyStore = keyStore;
31-
this._ec = new Elliptic.ec('secp256k1');
3234
}
3335
/**
3436
* generates a baby jub jub key from a seed phrase
@@ -39,14 +41,15 @@ export class Sec256k1Provider implements IKeyProvider {
3941
if (seed.length !== 32) {
4042
throw new Error('Seed should be 32 bytes');
4143
}
42-
const keyPair = this._ec.keyFromPrivate(seed);
44+
const publicKey = secp256k1.getPublicKey(seed);
4345
const kmsId = {
4446
type: this.keyType,
45-
id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false))
47+
id: providerHelpers.keyPath(this.keyType, bytesToHex(publicKey))
4648
};
49+
4750
await this._keyStore.importKey({
4851
alias: kmsId.id,
49-
key: keyPair.getPrivate().toString('hex').padStart(64, '0')
52+
key: bytesToHex(seed).padStart(64, '0')
5053
});
5154

5255
return kmsId;
@@ -59,37 +62,46 @@ export class Sec256k1Provider implements IKeyProvider {
5962
*/
6063
async publicKey(keyId: KmsKeyId): Promise<string> {
6164
const privateKeyHex = await this.privateKey(keyId);
62-
return this._ec.keyFromPrivate(privateKeyHex, 'hex').getPublic().encode('hex', false); // 04 + x + y (uncompressed key)
65+
const publicKey = secp256k1.getPublicKey(privateKeyHex, false); // 04 + x + y (uncompressed key)
66+
return bytesToHex(publicKey);
6367
}
6468

6569
/**
66-
* signs prepared payload of size,
67-
* with a key id
68-
*
69-
* @param {KmsKeyId} keyId - key identifier
70-
* @param {Uint8Array} data - data to sign (32 bytes)
71-
* @returns Uint8Array signature
70+
* Signs the given data using the private key associated with the specified key identifier.
71+
* @param keyId - The key identifier to use for signing.
72+
* @param data - The data to sign.
73+
* @param opts - Signing options, such as the algorithm to use.
74+
* @returns A Promise that resolves to the signature as a Uint8Array.
7275
*/
7376
async sign(
7477
keyId: KmsKeyId,
7578
data: Uint8Array,
7679
opts: { [key: string]: unknown } = { alg: 'ES256K' }
7780
): Promise<Uint8Array> {
7881
const privateKeyHex = await this.privateKey(keyId);
79-
if (!privateKeyHex) {
80-
throw new Error('Private key not found for keyId: ' + keyId.id);
81-
}
82+
8283
const signatureBase64 = await ES256KSigner(
8384
hexToBytes(privateKeyHex),
8485
opts.alg === 'ES256K-R'
8586
)(data);
8687

87-
const signatureHex = bytesToHex(base64UrlToBytes(signatureBase64.toString()));
88-
if (typeof signatureHex !== 'string') {
89-
throw new Error('Signature is not a string');
88+
if (typeof signatureBase64 !== 'string') {
89+
throw new Error('signatureBase64 must be a string');
9090
}
9191

92-
return byteEncoder.encode(signatureBase64.toString());
92+
return base64UrlToBytes(signatureBase64);
93+
}
94+
95+
/**
96+
* Verifies a signature for the given message and key identifier.
97+
* @param message - The message to verify the signature against.
98+
* @param signatureHex - The signature to verify, as a hexadecimal string.
99+
* @param keyId - The key identifier to use for verification.
100+
* @returns A Promise that resolves to a boolean indicating whether the signature is valid.
101+
*/
102+
async verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
103+
const publicKeyHex = await this.publicKey(keyId);
104+
return secp256k1.verify(signatureHex, sha256(message), publicKeyHex);
93105
}
94106

95107
private async privateKey(keyId: KmsKeyId): Promise<string> {

src/kms/kms.ts

+26
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ export interface IKeyProvider {
3737
* @returns `Promise<KmsKeyId>`
3838
*/
3939
newPrivateKeyFromSeed(seed: Uint8Array): Promise<KmsKeyId>;
40+
41+
/**
42+
* Verifies a message signature using the provided key ID.
43+
*
44+
* @param message - The message bytes to verify.
45+
* @param signatureHex - The signature in hexadecimal format.
46+
* @param keyId - The KMS key ID used to verify the signature.
47+
* @returns A promise that resolves to a boolean indicating whether the signature is valid.
48+
*/
49+
verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean>;
4050
}
4151
/**
4252
* Key management system class contains different key providers.
@@ -112,4 +122,20 @@ export class KMS {
112122

113123
return keyProvider.sign(keyId, data, opts);
114124
}
125+
126+
/**
127+
* Verifies a signature against the provided data and key ID.
128+
*
129+
* @param data - The data to verify the signature against.
130+
* @param signatureHex - The signature to verify, in hexadecimal format.
131+
* @param keyId - The key ID to use for verification.
132+
* @returns A promise that resolves to a boolean indicating whether the signature is valid.
133+
*/
134+
verify(data: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
135+
const keyProvider = this._registry.get(keyId.type);
136+
if (!keyProvider) {
137+
throw new Error(`keyProvider not found for: ${keyId.type}`);
138+
}
139+
return keyProvider.verify(data, signatureHex, keyId);
140+
}
115141
}

0 commit comments

Comments
 (0)