|
| 1 | +import * as assert from 'assert'; |
| 2 | +import { randomBytes } from 'crypto'; |
| 3 | +import * as sjcl from '@bitgo/sjcl'; |
| 4 | +import { isEddsaMpcV1SigningMaterial } from '../../../../../../src/bitgo/utils/tss/eddsa/eddsaMPCv2'; |
| 5 | + |
| 6 | +const PASSPHRASE = 'test-passphrase'; |
| 7 | + |
| 8 | +/** Minimal valid MPCv1 signing material (backupYShare variant). */ |
| 9 | +const MPCv1_MATERIAL_BACKUP = { |
| 10 | + uShare: { i: 1, t: 2, n: 3, y: 'aabbcc', seed: 'deadbeef01234567', chaincode: '00' }, |
| 11 | + bitgoYShare: { i: 3, j: 1, y: 'aabbcc', u: 'bitgo-u-value', chaincode: '00' }, |
| 12 | + backupYShare: { i: 2, j: 1, y: 'aabbcc', u: 'backup-u-value', chaincode: '00' }, |
| 13 | +}; |
| 14 | + |
| 15 | +/** Minimal valid MPCv1 signing material (userYShare variant). */ |
| 16 | +const MPCv1_MATERIAL_USER = { |
| 17 | + uShare: { i: 2, t: 2, n: 3, y: 'aabbcc', seed: 'deadbeef01234567', chaincode: '00' }, |
| 18 | + bitgoYShare: { i: 3, j: 2, y: 'aabbcc', u: 'bitgo-u-value', chaincode: '00' }, |
| 19 | + userYShare: { i: 1, j: 2, y: 'aabbcc', u: 'user-u-value', chaincode: '00' }, |
| 20 | +}; |
| 21 | + |
| 22 | +/** CBOR-like binary content that would come from an MPCv2 keycard. */ |
| 23 | +const MPCv2_CBOR_BYTES = Buffer.from([0xd9, 0x01, 0x04, 0xa3, 0x61, 0x78, 0x18, 0x00]).toString('base64'); |
| 24 | + |
| 25 | +function bytesToWord(bytes: Buffer): number { |
| 26 | + return bytes.reduce((num, byte) => num * 0x100 + byte, 0); |
| 27 | +} |
| 28 | + |
| 29 | +function encryptSjcl(plaintext: string, password: string): string { |
| 30 | + const salt = randomBytes(8); |
| 31 | + const iv = randomBytes(16); |
| 32 | + return sjcl.encrypt(password, plaintext, { |
| 33 | + salt: [bytesToWord(salt.subarray(0, 4)), bytesToWord(salt.subarray(4))], |
| 34 | + iv: [ |
| 35 | + bytesToWord(iv.subarray(0, 4)), |
| 36 | + bytesToWord(iv.subarray(4, 8)), |
| 37 | + bytesToWord(iv.subarray(8, 12)), |
| 38 | + bytesToWord(iv.subarray(12, 16)), |
| 39 | + ], |
| 40 | + }); |
| 41 | +} |
| 42 | + |
| 43 | +describe('isEddsaMpcV1SigningMaterial', () => { |
| 44 | + it('returns true for MPCv1 SJCL-encrypted keycard with backupYShare + correct passphrase', async () => { |
| 45 | + const encrypted = encryptSjcl(JSON.stringify(MPCv1_MATERIAL_BACKUP), PASSPHRASE); |
| 46 | + assert.strictEqual(await isEddsaMpcV1SigningMaterial(encrypted, PASSPHRASE), true); |
| 47 | + }); |
| 48 | + |
| 49 | + it('returns true for MPCv1 SJCL-encrypted keycard with userYShare + correct passphrase', async () => { |
| 50 | + const encrypted = encryptSjcl(JSON.stringify(MPCv1_MATERIAL_USER), PASSPHRASE); |
| 51 | + assert.strictEqual(await isEddsaMpcV1SigningMaterial(encrypted, PASSPHRASE), true); |
| 52 | + }); |
| 53 | + |
| 54 | + it('returns false for MPCv2 CBOR content wrapped in SJCL envelope + correct passphrase', async () => { |
| 55 | + // CBOR binary content fails JSON.parse → structural checks never reached → false |
| 56 | + const encrypted = encryptSjcl(MPCv2_CBOR_BYTES, PASSPHRASE); |
| 57 | + assert.strictEqual(await isEddsaMpcV1SigningMaterial(encrypted, PASSPHRASE), false); |
| 58 | + }); |
| 59 | + |
| 60 | + it('returns false for MPCv2 Argon2id envelope (v2) + correct passphrase (forward-compat)', async () => { |
| 61 | + // A v2 envelope is not a valid SJCL ciphertext — sjcl.decrypt throws → catch → false |
| 62 | + const fakeV2Envelope = JSON.stringify({ v: 2, m: 65536, t: 3, p: 4, salt: 'AAAA', iv: 'AAAA', ct: 'AAAA' }); |
| 63 | + assert.strictEqual(await isEddsaMpcV1SigningMaterial(fakeV2Envelope, PASSPHRASE), false); |
| 64 | + }); |
| 65 | + |
| 66 | + it('returns false for wrong passphrase — does not throw', async () => { |
| 67 | + const encrypted = encryptSjcl(JSON.stringify(MPCv1_MATERIAL_BACKUP), PASSPHRASE); |
| 68 | + assert.strictEqual(await isEddsaMpcV1SigningMaterial(encrypted, 'wrong-passphrase'), false); |
| 69 | + }); |
| 70 | + |
| 71 | + it('returns false for undefined passphrase — does not throw', async () => { |
| 72 | + const encrypted = encryptSjcl(JSON.stringify(MPCv1_MATERIAL_BACKUP), PASSPHRASE); |
| 73 | + assert.strictEqual(await isEddsaMpcV1SigningMaterial(encrypted, undefined), false); |
| 74 | + }); |
| 75 | +}); |
0 commit comments