From 9e1eb22e6bbad8b57b82d0edee97cffcb154a487 Mon Sep 17 00:00:00 2001 From: Bahaa Desoky Date: Mon, 1 Jun 2026 14:17:45 -0400 Subject: [PATCH] feat: migrate remaining sync decrypt/encrypt calls Ticket: WCN-284 --- modules/abstract-cosmos/src/cosmosCoin.ts | 5 +- .../src/abstractEthLikeNewCoins.ts | 3 +- .../src/wallet/lightning.ts | 4 +- .../src/wallet/selfCustodialLightning.ts | 16 ++-- modules/abstract-utxo/src/abstractUtxoCoin.ts | 9 +- .../src/recovery/backupKeyRecovery.ts | 8 +- .../src/recovery/crossChainRecovery.ts | 4 +- .../fixedScript/verifyTransaction.ts | 4 +- modules/abstract-utxo/src/verifyKey.ts | 68 +++++++++----- modules/bitgo/test/v2/unit/keychains.ts | 4 +- modules/express/src/clientRoutes.ts | 18 ++-- modules/express/src/fetchEncryptedPrivKeys.ts | 2 +- .../src/lightning/lightningSignerRoutes.ts | 18 ++-- .../test/unit/clientRoutes/signPayload.ts | 6 +- .../express/test/unit/typedRoutes/coinSign.ts | 25 ++--- .../express/test/unit/typedRoutes/decrypt.ts | 18 ++-- .../express/test/unit/typedRoutes/encrypt.ts | 24 ++--- .../unit/typedRoutes/expressWalletUpdate.ts | 12 +++ .../test/unit/typedRoutes/generateShareTSS.ts | 18 ++-- .../test/unit/typedRoutes/lightningPayment.ts | 30 ++++++ .../unit/typedRoutes/ofcExtSignPayload.ts | 14 +-- .../test/unit/typedRoutes/signerMacaroon.ts | 14 +++ modules/sdk-api/src/v1/keychains.ts | 13 ++- modules/sdk-api/src/v1/travelRule.ts | 8 +- modules/sdk-api/src/v1/wallet.ts | 27 ++++-- modules/sdk-api/src/v1/wallets.ts | 15 +-- modules/sdk-coin-eos/src/eos.ts | 4 +- modules/sdk-coin-icp/src/icp.ts | 3 +- modules/sdk-coin-rune/src/rune.ts | 3 +- modules/sdk-coin-stx/src/stx.ts | 4 +- modules/sdk-coin-tempo/src/tempo.ts | 3 +- modules/sdk-coin-trx/src/trx.ts | 6 +- modules/sdk-coin-vet/src/vet.ts | 6 +- modules/sdk-coin-xrp/src/xrp.ts | 4 +- modules/sdk-coin-xrp/test/resources/xrp.ts | 14 +-- .../sdk-core/src/bitgo/baseCoin/baseCoin.ts | 21 ++++- .../sdk-core/src/bitgo/baseCoin/iBaseCoin.ts | 1 + .../sdk-core/src/bitgo/keychain/keychains.ts | 4 +- .../src/bitgo/trading/tradingAccount.ts | 2 +- .../src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 94 +++++++++++++++---- .../src/bitgo/utils/tss/eddsa/eddsa.ts | 6 +- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 10 +- modules/sdk-core/src/bitgo/wallet/wallets.ts | 14 +-- .../test/unit/bitgo/trading/tradingAccount.ts | 10 +- .../test/unit/bitgo/utils/tss/baseTSSUtils.ts | 21 +++-- .../unit/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 15 ++- .../unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 66 +++++++++---- 47 files changed, 467 insertions(+), 231 deletions(-) diff --git a/modules/abstract-cosmos/src/cosmosCoin.ts b/modules/abstract-cosmos/src/cosmosCoin.ts index 7d6ac6541e..cd7ef8de6c 100644 --- a/modules/abstract-cosmos/src/cosmosCoin.ts +++ b/modules/abstract-cosmos/src/cosmosCoin.ts @@ -243,7 +243,7 @@ export class CosmosCoin extends BaseCoin { throw new Error('Invalid key format'); } - return await ECDSAUtils.getMpcV2RecoveryKeyShares(userKey, backupKey, walletPassphrase); + return await ECDSAUtils.getMpcV2RecoveryKeyShares(userKey, backupKey, walletPassphrase, this.bitgo); } /** @@ -491,7 +491,8 @@ export class CosmosCoin extends BaseCoin { const { userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userKey, backupKey, - params.walletPassphrase + params.walletPassphrase, + this.bitgo ); // baseAddress is not extracted const MPC = new Ecdsa(); diff --git a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts index 4cb72147f8..77e7e12124 100644 --- a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts +++ b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts @@ -2250,7 +2250,8 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { const { userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userPublicOrPrivateKeyShare, backupPrivateOrPublicKeyShare, - params.walletPassphrase + params.walletPassphrase, + this.bitgo ); const { gasLimit, gasPrice } = await this.getGasValues(params); diff --git a/modules/abstract-lightning/src/wallet/lightning.ts b/modules/abstract-lightning/src/wallet/lightning.ts index a0c0a28c8c..60b892bc4c 100644 --- a/modules/abstract-lightning/src/wallet/lightning.ts +++ b/modules/abstract-lightning/src/wallet/lightning.ts @@ -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 } = { @@ -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 diff --git a/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts b/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts index ecacfe7773..aed41c6620 100644 --- a/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts +++ b/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts @@ -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 { const coinName = wallet.coin() as 'tlnbtc' | 'lnbtc'; const requestWithEncryption: Partial = { ...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, }); @@ -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()]: { diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 3496d442e0..b0060a50f8 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -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'; @@ -706,6 +706,13 @@ export abstract class AbstractUtxoCoin return verifyUserPublicKey(this.bitgo, params); } + /** + * @deprecated - use function verifyUserPublicKeyAsync instead + */ + protected async verifyUserPublicKeyAsync(params: VerifyUserPublicKeyOptions): Promise { + return await verifyUserPublicKeyAsync(this.bitgo, params); + } + /** * @deprecated - use function verifyKeySignature instead */ diff --git a/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts b/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts index 8c902d1401..3cf4c50052 100644 --- a/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts +++ b/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts @@ -3,7 +3,7 @@ import { BitGoBase, ErrorNoInputToRecover, getKrsProvider, - getBip32Keys as getBip32KeysFromSdkCore, + getBip32KeysAsync as getBip32KeysFromSdkCore, isTriple, krsProviders, Triple, @@ -410,8 +410,8 @@ export function formatBackupKeyRecoveryResult( return txInfo; } -function getBip32Keys(bitgo: BitGoBase, params: RecoverParams): Triple { - const keys = getBip32KeysFromSdkCore(bitgo, params, { requireBitGoXpub: true }); +async function getBip32Keys(bitgo: BitGoBase, params: RecoverParams): Promise> { + const keys = await getBip32KeysFromSdkCore(bitgo, params, { requireBitGoXpub: true }); if (!isTriple(keys)) { throw new Error(`expected key triple`); } @@ -469,7 +469,7 @@ export async function backupKeyRecovery( } // check whether key material and password authenticate the users and return parent keys of all three keys of the wallet - const keys = getBip32Keys(bitgo, params); + const keys = await getBip32Keys(bitgo, params); const walletKeys = fixedScriptWallet.RootWalletKeys.from({ triple: keys, derivationPrefixes: [params.userKeyPath || 'm/0/0', 'm/0/0', 'm/0/0'], diff --git a/modules/abstract-utxo/src/recovery/crossChainRecovery.ts b/modules/abstract-utxo/src/recovery/crossChainRecovery.ts index eb27a2423d..dc6c5b3a3f 100644 --- a/modules/abstract-utxo/src/recovery/crossChainRecovery.ts +++ b/modules/abstract-utxo/src/recovery/crossChainRecovery.ts @@ -1,6 +1,6 @@ import { BIP32, CoinName, fixedScriptWallet, address as wasmAddress } from '@bitgo/wasm-utxo'; +import { decryptAsync } from '@bitgo/sdk-api'; import { BitGoBase, IWallet, Keychain, Triple, Wallet } from '@bitgo/sdk-core'; -import { decrypt } from '@bitgo/sdk-api'; import { AbstractUtxoCoin, TransactionInfo } from '../abstractUtxoCoin'; import { signAndVerifyPsbt } from '../transaction/fixedScript/signTransaction'; @@ -313,7 +313,7 @@ async function getPrv(xprv?: string, passphrase?: string, wallet?: IWallet | Wal encryptedPrv = (await (wallet as WalletV1).getEncryptedUserKeychain()).encryptedXprv; } - return getPrv(decrypt(passphrase, encryptedPrv)); + return getPrv(await decryptAsync(passphrase, encryptedPrv)); } /** diff --git a/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts index 9d313cf5b6..bd81eeda83 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts @@ -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'); @@ -80,7 +80,7 @@ export async function verifyTransaction( 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, diff --git a/modules/abstract-utxo/src/verifyKey.ts b/modules/abstract-utxo/src/verifyKey.ts index 15dbbc1391..6f9ce94b0e 100644 --- a/modules/abstract-utxo/src/verifyKey.ts +++ b/modules/abstract-utxo/src/verifyKey.ts @@ -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'; @@ -81,8 +81,37 @@ export function verifyCustomChangeKeySignatures return true; } +function verifyUserPublicKeyWithPrv( + userKeychain: NonNullable, + 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; @@ -90,30 +119,27 @@ export function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPublicKe 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 { + 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); } diff --git a/modules/bitgo/test/v2/unit/keychains.ts b/modules/bitgo/test/v2/unit/keychains.ts index 55c526c469..c2d95e03ae 100644 --- a/modules/bitgo/test/v2/unit/keychains.ts +++ b/modules/bitgo/test/v2/unit/keychains.ts @@ -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 () { @@ -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', diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index c57235067a..8d72635ffe 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -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), }; } @@ -424,9 +424,9 @@ async function getEncryptedPrivKey(path: string, walletId: string): Promise { 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}`); } @@ -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; @@ -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; @@ -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); diff --git a/modules/express/src/fetchEncryptedPrivKeys.ts b/modules/express/src/fetchEncryptedPrivKeys.ts index 2c2e08f4fc..00e22521c9 100644 --- a/modules/express/src/fetchEncryptedPrivKeys.ts +++ b/modules/express/src/fetchEncryptedPrivKeys.ts @@ -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`); diff --git a/modules/express/src/lightning/lightningSignerRoutes.ts b/modules/express/src/lightning/lightningSignerRoutes.ts index dc0a814745..f74dc4c273 100644 --- a/modules/express/src/lightning/lightningSignerRoutes.ts +++ b/modules/express/src/lightning/lightningSignerRoutes.ts @@ -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; async function createSignerMacaroon( lndSignerClient: LndSignerClient, @@ -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 { + 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 { + const hdNode = utxolib.bip32.fromBase58(await decrypt({ password: passphrase, input: nodeAuthEncryptedPrv })); if (!hdNode.privateKey) { throw new Error('nodeAuthEncryptedPrv is not a private key'); } @@ -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 @@ -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, }); diff --git a/modules/express/test/unit/clientRoutes/signPayload.ts b/modules/express/test/unit/clientRoutes/signPayload.ts index adcfee820e..009694d5bd 100644 --- a/modules/express/test/unit/clientRoutes/signPayload.ts +++ b/modules/express/test/unit/clientRoutes/signPayload.ts @@ -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(); @@ -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(); @@ -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(); diff --git a/modules/express/test/unit/typedRoutes/coinSign.ts b/modules/express/test/unit/typedRoutes/coinSign.ts index c79250bb77..565360e08b 100644 --- a/modules/express/test/unit/typedRoutes/coinSign.ts +++ b/modules/express/test/unit/typedRoutes/coinSign.ts @@ -104,7 +104,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { // Create mock BitGo with decrypt method const mockBitGo = { - decrypt: sinon.stub().returns(decryptedPrivKey), + decryptAsync: sinon.stub().resolves(decryptedPrivKey), coin: sinon.stub(), }; @@ -117,7 +117,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { // Stub BitGo constructor sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').callsFake(mockBitGo.decrypt); + sinon.stub(BitGo.prototype, 'decryptAsync').callsFake(mockBitGo.decryptAsync); // Make the request to Express const result = await agent @@ -139,8 +139,11 @@ describe('CoinSign codec tests (External Signer Mode)', function () { assert.strictEqual(fsReadFileStub.calledOnce, true); // Verify private key was decrypted - assert.strictEqual(mockBitGo.decrypt.calledOnce, true); - assert.strictEqual(mockBitGo.decrypt.calledWith({ password: walletPassphrase, input: encryptedPrivKey }), true); + assert.strictEqual(mockBitGo.decryptAsync.calledOnce, true); + assert.strictEqual( + mockBitGo.decryptAsync.calledWith({ password: walletPassphrase, input: encryptedPrivKey }), + true + ); // Verify signTransaction was called with decrypted key assert.strictEqual(mockCoin.signTransaction.calledOnce, true); @@ -181,7 +184,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { // Stub BitGo methods sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent @@ -238,7 +241,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent @@ -284,7 +287,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent @@ -328,7 +331,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent @@ -399,7 +402,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent @@ -572,7 +575,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { ); // Mock decrypt to throw error - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Invalid passphrase')); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error('Invalid passphrase')); // Make the request const result = await agent @@ -607,7 +610,7 @@ describe('CoinSign codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent diff --git a/modules/express/test/unit/typedRoutes/decrypt.ts b/modules/express/test/unit/typedRoutes/decrypt.ts index dca0c0fa1b..fae8c74815 100644 --- a/modules/express/test/unit/typedRoutes/decrypt.ts +++ b/modules/express/test/unit/typedRoutes/decrypt.ts @@ -170,7 +170,7 @@ describe('Decrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'decrypt').returns(mockDecryptResponse); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(mockDecryptResponse); const result = await agent .post('/api/v1/decrypt') @@ -192,7 +192,7 @@ describe('Decrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'decrypt').returns(mockDecryptResponse); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(mockDecryptResponse); const result = await agent .post('/api/v2/decrypt') @@ -215,7 +215,7 @@ describe('Decrypt codec tests', function () { }; const mockLongDecrypted = 'b'.repeat(500); - sinon.stub(BitGo.prototype, 'decrypt').returns(mockLongDecrypted); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(mockLongDecrypted); const result = await agent .post('/api/v1/decrypt') @@ -236,7 +236,7 @@ describe('Decrypt codec tests', function () { password: 'p@ssw0rd!#$%^&*()', }; - sinon.stub(BitGo.prototype, 'decrypt').returns(mockDecryptResponse); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(mockDecryptResponse); const result = await agent .post('/api/v2/decrypt') @@ -269,7 +269,7 @@ describe('Decrypt codec tests', function () { password: 'wrongPassword', }; - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error("password error - ccm: tag doesn't match")); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error("password error - ccm: tag doesn't match")); const result = await agent .post('/api/v1/decrypt') @@ -287,7 +287,7 @@ describe('Decrypt codec tests', function () { password: 'wrongPassword', }; - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error("password error - ccm: tag doesn't match")); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error("password error - ccm: tag doesn't match")); const result = await agent .post('/api/v2/decrypt') @@ -305,7 +305,7 @@ describe('Decrypt codec tests', function () { password: '', }; - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('cannot decrypt without password')); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error('cannot decrypt without password')); const result = await agent .post('/api/v1/decrypt') @@ -323,7 +323,7 @@ describe('Decrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Invalid encrypted input format')); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error('Invalid encrypted input format')); const result = await agent .post('/api/v2/decrypt') @@ -341,7 +341,7 @@ describe('Decrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Decrypt method not available')); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error('Decrypt method not available')); const result = await agent .post('/api/v1/decrypt') diff --git a/modules/express/test/unit/typedRoutes/encrypt.ts b/modules/express/test/unit/typedRoutes/encrypt.ts index 887ddf78b6..98d949ac6c 100644 --- a/modules/express/test/unit/typedRoutes/encrypt.ts +++ b/modules/express/test/unit/typedRoutes/encrypt.ts @@ -197,7 +197,7 @@ describe('Encrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'encrypt').returns(mockEncryptResponse); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockEncryptResponse); const result = await agent .post('/api/v1/encrypt') @@ -219,7 +219,7 @@ describe('Encrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'encrypt').returns(mockEncryptResponse); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockEncryptResponse); const result = await agent .post('/api/v2/encrypt') @@ -242,7 +242,7 @@ describe('Encrypt codec tests', function () { adata: 'additionalAuthData', }; - sinon.stub(BitGo.prototype, 'encrypt').returns(mockEncryptResponse); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockEncryptResponse); const result = await agent .post('/api/v1/encrypt') @@ -264,7 +264,7 @@ describe('Encrypt codec tests', function () { adata: 'additionalAuthData', }; - sinon.stub(BitGo.prototype, 'encrypt').returns(mockEncryptResponse); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockEncryptResponse); const result = await agent .post('/api/v2/encrypt') @@ -286,7 +286,7 @@ describe('Encrypt codec tests', function () { }; const mockLongEncrypted = 'b'.repeat(1500); - sinon.stub(BitGo.prototype, 'encrypt').returns(mockLongEncrypted); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockLongEncrypted); const result = await agent .post('/api/v1/encrypt') @@ -307,7 +307,7 @@ describe('Encrypt codec tests', function () { password: 'p@ssw0rd!#$%^&*()', }; - sinon.stub(BitGo.prototype, 'encrypt').returns(mockEncryptResponse); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockEncryptResponse); const result = await agent .post('/api/v2/encrypt') @@ -328,7 +328,7 @@ describe('Encrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'encrypt').returns(mockEncryptResponse); + sinon.stub(BitGo.prototype, 'encryptAsync').resolves(mockEncryptResponse); const result = await agent .post('/api/v1/encrypt') @@ -361,7 +361,7 @@ describe('Encrypt codec tests', function () { password: '', }; - sinon.stub(BitGo.prototype, 'encrypt').throws(new Error('cannot encrypt without password')); + sinon.stub(BitGo.prototype, 'encryptAsync').rejects(new Error('cannot encrypt without password')); const result = await agent .post('/api/v1/encrypt') @@ -379,7 +379,7 @@ describe('Encrypt codec tests', function () { password: '', }; - sinon.stub(BitGo.prototype, 'encrypt').throws(new Error('cannot encrypt without password')); + sinon.stub(BitGo.prototype, 'encryptAsync').rejects(new Error('cannot encrypt without password')); const result = await agent .post('/api/v2/encrypt') @@ -397,7 +397,7 @@ describe('Encrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'encrypt').throws(new Error('Invalid input format')); + sinon.stub(BitGo.prototype, 'encryptAsync').rejects(new Error('Invalid input format')); const result = await agent .post('/api/v1/encrypt') @@ -415,7 +415,7 @@ describe('Encrypt codec tests', function () { password: 'mySecurePassword123', }; - sinon.stub(BitGo.prototype, 'encrypt').throws(new Error('Encrypt method not available')); + sinon.stub(BitGo.prototype, 'encryptAsync').rejects(new Error('Encrypt method not available')); const result = await agent .post('/api/v2/encrypt') @@ -434,7 +434,7 @@ describe('Encrypt codec tests', function () { adata: 'invalidAdataFormat', }; - sinon.stub(BitGo.prototype, 'encrypt').throws(new Error('Invalid adata format')); + sinon.stub(BitGo.prototype, 'encryptAsync').rejects(new Error('Invalid adata format')); const result = await agent .post('/api/v1/encrypt') diff --git a/modules/express/test/unit/typedRoutes/expressWalletUpdate.ts b/modules/express/test/unit/typedRoutes/expressWalletUpdate.ts index a84ebc00ab..0301dff87f 100644 --- a/modules/express/test/unit/typedRoutes/expressWalletUpdate.ts +++ b/modules/express/test/unit/typedRoutes/expressWalletUpdate.ts @@ -114,7 +114,13 @@ describe('Express Wallet Update Typed Routes Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), encrypt: sinon.stub().callsFake(({ input }: { input: string }) => `encrypted_${input}`), + encryptAsync: sinon.stub().callsFake(async ({ input }: { input: string }) => `encrypted_${input}`), put: putStub, }, } as any; @@ -231,7 +237,13 @@ describe('Express Wallet Update Typed Routes Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), encrypt: sinon.stub().callsFake(({ input }: { input: string }) => `encrypted_${input}`), + encryptAsync: sinon.stub().callsFake(async ({ input }: { input: string }) => `encrypted_${input}`), put: putStub, }, } as any; diff --git a/modules/express/test/unit/typedRoutes/generateShareTSS.ts b/modules/express/test/unit/typedRoutes/generateShareTSS.ts index ff0c869ea4..d258971132 100644 --- a/modules/express/test/unit/typedRoutes/generateShareTSS.ts +++ b/modules/express/test/unit/typedRoutes/generateShareTSS.ts @@ -424,7 +424,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); sinon .stub(EddsaUtils.prototype, 'createCommitmentShareFromTxRequest') .callsFake(mockEddsaUtils.createCommitmentShareFromTxRequest); @@ -502,7 +502,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); sinon .stub(EddsaUtils.prototype, 'createRShareFromTxRequest') .callsFake(mockEddsaUtils.createRShareFromTxRequest); @@ -578,7 +578,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); sinon .stub(EddsaUtils.prototype, 'createGShareFromTxRequest') .callsFake(mockEddsaUtils.createGShareFromTxRequest); @@ -631,7 +631,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); sinon .stub(EcdsaUtils.prototype, 'getOfflineSignerPaillierModulus') .callsFake(mockEcdsaUtils.getOfflineSignerPaillierModulus); @@ -709,7 +709,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); sinon.stub(EcdsaUtils.prototype, 'createOfflineKShare').callsFake(mockEcdsaUtils.createOfflineKShare); // Call API via supertest @@ -878,7 +878,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { ); // Mock decrypt to throw error - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Invalid passphrase')); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error('Invalid passphrase')); // Make the request const result = await agent @@ -911,7 +911,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request const result = await agent @@ -944,7 +944,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request with invalid share type for EDDSA (K is ECDSA only) const result = await agent @@ -977,7 +977,7 @@ describe('GenerateShareTSS codec tests (External Signer Mode)', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); // Make the request with invalid share type const result = await agent diff --git a/modules/express/test/unit/typedRoutes/lightningPayment.ts b/modules/express/test/unit/typedRoutes/lightningPayment.ts index 4bd3462d7f..54d882a021 100644 --- a/modules/express/test/unit/typedRoutes/lightningPayment.ts +++ b/modules/express/test/unit/typedRoutes/lightningPayment.ts @@ -338,6 +338,11 @@ describe('Lightning Payment API Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), url: sinon.stub().returnsArg(0), post: postStub, get: sinon.stub().returns({ @@ -509,6 +514,11 @@ describe('Lightning Payment API Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), url: sinon.stub().returnsArg(0), post: postStub, get: sinon.stub().returns({ @@ -640,6 +650,11 @@ describe('Lightning Payment API Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), url: sinon.stub().returnsArg(0), post: postStub, get: sinon.stub().returns({ @@ -797,6 +812,11 @@ describe('Lightning Payment API Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), url: sinon.stub().returnsArg(0), post: sinon.stub().returns({ send: sinon.stub().returns({ @@ -876,6 +896,11 @@ describe('Lightning Payment API Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), url: sinon.stub().returnsArg(0), post: sinon.stub().returns({ send: sinon.stub().returns({ @@ -956,6 +981,11 @@ describe('Lightning Payment API Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), url: sinon.stub().returnsArg(0), post: sinon.stub().returns({ send: sinon.stub().returns({ diff --git a/modules/express/test/unit/typedRoutes/ofcExtSignPayload.ts b/modules/express/test/unit/typedRoutes/ofcExtSignPayload.ts index 43475c62cf..f197558378 100644 --- a/modules/express/test/unit/typedRoutes/ofcExtSignPayload.ts +++ b/modules/express/test/unit/typedRoutes/ofcExtSignPayload.ts @@ -82,7 +82,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); const result = await agent .post('/api/v2/ofc/signPayload') @@ -100,7 +100,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { // Verify external signing mode operations assert.strictEqual(fsReadFileStub.calledOnce, true); - const decryptStub = BitGo.prototype.decrypt as sinon.SinonStub; + const decryptStub = BitGo.prototype.decryptAsync as sinon.SinonStub; assert.strictEqual(decryptStub.calledOnce, true); assert.strictEqual(decryptStub.calledWith({ password: walletPassphrase, input: encryptedPrivKey }), true); assert.strictEqual(mockCoin.signMessage.calledOnce, true); @@ -126,7 +126,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); const result = await agent .post('/api/v2/ofc/signPayload') @@ -161,7 +161,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); const result = await agent .post('/api/v2/ofc/signPayload') @@ -174,7 +174,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { assert.ok(decodedResponse.signature); // Verify decrypt was called with env passphrase - const decryptStub = BitGo.prototype.decrypt as sinon.SinonStub; + const decryptStub = BitGo.prototype.decryptAsync as sinon.SinonStub; assert.strictEqual(decryptStub.calledWith({ password: walletPassphrase, input: encryptedPrivKey }), true); }); @@ -258,7 +258,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { }) ); - sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Invalid passphrase')); + sinon.stub(BitGo.prototype, 'decryptAsync').rejects(new Error('Invalid passphrase')); const result = await agent .post('/api/v2/ofc/signPayload') @@ -288,7 +288,7 @@ describe('OfcExtSignPayload External Signer Mode Tests', function () { }; sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); - sinon.stub(BitGo.prototype, 'decrypt').returns(decryptedPrivKey); + sinon.stub(BitGo.prototype, 'decryptAsync').resolves(decryptedPrivKey); const result = await agent .post('/api/v2/ofc/signPayload') diff --git a/modules/express/test/unit/typedRoutes/signerMacaroon.ts b/modules/express/test/unit/typedRoutes/signerMacaroon.ts index f93b819371..e51ae31db6 100644 --- a/modules/express/test/unit/typedRoutes/signerMacaroon.ts +++ b/modules/express/test/unit/typedRoutes/signerMacaroon.ts @@ -119,7 +119,13 @@ describe('Signer Macaroon Typed Routes Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), encrypt: sinon.stub().callsFake(({ input }: { input: string }) => `encrypted_${input}`), + encryptAsync: sinon.stub().callsFake(async ({ input }: { input: string }) => `encrypted_${input}`), put: putStub, }, baseCoin: { @@ -138,6 +144,7 @@ describe('Signer Macaroon Typed Routes Tests', function () { sinon.stub(BitGo.prototype, 'coin').returns(coinStub); sinon.stub(BitGo.prototype, 'decrypt').callsFake(walletStub.bitgo.decrypt); + sinon.stub(BitGo.prototype, 'decryptAsync').callsFake(walletStub.bitgo.decryptAsync); sinon.stub(BitGo.prototype, 'put').callsFake(putStub as any); const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/signermacaroon`).send({ @@ -234,7 +241,13 @@ describe('Signer Macaroon Typed Routes Tests', function () { .returns( 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ), + decryptAsync: sinon + .stub() + .resolves( + 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' + ), encrypt: sinon.stub().callsFake(({ input }: { input: string }) => `encrypted_${input}`), + encryptAsync: sinon.stub().callsFake(async ({ input }: { input: string }) => `encrypted_${input}`), put: putStub, }, baseCoin: { @@ -253,6 +266,7 @@ describe('Signer Macaroon Typed Routes Tests', function () { sinon.stub(BitGo.prototype, 'coin').returns(coinStub); sinon.stub(BitGo.prototype, 'decrypt').callsFake(walletStub.bitgo.decrypt); + sinon.stub(BitGo.prototype, 'decryptAsync').callsFake(walletStub.bitgo.decryptAsync); sinon.stub(BitGo.prototype, 'put').callsFake(putStub as any); const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/signermacaroon`).send({ diff --git a/modules/sdk-api/src/v1/keychains.ts b/modules/sdk-api/src/v1/keychains.ts index f537adcc42..718048e63d 100644 --- a/modules/sdk-api/src/v1/keychains.ts +++ b/modules/sdk-api/src/v1/keychains.ts @@ -188,16 +188,19 @@ Keychains.prototype.updatePassword = function (params, callback) { const newKeychains = {}; // @ts-expect-error - no implicit this const self = this; - _.forOwn((encrypted as any).keychains, function keychainsForOwn(oldEncryptedXprv, xpub) { + for (const [xpub, oldEncryptedXprv] of Object.entries((encrypted as any).keychains)) { try { - const decryptedPrv = self.bitgo.decrypt({ input: oldEncryptedXprv, password: params.oldPassword }); - const newEncryptedPrv = self.bitgo.encrypt({ input: decryptedPrv, password: params.newPassword }); + const decryptedPrv = await self.bitgo.decryptAsync({ + input: oldEncryptedXprv as string, + password: params.oldPassword, + }); + const newEncryptedPrv = await self.bitgo.encryptAsync({ input: decryptedPrv, password: params.newPassword }); newKeychains[xpub] = newEncryptedPrv; } catch (e) { // decrypting the keychain with the old password didn't work so we just keep it the way it is - newKeychains[xpub] = oldEncryptedXprv; + newKeychains[xpub] = oldEncryptedXprv as string; } - }); + } return { keychains: newKeychains, version: (encrypted as any).version }; } .call(this) diff --git a/modules/sdk-api/src/v1/travelRule.ts b/modules/sdk-api/src/v1/travelRule.ts index d49fb4a7a4..878558360d 100644 --- a/modules/sdk-api/src/v1/travelRule.ts +++ b/modules/sdk-api/src/v1/travelRule.ts @@ -341,20 +341,20 @@ TravelRule.prototype.sendMany = function (params, callback) { }) .value(); - return self.getRecipients({ txid: params.txid }).then(function (recipients) { + return self.getRecipients({ txid: params.txid }).then(async function (recipients) { // Build up data to post const sendParamsList: any[] = []; // don't regenerate a new random key for each recipient const fromKey = params.fromKey || makeRandomKey().toWIF(); - recipients.forEach(function (recipient) { + for (const recipient of recipients) { const outputIndex = recipient.outputIndex; const info = travelInfoMap[outputIndex]; if (info) { if (info.amount && info.amount !== recipient.amount) { throw new Error('amount did not match for output index ' + outputIndex); } - const sendParams = self.prepareParams({ + const sendParams = await self.prepareParamsAsync({ txid: params.txid, recipient: recipient, travelInfo: info, @@ -363,7 +363,7 @@ TravelRule.prototype.sendMany = function (params, callback) { }); sendParamsList.push(sendParams); } - }); + } const result: { matched: number; diff --git a/modules/sdk-api/src/v1/wallet.ts b/modules/sdk-api/src/v1/wallet.ts index b06f431377..6c98a22fc8 100644 --- a/modules/sdk-api/src/v1/wallet.ts +++ b/modules/sdk-api/src/v1/wallet.ts @@ -1751,7 +1751,10 @@ Wallet.prototype.createAndSignTransaction = function (params, callback) { const safeUserKey = _.get(this.wallet, 'private.userPrivKey'); if (_.isString(safeUserKey) && _.isString(params.walletPassphrase)) { // @ts-expect-error - no implicit this - transaction.signingKey = this.bitgo.decrypt({ password: params.walletPassphrase, input: safeUserKey }); + transaction.signingKey = await this.bitgo.decryptAsync({ + password: params.walletPassphrase, + input: safeUserKey, + }); } else { throw e; } @@ -1809,10 +1812,13 @@ Wallet.prototype.getAndPrepareSigningKeychain = function (params, callback) { // Caller provided a wallet passphrase if (params.walletPassphrase) { - return self.getEncryptedUserKeychain().then(function (keychain) { + return self.getEncryptedUserKeychain().then(async function (keychain) { // Decrypt the user key with a passphrase try { - keychain.xprv = self.bitgo.decrypt({ password: params.walletPassphrase, input: keychain.encryptedXprv }); + keychain.xprv = await self.bitgo.decryptAsync({ + password: params.walletPassphrase, + input: keychain.encryptedXprv, + }); } catch (e) { throw new Error('Unable to decrypt user keychain'); } @@ -2295,21 +2301,24 @@ Wallet.prototype.shareWallet = function (params, callback) { sharing = result; if (needsKeychain) { - return self.getEncryptedUserKeychain({}).then(function (keychain) { + return self.getEncryptedUserKeychain({}).then(async function (keychain) { // Decrypt the user key with a passphrase if (keychain.encryptedXprv) { if (!params.walletPassphrase) { throw new Error('Missing walletPassphrase argument'); } try { - keychain.xprv = self.bitgo.decrypt({ password: params.walletPassphrase, input: keychain.encryptedXprv }); + keychain.xprv = await self.bitgo.decryptAsync({ + password: params.walletPassphrase, + input: keychain.encryptedXprv, + }); } catch (e) { throw new Error('Unable to decrypt user keychain'); } const eckey = makeRandomKey(); const secret = getSharedSecret(eckey, Buffer.from(sharing.pubkey, 'hex')).toString('hex'); - const newEncryptedXprv = self.bitgo.encrypt({ password: secret, input: keychain.xprv }); + const newEncryptedXprv = await self.bitgo.encryptAsync({ password: secret, input: keychain.xprv }); sharedKeychain = { xpub: keychain.xpub, @@ -2599,10 +2608,10 @@ Wallet.prototype.recover = async function (params) { assert(parsedUnsignedTx.outs.length === 1); assert(_.sumBy(params.unspents, 'value') - _.sumBy(parsedUnsignedTx.outs, 'value') === Number(approximateTxFee)); - const plainUserKey = this.bitgo.decrypt({ password: params.walletPassphrase, input: params.userKey }); + const plainUserKey = await this.bitgo.decryptAsync({ password: params.walletPassphrase, input: params.userKey }); const halfSignedTx = await this.signTransaction({ ...unsignedTx, signingKey: plainUserKey }); - const plainBackupKey = this.bitgo.decrypt({ password: params.walletPassphrase, input: params.backupKey }); + const plainBackupKey = await this.bitgo.decryptAsync({ password: params.walletPassphrase, input: params.backupKey }); const fullSignedTx = await this.signTransaction({ ...unsignedTx, transactionHex: halfSignedTx.tx, @@ -2659,7 +2668,7 @@ Wallet.prototype.sweep = async function (params) { assert(parsedUnsignedTx.outs.length === 1); assert(_.sumBy(params.unspents, 'value') - _.sumBy(parsedUnsignedTx.outs, 'value') === Number(approximateTxFee)); - const plainUserKey = this.bitgo.decrypt({ password: params.walletPassphrase, input: params.userKey }); + const plainUserKey = await this.bitgo.decryptAsync({ password: params.walletPassphrase, input: params.userKey }); const halfSignedTx = await this.signTransaction({ ...unsignedTx, signingKey: plainUserKey }); return await this.sendTransaction({ diff --git a/modules/sdk-api/src/v1/wallets.ts b/modules/sdk-api/src/v1/wallets.ts index 2b4a9ddb12..ed106f3a92 100644 --- a/modules/sdk-api/src/v1/wallets.ts +++ b/modules/sdk-api/src/v1/wallets.ts @@ -247,13 +247,13 @@ Wallets.prototype.acceptShare = function (params, callback) { throw new Error('userPassword param must be provided to decrypt shared key'); } - return self.bitgo.getECDHKeychain().then(function (sharingKeychain) { + return self.bitgo.getECDHKeychain().then(async function (sharingKeychain) { if (!sharingKeychain.encryptedXprv) { throw new Error('EncryptedXprv was not found on sharing keychain'); } // Now we have the sharing keychain, we can work out the secret used for sharing the wallet with us - sharingKeychain.xprv = self.bitgo.decrypt({ + sharingKeychain.xprv = await self.bitgo.decryptAsync({ password: params.userPassword, input: sharingKeychain.encryptedXprv, }); @@ -265,14 +265,17 @@ Wallets.prototype.acceptShare = function (params, callback) { ).toString('hex'); // Yes! We got the secret successfully here, now decrypt the shared wallet xprv - const decryptedSharedWalletXprv = self.bitgo.decrypt({ + const decryptedSharedWalletXprv = await self.bitgo.decryptAsync({ password: secret, input: walletShare.keychain.encryptedXprv, }); // We will now re-encrypt the wallet with our own password const newWalletPassphrase = params.newWalletPassphrase || params.userPassword; - encryptedXprv = self.bitgo.encrypt({ password: newWalletPassphrase, input: decryptedSharedWalletXprv }); + encryptedXprv = await self.bitgo.encryptAsync({ + password: newWalletPassphrase, + input: decryptedSharedWalletXprv, + }); // Carry on to the next block where we will post the acceptance of the share with the encrypted xprv return walletShare; @@ -337,7 +340,7 @@ Wallets.prototype.createKey = function (params) { // ** BE SURE TO BACK UP THE ENCRYPTED USER AND BACKUP KEYCHAINS!** // // } -Wallets.prototype.createWalletWithKeychains = function (params, callback) { +Wallets.prototype.createWalletWithKeychains = async function (params, callback) { params = params || {}; common.validateParams( params, @@ -350,7 +353,7 @@ Wallets.prototype.createWalletWithKeychains = function (params, callback) { // Create the user and backup key. const userKeychain = this.bitgo.keychains().create(); - userKeychain.encryptedXprv = this.bitgo.encrypt({ password: params.passphrase, input: userKeychain.xprv }); + userKeychain.encryptedXprv = await this.bitgo.encryptAsync({ password: params.passphrase, input: userKeychain.xprv }); const keychainData: any = { xpub: userKeychain.xpub, diff --git a/modules/sdk-coin-eos/src/eos.ts b/modules/sdk-coin-eos/src/eos.ts index d364b727ef..33d8a459c6 100644 --- a/modules/sdk-coin-eos/src/eos.ts +++ b/modules/sdk-coin-eos/src/eos.ts @@ -20,7 +20,7 @@ import { BitGoBase, checkKrsProvider, Environments, - getBip32Keys, + getBip32KeysAsync, getIsKrsRecovery, getIsUnsignedSweep, HalfSignedAccountTransaction as BaseHalfSignedTransaction, @@ -905,7 +905,7 @@ export class Eos extends BaseCoin { throw new Error('Invalid destination address!'); } - const keys = getBip32Keys(this.bitgo, params, { requireBitGoXpub: false }); + const keys = await getBip32KeysAsync(this.bitgo, params, { requireBitGoXpub: false }); const rootAddressDetails = this.getAddressDetails(params.rootAddress); const account = await this.getAccountFromNode({ address: rootAddressDetails.address }); diff --git a/modules/sdk-coin-icp/src/icp.ts b/modules/sdk-coin-icp/src/icp.ts index bbf403d0cf..75b8303d62 100644 --- a/modules/sdk-coin-icp/src/icp.ts +++ b/modules/sdk-coin-icp/src/icp.ts @@ -409,7 +409,8 @@ export class Icp extends BaseCoin { ({ userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userKey, backupKey, - params.walletPassphrase + params.walletPassphrase, + this.bitgo )); publicKey = MPC.deriveUnhardened(commonKeyChain, ROOT_PATH).slice(0, 66); } else { diff --git a/modules/sdk-coin-rune/src/rune.ts b/modules/sdk-coin-rune/src/rune.ts index 682d32b84d..fcd2fb0aa0 100644 --- a/modules/sdk-coin-rune/src/rune.ts +++ b/modules/sdk-coin-rune/src/rune.ts @@ -167,7 +167,8 @@ export class Rune extends CosmosCoin { const { userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userKey, backupKey, - params.walletPassphrase + params.walletPassphrase, + this.bitgo ); // baseAddress is not extracted // Step 3: Instantiate the ECDSA signer and fetch the address details const MPC = new Ecdsa(); diff --git a/modules/sdk-coin-stx/src/stx.ts b/modules/sdk-coin-stx/src/stx.ts index b9e1c537b0..622050447f 100644 --- a/modules/sdk-coin-stx/src/stx.ts +++ b/modules/sdk-coin-stx/src/stx.ts @@ -4,7 +4,7 @@ import { BaseTransaction, BitGoBase, Environments, - getBip32Keys, + getBip32KeysAsync, getIsUnsignedSweep, KeyPair, MethodNotImplementedError, @@ -701,7 +701,7 @@ export class Stx extends BaseCoin { } } const isUnsignedSweep = getIsUnsignedSweep(params); - const keys = getBip32Keys(this.bitgo, params, { requireBitGoXpub: true }); + const keys = await getBip32KeysAsync(this.bitgo, params, { requireBitGoXpub: true }); const rootAddressDetails = getAddressDetails(params.rootAddress); const [accountBalanceData, accountNonceData] = await Promise.all([ this.getNativeStxBalanceFromNode({ address: rootAddressDetails.address }), diff --git a/modules/sdk-coin-tempo/src/tempo.ts b/modules/sdk-coin-tempo/src/tempo.ts index 685163a93b..f7fddb897a 100644 --- a/modules/sdk-coin-tempo/src/tempo.ts +++ b/modules/sdk-coin-tempo/src/tempo.ts @@ -492,7 +492,8 @@ export class Tempo extends AbstractEthLikeNewCoins { const { userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userKey, backupKey, - params.walletPassphrase + params.walletPassphrase, + this.bitgo ); const MPC = new Ecdsa(); diff --git a/modules/sdk-coin-trx/src/trx.ts b/modules/sdk-coin-trx/src/trx.ts index e68383c1a7..e3bc919b3a 100644 --- a/modules/sdk-coin-trx/src/trx.ts +++ b/modules/sdk-coin-trx/src/trx.ts @@ -10,7 +10,7 @@ import { BaseCoin, BitGoBase, common, - getBip32Keys, + getBip32KeysAsync, getIsKrsRecovery, getIsUnsignedSweep, KeyPair, @@ -848,7 +848,7 @@ export class Trx extends BaseCoin { } // get our user, backup keys - const keys = getBip32Keys(this.bitgo, params, { requireBitGoXpub: false }); + const keys = await getBip32KeysAsync(this.bitgo, params, { requireBitGoXpub: false }); // we need to decode our bitgoKey to a base58 address const bitgoHexAddr = this.pubToHexAddress(this.xpubToUncompressedPub(params.bitgoKey)); @@ -1032,7 +1032,7 @@ export class Trx extends BaseCoin { ); } - const keys = getBip32Keys(this.bitgo, params, { requireBitGoXpub: false }); + const keys = await getBip32KeysAsync(this.bitgo, params, { requireBitGoXpub: false }); const baseAddrHex = this.pubToHexAddress(this.xpubToUncompressedPub(params.bitgoKey)); const txnsBatch: RecoveryTransaction[] = []; diff --git a/modules/sdk-coin-vet/src/vet.ts b/modules/sdk-coin-vet/src/vet.ts index 245b12e967..a7c1a71312 100644 --- a/modules/sdk-coin-vet/src/vet.ts +++ b/modules/sdk-coin-vet/src/vet.ts @@ -415,7 +415,8 @@ export class Vet extends BaseCoin { ({ userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userKey, backupKey, - params.walletPassphrase + params.walletPassphrase, + this.bitgo )); publicKey = MPC.deriveUnhardened(commonKeyChain, 'm/0').slice(0, 66); } @@ -763,7 +764,8 @@ export class Vet extends BaseCoin { ({ userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares( userKey, backupKey, - params.walletPassphrase + params.walletPassphrase, + this.bitgo )); publicKey = MPC.deriveUnhardened(commonKeyChain, 'm/0').slice(0, 66); } diff --git a/modules/sdk-coin-xrp/src/xrp.ts b/modules/sdk-coin-xrp/src/xrp.ts index 5eab9194a4..e88cf802d5 100644 --- a/modules/sdk-coin-xrp/src/xrp.ts +++ b/modules/sdk-coin-xrp/src/xrp.ts @@ -11,7 +11,7 @@ import { BaseCoin, BitGoBase, checkKrsProvider, - getBip32Keys, + getBip32KeysAsync, InvalidAddressError, KeyPair, MethodNotImplementedError, @@ -552,7 +552,7 @@ export class Xrp extends BaseCoin { throw new Error('Invalid destination address!'); } - const keys = getBip32Keys(this.bitgo, params, { requireBitGoXpub: false }); + const keys = await getBip32KeysAsync(this.bitgo, params, { requireBitGoXpub: false }); const { addressDetails, feeDetails, serverDetails, accountLines } = await promiseProps({ addressDetails: this.bitgo.post(rippledUrl).send(accountInfoParams), diff --git a/modules/sdk-coin-xrp/test/resources/xrp.ts b/modules/sdk-coin-xrp/test/resources/xrp.ts index 821bb766a8..bbe1545457 100644 --- a/modules/sdk-coin-xrp/test/resources/xrp.ts +++ b/modules/sdk-coin-xrp/test/resources/xrp.ts @@ -81,17 +81,17 @@ export const TEST_TOKEN_TRANSFER_TX = { export const keys = { userKey: - '{"iv":"ZN/gBap8QYIpjbbkZCDY8g==","v":1,"iter":10000,"ks":256,"ts":64,"mode"\n' + - ':"ccm","adata":"","cipher":"aes","salt":"Egt/IC14ugw=","ct":"eRiONKtGrlEX8e\n' + - '5s5EAon7MadWZxyQWJMFJp16rimEd/2LWyGObo/d6hdJWUSZE1lDzpYV9x/Qg3vKz8Wy4ee8R0h\n' + + '{"iv":"ZN/gBap8QYIpjbbkZCDY8g==","v":1,"iter":10000,"ks":256,"ts":64,"mode"' + + ':"ccm","adata":"","cipher":"aes","salt":"Egt/IC14ugw=","ct":"eRiONKtGrlEX8e' + + '5s5EAon7MadWZxyQWJMFJp16rimEd/2LWyGObo/d6hdJWUSZE1lDzpYV9x/Qg3vKz8Wy4ee8R0h' + '8J+Ddo/Q8dR/yDNImcNGBclBMrh9c8cowuzRMnbMlbrLc949tN3d3A1jXOu3Rr5Wt4h1ag="}', backupKey: - '{"iv":"D5SCw343R+l9qbP3TrXzlg==","v":1,"iter":10000,"ks":256,"ts":64,"mode"\n' + - ':"ccm","adata":"","cipher":"aes","salt":"yug6WjWDjCA=","ct":"++m1LyBWw9emM2\n' + - 'J1P85+T2VJEFPXFjshWssVBaHuccsiD0MsYsFX5d+hVfDrWV2aDOJAuOdtoCo+R3LrG2JST80ru\n' + + '{"iv":"D5SCw343R+l9qbP3TrXzlg==","v":1,"iter":10000,"ks":256,"ts":64,"mode"' + + ':"ccm","adata":"","cipher":"aes","salt":"yug6WjWDjCA=","ct":"++m1LyBWw9emM2' + + 'J1P85+T2VJEFPXFjshWssVBaHuccsiD0MsYsFX5d+hVfDrWV2aDOJAuOdtoCo+R3LrG2JST80ru' + '37Y383IvRlB3A85MSo/poMtN1JyzorwF6Cfiz26bY3OKxywaeWJvr9SEDJxTDTx8HH9GsE="}', bitgoKey: - 'xpub661MyMwAqRbcGBXTTnaLrqur67ZHc9BA9X3GdAx6Kj8HVyg32TvktXv8DPN13QvnWSnrfC8\n' + + 'xpub661MyMwAqRbcGBXTTnaLrqur67ZHc9BA9X3GdAx6Kj8HVyg32TvktXv8DPN13QvnWSnrfC8' + 'KFWvaUfR4kfwyikf6TuyJ3Ei8HGs7vxfdyia', rootAddress: 'rNTfZB1h4TDdF9QXw37nbWk9euZmRby4qn', }; diff --git a/modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts b/modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts index 6b393129ee..2b25c88fb0 100644 --- a/modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts +++ b/modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts @@ -5,7 +5,6 @@ import * as crypto from 'crypto'; import { Hash } from 'crypto'; import * as utxolib from '@bitgo/utxo-lib'; import { bip32 } from '@bitgo/utxo-lib'; -import * as sjcl from '@bitgo/sjcl'; import { BigNumber } from 'bignumber.js'; import { BaseCoin as StaticsBaseCoin, CoinFeature } from '@bitgo/statics'; @@ -645,10 +644,28 @@ export abstract class BaseCoin implements IBaseCoin { /** @inheritDoc */ assertIsValidKey(params: AuditKeyParams): void { + if (!params.encryptedPrv) { + throw new Error('encryptedPrv is required'); + } + let decryptedKey: string; + + try { + decryptedKey = this.bitgo.decrypt({ password: params.walletPassphrase, input: params.encryptedPrv }); + } catch (e) { + throw new Error(`failed to decrypt prv: ${e.message}`); + } + this.auditDecryptedKey({ ...params, prv: decryptedKey }); + } + + /** @inheritDoc */ + async assertIsValidKeyAsync(params: AuditKeyParams): Promise { + if (!params.encryptedPrv) { + throw new Error('encryptedPrv is required'); + } let decryptedKey: string; try { - decryptedKey = sjcl.decrypt(params.walletPassphrase, params.encryptedPrv); + decryptedKey = await this.bitgo.decryptAsync({ password: params.walletPassphrase, input: params.encryptedPrv }); } catch (e) { throw new Error(`failed to decrypt prv: ${e.message}`); } diff --git a/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts b/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts index 80cf6d3236..510d0d8abd 100644 --- a/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts +++ b/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts @@ -669,5 +669,6 @@ export interface IBaseCoin { * @param {string} params.multiSigType - The type of multisig (e.g. 'onchain' or 'tss') */ assertIsValidKey({ publicKey, encryptedPrv, walletPassphrase, multiSigType }: AuditKeyParams): void; + assertIsValidKeyAsync(params: AuditKeyParams): Promise; requiresWalletInitializationTransaction(): boolean; } diff --git a/modules/sdk-core/src/bitgo/keychain/keychains.ts b/modules/sdk-core/src/bitgo/keychain/keychains.ts index 44e3f02dc6..a2f13b9b6c 100644 --- a/modules/sdk-core/src/bitgo/keychain/keychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/keychains.ts @@ -572,7 +572,7 @@ export class Keychains implements IKeychains { const newKeychain = keychains.create(); const originalPasscodeEncryptionCode = generateRandomPassword(5); - const encryptedPrv = this.bitgo.encrypt({ + const encryptedPrv = await this.bitgo.encryptAsync({ password: walletPassphrase, input: newKeychain.prv, }); @@ -614,7 +614,7 @@ export class Keychains implements IKeychains { throw Error('Expected a public key to be generated'); } pub = keyPub; - encryptedPrv = this.bitgo.encrypt({ input: keyPrv, password: params.password }); + encryptedPrv = await this.bitgo.encryptAsync({ input: keyPrv, password: params.password }); } return this.bitgo diff --git a/modules/sdk-core/src/bitgo/trading/tradingAccount.ts b/modules/sdk-core/src/bitgo/trading/tradingAccount.ts index e58745cd6c..8c5942681c 100644 --- a/modules/sdk-core/src/bitgo/trading/tradingAccount.ts +++ b/modules/sdk-core/src/bitgo/trading/tradingAccount.ts @@ -92,7 +92,7 @@ export class TradingAccount implements ITradingAccount { if (!key.encryptedPrv) { throw new Error('Expected encryptedPrv to be present on user keychain.'); } - prv = this.wallet.bitgo.decrypt({ + prv = await this.wallet.bitgo.decryptAsync({ input: key.encryptedPrv, password: params.walletPassphrase, }); diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts index 006cb8f775..c6b9057825 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -54,6 +54,7 @@ import { BaseEcdsaUtils } from './base'; import { EcdsaMPCv2KeyGenSendFn, KeyGenSenderForEnterprise } from './ecdsaMPCv2KeyGenSender'; import { envRequiresBitgoPubGpgKeyConfig, isBitgoMpcPubKey } from '../../../tss/bitgoPubKeys'; import { InvalidTransactionError } from '../../../errors'; +import { BitGoBase } from '../../../bitgoBase'; export class EcdsaMPCv2Utils extends BaseEcdsaUtils { private static readonly DKLS23_SIGNING_USER_GPG_KEY = 'DKLS23_SIGNING_USER_GPG_KEY'; @@ -396,7 +397,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { btoa(String.fromCharCode.apply(null, Array.from(new Uint8Array(reducedPrivateMaterial)))) ); } else { - encryptedPrv = this.bitgo.encrypt({ + encryptedPrv = await this.bitgo.encryptAsync({ input: privateMaterialBase64, password: passphrase, }); @@ -404,7 +405,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { // scalar s_i) with the wallet passphrase. The result is stored as reducedEncryptedPrv // on the key card QR code and represents a second copy of private key material // beyond the server-stored encryptedPrv. - reducedEncryptedPrv = this.bitgo.encrypt({ + reducedEncryptedPrv = await this.bitgo.encryptAsync({ // Buffer.toString('base64') can not be used here as it does not work on the browser. // The browser deals with a Buffer as Uint8Array, therefore in the browser .toString('base64') just creates a comma seperated string of the array values. input: btoa(String.fromCharCode.apply(null, Array.from(new Uint8Array(reducedPrivateMaterial)))), @@ -1209,12 +1210,12 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { } } - const encryptedRound1Session = this.bitgo.encrypt({ + const encryptedRound1Session = await this.bitgo.encryptAsync({ input: sessionData, password: walletPassphrase, adata: `${EcdsaMPCv2Utils.DKLS23_SIGNING_ROUND1_STATE}:${adata}`, }); - const encryptedUserGpgPrvKey = this.bitgo.encrypt({ + const encryptedUserGpgPrvKey = await this.bitgo.encryptAsync({ input: userGpgKey.privateKey, password: walletPassphrase, adata: `${EcdsaMPCv2Utils.DKLS23_SIGNING_USER_GPG_KEY}:${adata}`, @@ -1310,7 +1311,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { } } - const encryptedRound2Session = this.bitgo.encrypt({ + const encryptedRound2Session = await this.bitgo.encryptAsync({ input: sessionData, password: walletPassphrase, adata: `${EcdsaMPCv2Utils.DKLS23_SIGNING_ROUND2_STATE}:${adata}`, @@ -1417,22 +1418,59 @@ export function isGG18SigningMaterial(keyShare: string, walletPassphrase: string } } +/** + * Async version of {@link isGG18SigningMaterial} with v1/v2 auto-detect decrypt via `bitgo.decryptAsync`. + */ +export async function isGG18SigningMaterialAsync( + keyShare: string, + walletPassphrase: string | undefined, + bitgo: BitGoBase +): Promise { + try { + const prv = await bitgo.decryptAsync({ password: walletPassphrase, input: keyShare }); + const signingMaterial = JSON.parse(prv); + return ( + signingMaterial.pShare && + signingMaterial.bitgoNShare && + (signingMaterial.userNShare || signingMaterial.backupNShare) + ); + } catch (error) { + return false; + } +} + +async function decryptWalletKeyAsync( + walletPassphrase: string | undefined, + encryptedKey: string, + bitgo: BitGoBase +): Promise { + return bitgo.decryptAsync({ password: walletPassphrase, input: encryptedKey }); +} + /** * Get the MPC v2 recovery key shares from the provided user and backup key shares. * @param encryptedUserKey encrypted gg18 or MPCv2 user key * @param encryptedBackupKey encrypted gg18 or MPCv2 backup key * @param walletPassphrase password for user and backup key + * @param bitgo BitGo instance for v1/v2 auto-detect decrypt * @returns MPC v2 recovery key shares */ export async function getMpcV2RecoveryKeyShares( encryptedUserKey: string, encryptedBackupKey: string, - walletPassphrase?: string + walletPassphrase?: string, + bitgo?: BitGoBase ): Promise<{ userKeyShare: Buffer; backupKeyShare: Buffer; commonKeyChain: string; }> { + if (bitgo) { + if (await isGG18SigningMaterialAsync(encryptedUserKey, walletPassphrase, bitgo)) { + return getMpcV2RecoveryKeySharesFromGG18(encryptedUserKey, encryptedBackupKey, walletPassphrase, bitgo); + } + return getMpcV2RecoveryKeySharesFromReducedKey(encryptedUserKey, encryptedBackupKey, walletPassphrase, bitgo); + } if (isGG18SigningMaterial(encryptedUserKey, walletPassphrase)) { return getMpcV2RecoveryKeySharesFromGG18(encryptedUserKey, encryptedBackupKey, walletPassphrase); } @@ -1494,16 +1532,18 @@ export async function signRecoveryMpcV2( async function getMpcV2RecoveryKeySharesFromGG18( encryptedGG18UserKey: string, encryptedGG18BackupKey: string, - walletPassphrase?: string + walletPassphrase?: string, + bitgo?: BitGoBase ): Promise<{ userKeyShare: Buffer; backupKeyShare: Buffer; commonKeyChain: string; }> { - const [userKeyCombined, backupKeyCombined] = getKeyCombinedFromTssKeyShares( + const [userKeyCombined, backupKeyCombined] = await getKeyCombinedFromTssKeyShares( encryptedGG18UserKey, encryptedGG18BackupKey, - walletPassphrase + walletPassphrase, + bitgo ); const retrofitDataA: DklsTypes.RetrofitData = { xShare: userKeyCombined.xShare, @@ -1535,14 +1575,28 @@ async function getMpcV2RecoveryKeySharesFromGG18( async function getMpcV2RecoveryKeySharesFromReducedKey( encryptedMPCv2UserKey: string, encryptedMPCv2BackupKey: string, - walletPassphrase?: string + walletPassphrase?: string, + bitgo?: BitGoBase ): Promise<{ userKeyShare: Buffer; backupKeyShare: Buffer; commonKeyChain: string; }> { - const userCompressedPrv = Buffer.from(sjcl.decrypt(walletPassphrase, encryptedMPCv2UserKey), 'base64'); - const bakcupCompressedPrv = Buffer.from(sjcl.decrypt(walletPassphrase, encryptedMPCv2BackupKey), 'base64'); + let userCompressedPrv: Buffer; + let bakcupCompressedPrv: Buffer; + if (bitgo) { + userCompressedPrv = Buffer.from( + await decryptWalletKeyAsync(walletPassphrase, encryptedMPCv2UserKey, bitgo), + 'base64' + ); + bakcupCompressedPrv = Buffer.from( + await decryptWalletKeyAsync(walletPassphrase, encryptedMPCv2BackupKey, bitgo), + 'base64' + ); + } else { + userCompressedPrv = Buffer.from(sjcl.decrypt(walletPassphrase, encryptedMPCv2UserKey), 'base64'); + bakcupCompressedPrv = Buffer.from(sjcl.decrypt(walletPassphrase, encryptedMPCv2BackupKey), 'base64'); + } const userPrvJSON: DklsTypes.ReducedKeyShare = DklsTypes.getDecodedReducedKeyShare(userCompressedPrv); const backupPrvJSON: DklsTypes.ReducedKeyShare = DklsTypes.getDecodedReducedKeyShare(bakcupCompressedPrv); @@ -1576,16 +1630,22 @@ async function getMpcV2RecoveryKeySharesFromReducedKey( * @param walletPassphrase wallet passphrase * @returns key shares */ -function getKeyCombinedFromTssKeyShares( +async function getKeyCombinedFromTssKeyShares( encryptedGG18UserKey: string, encryptedGG18BackupKey: string, - walletPassphrase?: string -): [ECDSAMethodTypes.KeyCombined, ECDSAMethodTypes.KeyCombined] { + walletPassphrase?: string, + bitgo?: BitGoBase +): Promise<[ECDSAMethodTypes.KeyCombined, ECDSAMethodTypes.KeyCombined]> { let backupPrv; let userPrv; try { - backupPrv = sjcl.decrypt(walletPassphrase, encryptedGG18BackupKey); - userPrv = sjcl.decrypt(walletPassphrase, encryptedGG18UserKey); + if (bitgo) { + backupPrv = await decryptWalletKeyAsync(walletPassphrase, encryptedGG18BackupKey, bitgo); + userPrv = await decryptWalletKeyAsync(walletPassphrase, encryptedGG18UserKey, bitgo); + } else { + backupPrv = sjcl.decrypt(walletPassphrase, encryptedGG18BackupKey); + userPrv = sjcl.decrypt(walletPassphrase, encryptedGG18UserKey); + } } catch (e) { throw new Error(`Error decrypting backup keychain: ${e.message}`); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts index 809935db04..9a2a45d648 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -193,7 +193,7 @@ export class EddsaUtils extends baseTSSUtils { if (encryptionSession) { userKeychainParams.encryptedPrv = await encryptionSession.encrypt(JSON.stringify(userSigningMaterial)); } else { - userKeychainParams.encryptedPrv = this.bitgo.encrypt({ + userKeychainParams.encryptedPrv = await this.bitgo.encryptAsync({ input: JSON.stringify(userSigningMaterial), password: passphrase, }); @@ -295,7 +295,7 @@ export class EddsaUtils extends baseTSSUtils { if (encryptionSession) { params.encryptedPrv = await encryptionSession.encrypt(prv); } else { - params.encryptedPrv = this.bitgo.encrypt({ input: prv, password: passphrase }); + params.encryptedPrv = await this.bitgo.encryptAsync({ input: prv, password: passphrase }); } } @@ -489,7 +489,7 @@ export class EddsaUtils extends baseTSSUtils { session.destroy(); } } else { - encryptedRShare = this.bitgo.encrypt({ input: stringifiedRShare, password: params.walletPassphrase }); + encryptedRShare = await this.bitgo.encryptAsync({ input: stringifiedRShare, password: params.walletPassphrase }); } const encryptedUserToBitgoRShare = this.createUserToBitgoEncryptedRShare(encryptedRShare); diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts index 3754f353a2..8a27536ee7 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -226,7 +226,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { assert(reducedPrivateMaterial, `Reduced private material is required for ${source} keychain`); assert(passphrase, `Passphrase is required for ${source} keychain`); privateMaterialBase64 = privateMaterial.toString('base64'); - encryptedPrv = this.bitgo.encrypt({ + encryptedPrv = await this.bitgo.encryptAsync({ input: privateMaterialBase64, password: passphrase, }); @@ -234,7 +234,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { // key) with the wallet passphrase. The result is stored as reducedEncryptedPrv // on the key card QR code and represents a second copy of key material // beyond the server-stored encryptedPrv. - reducedEncryptedPrv = this.bitgo.encrypt({ + reducedEncryptedPrv = await this.bitgo.encryptAsync({ // Buffer.toString('base64') can not be used here as it does not work on the browser. // The browser deals with a Buffer as Uint8Array, therefore in the browser .toString('base64') just creates a comma separated string of the array values. input: btoa(String.fromCharCode.apply(null, Array.from(new Uint8Array(reducedPrivateMaterial)))), @@ -586,12 +586,12 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { } } - const encryptedRound1Session = this.bitgo.encrypt({ + const encryptedRound1Session = await this.bitgo.encryptAsync({ input: sessionPayload, password: walletPassphrase, adata: `${EddsaMPCv2Utils.MPS_DSG_SIGNING_ROUND1_STATE}:${adata}`, }); - const encryptedUserGpgPrvKey = this.bitgo.encrypt({ + const encryptedUserGpgPrvKey = await this.bitgo.encryptAsync({ input: userGpgKey.privateKey, password: walletPassphrase, adata: `${EddsaMPCv2Utils.MPS_DSG_SIGNING_USER_GPG_KEY}:${adata}`, @@ -697,7 +697,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { } } - const encryptedRound2Session = this.bitgo.encrypt({ + const encryptedRound2Session = await this.bitgo.encryptAsync({ input: sessionPayload, password: walletPassphrase, adata: `${EddsaMPCv2Utils.MPS_DSG_SIGNING_ROUND2_STATE}:${adata}`, diff --git a/modules/sdk-core/src/bitgo/wallet/wallets.ts b/modules/sdk-core/src/bitgo/wallet/wallets.ts index ebbb4dbb9b..2a6916707b 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallets.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallets.ts @@ -931,7 +931,7 @@ export class Wallets implements IWallets { } const walletKeychain = this.baseCoin.keychains().create(); - const encryptedPrv = this.bitgo.encrypt({ + const encryptedPrv = await this.bitgo.encryptAsync({ password: params.newWalletPassphrase || params.userPassword, input: walletKeychain.prv, }); @@ -1032,7 +1032,7 @@ export class Wallets implements IWallets { // We will now re-encrypt the wallet with our own password const newWalletPassphrase = params.newWalletPassphrase || params.userPassword; - encryptedPrv = this.bitgo.encrypt({ + encryptedPrv = await this.bitgo.encryptAsync({ password: newWalletPassphrase, input: decryptedSharedWalletPrv, }); @@ -1105,7 +1105,7 @@ export class Wallets implements IWallets { throw new Error('userLoginPassword param must be provided to generate user keychain'); } const walletKeychain = this.baseCoin.keychains().create(); - const encryptedPrv = this.bitgo.encrypt({ + const encryptedPrv = await this.bitgo.encryptAsync({ password: newWalletPassphrase, input: walletKeychain.prv, }); @@ -1131,7 +1131,7 @@ export class Wallets implements IWallets { password: secret, input: walletShare.keychain.encryptedPrv, }); - const newEncryptedPrv = this.bitgo.encrypt({ + const newEncryptedPrv = await this.bitgo.encryptAsync({ password: newWalletPassphrase, input: decryptedSharedWalletPrv, }); @@ -1143,7 +1143,7 @@ export class Wallets implements IWallets { entry.webauthnInfo = { otpDeviceId: webauthnInfo.otpDeviceId, prfSalt: webauthnInfo.prfSalt, - encryptedPrv: this.bitgo.encrypt({ + encryptedPrv: await this.bitgo.encryptAsync({ password: webauthnInfo.passphrase, input: decryptedSharedWalletPrv, }), @@ -1403,7 +1403,7 @@ export class Wallets implements IWallets { } const walletKeychain = this.baseCoin.keychains().create(); - const encryptedPrv = this.bitgo.encrypt({ + const encryptedPrv = await this.bitgo.encryptAsync({ password: newWalletPassphrase || userLoginPassword, input: walletKeychain.prv, }); @@ -1448,7 +1448,7 @@ export class Wallets implements IWallets { }); // We will now re-encrypt the wallet with our own password - const encryptedPrv = this.bitgo.encrypt({ + const encryptedPrv = await this.bitgo.encryptAsync({ password: newWalletPassphrase || userLoginPassword, input: decryptedPrv, }); diff --git a/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts b/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts index 2faf84e19b..a6ab5af59b 100644 --- a/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts +++ b/modules/sdk-core/test/unit/bitgo/trading/tradingAccount.ts @@ -31,9 +31,9 @@ describe('TradingAccount', function () { coin: sinon.stub().callsFake((coin: string) => ({ url: sinon.stub().callsFake((url: string) => `https://app.bitgo-staging.com/api/v2/${coin}${url}`), })), - decrypt: sinon + decryptAsync: sinon .stub() - .callsFake(({ input, password }) => + .callsFake(async ({ input, password }) => input === encryptedPrv && password === walletPassphrase ? decryptedPrv : undefined ), }; @@ -160,7 +160,7 @@ describe('TradingAccount', function () { const result = await tradingAccount.signPayload({ payload, walletPassphrase }); mockBaseCoin.keychains().get.calledWith({ id: 'user-key-id' }).should.be.true(); - mockBitGo.decrypt.calledWith({ input: encryptedPrv, password: walletPassphrase }).should.be.true(); + mockBitGo.decryptAsync.calledWith({ input: encryptedPrv, password: walletPassphrase }).should.be.true(); mockBaseCoin.signMessage.calledOnce.should.be.true(); result.should.equal(Buffer.from(signature, 'hex').toString('hex')); }); @@ -184,7 +184,7 @@ describe('TradingAccount', function () { it('should use prv directly and not call decrypt when both are provided', async function () { await tradingAccount.signPayload({ payload, walletPassphrase, prv: decryptedPrv }); - mockBitGo.decrypt.called.should.be.false(); + mockBitGo.decryptAsync.called.should.be.false(); mockBaseCoin.keychains.called.should.be.false(); const signMessageCall = mockBaseCoin.signMessage.getCall(0); signMessageCall.args[0].should.deepEqual({ prv: decryptedPrv }); @@ -195,7 +195,7 @@ describe('TradingAccount', function () { it('should sign using the provided prv without calling decrypt', async function () { const result = await tradingAccount.signPayload({ payload, prv: decryptedPrv }); - mockBitGo.decrypt.called.should.be.false(); + mockBitGo.decryptAsync.called.should.be.false(); mockBaseCoin.keychains.called.should.be.false(); mockBaseCoin.signMessage.calledOnce.should.be.true(); result.should.equal(Buffer.from(signature, 'hex').toString('hex')); diff --git a/modules/sdk-core/test/unit/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/test/unit/bitgo/utils/tss/baseTSSUtils.ts index 597ea4f7cc..91599df991 100644 --- a/modules/sdk-core/test/unit/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/test/unit/bitgo/utils/tss/baseTSSUtils.ts @@ -79,16 +79,24 @@ describe('Base TSS Utils', function () { }); beforeEach(function () { - decryptAsyncStub = sinon.stub().callsFake(({ input }: Parameters[0]) => - // Fake v2 envelope: resolve the plaintext embedded in the JSON. - Promise.resolve((JSON.parse(input) as { plaintext: string }).plaintext) - ); + decryptAsyncStub = sinon.stub().callsFake(async ({ input, password }: Parameters[0]) => { + try { + const parsed = JSON.parse(input); + if (parsed.v === 2 && parsed.plaintext !== undefined) { + return parsed.plaintext; + } + } catch {} + return sjcl.decrypt(password ?? '', input); + }); const mockBg = {} as BitGoBase; mockBg.getEnv = sinon.stub().returns('test'); mockBg.encrypt = sinon .stub() .callsFake((params) => encryptWithSjcl(params.password ?? '', params.input, params.adata)); + mockBg.encryptAsync = sinon + .stub() + .callsFake(async (params) => encryptWithSjcl(params.password ?? '', params.input, params.adata)); mockBg.decrypt = sinon.stub().callsFake((params) => sjcl.decrypt(params.password ?? '', params.input)); mockBg.decryptAsync = decryptAsyncStub; mockBitgo = mockBg; @@ -258,9 +266,10 @@ describe('Base TSS Utils', function () { assert.equal(result.userGpgPrvKey.isPrivate(), true); }); - it('decrypts v2 envelope and returns GPG keys', async function () { + it('decrypts via decryptAsync and returns GPG keys', async function () { const adata = 'test-adata'; - // Fake v2 envelope: JSON with v:2 triggers isV2Envelope; decryptAsync stub returns plaintext directly. + // Use a fake v2 envelope (v:2) so isV2Envelope returns true and decryptAsync is called. + // The stub extracts plaintext directly from this fake format. const encryptedUserGpgPrvKey = JSON.stringify({ v: 2, adata: `${signingUserGpgKeyDomainSeparator}:${adata}`, diff --git a/modules/sdk-core/test/unit/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts b/modules/sdk-core/test/unit/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts index e3f3b13d73..d8a3a0b5c7 100644 --- a/modules/sdk-core/test/unit/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/test/unit/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -41,7 +41,7 @@ describe('ECDSA MPC v2', async () => { before('initialize EcdsaMPCv2Utils', async () => { const mockBg = {} as BitGoBase; mockBg.getEnv = sinon.stub().returns('test'); - mockBg.encrypt = sinon.stub().callsFake((params) => { + const sjclEncrypt = (params: { password: string; input: string; adata?: string }) => { const salt = randomBytes(8); const iv = randomBytes(16); return sjcl.encrypt(params.password, params.input, { @@ -54,10 +54,11 @@ describe('ECDSA MPC v2', async () => { ], adata: params.adata, }); - }); - mockBg.decrypt = sinon.stub().callsFake((params) => { - return sjcl.decrypt(params.password, params.input); - }); + }; + mockBg.encrypt = sinon.stub().callsFake(sjclEncrypt); + mockBg.encryptAsync = sinon.stub().callsFake(async (params) => sjclEncrypt(params)); + mockBg.decrypt = sinon.stub().callsFake((params) => sjcl.decrypt(params.password, params.input)); + mockBg.decryptAsync = sinon.stub().callsFake(async (params) => sjcl.decrypt(params.password, params.input)); const mockCoin = {} as IBaseCoin; mockCoin.getHashFunction = sinon.stub().callsFake(() => createKeccakHash('keccak256') as Hash); @@ -611,7 +612,9 @@ describe('ECDSA MPC v2', async () => { mockBgWithPost.getEnv = sinon.stub().returns('test'); mockBgWithPost.setRequestTracer = sinon.stub(); mockBgWithPost.encrypt = sinon.stub().returns('encrypted'); + mockBgWithPost.encryptAsync = sinon.stub().resolves('encrypted'); mockBgWithPost.decrypt = sinon.stub().returns('decrypted'); + mockBgWithPost.decryptAsync = sinon.stub().resolves('decrypted'); mockBgWithPost.post = sinon.stub().returns({ send: sinon.stub().returnsThis(), set: sinon.stub().returnsThis(), @@ -677,7 +680,9 @@ describe('ECDSA MPC v2', async () => { mockBgWithPost.getEnv = sinon.stub().returns('test'); mockBgWithPost.setRequestTracer = sinon.stub(); mockBgWithPost.encrypt = sinon.stub().returns('encrypted'); + mockBgWithPost.encryptAsync = sinon.stub().resolves('encrypted'); mockBgWithPost.decrypt = sinon.stub().returns('decrypted'); + mockBgWithPost.decryptAsync = sinon.stub().resolves('decrypted'); mockBgWithPost.post = sinon.stub().returns({ send: sinon.stub().returnsThis(), set: sinon.stub().returnsThis(), diff --git a/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts b/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts index 52ac0c69e7..c2da2fcae4 100644 --- a/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -374,21 +374,23 @@ describe('EddsaMPCv2Utils.createOfflineRound1Share', () => { }); beforeEach(() => { + const sjclEncrypt = (params: { password: string; input: string; adata?: string }) => { + const salt = randomBytes(8); + const iv = randomBytes(16); + return sjcl.encrypt(params.password, params.input, { + salt: [bytesToWord(salt.subarray(0, 4)), bytesToWord(salt.subarray(4))], + iv: [ + bytesToWord(iv.subarray(0, 4)), + bytesToWord(iv.subarray(4, 8)), + bytesToWord(iv.subarray(8, 12)), + bytesToWord(iv.subarray(12, 16)), + ], + adata: params.adata, + }); + }; mockBitgo = { - encrypt: sinon.stub().callsFake((params) => { - const salt = randomBytes(8); - const iv = randomBytes(16); - return sjcl.encrypt(params.password, params.input, { - salt: [bytesToWord(salt.subarray(0, 4)), bytesToWord(salt.subarray(4))], - iv: [ - bytesToWord(iv.subarray(0, 4)), - bytesToWord(iv.subarray(4, 8)), - bytesToWord(iv.subarray(8, 12)), - bytesToWord(iv.subarray(12, 16)), - ], - adata: params.adata, - }); - }), + encrypt: sinon.stub().callsFake(sjclEncrypt), + encryptAsync: sinon.stub().callsFake(async (params) => sjclEncrypt(params)), } as unknown as BitGoBase; const mockCoin = { @@ -455,7 +457,7 @@ describe('EddsaMPCv2Utils.createOfflineRound1Share', () => { sinon.assert.calledOnce(createEncryptionSession); assert.strictEqual(createEncryptionSession.getCall(0).args[0], walletPassphrase); - sinon.assert.notCalled(mockBitgo.encrypt as sinon.SinonStub); + sinon.assert.notCalled(mockBitgo.encryptAsync as sinon.SinonStub); sinon.assert.calledTwice(encrypt); sinon.assert.calledOnce(destroy); @@ -483,7 +485,7 @@ describe('EddsaMPCv2Utils.createOfflineRound1Share', () => { }); sinon.assert.notCalled(mockBitgo.createEncryptionSession as sinon.SinonStub); - sinon.assert.calledTwice(mockBitgo.encrypt as sinon.SinonStub); + sinon.assert.calledTwice(mockBitgo.encryptAsync as sinon.SinonStub); assert.ok(JSON.parse(result.encryptedRound1Session).ct, 'encryptedRound1Session should be an SJCL JSON blob'); assert.ok(JSON.parse(result.encryptedUserGpgPrvKey).ct, 'encryptedUserGpgPrvKey should be an SJCL JSON blob'); }); @@ -558,7 +560,22 @@ describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { adata: params.adata, }); }), + encryptAsync: sinon.stub().callsFake(async (params) => { + const salt = randomBytes(8); + const iv = randomBytes(16); + return sjcl.encrypt(params.password, params.input, { + salt: [bytesToWord(salt.subarray(0, 4)), bytesToWord(salt.subarray(4))], + iv: [ + bytesToWord(iv.subarray(0, 4)), + bytesToWord(iv.subarray(4, 8)), + bytesToWord(iv.subarray(8, 12)), + bytesToWord(iv.subarray(12, 16)), + ], + adata: params.adata, + }); + }), decrypt: sinon.stub().callsFake((params) => sjcl.decrypt(params.password, params.input)), + decryptAsync: sinon.stub().callsFake(async (params) => sjcl.decrypt(params.password, params.input)), } as unknown as BitGoBase; const mockCoin = { @@ -660,7 +677,6 @@ describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { }); sinon.assert.called(mockBitgo.decryptAsync as sinon.SinonStub); - sinon.assert.notCalled(mockBitgo.decrypt as sinon.SinonStub); assert.strictEqual(round2.signatureShareRound2.from, SignatureShareType.USER); const encryptedRound2Session = JSON.parse(round2.encryptedRound2Session); @@ -900,7 +916,22 @@ describe('EddsaMPCv2Utils.createOfflineRound3Share', () => { adata: params.adata, }); }), + encryptAsync: sinon.stub().callsFake(async (params) => { + const salt = randomBytes(8); + const iv = randomBytes(16); + return sjcl.encrypt(params.password, params.input, { + salt: [bytesToWord(salt.subarray(0, 4)), bytesToWord(salt.subarray(4))], + iv: [ + bytesToWord(iv.subarray(0, 4)), + bytesToWord(iv.subarray(4, 8)), + bytesToWord(iv.subarray(8, 12)), + bytesToWord(iv.subarray(12, 16)), + ], + adata: params.adata, + }); + }), decrypt: sinon.stub().callsFake((params) => sjcl.decrypt(params.password, params.input)), + decryptAsync: sinon.stub().callsFake(async (params) => sjcl.decrypt(params.password, params.input)), } as unknown as BitGoBase; const mockCoin = { @@ -1025,7 +1056,6 @@ describe('EddsaMPCv2Utils.createOfflineRound3Share', () => { }); assert.strictEqual((mockBitgo.decryptAsync as sinon.SinonStub).callCount, 4); - sinon.assert.notCalled(mockBitgo.decrypt as sinon.SinonStub); assert.strictEqual(round3.signatureShareRound3.from, SignatureShareType.USER); });