@@ -159,11 +159,26 @@ describe('TSS EdDSA MPCv2 Utils:', async function () {
159159 ( { ...userKeychain , reducedEncryptedPrv : '' } ) . should . deepEqual ( nockedUserKeychain ) ;
160160 ( { ...backupKeychain , reducedEncryptedPrv : '' } ) . should . deepEqual ( nockedBackupKeychain ) ;
161161 ( { ...bitgoKeychain , reducedEncryptedPrv : '' } ) . should . deepEqual ( nockedBitGoKeychain ) ;
162+
163+ // reducedEncryptedPrv must round-trip: decrypting with the passphrase should recover
164+ // the browser-safe btoa encoding of the original reduced private material.
165+ const encodeReduced = ( buf : Buffer ) => btoa ( String . fromCharCode . apply ( null , Array . from ( new Uint8Array ( buf ) ) ) ) ;
166+
167+ assert . ok ( userKeychain . reducedEncryptedPrv ) ;
168+ assert . equal (
169+ bitgo . decrypt ( { input : userKeychain . reducedEncryptedPrv , password : 'passphrase' } ) ,
170+ encodeReduced ( Buffer . from ( 'userReduced' ) )
171+ ) ;
172+ assert . ok ( backupKeychain . reducedEncryptedPrv ) ;
173+ assert . equal (
174+ bitgo . decrypt ( { input : backupKeychain . reducedEncryptedPrv , password : 'passphrase' } ) ,
175+ encodeReduced ( Buffer . from ( 'backupReduced' ) )
176+ ) ;
162177 } ) ;
163178
164179 it ( 'should reject when BitGo PGP signature on round 1 response is invalid' , async function ( ) {
165180 nock ( bgUrl )
166- . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPS -R1' )
181+ . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPCv2 -R1' )
167182 . once ( )
168183 . reply ( 200 , {
169184 sessionId : 'bad-session' ,
@@ -175,6 +190,111 @@ describe('TSS EdDSA MPCv2 Utils:', async function () {
175190
176191 await assert . rejects ( tssUtils . createKeychains ( { passphrase : 'test' , enterprise : enterpriseId } ) ) ;
177192 } ) ;
193+
194+ it ( 'should reject when BitGo PGP signature on round 2 response is invalid' , async function ( ) {
195+ const bitgoSession = new EddsaMPSDkg . DKG ( 3 , 2 , 2 ) ;
196+ const bitgoState : { msg2 ?: MPSTypes . DeserializedMessage } = { } ;
197+ await nockMPSKeyGenRound1 ( bitgoSession , bitgoState , 1 ) ;
198+
199+ nock ( bgUrl )
200+ . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPCv2-R2' )
201+ . once ( )
202+ . reply ( 200 , {
203+ sessionId : 'test-session-id' ,
204+ commonPublicKey : 'a' . repeat ( 64 ) ,
205+ bitgoMsg2 : {
206+ message : Buffer . from ( 'garbage' ) . toString ( 'base64' ) ,
207+ signature : '-----BEGIN PGP SIGNATURE-----\nFAKE\n-----END PGP SIGNATURE-----' ,
208+ } ,
209+ } ) ;
210+
211+ await assert . rejects ( tssUtils . createKeychains ( { passphrase : 'test' , enterprise : enterpriseId } ) ) ;
212+ } ) ;
213+
214+ it ( 'should reject when session IDs from round 1 and round 2 do not match' , async function ( ) {
215+ const bitgoSession = new EddsaMPSDkg . DKG ( 3 , 2 , 2 ) ;
216+ const bitgoState : { msg2 ?: MPSTypes . DeserializedMessage } = { } ;
217+ await nockMPSKeyGenRound1 ( bitgoSession , bitgoState , 1 ) ;
218+
219+ nock ( bgUrl )
220+ . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPCv2-R2' )
221+ . once ( )
222+ . reply ( 200 , {
223+ sessionId : 'different-session-id' ,
224+ commonPublicKey : 'a' . repeat ( 64 ) ,
225+ bitgoMsg2 : { message : '' , signature : '' } ,
226+ } ) ;
227+
228+ await assert . rejects (
229+ tssUtils . createKeychains ( { passphrase : 'test' , enterprise : enterpriseId } ) ,
230+ / R o u n d 1 a n d r o u n d 2 s e s s i o n I D s d o n o t m a t c h /
231+ ) ;
232+ } ) ;
233+
234+ it ( 'should reject when commonPublicKey from BitGo does not match the locally computed key' , async function ( ) {
235+ const bitgoSession = new EddsaMPSDkg . DKG ( 3 , 2 , 2 ) ;
236+ const bitgoState : { msg2 ?: MPSTypes . DeserializedMessage } = { } ;
237+ await nockMPSKeyGenRound1 ( bitgoSession , bitgoState , 1 ) ;
238+
239+ nock ( bgUrl )
240+ . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPCv2-R2' )
241+ . once ( )
242+ . reply ( 200 , async ( _uri , { payload } : { payload : EddsaMPCv2KeyGenRound2Request } ) => {
243+ const { userMsg2, backupMsg2 } = payload ;
244+ assert . ok ( bitgoState . msg2 , 'BitGo round-2 message missing — round-1 nock must run first' ) ;
245+
246+ const userDeserMsg2 : MPSTypes . DeserializedMessage = {
247+ from : 0 ,
248+ payload : new Uint8Array ( Buffer . from ( userMsg2 . message , 'base64' ) ) ,
249+ } ;
250+ const backupDeserMsg2 : MPSTypes . DeserializedMessage = {
251+ from : 1 ,
252+ payload : new Uint8Array ( Buffer . from ( backupMsg2 . message , 'base64' ) ) ,
253+ } ;
254+ bitgoSession . handleIncomingMessages ( [ userDeserMsg2 , backupDeserMsg2 , bitgoState . msg2 ] ) ;
255+
256+ return {
257+ sessionId : 'test-session-id' ,
258+ commonPublicKey : 'fakefakeee' . repeat ( 8 ) , // mutated — will not match user/backup computed key
259+ bitgoMsg2 : await MPSComms . detachSignMpsMessage ( Buffer . from ( bitgoState . msg2 . payload ) , bitgoPrvKeyObj ) ,
260+ } ;
261+ } ) ;
262+
263+ await assert . rejects (
264+ tssUtils . createKeychains ( { passphrase : 'test' , enterprise : enterpriseId } ) ,
265+ / d o e s n o t m a t c h B i t G o c o m m o n p u b l i c k e y /
266+ ) ;
267+ } ) ;
268+
269+ it ( 'should reject when BitGo GPG public key does not match known keys in prod/test envs' , async function ( ) {
270+ // Use a staging env so envRequiresBitgoPubGpgKeyConfig returns true
271+ const stagingBitgo = TestBitGo . decorate ( BitGo , { env : 'staging' } ) ;
272+ stagingBitgo . initializeTestVars ( ) ;
273+ const stagingBaseCoin = stagingBitgo . coin ( coinName ) ;
274+ const stagingBgUrl = common . Environments [ stagingBitgo . getEnv ( ) ] . uri ;
275+ const stagingWallet = new Wallet ( stagingBitgo , stagingBaseCoin , {
276+ id : walletId ,
277+ enterprise : enterpriseId ,
278+ coin : coinName ,
279+ coinSpecific : { } ,
280+ multisigType : 'tss' ,
281+ } ) ;
282+ const stagingTssUtils = new EDDSAUtils . EddsaMPCv2Utils ( stagingBitgo , stagingBaseCoin , stagingWallet ) ;
283+
284+ // Return a key that is NOT in the hardcoded BitGo MPC v2 key list
285+ nock ( stagingBgUrl ) . get ( `/api/v2/${ coinName } /tss/pubkey` ) . query ( { enterpriseId } ) . reply ( 200 , {
286+ name : 'irrelevant' ,
287+ publicKey : bitgoGpgKeyPair . publicKey ,
288+ mpcv2PublicKey : bitgoGpgKeyPair . publicKey ,
289+ enterpriseId,
290+ } ) ;
291+ nock ( stagingBgUrl ) . get ( '/api/v1/client/constants' ) . reply ( 200 , { ttl : 3600 , constants } ) ;
292+
293+ await assert . rejects (
294+ stagingTssUtils . createKeychains ( { passphrase : 'test' , enterprise : enterpriseId } ) ,
295+ / I n v a l i d B i t G o G P G p u b l i c k e y /
296+ ) ;
297+ } ) ;
178298 } ) ;
179299
180300 // ---------------------------------------------------------------------------
@@ -202,7 +322,7 @@ describe('TSS EdDSA MPCv2 Utils:', async function () {
202322 times = 1
203323 ) {
204324 return nock ( bgUrl )
205- . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPS -R1' )
325+ . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPCv2 -R1' )
206326 . times ( times )
207327 . reply ( 200 , async ( _uri , { payload } : { payload : EddsaMPCv2KeyGenRound1Request } ) => {
208328 const { userGpgPublicKey, backupGpgPublicKey, userMsg1, backupMsg1 } = payload ;
@@ -211,15 +331,9 @@ describe('TSS EdDSA MPCv2 Utils:', async function () {
211331 const userPubKeyObj = await openpgp . readKey ( { armoredKey : userGpgPublicKey } ) ;
212332 const backupPubKeyObj = await openpgp . readKey ( { armoredKey : backupGpgPublicKey } ) ;
213333
214- const userPk = Buffer . from (
215- ( ( await userPubKeyObj . getEncryptionKey ( ) ) . keyPacket . publicParams as { Q : Uint8Array } ) . Q
216- ) . subarray ( 1 ) ;
217- const backupPk = Buffer . from (
218- ( ( await backupPubKeyObj . getEncryptionKey ( ) ) . keyPacket . publicParams as { Q : Uint8Array } ) . Q
219- ) . subarray ( 1 ) ;
220- const bitgoSk = Buffer . from (
221- ( ( await bitgoPrvKeyObj . getDecryptionKeys ( ) ) [ 0 ] . keyPacket . privateParams as { d : Uint8Array } ) . d
222- ) . reverse ( ) ;
334+ const userPk = await MPSComms . extractEd25519PublicKey ( userPubKeyObj ) ;
335+ const backupPk = await MPSComms . extractEd25519PublicKey ( backupPubKeyObj ) ;
336+ const [ , bitgoSk ] = await MPSComms . extractEd25519KeyPair ( bitgoPrvKeyObj ) ;
223337
224338 bitgoSession . initDkg ( bitgoSk , [ userPk , backupPk ] ) ;
225339 const bitgoRawMsg1 = bitgoSession . getFirstMessage ( ) ;
@@ -252,7 +366,7 @@ describe('TSS EdDSA MPCv2 Utils:', async function () {
252366 times = 1
253367 ) {
254368 return nock ( bgUrl )
255- . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPS -R2' )
369+ . post ( '/api/v2/mpc/generatekey' , ( body ) => body . round === 'MPCv2 -R2' )
256370 . times ( times )
257371 . reply ( 200 , async ( _uri , { payload } : { payload : EddsaMPCv2KeyGenRound2Request } ) => {
258372 const { sessionId, userMsg2, backupMsg2 } = payload ;
0 commit comments