Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/abstract-lightning/src/wallet/lightning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export class LightningWallet implements ILightningWallet {
}
const signature = createMessageSignature(
t.exact(LightningPaymentRequest).encode(params),
this.wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
await this.wallet.bitgo.decryptAsync({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
);

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

const transactionRequestWithSignature = (await this.wallet.bitgo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,38 @@ import { getLightningAuthKeychains, ILightningWallet, LightningWallet } from './
import { createMessageSignature, deriveLightningServiceSharedSecret, isLightningCoinName } from '../lightning';
import * as t from 'io-ts';

function encryptWalletUpdateRequest(
async function encryptWalletUpdateRequest(
wallet: sdkcore.IWallet,
params: UpdateLightningWalletClientRequest,
userAuthKeyEncryptedPrv: string
): UpdateLightningWalletEncryptedRequest {
): Promise<UpdateLightningWalletEncryptedRequest> {
const coinName = wallet.coin() as 'tlnbtc' | 'lnbtc';

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

const userAuthXprv = wallet.bitgo.decrypt({
const userAuthXprv = await wallet.bitgo.decryptAsync({
password: params.passphrase,
input: userAuthKeyEncryptedPrv,
});

if (params.signerTlsKey) {
requestWithEncryption.encryptedSignerTlsKey = wallet.bitgo.encrypt({
requestWithEncryption.encryptedSignerTlsKey = await wallet.bitgo.encryptAsync({
password: params.passphrase,
input: params.signerTlsKey,
});
}

if (params.signerAdminMacaroon) {
requestWithEncryption.encryptedSignerAdminMacaroon = wallet.bitgo.encrypt({
requestWithEncryption.encryptedSignerAdminMacaroon = await wallet.bitgo.encryptAsync({
password: params.passphrase,
input: params.signerAdminMacaroon,
});
}

if (params.signerMacaroon) {
requestWithEncryption.encryptedSignerMacaroon = wallet.bitgo.encrypt({
requestWithEncryption.encryptedSignerMacaroon = await wallet.bitgo.encryptAsync({
password: deriveLightningServiceSharedSecret(coinName, userAuthXprv).toString('hex'),
input: params.signerMacaroon,
});
Expand Down Expand Up @@ -86,10 +86,10 @@ export async function updateWalletCoinSpecific(
if (!userAuthKeyEncryptedPrv) {
throw new Error(`user auth key is missing encrypted private key`);
}
const updateRequestWithEncryption = encryptWalletUpdateRequest(wallet, params, userAuthKeyEncryptedPrv);
const updateRequestWithEncryption = await encryptWalletUpdateRequest(wallet, params, userAuthKeyEncryptedPrv);
const signature = createMessageSignature(
updateRequestWithEncryption,
wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
await wallet.bitgo.decryptAsync({ password: params.passphrase, input: userAuthKeyEncryptedPrv })
);
const coinSpecific = {
[wallet.coin()]: {
Expand Down
9 changes: 8 additions & 1 deletion modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ import {
stringToBufferTryFormats,
} from './transaction/decode';
import { fetchKeychains, toBip32Triple, UtxoKeychain } from './keychains';
import { verifyKeySignature, verifyUserPublicKey } from './verifyKey';
import { verifyKeySignature, verifyUserPublicKey, verifyUserPublicKeyAsync } from './verifyKey';
import { getPolicyForEnv } from './descriptor/validatePolicy';
import { signTransaction } from './transaction/signTransaction';
import { isUtxoWalletData, UtxoWallet } from './wallet';
Expand Down Expand Up @@ -707,6 +707,13 @@ export abstract class AbstractUtxoCoin
return verifyUserPublicKey(this.bitgo, params);
}

/**
* @deprecated - use function verifyUserPublicKeyAsync instead
*/
protected async verifyUserPublicKeyAsync(params: VerifyUserPublicKeyOptions): Promise<boolean> {
return await verifyUserPublicKeyAsync(this.bitgo, params);
}

/**
* @deprecated - use function verifyKeySignature instead
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as utxolib from '@bitgo/utxo-lib';

import { AbstractUtxoCoin, VerifyTransactionOptions } from '../../abstractUtxoCoin';
import { Output, ParsedTransaction } from '../types';
import { verifyCustomChangeKeySignatures, verifyKeySignature, verifyUserPublicKey } from '../../verifyKey';
import { verifyCustomChangeKeySignatures, verifyKeySignature, verifyUserPublicKeyAsync } from '../../verifyKey';
import { getPsbtTxInputs, getTxInputs } from '../fetchInputs';

const debug = buildDebug('bitgo:abstract-utxo:verifyTransaction');
Expand Down Expand Up @@ -80,7 +80,7 @@ export async function verifyTransaction<TNumber extends bigint | number>(
let userPublicKeyVerified = false;
try {
// verify the user public key matches the private key - this will throw if there is no match
userPublicKeyVerified = verifyUserPublicKey(bitgo, {
userPublicKeyVerified = await verifyUserPublicKeyAsync(bitgo, {
userKeychain: keychains.user,
disableNetworking,
txParams,
Expand Down
68 changes: 47 additions & 21 deletions modules/abstract-utxo/src/verifyKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import assert from 'assert';

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

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

function verifyUserPublicKeyWithPrv(
userKeychain: NonNullable<VerifyUserPublicKeyOptions['userKeychain']>,
userPrv: string | undefined,
disableNetworking: boolean | undefined
): boolean {
const userPub = userKeychain.pub;

if (!userPrv) {
const errorMessage = 'user private key unavailable for verification';
if (disableNetworking) {
console.log(errorMessage);
return false;
} else {
throw new Error(errorMessage);
}
}

const userPrivateKey = BIP32.fromBase58(userPrv);
if (userPrivateKey.toBase58() === userPrivateKey.neutered().toBase58()) {
throw new Error('user private key is only public');
}
if (userPrivateKey.neutered().toBase58() !== userPub) {
throw new Error('user private key does not match public key');
}

return true;
}

/**
* Decrypt the wallet's user private key and verify that the claimed public key matches
* TODO: Deprecate in favor of verifyUserPublicKeyAsync once v2 encryption is default.
* Decrypt the wallet's user private key and verify that the claimed public key matches (sync, v1 only).
*/
export function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPublicKeyOptions): boolean {
const { userKeychain, txParams, disableNetworking } = params;
if (!userKeychain) {
throw new Error('user keychain is required');
}

const userPub = userKeychain.pub;

let userPrv = userKeychain.prv;
if (!userPrv && txParams.walletPassphrase) {
userPrv = decryptKeychainPrivateKey(bitgo, userKeychain, txParams.walletPassphrase);
}

if (!userPrv) {
const errorMessage = 'user private key unavailable for verification';
if (disableNetworking) {
console.log(errorMessage);
return false;
} else {
throw new Error(errorMessage);
}
} else {
const userPrivateKey = BIP32.fromBase58(userPrv);
if (userPrivateKey.toBase58() === userPrivateKey.neutered().toBase58()) {
throw new Error('user private key is only public');
}
if (userPrivateKey.neutered().toBase58() !== userPub) {
throw new Error('user private key does not match public key');
}
return verifyUserPublicKeyWithPrv(userKeychain, userPrv, disableNetworking);
}

/**
* Async version of verifyUserPublicKey with v2 encrypt/decrypt support.
*/
export async function verifyUserPublicKeyAsync(bitgo: BitGoBase, params: VerifyUserPublicKeyOptions): Promise<boolean> {
const { userKeychain, txParams, disableNetworking } = params;
if (!userKeychain) {
throw new Error('user keychain is required');
}

return true;
let userPrv = userKeychain.prv;
if (!userPrv && txParams.walletPassphrase) {
userPrv = await decryptKeychainPrivateKeyAsync(bitgo, userKeychain, txParams.walletPassphrase);
}

return verifyUserPublicKeyWithPrv(userKeychain, userPrv, disableNetworking);
}
2 changes: 1 addition & 1 deletion modules/abstract-utxo/test/unit/keychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('Audit Key', function () {
encryptedPrv: btcBackupKey.key,
walletPassphrase: 'foo',
}),
{ message: "failed to decrypt prv: ccm: tag doesn't match" }
{ message: "failed to decrypt prv: password error - ccm: tag doesn't match" }
);
});

Expand Down
4 changes: 2 additions & 2 deletions modules/bitgo/test/v2/unit/keychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ describe('V2 Keychains', function () {
updateKeychainStub = sandbox.stub().returns({ result: sandbox.stub().resolves() });
sandbox.stub(BitGo.prototype, 'put').returns({ send: updateKeychainStub });
createKeypairStub = sandbox.stub(ofcKeychains, 'create').returns(mockNewKeypair);
encryptionStub = sandbox.stub(BitGo.prototype, 'encrypt').returns('newEncryptedPrv');
encryptionStub = sandbox.stub(BitGo.prototype, 'encryptAsync').resolves('newEncryptedPrv');
});

afterEach(function () {
Expand All @@ -1088,7 +1088,7 @@ describe('V2 Keychains', function () {

await ofcKeychains.rotateKeychain({ id: mockOfcKeychain.id, password: '1234' });
sinon.assert.called(createKeypairStub);
sinon.assert.calledWith(encryptionStub, { input: mockNewKeypair.prv, password: '1234' });
sinon.assert.calledWith(encryptionStub, { input: mockNewKeypair.prv, password: '1234', encryptionVersion: 2 });
sinon.assert.calledWith(updateKeychainStub, {
pub: mockNewKeypair.pub,
encryptedPrv: 'newEncryptedPrv',
Expand Down
18 changes: 9 additions & 9 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,15 @@ function handleLogin(req: ExpressApiRouteRequest<'express.v1.login' | 'express.l
return req.bitgo.authenticate(body);
}

function handleDecrypt(req: ExpressApiRouteRequest<'express.v1.decrypt' | 'express.decrypt', 'post'>) {
async function handleDecrypt(req: ExpressApiRouteRequest<'express.v1.decrypt' | 'express.decrypt', 'post'>) {
return {
decrypted: req.bitgo.decrypt(req.body),
decrypted: await req.bitgo.decryptAsync(req.body),
};
}

function handleEncrypt(req: ExpressApiRouteRequest<'express.v1.encrypt' | 'express.encrypt', 'post'>) {
async function handleEncrypt(req: ExpressApiRouteRequest<'express.v1.encrypt' | 'express.encrypt', 'post'>) {
return {
encrypted: req.bitgo.encrypt(req.body),
encrypted: await req.bitgo.encryptAsync(req.body),
};
}

Expand Down Expand Up @@ -424,9 +424,9 @@ async function getEncryptedPrivKey(path: string, walletId: string): Promise<stri
return encryptedPrivKey[walletId];
}

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

const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
const bitgo = req.bitgo;
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const privKey = await decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.decoded.coin);
req.body.prv = privKey;
req.body.walletPassphrase = walletPw;
Expand Down Expand Up @@ -547,7 +547,7 @@ export async function handleV2Sign(req: ExpressApiRouteRequest<'express.v2.coin.

const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
const bitgo = req.bitgo;
let privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
let privKey = await decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.decoded.coin);
if (req.body.derivationSeed) {
privKey = coin.deriveKeyWithSeed({ key: privKey, seed: req.body.derivationSeed }).key;
Expand Down Expand Up @@ -589,7 +589,7 @@ export async function handleV2OFCSignPayloadInExtSigningMode(
const bitgo = req.bitgo;

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

// create a BaseCoin instance for 'ofc'
const coin = bitgo.coin(ofcCoinName);
Expand Down
2 changes: 1 addition & 1 deletion modules/express/src/fetchEncryptedPrivKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function fetchKeys(ids: WalletIds, token: string, accessToken?: str

if (keychain.encryptedPrv === undefined) {
if (typeof credential === 'object') {
const encryptedPrv = bg.encrypt({ password: credential.walletPassword, input: credential.secret });
const encryptedPrv = await bg.encryptAsync({ password: credential.walletPassword, input: credential.secret });
output[id] = encryptedPrv;
} else {
console.warn(`could not find a ${coinName} encrypted user private key for wallet id ${id}, skipping`);
Expand Down
18 changes: 9 additions & 9 deletions modules/express/src/lightning/lightningSignerRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { LndSignerClient } from './lndSignerClient';
import { ApiResponseError } from '../errors';
import { ExpressApiRouteRequest } from '../typedRoutes/api';

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

async function createSignerMacaroon(
lndSignerClient: LndSignerClient,
Expand All @@ -31,21 +31,21 @@ async function createSignerMacaroon(
return macaroonBase64 ? Buffer.from(macaroonBase64, 'base64').toString('hex') : macaroon;
}

function getSignerRootKey(
async function getSignerRootKey(
passphrase: string,
userMainnetEncryptedPrv: string,
network: utxolib.Network,
decrypt: Decrypt
) {
const userMainnetPrv = decrypt({
): Promise<string> {
const userMainnetPrv = await decrypt({
password: passphrase,
input: userMainnetEncryptedPrv,
});
return utxolib.bitgo.keyutil.convertExtendedKeyNetwork(userMainnetPrv, utxolib.networks.bitcoin, network);
}

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

const { admin_macaroon: adminMacaroon } = await lndSignerClient.initWallet({
// The passphrase at LND can only accommodate a base64 character set
Expand Down Expand Up @@ -139,7 +139,7 @@ export async function handleCreateSignerMacaroon(
if (!encryptedSignerAdminMacaroon) {
throw new ApiResponseError('Missing encryptedSignerAdminMacaroon in wallet', 400);
}
const adminMacaroon = bitgo.decrypt({
const adminMacaroon = await bitgo.decryptAsync({
password: passphrase,
input: encryptedSignerAdminMacaroon,
});
Expand Down
6 changes: 3 additions & 3 deletions modules/express/test/unit/clientRoutes/signPayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ describe('With the handler to sign an arbitrary payload in external signing mode
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;

await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
"Error when trying to decrypt private key: INVALID: json decode: this isn't json!"
'Error when trying to decrypt private key: Error: decrypt: ciphertext is not valid JSON'
);

readFileStub.restore();
Expand Down Expand Up @@ -387,7 +387,7 @@ describe('With the handler to sign an arbitrary payload in external signing mode
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;

await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
"Error when trying to decrypt private key: CORRUPT: password error - ccm: tag doesn't match"
'Error when trying to decrypt private key: Error: incorrect password'
);

readFileStub.restore();
Expand Down Expand Up @@ -415,7 +415,7 @@ describe('With the handler to sign an arbitrary payload in external signing mode
} as unknown as ExpressApiRouteRequest<'express.v2.ofc.extSignPayload', 'post'>;

await handleV2OFCSignPayloadInExtSigningMode(req).should.be.rejectedWith(
"Error when trying to decrypt private key: CORRUPT: password error - ccm: tag doesn't match"
'Error when trying to decrypt private key: Error: incorrect password'
);

readFileStub.restore();
Expand Down
Loading
Loading