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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ CVE-2014-10077 # rubygem-i18n 0.7.0
CVE-2020-10663 # rubygem-json 1.8.2
CVE-2022-31163 # rubygem-tzinfo 1.2.2
CVE-2026-33176 # rubygem-activesupport 4.2.1 in js-xdr/Gemfile.lock — Ruby gem not executed in Node.js runtime
GHSA-8474-xrp6-wh69 # rubygem-activesupport 4.2.1 in js-xdr/Gemfile.lock — Ruby gem not executed in Node.js runtime
6,030 changes: 1,416 additions & 4,614 deletions package-lock.json

Large diffs are not rendered by default.

161 changes: 81 additions & 80 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,84 +30,84 @@
"@api-ts/response": "^2.1.0",
"@api-ts/superagent-wrapper": "^1.3.3",
"@api-ts/typed-express-router": "2.0.0",
"@bitgo-beta/abstract-cosmos": "1.0.1-beta.2112",
"@bitgo-beta/abstract-eth": "1.0.2-beta.2349",
"@bitgo-beta/abstract-utxo": "1.1.1-beta.2352",
"@bitgo-beta/sdk-api": "1.10.1-beta.2116",
"@bitgo-beta/sdk-coin-ada": "2.3.14-beta.2114",
"@bitgo-beta/sdk-coin-algo": "2.8.9-beta.593",
"@bitgo-beta/sdk-coin-apt": "1.0.1-beta.1555",
"@bitgo-beta/sdk-coin-arbeth": "1.0.1-beta.1969",
"@bitgo-beta/sdk-coin-asi": "1.0.1-beta.1201",
"@bitgo-beta/sdk-coin-atom": "3.1.2-beta.2111",
"@bitgo-beta/sdk-coin-avaxc": "1.1.1-beta.2335",
"@bitgo-beta/sdk-coin-avaxp": "5.7.3-beta.432",
"@bitgo-beta/sdk-coin-baby": "1.0.1-beta.1456",
"@bitgo-beta/sdk-coin-bch": "1.1.1-beta.2340",
"@bitgo-beta/sdk-coin-bcha": "1.1.1-beta.2336",
"@bitgo-beta/sdk-coin-bera": "1.0.1-beta.1968",
"@bitgo-beta/sdk-coin-bld": "1.0.1-beta.2074",
"@bitgo-beta/sdk-coin-bsc": "1.0.1-beta.2336",
"@bitgo-beta/sdk-coin-bsv": "1.1.1-beta.2340",
"@bitgo-beta/sdk-coin-btc": "1.0.2-beta.2337",
"@bitgo-beta/sdk-coin-btg": "1.1.1-beta.2337",
"@bitgo-beta/sdk-coin-celo": "1.1.1-beta.2335",
"@bitgo-beta/sdk-coin-coredao": "1.0.0-beta.1545",
"@bitgo-beta/sdk-coin-coreum": "1.0.1-beta.1954",
"@bitgo-beta/sdk-coin-cronos": "1.0.1-beta.1245",
"@bitgo-beta/sdk-coin-cspr": "1.2.19-beta.2112",
"@bitgo-beta/sdk-coin-dash": "1.1.1-beta.2338",
"@bitgo-beta/sdk-coin-doge": "1.0.2-beta.2339",
"@bitgo-beta/sdk-coin-dot": "2.2.8-beta.2112",
"@bitgo-beta/sdk-coin-eos": "1.3.19-beta.2108",
"@bitgo-beta/sdk-coin-etc": "1.0.2-beta.2336",
"@bitgo-beta/sdk-coin-eth": "4.4.1-beta.2097",
"@bitgo-beta/sdk-coin-ethw": "20.0.76-beta.1273",
"@bitgo-beta/sdk-coin-flr": "1.0.1-beta.1449",
"@bitgo-beta/sdk-coin-hash": "1.0.1-beta.2069",
"@bitgo-beta/sdk-coin-hbar": "1.0.2-beta.2339",
"@bitgo-beta/sdk-coin-icp": "1.0.1-beta.1466",
"@bitgo-beta/sdk-coin-initia": "1.1.1-beta.1226",
"@bitgo-beta/sdk-coin-injective": "1.1.3-beta.2064",
"@bitgo-beta/sdk-coin-islm": "1.0.1-beta.1966",
"@bitgo-beta/sdk-coin-ltc": "1.1.1-beta.2337",
"@bitgo-beta/sdk-coin-mon": "1.0.1-beta.1283",
"@bitgo-beta/sdk-coin-near": "1.3.19-beta.2110",
"@bitgo-beta/sdk-coin-oas": "1.0.1-beta.1547",
"@bitgo-beta/sdk-coin-opeth": "1.0.1-beta.1965",
"@bitgo-beta/sdk-coin-osmo": "1.0.1-beta.2095",
"@bitgo-beta/sdk-coin-polygon": "1.0.2-beta.2337",
"@bitgo-beta/sdk-coin-polyx": "1.0.1-beta.1309",
"@bitgo-beta/sdk-coin-rbtc": "1.1.1-beta.2334",
"@bitgo-beta/sdk-coin-rune": "1.0.0-beta.1578",
"@bitgo-beta/sdk-coin-sei": "1.0.1-beta.2065",
"@bitgo-beta/sdk-coin-sgb": "1.1.1-beta.1451",
"@bitgo-beta/sdk-coin-sol": "7.6.4-beta.757",
"@bitgo-beta/sdk-coin-soneium": "1.0.1-beta.1261",
"@bitgo-beta/sdk-coin-stt": "1.0.1-beta.1270",
"@bitgo-beta/sdk-coin-stx": "1.1.1-beta.2343",
"@bitgo-beta/sdk-coin-sui": "3.0.3-beta.2107",
"@bitgo-beta/sdk-coin-tao": "1.0.0-beta.1484",
"@bitgo-beta/sdk-coin-tia": "3.1.8-beta.1080",
"@bitgo-beta/sdk-coin-ton": "1.0.1-beta.1971",
"@bitgo-beta/sdk-coin-trx": "1.0.2-beta.2341",
"@bitgo-beta/sdk-coin-vet": "1.0.1-beta.1225",
"@bitgo-beta/sdk-coin-wemix": "1.0.1-beta.1442",
"@bitgo-beta/sdk-coin-world": "1.0.1-beta.1271",
"@bitgo-beta/sdk-coin-xdc": "1.0.1-beta.1453",
"@bitgo-beta/sdk-coin-xlm": "2.0.5-beta.2104",
"@bitgo-beta/sdk-coin-xrp": "1.1.1-beta.2338",
"@bitgo-beta/sdk-coin-xtz": "1.1.1-beta.2341",
"@bitgo-beta/sdk-coin-zec": "1.1.1-beta.2336",
"@bitgo-beta/sdk-coin-zeta": "1.0.1-beta.2030",
"@bitgo-beta/sdk-coin-zketh": "1.0.1-beta.1895",
"@bitgo-beta/sdk-core": "8.2.1-beta.2127",
"@bitgo-beta/sdk-lib-mpc": "8.2.0-beta.2139",
"@bitgo-beta/statics": "15.1.1-beta.2161",
"@bitgo-beta/abstract-cosmos": "1.0.1-beta.2143",
"@bitgo-beta/abstract-eth": "1.0.2-beta.2380",
"@bitgo-beta/abstract-utxo": "1.1.1-beta.2382",
"@bitgo-beta/sdk-api": "1.10.1-beta.2146",
"@bitgo-beta/sdk-coin-ada": "2.3.14-beta.2144",
"@bitgo-beta/sdk-coin-algo": "2.8.9-beta.623",
"@bitgo-beta/sdk-coin-apt": "1.0.1-beta.1585",
"@bitgo-beta/sdk-coin-arbeth": "1.0.1-beta.1998",
"@bitgo-beta/sdk-coin-asi": "1.0.1-beta.1231",
"@bitgo-beta/sdk-coin-atom": "3.1.2-beta.2141",
"@bitgo-beta/sdk-coin-avaxc": "1.1.1-beta.2364",
"@bitgo-beta/sdk-coin-avaxp": "5.7.3-beta.462",
"@bitgo-beta/sdk-coin-baby": "1.0.1-beta.1486",
"@bitgo-beta/sdk-coin-bch": "1.1.1-beta.2370",
"@bitgo-beta/sdk-coin-bcha": "1.1.1-beta.2366",
"@bitgo-beta/sdk-coin-bera": "1.0.1-beta.1998",
"@bitgo-beta/sdk-coin-bld": "1.0.1-beta.2104",
"@bitgo-beta/sdk-coin-bsc": "1.0.1-beta.2366",
"@bitgo-beta/sdk-coin-bsv": "1.1.1-beta.2369",
"@bitgo-beta/sdk-coin-btc": "1.0.2-beta.2367",
"@bitgo-beta/sdk-coin-btg": "1.1.1-beta.2366",
"@bitgo-beta/sdk-coin-celo": "1.1.1-beta.2363",
"@bitgo-beta/sdk-coin-coredao": "1.0.0-beta.1575",
"@bitgo-beta/sdk-coin-coreum": "1.0.1-beta.1984",
"@bitgo-beta/sdk-coin-cronos": "1.0.1-beta.1274",
"@bitgo-beta/sdk-coin-cspr": "1.2.19-beta.2142",
"@bitgo-beta/sdk-coin-dash": "1.1.1-beta.2367",
"@bitgo-beta/sdk-coin-doge": "1.0.2-beta.2369",
"@bitgo-beta/sdk-coin-dot": "2.2.8-beta.2140",
"@bitgo-beta/sdk-coin-eos": "1.3.19-beta.2138",
"@bitgo-beta/sdk-coin-etc": "1.0.2-beta.2365",
"@bitgo-beta/sdk-coin-eth": "4.4.1-beta.2121",
"@bitgo-beta/sdk-coin-ethw": "20.0.76-beta.1301",
"@bitgo-beta/sdk-coin-flr": "1.0.1-beta.1477",
"@bitgo-beta/sdk-coin-hash": "1.0.1-beta.2098",
"@bitgo-beta/sdk-coin-hbar": "1.0.2-beta.2368",
"@bitgo-beta/sdk-coin-icp": "1.0.1-beta.1496",
"@bitgo-beta/sdk-coin-initia": "1.1.1-beta.1255",
"@bitgo-beta/sdk-coin-injective": "1.1.3-beta.2094",
"@bitgo-beta/sdk-coin-islm": "1.0.1-beta.1997",
"@bitgo-beta/sdk-coin-ltc": "1.1.1-beta.2367",
"@bitgo-beta/sdk-coin-mon": "1.0.1-beta.1312",
"@bitgo-beta/sdk-coin-near": "1.3.19-beta.2140",
"@bitgo-beta/sdk-coin-oas": "1.0.1-beta.1576",
"@bitgo-beta/sdk-coin-opeth": "1.0.1-beta.1996",
"@bitgo-beta/sdk-coin-osmo": "1.0.1-beta.2125",
"@bitgo-beta/sdk-coin-polygon": "1.0.2-beta.2366",
"@bitgo-beta/sdk-coin-polyx": "1.0.1-beta.1338",
"@bitgo-beta/sdk-coin-rbtc": "1.1.1-beta.2363",
"@bitgo-beta/sdk-coin-rune": "1.0.0-beta.1608",
"@bitgo-beta/sdk-coin-sei": "1.0.1-beta.2095",
"@bitgo-beta/sdk-coin-sgb": "1.1.1-beta.1481",
"@bitgo-beta/sdk-coin-sol": "7.6.4-beta.786",
"@bitgo-beta/sdk-coin-soneium": "1.0.1-beta.1290",
"@bitgo-beta/sdk-coin-stt": "1.0.1-beta.1300",
"@bitgo-beta/sdk-coin-stx": "1.1.1-beta.2373",
"@bitgo-beta/sdk-coin-sui": "3.0.3-beta.2137",
"@bitgo-beta/sdk-coin-tao": "1.0.0-beta.1515",
"@bitgo-beta/sdk-coin-tia": "3.1.8-beta.1110",
"@bitgo-beta/sdk-coin-ton": "1.0.1-beta.2001",
"@bitgo-beta/sdk-coin-trx": "1.0.2-beta.2370",
"@bitgo-beta/sdk-coin-vet": "1.0.1-beta.1255",
"@bitgo-beta/sdk-coin-wemix": "1.0.1-beta.1471",
"@bitgo-beta/sdk-coin-world": "1.0.1-beta.1299",
"@bitgo-beta/sdk-coin-xdc": "1.0.1-beta.1483",
"@bitgo-beta/sdk-coin-xlm": "2.0.5-beta.2134",
"@bitgo-beta/sdk-coin-xrp": "1.1.1-beta.2367",
"@bitgo-beta/sdk-coin-xtz": "1.1.1-beta.2371",
"@bitgo-beta/sdk-coin-zec": "1.1.1-beta.2365",
"@bitgo-beta/sdk-coin-zeta": "1.0.1-beta.2059",
"@bitgo-beta/sdk-coin-zketh": "1.0.1-beta.1925",
"@bitgo-beta/sdk-core": "8.2.1-beta.2157",
"@bitgo-beta/sdk-lib-mpc": "8.2.0-beta.2172",
"@bitgo-beta/statics": "15.1.1-beta.2198",
"@bitgo/wasm-miniscript": "2.0.0-beta.7",
"@commitlint/config-conventional": "^19.8.1",
"@ethereumjs/tx": "^3.3.0",
"body-parser": "^1.20.3",
"body-parser": "^1.20.9",
"connect-timeout": "^1.9.0",
"debug": "^3.1.0",
"express": "4.21.2",
Expand All @@ -122,13 +122,14 @@
"zod": "^3.25.48"
},
"overrides": {
"@bitgo-beta/sdk-core": "8.2.1-beta.2127",
"@bitgo-beta/statics": "15.1.1-beta.2161",
"@bitgo-beta/sdk-core": "8.2.1-beta.2157",
"@bitgo-beta/statics": "15.1.1-beta.2198",
"elliptic": "^6.6.1",
"expo": "^48.0.0",
"form-data": "^4.0.6",
"axios": "^1.20.0",
"follow-redirects": "^1.16.1",
"follow-redirects": "^1.16.2",
"body-parser": "^1.20.9",
"node-fetch": "^2.6.7",
"secp256k1": "^5.0.1",
"semver": "^7.5.2",
Expand All @@ -149,7 +150,7 @@
"proxy-addr": "^2.0.8",
"tough-cookie": "^4.1.3",
"validator": "^13.15.22",
"node-forge": "^1.3.2",
"node-forge": "^1.4.1",
"xml2js": "^0.5.0",
"glob": "^11.1.0",
"@xmldom/xmldom": "^0.9.12",
Expand All @@ -165,7 +166,7 @@
"js-yaml@3": "^3.15.2",
"js-yaml@4": "^4.3.2",
"@grpc/grpc-js": "^1.14.5",
"joi": "^17.13.7",
"joi": "^17.13.9",
"toml": "^5.0.1",
"image-size": "^2.0.3"
},
Expand Down
16 changes: 15 additions & 1 deletion src/__tests__/api/master/musigRecovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import * as request from 'supertest';
import { app as expressApp } from '../../../masterBitGoExpressApp';
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
import { data as ethRecoveryData } from '../../mocks/ethRecoveryMusigMockData';
import { BitGoAPITestHarness, DEFAULT_ASYNC_MODE_CONFIG } from './testUtils';
import {
BitGoAPITestHarness,
DEFAULT_ASYNC_MODE_CONFIG,
ETH_TEST_USER_SIGNER_ADDRESS,
nockEthWalletIsSigner,
} from './testUtils';

describe('POST /api/v1/:coin/advancedwallet/recovery', () => {
let agent: request.SuperAgentTest;
Expand Down Expand Up @@ -73,6 +78,14 @@ describe('POST /api/v1/:coin/advancedwallet/recovery', () => {
)
.reply(200, { result: '1000000000000000000' });

const signerNocks = nockEthWalletIsSigner({
etherscanBase,
chainid,
apiKey,
walletContractAddress: ethRecoveryData.walletContractAddress,
signerAddresses: [ETH_TEST_USER_SIGNER_ADDRESS, backupKeyAddress],
});

const sequenceIdNock = nock(etherscanBase)
.get(
`/v2/api?chainid=${chainid}&module=proxy&action=eth_call&to=${ethRecoveryData.walletContractAddress}&data=a0b7967b&tag=latest&apikey=${apiKey}`,
Expand Down Expand Up @@ -115,6 +128,7 @@ describe('POST /api/v1/:coin/advancedwallet/recovery', () => {
backupBalanceNock.isDone().should.be.true();
walletBalanceNock.isDone().should.be.true();
sequenceIdNock.isDone().should.be.true();
signerNocks.forEach((signerNock) => signerNock.isDone().should.be.true());
eveRecoverWalletNock.done();
});

Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/api/master/recoveryEcdsa.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'should';
import sinon from 'sinon';
import { BitGoAPI } from '@bitgo-beta/sdk-api';
import { Hteth } from '@bitgo-beta/sdk-coin-eth';
import { AdvancedWalletManagerClient } from '../../../masterBitgoExpress/clients/advancedWalletManagerClient';
import { recoverEcdsaMPCv2Wallets } from '../../../masterBitgoExpress/handlers/recoveryEcdsa';
import { isEthLikeCoin } from '../../../shared/coinUtils';
import { makeMasterExpressTestConfig } from './testUtils';

describe('recoverEcdsaMPCv2Wallets', function () {
const coin = 'hteth';
const bitgo = new BitGoAPI({ env: 'test' });
bitgo.register(coin, Hteth.createInstance);
const baseCoin = bitgo.coin(coin);
const awmClient = new AdvancedWalletManagerClient(
makeMasterExpressTestConfig('http://advancedwalletmanager.invalid'),
coin,
);

afterEach(function () {
sinon.restore();
});

it('should reject eth-like recovery without userKey before querying the explorer or AWM', async function () {
if (!isEthLikeCoin(baseCoin)) {
throw new Error('hteth must be an eth-like coin');
}
const recoverSpy = sinon.spy(baseCoin, 'recover');
const awmRecoverStub = sinon.stub(awmClient, 'recoverEcdsaMpcV2Wallet');

await recoverEcdsaMPCv2Wallets(bitgo, baseCoin, awmClient, {
commonKeychain: 'common-keychain',
ethLikeParams: {
backupKey: 'common-keychain',
recoveryDestination: '0x1234567890123456789012345678901234567890',
walletContractAddress: '0x0987654321098765432109876543210987654321',
walletPassphrase: '',
isTss: true,
},
}).should.be.rejectedWith(
'userKey is required in eth like recovery params for Ecdsa MPCv2 recovery',
);

recoverSpy.called.should.be.false();
awmRecoverStub.called.should.be.false();
});
});
27 changes: 27 additions & 0 deletions src/__tests__/api/master/recoveryWallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
import {
BitGoAPITestHarness,
DEFAULT_ASYNC_MODE_CONFIG,
ETH_TEST_USER_SIGNER_ADDRESS,
makeMasterExpressTestConfig,
makeSplitAwmMasterExpressConfig,
nockAsyncMultisigRecoveryJob,
nockAsyncRecoveryJobBypass,
nockEthWalletIsSigner,
nockSplitAwmMultisigRecovery,
} from './testUtils';

Expand Down Expand Up @@ -291,6 +293,14 @@ describe('Recovery Tests', () => {
)
.reply(200, { result: '1000000000000000000' });

const signerNocks = nockEthWalletIsSigner({
etherscanBase,
chainid,
apiKey,
walletContractAddress,
signerAddresses: [ETH_TEST_USER_SIGNER_ADDRESS, backupKeyAddress],
});

const sequenceIdNock = nock(etherscanBase)
.get(
`/v2/api?chainid=${chainid}&module=proxy&action=eth_call&to=${walletContractAddress}&data=a0b7967b&tag=latest&apikey=${apiKey}`,
Expand Down Expand Up @@ -335,6 +345,7 @@ describe('Recovery Tests', () => {
backupBalanceNock.isDone().should.be.true();
walletBalanceNock.isDone().should.be.true();
sequenceIdNock.isDone().should.be.true();
signerNocks.forEach((signerNock) => signerNock.isDone().should.be.true());
recoveryNock.done();
});

Expand Down Expand Up @@ -728,6 +739,14 @@ describe('Recovery Tests', () => {
result: '0x0000000000000000000000000000000000000000000000000000000000000001',
});

nockEthWalletIsSigner({
etherscanBase,
chainid,
apiKey,
walletContractAddress,
signerAddresses: [ETH_TEST_USER_SIGNER_ADDRESS, backupKeyAddress],
});

let capturedBody: Record<string, unknown> | undefined;
const { bridgeNock, awmRecoveryNock } = nockAsyncMultisigRecoveryJob({
coin: ethCoinId,
Expand Down Expand Up @@ -944,6 +963,14 @@ describe('Split AWM recovery (separate user and backup AWMs)', () => {
result: '0x0000000000000000000000000000000000000000000000000000000000000001',
});

nockEthWalletIsSigner({
etherscanBase,
chainid,
apiKey,
walletContractAddress,
signerAddresses: [ETH_TEST_USER_SIGNER_ADDRESS, backupKeyAddress],
});

const halfSignedObject = {
halfSigned: {
txHex: '0xhalfsigned',
Expand Down
28 changes: 28 additions & 0 deletions src/__tests__/api/master/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,34 @@ export function nockSplitAwmMultisigRecovery(options: {
return { userAwmNock, backupAwmNock };
}

const ETH_IS_SIGNER_SELECTOR = '7df73e27';
const ETH_ABI_TRUE = `0x${'0'.repeat(63)}1`;

/** Address of the key behind the `ethUserPub` xpub used by the EVM recovery tests. */
export const ETH_TEST_USER_SIGNER_ADDRESS = '0x6df6065fdc0e24b7bc15e939a681e8f2ae0f9a3b';

/**
* Etherscan eth_call nocks for the SDK's recovery signer-membership check
* (`isSigner(address)` on the wallet contract), one per signer address.
*/
export function nockEthWalletIsSigner(options: {
etherscanBase: string;
chainid: string;
apiKey: string;
walletContractAddress: string;
signerAddresses: string[];
}) {
const { etherscanBase, chainid, apiKey, walletContractAddress, signerAddresses } = options;
return signerAddresses.map((signerAddress) => {
const paddedAddress = signerAddress.toLowerCase().replace(/^0x/, '').padStart(64, '0');
return nock(etherscanBase)
.get(
`/v2/api?chainid=${chainid}&module=proxy&action=eth_call&to=${walletContractAddress}&data=${ETH_IS_SIGNER_SELECTOR}${paddedAddress}&tag=latest&apikey=${apiKey}`,
)
.reply(200, { result: ETH_ABI_TRUE });
});
}

/** Bridge job nock that split-AWM sync paths must not reach. */
export function nockAsyncRecoveryJobBypass(coin: string, bridgeUrl = ASYNC_TEST_BRIDGE_URL) {
return nock(bridgeUrl)
Expand Down
Loading
Loading