Skip to content

[BWC/BWS] Add TSS to BWC & BWS #3895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
24,111 changes: 10,884 additions & 13,227 deletions packages/bitcore-node/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/bitcore-p2p-cash/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/bitcore-p2p-doge/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/bitcore-p2p/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 25 additions & 18 deletions packages/bitcore-tss/ecdsa/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ class KeyGen {
#round;

/**
*
* @param {Object} params
* @param {number} params.n - number of participants
* @param {number} params.m - minimum number of signers
* @param {number} params.partyId - party id
* @param {Buffer} params.seed - OPTIONAL seed for the DKG
* @param {Buffer} params.authKey - authentication key for the DKG
* @param {number} params.round - round number for the DKG
* Create a new Threshold Signature Scheme (TSS) key generation instance
* @param {object} params
* @param {number} params.n Number of participants
* @param {number} params.m Minimum number of signers
* @param {number} params.partyId Party id
* @param {Buffer} [params.seed] Seed for the DKG. Randomly generated if not given
* @param {Buffer} params.authKey Authentication key for the DKG
* @param {number} params.round Round number for the DKG
*/
constructor({ n, m, partyId, seed, authKey, round }) {
$.checkArgument(n != null, 'n is required');
Expand Down Expand Up @@ -51,6 +51,10 @@ class KeyGen {
this.#dkg = new DklsDkg.Dkg(this.#partySize, this.#minSigners, this.#partyId, this.#seed);
}

getRound() {
return this.#round;
}

/**
* Export the keygen session to a base64 encoded string
* @returns {string} Base64 encoded session string
Expand All @@ -60,25 +64,27 @@ class KeyGen {
$.checkState(!this.isKeyChainReady(), 'Cannot export a completed session. The keychain is ready with getKeyChain()');
const chainCodeCommitment = this.#dkg.chainCodeCommitment;
const sessionBytes = this.#dkg.dkgSessionBytes || this.#dkg.dkgSession?.toBytes();
const seedBytes = this.#dkg.seed;
const payload = this.#round +
':' + this.#partySize +
':' + this.#minSigners +
':' + this.#partyId +
':' + Buffer.from(sessionBytes).toString('base64') +
':' + Buffer.from(chainCodeCommitment || []).toString('base64');
':' + Buffer.from(chainCodeCommitment || []).toString('base64') +
':' + seedBytes.toString('base64');

const buf = encrypt(Buffer.from(payload, 'utf8'), this.#authKey.publicKey, this.#authKey);
return buf.toString('base64');
}

/**
* Restore a keygen session from an exported session
* @param {Object} params
* @param {object} params
* @param {string} params.session Base64 encoded session string
* @param {bitcoreLib.PrivateKey} params.authKey Private key to use for decrypting the session
* @param {Buffer} params.seed Seed used for key generation
* @returns {Sign}
*/
static async restore({ session, authKey, seed }) {
static async restore({ session, authKey }) {
const _authKey = new bitcoreLib.PrivateKey(authKey);
$.checkArgument(_authKey.toString('hex') === authKey.toString('hex') || _authKey.toWIF() === authKey, 'Unrecognized authKey format');
session = decrypt(Buffer.from(session, 'base64'), _authKey.publicKey, _authKey).toString('utf8');
Expand All @@ -88,15 +94,16 @@ class KeyGen {
minSigners,
partyId,
sessionBytes,
chainCodeCommitment
chainCodeCommitment,
seedBytes
] = session.split(':');
const initParams = {
round: parseInt(round),
n: parseInt(partySize),
m: parseInt(minSigners),
partyId: parseInt(partyId),
authKey,
seed: Buffer.from(seed, 'base64'),
seed: Buffer.from(seedBytes, 'base64')
};
const keygen = new KeyGen(initParams);
await keygen.#dkg.loadDklsWasm();
Expand All @@ -109,7 +116,7 @@ class KeyGen {
/**
* @private
* Format the message to be sent to the other parties
* @param {Object} signedMessage
* @param {object} signedMessage
* @returns
*/
_formatMessage(signedMessage) {
Expand All @@ -124,7 +131,7 @@ class KeyGen {

/**
* Initialize the keygen session with a broadcast message to send to the other participants
* @returns {Promise<{round: number, partyId: number, publicKey: string, p2pMessages: Object[], broadcastMessages: Object[]}>}
* @returns {Promise<{round: number, partyId: number, publicKey: string, p2pMessages: object[], broadcastMessages: object[]}>}
*/
async initJoin() {
$.checkState(this.#round == 0, 'initJoin must be called before the rounds ');
Expand All @@ -143,8 +150,8 @@ class KeyGen {
/**
* Call this after receiving the initJoin broadcast messages from the other participants
* and while isKeyChainReady() is false
* @param {Array<Object>} prevRoundMessages
* @returns {{ round: number, partyId: number, publicKey: string, p2pMessages: Object[], broadcastMessage: Object[] }}
* @param {Array<object>} prevRoundMessages
* @returns {{ round: number, partyId: number, publicKey: string, p2pMessages: object[], broadcastMessage: object[] }}
*/
nextRound(prevRoundMessages) {
$.checkState(this.#round > 0, 'initJoin must be called before participating in the rounds');
Expand Down
35 changes: 19 additions & 16 deletions packages/bitcore-tss/ecies/ecies.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ function KDF(privateKey, publicKey) {

/**
* Encrypts the message (String or Buffer) using EC keys.
* @param {Object} params
* @param {Buffer|string} params.message - Message to be encrypted.
* @param {PublicKey} params.publicKey - Receipient's public key is used to encrypt the message.
* @param {PrivateKey} params.privateKey - Your private key is used to sign the payload.
* @param {Buffer} params.ivbuf (optional) 16-byte initialization vector (IV) Buffer to be used in AES-CBC.
* By default, `ivbuf` is computed deterministically from message and private key using HMAC-SHA256.
* Deterministic IV enables end-to-end test vectors for alternative implementations.
* @param {object} params
* @param {Buffer|string} params.message Message to be encrypted.
* @param {PublicKey} params.publicKey Receipient's public key is used to encrypt the message.
* @param {PrivateKey} params.privateKey Your private key is used to sign the payload.
* @param {Buffer} [params.ivbuf] (optional) 16-byte initialization vector (IV) Buffer to be used in AES-CBC.
* By default, `ivbuf` is randomly generated.
* @param {object} params.opts (optional) Options object. Every field is optional.
* @param {boolean} params.opts.noKey Do not include pubkey in the output.
* @param {boolean} params.opts.shortTag Use 4-byte tag instead of 32-byte. This must be communicated to the payload recipient.
* @param {boolean} params.opts.deterministicIv Compute IV deterministically from message and private key using HMAC-SHA256.
* A deterministic IV enables end-to-end test vectors for alternative implementations.
* Note that identical messages have identical ciphertexts. If it is important to not allow an attacker
* to learn that a message is repeated, then you should use a custom IV *or* use some sequence identifier
* or "salt" inside the message.
* @param {Object} params.opts (optional) Options object. Every field is optional.
* @param {boolean} params.opts.noKey - Do not include pubkey in the output.
* @param {boolean} params.opts.shortTag - Use 4-byte tag instead of 32-byte. This must be communicated to the payload recipient.
* to learn that a message is repeated, then you should leave opts.deterministicIv to false, pass in a custom IV
* with `ivbuf`, or use a salt inside the message.
* @returns {Buffer} Payload buffer with `pubkey|iv|ciphertext|tag` (pubkey is excluded if `noKey` is given).
*/
function encrypt({ message, publicKey, privateKey, ivbuf, opts = {} }) {
Expand All @@ -49,8 +50,10 @@ function encrypt({ message, publicKey, privateKey, ivbuf, opts = {} }) {
if (!Buffer.isBuffer(message)) {
message = Buffer.from(message);
}
if (!ivbuf) {
if (opts.deterministicIv) {
ivbuf = Hash.sha256hmac(message, privateKey.toBuffer()).subarray(0, 16);
} else if (!ivbuf) {
ivbuf = crypto.randomBytes(16);
}
if (!(publicKey instanceof PublicKey)) {
publicKey = new PublicKey(publicKey);
Expand Down Expand Up @@ -81,18 +84,18 @@ function encrypt({ message, publicKey, privateKey, ivbuf, opts = {} }) {

/**
* Decrypt the payload
* @param {Object} params
* @param {object} params
* @param {Buffer} params.payload - Encrypted payload buffer.
* @param {PrivateKey} params.privateKey - Your private key is used to decrypt the payload.
* @param {PublicKey} params.publicKey - Sender's public key is used to verify the payload.
* *Only* include this if the encrypter specified the `noKey` option, otherwise the public key is included in the payload.
* @param {Object} params.opts (optional) Options object. Every field is optional.
* @param {object} params.opts (optional) Options object. Every field is optional.
* @param {boolean} params.opts.shortTag - Use 4-byte tag instead of 32-byte.
* This was decided during encryption and must be communicated by the sender.
* @returns {Buffer} Decrypted message buffer.
*/
function decrypt({ payload, privateKey, publicKey, opts = {} }) {
$.checkArgument(Buffer.isBuffer(payload), '`buffer` must be a Buffer');
$.checkArgument(Buffer.isBuffer(payload), 'payload must be a Buffer');
$.checkArgument(privateKey, 'privateKey is required');

if (!(privateKey instanceof PrivateKey)) {
Expand Down
12 changes: 6 additions & 6 deletions packages/bitcore-tss/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module.exports = {
// ECDSA
...require('./ecdsa/keygen'),
...require('./ecdsa/sign'),

// ECIES
...require('./ecies/ecies')
ECDSA: {
KeyGen: require('./ecdsa/keygen').KeyGen,
Sign: require('./ecdsa/sign').Sign,
},
utils: require('./ecdsa/utils'),
ECIES: require('./ecies/ecies')
}
3 changes: 0 additions & 3 deletions packages/bitcore-tss/test/ecdsa.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe('ECDSA', function() {
assert.strictEqual(typeof session, 'string');

const keygen = await KeyGen.restore({
seed: seeds[party],
authKey: authKeys[party],
session
});
Expand Down Expand Up @@ -166,7 +165,6 @@ describe('ECDSA', function() {
assert.strictEqual(typeof session, 'string');

const keygen = await KeyGen.restore({
seed: seeds[party],
authKey: authKeys[party],
session
});
Expand Down Expand Up @@ -209,7 +207,6 @@ describe('ECDSA', function() {
assert.strictEqual(typeof session, 'string');

const keygen = await KeyGen.restore({
seed: seeds[party],
authKey: authKeys[party],
session
});
Expand Down
25 changes: 20 additions & 5 deletions packages/bitcore-tss/test/ecies.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('ECIES', function() {
});

it('correctly encrypts a message', function() {
const ciphertext = alice.encrypt(message);
const ciphertext = alice.encrypt(message, { deterministicIv: true });
assert.strictEqual(Buffer.isBuffer(ciphertext), true);
assert.strictEqual(ciphertext.toString('hex'), encrypted)
});
Expand All @@ -51,29 +51,34 @@ describe('ECIES', function() {
});

it('correctly encrypts a message without key', function() {
const ciphertext = alice.encrypt(message, { noKey: true });
const ciphertext = alice.encrypt(message, { noKey: true, deterministicIv: true });
assert.strictEqual(Buffer.isBuffer(ciphertext), true);
assert.strictEqual(ciphertext.toString('hex'), encryptedNoKey)
});

it('correctly decrypts a message without key', function() {
const decrypted = bob.decrypt(encNoKeyBuf, { noKey: true });
const decrypted = bob.decrypt(encNoKeyBuf, { noKey: true, deterministicIv: true });
assert.strictEqual(Buffer.isBuffer(decrypted), true);
assert.strictEqual(decrypted.toString(), message);
});

it('correctly encrypts a message with short tag', function() {
const ciphertext = alice.encrypt(message, { shortTag: true });
const ciphertext = alice.encrypt(message, { shortTag: true, deterministicIv: true });
assert.strictEqual(Buffer.isBuffer(ciphertext), true);
assert.strictEqual(ciphertext.toString('hex'), encryptedShortTag)
});

it('correctly decrypts a message with short tag', function() {
const decrypted = bob.decrypt(encShortTagBuf, { shortTag: true });
const decrypted = bob.decrypt(encShortTagBuf, { shortTag: true, deterministicIv: true });
assert.strictEqual(Buffer.isBuffer(decrypted), true);
assert.strictEqual(decrypted.toString(), message);
});

it('encrypts a message with random IV', function() {
const ciphertext = alice.encrypt(message);
assert.strictEqual(Buffer.isBuffer(ciphertext), true);
assert.notEqual(ciphertext.toString('hex'), encrypted);
});

it('roundtrips', function() {
const secret = 'some secret message!!!';
Expand Down Expand Up @@ -104,6 +109,16 @@ describe('ECIES', function() {
assert.strictEqual(decrypted, secret);
});

it('roundtrips (deterministic iv)', function() {
const opts = { deterministicIv: true };
const secret = 'some secret message!!!';
const encrypted = alice.encrypt(secret, opts);
const decrypted = bob
.decrypt(encrypted, opts)
.toString();
assert.strictEqual(decrypted, secret);
});

it('roundtrips (no public key & short tag)', function() {
const opts = { noKey: true, shortTag: true };
const secret = 'some secret message!!!';
Expand Down
1 change: 1 addition & 0 deletions packages/bitcore-wallet-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"async": "0.9.2",
"bip38": "1.4.0",
"bitcore-mnemonic": "^10.9.0",
"bitcore-tss": "^10.9.0",
"crypto-wallet-core": "^10.9.0",
"json-stable-stringify": "1.0.1",
"preconditions": "2.2.3",
Expand Down
Loading