|
| 1 | +import assert from 'assert'; |
| 2 | +import crypto from 'crypto'; |
| 3 | +import { isLeft } from 'fp-ts/Either'; |
| 4 | +import * as t from 'io-ts'; |
| 5 | +import { generateEdDsaDKGKeyShares } from './util'; |
| 6 | + |
| 7 | +const otherIndices: ReadonlyArray<ReadonlyArray<number>> = [ |
| 8 | + [1, 2], |
| 9 | + [0, 2], |
| 10 | + [0, 1], |
| 11 | +]; |
| 12 | + |
| 13 | +const Uint8ArrayCodec = new t.Type<Uint8Array, Uint8Array, unknown>( |
| 14 | + 'Uint8Array', |
| 15 | + (u): u is Uint8Array => u instanceof Uint8Array, |
| 16 | + (u, c) => (u instanceof Uint8Array ? t.success(u) : t.failure(u, c)), |
| 17 | + t.identity |
| 18 | +); |
| 19 | + |
| 20 | +/** wasm-mps round1 VRF messages are a party-id → bytes map. */ |
| 21 | +const VrfDkgRound1Msg = t.record(t.string, Uint8ArrayCodec); |
| 22 | + |
| 23 | +function vrfRound1MsgForParty(msg: unknown, partyId: number): Uint8Array { |
| 24 | + const decoded = VrfDkgRound1Msg.decode(msg); |
| 25 | + if (isLeft(decoded)) { |
| 26 | + throw new Error('VRF DKG round1 message is not a party-id map of byte arrays'); |
| 27 | + } |
| 28 | + const bytes = decoded.right[String(partyId)]; |
| 29 | + if (bytes === undefined) { |
| 30 | + throw new Error(`VRF DKG round1 message missing party ${partyId}`); |
| 31 | + } |
| 32 | + return bytes; |
| 33 | +} |
| 34 | + |
| 35 | +describe('EdDSA MPS VRF DKG and hard derive (@bitgo/wasm-mps)', function () { |
| 36 | + it('completes a 2-of-3 VRF DKG and a 2-party hard derive', async function () { |
| 37 | + const mps = await import('@bitgo/wasm-mps'); |
| 38 | + const [user, backup, bitgo] = await generateEdDsaDKGKeyShares(); |
| 39 | + const rootShares = [user.getKeyShare(), backup.getKeyShare(), bitgo.getKeyShare()]; |
| 40 | + |
| 41 | + const vrfRound0 = [0, 1, 2].map((i) => mps.ed25519_vrf_dkg_round0_process(i, crypto.randomBytes(32))); |
| 42 | + const vrfRound1 = [0, 1, 2].map((i) => |
| 43 | + mps.ed25519_vrf_dkg_round1_process( |
| 44 | + otherIndices[i].map((j) => vrfRound0[j].msg), |
| 45 | + vrfRound0[i].state |
| 46 | + ) |
| 47 | + ); |
| 48 | + const vrfShares = [0, 1, 2].map((i) => |
| 49 | + mps.ed25519_vrf_dkg_round2_process( |
| 50 | + otherIndices[i].map((j) => vrfRound1MsgForParty(vrfRound1[j].msg, i)), |
| 51 | + vrfRound1[i].state |
| 52 | + ) |
| 53 | + ); |
| 54 | + for (const share of vrfShares) { |
| 55 | + assert.ok(share.share.length > 0); |
| 56 | + } |
| 57 | + |
| 58 | + const path = "m/0'"; |
| 59 | + const deriveRound0 = [0, 2].map((i) => |
| 60 | + mps.ed25519_hard_derive_round0_process(vrfShares[i].share, rootShares[i], path) |
| 61 | + ); |
| 62 | + const deriveRound1 = [0, 1].map((i) => |
| 63 | + mps.ed25519_hard_derive_round1_process(deriveRound0[1 - i].msg, deriveRound0[i].state) |
| 64 | + ); |
| 65 | + const derived = [0, 1].map((i) => |
| 66 | + mps.ed25519_hard_derive_round2_process(deriveRound1[1 - i].msg, deriveRound1[i].state) |
| 67 | + ); |
| 68 | + |
| 69 | + const backupDeriveRound0 = [0, 1].map((i) => |
| 70 | + mps.ed25519_hard_derive_round0_process(vrfShares[i].share, rootShares[i], path) |
| 71 | + ); |
| 72 | + const backupDeriveRound1 = [0, 1].map((i) => |
| 73 | + mps.ed25519_hard_derive_round1_process(backupDeriveRound0[1 - i].msg, backupDeriveRound0[i].state) |
| 74 | + ); |
| 75 | + const backupDerived = [0, 1].map((i) => |
| 76 | + mps.ed25519_hard_derive_round2_process(backupDeriveRound1[1 - i].msg, backupDeriveRound1[i].state) |
| 77 | + ); |
| 78 | + |
| 79 | + assert.deepStrictEqual(derived[0].pk, derived[1].pk); |
| 80 | + assert.deepStrictEqual(derived[0].chaincode, derived[1].chaincode); |
| 81 | + assert.notDeepStrictEqual(Buffer.from(derived[0].pk), user.getSharePublicKey()); |
| 82 | + assert.deepStrictEqual(backupDerived[0].pk, derived[0].pk); |
| 83 | + assert.deepStrictEqual(backupDerived[0].chaincode, derived[0].chaincode); |
| 84 | + }); |
| 85 | +}); |
0 commit comments