Skip to content

Commit 36c78cd

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 7957f0d commit 36c78cd

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
@@ -2566,7 +2566,7 @@ describe('V2 Wallets:', function () {
25662566
const keychainTest: OptionalKeychainEncryptedKey = {
25672567
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
25682568
};
2569-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2569+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
25702570
if (!userPrv) {
25712571
throw new Error('Unable to decrypt user keychain');
25722572
}
@@ -2638,7 +2638,7 @@ describe('V2 Wallets:', function () {
26382638
const keychainTest: OptionalKeychainEncryptedKey = {
26392639
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
26402640
};
2641-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2641+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
26422642
if (!userPrv) {
26432643
throw new Error('Unable to decrypt user keychain');
26442644
}
@@ -2717,7 +2717,7 @@ describe('V2 Wallets:', function () {
27172717
const keychainTest: OptionalKeychainEncryptedKey = {
27182718
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
27192719
};
2720-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2720+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
27212721
if (!userPrv) {
27222722
throw new Error('Unable to decrypt user keychain');
27232723
}
@@ -2785,7 +2785,7 @@ describe('V2 Wallets:', function () {
27852785
const keychainTest: OptionalKeychainEncryptedKey = {
27862786
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
27872787
};
2788-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2788+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
27892789
if (!userPrv) {
27902790
throw new Error('Unable to decrypt user keychain');
27912791
}
@@ -2886,7 +2886,7 @@ describe('V2 Wallets:', function () {
28862886
const keychainTest: OptionalKeychainEncryptedKey = {
28872887
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
28882888
};
2889-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2889+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
28902890
if (!userPrv) {
28912891
throw new Error('Unable to decrypt user keychain');
28922892
}
@@ -2988,7 +2988,7 @@ describe('V2 Wallets:', function () {
29882988
const keychainTest: OptionalKeychainEncryptedKey = {
29892989
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
29902990
};
2991-
const userPrv = decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2991+
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
29922992
if (!userPrv) {
29932993
throw new Error('Unable to decrypt user keychain');
29942994
}

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
@@ -1673,7 +1673,7 @@ export class Wallet implements IWallet {
16731673
if (!params.walletPassphrase) {
16741674
throw new Error('wallet passphrase was not provided');
16751675
}
1676-
const userPrv = decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
1676+
const userPrv = await decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
16771677
if (!userPrv) {
16781678
throw new Error('error decrypting wallet private key');
16791679
}
@@ -1853,7 +1853,7 @@ export class Wallet implements IWallet {
18531853
throw new Error('Missing walletPassphrase argument');
18541854
}
18551855

1856-
const prv = decryptKeychainPrivateKey(this.bitgo, keychain, walletPassphrase);
1856+
const prv = await decryptKeychainPrivateKey(this.bitgo, keychain, walletPassphrase);
18571857
if (!prv) {
18581858
throw new IncorrectPasswordError('Password shared is incorrect for this wallet');
18591859
}
@@ -2218,7 +2218,7 @@ export class Wallet implements IWallet {
22182218
if (this.multisigType() === 'tss') {
22192219
return this.signTransactionTss({
22202220
...presign,
2221-
prv: this.getUserPrv(presign as GetUserPrvOptions),
2221+
prv: await this.getUserPrv(presign as GetUserPrvOptions),
22222222
apiVersion,
22232223
});
22242224
}
@@ -2256,7 +2256,7 @@ export class Wallet implements IWallet {
22562256
}
22572257
return this.baseCoin.signTransaction({
22582258
...signTransactionParams,
2259-
prv: this.getUserPrv(presign as GetUserPrvOptions),
2259+
prv: await this.getUserPrv(presign as GetUserPrvOptions),
22602260
wallet: this,
22612261
});
22622262
}
@@ -2291,7 +2291,7 @@ export class Wallet implements IWallet {
22912291
...params,
22922292
walletData: this._wallet,
22932293
tssUtils: this.tssUtils,
2294-
prv: this.getUserPrv(userPrvOptions),
2294+
prv: await this.getUserPrv(userPrvOptions),
22952295
keychain: keychains[0],
22962296
backupKeychain: keychains.length > 1 ? keychains[1] : null,
22972297
bitgoKeychain: keychains.length > 2 ? keychains[2] : null,
@@ -2330,7 +2330,7 @@ export class Wallet implements IWallet {
23302330
...params,
23312331
walletData: this._wallet,
23322332
tssUtils: this.tssUtils,
2333-
prv: this.getUserPrv(userPrvOptions),
2333+
prv: await this.getUserPrv(userPrvOptions),
23342334
keychain: keychains[0],
23352335
backupKeychain: keychains.length > 1 ? keychains[1] : null,
23362336
bitgoKeychain: keychains.length > 2 ? keychains[2] : null,
@@ -2387,7 +2387,7 @@ export class Wallet implements IWallet {
23872387
* @param [params.keychain / params.key] (object) or params.prv (string)
23882388
* @param params.walletPassphrase (string)
23892389
*/
2390-
getUserPrv(params: GetUserPrvOptions = {}): string {
2390+
async getUserPrv(params: GetUserPrvOptions = {}): Promise<string> {
23912391
const userKeychain = params.keychain || params.key;
23922392
let userPrv = params.prv;
23932393
if (userPrv && typeof userPrv !== 'string') {
@@ -2424,7 +2424,7 @@ export class Wallet implements IWallet {
24242424
if (!params.walletPassphrase) {
24252425
throw new Error('walletPassphrase property missing');
24262426
}
2427-
userPrv = decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
2427+
userPrv = await decryptKeychainPrivateKey(this.bitgo, userKeychain, params.walletPassphrase);
24282428
if (!userPrv) {
24292429
throw new Error('failed to decrypt user keychain');
24302430
}
@@ -4409,7 +4409,7 @@ export class Wallet implements IWallet {
44094409
// we ignore this check with if customSigningFunction is provided
44104410
// which means that the user is handling the signing in external signing mode
44114411
if (!customSigningFunction && keychains?.[0]?.encryptedPrv && walletPassphrase) {
4412-
if (!decryptKeychainPrivateKey(this.bitgo, keychains[0], walletPassphrase)) {
4412+
if (!(await decryptKeychainPrivateKey(this.bitgo, keychains[0], walletPassphrase))) {
44134413
const error: Error & { code?: string } = new Error(
44144414
`unable to decrypt keychain with the given wallet passphrase`
44154415
);

0 commit comments

Comments
 (0)