Skip to content

Commit

Permalink
KMS provider improvements (#222)
Browse files Browse the repository at this point in the history
 Migrate secp256k1 provider
  • Loading branch information
Kolezhniuk authored Apr 22, 2024
1 parent 66c2a72 commit 7588468
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 497 deletions.
609 changes: 210 additions & 399 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 9 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@0xpolygonid/js-sdk",
"version": "1.10.4",
"version": "1.11.0",
"description": "SDK to work with Polygon ID",
"main": "dist/node/cjs/index.js",
"module": "dist/node/esm/index.js",
Expand All @@ -25,7 +25,7 @@
"build": "npm run clean && npm run build:node && npm run build:browser",
"build:node": "npm run build:tsc && npm run build:esm",
"build:esm": "tsc --outDir dist/node/esm --declaration --declarationDir dist/types",
"build:browser": "rollup -c rollup.config.mjs --failAfterWarnings",
"build:browser": "rollup -c rollup.config.mjs --failAfterWarnings --filterLogs \"/* @__PURE__ */\"",
"build:tsc": "tsc --module commonjs --outDir dist/node/cjs",
"doc:extract": "ts-node ./scripts/doc-extract.ts",
"doc:documenter": "ts-node ./scripts/doc-documenter.ts",
Expand Down Expand Up @@ -53,18 +53,17 @@
"@iden3/eslint-config": "https://github.com/iden3/eslint-config",
"@microsoft/api-documenter": "^7.8.20",
"@microsoft/api-extractor": "^7.9.0",
"@rollup/plugin-commonjs": "^25.0.4",
"@rollup/plugin-json": "^6.0.1",
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-replace": "^5.0.3",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.4",
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-virtual": "^3.0.2",
"@typechain/ethers-v6": "^0.5.1",
"@types/chai": "^4.3.9",
"@types/chai-as-promised": "^7.1.7",
"@types/chai-spies": "^1.0.5",
"@types/elliptic": "^6.4.16",
"@types/fs-extra": "^11.0.1",
"@types/jsonld": "^1.5.11",
"@types/mocha": "^10.0.3",
Expand All @@ -81,7 +80,7 @@
"mocha": "10.2.0",
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"rollup": "^4.2.0",
"rollup": "^4.14.3",
"ts-loader": "^9.4.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
Expand All @@ -100,9 +99,8 @@
"@noble/curves": "^1.4.0",
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"did-jwt": "6.11.6",
"did-jwt": "8.0.4",
"did-resolver": "4.1.0",
"elliptic": "6.5.4",
"ethers": "6.8.0",
"idb-keyval": "6.2.0",
"js-sha3": "0.9.3",
Expand Down
10 changes: 6 additions & 4 deletions src/iden3comm/packers/jws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { keyPath, KMS } from '../../kms/';

import { verifyJWS } from 'did-jwt';
import { DIDDocument, Resolvable, parse } from 'did-resolver';
import { bytesToBase64url } from '../../utils/encoding';
import {
byteDecoder,
byteEncoder,
Expand Down Expand Up @@ -163,8 +164,8 @@ export class JWSPacker implements IPacker {
const signingInputBytes = byteEncoder.encode(signingInput);
let signatureBase64: string;
if (params.signer) {
const signerFn = params.signer(vm, signingInputBytes);
signatureBase64 = (await signerFn(signingInput)).toString();
const signature = await params.signer(vm, signingInputBytes);
signatureBase64 = bytesToBase64url(signature);
} else {
if (!publicKeyBytes) {
throw new Error('No public key found');
Expand All @@ -176,10 +177,11 @@ export class JWSPacker implements IPacker {

const signatureBytes = await this._kms.sign(
{ type: kmsKeyType, id: keyPath(kmsKeyType, bytesToHex(publicKeyBytes)) },
signingInputBytes
signingInputBytes,
{ alg: params.alg }
);

signatureBase64 = byteDecoder.decode(signatureBytes);
signatureBase64 = bytesToBase64url(signatureBytes);
}

return byteEncoder.encode(`${signingInput}.${signatureBase64}`);
Expand Down
4 changes: 2 additions & 2 deletions src/iden3comm/types/packer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ProvingMethodAlg } from '@iden3/js-jwz';
import { CircuitId } from '../../circuits';
import { MediaType } from '../constants';
import { DIDDocument, VerificationMethod } from 'did-resolver';
import { Signer } from 'did-jwt';
/**
* Protocol message type
*/
Expand Down Expand Up @@ -50,8 +49,9 @@ export type ZKPPackerParams = PackerParams & {

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

/**
* JWSPackerParams are parameters for JWS packer
Expand Down
22 changes: 11 additions & 11 deletions src/iden3comm/utils/did.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SUPPORTED_PUBLIC_KEY_TYPES } from '../constants';
import elliptic from 'elliptic';
import { DIDDocument, VerificationMethod } from 'did-resolver';
import { secp256k1 as sec } from '@noble/curves/secp256k1';

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

const secp256k1 = new elliptic.ec('secp256k1');

export const extractPublicKeyBytes = (
vm: VerificationMethod
): { publicKeyBytes: Uint8Array | null; kmsKeyType?: KmsKeyType } => {
Expand All @@ -55,15 +53,17 @@ export const extractPublicKeyBytes = (
vm.publicKeyJwk.x &&
vm.publicKeyJwk.y
) {
const [xHex, yHex] = [
base64UrlToBytes(vm.publicKeyJwk.x),
base64UrlToBytes(vm.publicKeyJwk.y)
].map(bytesToHex);
const x = xHex.includes('0x') ? BigInt(xHex) : BigInt(`0x${xHex}`);
const y = yHex.includes('0x') ? BigInt(yHex) : BigInt(`0x${yHex}`);
return {
publicKeyBytes: hexToBytes(
secp256k1
.keyFromPublic({
x: bytesToHex(base64UrlToBytes(vm.publicKeyJwk.x)),
y: bytesToHex(base64UrlToBytes(vm.publicKeyJwk.y))
})
.getPublic('hex')
),
publicKeyBytes: sec.ProjectivePoint.fromAffine({
x,
y
}).toRawBytes(false),
kmsKeyType: KmsKeyType.Secp256k1
};
}
Expand Down
15 changes: 14 additions & 1 deletion src/kms/key-providers/bjj-provider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Hex, PrivateKey } from '@iden3/js-crypto';
import { Hex, PrivateKey, PublicKey, Signature } from '@iden3/js-crypto';
import { BytesHelper, checkBigIntInField } from '@iden3/js-iden3-core';
import { IKeyProvider } from '../kms';
import { AbstractPrivateKeyStore, KmsKeyId, KmsKeyType } from '../store';

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

/**
* Provider for Baby Jub Jub keys
Expand All @@ -24,6 +25,9 @@ export class BjjProvider implements IKeyProvider {
* @param {AbstractPrivateKeyStore} keyStore - key store for kms
*/
constructor(keyType: KmsKeyType, keyStore: AbstractPrivateKeyStore) {
if (keyType !== KmsKeyType.BabyJubJub) {
throw new Error('Key type must be BabyJubJub');
}
this.keyType = keyType;
this.keyStore = keyStore;
}
Expand Down Expand Up @@ -88,4 +92,13 @@ export class BjjProvider implements IKeyProvider {

return new PrivateKey(Hex.decodeString(privateKeyHex));
}

async verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
const publicKey = await this.publicKey(keyId);

return PublicKey.newFromCompressed(hexToBytes(publicKey)).verifyPoseidon(
BytesHelper.bytesToInt(message),
Signature.newFromCompressed(hexToBytes(signatureHex))
);
}
}
15 changes: 14 additions & 1 deletion src/kms/key-providers/ed25519-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AbstractPrivateKeyStore, KmsKeyId, KmsKeyType } from '../store';
import * as providerHelpers from '../provider-helpers';
import { ed25519 } from '@noble/curves/ed25519';
import { bytesToHex } from '../../utils';
import { sha256 } from '@iden3/js-crypto';

/**
* Provider for Ed25519 keys
Expand Down Expand Up @@ -65,7 +66,19 @@ export class Ed25519Provider implements IKeyProvider {
*/
async sign(keyId: KmsKeyId, data: Uint8Array): Promise<Uint8Array> {
const privateKeyHex = await this.privateKey(keyId);
return ed25519.sign(data, privateKeyHex);
return ed25519.sign(sha256(data), privateKeyHex);
}

/**
* Verifies a signature for the given message and key identifier.
* @param message - The message to verify the signature against.
* @param signatureHex - The signature to verify, as a hexadecimal string.
* @param keyId - The key identifier to use for verification.
* @returns A Promise that resolves to a boolean indicating whether the signature is valid.
*/
async verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
const publicKeyHex = await this.publicKey(keyId);
return ed25519.verify(signatureHex, sha256(message), publicKeyHex);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/kms/key-providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './bjj-provider';
export * from './ed25519-provider';
export * from './sec256k1-provider';
export * from './secp256k1-provider';
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { IKeyProvider } from '../kms';
import { AbstractPrivateKeyStore, KmsKeyId, KmsKeyType } from '../store';
import Elliptic from 'elliptic';
import * as providerHelpers from '../provider-helpers';
import { ES256KSigner } from 'did-jwt';
import { base64UrlToBytes, byteEncoder, bytesToHex, hexToBytes } from '../../utils';
import { base64UrlToBytes, bytesToHex } from '../../utils';
import { secp256k1 } from '@noble/curves/secp256k1';
import { sha256 } from '@iden3/js-crypto';
import { ES256KSigner, hexToBytes } from 'did-jwt';

/**
* Provider for Sec256p1 keys256p1
* Provider for Secp256k1
* @public
* @class Sec256p1Provider
* @class Secp256k1Provider
* @implements implements IKeyProvider interface
*/
export class Sec256k1Provider implements IKeyProvider {
Expand All @@ -19,16 +20,17 @@ export class Sec256k1Provider implements IKeyProvider {
keyType: KmsKeyType;
private _keyStore: AbstractPrivateKeyStore;

private readonly _ec;
/**
* Creates an instance of BjjProvider.
* @param {KmsKeyType} keyType - kms key type
* @param {AbstractPrivateKeyStore} keyStore - key store for kms
*/
constructor(keyType: KmsKeyType, keyStore: AbstractPrivateKeyStore) {
if (keyType !== KmsKeyType.Secp256k1) {
throw new Error('Key type must be Secp256k1');
}
this.keyType = keyType;
this._keyStore = keyStore;
this._ec = new Elliptic.ec('secp256k1');
}
/**
* generates a baby jub jub key from a seed phrase
Expand All @@ -39,14 +41,15 @@ export class Sec256k1Provider implements IKeyProvider {
if (seed.length !== 32) {
throw new Error('Seed should be 32 bytes');
}
const keyPair = this._ec.keyFromPrivate(seed);
const publicKey = secp256k1.getPublicKey(seed);
const kmsId = {
type: this.keyType,
id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false))
id: providerHelpers.keyPath(this.keyType, bytesToHex(publicKey))
};

await this._keyStore.importKey({
alias: kmsId.id,
key: keyPair.getPrivate().toString('hex').padStart(64, '0')
key: bytesToHex(seed).padStart(64, '0')
});

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

/**
* signs prepared payload of size,
* with a key id
*
* @param {KmsKeyId} keyId - key identifier
* @param {Uint8Array} data - data to sign (32 bytes)
* @returns Uint8Array signature
* Signs the given data using the private key associated with the specified key identifier.
* @param keyId - The key identifier to use for signing.
* @param data - The data to sign.
* @param opts - Signing options, such as the algorithm to use.
* @returns A Promise that resolves to the signature as a Uint8Array.
*/
async sign(
keyId: KmsKeyId,
data: Uint8Array,
opts: { [key: string]: unknown } = { alg: 'ES256K' }
): Promise<Uint8Array> {
const privateKeyHex = await this.privateKey(keyId);
if (!privateKeyHex) {
throw new Error('Private key not found for keyId: ' + keyId.id);
}

const signatureBase64 = await ES256KSigner(
hexToBytes(privateKeyHex),
opts.alg === 'ES256K-R'
)(data);

const signatureHex = bytesToHex(base64UrlToBytes(signatureBase64.toString()));
if (typeof signatureHex !== 'string') {
throw new Error('Signature is not a string');
if (typeof signatureBase64 !== 'string') {
throw new Error('signatureBase64 must be a string');
}

return byteEncoder.encode(signatureBase64.toString());
return base64UrlToBytes(signatureBase64);
}

/**
* Verifies a signature for the given message and key identifier.
* @param message - The message to verify the signature against.
* @param signatureHex - The signature to verify, as a hexadecimal string.
* @param keyId - The key identifier to use for verification.
* @returns A Promise that resolves to a boolean indicating whether the signature is valid.
*/
async verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
const publicKeyHex = await this.publicKey(keyId);
return secp256k1.verify(signatureHex, sha256(message), publicKeyHex);
}

private async privateKey(keyId: KmsKeyId): Promise<string> {
Expand Down
26 changes: 26 additions & 0 deletions src/kms/kms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ export interface IKeyProvider {
* @returns `Promise<KmsKeyId>`
*/
newPrivateKeyFromSeed(seed: Uint8Array): Promise<KmsKeyId>;

/**
* Verifies a message signature using the provided key ID.
*
* @param message - The message bytes to verify.
* @param signatureHex - The signature in hexadecimal format.
* @param keyId - The KMS key ID used to verify the signature.
* @returns A promise that resolves to a boolean indicating whether the signature is valid.
*/
verify(message: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean>;
}
/**
* Key management system class contains different key providers.
Expand Down Expand Up @@ -112,4 +122,20 @@ export class KMS {

return keyProvider.sign(keyId, data, opts);
}

/**
* Verifies a signature against the provided data and key ID.
*
* @param data - The data to verify the signature against.
* @param signatureHex - The signature to verify, in hexadecimal format.
* @param keyId - The key ID to use for verification.
* @returns A promise that resolves to a boolean indicating whether the signature is valid.
*/
verify(data: Uint8Array, signatureHex: string, keyId: KmsKeyId): Promise<boolean> {
const keyProvider = this._registry.get(keyId.type);
if (!keyProvider) {
throw new Error(`keyProvider not found for: ${keyId.type}`);
}
return keyProvider.verify(data, signatureHex, keyId);
}
}
Loading

0 comments on commit 7588468

Please sign in to comment.