diff --git a/modules/sdk-lib-mpc/package.json b/modules/sdk-lib-mpc/package.json index 138a3cebfd..2426626a1a 100644 --- a/modules/sdk-lib-mpc/package.json +++ b/modules/sdk-lib-mpc/package.json @@ -45,7 +45,8 @@ "cbor": "^9.0.1", "libsodium-wrappers-sumo": "^0.7.9", "openpgp": "5.10.1", - "paillier-bigint": "3.3.0" + "paillier-bigint": "3.3.0", + "secp256k1": "5.0.0" }, "devDependencies": { "@types/lodash": "^4.14.151", diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts index c45ac9be4e..edc54a292d 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts @@ -3,6 +3,10 @@ import { Secp256k1Curve } from '../../curves'; import { bigIntFromBufferBE, bigIntToBufferBE } from '../../util'; import { DeserializedDklsSignature } from './types'; import { decode } from 'cbor'; +import * as secp256k1 from 'secp256k1'; +import { Hash, createHash } from 'crypto'; + +const delimeter = ':'; /** * Combines partial signatures from parties participating in DSG. @@ -27,3 +31,36 @@ export function combinePartialSignatures(round4MessagePayloads: Uint8Array[], rH S: new Uint8Array(bigIntToBufferBE(normalizedSig.s)), }; } + +/** + * Verify a DKLs Signature and serialize it to recid:r:s:publickey format. + * @param message - message that was signed. + * @param dklsSignature - R and S values of the ECDSA signature. + * @param commonKeychain - public key appended to chaincode in hex. + * @param hash - optional hash function to apply on message before verifying. Default is sha256. + * @param shouldHash - flag to determine whether message should be hashed before verifying. + * @returns {string} - serialized signature in `recid:r:s:publickey` format + */ +export function verifyAndConvertDklsSignature( + message: Buffer, + dklsSignature: DeserializedDklsSignature, + commonKeychain: string, + hash?: Hash, + shouldHash = true +): string { + const messageToVerify = shouldHash ? (hash || createHash('sha256')).update(message).digest() : message; + const pub0 = secp256k1.ecdsaRecover(Buffer.concat([dklsSignature.R, dklsSignature.S]), 0, messageToVerify, true); + const pub1 = secp256k1.ecdsaRecover(Buffer.concat([dklsSignature.R, dklsSignature.S]), 1, messageToVerify, true); + const truePub = commonKeychain.slice(0, 66); + let recId: number; + if (truePub === Buffer.from(pub0).toString('hex')) { + recId = 0; + } else if (truePub === Buffer.from(pub1).toString('hex')) { + recId = 1; + } else { + throw Error('Invalid Signature'); + } + return `${recId}${delimeter}${Buffer.from(dklsSignature.R).toString('hex')}${delimeter}${Buffer.from( + dklsSignature.S + ).toString('hex')}${delimeter}${truePub}`; +} diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts index 6f9200f254..e1ab00cb70 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts @@ -5,7 +5,7 @@ import should from 'should'; import { Keyshare } from '@silencelaboratories/dkls-wasm-ll-node'; import { decode } from 'cbor'; import { Secp256k1Bip32HdTree, bigIntFromBufferBE, bigIntToBufferBE } from '../../../../src'; -import * as secp256k1 from 'secp256k1'; +import { verifyAndConvertDklsSignature } from '../../../../src/tss/ecdsa-dkls/util'; describe('DKLS Dsg 2x3', function () { const vectors = [ @@ -112,24 +112,17 @@ describe('DKLS Dsg 2x3', function () { const chaincode = bigIntFromBufferBE(Buffer.from(decode(keyShare.toBytes()).root_chain_code)); const hdTree = new Secp256k1Bip32HdTree(); const derivedKey = hdTree.publicDerive({ pk: pk, chaincode: chaincode }, vector.derivationPath); - const pub1 = secp256k1.ecdsaRecover( - Buffer.concat([party1.signature.R, party1.signature.S]), - 0, - crypto.createHash('sha256').update(Buffer.from(vector.msgToSign, 'hex')).digest(), - true - ); - const pub2 = secp256k1.ecdsaRecover( - Buffer.concat([party1.signature.R, party1.signature.S]), - 1, - crypto.createHash('sha256').update(Buffer.from(vector.msgToSign, 'hex')).digest(), - true - ); const derivedPub = - vector.derivationPath === 'm' ? keyShare.publicKey : new Uint8Array(bigIntToBufferBE(derivedKey.pk)); - ( - (pub1.every((p) => derivedPub.includes(p)) && derivedPub.every((p) => pub1.includes(p))) || - (pub2.every((p) => derivedPub.includes(p)) && derivedPub.every((p) => pub2.includes(p))) - ).should.equal(true); + vector.derivationPath === 'm' + ? Buffer.from(keyShare.publicKey).toString('hex') + : bigIntToBufferBE(derivedKey.pk).toString('hex'); + const convertedSignature = verifyAndConvertDklsSignature( + Buffer.from(vector.msgToSign, 'hex'), + party1.signature, + derivedPub + ); + should.exist(convertedSignature); + convertedSignature.split(':').length.should.equal(4); }); });