Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(connect): eip1559 correct maxFeePerGas #15907

Merged
merged 4 commits into from
Dec 11, 2024
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
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import { LegacyTxData } from '@ethereumjs/tx';
import { EthereumTransaction, EthereumTransactionEIP1559 } from '../../../exports';

interface EthereumTxFixture {
description: string;
chainId: number;
tx: LegacyTxData; // + TODO: FeeMarketEIP1559TxData
type?: number;
tx: EthereumTransactionEIP1559 | EthereumTransaction;
signature: {
v: `0x${string}`;
r: `0x${string}`;
s: `0x${string}`;
};
from: string;
result: string;
}

export const serializeEthereumTx: EthereumTxFixture[] = [
{
// https://eth1.trezor.io/tx/0xf6652a681b4474132b8b96512eb0bd5311f5ed8414af59e715c9738a3b3673f3
description: 'Legacy tx - ETH regular',
chainId: 1,
from: '0x73d0385f4d8e00c5e6504c6030f47bf6212736a8',
tx: {
// data sent to TrezorConnect.ethereumSignTransaction
chainId: 1,
to: '0x4dC573D5DB497C0bF0674599E81c7dB91151D4e6',
value: '0x3905f13a8f0e',
nonce: '0x12',
gasLimit: '0x5208',
gasPrice: '0x104c533c00',
data: '0x',
// data received from TrezorConnect.ethereumSignTransaction
},
// data received from TrezorConnect.ethereumSignTransaction
signature: {
v: '0x1c',
r: '0x4256ec5ddf73f12f781e9f646f56fd8843296cf3eb7e2fb8f0b67ea317be3e7c',
s: '0x7be26525b6d6d39ef8745801bbb463c35ede09746708316a011e6eee7a2d83cf',
Expand All @@ -30,14 +39,17 @@ export const serializeEthereumTx: EthereumTxFixture[] = [
{
// https://eth1.trezor.io/tx/0xdcaf3eba690a3cdbad8c2926a8f5a95cd20003c5ba2aace91d8c5fe8048e395b
description: 'Legacy tx - ETH with ERC20',
chainId: 1,
from: '0x73d0385f4d8e00c5e6504c6030f47bf6212736a8',
tx: {
chainId: 1,
to: '0xa74476443119A942dE498590Fe1f2454d7D4aC0d',
value: '0x0',
nonce: '0xb',
gasLimit: '0x30d40',
gasPrice: '0x12a05f200',
data: '0xa9059cbb000000000000000000000000a6abb480640d6d27d2fb314196d94463cedcb31e0000000000000000000000000000000000000000000000000011c37937e08000',
},
signature: {
v: '0x26',
r: '0x47ef1bb1625e4152b0febf6ddc1a57bfcea6438132928dda4c9c092b34f38a78',
s: '0x3f70084c300235d588b70103988dd6f367e0f67bf38e2759a4c77aa461b220e2',
Expand All @@ -47,20 +59,42 @@ export const serializeEthereumTx: EthereumTxFixture[] = [
{
// https://etc1.trezor.io/tx/0xebd7ef20c4358a6fdb09a951d6e77b8e88b37ac0f7a8d4e3b68f1666bf4c1d1a
description: 'Legacy tx - ETC regular',
chainId: 61,
from: '0xf410e37e9c8bcf8cf319c84ae9dcebe057804a04',
tx: {
chainId: 61,
to: '0xABE894C18832edbe9B7926D729FA950673faD1EC',
value: '0x56c212a8e4628',
nonce: '0x0',
gasLimit: '0x5208',
gasPrice: '0x5409c6a7b',
data: '0x',
},
signature: {
v: '0x9e',
r: '0x9d4599beedc587e0dc3d88578d79573c0138f9389810ffb036c37423ccd86375',
s: '0x4a0eb870fbae9a11a02e3e0068830d141ee952bb4ab4d1e1b7542d75f7a24dc1',
},
result: '0xebd7ef20c4358a6fdb09a951d6e77b8e88b37ac0f7a8d4e3b68f1666bf4c1d1a',
},
{
description: 'EIP-1559 tx - ETH contract call',
type: 2,
from: '0x73d0385f4d8e00c5e6504c6030f47bf6212736a8',
tx: {
chainId: 137,
to: '0x958c6ABB69FeF37CB1E8f1bABB745da3bec78F86',
value: '0x2386f26fc10000',
nonce: '0x13',
gasLimit: '0x5e56',
maxFeePerGas: '0xb2d2318f8',
maxPriorityFeePerGas: '0xb2d215740',
data: '0x',
},
signature: {
v: '0x0',
r: '0x3e6f0a17f515ccfdfdd5d6320dcc006959e942b715e78a52decb5d93ffb97af5',
s: '0x2f45463634e03d918ac099db760b2ca47f9f1ba900d86bf670a21cb46c41ddbe',
},
result: '0xdbfebc05b18c0b5a1e28e8f93283409c6c58fa0cd09610d2f0c580751d535de0',
},
];

// TODO: add EIP1559 tx type
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ describe('helpers/ethereumSignTx', () => {
describe('serializeEthereumTx', () => {
fixtures.serializeEthereumTx.forEach(f => {
it(f.description, () => {
// ETC is not supported
if (f.chainId !== 61) {
const tx = TransactionFactory.fromTxData(f.tx);
const hash1 = Buffer.from(tx.hash()).toString('hex');
expect(`0x${hash1}`).toEqual(f.result);
}
const serialized = serializeEthereumTx({ ...f.tx, type: 0 }, f.chainId);
const hash2 = toHex(
const isLegacy = f.type === undefined || f.type === 0;
const serialized = serializeEthereumTx(f.tx, f.signature, isLegacy);

// Verify signature hash
const hash = toHex(
keccak256(Uint8Array.from(Buffer.from(serialized.slice(2), 'hex'))),
);
expect(hash2).toEqual(f.result);
expect(hash).toEqual(f.result);

// Verify by parsing the serialized tx
const tx = TransactionFactory.fromSerializedData(
Buffer.from(serialized.slice(2), 'hex'),
);
const hash2 = Buffer.from(tx.hash()).toString('hex');
expect(`0x${hash2}`).toEqual(f.result);

// Compare sender address (based on signature)
expect(tx.getSenderAddress().toString()).toEqual(f.from);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/EthereumSignTransaction.js

import { FeeMarketEIP1559TxData, LegacyTxData } from '@ethereumjs/tx';

import { MessagesSchema } from '@trezor/protobuf';
import { Assert } from '@trezor/schema-utils';

Expand Down Expand Up @@ -155,16 +153,7 @@ export default class EthereumSignTransaction extends AbstractMethod<
definitions,
);

const txData = {
...tx,
...signature,
type: isLegacy ? 0 : 2, // 0 for legacy, 2 for EIP-1559
gasPrice: isLegacy ? tx.gasPrice : null,
maxFeePerGas: isLegacy ? tx.maxFeePerGas : tx.maxPriorityFeePerGas,
maxPriorityFeePerGas: !isLegacy ? tx.maxPriorityFeePerGas : undefined,
} as LegacyTxData | FeeMarketEIP1559TxData;

const serializedTx = helper.serializeEthereumTx(txData, tx.chainId);
const serializedTx = helper.serializeEthereumTx(tx, signature, isLegacy);

return { ...signature, serializedTx };
}
Expand Down
62 changes: 46 additions & 16 deletions packages/connect/src/api/ethereum/ethereumSignTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { MessagesSchema } from '@trezor/protobuf';

import { PROTO, ERRORS } from '../../constants';
import type { TypedCall } from '../../device/DeviceCommands';
import type { EthereumAccessList } from '../../types/api/ethereum';
import type {
EthereumAccessList,
EthereumTransaction,
EthereumTransactionEIP1559,
} from '../../types/api/ethereum';
import { addHexPrefix, deepTransform } from '../../utils/formatUtils';

const splitString = (str?: string, len?: number) => {
Expand All @@ -26,9 +30,9 @@ const processTxRequest = async (
data?: string,
chain_id?: number,
): Promise<{
v: string;
r: string;
s: string;
v: `0x${string}`;
r: `0x${string}`;
s: `0x${string}`;
}> => {
if (!request.data_length) {
let v = request.signature_v;
Expand Down Expand Up @@ -59,24 +63,50 @@ const processTxRequest = async (

const deepHexPrefix = deepTransform(addHexPrefix);

export const serializeEthereumTx = (
txData: LegacyTxData | FeeMarketEIP1559TxData,
chainId: number,
) => {
export const getCommonForChain = (chainId: number) => {
// @ethereumjs/tx directly supported chains
if (Common.isSupportedChainId(BigInt(chainId))) return new Common({ chain: chainId });

// @ethereumjs/tx doesn't support ETC (chain 61) by default
// and it needs to be declared as custom chain
// see: https://github.com/ethereumjs/ethereumjs-tx/blob/master/examples/custom-chain-tx.ts
const txOptions =
chainId === 61
if (chainId === 61)
return Common.custom(
{ name: 'ethereum-classic', networkId: 1, chainId: 61 },
{ baseChain: Chain.Mainnet, hardfork: Hardfork.Petersburg },
);

// other chains
return Common.custom({ chainId });
};

export const serializeEthereumTx = (
tx: EthereumTransactionEIP1559 | EthereumTransaction,
signature: { v: `0x${string}`; r: `0x${string}`; s: `0x${string}` },
isLegacy: boolean,
) => {
const txData = deepHexPrefix({
...tx,
...signature,
type: isLegacy ? 0 : 2, // 0 for legacy, 2 for EIP-1559
...(isLegacy
? {
common: Common.custom(
{ name: 'ethereum-classic', networkId: 1, chainId: 61 },
{ baseChain: Chain.Mainnet, hardfork: Hardfork.Petersburg },
),
gasPrice: tx.gasPrice,
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
}
: { chain: chainId };
: {
gasPrice: undefined,
maxFeePerGas: tx.maxFeePerGas,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
}),
}) satisfies LegacyTxData | FeeMarketEIP1559TxData;

const txOptions = {
common: getCommonForChain(tx.chainId),
};

const ethTx = TransactionFactory.fromTxData(deepHexPrefix(txData), txOptions);
const ethTx = TransactionFactory.fromTxData(txData, txOptions);

return `0x${Buffer.from(ethTx.serialize()).toString('hex')}`;
};
Expand Down
24 changes: 16 additions & 8 deletions packages/connect/src/utils/formatUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const hasHexPrefix = (str: string) => str.slice(0, 2).toLowerCase() === '

export const stripHexPrefix = (str: string) => (hasHexPrefix(str) ? str.slice(2) : str);

export const addHexPrefix = (str: string) =>
str !== undefined && !hasHexPrefix(str) ? `0x${str}` : str;
export const addHexPrefix = (str: string): `0x${string}` =>
str !== undefined && !hasHexPrefix(str) ? `0x${str}` : (str as `0x${string}`);

// from (isHexString) https://github.com/ethjs/ethjs-util/blob/master/src/index.js
const isHexString = (value: string, length?: number) => {
Expand Down Expand Up @@ -65,23 +65,31 @@ export const messageToHex = (message: string) => {
return buffer.toString('hex');
};

export const deepTransform = (transform: (str: string) => string) => {
const recursion = <T>(value: T): T => {
export const deepTransform = <V>(transform: (str: string) => V) => {
const recursion = <T>(value: T): DeepTransformed<T, V> => {
if (typeof value === 'string') {
return transform(value) as T;
return transform(value) as DeepTransformed<T, V>;
}
if (Array.isArray(value)) {
return value.map(recursion) as T;
return value.map(recursion) as DeepTransformed<T, V>;
}
if (value && typeof value === 'object') {
return Object.entries(value).reduce(
(obj, [k, v]) => ({ ...obj, [k]: recursion(v) }),
{},
) as T;
) as DeepTransformed<T, V>;
}

return value;
return value as DeepTransformed<T, V>;
};

return recursion;
};

type DeepTransformed<T, V> = T extends string
? V
: T extends (infer U)[]
? DeepTransformed<U, V>[]
: T extends object
? { [K in keyof T]: DeepTransformed<T[K], V> }
: T;
Loading