From 39b2d97c3d9e2b3e5a39f101b17c5472c0fca5dc Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 3 Apr 2020 01:17:57 +0100 Subject: [PATCH 1/3] Remove temp-signatures, use forked repo for fix Secp256k1KeyPair Signed-off-by: Sam Hellawell --- package.json | 1 + src/modules/vc.js | 3 +- src/utils/vc/signatures.js | 36 ++++ src/utils/vc/temp-signatures.js | 310 -------------------------------- yarn.lock | 7 + 5 files changed, 46 insertions(+), 311 deletions(-) create mode 100644 src/utils/vc/signatures.js delete mode 100644 src/utils/vc/temp-signatures.js diff --git a/package.json b/package.json index bd6b2a640..0246ca971 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "ecdsa-secp256k1-signature-2019": "^1.0.0", "elliptic": "^6.5.2", "ethr-did-resolver": "^2.2.0", + "secp256k1-key-pair": "https://github.com/SamHellawell/secp256k1-key-pair", "vc-js": "^0.5.0" } } diff --git a/src/modules/vc.js b/src/modules/vc.js index 5e454a284..70b9bbc14 100644 --- a/src/modules/vc.js +++ b/src/modules/vc.js @@ -2,7 +2,8 @@ import documentLoader from '../utils/vc/document-loader'; import {issue, verifyCredential, createPresentation, signPresentation, verify} from 'vc-js'; import {Ed25519KeyPair, suites} from 'jsonld-signatures'; const {Ed25519Signature2018} = suites; -import {EcdsaSepc256k1Signature2019, Secp256k1KeyPair} from '../utils/vc/temp-signatures'; +import Secp256k1KeyPair from 'secp256k1-key-pair'; +import {EcdsaSepc256k1Signature2019} from '../utils/vc/signatures'; /** Class to sign and verify Verifiable Credentials */ diff --git a/src/utils/vc/signatures.js b/src/utils/vc/signatures.js new file mode 100644 index 000000000..21c9bf369 --- /dev/null +++ b/src/utils/vc/signatures.js @@ -0,0 +1,36 @@ +import Secp256k1KeyPair from 'secp256k1-key-pair'; +import {suites} from 'jsonld-signatures'; + +export class EcdsaSepc256k1Signature2019 extends suites.JwsLinkedDataSignature { + /** + * @param type {string} Provided by subclass. + * + * One of these parameters is required to use a suite for signing: + * + * @param [creator] {string} A key id URL to the paired public key. + * @param [verificationMethod] {string} A key id URL to the paired public key. + * + * This parameter is required for signing: + * + * @param [signer] {function} an optional signer. + * + * Advanced optional parameters and overrides: + * + * @param [proof] {object} a JSON-LD document with options to use for + * the `proof` node (e.g. any other custom fields can be provided here + * using a context different from security-v2). + * @param [date] {string|Date} signing date to use if not passed. + * @param [key] {LDKeyPair} an optional crypto-ld KeyPair. + * @param [useNativeCanonize] {boolean} true to use a native canonize + * algorithm. + */ + constructor({ + signer, key, creator, verificationMethod, proof, date, useNativeCanonize + } = {}) { + super({ + type: 'EcdsaSecp256k1Signature2019', alg: 'ES256K', + LDKeyClass: Secp256k1KeyPair, creator, verificationMethod, signer, key, + proof, date, useNativeCanonize}); + this.requiredKeyType = 'EcdsaSecp256k1VerificationKey2019'; + } +} diff --git a/src/utils/vc/temp-signatures.js b/src/utils/vc/temp-signatures.js deleted file mode 100644 index 37d21864c..000000000 --- a/src/utils/vc/temp-signatures.js +++ /dev/null @@ -1,310 +0,0 @@ -import crypto from 'crypto'; -import forge from 'node-forge'; -const {util: {binary: {base58}}} = forge; -import elliptic from 'elliptic'; -const ec = (elliptic).ec('secp256k1'); -import util from 'secp256k1-key-pair/lib/util'; -import {suites} from 'jsonld-signatures'; -const {JwsLinkedDataSignature} = suites; - -//TODO: remove this file and integrate our own signatures - -export class Secp256k1KeyPair { - /** - * @param {KeyPairOptions} options - The options to use. - * @param {string} options.id - The key ID. - * @param {string} options.controller - The key controller. - * @param {string} options.publicKeyBase58 - The Base58 encoded Public Key. - * @param {string} options.privateKeyBase58 - The Base58 Private Key. - */ - constructor(options = {}) { - this.id = options.id; - this.controller = options.controller; - this.type = 'EcdsaSecp256k1VerificationKey2019'; - this.privateKeyBase58 = options.privateKeyBase58; - this.publicKeyBase58 = options.publicKeyBase58; - } - - /** - * Returns the Base58 encoded public key. - * - * @returns {string} The Base58 encoded public key. - */ - get publicKey() { - return this.publicKeyBase58; - } - - /** - * Returns the Base58 encoded private key. - * - * @returns {string} The Base58 encoded private key. - */ - get privateKey() { - return this.privateKeyBase58; - } - - /** - * Generates a KeyPair with an optional deterministic seed. - * @param {KeyPairOptions} [options={}] - The options to use. - * - * @returns {Promise} Generates a key pair. - */ - static async generate(options = {}) { - const key = ec.genKeyPair(); - const pubPoint = key.getPublic(); - // encode public X and Y in compressed form - const publicKeyBase58 = base58.encode(new Uint8Array( - pubPoint.encodeCompressed())); - const privateKeyBase58 = base58.encode(new Uint8Array( - key.getPrivate().toArray())); - return new Secp256k1KeyPair({ - privateKeyBase58, - publicKeyBase58, - ...options - }); - } - - /** - * Returns a signer object for use with jsonld-signatures. - * - * @returns {{sign: Function}} A signer for the json-ld block. - */ - signer() { - return secp256SignerFactory(this); - } - - /** - * Returns a verifier object for use with jsonld-signatures. - * - * @returns {{verify: Function}} Used to verify jsonld-signatures. - */ - verifier() { - return secp256VerifierFactory(this); - } - - /** - * Adds a public key base to a public key node. - * - * @param {Object} publicKeyNode - The public key node in a jsonld-signature. - * @param {string} publicKeyNode.publicKeyBase58 - Base58 Public Key for - * jsonld-signatures. - * - * @returns {Object} A PublicKeyNode in a block. - */ - addEncodedPublicKey(publicKeyNode) { - publicKeyNode.publicKeyBase58 = this.publicKeyBase58; - return publicKeyNode; - } - - /** - * Generates and returns a public key fingerprint. - * - * @param {string} publicKeyBase58 - The base58 encoded public key material. - * - * @returns {string} The fingerprint. - */ - static fingerprintFromPublicKey({publicKeyBase58}) { - const pubkeyBytes = util.base58Decode({ - decode: base58.decode, - keyMaterial: publicKeyBase58, - type: 'public' - }); - const buffer = new Uint8Array(2 + pubkeyBytes.length); - // See https://github.com/multiformats/multicodec/blob/master/table.csv - // 0xe7 is Secp256k1 public key - buffer[0] = 0xe7; // - buffer[1] = 0x01; - buffer.set(pubkeyBytes, 2); - // prefix with `z` to indicate multi-base base58btc encoding - return `z${base58.encode(buffer)}`; - } - - /** - * Generates and returns a public key fingerprint. - * - * @returns {string} The fingerprint. - */ - fingerprint() { - const {publicKeyBase58} = this; - return Secp256k1KeyPair.fingerprintFromPublicKey({publicKeyBase58}); - } - - /** - * Tests whether the fingerprint was generated from a given key pair. - * - * @param {string} fingerprint - A Base58 public key. - * - * @returns {Object} An object indicating valid is true or false. - */ - verifyFingerprint(fingerprint) { - // fingerprint should have `z` prefix indicating - // that it's multi-base encoded - if(!(typeof fingerprint === 'string' && fingerprint[0] === 'z')) { - return { - error: new Error('`fingerprint` must be a multibase encoded string.'), - valid: false - }; - } - let fingerprintBuffer; - try { - fingerprintBuffer = util.base58Decode({ - decode: base58.decode, - keyMaterial: fingerprint.slice(1), - type: 'fingerprint\'s' - }); - } catch(e) { - return {error: e, valid: false}; - } - let publicKeyBuffer; - try { - publicKeyBuffer = util.base58Decode({ - decode: base58.decode, - keyMaterial: this.publicKeyBase58, - type: 'public' - }); - } catch(e) { - return {error: e, valid: false}; - } - - // validate the first two multicodec bytes 0xe701 - const valid = fingerprintBuffer.slice(0, 2).toString('hex') === 'e701' && - publicKeyBuffer.equals(fingerprintBuffer.slice(2)); - if(!valid) { - return { - error: new Error('The fingerprint does not match the public key.'), - valid: false - }; - } - return {valid}; - } - - static async from(options) { - return new Secp256k1KeyPair(options); - } - - /** - * Contains the public key for the KeyPair - * and other information that json-ld Signatures can use to form a proof. - * @param {Object} [options={}] - Needs either a controller or owner. - * @param {string} [options.controller=this.controller] - DID of the - * person/entity controlling this key pair. - * - * @returns {Object} A public node with - * information used in verification methods by signatures. - */ - publicNode({controller = this.controller} = {}) { - const publicNode = { - id: this.id, - type: this.type, - }; - if(controller) { - publicNode.controller = controller; - } - this.addEncodedPublicKey(publicNode); // Subclass-specific - return publicNode; - } -} - -/** - * @ignore - * Returns an object with an async sign function. - * The sign function is bound to the KeyPair - * and then returned by the KeyPair's signer method. - * @param {Secp256k1KeyPair} key - An Secp256k1KeyPair. - * - * @returns {{sign: Function}} An object with an async function sign - * using the private key passed in. - */ -function secp256SignerFactory(key) { - if(!key.privateKeyBase58) { - return { - async sign() { - throw new Error('No private key to sign with.'); - } - }; - } - - const privateKey = util.base58Decode({ - decode: base58.decode, - keyMaterial: key.privateKeyBase58, - type: 'private' - }); - const k = ec.keyPair({ - priv: privateKey.toString('hex'), - privEnc: 'hex' - }); - return { - async sign({data}) { - const md = crypto.createHash('sha256').update(data).digest(); - return new Uint8Array(k.sign(md).toDER()); - } - }; -} - -/** - * @ignore - * Returns an object with an async verify function. - * The verify function is bound to the KeyPair - * and then returned by the KeyPair's verifier method. - * @param {Secp256k1KeyPair} key - An Secp256k1KeyPair. - * - * @returns {{verify: Function}} An async verifier specific - * to the key passed in. - */ -function secp256VerifierFactory(key) { - const publicKey = util.base58Decode({ - decode: base58.decode, - keyMaterial: key.publicKeyBase58, - type: 'public' - }); - const k = ec.keyPair({ - pub: publicKey.toString('hex'), - pubEnc: 'hex' - }); - return { - async verify({data, signature}) { - const md = crypto.createHash('sha256').update(data).digest(); - let verified = false; - try { - verified = k.verify(md, signature); - } catch(e) { - console.error('An error occurred when verifying signature: ', e); - } - return verified; - } - }; -} - -export class EcdsaSepc256k1Signature2019 extends JwsLinkedDataSignature { - /** - * @param type {string} Provided by subclass. - * - * One of these parameters is required to use a suite for signing: - * - * @param [creator] {string} A key id URL to the paired public key. - * @param [verificationMethod] {string} A key id URL to the paired public key. - * - * This parameter is required for signing: - * - * @param [signer] {function} an optional signer. - * - * Advanced optional parameters and overrides: - * - * @param [proof] {object} a JSON-LD document with options to use for - * the `proof` node (e.g. any other custom fields can be provided here - * using a context different from security-v2). - * @param [date] {string|Date} signing date to use if not passed. - * @param [key] {LDKeyPair} an optional crypto-ld KeyPair. - * @param [useNativeCanonize] {boolean} true to use a native canonize - * algorithm. - */ - constructor({ - signer, key, creator, verificationMethod, proof, date, useNativeCanonize - } = {}) { - super({ - type: 'EcdsaSecp256k1Signature2019', alg: 'ES256K', - LDKeyClass: Secp256k1KeyPair, creator, verificationMethod, signer, key, - proof, date, useNativeCanonize}); - this.requiredKeyType = 'EcdsaSecp256k1VerificationKey2019'; - } -} diff --git a/yarn.lock b/yarn.lock index 01f2b4e5d..6ee3cbbeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5163,6 +5163,13 @@ secp256k1-key-pair@^1.0.0: elliptic "^6.5.0" node-forge "^0.8.5" +"secp256k1-key-pair@https://github.com/SamHellawell/secp256k1-key-pair": + version "1.1.1-0" + resolved "https://github.com/SamHellawell/secp256k1-key-pair#319ce02e0366503aea0c87c49e8073fe7ae5f357" + dependencies: + elliptic "^6.5.0" + node-forge "^0.8.5" + security-context@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/security-context/-/security-context-4.0.0.tgz#e73f5d22bee9c7699a02eaaced359d001dc948e9" From bb1a31d2efe2620a18c940f662be8a4a3d60d752 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 3 Apr 2020 01:21:01 +0100 Subject: [PATCH 2/3] add todo comment Signed-off-by: Sam Hellawell --- src/utils/vc/signatures.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/vc/signatures.js b/src/utils/vc/signatures.js index 21c9bf369..6a996714d 100644 --- a/src/utils/vc/signatures.js +++ b/src/utils/vc/signatures.js @@ -1,6 +1,8 @@ import Secp256k1KeyPair from 'secp256k1-key-pair'; import {suites} from 'jsonld-signatures'; +// TODO: use this package https://github.com/digitalbazaar/ecdsa-secp256k1-signature-2019 +// once this PR is merged: https://github.com/digitalbazaar/secp256k1-key-pair/pull/8 export class EcdsaSepc256k1Signature2019 extends suites.JwsLinkedDataSignature { /** * @param type {string} Provided by subclass. From 8cfb6f7d664bb2817c5b1d950b6a946d4c53e883 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 3 Apr 2020 18:02:27 +0100 Subject: [PATCH 3/3] update secp256k1-key-pair package Signed-off-by: Sam Hellawell --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 6ee3cbbeb..c04e0a2c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5165,7 +5165,7 @@ secp256k1-key-pair@^1.0.0: "secp256k1-key-pair@https://github.com/SamHellawell/secp256k1-key-pair": version "1.1.1-0" - resolved "https://github.com/SamHellawell/secp256k1-key-pair#319ce02e0366503aea0c87c49e8073fe7ae5f357" + resolved "https://github.com/SamHellawell/secp256k1-key-pair#da66af97da3944b864406ebe5a8aec96cb6152a5" dependencies: elliptic "^6.5.0" node-forge "^0.8.5"