Skip to content
Open
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
2 changes: 1 addition & 1 deletion modules/sdk-lib-mpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
]
},
"dependencies": {
"@bitgo/wasm-mps": "1.14.0",
"@bitgo/wasm-mps": "1.15.0",
"@noble/curves": "1.8.1",
"@silencelaboratories/dkls-wasm-ll-node": "1.2.0-pre.4",
"@silencelaboratories/dkls-wasm-ll-vrf-node": "1.0.0-pre.9",
Expand Down
13 changes: 4 additions & 9 deletions modules/sdk-lib-mpc/src/tss/redpallas-mps/dkg.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MsgDerivationInit, MsgState } from '@bitgo/wasm-mps';
import type { MsgState } from '@bitgo/wasm-mps';
import { encode } from 'cbor-x';
import crypto from 'crypto';
import { DeserializedMessage, DeserializedMessages, RedPallasDkgState, RedPallasReducedKeyShare } from './types';
Expand Down Expand Up @@ -149,10 +149,8 @@ export class RedPallasDKG {
* matching the ordering expected by @bitgo/wasm-mps.
*
* @param messagesForIthRound - All n messages for this round (including own).
* @param derivationSeed - Required only when advancing WaitMsg2 -> Complete (round2): a
* 32-byte seed consumed by the subsequent, platform-side-only derivation process.
*/
handleIncomingMessages(messagesForIthRound: DeserializedMessages, derivationSeed?: Buffer): DeserializedMessages {
handleIncomingMessages(messagesForIthRound: DeserializedMessages): DeserializedMessages {
if (this.dkgState === RedPallasDkgState.Complete) {
throw Error('DKG session already completed');
}
Expand Down Expand Up @@ -190,12 +188,9 @@ export class RedPallasDKG {
}

if (this.dkgState === RedPallasDkgState.WaitMsg2) {
if (!derivationSeed || derivationSeed.length !== 32) {
throw Error('Missing or invalid derivationSeed: must be 32 bytes (required for round2)');
}
let result: MsgDerivationInit;
let result;
try {
result = wasm.redpallas_dkg_round2_process(otherMsgs, this.dkgStateBytes!, derivationSeed);
result = wasm.redpallas_dkg_round2_process(otherMsgs, this.dkgStateBytes!);
} catch (err) {
throw new Error(`Error while creating messages from party ${this.partyIdx}, round ${this.dkgState}: ${err}`);
}
Expand Down
11 changes: 3 additions & 8 deletions modules/sdk-lib-mpc/src/tss/redpallas-mps/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,8 @@ function validateSeed(seed?: RedPallasDKGPartySeed): RedPallasDKGPartySeed {
/**
* Runs a full 3-party (2-of-3) RedPallas DKG in-process. See `RedPallasDKGPartySeed`.
* Mirrors `generateEdDsaDKGKeyShares` in `../eddsa-mps/util.ts`.
*
* @param derivationSeed - 32-byte seed consumed by round2 for the (platform-side-only)
* subsequent derivation process. Must be the same value across all three parties.
*/
export async function generateRedPallasDKGKeyShares(
derivationSeed: Buffer,
seedUser?: RedPallasDKGPartySeed,
seedBackup?: RedPallasDKGPartySeed,
seedBitgo?: RedPallasDKGPartySeed
Expand Down Expand Up @@ -67,10 +63,9 @@ export async function generateRedPallasDKGKeyShares(
...bitgo.handleIncomingMessages(r1Messages),
];

user.handleIncomingMessages(r2Messages, derivationSeed);
backup.handleIncomingMessages(r2Messages, derivationSeed);
bitgo.handleIncomingMessages(r2Messages, derivationSeed);

user.handleIncomingMessages(r2Messages);
backup.handleIncomingMessages(r2Messages);
bitgo.handleIncomingMessages(r2Messages);
return [user, backup, bitgo];
}

Expand Down
59 changes: 11 additions & 48 deletions modules/sdk-lib-mpc/test/unit/tss/redpallas/dkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,21 @@ function makeKeypair(seed?: Buffer) {
return { privKey, pubKey };
}

function makeDerivationSeed(): Buffer {
return crypto.randomBytes(32);
}

describe('RedPallas MPS DKG', function () {
let user: RedPallasMPSDkg.RedPallasDKG;
let backup: RedPallasMPSDkg.RedPallasDKG;
let bitgo: RedPallasMPSDkg.RedPallasDKG;
let userKP: { privKey: Buffer; pubKey: Buffer };
let backupKP: { privKey: Buffer; pubKey: Buffer };
let bitgoKP: { privKey: Buffer; pubKey: Buffer };
let derivationSeed: Buffer;

beforeEach(function () {
user = new RedPallasMPSDkg.RedPallasDKG(3, 2, 0);
backup = new RedPallasMPSDkg.RedPallasDKG(3, 2, 1);
bitgo = new RedPallasMPSDkg.RedPallasDKG(3, 2, 2);

userKP = makeKeypair();
backupKP = makeKeypair();
bitgoKP = makeKeypair();
derivationSeed = makeDerivationSeed();
});

describe('DKG Initialization', function () {
Expand Down Expand Up @@ -131,9 +124,9 @@ describe('RedPallas MPS DKG', function () {
});

const r3Messages = [
...user.handleIncomingMessages(r2Messages, derivationSeed),
...backup.handleIncomingMessages(r2Messages, derivationSeed),
...bitgo.handleIncomingMessages(r2Messages, derivationSeed),
...user.handleIncomingMessages(r2Messages),
...backup.handleIncomingMessages(r2Messages),
...bitgo.handleIncomingMessages(r2Messages),
];

assert.strictEqual(user.getState(), RedPallasDkgState.Complete);
Expand All @@ -154,38 +147,16 @@ describe('RedPallas MPS DKG', function () {
assert(Buffer.isBuffer(bitgoKeyShare) && bitgoKeyShare.length > 0, 'BitGo key share should be non-empty Buffer');
});

it('should require a 32-byte derivationSeed for round 2', async function () {
const r1Messages = [user.getFirstMessage(), backup.getFirstMessage(), bitgo.getFirstMessage()];
const r2Messages = [
...user.handleIncomingMessages(r1Messages),
...backup.handleIncomingMessages(r1Messages),
...bitgo.handleIncomingMessages(r1Messages),
];

assert.strictEqual(user.getState(), RedPallasDkgState.WaitMsg2);

assert.throws(() => {
user.handleIncomingMessages(r2Messages);
}, /Missing or invalid derivationSeed/);

assert.throws(() => {
user.handleIncomingMessages(r2Messages, Buffer.alloc(31));
}, /Missing or invalid derivationSeed/);

// Failed round2 must leave the session in WaitMsg2
assert.strictEqual(user.getState(), RedPallasDkgState.WaitMsg2);
});

it('should generate consistent public keys across all parties', async function () {
const r1Messages = [user.getFirstMessage(), backup.getFirstMessage(), bitgo.getFirstMessage()];
const r2Messages = [
...user.handleIncomingMessages(r1Messages),
...backup.handleIncomingMessages(r1Messages),
...bitgo.handleIncomingMessages(r1Messages),
];
user.handleIncomingMessages(r2Messages, derivationSeed);
backup.handleIncomingMessages(r2Messages, derivationSeed);
bitgo.handleIncomingMessages(r2Messages, derivationSeed);
user.handleIncomingMessages(r2Messages);
backup.handleIncomingMessages(r2Messages);
bitgo.handleIncomingMessages(r2Messages);

const userPk = user.getSharePublicKey().toString('hex');
const backupPk = backup.getSharePublicKey().toString('hex');
Expand All @@ -200,27 +171,21 @@ describe('RedPallas MPS DKG', function () {
const seedUser = Buffer.from('a304733c16cc821fe171d5c7dbd7276fd90deae808b7553d17a1e55e4a76b270', 'hex');
const seedBackup = Buffer.from('9d91c2e6353202cf61f8f275158b3468e9a00f7872fc2fd310b72cd026e2e2f9', 'hex');
const seedBitgo = Buffer.from('33c749b635cdba7f9fbf51ad0387431cde47e20d8dc13acd1f51a9a0ad06ebfe', 'hex');
const fixedDerivationSeed = Buffer.from('c526955e37be0a0c8b77a831eb615948772b38df9f04d8c5a2e0e1f1d0c9b8a7', 'hex');

it('should create key shares with deterministic seeds', async function () {
const userParty = { encKey: seedUser, dkgSeed: seedUser };
const backupParty = { encKey: seedBackup, dkgSeed: seedBackup };
const bitgoParty = { encKey: seedBitgo, dkgSeed: seedBitgo };

const [user1, backup1, bitgo1] = await generateRedPallasDKGKeyShares(
fixedDerivationSeed,
userParty,
backupParty,
bitgoParty
);
const [user1, backup1, bitgo1] = await generateRedPallasDKGKeyShares(userParty, backupParty, bitgoParty);

const pk0 = user1.getSharePublicKey().toString('hex');
const pk1 = backup1.getSharePublicKey().toString('hex');
const pk2 = bitgo1.getSharePublicKey().toString('hex');
assert.strictEqual(pk0, pk1, 'User and backup should have same public key');
assert.strictEqual(pk1, pk2, 'Backup and BitGo should have same public key');

const [user2] = await generateRedPallasDKGKeyShares(fixedDerivationSeed, userParty, backupParty, bitgoParty);
const [user2] = await generateRedPallasDKGKeyShares(userParty, backupParty, bitgoParty);
assert.strictEqual(
user1.getSharePublicKey().toString('hex'),
user2.getSharePublicKey().toString('hex'),
Expand All @@ -237,13 +202,11 @@ describe('RedPallas MPS DKG', function () {
const seedBBitgo = Buffer.from('44d85ab746decb8f0f0c62be0498542ddf58f31d9ed24bd1f62b1b1be17fce0f', 'hex');

const [user1] = await generateRedPallasDKGKeyShares(
fixedDerivationSeed,
{ encKey: seedAUser, dkgSeed: seedAUser },
{ encKey: seedABackup, dkgSeed: seedABackup },
{ encKey: seedABitgo, dkgSeed: seedABitgo }
);
const [user2] = await generateRedPallasDKGKeyShares(
fixedDerivationSeed,
{ encKey: seedBUser, dkgSeed: seedBUser },
{ encKey: seedBBackup, dkgSeed: seedBBackup },
{ encKey: seedBBitgo, dkgSeed: seedBBitgo }
Expand All @@ -257,7 +220,7 @@ describe('RedPallas MPS DKG', function () {
});

it('should create key shares without party seeds (random)', async function () {
const [userDkg, backupDkg, bitgoDkg] = await generateRedPallasDKGKeyShares(derivationSeed);
const [userDkg, backupDkg, bitgoDkg] = await generateRedPallasDKGKeyShares();

const userPk = userDkg.getSharePublicKey().toString('hex');
const backupPk = backupDkg.getSharePublicKey().toString('hex');
Expand All @@ -268,7 +231,7 @@ describe('RedPallas MPS DKG', function () {
});

it('should generate valid reduced key shares', async function () {
const [userDkg, backupDkg, bitgoDkg] = await generateRedPallasDKGKeyShares(derivationSeed);
const [userDkg, backupDkg, bitgoDkg] = await generateRedPallasDKGKeyShares();

const userReduced = userDkg.getReducedKeyShare();
const backupReduced = backupDkg.getReducedKeyShare();
Expand Down Expand Up @@ -361,7 +324,7 @@ describe('RedPallas MPS DKG', function () {
});

it('should throw error when trying to export session after completion', async function () {
const [userDkg, backupDkg, bitgoDkg] = await generateRedPallasDKGKeyShares(derivationSeed);
const [userDkg, backupDkg, bitgoDkg] = await generateRedPallasDKGKeyShares();

assert.throws(() => {
userDkg.getSession();
Expand Down
26 changes: 5 additions & 21 deletions modules/sdk-lib-mpc/test/unit/tss/redpallas/redpallas-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@ describe('RedPallas Utility Functions', function () {
const dkgSeedUser = Buffer.from('b415844d27dd9320f282d6d8ecd8387f0e9fbf9198664e28a2f66e6f5b87c381', 'hex');
const dkgSeedBackup = Buffer.from('ae02d3f7464313d0f72f9f3862694579fa11f8983fc3fe42183cd137e3f3f30a', 'hex');
const dkgSeedBitgo = Buffer.from('44d85ab746decb8f0f0c62be0498542ddf58f31d9ed24bd1f62b1b1be17fce0f', 'hex');
const derivationSeed = Buffer.from('c526955e37be0a0c8b77a831eb615948772b38df9f04d8c5a2e0e1f1d0c9b8a7', 'hex');

it('should be deterministic with split encKey and dkgSeed', async function () {
const split = {
user: { encKey: seedUser, dkgSeed: dkgSeedUser },
backup: { encKey: seedBackup, dkgSeed: dkgSeedBackup },
bitgo: { encKey: seedBitgo, dkgSeed: dkgSeedBitgo },
};
const [user, backup, bitgo] = await generateRedPallasDKGKeyShares(
derivationSeed,
split.user,
split.backup,
split.bitgo
);
const [repeatUser] = await generateRedPallasDKGKeyShares(derivationSeed, split.user, split.backup, split.bitgo);
const [user, backup, bitgo] = await generateRedPallasDKGKeyShares(split.user, split.backup, split.bitgo);
const [repeatUser] = await generateRedPallasDKGKeyShares(split.user, split.backup, split.bitgo);

const userPublicKey = user.getSharePublicKey().toString('hex');
assert.strictEqual(userPublicKey, backup.getSharePublicKey().toString('hex'));
Expand All @@ -35,27 +29,17 @@ describe('RedPallas Utility Functions', function () {
const okBackup = { encKey: seedBackup, dkgSeed: dkgSeedBackup };
const okBitgo = { encKey: seedBitgo, dkgSeed: dkgSeedBitgo };
await assert.rejects(
generateRedPallasDKGKeyShares(
derivationSeed,
{ encKey: Buffer.alloc(31), dkgSeed: dkgSeedUser },
okBackup,
okBitgo
),
generateRedPallasDKGKeyShares({ encKey: Buffer.alloc(31), dkgSeed: dkgSeedUser }, okBackup, okBitgo),
/encKey must be at least 32 bytes/
);
await assert.rejects(
generateRedPallasDKGKeyShares(
derivationSeed,
{ encKey: seedUser, dkgSeed: Buffer.alloc(31) },
okBackup,
okBitgo
),
generateRedPallasDKGKeyShares({ encKey: seedUser, dkgSeed: Buffer.alloc(31) }, okBackup, okBitgo),
/dkgSeed must be at least 32 bytes/
);
});

it('should produce distinct key shares per party with a shared public key', async function () {
const [user, backup, bitgo] = await generateRedPallasDKGKeyShares(derivationSeed);
const [user, backup, bitgo] = await generateRedPallasDKGKeyShares();

const userShare = user.getKeyShare();
const backupShare = backup.getKeyShare();
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1037,10 +1037,10 @@
resolved "https://registry.npmjs.org/@bitgo/wasm-dot/-/wasm-dot-1.7.0.tgz"
integrity sha512-KoXavJvyDHlEN+sWcigbgxYJtdFaU7gS0EkYQbNH4npVjNlzo6rL6gwjyWbyOy7oEs65DhpJ9vY5kRbE/bKiTQ==

"@bitgo/wasm-mps@1.14.0":
version "1.14.0"
resolved "https://registry.npmjs.org/@bitgo/wasm-mps/-/wasm-mps-1.14.0.tgz#5a5dece54486006852735651df7a63f7f61fc050"
integrity sha512-PDVU5Yg8HWy6fHH0iHstUXKLNggyXXGMCSAFCfaVSi36bfFuRKa5fd2ljMGKHl6RaAhQOvv4h2njVomYtjADEQ==
"@bitgo/wasm-mps@1.15.0":
version "1.15.0"
resolved "https://registry.npmjs.org/@bitgo/wasm-mps/-/wasm-mps-1.15.0.tgz#734bcb78a18b1b2d76125f86f31cf7d30caeca88"
integrity sha512-/764uy+CBV6XOll2ssyv0dQ1QWk3Hza/SGrk85drjCx1f7I6b5TDIOVTdx3MA6sEszKT+6tZlSQMFTz4KYX3sQ==

"@bitgo/wasm-solana@^2.6.0":
version "2.6.0"
Expand Down
Loading