@@ -24,6 +24,7 @@ import {
2424 UpdateSingleKeychainPasswordOptions ,
2525} from './iKeychains' ;
2626import { BitGoKeyFromOvcShares , BitGoToOvcJSON , OvcToBitGoJSON } from './ovcJsonCodec' ;
27+ import { EncryptionVersion } from '../../api' ;
2728
2829export class Keychains implements IKeychains {
2930 private readonly bitgo : BitGoBase ;
@@ -113,7 +114,7 @@ export class Keychains implements IKeychains {
113114 continue ;
114115 }
115116 try {
116- const updatedKeychain = this . updateSingleKeychainPassword ( {
117+ const updatedKeychain = await this . updateSingleKeychainPasswordAsync ( {
117118 keychain : key ,
118119 oldPassword : params . oldPassword ,
119120 newPassword : params . newPassword ,
@@ -145,12 +146,13 @@ export class Keychains implements IKeychains {
145146 }
146147
147148 /**
148- * Update the password used to decrypt a single keychain
149+ * Update the password used to decrypt a single keychain.
150+ * Handles v1 (SJCL) envelopes only. For v2 (Argon2id) support use {@link updateSingleKeychainPasswordAsync}.
149151 * @param params
150152 * @param params.keychain - The keychain whose password should be updated
151153 * @param params.oldPassword - The old password used for encrypting the key
152154 * @param params.newPassword - The new password to be used for encrypting the key
153- * @returns {object }
155+ * @returns {Keychain }
154156 */
155157 updateSingleKeychainPassword ( params : UpdateSingleKeychainPasswordOptions = { } ) : Keychain {
156158 if ( ! _ . isString ( params . oldPassword ) ) {
@@ -176,6 +178,53 @@ export class Keychains implements IKeychains {
176178 }
177179 }
178180
181+ /**
182+ * Update the password used to decrypt a single keychain, with support for v2 (Argon2id) envelopes.
183+ * Automatically detects and preserves the envelope version — a v2-encrypted key stays v2 after the password change.
184+ * @param params
185+ * @param params.keychain - The keychain whose password should be updated
186+ * @param params.oldPassword - The old password used for encrypting the key
187+ * @param params.newPassword - The new password to be used for encrypting the key
188+ * @returns {Promise<Keychain> }
189+ */
190+ async updateSingleKeychainPasswordAsync ( params : UpdateSingleKeychainPasswordOptions = { } ) : Promise < Keychain > {
191+ if ( ! _ . isString ( params . oldPassword ) ) {
192+ throw new Error ( 'expected old password to be a string' ) ;
193+ }
194+
195+ if ( ! _ . isString ( params . newPassword ) ) {
196+ throw new Error ( 'expected new password to be a string' ) ;
197+ }
198+
199+ if ( ! _ . isObject ( params . keychain ) || ! _ . isString ( params . keychain . encryptedPrv ) ) {
200+ throw new Error ( 'expected keychain to be an object with an encryptedPrv property' ) ;
201+ }
202+
203+ const oldEncryptedPrv = params . keychain . encryptedPrv ;
204+ try {
205+ const decryptedPrv = await this . bitgo . decryptAsync ( { input : oldEncryptedPrv , password : params . oldPassword } ) ;
206+ // Preserve the original envelope's encryption version so v2-encrypted keys stay v2 after the password change.
207+ let encryptionVersion : EncryptionVersion | undefined ;
208+ try {
209+ const parsed = JSON . parse ( oldEncryptedPrv ) ;
210+ if ( parsed . v === 2 ) {
211+ encryptionVersion = 2 ;
212+ }
213+ } catch {
214+ // non-JSON input — default to v1 (no encryptionVersion)
215+ }
216+ const newEncryptedPrv = await this . bitgo . encryptAsync ( {
217+ input : decryptedPrv ,
218+ password : params . newPassword ,
219+ encryptionVersion,
220+ } ) ;
221+ return _ . assign ( { } , params . keychain , { encryptedPrv : newEncryptedPrv } ) ;
222+ } catch ( e ) {
223+ // catching an error here means that the password was incorrect or, less likely, the input to decrypt is corrupted
224+ throw new Error ( 'password used to decrypt keychain private key is incorrect' ) ;
225+ }
226+ }
227+
179228 /**
180229 * Create a public/private key pair
181230 * @param params - optional params
@@ -359,17 +408,17 @@ export class Keychains implements IKeychains {
359408 throw new Error ( 'failed to get recovery info' ) ;
360409 }
361410
362- const decryptedWalletPassphrase = this . bitgo . decrypt ( {
411+ const decryptedWalletPassphrase = await this . bitgo . decryptAsync ( {
363412 input : params . encryptedMaterial . encryptedWalletPassphrase ,
364413 password : recoveryInfo . passcodeEncryptionCode ,
365414 } ) ;
366415
367- const decryptedUserKey = this . bitgo . decrypt ( {
416+ const decryptedUserKey = await this . bitgo . decryptAsync ( {
368417 input : params . encryptedMaterial . encryptedUserKey ,
369418 password : decryptedWalletPassphrase ,
370419 } ) ;
371420
372- const decryptedBackupKey = this . bitgo . decrypt ( {
421+ const decryptedBackupKey = await this . bitgo . decryptAsync ( {
373422 input : params . encryptedMaterial . encryptedBackupKey ,
374423 password : decryptedWalletPassphrase ,
375424 } ) ;
0 commit comments