@@ -2504,6 +2504,218 @@ describe('V2 Wallets:', function () {
25042504 } ) ;
25052505 } ) ;
25062506
2507+ it ( 'should include webauthnInfo in request when provided (ECDH branch)' , async ( ) => {
2508+ const fromUserPrv = Math . random ( ) ;
2509+ const walletPassphrase = 'bitgo1234' ;
2510+ const webauthnPassphrase = 'prf-derived-secret' ;
2511+ const shareId = '66a229dbdccdcfb95b44fc2745a60bd4' ;
2512+ const keychainTest : OptionalKeychainEncryptedKey = {
2513+ encryptedPrv : bitgo . encrypt ( { input : fromUserPrv . toString ( ) , password : walletPassphrase } ) ,
2514+ } ;
2515+ const userPrv = decryptKeychainPrivateKey ( bitgo , keychainTest , walletPassphrase ) ;
2516+ if ( ! userPrv ) {
2517+ throw new Error ( 'Unable to decrypt user keychain' ) ;
2518+ }
2519+
2520+ const toKeychain = utxoLib . bip32 . fromSeed ( Buffer . from ( 'deadbeef02deadbeef02deadbeef02deadbeef02' , 'hex' ) ) ;
2521+ const path = 'm/999999/1/1' ;
2522+ const pubkey = toKeychain . derivePath ( path ) . publicKey . toString ( 'hex' ) ;
2523+
2524+ const eckey = makeRandomKey ( ) ;
2525+ const secret = getSharedSecret ( eckey , Buffer . from ( pubkey , 'hex' ) ) . toString ( 'hex' ) ;
2526+ const newEncryptedPrv = bitgo . encrypt ( { password : secret , input : userPrv } ) ;
2527+
2528+ let capturedBody : any ;
2529+ nock ( bgUrl )
2530+ . get ( '/api/v2/walletshares' )
2531+ . reply ( 200 , {
2532+ incoming : [
2533+ {
2534+ id : shareId ,
2535+ isUMSInitiated : true ,
2536+ keychain : {
2537+ path : path ,
2538+ fromPubKey : eckey . publicKey . toString ( 'hex' ) ,
2539+ encryptedPrv : newEncryptedPrv ,
2540+ toPubKey : pubkey ,
2541+ pub : pubkey ,
2542+ } ,
2543+ } ,
2544+ ] ,
2545+ } ) ;
2546+ nock ( bgUrl )
2547+ . put ( '/api/v2/walletshares/accept' , ( body ) => {
2548+ capturedBody = body ;
2549+ return true ;
2550+ } )
2551+ . reply ( 200 , {
2552+ acceptedWalletShares : [ { walletShareId : shareId } ] ,
2553+ } ) ;
2554+
2555+ const myEcdhKeychain = await bitgo . keychains ( ) . create ( ) ;
2556+ sinon . stub ( bitgo , 'getECDHKeychain' ) . resolves ( {
2557+ encryptedXprv : bitgo . encrypt ( { input : myEcdhKeychain . xprv , password : walletPassphrase } ) ,
2558+ } ) ;
2559+
2560+ const prvKey = bitgo . decrypt ( {
2561+ password : walletPassphrase ,
2562+ input : bitgo . encrypt ( { input : myEcdhKeychain . xprv , password : walletPassphrase } ) ,
2563+ } ) ;
2564+ sinon . stub ( bitgo , 'decrypt' ) . returns ( prvKey ) ;
2565+ sinon . stub ( moduleBitgo , 'getSharedSecret' ) . resolves ( 'fakeSharedSecret' ) ;
2566+
2567+ await wallets . bulkAcceptShare ( {
2568+ walletShareIds : [ shareId ] ,
2569+ userLoginPassword : walletPassphrase ,
2570+ webauthnInfo : {
2571+ otpDeviceId : 'device-001' ,
2572+ prfSalt : 'salt-abc' ,
2573+ passphrase : webauthnPassphrase ,
2574+ } ,
2575+ } ) ;
2576+
2577+ const sentEntries = capturedBody . keysForWalletShares ;
2578+ sentEntries . should . have . length ( 1 ) ;
2579+ sentEntries [ 0 ] . should . have . property ( 'encryptedPrv' ) ;
2580+ sentEntries [ 0 ] . should . have . property ( 'webauthnInfo' ) ;
2581+ sentEntries [ 0 ] . webauthnInfo . should . have . property ( 'otpDeviceId' , 'device-001' ) ;
2582+ sentEntries [ 0 ] . webauthnInfo . should . have . property ( 'prfSalt' , 'salt-abc' ) ;
2583+ sentEntries [ 0 ] . webauthnInfo . should . have . property ( 'encryptedPrv' ) ;
2584+ sentEntries [ 0 ] . webauthnInfo . should . not . have . property ( 'passphrase' ) ;
2585+ } ) ;
2586+
2587+ it ( 'should include webauthnInfo in request when provided (userMultiKeyRotationRequired branch)' , async ( ) => {
2588+ const walletPassphrase = 'bitgo1234' ;
2589+ const webauthnPassphrase = 'prf-derived-secret' ;
2590+ const shareId = 'multi-key-share-id-001' ;
2591+
2592+ sinon . stub ( Wallets . prototype , 'listSharesV2' ) . resolves ( {
2593+ incoming : [
2594+ {
2595+ id : shareId ,
2596+ coin : 'tsol' ,
2597+ walletLabel : 'testing' ,
2598+ fromUser : 'dummyFromUser' ,
2599+ toUser : 'dummyToUser' ,
2600+ wallet : 'dummyWalletId' ,
2601+ permissions : [ 'spend' ] ,
2602+ state : 'active' ,
2603+ userMultiKeyRotationRequired : true ,
2604+ } ,
2605+ ] ,
2606+ outgoing : [ ] ,
2607+ } ) ;
2608+
2609+ const myEcdhKeychain = await bitgo . keychains ( ) . create ( ) ;
2610+ sinon . stub ( bitgo , 'getECDHKeychain' ) . resolves ( {
2611+ encryptedXprv : bitgo . encrypt ( { input : myEcdhKeychain . xprv , password : walletPassphrase } ) ,
2612+ } ) ;
2613+ const prvKey = bitgo . decrypt ( {
2614+ password : walletPassphrase ,
2615+ input : bitgo . encrypt ( { input : myEcdhKeychain . xprv , password : walletPassphrase } ) ,
2616+ } ) ;
2617+ sinon . stub ( bitgo , 'decrypt' ) . returns ( prvKey ) ;
2618+
2619+ let capturedBody : any ;
2620+ nock ( bgUrl )
2621+ . put ( '/api/v2/walletshares/accept' , ( body ) => {
2622+ capturedBody = body ;
2623+ return true ;
2624+ } )
2625+ . reply ( 200 , {
2626+ acceptedWalletShares : [ { walletShareId : shareId } ] ,
2627+ } ) ;
2628+
2629+ await wallets . bulkAcceptShare ( {
2630+ walletShareIds : [ shareId ] ,
2631+ userLoginPassword : walletPassphrase ,
2632+ webauthnInfo : {
2633+ otpDeviceId : 'device-002' ,
2634+ prfSalt : 'salt-xyz' ,
2635+ passphrase : webauthnPassphrase ,
2636+ } ,
2637+ } ) ;
2638+
2639+ const sentEntries = capturedBody . keysForWalletShares ;
2640+ sentEntries . should . have . length ( 1 ) ;
2641+ sentEntries [ 0 ] . should . have . property ( 'pub' ) ;
2642+ sentEntries [ 0 ] . should . have . property ( 'encryptedPrv' ) ;
2643+ sentEntries [ 0 ] . should . have . property ( 'webauthnInfo' ) ;
2644+ sentEntries [ 0 ] . webauthnInfo . should . have . property ( 'otpDeviceId' , 'device-002' ) ;
2645+ sentEntries [ 0 ] . webauthnInfo . should . have . property ( 'prfSalt' , 'salt-xyz' ) ;
2646+ sentEntries [ 0 ] . webauthnInfo . should . have . property ( 'encryptedPrv' ) ;
2647+ sentEntries [ 0 ] . webauthnInfo . should . not . have . property ( 'passphrase' ) ;
2648+ } ) ;
2649+
2650+ it ( 'should NOT include webauthnInfo when not provided (backward compat)' , async ( ) => {
2651+ const fromUserPrv = Math . random ( ) ;
2652+ const walletPassphrase = 'bitgo1234' ;
2653+ const shareId = '66a229dbdccdcfb95b44fc2745a60bd4' ;
2654+ const keychainTest : OptionalKeychainEncryptedKey = {
2655+ encryptedPrv : bitgo . encrypt ( { input : fromUserPrv . toString ( ) , password : walletPassphrase } ) ,
2656+ } ;
2657+ const userPrv = decryptKeychainPrivateKey ( bitgo , keychainTest , walletPassphrase ) ;
2658+ if ( ! userPrv ) {
2659+ throw new Error ( 'Unable to decrypt user keychain' ) ;
2660+ }
2661+
2662+ const toKeychain = utxoLib . bip32 . fromSeed ( Buffer . from ( 'deadbeef02deadbeef02deadbeef02deadbeef02' , 'hex' ) ) ;
2663+ const path = 'm/999999/1/1' ;
2664+ const pubkey = toKeychain . derivePath ( path ) . publicKey . toString ( 'hex' ) ;
2665+
2666+ const eckey = makeRandomKey ( ) ;
2667+ const secret = getSharedSecret ( eckey , Buffer . from ( pubkey , 'hex' ) ) . toString ( 'hex' ) ;
2668+ const newEncryptedPrv = bitgo . encrypt ( { password : secret , input : userPrv } ) ;
2669+
2670+ let capturedBody : any ;
2671+ nock ( bgUrl )
2672+ . get ( '/api/v2/walletshares' )
2673+ . reply ( 200 , {
2674+ incoming : [
2675+ {
2676+ id : shareId ,
2677+ isUMSInitiated : true ,
2678+ keychain : {
2679+ path : path ,
2680+ fromPubKey : eckey . publicKey . toString ( 'hex' ) ,
2681+ encryptedPrv : newEncryptedPrv ,
2682+ toPubKey : pubkey ,
2683+ pub : pubkey ,
2684+ } ,
2685+ } ,
2686+ ] ,
2687+ } ) ;
2688+ nock ( bgUrl )
2689+ . put ( '/api/v2/walletshares/accept' , ( body ) => {
2690+ capturedBody = body ;
2691+ return true ;
2692+ } )
2693+ . reply ( 200 , {
2694+ acceptedWalletShares : [ { walletShareId : shareId } ] ,
2695+ } ) ;
2696+
2697+ const myEcdhKeychain = await bitgo . keychains ( ) . create ( ) ;
2698+ sinon . stub ( bitgo , 'getECDHKeychain' ) . resolves ( {
2699+ encryptedXprv : bitgo . encrypt ( { input : myEcdhKeychain . xprv , password : walletPassphrase } ) ,
2700+ } ) ;
2701+ const prvKey = bitgo . decrypt ( {
2702+ password : walletPassphrase ,
2703+ input : bitgo . encrypt ( { input : myEcdhKeychain . xprv , password : walletPassphrase } ) ,
2704+ } ) ;
2705+ sinon . stub ( bitgo , 'decrypt' ) . returns ( prvKey ) ;
2706+ sinon . stub ( moduleBitgo , 'getSharedSecret' ) . resolves ( 'fakeSharedSecret' ) ;
2707+
2708+ await wallets . bulkAcceptShare ( {
2709+ walletShareIds : [ shareId ] ,
2710+ userLoginPassword : walletPassphrase ,
2711+ } ) ;
2712+
2713+ const sentEntries = capturedBody . keysForWalletShares ;
2714+ sentEntries . should . have . length ( 1 ) ;
2715+ sentEntries [ 0 ] . should . have . property ( 'encryptedPrv' ) ;
2716+ sentEntries [ 0 ] . should . not . have . property ( 'webauthnInfo' ) ;
2717+ } ) ;
2718+
25072719 it ( 'should handle 413 payload too large error with smart retry' , async ( ) => {
25082720 const walletPassphrase = 'bitgo1234' ;
25092721 const fromUserPrv = Math . random ( ) ;
0 commit comments