Skip to content
Draft
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
10 changes: 9 additions & 1 deletion modules/abstract-eth/src/abstractEthLikeCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from '@bitgo/sdk-core';
import BigNumber from 'bignumber.js';

import { isValidEthAddress, KeyPair as EthKeyPair, TransactionBuilder } from './lib';
import { isValidEthAddress, KeyPair as EthKeyPair, Transaction as EthTransaction, TransactionBuilder } from './lib';
import { VerifyEthAddressOptions } from './abstractEthLikeNewCoins';
import { auditEcdsaPrivateKey } from '@bitgo/sdk-lib-mpc';

Expand Down Expand Up @@ -230,6 +230,14 @@ export abstract class AbstractEthLikeCoin extends BaseCoin {
};
}

/** @inheritDoc */
async getSignablePayload(serializedTx: string): Promise<Buffer> {
const txBuilder = this.getTransactionBuilder();
txBuilder.from(serializedTx);
const tx = (await txBuilder.build()) as EthTransaction;
return tx.signablePayload;
}

/**
* Create a new transaction builder for the current chain
* @return a new transaction builder
Expand Down
5 changes: 5 additions & 0 deletions modules/abstract-eth/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export interface EthLikeTransactionData {
* Return the hex string serialization of this transaction
*/
toSerialized(): string;

/**
* Return the keccak256 hash of the unsigned transaction — the bytes AKM must sign
*/
getSignablePayload(): Buffer;
}

export interface SignatureParts {
Expand Down
8 changes: 8 additions & 0 deletions modules/abstract-eth/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ export class Transaction extends BaseTransaction {
this._signatures.push(toStringSig({ v: txData.v!, r: txData.r!, s: txData.s! }));
}

/** @inheritdoc */
get signablePayload(): Buffer {
if (!this._transactionData) {
throw new InvalidTransactionError('No transaction data to sign');
}
return this._transactionData.getSignablePayload();
}

/** @inheritdoc */
toBroadcastFormat(): string {
if (this._transactionData) {
Expand Down
4 changes: 4 additions & 0 deletions modules/abstract-eth/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class EthTransactionData implements EthLikeTransactionData {
this.tx = this.tx.sign(privateKey);
}

getSignablePayload(): Buffer {
return Buffer.from(this.tx.getMessageToSign(true));
}

/** @inheritdoc */
toJson(): TxData {
const result: BaseTxData = {
Expand Down
15 changes: 15 additions & 0 deletions modules/abstract-eth/test/unit/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,20 @@ export function runTransactionTests(coinName: string, testData: any, common: Com
should.equal(tx.toBroadcastFormat(), testData.ENCODED_TRANSACTION);
});
});

describe('signablePayload', () => {
it('should throw on an empty transaction', () => {
const tx = getTransaction();
should.throws(() => tx.signablePayload);
});

it('should return a 32-byte keccak256 hash for an unsigned transaction', () => {
const tx = getTransaction();
tx.setTransactionData(testData.TXDATA);
const payload = tx.signablePayload;
payload.should.be.instanceof(Buffer);
payload.length.should.equal(32);
});
});
});
}
54 changes: 54 additions & 0 deletions modules/sdk-coin-eth/test/unit/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2522,4 +2522,58 @@ describe('ETH:', function () {
});
});
});

describe('getSignablePayload', function () {
let coin: Teth;

before(function () {
coin = bitgo.coin('teth') as Teth;
});

it('should return a 32-byte keccak256 hash for a Legacy transaction', async function () {
const txBuilder = getBuilder('teth') as TransactionBuilder;
txBuilder.type(TransactionType.Send);
txBuilder.fee({ fee: '10000000000', gasLimit: '7000000' });
txBuilder.counter(1);
txBuilder.contract('0x8Ce59c2d1702844F8EdED451AA103961bC37B4e8');
const transferBuilder = txBuilder.transfer() as TransferBuilder;
transferBuilder
.coin('teth')
.expirationTime(Math.floor(Date.now() / 1000) + 3600)
.amount('100000')
.to('0xeeaf0F05f37891ab4a21208B105A0687d12c5aF7')
.contractSequenceId(1);
const tx = await txBuilder.build();
const serializedTx = tx.toBroadcastFormat();

const payload = await coin.getSignablePayload(serializedTx);
assert.ok(Buffer.isBuffer(payload));
assert.strictEqual(payload.length, 32);
});

it('should return a 32-byte keccak256 hash for an EIP1559 transaction', async function () {
const txBuilder = getBuilder('teth') as TransactionBuilder;
txBuilder.type(TransactionType.Send);
txBuilder.fee({
fee: '280000000000',
gasLimit: '7000000',
eip1559: { maxFeePerGas: '7593123', maxPriorityFeePerGas: '150' },
});
txBuilder.counter(1);
txBuilder.contract('0x8Ce59c2d1702844F8EdED451AA103961bC37B4e8');
const transferBuilder = txBuilder.transfer() as TransferBuilder;
transferBuilder
.coin('teth')
.expirationTime(Math.floor(Date.now() / 1000) + 3600)
.amount('100000')
.to('0xeeaf0F05f37891ab4a21208B105A0687d12c5aF7')
.contractSequenceId(1);
const tx = await txBuilder.build();
const serializedTx = tx.toBroadcastFormat();

const payload = await coin.getSignablePayload(serializedTx);
assert.ok(Buffer.isBuffer(payload));
assert.strictEqual(payload.length, 32);
});
});
});
16 changes: 16 additions & 0 deletions modules/sdk-coin-eth/test/unit/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,20 @@ describe('ETH Transaction', () => {
});
});
});

describe('signablePayload', () => {
it('should throw on an empty transaction', () => {
const tx = getTransaction();
assert.throws(() => tx.signablePayload);
});

testParams.map(([txnType, txData]) => {
it(`should return a 32-byte keccak256 hash for a ${txnType} transaction`, () => {
const tx = getTransaction(txData);
const payload = tx.signablePayload;
assert.ok(Buffer.isBuffer(payload));
assert.strictEqual(payload.length, 32);
});
});
});
});
Loading