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
154 changes: 154 additions & 0 deletions modules/bitgo/test/v2/unit/signTransactionVerification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import nock = require('nock');
import * as sinon from 'sinon';
import * as assert from 'assert';
import 'should';

import { BitGoAPI } from '@bitgo/sdk-api';
import { TestBitGo } from '@bitgo/sdk-test';
import { Tbtc } from '@bitgo/sdk-coin-btc';
import { common, BaseCoin, BitGoBase, Wallet, WalletSignTransactionOptions } from '@bitgo/sdk-core';

describe('Wallet signTransaction with verifyTxParams', function () {
let wallet: Wallet;
let basecoin: BaseCoin;
let verifyTransactionStub: sinon.SinonStub;

beforeEach(function () {
const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' });
bitgo.initializeTestVars();
bitgo.safeRegister('tbtc', Tbtc.createInstance);
basecoin = bitgo.coin('tbtc');

// Mock wallet data
const walletData = {
id: 'test-wallet-id',
coin: 'tbtc',
label: 'Test Wallet',
m: 2,
n: 3,
keys: ['key1', 'key2', 'key3'],
multisigType: 'onchain',
type: 'hot',
balance: 100000,
balanceString: '100000',
confirmedBalance: 100000,
confirmedBalanceString: '100000',
spendableBalance: 100000,
spendableBalanceString: '100000',
};

wallet = new Wallet(bitgo as unknown as BitGoBase, basecoin as unknown as BaseCoin, walletData);

// Create stubs for verification
sinon.stub(basecoin, 'signTransaction').resolves({ txHex: 'mock-signed-tx-hex' });
verifyTransactionStub = sinon.stub(basecoin, 'verifyTransaction');
});

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

it('should fail verification when verifyTransaction throws an error', async function () {
// Mock the verification function to throw an error (simulating verification failure)
verifyTransactionStub.throws(new Error('Transaction verification failed'));

const txPrebuild = {
txHex: 'mock-tx-hex',
walletId: 'test-wallet-id',
};

const verifyTxParams = {
txParams: {
recipients: [
{
address: 'test-address',
amount: '10000',
},
],
type: 'send',
},
};

const signParams: WalletSignTransactionOptions = {
txPrebuild,
verifyTxParams,
};

try {
await wallet.signTransaction(signParams);
assert.fail('Should have thrown verification error');
} catch (error) {
assert.ok(
error.message.includes('Transaction verification failed'),
`Error message should contain 'Transaction verification failed', got: ${error.message}`
);
}

// Verify that the verification function was called with the expected parameters
sinon.assert.calledOnce(verifyTransactionStub);
const callArgs = verifyTransactionStub.getCall(0).args;
const verifyParams = callArgs[0];
assert.strictEqual(verifyParams.txPrebuild.txHex, 'mock-tx-hex');
assert.deepStrictEqual(verifyParams.txParams, verifyTxParams.txParams);
});

it('should pass verification when verifyTransaction succeeds', async function () {
// Mock the verification function to succeed (no error thrown)
verifyTransactionStub.returns(true);

// Mock key retrieval endpoints
const bgUrl = common.Environments['mock'].uri;
nock(bgUrl).get('/api/v2/tbtc/key/key1').reply(200, {
id: 'key1',
pub: 'pub',
prv: 'prv',
});

nock(bgUrl).get('/api/v2/tbtc/key/key2').reply(200, {
id: 'key2',
pub: 'pub',
prv: 'prv',
});

nock(bgUrl).get('/api/v2/tbtc/key/key3').reply(200, {
id: 'key3',
pub: 'pub',
});

const txPrebuild = {
txHex: 'mock-tx-hex',
walletId: 'test-wallet-id',
};

const verifyTxParams: WalletSignTransactionOptions['verifyTxParams'] = {
txParams: {
recipients: [
{
address: 'test-address',
amount: '1000',
},
],
type: 'send',
},
};

const signParams: WalletSignTransactionOptions = {
txPrebuild,
verifyTxParams,
prv: 'prv',
};

const result = await wallet.signTransaction(signParams);

// Verify the result
result.should.have.property('txHex', 'mock-signed-tx-hex');

// Verify that the verification function was called with the expected parameters
sinon.assert.calledOnce(verifyTransactionStub);
const callArgs = verifyTransactionStub.getCall(0).args;
const verifyParams = callArgs[0];
assert.strictEqual(verifyParams.txPrebuild.txHex, 'mock-tx-hex');
assert.deepStrictEqual(verifyParams.txParams, verifyTxParams.txParams);
});
});
9 changes: 9 additions & 0 deletions modules/sdk-core/src/bitgo/wallet/iWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Message,
SignedMessage,
SignedTransaction,
TransactionParams,
TransactionPrebuild,
VerificationOptions,
TypedData,
Expand Down Expand Up @@ -274,6 +275,14 @@ export interface WalletSignTransactionOptions extends WalletSignBaseOptions {
apiVersion?: ApiVersion;
multisigTypeVersion?: 'MPCv2';
walletPassphrase?: string;
/**
* Optional transaction verification parameters. When provided, the transaction will be verified
* using verifyTransaction before signing.
*/
verifyTxParams?: {
txParams: TransactionParams;
verification?: VerificationOptions;
};
[index: string]: unknown;
}

Expand Down
19 changes: 18 additions & 1 deletion modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ import {
GetTransactionOptions,
GetTransferOptions,
GetUserPrvOptions,
IWallet,
type IWallet,
ManageUnspentReservationOptions,
MaximumSpendable,
MaximumSpendableOptions,
Expand Down Expand Up @@ -1953,6 +1953,9 @@ export class Wallet implements IWallet {
* - txPrebuild
* - [keychain / key] (object) or prv (string)
* - walletPassphrase
* - verifyTxParams (optional) - when provided, the transaction will be verified before signing
* - txParams: transaction parameters used for verification
* - verification: optional verification options
* @return {*}
*/
async signTransaction(params: WalletSignTransactionOptions = {}): Promise<SignedTransaction | TxRequest> {
Expand Down Expand Up @@ -1997,6 +2000,20 @@ export class Wallet implements IWallet {
params.txPrebuild = { txRequestId };
}

// Verify transaction if verifyTxParams is provided
if (params.verifyTxParams && txPrebuild?.txHex) {
const verifyParams = {
txPrebuild: { ...txPrebuild },
txParams: params.verifyTxParams.txParams,
wallet: this as IWallet,
verification: params.verifyTxParams.verification,
reqId: params.reqId,
walletType: this.multisigType() as 'onchain' | 'tss',
};

await this.baseCoin.verifyTransaction(verifyParams);
}

if (
params.walletPassphrase &&
!(params.keychain || params.key) &&
Expand Down