diff --git a/modules/account-lib/package.json b/modules/account-lib/package.json index f17265bc2e..405cfb6d1a 100644 --- a/modules/account-lib/package.json +++ b/modules/account-lib/package.json @@ -43,6 +43,7 @@ "@bitgo/bls-dkg": "^1.0.2", "@bitgo/statics": "^6.17.0", "@bitgo/sdk-core": "^1.0.1", + "@noble/secp256k1": "git+https://github.com/brandonblack/noble-secp256k1.git#856129c06436fb696bab7d827edad5effdffaee2", "@celo/contractkit": "^1.2.4", "@ethereumjs/common": "^2.4.0", "@ethereumjs/tx": "^3.3.0", diff --git a/modules/account-lib/src/mpc/curves/baseCurve.ts b/modules/account-lib/src/mpc/curves/baseCurve.ts index e5e76a8d06..487ae03720 100644 --- a/modules/account-lib/src/mpc/curves/baseCurve.ts +++ b/modules/account-lib/src/mpc/curves/baseCurve.ts @@ -28,7 +28,9 @@ interface BaseCurve { // Function that adds two group elements. pointAdd(p: bigint, q: bigint): bigint; // Function that verifies a signature. - verify(y: bigint, signedMessage: Buffer): Buffer; + verify(message: Buffer, signature: Buffer, publicKey: bigint): boolean; + // order of the curve + order?: () => bigint; } export default BaseCurve; diff --git a/modules/account-lib/src/mpc/curves/ed25519.ts b/modules/account-lib/src/mpc/curves/ed25519.ts index 97fdde7c61..1a0b87c3d6 100644 --- a/modules/account-lib/src/mpc/curves/ed25519.ts +++ b/modules/account-lib/src/mpc/curves/ed25519.ts @@ -61,7 +61,16 @@ export class Ed25519Curve implements BaseCurve { ); } - verify(y: bigint, signedMessage: Buffer): Buffer { - return Buffer.from(sodium.crypto_sign_open(signedMessage, bigIntToBufferLE(y, 32))); + verify(message: Buffer, signature: Buffer, publicKey: bigint): boolean { + const signedMessage = Buffer.concat([signature, message]); + try { + // Returns the message which was signed if the signature is valid + const result = Buffer.from(sodium.crypto_sign_open(signedMessage, bigIntToBufferLE(publicKey))); + const isValid = !!!Buffer.compare(message, result); // Buffer compare gives 0 if equal and a non zero integer if not + return isValid; + } catch (error) { + // Invalid signature causes an exception + return false; + } } } diff --git a/modules/account-lib/src/mpc/curves/index.ts b/modules/account-lib/src/mpc/curves/index.ts index 668560f39d..c698a7cf67 100644 --- a/modules/account-lib/src/mpc/curves/index.ts +++ b/modules/account-lib/src/mpc/curves/index.ts @@ -1,5 +1,6 @@ import curve from './baseCurve'; import { Ed25519Curve } from './ed25519'; +import { Secp256k1Curve } from './secp256k1'; export default curve; -export { Ed25519Curve }; +export { Ed25519Curve, Secp256k1Curve }; diff --git a/modules/account-lib/src/mpc/curves/secp256k1.ts b/modules/account-lib/src/mpc/curves/secp256k1.ts new file mode 100644 index 0000000000..0cb0353afe --- /dev/null +++ b/modules/account-lib/src/mpc/curves/secp256k1.ts @@ -0,0 +1,55 @@ +import { bigIntFromU8ABE, bigIntToBufferBE } from '../util'; +import BaseCurve from './baseCurve'; +import * as secp from '@noble/secp256k1'; + +const order = secp.CURVE.n; + +export class Secp256k1Curve implements BaseCurve { + scalarRandom(): bigint { + return bigIntFromU8ABE(secp.utils.randomPrivateKey()); + } + + scalarAdd(x: bigint, y: bigint): bigint { + return bigIntFromU8ABE(secp.utils.privateAdd(x, bigIntToBufferBE(y))); + } + + scalarSub(x: bigint, y: bigint): bigint { + const negatedY = secp.utils.privateNegate(y); + return bigIntFromU8ABE(secp.utils.privateAdd(x, negatedY)); + } + + scalarMult(x: bigint, y: bigint): bigint { + return secp.utils.mod(x * y, order); + } + + scalarReduce(s: bigint): bigint { + return secp.utils.mod(s, order); + } + + scalarNegate(s: bigint): bigint { + return bigIntFromU8ABE(secp.utils.privateNegate(s)); + } + + scalarInvert(s: bigint): bigint { + return secp.utils.invert(s, order); + } + + pointAdd(a: bigint, b: bigint): bigint { + const pointA = secp.Point.fromHex(bigIntToBufferBE(a)); + const pointB = secp.Point.fromHex(bigIntToBufferBE(b)); + return bigIntFromU8ABE(pointA.add(pointB).toRawBytes(true)); + } + + basePointMult(n: bigint): bigint { + const point = bigIntToBufferBE(n); + return bigIntFromU8ABE(secp.getPublicKey(point, true)); + } + + verify(message: Buffer, signature: Buffer, publicKey: bigint): boolean { + return secp.verify(signature, message, bigIntToBufferBE(publicKey)); + } + + order(): bigint { + return order; + } +} diff --git a/modules/account-lib/src/mpc/tss.ts b/modules/account-lib/src/mpc/tss.ts index 719078d12d..fc667a621a 100644 --- a/modules/account-lib/src/mpc/tss.ts +++ b/modules/account-lib/src/mpc/tss.ts @@ -386,13 +386,9 @@ export default class Eddsa { return result; } - verify(message: Buffer, signature: Signature): Buffer { + verify(message: Buffer, signature: Signature): boolean { const publicKey = bigIntFromBufferLE(Buffer.from(signature.y, 'hex')); - const signedMessage = Buffer.concat([ - Buffer.from(signature.R, 'hex'), - Buffer.from(signature.sigma, 'hex'), - message, - ]); - return Eddsa.curve.verify(publicKey, signedMessage); + const signedMessage = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]); + return Eddsa.curve.verify(message, signedMessage, publicKey); } } diff --git a/modules/account-lib/src/mpc/util.ts b/modules/account-lib/src/mpc/util.ts index f4fb945706..3968091e53 100644 --- a/modules/account-lib/src/mpc/util.ts +++ b/modules/account-lib/src/mpc/util.ts @@ -16,6 +16,10 @@ export function bigIntFromBufferBE(buf: Buffer): bigint { return BigInt('0x' + buf.toString('hex')); } +export function bigIntFromU8ABE(buf: Uint8Array): bigint { + return bigIntFromBufferBE(Buffer.from(buf)); +} + export function bigIntToBufferBE(n: bigint, bytes?: number): Buffer { let v = n.toString(16); v = '0'.slice(0, v.length % 2) + v; diff --git a/modules/account-lib/test/unit/mpc/curves/secp256k1.ts b/modules/account-lib/test/unit/mpc/curves/secp256k1.ts new file mode 100644 index 0000000000..a901fbc8f5 --- /dev/null +++ b/modules/account-lib/test/unit/mpc/curves/secp256k1.ts @@ -0,0 +1,108 @@ +/** + * @prettier + */ +import { Secp256k1Curve } from '../../../../src/mpc/curves'; +import { bigIntToBufferBE } from '../../../../src/mpc/util'; + +describe('secp256k1 curve implementation', function () { + const sec256k1 = new Secp256k1Curve(); + const testValue = BigInt(12000); + + it('should properly generate a random number in the specified range', function () { + const start = BigInt(1); + const stop = sec256k1.order(); + const test = sec256k1.scalarRandom(); + const isOkay = test >= start && test <= stop; + isOkay.should.equal(true); + }); + + it('should correctly perform scalar add', function () { + const valueOne = sec256k1.scalarAdd(sec256k1.order() - BigInt(1), testValue); + valueOne.should.equal(testValue - BigInt(1)); + const valueTwo = sec256k1.scalarAdd(BigInt(5), testValue); + valueTwo.should.equal(BigInt(5) + testValue); + }); + + it('should correctly perform scalar sub', function () { + const valueOne = sec256k1.scalarSub(BigInt(2) * testValue, testValue); + valueOne.should.equal(testValue); + const valueTwo = sec256k1.scalarSub(BigInt(5), testValue); + valueTwo.should.equal(sec256k1.order() + (BigInt(5) - testValue)); + }); + + it('should correctly perform scalar multiplication', function () { + const valueOne = sec256k1.scalarMult(sec256k1.order(), testValue); + valueOne.should.equal(BigInt(0)); + const valueTwo = sec256k1.scalarMult(BigInt(5), testValue); + valueTwo.should.equal(BigInt(5) * testValue); + }); + + it('should correctly perform scalar reduce', function () { + const valueOne = sec256k1.scalarReduce(sec256k1.order() + testValue); + valueOne.should.equal(testValue); + const valueTwo = sec256k1.scalarReduce(sec256k1.order() - testValue); + valueTwo.should.equal(sec256k1.order() - testValue); + }); + + it('should correctly perform scalar negate', function () { + const valueOne = sec256k1.scalarNegate(testValue); + valueOne.should.equal(sec256k1.order() - testValue); + const valueTwo = sec256k1.scalarNegate(sec256k1.order() - testValue); + valueTwo.should.equal(testValue); + }); + + it('should correctly perform inverts', function () { + // Had to convert bigint to string as comparison was not properly working in mocha + const testOne = sec256k1.scalarInvert(BigInt(3)).toString(); + testOne.should.equal('77194726158210796949047323339125271901891709519383269588403442094345440996225'); + + const testTwo = sec256k1.scalarInvert(sec256k1.order() + BigInt(1)).toString(); + testTwo.should.equal('1'); + + const testThree = sec256k1 + .scalarInvert(BigInt('57657865876576467547584635432343132435146576543543134312435443514313234131324551034534')) + .toString(); + testThree.should.equal('45199239020920791752653273479365580486664737121914751844510223387235717825519'); + + const testFour = sec256k1.scalarInvert(BigInt('-23232424342224')).toString(); + testFour.should.equal('109007816100548644677753129517258866095932985532097924870123596204155595187610'); + + const testFive = sec256k1.scalarInvert(testValue); + testFive.toString().should.equal('16664411509403755791375590925833668071820872792496863322396593062116822075060'); + // Checking invert(invert(x)) === x + sec256k1.scalarInvert(testFive).should.equal(testValue); + }); + + it('should correctly perform point add', function () { + const A = BigInt('0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'); + const B = BigInt('0x0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'); + const publicKey = '0x0' + sec256k1.pointAdd(A, B).toString(16); + publicKey.should.equal('0x03e2d3ec45106a89f82e7d46400faefc191ed72344caa781bfa97677a999d0828e'); + }); + + it('should correctly perform base point multiplication', function () { + const privKey = BigInt('0x79FE45D61339181238E49424E905446A35497A8ADEA8B7D5241A1E7F2C95A04D'); + const publicKey = '0x0' + sec256k1.basePointMult(privKey).toString(16); + publicKey.should.equal('0x032a574ea59cae80b09d6ba415746e9b031abfbe83f149b43b37be035b87164872'); + }); + + it('should correctly verify signature', function () { + const publicKey = BigInt('0x33ccfc7998b1ce2fad8b3f287d873f07ca8b3037897c2136c11100c02213c028e'); + const signature = bigIntToBufferBE( + BigInt( + '0x6a43d5da66ec4b4c6eb307d18791b9744cc59dd2402ad395bb9efb513898c07f767139118b76e387564bf572b2cc481dbf3068b7ac27620f83a2819d735b7e5f', + ), + ); + const message = bigIntToBufferBE(BigInt('0xbe5548911159dca31c02102c9df8c842adb50ac6def360c343ee5586f2749c0c')); + let verify = sec256k1.verify(message, signature, publicKey); + verify.should.equal(true); + const messageWrong = bigIntToBufferBE(BigInt('0xd3b91b70417e766b738288cb1c5f6ef13a607011134544d4bce2ebf6ed5944ef')); + verify = sec256k1.verify(messageWrong, signature, publicKey); + verify.should.equal(false); + }); + + it('should have the correct curve order', function () { + const curveOrder = sec256k1.order().toString(16).toUpperCase(); + curveOrder.should.equal('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'); + }); +}); diff --git a/modules/account-lib/test/unit/mpc/tss.ts b/modules/account-lib/test/unit/mpc/tss.ts index 6aea136c94..454c637e63 100644 --- a/modules/account-lib/test/unit/mpc/tss.ts +++ b/modules/account-lib/test/unit/mpc/tss.ts @@ -34,6 +34,9 @@ describe('TSS EDDSA key generation and signing', function () { const message = 'MPC on a Friday night'; const message_buffer = Buffer.from(message); + const incorrect_message = 'MPC on a Monday night'; + const incorrect_message_buffer = Buffer.from(incorrect_message); + // signing with 3-3 signatures let A_sign_share = MPC.signShare(message_buffer, A_combine.pShare, [A_combine.jShares[2], A_combine.jShares[3]]); let B_sign_share = MPC.signShare(message_buffer, B_combine.pShare, [B_combine.jShares[1], B_combine.jShares[3]]); @@ -42,8 +45,10 @@ describe('TSS EDDSA key generation and signing', function () { let B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [A_sign_share.rShares[2], C_sign_share.rShares[2]]); let C_sign = MPC.sign(message_buffer, C_sign_share.xShare, [A_sign_share.rShares[3], B_sign_share.rShares[3]]); let signature = MPC.signCombine([A_sign, B_sign, C_sign]); - let result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + let result = MPC.verify(message_buffer, signature); + result.should.equal(true); + const resultTwo = MPC.verify(incorrect_message_buffer, signature); + resultTwo.should.equal(false); // signing with A and B A_sign_share = MPC.signShare(message_buffer, A_combine.pShare, [A_combine.jShares[2]]); @@ -51,8 +56,8 @@ describe('TSS EDDSA key generation and signing', function () { A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [B_sign_share.rShares[1]], [C.yShares[1]]); B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [A_sign_share.rShares[2]], [C.yShares[2]]); signature = MPC.signCombine([A_sign, B_sign]); - result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + result = MPC.verify(message_buffer, signature); + result.should.equal(true); // signing with A and C A_sign_share = MPC.signShare(message_buffer, A_combine.pShare, [A_combine.jShares[3]]); @@ -60,8 +65,8 @@ describe('TSS EDDSA key generation and signing', function () { A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [C_sign_share.rShares[1]], [B.yShares[1]]); C_sign = MPC.sign(message_buffer, C_sign_share.xShare, [A_sign_share.rShares[3]], [B.yShares[3]]); signature = MPC.signCombine([A_sign, C_sign]); - result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + result = MPC.verify(message_buffer, signature); + result.should.equal(true); // signing with B and C B_sign_share = MPC.signShare(message_buffer, B_combine.pShare, [B_combine.jShares[3]]); @@ -69,8 +74,8 @@ describe('TSS EDDSA key generation and signing', function () { B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [C_sign_share.rShares[2]], [A.yShares[2]]); C_sign = MPC.sign(message_buffer, C_sign_share.xShare, [B_sign_share.rShares[3]], [A.yShares[3]]); signature = MPC.signCombine([B_sign, C_sign]); - result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + result = MPC.verify(message_buffer, signature); + result.should.equal(true); }); it('should verify BIP32 subkey signature', function () { @@ -115,8 +120,8 @@ describe('TSS EDDSA key generation and signing', function () { const A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [B_sign_share.rShares[1]], [C.yShares[1]]); const B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [A_sign_share.rShares[2]], [C.yShares[2]]); const signature = MPC.signCombine([A_sign, B_sign]); - const result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + const result = MPC.verify(message_buffer, signature); + result.should.equal(true); // Verify the public key in the signature equals the separately derived public subkey. signature.y.should.equal(y); @@ -198,7 +203,7 @@ describe('TSS EDDSA key generation and signing', function () { const A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [B_sign_share.rShares[1]]); const signature = MPC.signCombine([A_sign]); - MPC.verify.bind(MPC, message_buffer, signature).should.throw(); + MPC.verify(message_buffer, signature).should.equal(false); }); describe('with specific seed', function () { @@ -229,8 +234,8 @@ describe('TSS EDDSA key generation and signing', function () { let B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [A_sign_share.rShares[2], C_sign_share.rShares[2]]); let C_sign = MPC.sign(message_buffer, C_sign_share.xShare, [A_sign_share.rShares[3], B_sign_share.rShares[3]]); let signature = MPC.signCombine([A_sign, B_sign, C_sign]); - let result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + let result = MPC.verify(message_buffer, signature); + result.should.equal(true); // signing with A and B A_sign_share = MPC.signShare(message_buffer, A_combine.pShare, [A_combine.jShares[2]]); @@ -238,8 +243,8 @@ describe('TSS EDDSA key generation and signing', function () { A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [B_sign_share.rShares[1]], [C.yShares[1]]); B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [A_sign_share.rShares[2]], [C.yShares[2]]); signature = MPC.signCombine([A_sign, B_sign]); - result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + result = MPC.verify(message_buffer, signature); + result.should.equal(true); // signing with A and C A_sign_share = MPC.signShare(message_buffer, A_combine.pShare, [A_combine.jShares[3]]); @@ -247,8 +252,8 @@ describe('TSS EDDSA key generation and signing', function () { A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [C_sign_share.rShares[1]], [B.yShares[1]]); C_sign = MPC.sign(message_buffer, C_sign_share.xShare, [A_sign_share.rShares[3]], [B.yShares[3]]); signature = MPC.signCombine([A_sign, C_sign]); - result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + result = MPC.verify(message_buffer, signature); + result.should.equal(true); // signing with B and C B_sign_share = MPC.signShare(message_buffer, B_combine.pShare, [B_combine.jShares[3]]); @@ -256,8 +261,8 @@ describe('TSS EDDSA key generation and signing', function () { B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [C_sign_share.rShares[2]], [A.yShares[2]]); C_sign = MPC.sign(message_buffer, C_sign_share.xShare, [B_sign_share.rShares[3]], [A.yShares[3]]); signature = MPC.signCombine([B_sign, C_sign]); - result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + result = MPC.verify(message_buffer, signature); + result.should.equal(true); }); it('should verify BIP32 subkey signature', function () { @@ -309,8 +314,8 @@ describe('TSS EDDSA key generation and signing', function () { const A_sign = MPC.sign(message_buffer, A_sign_share.xShare, [B_sign_share.rShares[1]], [C.yShares[1]]); const B_sign = MPC.sign(message_buffer, B_sign_share.xShare, [A_sign_share.rShares[2]], [C.yShares[2]]); const signature = MPC.signCombine([A_sign, B_sign]); - const result = MPC.verify(message_buffer, signature).toString(); - result.should.equal(message); + const result = MPC.verify(message_buffer, signature); + result.should.equal(true); // Verify the public key in the signature equals the separately derived public subkey. signature.y.should.equal(y);