Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 4 additions & 5 deletions src/__tests__/api/master/accelerate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import nock from 'nock';
import { app as expressApp } from '../../../masterExpressApp';
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
import { Environments, Wallet } from '@bitgo/sdk-core';
import { Tbtc } from '@bitgo/sdk-coin-btc';

describe('POST /api/:coin/wallet/:walletId/accelerate', () => {
let agent: request.SuperAgentTest;
Expand Down Expand Up @@ -70,7 +69,7 @@ describe('POST /api/:coin/wallet/:walletId/accelerate', () => {
.stub(Wallet.prototype, 'accelerateTransaction')
.resolves({
txid: 'accelerated-tx-id',
tx: "accerated-transaction-hex",
tx: 'accerated-transaction-hex',
status: 'signed',
});

Expand Down Expand Up @@ -121,7 +120,7 @@ describe('POST /api/:coin/wallet/:walletId/accelerate', () => {
.resolves({
txid: 'accelerated-tx-id',
status: 'signed',
tx: "accelerated-transaction-hex",
tx: 'accelerated-transaction-hex',
});

const response = await agent
Expand Down Expand Up @@ -262,7 +261,7 @@ describe('POST /api/:coin/wallet/:walletId/accelerate', () => {
.resolves({
txid: 'accelerated-tx-id',
status: 'signed',
tx: "accelerated-transaction-hex",
tx: 'accelerated-transaction-hex',
});

const response = await agent
Expand All @@ -284,4 +283,4 @@ describe('POST /api/:coin/wallet/:walletId/accelerate', () => {
keychainGetNock.done();
sinon.assert.calledOnce(accelerateTransactionStub);
});
});
});
38 changes: 17 additions & 21 deletions src/api/master/handlers/handleAccelerate.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import { RequestTracer, KeyIndices } from '@bitgo/sdk-core';
import logger from '../../../logger';
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
import { getWalletAndSigningKeychain, makeCustomSigningFunction } from '../../../shared/coinUtils';

export async function handleAccelerate(
req: MasterApiSpecRouteRequest<'v1.wallet.accelerate', 'post'>,
) {
const enclavedExpressClient = req.enclavedExpressClient;
const reqId = new RequestTracer();
const bitgo = req.bitgo;
const baseCoin = bitgo.coin(req.params.coin);
const params = req.decoded;
const walletId = req.params.walletId;
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });

// Log the runtime class name of the wallet object
logger.info('Wallet runtime class name: %s', wallet?.constructor.name);
logger.info('Wallet prototype chain: %s', Object.getPrototypeOf(wallet)?.constructor.name);
const coin = req.params.coin;

const { wallet, signingKeychain } = await getWalletAndSigningKeychain({
bitgo,
coin,
walletId,
params,
reqId,
KeyIndices,
});

if (!wallet) {
throw new Error(`Wallet ${walletId} not found`);
}

// Get the signing keychain based on source
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
const signingKeychain = await baseCoin.keychains().get({
id: wallet.keyIds()[keyIdIndex],
});

if (!signingKeychain || !signingKeychain.pub) {
throw new Error(`Signing keychain for ${params.source} not found`);
}
Expand All @@ -37,14 +36,11 @@ export async function handleAccelerate(

try {
// Create custom signing function that delegates to EBE
const customSigningFunction = async (signParams: any) => {
const signedTx = await enclavedExpressClient.signMultisig({
txPrebuild: signParams.txPrebuild,
source: params.source,
pub: signingKeychain.pub!,
});
return signedTx;
};
const customSigningFunction = makeCustomSigningFunction({
enclavedExpressClient,
source: params.source,
pub: signingKeychain.pub!,
});

// Prepare acceleration parameters
const accelerationParams = {
Expand All @@ -62,4 +58,4 @@ export async function handleAccelerate(
logger.error('Failed to accelerate transaction: %s', err.message);
throw err;
}
}
}
38 changes: 15 additions & 23 deletions src/api/master/handlers/handleConsolidate.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { RequestTracer, KeyIndices } from '@bitgo/sdk-core';
import logger from '../../../logger';
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
import { getWalletAndSigningKeychain, makeCustomSigningFunction } from '../../../shared/coinUtils';

export async function handleConsolidate(
req: MasterApiSpecRouteRequest<'v1.wallet.consolidate', 'post'>,
) {
const enclavedExpressClient = req.enclavedExpressClient;
const reqId = new RequestTracer();
const bitgo = req.bitgo;
const baseCoin = bitgo.coin(req.params.coin);
const params = req.decoded;
const walletId = req.params.walletId;
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });
const coin = req.params.coin;

if (!wallet) {
throw new Error(`Wallet ${walletId} not found`);
}
const { baseCoin, wallet, signingKeychain } = await getWalletAndSigningKeychain({
bitgo,
coin,
walletId,
params,
reqId,
KeyIndices,
});

// Check if the coin supports account consolidations
if (!baseCoin.allowsAccountConsolidations()) {
Expand All @@ -27,30 +32,17 @@ export async function handleConsolidate(
throw new Error('consolidateAddresses must be an array of addresses');
}

// Get the signing keychain based on source
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
const signingKeychain = await baseCoin.keychains().get({
id: wallet.keyIds()[keyIdIndex],
});

if (!signingKeychain || !signingKeychain.pub) {
throw new Error(`Signing keychain for ${params.source} not found`);
}

if (params.pubkey && params.pubkey !== signingKeychain.pub) {
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
}

try {
// Create custom signing function that delegates to EBE
const customSigningFunction = async (signParams: any) => {
const signedTx = await enclavedExpressClient.signMultisig({
txPrebuild: signParams.txPrebuild,
source: params.source,
pub: signingKeychain.pub!,
});
return signedTx;
};
const customSigningFunction = makeCustomSigningFunction({
enclavedExpressClient,
source: params.source,
pub: signingKeychain.pub!,
});

// Prepare consolidation parameters
const consolidationParams = {
Expand Down
4 changes: 2 additions & 2 deletions src/api/master/routers/masterApiSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ export const AccelerateRequest = {
const AccelerateResponse: HttpResponse = {
// TODO: Get type from public types repo / Wallet Platform
200: t.type({
"txid": t.string,
"tx": t.string,
txid: t.string,
tx: t.string,
}),
500: t.type({
error: t.string,
Expand Down
62 changes: 61 additions & 1 deletion src/shared/coinUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { FormattedOfflineVaultTxInfo, BackupKeyRecoveryTransansaction } from '@bitgo/abstract-utxo';
import { AbstractEthLikeNewCoins } from '@bitgo/abstract-eth';
import { CoinFamily } from '@bitgo/statics';
import { BaseCoin } from 'bitgo';
import { BaseCoin, BitGo } from 'bitgo';
import { AbstractUtxoCoin, Eos, Stx, Xtz } from 'bitgo/dist/types/src/v2/coins';
import { RequestTracer } from '@bitgo/sdk-core';
import { EnclavedExpressClient } from '../api/master/clients/enclavedExpressClient';

export function isEthLikeCoin(coin: BaseCoin): coin is AbstractEthLikeNewCoins {
const isEthPure = isFamily(coin, CoinFamily.ETH);
Expand Down Expand Up @@ -61,3 +63,61 @@ export function isFormattedOfflineVaultTxInfo(
): obj is FormattedOfflineVaultTxInfo {
return obj && 'txInfo' in obj && 'txHex' in obj && 'feeInfo' in obj;
}

/**
* Fetch wallet and signing keychain, with validation for source and pubkey.
* Throws with a clear error if not found or mismatched.
*/
export async function getWalletAndSigningKeychain({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets move this under api/master since it is only used within MBE. Also rename to handlerUtils since this isnt coin specific

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix this in the later PR.

bitgo,
coin,
walletId,
params,
reqId,
KeyIndices,
}: {
bitgo: BitGo;
coin: string;
walletId: string;
params: { source: 'user' | 'backup'; pubkey?: string };
reqId: RequestTracer;
KeyIndices: { USER: number; BACKUP: number; BITGO: number };
}) {
const baseCoin = bitgo.coin(coin);
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });
if (!wallet) {
throw new Error(`Wallet ${walletId} not found`);
}
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
const signingKeychain = await baseCoin.keychains().get({
id: wallet.keyIds()[keyIdIndex],
});
if (!signingKeychain || !signingKeychain.pub) {
throw new Error(`Signing keychain for ${params.source} not found`);
}
if (params.pubkey && params.pubkey !== signingKeychain.pub) {
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
}
return { baseCoin, wallet, signingKeychain };
}

/**
* Create a custom signing function that delegates to enclavedExpressClient.signMultisig.
*/
export function makeCustomSigningFunction({
enclavedExpressClient,
source,
pub,
}: {
enclavedExpressClient: EnclavedExpressClient;
source: 'user' | 'backup';
pub: string;
}) {
return async function customSigningFunction(signParams: any) {
return enclavedExpressClient.signMultisig({
txPrebuild: signParams.txPrebuild,
source,
pub,
});
};
}
Loading