Skip to content

Commit 0997d56

Browse files
authored
Merge pull request #8923 from BitGo/WCN-284-remainder
feat: migrate remaining sync decrypt/encrypt calls
2 parents b46ef91 + a52f1ae commit 0997d56

40 files changed

Lines changed: 446 additions & 214 deletions

File tree

modules/abstract-lightning/src/wallet/lightning.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export class LightningWallet implements ILightningWallet {
265265
}
266266
const signature = createMessageSignature(
267267
t.exact(LightningPaymentRequest).encode(params),
268-
this.wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
268+
await this.wallet.bitgo.decryptAsync({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
269269
);
270270

271271
const paymentIntent: { intent: LightningPaymentIntent } = {
@@ -390,7 +390,7 @@ export class LightningWallet implements ILightningWallet {
390390
}
391391
const signature = createMessageSignature(
392392
transactionRequestCreate.transactions[0].unsignedTx.serializedTxHex,
393-
this.wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
393+
await this.wallet.bitgo.decryptAsync({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
394394
);
395395

396396
const transactionRequestWithSignature = (await this.wallet.bitgo

modules/abstract-lightning/src/wallet/selfCustodialLightning.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,38 @@ import { getLightningAuthKeychains, ILightningWallet, LightningWallet } from './
44
import { createMessageSignature, deriveLightningServiceSharedSecret, isLightningCoinName } from '../lightning';
55
import * as t from 'io-ts';
66

7-
function encryptWalletUpdateRequest(
7+
async function encryptWalletUpdateRequest(
88
wallet: sdkcore.IWallet,
99
params: UpdateLightningWalletClientRequest,
1010
userAuthKeyEncryptedPrv: string
11-
): UpdateLightningWalletEncryptedRequest {
11+
): Promise<UpdateLightningWalletEncryptedRequest> {
1212
const coinName = wallet.coin() as 'tlnbtc' | 'lnbtc';
1313

1414
const requestWithEncryption: Partial<UpdateLightningWalletClientRequest & UpdateLightningWalletEncryptedRequest> = {
1515
...params,
1616
};
1717

18-
const userAuthXprv = wallet.bitgo.decrypt({
18+
const userAuthXprv = await wallet.bitgo.decryptAsync({
1919
password: params.passphrase,
2020
input: userAuthKeyEncryptedPrv,
2121
});
2222

2323
if (params.signerTlsKey) {
24-
requestWithEncryption.encryptedSignerTlsKey = wallet.bitgo.encrypt({
24+
requestWithEncryption.encryptedSignerTlsKey = await wallet.bitgo.encryptAsync({
2525
password: params.passphrase,
2626
input: params.signerTlsKey,
2727
});
2828
}
2929

3030
if (params.signerAdminMacaroon) {
31-
requestWithEncryption.encryptedSignerAdminMacaroon = wallet.bitgo.encrypt({
31+
requestWithEncryption.encryptedSignerAdminMacaroon = await wallet.bitgo.encryptAsync({
3232
password: params.passphrase,
3333
input: params.signerAdminMacaroon,
3434
});
3535
}
3636

3737
if (params.signerMacaroon) {
38-
requestWithEncryption.encryptedSignerMacaroon = wallet.bitgo.encrypt({
38+
requestWithEncryption.encryptedSignerMacaroon = await wallet.bitgo.encryptAsync({
3939
password: deriveLightningServiceSharedSecret(coinName, userAuthXprv).toString('hex'),
4040
input: params.signerMacaroon,
4141
});
@@ -86,10 +86,10 @@ export async function updateWalletCoinSpecific(
8686
if (!userAuthKeyEncryptedPrv) {
8787
throw new Error(`user auth key is missing encrypted private key`);
8888
}
89-
const updateRequestWithEncryption = encryptWalletUpdateRequest(wallet, params, userAuthKeyEncryptedPrv);
89+
const updateRequestWithEncryption = await encryptWalletUpdateRequest(wallet, params, userAuthKeyEncryptedPrv);
9090
const signature = createMessageSignature(
9191
updateRequestWithEncryption,
92-
wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
92+
await wallet.bitgo.decryptAsync({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
9393
);
9494
const coinSpecific = {
9595
[wallet.coin()]: {

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ import {
9494
stringToBufferTryFormats,
9595
} from './transaction/decode';
9696
import { fetchKeychains, toBip32Triple, UtxoKeychain } from './keychains';
97-
import { verifyKeySignature, verifyUserPublicKey } from './verifyKey';
97+
import { verifyKeySignature, verifyUserPublicKey, verifyUserPublicKeyAsync } from './verifyKey';
9898
import { getPolicyForEnv } from './descriptor/validatePolicy';
9999
import { signTransaction } from './transaction/signTransaction';
100100
import { isUtxoWalletData, UtxoWallet } from './wallet';
@@ -707,6 +707,13 @@ export abstract class AbstractUtxoCoin
707707
return verifyUserPublicKey(this.bitgo, params);
708708
}
709709

710+
/**
711+
* @deprecated - use function verifyUserPublicKeyAsync instead
712+
*/
713+
protected async verifyUserPublicKeyAsync(params: VerifyUserPublicKeyOptions): Promise<boolean> {
714+
return await verifyUserPublicKeyAsync(this.bitgo, params);
715+
}
716+
710717
/**
711718
* @deprecated - use function verifyKeySignature instead
712719
*/

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as utxolib from '@bitgo/utxo-lib';
66

77
import { AbstractUtxoCoin, VerifyTransactionOptions } from '../../abstractUtxoCoin';
88
import { Output, ParsedTransaction } from '../types';
9-
import { verifyCustomChangeKeySignatures, verifyKeySignature, verifyUserPublicKey } from '../../verifyKey';
9+
import { verifyCustomChangeKeySignatures, verifyKeySignature, verifyUserPublicKeyAsync } from '../../verifyKey';
1010
import { getPsbtTxInputs, getTxInputs } from '../fetchInputs';
1111

1212
const debug = buildDebug('bitgo:abstract-utxo:verifyTransaction');
@@ -80,7 +80,7 @@ export async function verifyTransaction<TNumber extends bigint | number>(
8080
let userPublicKeyVerified = false;
8181
try {
8282
// verify the user public key matches the private key - this will throw if there is no match
83-
userPublicKeyVerified = verifyUserPublicKey(bitgo, {
83+
userPublicKeyVerified = await verifyUserPublicKeyAsync(bitgo, {
8484
userKeychain: keychains.user,
8585
disableNetworking,
8686
txParams,

modules/abstract-utxo/src/verifyKey.ts

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import assert from 'assert';
77

88
import buildDebug from 'debug';
99
import { BIP32, message } from '@bitgo/wasm-utxo';
10-
import { BitGoBase, decryptKeychainPrivateKey, KeyIndices } from '@bitgo/sdk-core';
10+
import { BitGoBase, decryptKeychainPrivateKey, decryptKeychainPrivateKeyAsync, KeyIndices } from '@bitgo/sdk-core';
1111

1212
import { VerifyKeySignaturesOptions, VerifyUserPublicKeyOptions } from './abstractUtxoCoin';
1313
import { ParsedTransaction } from './transaction/types';
@@ -81,39 +81,65 @@ export function verifyCustomChangeKeySignatures<TNumber extends number | bigint>
8181
return true;
8282
}
8383

84+
function verifyUserPublicKeyWithPrv(
85+
userKeychain: NonNullable<VerifyUserPublicKeyOptions['userKeychain']>,
86+
userPrv: string | undefined,
87+
disableNetworking: boolean | undefined
88+
): boolean {
89+
const userPub = userKeychain.pub;
90+
91+
if (!userPrv) {
92+
const errorMessage = 'user private key unavailable for verification';
93+
if (disableNetworking) {
94+
console.log(errorMessage);
95+
return false;
96+
} else {
97+
throw new Error(errorMessage);
98+
}
99+
}
100+
101+
const userPrivateKey = BIP32.fromBase58(userPrv);
102+
if (userPrivateKey.toBase58() === userPrivateKey.neutered().toBase58()) {
103+
throw new Error('user private key is only public');
104+
}
105+
if (userPrivateKey.neutered().toBase58() !== userPub) {
106+
throw new Error('user private key does not match public key');
107+
}
108+
109+
return true;
110+
}
111+
84112
/**
85-
* Decrypt the wallet's user private key and verify that the claimed public key matches
113+
* TODO: Deprecate in favor of verifyUserPublicKeyAsync once v2 encryption is default.
114+
* Decrypt the wallet's user private key and verify that the claimed public key matches (sync, v1 only).
86115
*/
87116
export function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPublicKeyOptions): boolean {
88117
const { userKeychain, txParams, disableNetworking } = params;
89118
if (!userKeychain) {
90119
throw new Error('user keychain is required');
91120
}
92121

93-
const userPub = userKeychain.pub;
94-
95122
let userPrv = userKeychain.prv;
96123
if (!userPrv && txParams.walletPassphrase) {
97124
userPrv = decryptKeychainPrivateKey(bitgo, userKeychain, txParams.walletPassphrase);
98125
}
99126

100-
if (!userPrv) {
101-
const errorMessage = 'user private key unavailable for verification';
102-
if (disableNetworking) {
103-
console.log(errorMessage);
104-
return false;
105-
} else {
106-
throw new Error(errorMessage);
107-
}
108-
} else {
109-
const userPrivateKey = BIP32.fromBase58(userPrv);
110-
if (userPrivateKey.toBase58() === userPrivateKey.neutered().toBase58()) {
111-
throw new Error('user private key is only public');
112-
}
113-
if (userPrivateKey.neutered().toBase58() !== userPub) {
114-
throw new Error('user private key does not match public key');
115-
}
127+
return verifyUserPublicKeyWithPrv(userKeychain, userPrv, disableNetworking);
128+
}
129+
130+
/**
131+
* Async version of verifyUserPublicKey with v2 encrypt/decrypt support.
132+
*/
133+
export async function verifyUserPublicKeyAsync(bitgo: BitGoBase, params: VerifyUserPublicKeyOptions): Promise<boolean> {
134+
const { userKeychain, txParams, disableNetworking } = params;
135+
if (!userKeychain) {
136+
throw new Error('user keychain is required');
116137
}
117138

118-
return true;
139+
let userPrv = userKeychain.prv;
140+
if (!userPrv && txParams.walletPassphrase) {
141+
userPrv = await decryptKeychainPrivateKeyAsync(bitgo, userKeychain, txParams.walletPassphrase);
142+
}
143+
144+
return verifyUserPublicKeyWithPrv(userKeychain, userPrv, disableNetworking);
119145
}

modules/abstract-utxo/test/unit/keychains.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('Audit Key', function () {
5454
encryptedPrv: btcBackupKey.key,
5555
walletPassphrase: 'foo',
5656
}),
57-
{ message: "failed to decrypt prv: ccm: tag doesn't match" }
57+
{ message: "failed to decrypt prv: password error - ccm: tag doesn't match" }
5858
);
5959
});
6060

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ describe('V2 Keychains', function () {
10761076
updateKeychainStub = sandbox.stub().returns({ result: sandbox.stub().resolves() });
10771077
sandbox.stub(BitGo.prototype, 'put').returns({ send: updateKeychainStub });
10781078
createKeypairStub = sandbox.stub(ofcKeychains, 'create').returns(mockNewKeypair);
1079-
encryptionStub = sandbox.stub(BitGo.prototype, 'encrypt').returns('newEncryptedPrv');
1079+
encryptionStub = sandbox.stub(BitGo.prototype, 'encryptAsync').resolves('newEncryptedPrv');
10801080
});
10811081

10821082
afterEach(function () {
@@ -1088,7 +1088,7 @@ describe('V2 Keychains', function () {
10881088

10891089
await ofcKeychains.rotateKeychain({ id: mockOfcKeychain.id, password: '1234' });
10901090
sinon.assert.called(createKeypairStub);
1091-
sinon.assert.calledWith(encryptionStub, { input: mockNewKeypair.prv, password: '1234' });
1091+
sinon.assert.calledWith(encryptionStub, { input: mockNewKeypair.prv, password: '1234', encryptionVersion: 2 });
10921092
sinon.assert.calledWith(updateKeychainStub, {
10931093
pub: mockNewKeypair.pub,
10941094
encryptedPrv: 'newEncryptedPrv',

modules/express/src/clientRoutes.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,15 @@ function handleLogin(req: ExpressApiRouteRequest<'express.v1.login' | 'express.l
101101
return req.bitgo.authenticate(body);
102102
}
103103

104-
function handleDecrypt(req: ExpressApiRouteRequest<'express.v1.decrypt' | 'express.decrypt', 'post'>) {
104+
async function handleDecrypt(req: ExpressApiRouteRequest<'express.v1.decrypt' | 'express.decrypt', 'post'>) {
105105
return {
106-
decrypted: req.bitgo.decrypt(req.body),
106+
decrypted: await req.bitgo.decryptAsync(req.body),
107107
};
108108
}
109109

110-
function handleEncrypt(req: ExpressApiRouteRequest<'express.v1.encrypt' | 'express.encrypt', 'post'>) {
110+
async function handleEncrypt(req: ExpressApiRouteRequest<'express.v1.encrypt' | 'express.encrypt', 'post'>) {
111111
return {
112-
encrypted: req.bitgo.encrypt(req.body),
112+
encrypted: await req.bitgo.encryptAsync(req.body),
113113
};
114114
}
115115

@@ -428,9 +428,9 @@ async function getEncryptedPrivKey(path: string, walletId: string): Promise<stri
428428
return encryptedPrivKey[walletId];
429429
}
430430

431-
function decryptPrivKey(bg: BitGo, encryptedPrivKey: string, walletPw: string): string {
431+
async function decryptPrivKey(bg: BitGo, encryptedPrivKey: string, walletPw: string): Promise<string> {
432432
try {
433-
return bg.decrypt({ password: walletPw, input: encryptedPrivKey });
433+
return await bg.decryptAsync({ password: walletPw, input: encryptedPrivKey });
434434
} catch (e) {
435435
throw new Error(`Error when trying to decrypt private key: ${e}`);
436436
}
@@ -453,7 +453,7 @@ export async function handleV2GenerateShareTSS(
453453

454454
const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
455455
const bitgo = req.bitgo;
456-
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
456+
const privKey = await decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
457457
const coin = bitgo.coin(req.decoded.coin);
458458
req.body.prv = privKey;
459459
req.body.walletPassphrase = walletPw;
@@ -574,7 +574,7 @@ export async function handleV2Sign(req: ExpressApiRouteRequest<'express.v2.coin.
574574

575575
const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
576576
const bitgo = req.bitgo;
577-
let privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
577+
let privKey = await decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
578578
const coin = bitgo.coin(req.decoded.coin);
579579
if (req.body.derivationSeed) {
580580
privKey = coin.deriveKeyWithSeed({ key: privKey, seed: req.body.derivationSeed }).key;
@@ -616,7 +616,7 @@ export async function handleV2OFCSignPayloadInExtSigningMode(
616616
const bitgo = req.bitgo;
617617

618618
// decrypt the encrypted private key using the wallet pwd
619-
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
619+
const privKey = await decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
620620

621621
// create a BaseCoin instance for 'ofc'
622622
const coin = bitgo.coin(ofcCoinName);

modules/express/src/fetchEncryptedPrivKeys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export async function fetchKeys(ids: WalletIds, token: string, accessToken?: str
7777

7878
if (keychain.encryptedPrv === undefined) {
7979
if (typeof credential === 'object') {
80-
const encryptedPrv = bg.encrypt({ password: credential.walletPassword, input: credential.secret });
80+
const encryptedPrv = await bg.encryptAsync({ password: credential.walletPassword, input: credential.secret });
8181
output[id] = encryptedPrv;
8282
} else {
8383
console.warn(`could not find a ${coinName} encrypted user private key for wallet id ${id}, skipping`);

modules/express/src/lightning/lightningSignerRoutes.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { LndSignerClient } from './lndSignerClient';
1717
import { ApiResponseError } from '../errors';
1818
import { ExpressApiRouteRequest } from '../typedRoutes/api';
1919

20-
type Decrypt = (params: { input: string; password: string }) => string;
20+
type Decrypt = (params: { input: string; password: string }) => Promise<string>;
2121

2222
async function createSignerMacaroon(
2323
lndSignerClient: LndSignerClient,
@@ -31,21 +31,21 @@ async function createSignerMacaroon(
3131
return macaroonBase64 ? Buffer.from(macaroonBase64, 'base64').toString('hex') : macaroon;
3232
}
3333

34-
function getSignerRootKey(
34+
async function getSignerRootKey(
3535
passphrase: string,
3636
userMainnetEncryptedPrv: string,
3737
network: utxolib.Network,
3838
decrypt: Decrypt
39-
) {
40-
const userMainnetPrv = decrypt({
39+
): Promise<string> {
40+
const userMainnetPrv = await decrypt({
4141
password: passphrase,
4242
input: userMainnetEncryptedPrv,
4343
});
4444
return utxolib.bitgo.keyutil.convertExtendedKeyNetwork(userMainnetPrv, utxolib.networks.bitcoin, network);
4545
}
4646

47-
function getMacaroonRootKey(passphrase: string, nodeAuthEncryptedPrv: string, decrypt: Decrypt) {
48-
const hdNode = utxolib.bip32.fromBase58(decrypt({ password: passphrase, input: nodeAuthEncryptedPrv }));
47+
async function getMacaroonRootKey(passphrase: string, nodeAuthEncryptedPrv: string, decrypt: Decrypt): Promise<string> {
48+
const hdNode = utxolib.bip32.fromBase58(await decrypt({ password: passphrase, input: nodeAuthEncryptedPrv }));
4949
if (!hdNode.privateKey) {
5050
throw new Error('nodeAuthEncryptedPrv is not a private key');
5151
}
@@ -82,8 +82,8 @@ export async function handleInitLightningWallet(
8282
throw new ApiResponseError('Missing encryptedPrv in node auth keychain', 400);
8383
}
8484
const network = getUtxolibNetwork(coin.getChain());
85-
const signerRootKey = getSignerRootKey(passphrase, userKeyEncryptedPrv, network, bitgo.decrypt);
86-
const macaroonRootKey = getMacaroonRootKey(passphrase, nodeAuthKeyEncryptedPrv, bitgo.decrypt);
85+
const signerRootKey = await getSignerRootKey(passphrase, userKeyEncryptedPrv, network, (p) => bitgo.decryptAsync(p));
86+
const macaroonRootKey = await getMacaroonRootKey(passphrase, nodeAuthKeyEncryptedPrv, (p) => bitgo.decryptAsync(p));
8787

8888
const { admin_macaroon: adminMacaroon } = await lndSignerClient.initWallet({
8989
// The passphrase at LND can only accommodate a base64 character set
@@ -139,7 +139,7 @@ export async function handleCreateSignerMacaroon(
139139
if (!encryptedSignerAdminMacaroon) {
140140
throw new ApiResponseError('Missing encryptedSignerAdminMacaroon in wallet', 400);
141141
}
142-
const adminMacaroon = bitgo.decrypt({
142+
const adminMacaroon = await bitgo.decryptAsync({
143143
password: passphrase,
144144
input: encryptedSignerAdminMacaroon,
145145
});

0 commit comments

Comments
 (0)