11import * as assert from 'assert' ;
22import * as sinon from 'sinon' ;
33import { attachPasskeyToWallet } from '../../src/attachPasskeyToWallet' ;
4+ import { derivePassword } from '../../src/derivePassword' ;
45import { WebAuthnOtpDevice , PasskeyAuthResult , WebAuthnProvider } from '../../src/webAuthnTypes' ;
56
67describe ( 'attachPasskeyToWallet' , function ( ) {
@@ -53,8 +54,8 @@ describe('attachPasskeyToWallet', function () {
5354 url : sinon . SinonStub ;
5455 coin : sinon . SinonStub ;
5556 put : sinon . SinonStub ;
56- decrypt : sinon . SinonStub ;
57- encrypt : sinon . SinonStub ;
57+ decryptAsync : sinon . SinonStub ;
58+ encryptAsync : sinon . SinonStub ;
5859 } ;
5960
6061 let mockProvider : {
@@ -83,17 +84,17 @@ describe('attachPasskeyToWallet', function () {
8384 . callsFake ( ( path , version ) => `/api/v${ version ?? 1 } ${ path } ` ) ,
8485 coin : sinon . stub ( ) . returns ( mockBaseCoin ) ,
8586 put : sinon . stub ( ) ,
86- decrypt : sinon . stub ( ) ,
87- encrypt : sinon . stub ( ) ,
87+ decryptAsync : sinon . stub ( ) ,
88+ encryptAsync : sinon . stub ( ) ,
8889 } ;
8990
9091 mockProvider = {
9192 create : sinon . stub ( ) ,
9293 get : sinon . stub ( ) ,
9394 } ;
9495
95- mockBitGo . decrypt . returns ( decryptedPrv ) ;
96- mockBitGo . encrypt . returns ( reEncryptedPrv ) ;
96+ mockBitGo . decryptAsync . resolves ( decryptedPrv ) ;
97+ mockBitGo . encryptAsync . resolves ( reEncryptedPrv ) ;
9798
9899 const putSendStub = sinon . stub ( ) . returns ( { result : sinon . stub ( ) . resolves ( updatedKeychain ) } ) ;
99100 mockBitGo . put . returns ( { send : putSendStub } ) ;
@@ -124,8 +125,8 @@ describe('attachPasskeyToWallet', function () {
124125 sinon . assert . calledWith ( mockWallets . get , { id : walletId } ) ;
125126 sinon . assert . calledOnce ( mockWallet . type ) ;
126127 sinon . assert . calledOnce ( mockWallet . getEncryptedUserKeychain ) ;
127- sinon . assert . calledOnce ( mockBitGo . decrypt ) ;
128- sinon . assert . calledWithExactly ( mockBitGo . decrypt , { password : existingPassphrase , input : encryptedPrv } ) ;
128+ sinon . assert . calledOnce ( mockBitGo . decryptAsync ) ;
129+ sinon . assert . calledWithExactly ( mockBitGo . decryptAsync , { password : existingPassphrase , input : encryptedPrv } ) ;
129130
130131 // provider.get called with evalByCredential keyed on device.credentialId
131132 sinon . assert . calledOnce ( mockProvider . get ) ;
@@ -151,9 +152,30 @@ describe('attachPasskeyToWallet', function () {
151152 assert . match ( putBody . webauthnInfo . prfSalt , / ^ [ A - Z a - z 0 - 9 \- _ ] + $ / ) ;
152153 assert . strictEqual ( typeof putBody . webauthnInfo . encryptedPrv , 'string' ) ;
153154
155+ // encryptAsync must be called with encryptionVersion 2
156+ sinon . assert . calledOnce ( mockBitGo . encryptAsync ) ;
157+ sinon . assert . calledWithMatch ( mockBitGo . encryptAsync , { encryptionVersion : 2 } ) ;
158+
154159 assert . strictEqual ( result . id , keychainId ) ;
155160 } ) ;
156161
162+ it ( 'should re-encrypt the private key as a v2 Argon2id envelope' , async function ( ) {
163+ const expectedPrfPassword = derivePassword ( prfResultBuffer ) ;
164+
165+ await callAttach ( ) ;
166+
167+ // The PRF-derived password and the decrypted xprv must be passed to encryptAsync
168+ sinon . assert . calledWithMatch ( mockBitGo . encryptAsync , {
169+ password : expectedPrfPassword ,
170+ input : decryptedPrv ,
171+ encryptionVersion : 2 ,
172+ } ) ;
173+
174+ // The v2 blob returned by encryptAsync is what gets stored on the server
175+ const putBody = mockBitGo . put . firstCall . returnValue . send . firstCall . args [ 0 ] ;
176+ assert . strictEqual ( putBody . webauthnInfo . encryptedPrv , reEncryptedPrv ) ;
177+ } ) ;
178+
157179 it ( 'should decode credentialId containing base64url-specific characters (- and _)' , async function ( ) {
158180 const deviceWithUrlChars : WebAuthnOtpDevice = {
159181 ...device ,
@@ -223,7 +245,7 @@ describe('attachPasskeyToWallet', function () {
223245 } ) ;
224246
225247 it ( 'should propagate decrypt errors' , async function ( ) {
226- mockBitGo . decrypt . throws ( new Error ( 'decryption failed' ) ) ;
248+ mockBitGo . decryptAsync . rejects ( new Error ( 'decryption failed' ) ) ;
227249
228250 await assert . rejects (
229251 ( ) => callAttach ( ) ,
0 commit comments