Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/account-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
tylerlevine marked this conversation as resolved.
Outdated
"@celo/contractkit": "^1.2.4",
"@ethereumjs/common": "^2.4.0",
"@ethereumjs/tx": "^3.3.0",
Expand Down
4 changes: 3 additions & 1 deletion modules/account-lib/src/mpc/curves/baseCurve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
13 changes: 11 additions & 2 deletions modules/account-lib/src/mpc/curves/ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
sachushaji marked this conversation as resolved.
} catch (error) {
// Invalid signature causes an exception
return false;
}
}
}
3 changes: 2 additions & 1 deletion modules/account-lib/src/mpc/curves/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import curve from './baseCurve';
import { Ed25519Curve } from './ed25519';
import { Secp256k1Curve } from './secp256k1';

export default curve;
export { Ed25519Curve };
export { Ed25519Curve, Secp256k1Curve };
55 changes: 55 additions & 0 deletions modules/account-lib/src/mpc/curves/secp256k1.ts
Original file line number Diff line number Diff line change
@@ -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 {
Comment thread
sachushajiabraham marked this conversation as resolved.
Outdated
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;
}
}
10 changes: 3 additions & 7 deletions modules/account-lib/src/mpc/tss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
sachushajiabraham marked this conversation as resolved.
Outdated
}
}
4 changes: 4 additions & 0 deletions modules/account-lib/src/mpc/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
108 changes: 108 additions & 0 deletions modules/account-lib/test/unit/mpc/curves/secp256k1.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
47 changes: 26 additions & 21 deletions modules/account-lib/test/unit/mpc/tss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]]);
Expand All @@ -42,35 +45,37 @@ 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]]);
B_sign_share = MPC.signShare(message_buffer, B_combine.pShare, [B_combine.jShares[1]]);
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]]);
C_sign_share = MPC.signShare(message_buffer, C_combine.pShare, [C_combine.jShares[1]]);
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]]);
C_sign_share = MPC.signShare(message_buffer, C_combine.pShare, [C_combine.jShares[2]]);
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 () {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -229,35 +234,35 @@ 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]]);
B_sign_share = MPC.signShare(message_buffer, B_combine.pShare, [B_combine.jShares[1]]);
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]]);
C_sign_share = MPC.signShare(message_buffer, C_combine.pShare, [C_combine.jShares[1]]);
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]]);
C_sign_share = MPC.signShare(message_buffer, C_combine.pShare, [C_combine.jShares[2]]);
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 () {
Expand Down Expand Up @@ -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);
Expand Down