Skip to content

Commit 0a8e132

Browse files
pranavjain97claude
andcommitted
feat(sdk-core): make decryptKeychainPrivateKey async for v1/v2 auto-detection
WCN-32: Convert decryptKeychainPrivateKey to use decryptAsync internally so signing flows work with both v1 and v2 encrypted keychains. Make getUserPrv async and update all callers across sdk-core, abstract-utxo, abstract-eth, and bitgo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent edaf926 commit 0a8e132

10 files changed

Lines changed: 36 additions & 31 deletions

File tree

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2554,7 +2554,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
25542554
const walletPassphrase = buildParams.walletPassphrase;
25552555

25562556
const userKeychain = await this.keychains().get({ id: wallet.keyIds()[0] });
2557-
const userPrv = wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
2557+
const userPrv = await wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
25582558
const userPrvBuffer = bip32.fromBase58(userPrv).privateKey;
25592559
if (!userPrvBuffer) {
25602560
throw new Error('invalid userPrv');

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ export abstract class AbstractUtxoCoin
677677
/**
678678
* @deprecated - use function verifyUserPublicKey instead
679679
*/
680-
protected verifyUserPublicKey(params: VerifyUserPublicKeyOptions): boolean {
680+
protected async verifyUserPublicKey(params: VerifyUserPublicKeyOptions): Promise<boolean> {
681681
return verifyUserPublicKey(this.bitgo, params);
682682
}
683683

modules/abstract-utxo/src/impl/btc/inscriptionBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
302302
txPrebuild: PrebuildTransactionResult
303303
): Promise<SubmitTransactionResponse> {
304304
const userKeychain = await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.USER] });
305-
const prv = this.wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
305+
const prv = await this.wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
306306

307307
const halfSigned = (await this.wallet.signTransaction({ prv, txPrebuild })) as HalfSignedUtxoTransaction;
308308
return this.wallet.submitTransaction({ halfSigned });

modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ export async function verifyTransaction<TNumber extends bigint | number>(
7979
let userPublicKeyVerified = false;
8080
try {
8181
// verify the user public key matches the private key - this will throw if there is no match
82-
userPublicKeyVerified = verifyUserPublicKey(bitgo, { userKeychain: keychains.user, disableNetworking, txParams });
82+
userPublicKeyVerified = await verifyUserPublicKey(bitgo, {
83+
userKeychain: keychains.user,
84+
disableNetworking,
85+
txParams,
86+
});
8387
} catch (e) {
8488
debug('failed to verify user public key!', e);
8589
}

modules/abstract-utxo/src/verifyKey.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function verifyCustomChangeKeySignatures<TNumber extends number | bigint>
8484
/**
8585
* Decrypt the wallet's user private key and verify that the claimed public key matches
8686
*/
87-
export function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPublicKeyOptions): boolean {
87+
export async function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPublicKeyOptions): Promise<boolean> {
8888
const { userKeychain, txParams, disableNetworking } = params;
8989
if (!userKeychain) {
9090
throw new Error('user keychain is required');
@@ -94,7 +94,7 @@ export function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPublicKe
9494

9595
let userPrv = userKeychain.prv;
9696
if (!userPrv && txParams.walletPassphrase) {
97-
userPrv = decryptKeychainPrivateKey(bitgo, userKeychain, txParams.walletPassphrase);
97+
userPrv = await decryptKeychainPrivateKey(bitgo, userKeychain, txParams.walletPassphrase);
9898
}
9999

100100
if (!userPrv) {

modules/bitgo/test/v2/unit/wallet.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ describe('V2 Wallet:', function () {
352352
prv,
353353
coldDerivationSeed: '123',
354354
};
355-
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
355+
(await wallet.getUserPrv(userPrvOptions)).should.eql(derivedPrv);
356356
});
357357

358358
it('should use the user keychain derivedFromParentWithSeed as the cold derivation seed if none is provided', async () => {
@@ -365,7 +365,7 @@ describe('V2 Wallet:', function () {
365365
type: 'independent',
366366
},
367367
};
368-
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
368+
(await wallet.getUserPrv(userPrvOptions)).should.eql(derivedPrv);
369369
});
370370

371371
it('should prefer the explicit cold derivation seed to the user keychain derivedFromParentWithSeed', async () => {
@@ -379,7 +379,7 @@ describe('V2 Wallet:', function () {
379379
type: 'independent',
380380
},
381381
};
382-
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
382+
(await wallet.getUserPrv(userPrvOptions)).should.eql(derivedPrv);
383383
});
384384

385385
it('should return the prv provided for TSS SMC', async () => {
@@ -407,7 +407,7 @@ describe('V2 Wallet:', function () {
407407
prv,
408408
keychain,
409409
};
410-
wallet.getUserPrv(userPrvOptions).should.eql(prv);
410+
(await wallet.getUserPrv(userPrvOptions)).should.eql(prv);
411411
});
412412
});
413413

modules/bitgo/test/v2/unit/wallets.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,7 +2440,7 @@ describe('V2 Wallets:', function () {
24402440
const keychainTest: OptionalKeychainEncryptedKey = {
24412441
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
24422442
};
2443-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2443+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
24442444
if (!userPrv) {
24452445
throw new Error('Unable to decrypt user keychain');
24462446
}
@@ -2512,7 +2512,7 @@ describe('V2 Wallets:', function () {
25122512
const keychainTest: OptionalKeychainEncryptedKey = {
25132513
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
25142514
};
2515-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2515+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
25162516
if (!userPrv) {
25172517
throw new Error('Unable to decrypt user keychain');
25182518
}
@@ -2591,7 +2591,7 @@ describe('V2 Wallets:', function () {
25912591
const keychainTest: OptionalKeychainEncryptedKey = {
25922592
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
25932593
};
2594-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2594+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
25952595
if (!userPrv) {
25962596
throw new Error('Unable to decrypt user keychain');
25972597
}
@@ -2659,7 +2659,7 @@ describe('V2 Wallets:', function () {
26592659
const keychainTest: OptionalKeychainEncryptedKey = {
26602660
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
26612661
};
2662-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2662+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
26632663
if (!userPrv) {
26642664
throw new Error('Unable to decrypt user keychain');
26652665
}
@@ -2760,7 +2760,7 @@ describe('V2 Wallets:', function () {
27602760
const keychainTest: OptionalKeychainEncryptedKey = {
27612761
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
27622762
};
2763-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2763+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
27642764
if (!userPrv) {
27652765
throw new Error('Unable to decrypt user keychain');
27662766
}
@@ -2862,7 +2862,7 @@ describe('V2 Wallets:', function () {
28622862
const keychainTest: OptionalKeychainEncryptedKey = {
28632863
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
28642864
};
2865-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2865+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
28662866
if (!userPrv) {
28672867
throw new Error('Unable to decrypt user keychain');
28682868
}

modules/sdk-core/src/bitgo/keychain/decryptKeychain.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { BitGoBase } from '../bitgoBase';
22
import { OptionalKeychainEncryptedKey } from './iKeychains';
33
import { notEmpty } from '../utils';
44

5-
function maybeDecrypt(bitgo: BitGoBase, input: string, password: string): string | undefined {
5+
async function maybeDecrypt(bitgo: BitGoBase, input: string, password: string): Promise<string | undefined> {
66
try {
7-
return bitgo.decrypt({
7+
return await bitgo.decryptAsync({
88
input,
99
password,
1010
});
@@ -17,19 +17,20 @@ function maybeDecrypt(bitgo: BitGoBase, input: string, password: string): string
1717
* Decrypts the private key of a keychain.
1818
* This method will try the password against the traditional encryptedPrv,
1919
* and any webauthn device encryptedPrvs.
20+
* Auto-detects v1 (SJCL) and v2 (Argon2id) envelopes.
2021
*
2122
* @param bitgo
2223
* @param keychain
2324
* @param password
2425
*/
25-
export function decryptKeychainPrivateKey(
26+
export async function decryptKeychainPrivateKey(
2627
bitgo: BitGoBase,
2728
keychain: OptionalKeychainEncryptedKey,
2829
password: string
29-
): string | undefined {
30+
): Promise<string | undefined> {
3031
const prvs = [keychain.encryptedPrv, ...(keychain.webauthnDevices ?? []).map((d) => d.encryptedPrv)].filter(notEmpty);
3132
for (const prv of prvs) {
32-
const decrypted = maybeDecrypt(bitgo, prv, password);
33+
const decrypted = await maybeDecrypt(bitgo, prv, password);
3334
if (decrypted) {
3435
return decrypted;
3536
}

modules/sdk-core/src/bitgo/wallet/iWallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ export interface IWallet {
10701070
removeUser(params?: RemoveUserOptions): Promise<any>;
10711071
prebuildTransaction(params?: PrebuildTransactionOptions): Promise<PrebuildTransactionResult>;
10721072
signTransaction(params?: WalletSignTransactionOptions): Promise<SignedTransaction>;
1073-
getUserPrv(params?: GetUserPrvOptions): string;
1073+
getUserPrv(params?: GetUserPrvOptions): Promise<string>;
10741074
prebuildAndSignTransaction(params?: PrebuildAndSignTransactionOptions): Promise<SignedTransaction>;
10751075
signAndSendTxRequest(params?: SignAndSendTxRequestOptions): Promise<SignedTransaction>;
10761076
accelerateTransaction(params?: AccelerateTransactionOptions): Promise<any>;

modules/sdk-core/src/bitgo/wallet/wallet.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,7 @@ export class Wallet implements IWallet {
16691669
if (!params.walletPassphrase) {
16701670
throw new Error('wallet passphrase was not provided');
16711671
}
1672-
const userPrv = decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
1672+
const userPrv = await decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
16731673
if (!userPrv) {
16741674
throw new Error('error decrypting wallet private key');
16751675
}
@@ -1849,7 +1849,7 @@ export class Wallet implements IWallet {
18491849
throw new Error('Missing walletPassphrase argument');
18501850
}
18511851

1852-
const prv = decryptKeychainPrivateKey(this.bitgo, keychain, walletPassphrase);
1852+
const prv = await decryptKeychainPrivateKey(this.bitgo, keychain, walletPassphrase);
18531853
if (!prv) {
18541854
throw new IncorrectPasswordError('Password shared is incorrect for this wallet');
18551855
}
@@ -2214,7 +2214,7 @@ export class Wallet implements IWallet {
22142214
if (this.multisigType() === 'tss') {
22152215
return this.signTransactionTss({
22162216
...presign,
2217-
prv: this.getUserPrv(presign as GetUserPrvOptions),
2217+
prv: await this.getUserPrv(presign as GetUserPrvOptions),
22182218
apiVersion,
22192219
});
22202220
}
@@ -2252,7 +2252,7 @@ export class Wallet implements IWallet {
22522252
}
22532253
return this.baseCoin.signTransaction({
22542254
...signTransactionParams,
2255-
prv: this.getUserPrv(presign as GetUserPrvOptions),
2255+
prv: await this.getUserPrv(presign as GetUserPrvOptions),
22562256
wallet: this,
22572257
});
22582258
}
@@ -2287,7 +2287,7 @@ export class Wallet implements IWallet {
22872287
...params,
22882288
walletData: this._wallet,
22892289
tssUtils: this.tssUtils,
2290-
prv: this.getUserPrv(userPrvOptions),
2290+
prv: await this.getUserPrv(userPrvOptions),
22912291
keychain: keychains[0],
22922292
backupKeychain: keychains.length > 1 ? keychains[1] : null,
22932293
bitgoKeychain: keychains.length > 2 ? keychains[2] : null,
@@ -2326,7 +2326,7 @@ export class Wallet implements IWallet {
23262326
...params,
23272327
walletData: this._wallet,
23282328
tssUtils: this.tssUtils,
2329-
prv: this.getUserPrv(userPrvOptions),
2329+
prv: await this.getUserPrv(userPrvOptions),
23302330
keychain: keychains[0],
23312331
backupKeychain: keychains.length > 1 ? keychains[1] : null,
23322332
bitgoKeychain: keychains.length > 2 ? keychains[2] : null,
@@ -2383,7 +2383,7 @@ export class Wallet implements IWallet {
23832383
* @param [params.keychain / params.key] (object) or params.prv (string)
23842384
* @param params.walletPassphrase (string)
23852385
*/
2386-
getUserPrv(params: GetUserPrvOptions = {}): string {
2386+
async getUserPrv(params: GetUserPrvOptions = {}): Promise<string> {
23872387
const userKeychain = params.keychain || params.key;
23882388
let userPrv = params.prv;
23892389
if (userPrv && typeof userPrv !== 'string') {
@@ -2420,7 +2420,7 @@ export class Wallet implements IWallet {
24202420
if (!params.walletPassphrase) {
24212421
throw new Error('walletPassphrase property missing');
24222422
}
2423-
userPrv = decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
2423+
userPrv = await decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
24242424
if (!userPrv) {
24252425
throw new Error('failed to decrypt user keychain');
24262426
}
@@ -4393,7 +4393,7 @@ export class Wallet implements IWallet {
43934393
// we ignore this check with if customSigningFunction is provided
43944394
// which means that the user is handling the signing in external signing mode
43954395
if (!customSigningFunction && keychains?.[0]?.encryptedPrv && walletPassphrase) {
4396-
if (!decryptKeychainPrivateKey(this.bitgo, keychains[0], walletPassphrase)) {
4396+
if (!(await decryptKeychainPrivateKey(this.bitgo, keychains[0], walletPassphrase))) {
43974397
const error: Error & { code?: string } = new Error(
43984398
`unable to decrypt keychain with the given wallet passphrase`
43994399
);

0 commit comments

Comments
 (0)