@@ -2467,3 +2467,149 @@ describe('EddsaMPCv2Utils.getMpcV2RetrofitDataFromMpcV1Keys', () => {
24672467 ) ;
24682468 } ) ;
24692469} ) ;
2470+
2471+ describe ( 'EddsaMPCv2Utils.getUserAndBackupSession' , ( ) => {
2472+ let utils : EddsaMPCv2Utils ;
2473+ let userSigningMaterial : Record < string , unknown > ;
2474+ let backupSigningMaterial : Record < string , unknown > ;
2475+
2476+ before ( async ( ) => {
2477+ const MPC = await getInitializedMpcInstance ( ) ;
2478+ const user = MPC . keyShare ( 1 , 2 , 3 ) ;
2479+ const backup = MPC . keyShare ( 2 , 2 , 3 ) ;
2480+ const bitgo = MPC . keyShare ( 3 , 2 , 3 ) ;
2481+ userSigningMaterial = {
2482+ uShare : user . uShare ,
2483+ bitgoYShare : bitgo . yShares [ 1 ] ,
2484+ backupYShare : backup . yShares [ 1 ] ,
2485+ } ;
2486+ backupSigningMaterial = {
2487+ uShare : backup . uShare ,
2488+ bitgoYShare : bitgo . yShares [ 2 ] ,
2489+ userYShare : user . yShares [ 2 ] ,
2490+ } ;
2491+ } ) ;
2492+
2493+ beforeEach ( ( ) => {
2494+ const mockBitGo = { } as unknown as BitGoBase ;
2495+ const mockCoin = { } as unknown as IBaseCoin ;
2496+ utils = new EddsaMPCv2Utils ( mockBitGo , mockCoin ) ;
2497+ } ) ;
2498+
2499+ afterEach ( ( ) => {
2500+ sinon . restore ( ) ;
2501+ } ) ;
2502+
2503+ it ( 'returns plain DKG sessions when retrofit is undefined' , async ( ) => {
2504+ const { userDkg, backupDkg } = await ( utils as any ) . getUserAndBackupSession ( undefined ) ;
2505+ assert . ok ( userDkg , 'user DKG should be created' ) ;
2506+ assert . ok ( backupDkg , 'backup DKG should be created' ) ;
2507+ } ) ;
2508+
2509+ it ( 'returns retrofit-seeded DKG sessions when retrofit payload is supplied' , async ( ) => {
2510+ const retrofit = {
2511+ decryptedUserKey : JSON . stringify ( userSigningMaterial ) ,
2512+ decryptedBackupKey : JSON . stringify ( backupSigningMaterial ) ,
2513+ walletId : 'wallet-123' ,
2514+ } ;
2515+ const { userDkg, backupDkg } = await ( utils as any ) . getUserAndBackupSession ( retrofit ) ;
2516+ assert . ok ( userDkg , 'user DKG should be created with retrofit data' ) ;
2517+ assert . ok ( backupDkg , 'backup DKG should be created with retrofit data' ) ;
2518+ } ) ;
2519+ } ) ;
2520+
2521+ describe ( 'EddsaMPCv2Utils.createKeychains with retrofit wiring' , ( ) => {
2522+ let utils : EddsaMPCv2Utils ;
2523+ let userSigningMaterial : Record < string , unknown > ;
2524+ let backupSigningMaterial : Record < string , unknown > ;
2525+ let bitgoGpgPublicKeyArmored : string ;
2526+ const enterprise = 'enterprise-id' ;
2527+ const sessionId = 'session-001' ;
2528+ const walletId = 'wallet-retrofit-123' ;
2529+
2530+ before ( async ( ) => {
2531+ const MPC = await getInitializedMpcInstance ( ) ;
2532+ const user = MPC . keyShare ( 1 , 2 , 3 ) ;
2533+ const backup = MPC . keyShare ( 2 , 2 , 3 ) ;
2534+ const bitgo = MPC . keyShare ( 3 , 2 , 3 ) ;
2535+ userSigningMaterial = {
2536+ uShare : user . uShare ,
2537+ bitgoYShare : bitgo . yShares [ 1 ] ,
2538+ backupYShare : backup . yShares [ 1 ] ,
2539+ } ;
2540+ backupSigningMaterial = {
2541+ uShare : backup . uShare ,
2542+ bitgoYShare : bitgo . yShares [ 2 ] ,
2543+ userYShare : user . yShares [ 2 ] ,
2544+ } ;
2545+ // Generate a real Ed25519 GPG key to stand in for the BitGo GPG key
2546+ const bitgoGpgKeyPair = await generateGPGKeyPair ( 'ed25519' ) ;
2547+ bitgoGpgPublicKeyArmored = bitgoGpgKeyPair . publicKey ;
2548+ } ) ;
2549+
2550+ beforeEach ( ( ) => {
2551+ const mockBitGo = {
2552+ getEnv : sinon . stub ( ) . returns ( 'dev' ) ,
2553+ encrypt : sinon . stub ( ) . resolves ( 'encrypted' ) ,
2554+ } as any ;
2555+ const mockKeychains = {
2556+ add : sinon
2557+ . stub ( )
2558+ . callsFake ( ( params : any ) =>
2559+ Promise . resolve ( { id : `${ params . source } -key-id` , commonKeychain : 'a' . repeat ( 128 ) , isMPCv2 : true } )
2560+ ) ,
2561+ } ;
2562+ const mockCoin = {
2563+ keychains : sinon . stub ( ) . returns ( mockKeychains ) ,
2564+ } as any ;
2565+
2566+ utils = new EddsaMPCv2Utils ( mockBitGo , mockCoin ) ;
2567+ sinon . stub ( utils , 'getBitgoGpgPubkeyBasedOnFeatureFlags' as any ) . resolves ( { eddsaMpcv2PublicKey : null } ) ;
2568+ // Use a real armored GPG public key so pgp.readKey() succeeds inside createKeychains
2569+ ( utils as any ) . bitgoEddsaMpcv2PublicGpgKey = { armor : ( ) => bitgoGpgPublicKeyArmored } ;
2570+ sinon . stub ( utils as any , 'addBitgoKeychain' ) . resolves ( { id : 'bitgo-key-id' , commonKeychain : 'a' . repeat ( 128 ) } ) ;
2571+ } ) ;
2572+
2573+ afterEach ( ( ) => {
2574+ sinon . restore ( ) ;
2575+ } ) ;
2576+
2577+ it ( 'spreads walletId into round-1 payload when retrofit is provided' , async ( ) => {
2578+ const capturedPayloads : any [ ] = [ ] ;
2579+ sinon . stub ( utils , 'sendKeyGenerationRound1' ) . callsFake ( async ( _enterprise : string , payload : any ) => {
2580+ capturedPayloads . push ( payload ) ;
2581+ // Return a bad bitgoMsg1 to short-circuit the ceremony after R1 capture
2582+ return { sessionId : sessionId as any , bitgoMsg1 : { message : '' , signature : '' } as any } ;
2583+ } ) ;
2584+
2585+ const retrofit = {
2586+ decryptedUserKey : JSON . stringify ( userSigningMaterial ) ,
2587+ decryptedBackupKey : JSON . stringify ( backupSigningMaterial ) ,
2588+ walletId,
2589+ } ;
2590+
2591+ await assert . rejects (
2592+ ( ) => utils . createKeychains ( { passphrase : 'test' , enterprise, retrofit } ) ,
2593+ ( ) => true
2594+ ) ;
2595+
2596+ assert . strictEqual ( capturedPayloads . length , 1 , 'sendKeyGenerationRound1 should be called once' ) ;
2597+ assert . strictEqual ( capturedPayloads [ 0 ] . walletId , walletId , 'walletId must be present in round-1 payload' ) ;
2598+ } ) ;
2599+
2600+ it ( 'omits walletId from round-1 payload when retrofit is absent' , async ( ) => {
2601+ const capturedPayloads : any [ ] = [ ] ;
2602+ sinon . stub ( utils , 'sendKeyGenerationRound1' ) . callsFake ( async ( _enterprise : string , payload : any ) => {
2603+ capturedPayloads . push ( payload ) ;
2604+ return { sessionId : sessionId as any , bitgoMsg1 : { message : '' , signature : '' } as any } ;
2605+ } ) ;
2606+
2607+ await assert . rejects (
2608+ ( ) => utils . createKeychains ( { passphrase : 'test' , enterprise } ) ,
2609+ ( ) => true
2610+ ) ;
2611+
2612+ assert . strictEqual ( capturedPayloads . length , 1 , 'sendKeyGenerationRound1 should be called once' ) ;
2613+ assert . strictEqual ( capturedPayloads [ 0 ] . walletId , undefined , 'walletId must be absent when no retrofit' ) ;
2614+ } ) ;
2615+ } ) ;
0 commit comments