PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-eth/test/unit
Просмотр файла: ethWallet.ts
import should from 'should';
import { bip32 } from '@bitgo/secp256k1';
import * as secp256k1 from 'secp256k1';
import nock from 'nock';
import sinon from 'sinon';
import { common, Util } from '@bitgo/sdk-core';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { Capability, Transaction as EthTx } from '@ethereumjs/tx';
const fixtures = require('../fixtures/eth');
import { BitGoAPI } from '@bitgo/sdk-api';
import { Erc20Token, SignTransactionOptions, Teth } from '../../src';
import { getBuilder } from './getBuilder';
describe('Sign ETH Transaction', async function () {
let bitgo: TestBitGoAPI;
let ethWallet;
let recipients;
let tx;
before(function () {
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
bitgo.initializeTestVars();
bitgo.safeRegister('teth', Teth.createInstance);
const coin = bitgo.coin('teth');
ethWallet = coin.newWalletObject({});
recipients = [
{
address: '0xe59dfe5c67114b39a5662cc856be536c614124c0',
amount: '100000',
},
];
tx = { recipients, nextContractSequenceId: 0 };
});
it('should read transaction recipients from txPrebuild even if none are specified as top-level params', async function () {
sinon.stub(Util, 'xprvToEthPrivateKey');
sinon.stub(Util, 'ethSignMsgHash');
sinon.stub(ethWallet.getOperationSha3ForExecuteAndConfirm);
const { halfSigned } = (await ethWallet.signTransaction({ txPrebuild: tx, prv: 'my_user_prv' })) as any;
halfSigned.should.have.property('recipients', recipients);
sinon.restore();
});
it('should throw an error if no recipients are in the txPrebuild and none are specified as params', async function () {
await ethWallet
.signTransaction({ txPrebuild: {}, prv: 'my_user_prv' })
.should.be.rejectedWith('recipients missing or not array');
});
it('should throw an error if the recipients param is not an array', async function () {
await ethWallet
.signTransaction({ txPrebuild: { recipients: 'not-array' }, prv: 'my_user_prv' })
.should.be.rejectedWith('recipients missing or not array');
});
it('should set isBatch to false if single recipient', async function () {
sinon.stub(Util, 'xprvToEthPrivateKey');
sinon.stub(Util, 'ethSignMsgHash');
sinon.stub(ethWallet.getOperationSha3ForExecuteAndConfirm);
const singleRecipientsTx = { recipients: recipients, nextContractSequenceId: 0, isBatch: false };
const { halfSigned } = (await ethWallet.signTransaction({
txPrebuild: singleRecipientsTx,
prv: 'my_user_prv',
})) as any;
halfSigned.should.have.property('recipients', recipients);
halfSigned.should.have.property('isBatch', false);
sinon.restore();
});
it('should set isBatch to true if multiple recipients', async function () {
const multipleRecipients = [
{
address: '0x0c7f3bc5d2b2c0dbee1b45536b82569f41b54331',
amount: '200',
data: '0xcf4c58e2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000431745b89e73230b3bc8a19e019194efb4b99efd000000000000000000000000431745b89e73230b3bc8a19e019194efb4b99efd000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064',
},
];
const multipleRecipientsTx = { recipients: multipleRecipients, nextContractSequenceId: 0, isBatch: true };
sinon.stub(Util, 'xprvToEthPrivateKey');
sinon.stub(Util, 'ethSignMsgHash');
sinon.stub(ethWallet.getOperationSha3ForExecuteAndConfirm);
const { halfSigned } = (await ethWallet.signTransaction({
txPrebuild: multipleRecipientsTx,
prv: 'my_user_prv',
})) as any;
halfSigned.should.have.property('isBatch', true);
sinon.restore();
});
});
describe('Ethereum Hop Transactions', function () {
let bitgo: TestBitGoAPI;
let ethWallet;
let tx;
let txid;
let bitgoSignature;
let bitgoKeyXprv;
let bgUrl;
let env;
const userKeypair = {
xprv: 'xprv9s21ZrQH143K2fJ91S4BRsupcYrE6mmY96fcX5HkhoTrrwmwjd16Cn87cWinJjByrfpojjx7ezsJLx7TAKLT8m8hM5Kax9YcoxnBeJZ3t2k',
xpub: 'xpub661MyMwAqRbcF9Nc7TbBo1rZAagiWEVPWKbDKThNG8zqjk76HAKLkaSbTn6dK2dQPfuD7xjicxCZVWvj67fP5nQ9W7QURmoMVAX8m6jZsGp',
rawPub: '02c103ac74481874b5ef0f385d12725e4f14aedc9e00bc814ce96f47f62ce7adf2',
rawPrv: '936c5af3f8af81f75cdad1b08f29e7d9c01e598e2db2d7be18b9e5a8646e87c6',
path: 'm',
walletSubPath: '/0/0',
};
before(function () {
tx =
'0xf86c82015285012a05f200825208945208d8e80c6d1aef9be37b4bd19a9cf75ed93dc886b5e620f480008026a00e13f9e0e11337b2b0227e3412211d3625e43f1083fda399cc361dd4bf89083ba06c801a761e0aa3bc8db0ac2568d575b0fb306a1f04f4d5ba82ba3cc0ea0a83bd';
txid = '0x0ac669c5fef8294443c75a31e32c44b97bbc9e43a18ea8beabcc2a3b45eb6ffa';
bitgoKeyXprv =
'xprv9s21ZrQH143K3tpWBHWe31sLoXNRQ9AvRYJgitkKxQ4ATFQMwvr7hHNqYRUnS7PsjzB7aK1VxqHLuNQjj1sckJ2Jwo2qxmsvejwECSpFMfC';
const bitgoKey = bip32.fromBase58(bitgoKeyXprv);
if (!bitgoKey.privateKey) {
throw new Error('no privateKey');
}
const bitgoXpub = bitgoKey.neutered().toBase58();
bitgoSignature =
'0xaa' +
Buffer.from(secp256k1.ecdsaSign(Buffer.from(txid.slice(2), 'hex'), bitgoKey.privateKey).signature).toString(
'hex'
);
env = 'test';
bitgo = TestBitGo.decorate(BitGoAPI, { env });
bitgo.safeRegister('teth', Teth.createInstance);
common.Environments[env].hsmXpub = bitgoXpub;
bitgo.initializeTestVars();
bgUrl = common.Environments[bitgo.getEnv()].uri;
const coin = bitgo.coin('teth');
ethWallet = coin.newWalletObject({ keys: ['user', 'backup', 'bitgo'] });
});
describe('Verify HSM Hop prebuild', function () {
let prebuild;
let buildParams;
let finalRecipient;
let sendAmount;
before(function () {
finalRecipient = '0x5208d8e80c6d1aef9be37b4bd19a9cf75ed93dc8';
sendAmount = '200000000000000';
prebuild = {
tx,
id: txid,
signature: bitgoSignature,
};
buildParams = {
recipients: [
{
address: finalRecipient,
amount: sendAmount,
},
],
};
});
it('should accept a valid hop prebuild', async function () {
await ethWallet.baseCoin.validateHopPrebuild(ethWallet, prebuild, buildParams).should.be.resolved();
});
it('should fail if the HSM prebuild recipient is wrong', async function () {
const badBuildParams = JSON.parse(JSON.stringify(buildParams));
badBuildParams.recipients[0].address = '0x54bf1609aeed804aa231f08c53dbb18f7d374615';
await ethWallet.baseCoin
.validateHopPrebuild(ethWallet, prebuild, badBuildParams)
.should.be.rejectedWith(/does not equal original recipient/);
});
it('should fail if the HSM prebuild tx amount is wrong', async function () {
const badBuildParams = JSON.parse(JSON.stringify(buildParams));
badBuildParams.recipients[0].amount = '50000000';
await ethWallet.baseCoin
.validateHopPrebuild(ethWallet, prebuild, badBuildParams)
.should.be.rejectedWith(/does not equal original amount/);
});
it('should fail if the HSM signature is invalid', async function () {
// Mocking a different BitGo key means the signing key should be wrong (it maps to a different address than this xpub)
const goodXpub = common.Environments[env].hsmXpub;
common.Environments[env].hsmXpub =
'xpub661MyMwAqRbcErFqVXGiUFv9YeoPbhN72UiNCUdj9nj3T6M8h7iKNmbCYpMVWVZP7LA2ma3HWcPngz1gRTm4FPdtm9mHfrNvU93MCoszsGL';
await ethWallet.baseCoin
.validateHopPrebuild(ethWallet, prebuild, buildParams)
.should.be.rejectedWith(/Hop txid signature invalid/);
common.Environments[env].hsmXpub = goodXpub;
});
it('should fail if the HSM signature signed the wrong HSM commitment digest', async function () {
const badTxid = '0xb4b3827a529c9166786e796528017889ac5027255b65b3fa2a3d3ad91244a12b';
const badTxidBuffer = Buffer.from(badTxid.slice(2), 'hex');
const xprvNode = bip32.fromBase58(bitgoKeyXprv);
if (!xprvNode.privateKey) {
throw new Error('no privateKey');
}
const badSignature =
'0xaa' + Buffer.from(secp256k1.ecdsaSign(badTxidBuffer, xprvNode.privateKey).signature).toString('hex');
const badPrebuild = JSON.parse(JSON.stringify(prebuild));
badPrebuild.signature = badSignature;
await ethWallet.baseCoin
.validateHopPrebuild(ethWallet, badPrebuild, buildParams)
.should.be.rejectedWith(/Hop txid signature invalid/);
});
});
describe('Prebuild hop transaction', function () {
let prebuild;
let buildParams;
let finalRecipient;
let sendAmount;
let gasLimitEstimate;
let gasPrice;
const nockUserKey = function () {
nock(bgUrl)
.get(`/api/v2/teth/key/user`)
.reply(200, {
encryptedPrv: bitgo.encrypt({ input: userKeypair.xprv, password: TestBitGo.TEST_WALLET1_PASSCODE }),
path: userKeypair.path + userKeypair.walletSubPath,
});
};
const nockFees = function () {
const scope = nock(bgUrl)
.get('/api/v2/teth/tx/fee')
.query(true)
.reply(200, {
gasLimitEstimate: gasLimitEstimate,
feeEstimate: gasLimitEstimate * gasPrice,
});
return scope;
};
const nockBuild = function (walletId) {
nock(bgUrl)
.post('/api/v2/teth/wallet/' + walletId + '/tx/build')
.reply(200, { hopTransaction: prebuild, buildParams });
};
before(function () {
gasLimitEstimate = 100000;
gasPrice = 50000000;
finalRecipient = '0x5208d8e80c6d1aef9be37b4bd19a9cf75ed93dc8';
sendAmount = '200000000000000';
prebuild = {
tx,
id: txid,
signature: bitgoSignature,
};
buildParams = {
recipients: [
{
address: finalRecipient,
amount: sendAmount,
},
],
hop: true,
walletPassphrase: TestBitGo.TEST_WALLET1_PASSCODE,
};
});
it('should prebuild a hop transaction if given the correct args', async function () {
nockUserKey();
const feeScope = nockFees();
nockBuild(ethWallet.id());
const res = (await ethWallet.prebuildTransaction(buildParams)) as any;
should.exist(res.hopTransaction);
should.exist(res.hopTransaction.tx);
should.exist(res.hopTransaction.tx);
should.exist(res.hopTransaction.id);
should.exist(res.hopTransaction.signature);
should.not.exist(res.wallet);
should.not.exist(res.buildParams);
feeScope.isDone().should.equal(true);
const feeReq = (feeScope as any).interceptors[0].req;
feeReq.path.should.containEql('hop=true');
feeReq.path.should.containEql('recipient=' + finalRecipient);
feeReq.path.should.containEql('amount=' + sendAmount);
});
});
});
describe('Add final signature to ETH tx from offline vault', function () {
let paramsFromVault, expectedResult, bitgo, coin;
before(function () {
const vals = fixtures.getHalfSignedTethFromVault();
paramsFromVault = vals.paramsFromVault;
expectedResult = vals.expectedResult;
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
coin = bitgo.coin('teth');
});
it('should successfully fully sign a half-signed transaction from the offline vault', async function () {
const response = (await coin.signTransaction(paramsFromVault)) as any;
const expectedTx = EthTx.fromSerializedTx(Buffer.from(expectedResult.txHex, 'hex'));
const actualTx = EthTx.fromSerializedTx(Buffer.from(response.txHex, 'hex'));
actualTx.nonce.toString().should.deepEqual(expectedTx.nonce.toString());
should.exist(actualTx.to);
actualTx.to?.should.deepEqual(expectedTx.to);
actualTx.value.should.deepEqual(expectedTx.value);
actualTx.data.should.deepEqual(expectedTx.data);
actualTx.isSigned().should.equal(true);
actualTx.supports(Capability.EIP155ReplayProtection).should.equal(false);
actualTx.verifySignature().should.equal(true);
should.exist(actualTx.v);
actualTx?.v?.toString().should.deepEqual(expectedTx?.v?.toString());
actualTx?.r?.toString().should.deepEqual(expectedTx?.r?.toString());
actualTx?.s?.toString().should.deepEqual(expectedTx?.s?.toString());
actualTx.gasPrice.toString().should.deepEqual(expectedTx.gasPrice.toString());
actualTx.gasLimit.toString().should.deepEqual(expectedTx.gasLimit.toString());
response.txHex.toString().should.equal(expectedResult.txHex.toString());
});
});
describe('Add signature to EIP1559 tx from offline vault', function () {
let bitgo: TestBitGoAPI;
let paramsFromVault, expectedResult, coin;
before(function () {
const vals = fixtures.getUnsignedEip1559TethFromVault();
paramsFromVault = vals.paramsFromVault;
expectedResult = vals.expectedResult;
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
bitgo.safeRegister('teth', Teth.createInstance);
coin = bitgo.coin('teth');
});
it('should successfully sign an unsigned transaction from the offline vault', async function* () {
const response = await coin.signTransaction(paramsFromVault);
should.exist(response.halfSigned);
response.halfSigned.eip1559.should.deepEqual(expectedResult.halfSigned.eip1559);
response.halfSigned.recipients.should.deepEqual(expectedResult.halfSigned.recipients);
});
});
describe('prebuildTransaction', function () {
let bitgo: TestBitGoAPI;
let ethWallet;
let recipients;
let bgUrl;
let gasLimit;
before(function () {
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
Erc20Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
bitgo.safeRegister(name, coinConstructor);
});
bitgo.safeRegister('teth', Teth.createInstance);
bitgo.initializeTestVars();
const coin = bitgo.coin('teth');
ethWallet = coin.newWalletObject({});
gasLimit = 2100000;
recipients = [
{
address: '0xe59dfe5c67114b39a5662cc856be536c614124c0',
amount: '100000',
},
];
bgUrl = common.Environments[bitgo.getEnv()].uri;
});
it('should successfully accept gasLimit as a param', async function () {
const scope = nock(bgUrl)
.post('/api/v2/teth/wallet/' + ethWallet.id() + '/tx/build', {
recipients,
gasLimit,
})
.reply(200, { success: true });
const prebuild = await ethWallet.prebuildTransaction({ recipients, gasLimit });
scope.isDone().should.equal(true);
prebuild.success.should.equal(true);
});
it('should reject hop param for an erc20 token build', async function () {
const token = bitgo.coin('terc');
const tokenWallet = token.newWalletObject({});
recipients = [
{
address: '0xe59dfe5c67114b39a5662cc856be536c614124c0',
amount: '100',
},
];
await tokenWallet
.prebuildTransaction({ recipients, hop: true, walletPassphrase: 'hi' })
.should.be.rejectedWith(
`Hop transactions are not enabled for ERC-20 tokens, nor are they necessary. Please remove the 'hop' parameter and try again.`
);
});
});
describe('final-sign transaction from WRW', function () {
it('should add a second signature to unsigned sweep for teth', async function () {
const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
const basecoin: any = bitgo.coin('teth');
const gasPrice = 200000000000;
const gasLimit = 500000;
const prv =
'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2'; // placeholder test prv
const tx = {
txPrebuild: fixtures.WRWUnsignedSweepETHTx,
prv,
};
// sign transaction once
const halfSigned = await basecoin.signTransaction(tx);
const wrapper = {} as SignTransactionOptions;
wrapper.txPrebuild = halfSigned;
wrapper.txPrebuild.recipients = halfSigned.halfSigned.recipients;
wrapper.txPrebuild.gasPrice = gasPrice.toString();
wrapper.txPrebuild.gasLimit = gasLimit.toString();
wrapper.isLastSignature = true;
wrapper.walletContractAddress = fixtures.WRWUnsignedSweepETHTx.walletContractAddress;
wrapper.prv = prv;
// sign transaction twice with the "isLastSignature" flag
const finalSignedTx = await basecoin.signTransaction(wrapper);
finalSignedTx.should.have.property('txHex');
const txBuilder = getBuilder('eth');
txBuilder.from('0x' + finalSignedTx.txHex); // add a 0x in front of this txhex
const rebuiltTx = await txBuilder.build();
const outputs = rebuiltTx.outputs.map((output) => {
return {
address: output.address,
amount: output.value,
};
});
rebuiltTx.signature.length.should.equal(2);
outputs.length.should.equal(1);
outputs[0].address.should.equal(fixtures.WRWUnsignedSweepETHTx.recipient.address);
outputs[0].amount.should.equal(fixtures.WRWUnsignedSweepETHTx.recipient.amount);
});
it('should add a second signature to unsigned sweep for erc20 token', async function () {
const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
Erc20Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
bitgo.safeRegister(name, coinConstructor);
});
const basecoin: any = bitgo.coin('tdai');
const gasPrice = 200000000000;
const gasLimit = 500000;
const prv =
'xprv9s21ZrQH143K3399QBVvbmhs4RB5QzXD8XiW3NwtaeTem93QGd5VNjukUnwJQ94nUgugHSVzSVVe3RP16Urv1ZyijpYdyDamsxf2Shbq4w1'; // placeholder test prv
const tx = {
txPrebuild: fixtures.WRWUnsignedSweepERC20Tx,
prv,
};
// sign transaction once
const halfSigned = await basecoin.signTransaction(tx);
const wrapper = {} as SignTransactionOptions;
wrapper.txPrebuild = halfSigned;
wrapper.txPrebuild.recipients = halfSigned.halfSigned.recipients;
wrapper.txPrebuild.gasPrice = gasPrice.toString();
wrapper.txPrebuild.gasLimit = gasLimit.toString();
wrapper.isLastSignature = true;
wrapper.walletContractAddress = fixtures.WRWUnsignedSweepERC20Tx.walletContractAddress;
wrapper.prv = prv;
// sign transaction twice with the "isLastSignature" flag
const finalSignedTx = await basecoin.signTransaction(wrapper);
finalSignedTx.should.have.property('txHex');
const txBuilder = getBuilder('eth');
txBuilder.from('0x' + finalSignedTx.txHex); // add a 0x in front of this txhex
const rebuiltTx = await txBuilder.build();
const outputs = rebuiltTx.outputs.map((output) => {
return {
address: output.address,
amount: output.value,
};
});
rebuiltTx.signature.length.should.equal(2);
outputs.length.should.equal(1);
outputs[0].address.should.equal(fixtures.WRWUnsignedSweepERC20Tx.recipient.address);
outputs[0].amount.should.equal(fixtures.WRWUnsignedSweepERC20Tx.recipient.amount);
});
});
Выполнить команду
Для локальной разработки. Не используйте в интернете!