From b2bee5b7ae6556aea26c55e83a74088e47715137 Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:22:35 +0200 Subject: [PATCH 1/8] Remove `ethers` full lib from dependencies It should be decreasing the bundle size by using only sub librairies --- .../ledgerjs/packages/hw-app-eth/package.json | 9 +- .../src/modules/Uniswap/decoders.ts | 6 +- .../hw-app-eth/src/modules/Uniswap/index.ts | 5 +- .../hw-app-eth/src/services/ledger/index.ts | 10 +- .../ledgerjs/packages/hw-app-eth/src/utils.ts | 3 +- .../tests/ERC20/ERC20-CAL-KO.unit.test.ts | 15 +- .../tests/ERC20/ERC20-CAL-OK.unit.test.ts | 15 +- .../hw-app-eth/tests/Eth.unit.test.ts | 267 +++++++++--------- .../tests/Uniswap/index.unit.test.ts | 30 +- .../hw-app-eth/tests/fixtures/utils.ts | 35 ++- pnpm-lock.yaml | 85 +++++- 11 files changed, 270 insertions(+), 210 deletions(-) diff --git a/libs/ledgerjs/packages/hw-app-eth/package.json b/libs/ledgerjs/packages/hw-app-eth/package.json index 5e238cf3ce9e..194c261f70c6 100644 --- a/libs/ledgerjs/packages/hw-app-eth/package.json +++ b/libs/ledgerjs/packages/hw-app-eth/package.json @@ -26,8 +26,9 @@ "types": "lib/Eth.d.ts", "license": "Apache-2.0", "dependencies": { - "@ethersproject/abi": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", + "@ethersproject/abi": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", "@ledgerhq/cryptoassets-evm-signatures": "workspace:^", "@ledgerhq/domain-service": "workspace:^", "@ledgerhq/errors": "workspace:^", @@ -53,10 +54,12 @@ }, "gitHead": "dd0dea64b58e5a9125c8a422dcffd29e5ef6abec", "devDependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/units": "^5.7.0", "@types/jest": "^29.5.10", "@types/node": "^20.8.10", "documentation": "14.0.2", - "ethers": "5.7.2", "jest": "^29.7.0", "nock": "^13.0.5", "rimraf": "^4.4.1", diff --git a/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/decoders.ts b/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/decoders.ts index 24ca1ce6df71..d5cbcd1a4dde 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/decoders.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/decoders.ts @@ -1,9 +1,9 @@ -import { utils } from "ethers"; +import { defaultAbiCoder } from "@ethersproject/abi"; import { WETH_PER_CHAIN_ID } from "./constants"; import { UniswapSupportedCommand } from "./types"; const swapV2Decoder = (input: `0x${string}`): `0x${string}`[] => { - const [, , , addresses] = utils.defaultAbiCoder.decode( + const [, , , addresses] = defaultAbiCoder.decode( ["address", "uint256", "uint256", "address[]", "bool"], input, ); @@ -12,7 +12,7 @@ const swapV2Decoder = (input: `0x${string}`): `0x${string}`[] => { }; const swapV3Decoder = (input: `0x${string}`): `0x${string}`[] => { - const [, , , path] = utils.defaultAbiCoder.decode( + const [, , , path] = defaultAbiCoder.decode( ["address", "uint256", "uint256", "bytes", "bool"], input, ); diff --git a/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/index.ts b/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/index.ts index 579aee75fbd4..2af0a59528d4 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/index.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/modules/Uniswap/index.ts @@ -1,5 +1,6 @@ import { log } from "@ledgerhq/logs"; -import { utils, Transaction } from "ethers"; +import { Interface } from "@ethersproject/abi"; +import type { Transaction } from "@ethersproject/transactions"; import { byContractAddressAndChainId, findERC20SignaturesInfo } from "../../services/ledger/erc20"; import { LoadConfig } from "../../services/types"; import { UniswapDecoders } from "./decoders"; @@ -81,7 +82,7 @@ export const getCommandsAndTokensFromUniswapCalldata = ( chainId: number, ): CommandsAndTokens => { try { - const [commands, inputs] = new utils.Interface([ + const [commands, inputs] = new Interface([ "function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable", ]).decodeFunctionData("execute", calldata) as [`0x${string}`, `0x${string}`[]]; diff --git a/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts b/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts index 42ba367fba73..19c06419885c 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts @@ -1,4 +1,5 @@ -import { utils } from "ethers"; +import { parse as parseTransaction } from "@ethersproject/transactions"; +import { Interface } from "@ethersproject/abi"; import { log } from "@ledgerhq/logs"; import { signDomainResolution, @@ -133,8 +134,8 @@ const loadNanoAppPlugins = async ( } if (erc20OfInterest && erc20OfInterest.length && abi) { - const contract = new utils.Interface(abi); - const args = contract.parseTransaction(decodedTx).args; + const contract = new Interface(abi); + const args = contract.parseTransaction(parsedTransaction).args; for (const path of erc20OfInterest) { const erc20ContractAddress = path.split(".").reduce((value, seg) => { @@ -189,7 +190,8 @@ const resolveTransaction: LedgerEthTransactionService["resolveTransaction"] = as resolutionConfig, ) => { const rawTx = Buffer.from(rawTxHex, "hex"); - const { decodedTx, chainIdTruncated } = decodeTxInfo(rawTx); + const parsedTransaction = parseTransaction(`0x${rawTx.toString("hex")}`); + const chainIdUint4 = decodeTxInfo(rawTx); const { domains } = resolutionConfig; const contractAddress = decodedTx.to; diff --git a/libs/ledgerjs/packages/hw-app-eth/src/utils.ts b/libs/ledgerjs/packages/hw-app-eth/src/utils.ts index 307243daad3e..98800d7fe72a 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/utils.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/utils.ts @@ -1,10 +1,11 @@ import { BigNumber } from "bignumber.js"; +import * as rlp from "@ethersproject/rlp"; import { ERC20_CLEAR_SIGNED_SELECTORS, ERC721_CLEAR_SIGNED_SELECTORS, ERC1155_CLEAR_SIGNED_SELECTORS, } from "@ledgerhq/evm-tools/selectors/index"; -import { encode, decode } from "@ethersproject/rlp"; +import type { Transaction } from "@ethersproject/transactions"; import { LedgerEthTransactionResolution } from "./services/types"; export { diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-KO.unit.test.ts b/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-KO.unit.test.ts index 6a0f73bc636b..8203cf957203 100644 --- a/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-KO.unit.test.ts +++ b/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-KO.unit.test.ts @@ -1,24 +1,25 @@ +import path from "path"; import axios from "axios"; import fs from "fs/promises"; -import path from "path"; +import { BigNumber } from "@ethersproject/bignumber"; import evms from "@ledgerhq/cryptoassets-evm-signatures/lib/data/evm/index"; import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker"; +import { serialize as serializeTransaction, type Transaction } from "@ethersproject/transactions"; import { EthAppPleaseEnableContractData } from "../../src/errors"; import SignatureCALEth from "../fixtures/SignatureCALEth"; import ledgerService from "../../src/services/ledger"; import Eth from "../../src/Eth"; -import { ethers } from "ethers"; -const transaction: ethers.Transaction = { +const transaction: Transaction = { to: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", nonce: 14, - gasPrice: ethers.BigNumber.from("0x06a2bb7d00"), - gasLimit: ethers.BigNumber.from("0x01512c"), - value: ethers.BigNumber.from("0x00"), + gasPrice: BigNumber.from("0x06a2bb7d00"), + gasLimit: BigNumber.from("0x01512c"), + value: BigNumber.from("0x00"), data: "0xa9059cbb00000000000000000000000082ec3523f8a722694ca217ebfd95efbcadad77ee000000000000000000000000000000000000000000000002b5e3af16b1880000", chainId: 1, }; -const txHex = ethers.utils.serializeTransaction(transaction).slice(2); +const txHex = serializeTransaction(transaction).slice(2); jest.mock("@ledgerhq/cryptoassets-evm-signatures/data/evm/index", () => ({ get signatures() { diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-OK.unit.test.ts b/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-OK.unit.test.ts index 9a4699398cb7..07f0a8407903 100644 --- a/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-OK.unit.test.ts +++ b/libs/ledgerjs/packages/hw-app-eth/tests/ERC20/ERC20-CAL-OK.unit.test.ts @@ -1,22 +1,23 @@ import axios from "axios"; import path from "path"; import fs from "fs/promises"; -import { ethers } from "ethers"; +import { BigNumber } from "@ethersproject/bignumber"; import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker"; +import { serialize as serializeTransaction, type Transaction } from "@ethersproject/transactions"; import SignatureCALEth from "../fixtures/SignatureCALEth"; import ledgerService from "../../src/services/ledger"; import Eth from "../../src/Eth"; -const transaction: ethers.Transaction = { +const transaction: Transaction = { to: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", nonce: 14, - gasPrice: ethers.BigNumber.from("0x06a2bb7d00"), - gasLimit: ethers.BigNumber.from("0x01512c"), - value: ethers.BigNumber.from("0x00"), + gasPrice: BigNumber.from("0x06a2bb7d00"), + gasLimit: BigNumber.from("0x01512c"), + value: BigNumber.from("0x00"), data: "0xa9059cbb00000000000000000000000082ec3523f8a722694ca217ebfd95efbcadad77ee000000000000000000000000000000000000000000000002b5e3af16b1880000", chainId: 1, }; -const txHex = ethers.utils.serializeTransaction(transaction).slice(2); +const txHex = serializeTransaction(transaction).slice(2); describe("ERC20 dynamic cal", () => { describe("ERC20 is in local CAL", () => { @@ -87,4 +88,4 @@ describe("ERC20 dynamic cal", () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts b/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts index 5dd7aa2d5fc5..5c9c384edbaa 100644 --- a/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts +++ b/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts @@ -1,8 +1,11 @@ import nock from "nock"; import axios from "axios"; import { fail } from "assert"; -import { ethers } from "ethers"; import BigNumber from "bignumber.js"; +import { Interface } from "@ethersproject/abi"; +import { parseUnits } from "@ethersproject/units"; +import { BigNumber as EthersBigNumber } from "@ethersproject/bignumber"; +import { serialize as serializeTransaction } from "@ethersproject/transactions"; import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker"; import Eth, { ledgerService } from "../src/Eth"; import CAL_ETH from "./fixtures/SignatureCALEth"; @@ -36,18 +39,16 @@ describe("Eth app biding", () => { const eth = new Eth(transport); const result = await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: "0xB0b5B0106D69fE64545A60A68C014f7570D3F861", - value: ethers.BigNumber.from("1"), - gasLimit: ethers.BigNumber.from("21000"), - maxPriorityFeePerGas: ethers.BigNumber.from("400000000"), - maxFeePerGas: ethers.BigNumber.from("52925678988"), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: "0xB0b5B0106D69fE64545A60A68C014f7570D3F861", + value: EthersBigNumber.from("1"), + gasLimit: EthersBigNumber.from("21000"), + maxPriorityFeePerGas: EthersBigNumber.from("400000000"), + maxFeePerGas: EthersBigNumber.from("52925678988"), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true }, true, ); @@ -77,25 +78,23 @@ describe("Eth app biding", () => { ); const eth = new Eth(transport); - const contract = new ethers.utils.Interface(ERC20_ABI); + const contract = new Interface(ERC20_ABI); const result = await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", - value: ethers.BigNumber.from("0"), - gasLimit: ethers.BigNumber.from("64805"), - maxPriorityFeePerGas: ethers.BigNumber.from("550000000"), - maxFeePerGas: ethers.BigNumber.from("60105744798"), - data: contract.encodeFunctionData("transfer", [ - "0xb0b5b0106d69fe64545a60a68c014f7570d3f861", - ethers.utils.parseUnits("1", 18), - ]), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", + value: EthersBigNumber.from("0"), + gasLimit: EthersBigNumber.from("64805"), + maxPriorityFeePerGas: EthersBigNumber.from("550000000"), + maxFeePerGas: EthersBigNumber.from("60105744798"), + data: contract.encodeFunctionData("transfer", [ + "0xb0b5b0106d69fe64545a60a68c014f7570d3f861", + parseUnits("1", 18), + ]), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true }, true, ); @@ -146,29 +145,27 @@ describe("Eth app biding", () => { ); const eth = new Eth(transport); - const contract = new ethers.utils.Interface(ERC721_ABI); + const contract = new Interface(ERC721_ABI); const result = await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: contractAddr, - value: ethers.BigNumber.from("0"), - gasLimit: ethers.BigNumber.from("145513"), - maxPriorityFeePerGas: ethers.BigNumber.from("1150000000"), - maxFeePerGas: ethers.BigNumber.from("79336166290"), - data: contract.encodeFunctionData( - contract.getFunction("safeTransferFrom(address,address,uint256)"), - [ - "0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d", - "0xb0b5b0106d69fe64545a60a68c014f7570d3f861", - "1124761", - ], - ), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: contractAddr, + value: EthersBigNumber.from("0"), + gasLimit: EthersBigNumber.from("145513"), + maxPriorityFeePerGas: EthersBigNumber.from("1150000000"), + maxFeePerGas: EthersBigNumber.from("79336166290"), + data: contract.encodeFunctionData( + contract.getFunction("safeTransferFrom(address,address,uint256)"), + [ + "0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d", + "0xb0b5b0106d69fe64545a60a68c014f7570d3f861", + "1124761", + ], + ), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true }, true, ); @@ -219,28 +216,26 @@ describe("Eth app biding", () => { ); const eth = new Eth(transport); - const contract = new ethers.utils.Interface(ERC1155_ABI); + const contract = new Interface(ERC1155_ABI); const result = await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: contractAddr, - value: ethers.BigNumber.from("0"), - gasLimit: ethers.BigNumber.from("68276"), - maxPriorityFeePerGas: ethers.BigNumber.from("350000000"), - maxFeePerGas: ethers.BigNumber.from("57259265704"), - data: contract.encodeFunctionData("safeTransferFrom", [ - "0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d", - "0xb0b5b0106d69fe64545a60a68c014f7570d3f861", - "263196", - "1", - "0x", - ]), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: contractAddr, + value: EthersBigNumber.from("0"), + gasLimit: EthersBigNumber.from("68276"), + maxPriorityFeePerGas: EthersBigNumber.from("350000000"), + maxFeePerGas: EthersBigNumber.from("57259265704"), + data: contract.encodeFunctionData("safeTransferFrom", [ + "0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d", + "0xb0b5b0106d69fe64545a60a68c014f7570d3f861", + "263196", + "1", + "0x", + ]), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true }, true, ); @@ -290,40 +285,38 @@ describe("Eth app biding", () => { ); const eth = new Eth(transport); - const contract = new ethers.utils.Interface(PARASWAP_ABI); + const contract = new Interface(PARASWAP_ABI); const result = await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: transactionContracts.paraswap, - value: ethers.BigNumber.from("0"), - gasLimit: ethers.BigNumber.from("298891"), - maxPriorityFeePerGas: ethers.BigNumber.from("1150000000"), - maxFeePerGas: ethers.BigNumber.from("86969174616"), - data: contract.encodeFunctionData("simpleSwap", [ - [ - "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", // MATIC - "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI - "0x0de0b6b3a7640000", // 1 MATIC - "0x14655db2d8c71619", // ~1.469 DAI - "0x147f9aa1bc47718c", // EXPECT 1.477 DAI - ["0xE592427A0AEce92De3Edee1F18E0157C05861564"], - "0xc04b8d59000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee5700000000000000000000000000000000000000000000000000000000640b9d320000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b7d1afa7b718fb893db30a3abc0cfc608aacfebb00027106b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000", - ["0x00", "0x0124"], - ["0x00"], - "0x0000000000000000000000000000000000000000", - "0x558247e365be655f9144e1a0140D793984372Ef3", - "0x010000000000000000000000000000000000000000000000000000000000405f", - "0x", - "0x640be382", - "0x3d2fae4b5ec240cd871aa6b675e99899", - ], - ]), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: transactionContracts.paraswap, + value: EthersBigNumber.from("0"), + gasLimit: EthersBigNumber.from("298891"), + maxPriorityFeePerGas: EthersBigNumber.from("1150000000"), + maxFeePerGas: EthersBigNumber.from("86969174616"), + data: contract.encodeFunctionData("simpleSwap", [ + [ + "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", // MATIC + "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI + "0x0de0b6b3a7640000", // 1 MATIC + "0x14655db2d8c71619", // ~1.469 DAI + "0x147f9aa1bc47718c", // EXPECT 1.477 DAI + ["0xE592427A0AEce92De3Edee1F18E0157C05861564"], + "0xc04b8d59000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee5700000000000000000000000000000000000000000000000000000000640b9d320000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b7d1afa7b718fb893db30a3abc0cfc608aacfebb00027106b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000", + ["0x00", "0x0124"], + ["0x00"], + "0x0000000000000000000000000000000000000000", + "0x558247e365be655f9144e1a0140D793984372Ef3", + "0x010000000000000000000000000000000000000000000000000000000000405f", + "0x", + "0x640be382", + "0x3d2fae4b5ec240cd871aa6b675e99899", + ], + ]), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true, uniswapV3: true }, true, ); @@ -372,19 +365,17 @@ describe("Eth app biding", () => { const eth = new Eth(transport); const result = await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: transactionContracts.uniswapUniversaRouter, - value: ethers.BigNumber.from("0"), - gasLimit: ethers.BigNumber.from("298891"), - maxPriorityFeePerGas: ethers.BigNumber.from("1150000000"), - maxFeePerGas: ethers.BigNumber.from("86969174616"), - data: transactionData.uniswap["permit2>swap-out-v3>unwrap"], - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: transactionContracts.uniswapUniversaRouter, + value: EthersBigNumber.from("0"), + gasLimit: EthersBigNumber.from("298891"), + maxPriorityFeePerGas: EthersBigNumber.from("1150000000"), + maxFeePerGas: EthersBigNumber.from("86969174616"), + data: transactionData.uniswap["permit2>swap-out-v3>unwrap"], + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true, uniswapV3: true }, true, ); @@ -403,18 +394,16 @@ describe("Eth app biding", () => { try { await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: "0xB0b5B0106D69fE64545A60A68C014f7570D3F861", - value: ethers.BigNumber.from("1"), - gasLimit: ethers.BigNumber.from("21000"), - maxPriorityFeePerGas: ethers.BigNumber.from("400000000"), - maxFeePerGas: ethers.BigNumber.from("52925678988"), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: "0xB0b5B0106D69fE64545A60A68C014f7570D3F861", + value: EthersBigNumber.from("1"), + gasLimit: EthersBigNumber.from("21000"), + maxPriorityFeePerGas: EthersBigNumber.from("400000000"), + maxFeePerGas: EthersBigNumber.from("52925678988"), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true }, true, ); @@ -441,18 +430,16 @@ describe("Eth app biding", () => { try { await eth.clearSignTransaction( "44'/60'/0'/0/0", - ethers.utils - .serializeTransaction({ - to: "0xB0b5B0106D69fE64545A60A68C014f7570D3F861", - value: ethers.BigNumber.from("1"), - gasLimit: ethers.BigNumber.from("21000"), - maxPriorityFeePerGas: ethers.BigNumber.from("400000000"), - maxFeePerGas: ethers.BigNumber.from("52925678988"), - chainId: 1, - nonce: 0, - type: 2, - }) - .substring(2), + serializeTransaction({ + to: "0xB0b5B0106D69fE64545A60A68C014f7570D3F861", + value: EthersBigNumber.from("1"), + gasLimit: EthersBigNumber.from("21000"), + maxPriorityFeePerGas: EthersBigNumber.from("400000000"), + maxFeePerGas: EthersBigNumber.from("52925678988"), + chainId: 1, + nonce: 0, + type: 2, + }).slice(2), { erc20: true, externalPlugins: true, nft: true }, false, ); diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/Uniswap/index.unit.test.ts b/libs/ledgerjs/packages/hw-app-eth/tests/Uniswap/index.unit.test.ts index b067706d696d..b70efe9869b3 100644 --- a/libs/ledgerjs/packages/hw-app-eth/tests/Uniswap/index.unit.test.ts +++ b/libs/ledgerjs/packages/hw-app-eth/tests/Uniswap/index.unit.test.ts @@ -1,15 +1,17 @@ import nock from "nock"; -import { ethers } from "ethers"; +import { BigNumber } from "@ethersproject/bignumber"; +import { AddressZero } from "@ethersproject/constants"; +import { type Transaction } from "@ethersproject/transactions"; +import SignatureCALEth from "../fixtures/SignatureCALEth"; +import { + UNISWAP_EXECUTE_SELECTOR, + UNISWAP_UNIVERSAL_ROUTER_ADDRESS, +} from "../../src/modules/Uniswap/constants"; import { getCommandsAndTokensFromUniswapCalldata, isSupported, loadInfosForUniswap, } from "../../src/modules/Uniswap"; -import { - UNISWAP_EXECUTE_SELECTOR, - UNISWAP_UNIVERSAL_ROUTER_ADDRESS, -} from "../../src/modules/Uniswap/constants"; -import SignatureCALEth from "../fixtures/SignatureCALEth"; nock.disableNetConnect(); jest.mock("@ledgerhq/cryptoassets-evm-signatures/data/evm/index", () => ({ @@ -24,7 +26,7 @@ describe("Uniswap", () => { describe("index", () => { describe("isSupported", () => { it("should return false for a non-Uniswap transaction", () => { - expect(isSupported("0x", ethers.constants.AddressZero, 1, [])).toBe(false); + expect(isSupported("0x", AddressZero, 1, [])).toBe(false); }); it("should return false for a Uniswap transaction with an invalid selector", () => { @@ -142,12 +144,12 @@ describe("Uniswap", () => { const calldata = "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669bad2800000000000000000000000000000000000000000000000000000000000000040a08060c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000bdaa645097ef80f9d475f341d0d107a45b3a000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000687ce06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000687ce06c00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004142794d71565541a435d1bc910db84510c4d31ad35ff8c046222b2c232fcd99a6256a8f1abe7d42a44f3d92d65f9232db76df2fbef3dfa76eca64b034ed4df5321c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000001bffcca36d953d12266cb000000000000000000000000000000000000000000000000027c3dea3f90dafd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000bdaa645097ef80f9d475f341d0d107a45b3a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006eb940753b4b52fbec8d33c418133fdb0d4405e6000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000001dc813a4ee4410f144ae2122051c1cfc436f24a00000000000000000000000000000000000000000000000000275e122c92b911e"; - const transaction: ethers.Transaction = { + const transaction: Transaction = { to: UNISWAP_UNIVERSAL_ROUTER_ADDRESS, data: calldata, - gasLimit: ethers.BigNumber.from(21_000), + gasLimit: BigNumber.from(21_000), nonce: 0, - value: ethers.BigNumber.from(0), + value: BigNumber.from(0), chainId: 1, }; @@ -171,12 +173,12 @@ describe("Uniswap", () => { }); it("should return an empty object for an unsupported Uniswap transaction", async () => { - const transaction: ethers.Transaction = { - to: ethers.constants.AddressZero, + const transaction: Transaction = { + to: AddressZero, data: "0x", - gasLimit: ethers.BigNumber.from(21_000), + gasLimit: BigNumber.from(21_000), nonce: 0, - value: ethers.BigNumber.from(0), + value: BigNumber.from(0), chainId: 1, }; diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/fixtures/utils.ts b/libs/ledgerjs/packages/hw-app-eth/tests/fixtures/utils.ts index a6448d1f064b..fa202b8f3435 100644 --- a/libs/ledgerjs/packages/hw-app-eth/tests/fixtures/utils.ts +++ b/libs/ledgerjs/packages/hw-app-eth/tests/fixtures/utils.ts @@ -1,13 +1,14 @@ -import { ethers } from "ethers"; +import { Interface } from "@ethersproject/abi"; +import { serialize as serializeTransaction } from "@ethersproject/transactions"; import ERC20Abi from "./ABI/ERC20.json"; import ERC721Abi from "./ABI/ERC721.json"; import ERC1155Abi from "./ABI/ERC1155.json"; import PARASWAPAbi from "./ABI/PARASWAP.json"; -const ERC20Interface = new ethers.utils.Interface(ERC20Abi); -const ERC721Interface = new ethers.utils.Interface(ERC721Abi); -const ERC1155Interface = new ethers.utils.Interface(ERC1155Abi); -const PARASWAPInterface = new ethers.utils.Interface(PARASWAPAbi); +const ERC20Interface = new Interface(ERC20Abi); +const ERC721Interface = new Interface(ERC721Abi); +const ERC1155Interface = new Interface(ERC1155Abi); +const PARASWAPInterface = new Interface(PARASWAPAbi); export const transactionContracts = { erc20: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC @@ -102,16 +103,14 @@ export const transactionData = { }; export const getSerializedTransaction = (to: string, data: string): string => - ethers.utils - .serializeTransaction({ - to, - nonce: 0, - gasLimit: 21000, - data, - value: 1, - chainId: 1, - maxPriorityFeePerGas: 10000, - maxFeePerGas: 1000000, - type: 2, - }) - .substring(2); + serializeTransaction({ + to, + nonce: 0, + gasLimit: 21000, + data, + value: 1, + chainId: 1, + maxPriorityFeePerGas: 10000, + maxFeePerGas: 1000000, + type: 2, + }).substring(2); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d24efb66089f..747c3e3548d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4431,10 +4431,13 @@ importers: libs/ledgerjs/packages/hw-app-eth: dependencies: '@ethersproject/abi': - specifier: ^5.5.0 + specifier: ^5.7.0 version: 5.7.0 '@ethersproject/rlp': - specifier: ^5.5.0 + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/transactions': + specifier: ^5.7.0 version: 5.7.0 '@ledgerhq/cryptoassets-evm-signatures': specifier: workspace:^ @@ -4470,6 +4473,15 @@ importers: specifier: ^7.3.5 version: 7.6.2 devDependencies: + '@ethersproject/bignumber': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/constants': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/units': + specifier: ^5.7.0 + version: 5.7.0 '@types/jest': specifier: ^29.5.10 version: 29.5.12 @@ -4479,9 +4491,6 @@ importers: documentation: specifier: 14.0.2 version: 14.0.2 - ethers: - specifier: 5.7.2 - version: 5.7.2 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(source-map-support@0.5.21)(typescript@5.4.3)) @@ -26082,7 +26091,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: @@ -49933,8 +49941,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -50017,6 +50025,23 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.16.0 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.5 + is-core-module: 2.13.1 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.4 @@ -50061,6 +50086,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -50150,6 +50186,33 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 @@ -50919,12 +50982,12 @@ snapshots: react: 18.2.0 react-native: 0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0) - expo-font@11.4.0(zetkxmi6vkr5536rw42p64xrum): + expo-font@11.4.0(wrn4p6hlsdta5c4ir3h6ct54i4): dependencies: expo: 49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.12)(metro@0.80.12)(minimatch@5.1.6)(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0) fontfaceobserver: 2.3.0 optionalDependencies: - expo-asset: 8.10.1(fwfwxrny4no4cg4koyqmr3oiha) + expo-asset: 8.10.1(rxkmd2ynqonhzhnkgmzjv3uahy) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.12)(metro@0.80.12)(minimatch@5.1.6)(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0))(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0) expo-modules-core: 1.5.11(expo-constants@14.5.1)(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0) react: 18.2.0 @@ -51058,7 +51121,7 @@ snapshots: expo-asset: 8.10.1(rxkmd2ynqonhzhnkgmzjv3uahy) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.12)(metro@0.80.12)(minimatch@5.1.6)(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0))(react-native@0.74.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(@types/react@18.2.73)(metro-resolver@0.80.12)(metro-transform-worker@0.80.12)(react@18.2.0))(react@18.2.0) expo-file-system: 15.4.5(rxkmd2ynqonhzhnkgmzjv3uahy) - expo-font: 11.4.0(zetkxmi6vkr5536rw42p64xrum) + expo-font: 11.4.0(wrn4p6hlsdta5c4ir3h6ct54i4) expo-keep-awake: 12.3.0(rxkmd2ynqonhzhnkgmzjv3uahy) expo-modules-autolinking: 1.5.1(epzs5mtkmt3672rwgv2fv7s7qa) fbemitter: 3.0.0 From 213fee90046f6db8256d2bf2f6336023d4fdc3df Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:27:42 +0200 Subject: [PATCH 2/8] Refactoring of the whole transaction decoding flow It should be easier to reason about, increase chunk size and more tested Should also fix the `v` value of ECDSA sig bing incorrect for legacy txs --- libs/ledgerjs/packages/hw-app-eth/src/Eth.ts | 85 ++--- .../hw-app-eth/src/services/ledger/index.ts | 63 +++- .../ledgerjs/packages/hw-app-eth/src/utils.ts | 227 ++++++++---- .../hw-app-eth/tests/Eth.unit.test.ts | 212 +++++------ .../hw-app-eth/tests/utils.unit.test.ts | 341 ++++++++++++++++++ 5 files changed, 660 insertions(+), 268 deletions(-) create mode 100644 libs/ledgerjs/packages/hw-app-eth/tests/utils.unit.test.ts diff --git a/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts b/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts index a27425fb22e5..24bc560224b9 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts @@ -22,7 +22,8 @@ import { BigNumber } from "bignumber.js"; import { LedgerEthTransactionResolution, LoadConfig, ResolutionConfig } from "./services/types"; import { log } from "@ledgerhq/logs"; import { - decodeTxInfo, + safeChunkTransaction, + getV, hexBuffer, intAsHexBytes, maybeHexBuffer, @@ -193,6 +194,14 @@ export default class Eth { v: string; r: string; }> { + enum APDU_FIELDS { + CLA = 0xe0, + INS = 0x04, + P1_FIRST_CHUNK = 0x00, + P1_FOLLOWING_CHUNK = 0x80, + P2 = 0x00, + } + if (resolution === undefined) { console.warn( "hw-app-eth: signTransaction(path, rawTxHex, resolution): " + @@ -247,68 +256,36 @@ export default class Eth { } const rawTx = Buffer.from(rawTxHex, "hex"); - const { vrsOffset, txType, chainId, chainIdTruncated } = decodeTxInfo(rawTx); + const parsedTransaction = parseTransaction(`0x${rawTx.toString("hex")}`); + const chainId = new BigNumber(parsedTransaction.chainId); const paths = splitPath(path); - let response; - let offset = 0; - while (offset !== rawTx.length) { - const first = offset === 0; - const maxChunkSize = first ? 150 - 1 - paths.length * 4 : 150; - let chunkSize = offset + maxChunkSize > rawTx.length ? rawTx.length - offset : maxChunkSize; - - if (vrsOffset != 0 && offset + chunkSize >= vrsOffset) { - // Make sure that the chunk doesn't end right on the EIP 155 marker if set - chunkSize = rawTx.length - offset; - } - - const buffer = Buffer.alloc(first ? 1 + paths.length * 4 + chunkSize : chunkSize); - - if (first) { - buffer[0] = paths.length; - paths.forEach((element, index) => { - buffer.writeUInt32BE(element, 1 + 4 * index); - }); - rawTx.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize); - } else { - rawTx.copy(buffer, 0, offset, offset + chunkSize); - } + const derivationPathBuff = Buffer.alloc(1 + paths.length * 4); + derivationPathBuff[0] = paths.length; + paths.forEach((element, index) => { + derivationPathBuff.writeUInt32BE(element, 1 + 4 * index); + }); + const payloadChunks = safeChunkTransaction(rawTx, derivationPathBuff, parsedTransaction.type); + let response; + for (const chunk of payloadChunks) { + const isFirstChunk = chunk === payloadChunks[0]; response = await this.transport - .send(0xe0, 0x04, first ? 0x00 : 0x80, 0x00, buffer) + .send( + APDU_FIELDS.CLA, + APDU_FIELDS.INS, + isFirstChunk ? APDU_FIELDS.P1_FIRST_CHUNK : APDU_FIELDS.P1_FOLLOWING_CHUNK, + APDU_FIELDS.P2, + chunk, + ) .catch(e => { throw remapTransactionRelatedErrors(e); }); - - offset += chunkSize; - } - - const response_byte: number = response[0]; - let v = ""; - - if (chainId.times(2).plus(35).plus(1).isGreaterThan(255)) { - const oneByteChainId = (chainIdTruncated * 2 + 35) % 256; - - const ecc_parity = Math.abs(response_byte - oneByteChainId); - - if (txType != null) { - // For EIP2930 and EIP1559 tx, v is simply the parity. - v = ecc_parity % 2 == 1 ? "00" : "01"; - } else { - // Legacy type transaction with a big chain ID - v = chainId.times(2).plus(35).plus(ecc_parity).toString(16); - } - } else { - v = response_byte.toString(16); } - // Make sure v has is prefixed with a 0 if its length is odd ("1" -> "01"). - if (v.length % 2 == 1) { - v = "0" + v; - } - - const r = response.slice(1, 1 + 32).toString("hex"); - const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex"); + const v = getV(response[0], chainId, parsedTransaction.type); + const r = response.subarray(1, 1 + 32).toString("hex"); + const s = response.subarray(1 + 32, 1 + 32 + 32).toString("hex"); return { v, r, s }; } diff --git a/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts b/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts index 19c06419885c..57bc67b04063 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/index.ts @@ -6,12 +6,20 @@ import { signAddressResolution, } from "@ledgerhq/domain-service/signers/index"; import { LedgerEthTransactionResolution, LedgerEthTransactionService, LoadConfig } from "../types"; -import { decodeTxInfo, tokenSelectors, nftSelectors, mergeResolutions } from "../../utils"; import { UNISWAP_UNIVERSAL_ROUTER_ADDRESS } from "../../modules/Uniswap/constants"; import { byContractAddressAndChainId, findERC20SignaturesInfo } from "./erc20"; import { loadInfosForUniswap } from "../../modules/Uniswap"; import { loadInfosForContractMethod } from "./contracts"; import { getNFTInfo, loadNftPlugin } from "./nfts"; +import { + tokenSelectors, + nftSelectors, + mergeResolutions, + ERC20_CLEAR_SIGNED_SELECTORS, + ERC1155_CLEAR_SIGNED_SELECTORS, + ERC721_CLEAR_SIGNED_SELECTORS, + getChainIdAsUint32, +} from "../../utils"; type PotentialResolutions = { token: boolean | undefined; @@ -29,7 +37,7 @@ type PotentialResolutions = { */ const getAdditionalDataForContract = async ( contractAddress: string, - chainIdTruncated: number, + chainIdUint32: number, loadConfig: LoadConfig, shouldResolve: PotentialResolutions, ): Promise> => { @@ -39,7 +47,7 @@ const getAdditionalDataForContract = async ( }; if (shouldResolve.nft) { - const nftInfo = await getNFTInfo(contractAddress, chainIdTruncated, loadConfig); + const nftInfo = await getNFTInfo(contractAddress, chainIdUint32, loadConfig); if (nftInfo) { log( @@ -53,10 +61,10 @@ const getAdditionalDataForContract = async ( } if (shouldResolve.token) { - const erc20SignaturesBlob = await findERC20SignaturesInfo(loadConfig, chainIdTruncated); + const erc20SignaturesBlob = await findERC20SignaturesInfo(loadConfig, chainIdUint32); const erc20Info = byContractAddressAndChainId( contractAddress, - chainIdTruncated, + chainIdUint32, erc20SignaturesBlob, ); @@ -84,8 +92,8 @@ const getAdditionalDataForContract = async ( const loadNanoAppPlugins = async ( contractAddress: string, selector: string, - decodedTx, - chainIdTruncated: number, + parsedTransaction, + chainIdUint32: number, loadConfig: LoadConfig, shouldResolve: PotentialResolutions, ): Promise => { @@ -101,7 +109,7 @@ const loadNanoAppPlugins = async ( const nftPluginPayload = await loadNftPlugin( contractAddress, selector, - chainIdTruncated, + chainIdUint32, loadConfig, ); @@ -121,7 +129,7 @@ const loadNanoAppPlugins = async ( const contractMethodInfos = await loadInfosForContractMethod( contractAddress, selector, - chainIdTruncated, + chainIdUint32, loadConfig, ); @@ -147,7 +155,7 @@ const loadNanoAppPlugins = async ( const externalPluginResolution = await getAdditionalDataForContract( erc20ContractAddress, - chainIdTruncated, + chainIdUint32, loadConfig, { nft: false, @@ -165,7 +173,10 @@ const loadNanoAppPlugins = async ( } if (shouldResolve.uniswapV3) { - const { pluginData, tokenDescriptors } = await loadInfosForUniswap(decodedTx, chainIdTruncated); + const { pluginData, tokenDescriptors } = await loadInfosForUniswap( + parsedTransaction, + chainIdUint32, + ); if (pluginData && tokenDescriptors) { resolution.externalPlugin.push({ payload: pluginData.toString("hex"), @@ -191,17 +202,31 @@ const resolveTransaction: LedgerEthTransactionService["resolveTransaction"] = as ) => { const rawTx = Buffer.from(rawTxHex, "hex"); const parsedTransaction = parseTransaction(`0x${rawTx.toString("hex")}`); - const chainIdUint4 = decodeTxInfo(rawTx); + const chainIdUint32 = getChainIdAsUint32(parsedTransaction.chainId); const { domains } = resolutionConfig; - const contractAddress = decodedTx.to; - const selector = decodedTx.data.length >= 10 && decodedTx.data.substring(0, 10); + const contractAddress = parsedTransaction.to?.toLowerCase(); + if (!contractAddress) + return { + nfts: [], + erc20Tokens: [], + externalPlugin: [], + plugin: [], + domains: [], + }; + + const selector = parsedTransaction.data.length >= 10 && parsedTransaction.data.substring(0, 10); const resolutions: Partial[] = []; if (selector) { const shouldResolve: PotentialResolutions = { - token: resolutionConfig.erc20 && tokenSelectors.includes(selector), - nft: resolutionConfig.nft && nftSelectors.includes(selector), + token: + resolutionConfig.erc20 && tokenSelectors.includes(selector as ERC20_CLEAR_SIGNED_SELECTORS), + nft: + resolutionConfig.nft && + nftSelectors.includes( + selector as ERC721_CLEAR_SIGNED_SELECTORS | ERC1155_CLEAR_SIGNED_SELECTORS, + ), externalPlugins: resolutionConfig.externalPlugins, uniswapV3: resolutionConfig.uniswapV3, }; @@ -209,8 +234,8 @@ const resolveTransaction: LedgerEthTransactionService["resolveTransaction"] = as const pluginsResolution = await loadNanoAppPlugins( contractAddress, selector, - decodedTx, - chainIdTruncated, + parsedTransaction, + chainIdUint32, loadConfig, shouldResolve, ); @@ -220,7 +245,7 @@ const resolveTransaction: LedgerEthTransactionService["resolveTransaction"] = as const contractResolution = await getAdditionalDataForContract( contractAddress, - chainIdTruncated, + chainIdUint32, loadConfig, shouldResolve, ); diff --git a/libs/ledgerjs/packages/hw-app-eth/src/utils.ts b/libs/ledgerjs/packages/hw-app-eth/src/utils.ts index 98800d7fe72a..12e9b7448c30 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/utils.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/utils.ts @@ -44,81 +44,6 @@ export function maybeHexBuffer(str: string | null | undefined): Buffer | null | return hexBuffer(str); } -export const decodeTxInfo = (rawTx: Buffer) => { - const VALID_TYPES = [1, 2]; - const txType = VALID_TYPES.includes(rawTx[0]) ? rawTx[0] : null; - const rlpData = txType === null ? rawTx : rawTx.slice(1); - const rlpTx = decode(rlpData).map(hex => Buffer.from(hex.slice(2), "hex")); - let chainIdTruncated = 0; - const rlpDecoded = decode(rlpData); - - let decodedTx; - if (txType === 2) { - // EIP1559 - decodedTx = { - data: rlpDecoded[7], - to: rlpDecoded[5], - chainId: rlpTx[0], - }; - } else if (txType === 1) { - // EIP2930 - decodedTx = { - data: rlpDecoded[6], - to: rlpDecoded[4], - chainId: rlpTx[0], - }; - } else { - // Legacy tx - decodedTx = { - data: rlpDecoded[5], - to: rlpDecoded[3], - // Default to 1 for non EIP 155 txs - chainId: rlpTx.length > 6 ? rlpTx[6] : Buffer.from("01", "hex"), - }; - } - - const chainIdSrc = decodedTx.chainId; - let chainId = new BigNumber(0); - if (chainIdSrc) { - // Using BigNumber because chainID could be any uint256. - chainId = new BigNumber(chainIdSrc.toString("hex"), 16); - const chainIdTruncatedBuf = Buffer.alloc(4); - if (chainIdSrc.length > 4) { - chainIdSrc.copy(chainIdTruncatedBuf); - } else { - chainIdSrc.copy(chainIdTruncatedBuf, 4 - chainIdSrc.length); - } - chainIdTruncated = chainIdTruncatedBuf.readUInt32BE(0); - } - - let vrsOffset = 0; - if (txType === null && rlpTx.length > 6) { - const rlpVrs = Buffer.from(encode(rlpTx.slice(-3)).slice(2), "hex"); - - vrsOffset = rawTx.length - (rlpVrs.length - 1); - - // First byte > 0xf7 means the length of the list length doesn't fit in a single byte. - if (rlpVrs[0] > 0xf7) { - // Increment vrsOffset to account for that extra byte. - vrsOffset++; - - // Compute size of the list length. - const sizeOfListLen = rlpVrs[0] - 0xf7; - - // Increase rlpOffset by the size of the list length. - vrsOffset += sizeOfListLen - 1; - } - } - - return { - decodedTx, - txType, - chainId, - chainIdTruncated, - vrsOffset, - }; -}; - /** * @ignore for the README * @@ -157,3 +82,155 @@ export const mergeResolutions = ( return mergedResolutions; }; + +/** + * @ignore for the README + * + * Ledger devices are returning v with potentially EIP-155 already applied when using legacy transactions. + * Because that v value is only represented as a single byte, we need to replicate what would be the + * overflow happening on the device while applying EIP-155 and recover the original parity. + * + * @param vFromDevice + * @param chainIdUint32 + * @returns + */ +export const getParity = ( + vFromDevice: number, + chainId: BigNumber, + transactionType: Transaction["type"], +): 0 | 1 => { + if (transactionType) return vFromDevice as 0 | 1; + + // The device use a 4 bytes integer to represent the chainId and keeps the highest bytes + const chainIdUint32 = getChainIdAsUint32(chainId); + + // Then applies EIP-155 to this chainId + const chainIdWithEIP155 = chainIdUint32 * 2 + 35; + // Since it's a single byte, we need to apply the overflow after reaching the max 0xff value and starting again to 0x00 + // for both possible values, the chainId with EIP155 and a 0 or 1 parity included + const chainIdWithOverflowZero = chainIdWithEIP155 % 256; + const chainIdWithOverflowOne = (chainIdWithEIP155 + 1) % 256; + + if (chainIdWithOverflowZero === vFromDevice) { + return 0; + } else if (chainIdWithOverflowOne === vFromDevice) { + return 1; + } + throw new Error("Invalid v value"); +}; + +/** + * @ignore for the README + * + * Helper to convert a chainId from a BigNumber to a 4 bytes integer. + * ChainIds are uint256, but the device limits them to 4 bytes + * + * @param {Number|BigNumber} chainId + * @returns {Number} + */ +export const getChainIdAsUint32 = (chainId: BigNumber | number): number => { + const chainIdBuff = Buffer.from(padHexString(new BigNumber(chainId).toString(16)), "hex"); + const chainIdUint32 = chainIdBuff.subarray(0, 4); + + return parseInt(chainIdUint32.toString("hex"), 16); +}; + +/** + * @ignore for the README + * + * Depending on the transaction type you're trying to sign with the device, the v value will be different. + * For legacy transactions, the v value is used to store the chainId, and that chainId can be a uint256, + * and some math operation should be applied to it in order to comply with EIP-155 for replay attacks. + * + * In order to prevent breaking changes at the time, the `v` value has been kept as a single byte + * which forces us to replicate an overflow happening on the device to get the correct `v` value + * + * @param {number} vFromDevice + * @param {BigNumber} chainId + * @param {Transaction["type"]} transactionType + * @returns {string} hexa string of the v value + */ +export const getV = ( + vFromDevice: number, + chainId: BigNumber, + transactionType: Transaction["type"], +): string => { + if (chainId.isZero()) return vFromDevice.toString(16); + + const parity = getParity(vFromDevice, chainId, transactionType); + return !transactionType + ? // Legacy transactions (type 0) should apply EIP-155 + // EIP-155: rlp[(nonce, gasprice, startgas, to, value, data, chainid, 0, 0)] + padHexString(chainId.times(2).plus(35).plus(parity).toString(16)) + : // Transactions after type 1 should only use partity (00/01) as their v value + // EIP-2930: 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, *signatureYParity*, signatureR, signatureS]) + // EIP-1559: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, *signature_y_parity*, signature_r, signature_s]) + // EIP-4844: 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, *y_parity*, r, s]) + // EIP-7702: 0x05 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, *signature_y_parity*, signature_r, signature_s]) + padHexString(parity.toString(16)); +}; + +/** + * @ignore for the README + * + * In order to prevent the device from considering a transaction RLP as complete before it actually is + * we need to split the RLP into chunks which could not be mistaken for a complete transaction. + * This is true for legacy transaction, where the `v` value is used to store the chainId + * + * @param {Buffer} transactionRlp + * @param {Buffer }derivationPath + * @param {Transaction["type"]} transactionType + * + * @returns {Buffer[]} + */ +export const safeChunkTransaction = ( + transactionRlp: Buffer, + derivationPath: Buffer, + transactionType: Transaction["type"], +): Buffer[] => { + const maxChunkSize = 255; + // The full payload is the derivation path + the complete RLP of the transaction + const payload = Buffer.concat([derivationPath, transactionRlp]); + if (payload.length <= maxChunkSize) return [payload]; + + if (transactionType) { + const chunks = Math.ceil(payload.length / maxChunkSize); + return new Array(chunks) + .fill(null) + .map((_, i) => payload.subarray(i * maxChunkSize, (i + 1) * maxChunkSize)); + } + + // Decode the RLP of the full transaction and keep only the last 3 elements (v, r, s) + const decodedVrs: number[] = rlp.decode(transactionRlp).slice(-3); + // Encode those values back to RLP in order to get the length of this serialized list + // Result should be something like [0xc0 + list payload length, list.map(rlp)] + // since only v can be used to store the chainId in legacy transactions + const encodedVrs = rlp.encode(decodedVrs); + // Since chainIds are uint256, the list payload length can be 1B (v rlp description) + 32B (v) + 1B (r) + 1B (s) = 35B max (< 55B) + // Therefore, the RLP of this vrs list should be prefixed by a value between [0xc1, 0xe3] (0xc0 + 35B = 0xe3 max) + // @see https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + // `encodedVrs` is then everything but the first byte of this serialization + const encodedVrsBuff = hexBuffer(encodedVrs).subarray(1); + + // Since we want to avoid chunking just before the v,r,s values, + // we just check the size of that payload and detect + // if it would fit perfectly in 255B chunks + // if it does, we chunk smaller parts + let chunkSize = 0; + const lastChunkSize = payload.length % maxChunkSize; + if (lastChunkSize === 0 || lastChunkSize > encodedVrsBuff.length) { + chunkSize = maxChunkSize; + } else { + for (let i = 1; i <= maxChunkSize; i++) { + const lastChunkSize = payload.length % (maxChunkSize - i); + if (lastChunkSize === 0 || lastChunkSize > encodedVrsBuff.length) { + chunkSize = maxChunkSize - i; + break; + } + } + } + const chunks = Math.ceil(payload.length / chunkSize); + return new Array(chunks) + .fill(null) + .map((_, i) => payload.subarray(i * chunkSize, (i + 1) * chunkSize)); +}; diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts b/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts index 5c9c384edbaa..302d68181e97 100644 --- a/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts +++ b/libs/ledgerjs/packages/hw-app-eth/tests/Eth.unit.test.ts @@ -71,7 +71,7 @@ describe("Eth app biding", () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a000068054d415449437d1afa7b718fb893db30a3abc0cfc608aacfebb000000012000000013044022000d8fa7b6e409a0dc55723ba975179e7d1181d1fc78fccbece4e5a264814366a02203927d84a710c8892d02f7386ad20147c75fba4bdd486b0256ecd005770a7ca5b - <= 9000 + <= 009000 => e004000085058000002c8000003c80000000000000000000000002f86d01808420c85580850dfe94e19e82fd25947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844a9059cbb000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000000000000000000de0b6b3a7640000c0 <= 00089a7656b73c72721952d9102dcb608b5f8e9e12e8dfa5d546743e3aa5ff99e24adc7e77795383bb1df13c572db4abfbce86ee4bbe3eaf9b3b50e5b5524793829000 `), @@ -137,9 +137,7 @@ describe("Eth app biding", () => { <= 9000 => e01400007001010752617269626c6560f80121c31a0d46b5279700f9df786054aa5ee500000000000000010101473045022067d4254b89367a7e35fe7507001d6c8a0844a35aa4839c94a4724de1f332382d022100b9353df1f6f69feb9970946603723657f5682e0cac1b480a2beb2ecba10c872f <= 9000 - => e004000096058000002c8000003c80000000000000000000000002f88e018084448b9b80851278cdd392830238699460f80121c31a0d46b5279700f9df786054aa5ee580b86442842e0e0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000 - <= 9000 - => e004800010000000000000000000000000112999c0 + => e0040000a6058000002c8000003c80000000000000000000000002f88e018084448b9b80851278cdd392830238699460f80121c31a0d46b5279700f9df786054aa5ee580b86442842e0e0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000000000000000000000000000112999c0 <= 0187ce0994bbdfdfd93990a5afc03d7cf70a14c9efaabac810724a41f6375f54236c0056a02dc07650b1e68f86b9f18a92ff689a9eddf710bd9f76739260fff1939000 `), ); @@ -208,13 +206,12 @@ describe("Eth app biding", () => { <= 9000 => e01400007001010752617269626c65d07dc4262bcdbf85190c01c996b4c06a461d243000000000000000010101473045022100fddd2264ca0eb3cc8a588d82b41edf9d262145a0ca1f08caab5bb6a4eac34a9e0220602b57cabdc40bbeb3a46a5d362ac2544124c9806aee196a87a51f61bb7e9230 <= 9000 - => e004000096058000002c8000003c80000000000000000000000002f8ee01808414dc9380850d54eb0ea883010ab494d07dc4262bcdbf85190c01c996b4c06a461d243080b8c4f242432a0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000 + => e0040000ff058000002c8000003c80000000000000000000000002f8ee01808414dc9380850d54eb0ea883010ab494d07dc4262bcdbf85190c01c996b4c06a461d243080b8c4f242432a0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f861000000000000000000000000000000000000000000000000000000000004041c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000 <= 9000 - => e00480007000000000000000000000000004041c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000c0 + => e004800007000000000000c0 <= 016467047c39c2c3db9dbe8aa03802c80830beb659f0eed7470735098f2c44cfae7b2aa36e2f31c6e0d37a00b93789ef4eef4fd016dd3b92fa893e7e741a152be99000 `), ); - const eth = new Eth(transport); const contract = new Interface(ERC1155_ABI); const result = await eth.clearSignTransaction( @@ -262,24 +259,18 @@ describe("Eth app biding", () => { => e012000068085061726173776170def171fe48cf0115b1d80b88dc8eab59176fee5754e3f31b3045022100ec8e69d23371437ce5b5f1d894b836c036748e2fabf52fb069c34a9d0ba8704a022013e761d81c26ece4cb0ea385813699b7e646354d3404ed55f4bf068db02dda9a <= 9000 => e00a000068054d415449437d1afa7b718fb893db30a3abc0cfc608aacfebb000000012000000013044022000d8fa7b6e409a0dc55723ba975179e7d1181d1fc78fccbece4e5a264814366a02203927d84a710c8892d02f7386ad20147c75fba4bdd486b0256ecd005770a7ca5b - <= 9000 + <= 009000 => e00a000067034441496b175474e89094c44da98b954eedeac495271d0f00000012000000013045022100b3aa979633284eb0f55459099333ab92cf06fdd58dc90e9c070000c8e968864c02207b10ec7d6609f51dda53d083a6e165a0abf3a77e13250e6f260772809b49aff5 + <= 019000 + => e0040000ff058000002c8000003c80000000000000000000000002f9048f018084448b9b8085143fc44a5883048f8b94def171fe48cf0115b1d80b88dc8eab59176fee5780b9046454e3f31b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000007d1afa7b718fb893db30a3abc0cfc608aacfebb00000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000014655db2d8c71619000000000000000000000000000000000000000000000000 <= 9000 - => e004000096058000002c8000003c80000000000000000000000002f9048f018084448b9b8085143fc44a5883048f8b94def171fe48cf0115b1d80b88dc8eab59176fee5780b9046454e3f31b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000007d1afa7b718fb893db30a3abc0cfc608aacfebb00000000000000000000000006b1754 - <= 9000 - => e00480009674e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000014655db2d8c71619000000000000000000000000000000000000000000000000147f9aa1bc47718c00000000000000000000000000000000000000000000000000000000000001e00000000000 - <= 9000 - => e004800096000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000558247e365be655f9144e1a0140d79 - <= 9000 - => e0048000963984372ef3010000000000000000000000000000000000000000000000000000000000405f000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000640be3823d2fae4b5ec240cd871aa6b675e99899000000000000000000000000000000000000000000000000000000000000000000 - <= 9000 - => e004800096000000000000000000000000000001000000000000000000000000e592427a0aece92de3edee1f18e0157c058615640000000000000000000000000000000000000000000000000000000000000124c04b8d59000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a0000000 + => e0048000ff147f9aa1bc47718c00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000558247e365be655f9144e1a0140d793984372ef3010000000000000000000000000000000000000000000000000000000000405f0000000000000000000000000000000000000000000000 <= 9000 - => e004800096000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee5700000000000000000000000000000000000000000000000000000000640b9d320000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000 + => e0048000ff00000000000000042000000000000000000000000000000000000000000000000000000000640be3823d2fae4b5ec240cd871aa6b675e99899000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e592427a0aece92de3edee1f18e0157c058615640000000000000000000000000000000000000000000000000000000000000124c04b8d59000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000def171fe48cf <= 9000 - => e0048000960000000000002b7d1afa7b718fb893db30a3abc0cfc608aacfebb00027106b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000 + => e0048000ff0115b1d80b88dc8eab59176fee5700000000000000000000000000000000000000000000000000000000640b9d320000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b7d1afa7b718fb893db30a3abc0cfc608aacfebb00027106b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 <= 9000 - => e00480008e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000124000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0 + => e0048000ac000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000124000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0 <= 00775aed341ee9f0a0d0c0d724e242f9def19c09df02d1c474bc5750c86b952f5535bd98bc4a6482a895e94cd5ca9351ec1daeb955251a35dc8c2fb86851bf49189000 `), ); @@ -344,21 +335,15 @@ describe("Eth app biding", () => { <= 009000 => e00a0000680457455448c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000012000000013045022100b47ee8551c15a2cf681c649651e987d7e527c481d27c38da1f971a8242792bd3022069c3f688ac5493a23dab5798e3c9b07484765069e1d4be14321aae4d92cb8cbe <= 019000 - => e004000096058000002c8000003c80000000000000000000000002f9044f018084448b9b8085143fc44a5883048f8b943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b904243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000 - <= 9000 - => e00480009600000000000000000000000000669ba25a00000000000000000000000000000000000000000000000000000000000000030a010c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000 - <= 9000 - => e0048000960000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffff + => e0040000ff058000002c8000003c80000000000000000000000002f9044f018084448b9b8085143fc44a5883048f8b943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b904243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000669ba25a00000000000000000000000000000000000000000000000000000000000000030a010c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 <= 9000 - => e004800096ffffffffff0000000000000000000000000000000000000000000000000000000066c32ea60000000000000000000000000000000000000000000000000000000000000006000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669ba25a0000000000000000000000000000000000 + => e0048000ff0000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000016000000000000000000000000055747be9f9f5beb232ad59fe7af013b81d95fd5e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000066c32ea60000000000000000000000000000000000000000000000 <= 9000 - => e0048000960000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000413cbf00ab90b6d1b17401cbf49e00c40f98bcb3f39461ca65e26009f9e9f77029279a4587efa2d792ea61ede56e0fbd7c1305007bc59d09bc60eaba46efa23edd1c0000000000000000000000000000000000000000000000000000000000000000000000000000 + => e0048000ff000000000000000006000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b00000000000000000000000000000000000000000000000000000000669ba25a00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000413cbf00ab90b6d1b17401cbf49e00c40f98bcb3f39461ca65e26009f9e9f77029279a4587efa2d792ea61ede56e0fbd7c1305007bc59d09bc60eaba46efa23edd1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 <= 9000 - => e0048000960000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000233a3559d9da00000000000000000000000000000000000000000000000062e76d8ff4b926e80000000000000000000000000000000000000000000000000000000000000 + => e0048000ff0000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000233a3559d9da00000000000000000000000000000000000000000000000062e76d8ff4b926e800000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000 <= 9000 - => e0048000960000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271055747be9f9f5beb232ad59fe7af013b81d95fd5e00000000000000000000000000000000000000000000000000000000000000000000000000000000 - <= 9000 - => e00480004e0000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000233a3559d9da000c0 + => e00480006c0000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000233a3559d9da000c0 <= 00a42d6ead5539eb1ac552b65b46df4e8fd87c37da54a280d33dc7a78992b56bb02244776870b31cfb50ea71c0ea31a30e6e513bd9766f74d61dc52b523ac256879000 `), ); @@ -559,11 +544,11 @@ describe("Eth app biding", () => { }); }); - test("signTransaction", async () => { + test("signTransaction with chainId 0 (Legacy before EIP-155)", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00400003e058000002c8000003c800000008000000000000000e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080 - <= 1b3694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a4009000 + <= 263694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a4009000 `), ); const eth = new Eth(transport); @@ -575,7 +560,7 @@ describe("Eth app biding", () => { expect(result).toEqual({ r: "3694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b", s: "30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a400", - v: "1b", + v: "26", }); }); @@ -604,8 +589,8 @@ describe("Eth app biding", () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a000066035a5258e41d2489571d322189246dafa5ebde1f4699f4980000001200000001304402200ae8634c22762a8ba41d2acb1e068dcce947337c6dd984f13b820d396176952302203306a49d8a6c35b11a61088e1570b3928ca3a0db6bd36f577b5ef87628561ff7 - <= 9000 - => e00400008c058000002c8000003c80000000000000000000000002f8740106843b9aca008504a817c80082520894e41d2489571d322189246dafa5ebde1f4699f498872386f26fc10000b844095ea7b3000000000000000000000000221657776846890989a759ba2973e427dff5c9bb0000000000000000000000000000000000000000000000004563918244f40000c0 + <= 009000 + => e004000085058000002c8000003c80000000000000000000000002f86d0106843b9aca008504a817c80082520894e41d2489571d322189246dafa5ebde1f4699f49800b844095ea7b3000000000000000000000000221657776846890989a759ba2973e427dff5c9bb0000000000000000000000000000000000000000000000004563918244f40000c0 <= 00d6814aa5db69de910824b14462af006fde864224c616ab93e30f646e7309a93f0312ac6e580e918ce6e39e5f910cb95ba7b68167f4d71e581dec2495a198ecc09000 `), ); @@ -613,7 +598,7 @@ describe("Eth app biding", () => { const result = await signTxWithResolution( eth, "44'/60'/0'/0/0", - "02f8740106843b9aca008504a817c80082520894e41d2489571d322189246dafa5ebde1f4699f498872386f26fc10000b844095ea7b3000000000000000000000000221657776846890989a759ba2973e427dff5c9bb0000000000000000000000000000000000000000000000004563918244f40000c0", + "02f86d0106843b9aca008504a817c80082520894e41d2489571d322189246dafa5ebde1f4699f49800b844095ea7b3000000000000000000000000221657776846890989a759ba2973e427dff5c9bb0000000000000000000000000000000000000000000000004563918244f40000c0", ); expect(result).toEqual({ r: "d6814aa5db69de910824b14462af006fde864224c616ab93e30f646e7309a93f", @@ -625,9 +610,7 @@ describe("Eth app biding", () => { test("signTransaction supports EIP2930", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e004000096058000002c8000003c80000000000000000000000001f886030685012a05f20082520894b2bb2b958afa2e96dab3f3ce7162b87daea39017872386f26fc1000080f85bf85994de0b295669a9fd93d5f28d9ec85e40f4cb697baef842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000 - <= 9000 - => e0048000080000000000000007 + => e00400009e058000002c8000003c80000000000000000000000001f886030685012a05f20082520894b2bb2b958afa2e96dab3f3ce7162b87daea39017872386f26fc1000080f85bf85994de0b295669a9fd93d5f28d9ec85e40f4cb697baef842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007 <= 01a74d82400f49d1f9d85f734c22a1648d4ab74bb6367bef54c6abb0936be3d8b77a84a09673394c3c1bd76be05620ee17a2d0ff32837607625efa433cc017854e9000 `), ); @@ -648,24 +631,18 @@ describe("Eth app biding", () => { * if 1inch token is popular again might need to put this back, starting at line 2 => e00a0000680531494e4348111111111117dc0aa78b770fa6a738034120c3020000001200000001304402204623e5f1375c54a446157ae8a739204284cf053634b7abd083dc5f5d2675c4e702206ff94b4c84ba9e93f44065c38d7c92506621fa69ba04f767aa58221de8afbf17 - <= 9000 + <= 009000 */ const paraswapAPDUs = `=> e0120000670850617261737761701bd435f3c054b6e901b7b108a0ab7617c808677bcfc0afeb304402201c0cbe69aac517825b3a6eb5e7251e8fd57ff93a43bd3df52c7a841818eda81b022001a10cc326efaee2463fc96e7c29739c308fb8179bd2ac37303662bae4f7705c <= 9000 - => e004000096058000002c8000003c800000000000000000000000f903cd82043d8509c765240083042e73941bd435f3c054b6e901b7b108a0ab7617c808677b80b903a4cfc0afeb000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000 + => e0040000ff058000002c8000003c800000000000000000000000f903cd82043d8509c765240083042e73941bd435f3c054b6e901b7b108a0ab7617c808677b80b903a4cfc0afeb000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000af10f7eb24f506cfd00000000000000000000000000000000000000000000000002a5b905b3c9fa4c00000000000000000000000000000000000000000000000002baaee8d905020a0000000000000000000000000000000000000000000000000000000000 <= 9000 - => e0048000960000000af10f7eb24f506cfd00000000000000000000000000000000000000000000000002a5b905b3c9fa4c00000000000000000000000000000000000000000000000002baaee8d905020a000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000 + => e0048000ff00018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000086d3579b043585a97532514016dcf0c2 <= 9000 - => e004800096000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000000 + => e0048000ffd6c4b6a100000000000000000000000000000000000000000000000000000000000000c499585aac00000000000000000000000000000000000000000000000af10f7eb24f506cfd000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000 <= 9000 - => e004800096000000000000000000000000000000000000000000000000000000000000000100000000000000000000000086d3579b043585a97532514016dcf0c2d6c4b6a100000000000000000000000000000000000000000000000000000000000000c499585aac00000000000000000000000000000000000000000000000af10f7eb24f506cfd000000000000000000000000000000000000 - <= 9000 - => e004800096000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000 - <= 9000 - => e00480009600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000010000 - <= 9000 - => e00480006100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076c65646765723200000000000000000000000000000000000000000000000000018080 + => e0048000e800000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076c65646765723200000000000000000000000000000000000000000000000000018080 <= 26d9e62b0b6ae0c18d3d2ecdf20ce7f1c959e0f609b4e73e2d138bbdc3e1e9390012469e2124a8955b5159f670b0333b803a70dd7dc51558a8f7460b27eed77be59000`.toLowerCase(); test("paraswap", async () => { @@ -719,43 +696,32 @@ describe("Eth app biding", () => { }); }); - test("signTransactionLargeChainID", async () => { - const transport = await openTransportReplayer( - RecordStore.fromString(` - => e004000043058000002c8000003c800000008000000000000000ed018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a248740008082aa008080 - <= 1b3694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a4009000 - `), - ); - const eth = new Eth(transport); - const result = await signTxWithResolution( - eth, - "44'/60'/0'/0'/0", - "ed018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a248740008082aa008080", - ); - expect(result).toEqual({ - r: "3694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b", - s: "30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a400", - v: "01542b", - }); - }); - - test("signTransactionLargeChainID2", async () => { + test("signTransaction Large ChainID (2_716_446_429_837_000)", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e004000043058000002c8000003c800000008000000000000000ed018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a248740008081fa008080 - <= 173694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a4009000 + => e004000048058000002c8000003c800000008000000000000000f2018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a24874000808709a697f88076c88080 + <= 132ceca22fd700a75d8c18588cc4f5f8ba49ccbe583ddfb48f6b30186977f030312fb2c430662265c56fb09c0fcf0cfebde0e58d1a977d9c012b55c6c6cf66d99e9000 `), ); const eth = new Eth(transport); + const transaction = { + chainId: 2_716_446_429_837_000, + nonce: 1, + gasPrice: EthersBigNumber.from("0x4e3b29200"), + gasLimit: EthersBigNumber.from("0x5208"), + to: "0x28ee52a8f3d6e5d15f8b131996950d7f296c7952", + value: EthersBigNumber.from("0x2bd72a24874000"), + data: "0x", + }; const result = await signTxWithResolution( eth, "44'/60'/0'/0'/0", - "ed018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a248740008081fa008080", + serializeTransaction(transaction).slice(2), ); expect(result).toEqual({ - r: "3694583045a85ada8d15d5e01b373b00e86a405c9c52f7835691dcc522b7353b", - s: "30392e638a591c65ed307809825ca48346980f52d004ab7a5f93657f7e62a400", - v: "0217", + r: "2ceca22fd700a75d8c18588cc4f5f8ba49ccbe583ddfb48f6b30186977f03031", + s: "2fb2c430662265c56fb09c0fcf0cfebde0e58d1a977d9c012b55c6c6cf66d99e", + v: "134d2ff100edb3", }); }); @@ -782,12 +748,10 @@ describe("Eth app biding", () => { test("signTransactionChunkedLimit", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e004000096058000002c8000003c800000000000000000000000f901ad8205448505c205da808310c8e19402b3f51ac9202aa19be63d61a8c681579d6e3a5180b90184293491160000000000000000000000000000000000000000000000000000000005ee832e0000000000000000000000000000000000000000000000000000000005eeb9ac0000000000000000000000000000000000000000 - <= 9000 - => e00480009600000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000505846a0a89dd26fa5cd0677fd5406039c218620000000000000000000000000000000000000000000000000000000001f969fc88a4d19062c1525d6ca664dee285aa573c06e0f8bdd4971032d2b63be6183d05300000000000000000000 + => e0040000ff058000002c8000003c800000000000000000000000f901ad8205448505c205da808310c8e19402b3f51ac9202aa19be63d61a8c681579d6e3a5180b90184293491160000000000000000000000000000000000000000000000000000000005ee832e0000000000000000000000000000000000000000000000000000000005eeb9ac000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000505846a0a89dd26fa5cd0677fd5406039c218620000000000000000000000000000000000000000000000000000000001f <= 9000 - => e004800099000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000041c4c3f1f8711741f2180d850a09a2933bb21dff1c79caf8c45ecda957836ec7e60d78661c28ad96713e5f22a458376422599bd3776d9aeafc02e319ed0c1b41e51c00000000000000000000000000000000000000000000000000000000000000018080 - <= 1bdc6ad1d9d847defdffde2f3b70004c89a1a8a6c614fec484891ae8f1ebc46f9966159ca542f5cf36d64278218bfcce24ba96d7495dec25b10a7609346ca063ec9000 + => e0048000c6969fc88a4d19062c1525d6ca664dee285aa573c06e0f8bdd4971032d2b63be6183d05300000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000041c4c3f1f8711741f2180d850a09a2933bb21dff1c79caf8c45ecda957836ec7e60d78661c28ad96713e5f22a458376422599bd3776d9aeafc02e319ed0c1b41e51c00000000000000000000000000000000000000000000000000000000000000018080 + <= 26dc6ad1d9d847defdffde2f3b70004c89a1a8a6c614fec484891ae8f1ebc46f9966159ca542f5cf36d64278218bfcce24ba96d7495dec25b10a7609346ca063ec9000 `), // Incorrect signature but it doesn't matter for tests ); const eth = new Eth(transport); @@ -799,17 +763,17 @@ describe("Eth app biding", () => { expect(result).toEqual({ r: "dc6ad1d9d847defdffde2f3b70004c89a1a8a6c614fec484891ae8f1ebc46f99", s: "66159ca542f5cf36d64278218bfcce24ba96d7495dec25b10a7609346ca063ec", - v: "1b", + v: "26", }); }); test("signTransactionChunkedLimitBigVRS", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e004000096058000002c8000003c800000000000000000000000f9011782abcd8609184e72a00082271094aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa820300b8edbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + => e0040000ff058000002c8000003c800000000000000000000000f9011782abcd8609184e72a00082271094aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa820300b8edbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb <= 9000 - => e004800099bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb258080 - <= 1bdc6ad1d9d847defdffde2f3b70004c89a1a8a6c614fec484891ae8f1ebc46f9966159ca542f5cf36d64278218bfcce24ba96d7495dec25b10a7609346ca063ec9000 + => e004800030bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb258080 + <= 6edc6ad1d9d847defdffde2f3b70004c89a1a8a6c614fec484891ae8f1ebc46f9966159ca542f5cf36d64278218bfcce24ba96d7495dec25b10a7609346ca063ec9000 `), ); const eth = new Eth(transport); @@ -821,7 +785,7 @@ describe("Eth app biding", () => { expect(result).toEqual({ r: "dc6ad1d9d847defdffde2f3b70004c89a1a8a6c614fec484891ae8f1ebc46f99", s: "66159ca542f5cf36d64278218bfcce24ba96d7495dec25b10a7609346ca063ec", - v: "1b", + v: "6e", }); }); @@ -881,7 +845,7 @@ describe("Eth app biding", () => { return { data: { payload: - "01010302010113010314010112041dad95c421013c200930786b766e2e6574682214b0b5b0106d69fe64545a60a68c014f7570d3f86115463044022075f4fffb553cb615a6adcecac60ce57f72b5ed76a73b18ca99e8914529efaea70220031df5f609e06d26d1f4b55605f483977e08406528c4ceab729d010d52725dd2", + "01010302010113010314010112041dad95c421013c200d6465762e30786b766e2e65746822146cbcd73cd8e8a42844662f0a0e76d7f79afd933d1546304402200247a175329e4025500b6cb62fc466c8876a9e3d3cb681200ae70d5b69afdb2902200a5de51730d17f8d62e10ca4ddfc0b353e8b42e284293c074e4a62f96a7e9f9c", }, }; } @@ -891,11 +855,11 @@ describe("Eth app biding", () => { RecordStore.fromString(` => e020000000 <= 1dad95c49000 - => e022010080007e01010302010113010314010112041dad95c421013c200930786b766e2e6574682214b0b5b0106d69fe64545a60a68c014f7570d3f86115463044022075f4fffb553cb615a6adcecac60ce57f72b5ed76a73b18ca99e8914529efaea70220031df5f609e06d26d1f4b55605f483977e08406528c4ceab729d010d52725dd2 + => e022010084008201010302010113010314010112041dad95c421013c200d6465762e30786b766e2e65746822146cbcd73cd8e8a42844662f0a0e76d7f79afd933d1546304402200247a175329e4025500b6cb62fc466c8876a9e3d3cb681200ae70d5b69afdb2902200a5de51730d17f8d62e10ca4ddfc0b353e8b42e284293c074e4a62f96a7e9f9c <= 9000 => e00a000068054d415449437d1afa7b718fb893db30a3abc0cfc608aacfebb000000012000000013044022000d8fa7b6e409a0dc55723ba975179e7d1181d1fc78fccbece4e5a264814366a02203927d84a710c8892d02f7386ad20147c75fba4bdd486b0256ecd005770a7ca5b - <= 9000 - => e004000085058000002c8000003c80000000000000000000000002f86d0146841dcd650085086a18cd7882fd50947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844a9059cbb000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000000000000000029784d963dbc85a1ec0 + <= 009000 + => e004000085058000002c8000003c80000000000000000000000002f86d0146841dcd650085086a18cd7882fd50947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844a9059cbb0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d0000000000000000000000000000000000000000000000029784d963dbc85a1ec0 <= 0030c7d7899a892c9370dc43aa15d309805f52319c32daf22406a42ff32ec6013b746d9409a45b70c6e511d9802cc477cebfd7128140ac649371e988f33f3a85559000 `), ); @@ -903,7 +867,7 @@ describe("Eth app biding", () => { const result = await signTxWithResolution( eth, "44'/60'/0'/0/0", - "02f86d0146841dcd650085086a18cd7882fd50947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844a9059cbb000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000000000000000029784d963dbc85a1ec0", + "02f86d0146841dcd650085086a18cd7882fd50947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844a9059cbb0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d0000000000000000000000000000000000000000000000029784d963dbc85a1ec0", { erc20: true, nft: true, @@ -928,11 +892,11 @@ describe("Eth app biding", () => { test("signTransaction NFT with domain", async () => { jest.spyOn(axios, "request").mockImplementationOnce(async ({ url }) => { - if (url?.includes("dev.0xkvn.eth?challenge=0xa8a8b649")) { + if (url?.includes("dev.0xkvn.eth?challenge=0x1dad95c4")) { return { data: { payload: - "0101030201011301031401011204a8a8b64921013c200930786b766e2e6574682214b0b5b0106d69fe64545a60a68c014f7570d3f86115473045022100dc88dd13819497be8202dbdeddaefd68a1f64ad08855e2f35fc178625a06a7f1022023cdc8ca14233ee4778c81cd26d82e2a5ce27021f8c3cb51bb7d1125313a8cf5", + "01010302010113010314010112041dad95c421013c200d6465762e30786b766e2e65746822146cbcd73cd8e8a42844662f0a0e76d7f79afd933d1546304402200247a175329e4025500b6cb62fc466c8876a9e3d3cb681200ae70d5b69afdb2902200a5de51730d17f8d62e10ca4ddfc0b353e8b42e284293c074e4a62f96a7e9f9c", }, }; } @@ -962,16 +926,16 @@ describe("Eth app biding", () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e020000000 - <= a8a8b6499000 - => e022010081007f0101030201011301031401011204a8a8b64921013c200930786b766e2e6574682214b0b5b0106d69fe64545a60a68c014f7570d3f86115473045022100dc88dd13819497be8202dbdeddaefd68a1f64ad08855e2f35fc178625a06a7f1022023cdc8ca14233ee4778c81cd26d82e2a5ce27021f8c3cb51bb7d1125313a8cf5 + <= 1dad95c49000 + => e022010084008201010302010113010314010112041dad95c421013c200d6465762e30786b766e2e65746822146cbcd73cd8e8a42844662f0a0e76d7f79afd933d1546304402200247a175329e4025500b6cb62fc466c8876a9e3d3cb681200ae70d5b69afdb2902200a5de51730d17f8d62e10ca4ddfc0b353e8b42e284293c074e4a62f96a7e9f9c <= 9000 => e01600007301010645524337323160f80121c31a0d46b5279700f9df786054aa5ee5b88d4fde0000000000000001020147304502206224f90b61a09033d06ee1bd2045fb1f2edd26d479890d253229f3c2a1952aef0221008258bbde083cff7eab1e5b3e9dacccbdcf31232cc45740cb510f7aef00ec766e <= 9000 => e01400006f01010752617269626c6560f80121c31a0d46b5279700f9df786054aa5ee5000000000000000101014630440220587a77c4f5e7cc012e4e5e52548790a87c4eb20321249f3ef61e4018b107beeb02206ec8f371023bc15311c4637eb7ba7153fb9e47f1bb7b30db285f4bf9adaa2454 <= 9000 - => e004000096058000002c8000003c80000000000000000000000002f8ee0146841ad27480850912e4364a83023c549460f80121c31a0d46b5279700f9df786054aa5ee580b8c4b88d4fde0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000 + => e0040000ff058000002c8000003c80000000000000000000000002f8ee0146841ad27480850912e4364a83023c549460f80121c31a0d46b5279700f9df786054aa5ee580b8c4b88d4fde0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d0000000000000000000000000000000000000000000000000000000000112999000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000043078303000000000000000000000000000000000000000000000 <= 9000 - => e004800070000000000000000000000000112999000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000043078303000000000000000000000000000000000000000000000000000000000c0 + => e004800007000000000000c0 <= 01d82878b966f8e6fd94b76c942a7735e4668377f9030285aa2177bf77e59a39347f7908439d2968a8cdb261eae4498dbbf6c5808dc4974fc304486b373001a4349000 `), ); @@ -979,7 +943,7 @@ describe("Eth app biding", () => { const result = await signTxWithResolution( eth, "44'/60'/0'/0/0", - "02f8ee0146841ad27480850912e4364a83023c549460f80121c31a0d46b5279700f9df786054aa5ee580b8c4b88d4fde0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d000000000000000000000000b0b5b0106d69fe64545a60a68c014f7570d3f8610000000000000000000000000000000000000000000000000000000000112999000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000043078303000000000000000000000000000000000000000000000000000000000c0", + "02f8ee0146841ad27480850912e4364a83023c549460f80121c31a0d46b5279700f9df786054aa5ee580b8c4b88d4fde0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d0000000000000000000000006cbcd73cd8e8a42844662f0a0e76d7f79afd933d0000000000000000000000000000000000000000000000000000000000112999000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000043078303000000000000000000000000000000000000000000000000000000000c0", { erc20: true, nft: true, @@ -1051,7 +1015,7 @@ describe("Eth app biding", () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a000066035a5258e41d2489571d322189246dafa5ebde1f4699f4980000001200000001304402200ae8634c22762a8ba41d2acb1e068dcce947337c6dd984f13b820d396176952302203306a49d8a6c35b11a61088e1570b3928ca3a0db6bd36f577b5ef87628561ff7 - <= 9000 + <= 009000 `), ); const eth = new Eth(transport); @@ -1136,9 +1100,9 @@ describe("Eth app biding", () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a000066035a5258e41d2489571d322189246dafa5ebde1f4699f4980000001200000001304402200ae8634c22762a8ba41d2acb1e068dcce947337c6dd984f13b820d396176952302203306a49d8a6c35b11a61088e1570b3928ca3a0db6bd36f577b5ef87628561ff7 - <= 9000 + <= 009000 => e00a0000670455534454dac17f958d2ee523a2206206994597c13d831ec700000006000000013044022078c66ccea3e4dedb15a24ec3c783d7b582cd260daf62fd36afe9a8212a344aed0220160ba8c1c4b6a8aa6565bed20632a091aeeeb7bfdac67fc6589a6031acbf511c - <= 9000 + <= 019000 => f004010091028000534b00000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000001000000010000000100000000000186a00000000000030d4000000d6a00001618 <= 0003c4a1aef46539c90eaad9a71eee8319586e2b749793335060a2431c42d0d48901faac9386aaaf9d8d2cc3229aecf9e202f4b83f63e3fff7426ca07725d10fb29000 `), @@ -1177,9 +1141,9 @@ describe("Eth app biding", () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a000066035a5258e41d2489571d322189246dafa5ebde1f4699f4980000001200000001304402200ae8634c22762a8ba41d2acb1e068dcce947337c6dd984f13b820d396176952302203306a49d8a6c35b11a61088e1570b3928ca3a0db6bd36f577b5ef87628561ff7 - <= 9000 + <= 009000 => e00a0000670455534454dac17f958d2ee523a2206206994597c13d831ec700000006000000013044022078c66ccea3e4dedb15a24ec3c783d7b582cd260daf62fd36afe9a8212a344aed0220160ba8c1c4b6a8aa6565bed20632a091aeeeb7bfdac67fc6589a6031acbf511c - <= 9000 + <= 019000 => f0040300d3028000534b0000000002e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000010000000100000000000186a00000000000030d4000000d6a00001618 <= 0003c4a1aef46539c90eaad9a71eee8319586e2b749793335060a2431c42d0d48901faac9386aaaf9d8d2cc3229aecf9e202f4b83f63e3fff7426ca07725d10fb29000 `), @@ -1269,6 +1233,7 @@ describe("Eth app biding", () => { s: "0305fe1782f050839619c3e9627121bacd3a8dc87859e1ba5376fbd1b3bee4d4", }); }); + test("starkProvideQuantum", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1283,6 +1248,7 @@ describe("Eth app biding", () => { ); expect(result).toEqual(true); }); + test("starkProvideQuantum_v2", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1298,6 +1264,7 @@ describe("Eth app biding", () => { ); expect(result).toEqual(true); }); + test("starkDepositEth", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1320,16 +1287,15 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkDepositToken", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a0000670455534454dac17f958d2ee523a2206206994597c13d831ec700000006000000013044022078c66ccea3e4dedb15a24ec3c783d7b582cd260daf62fd36afe9a8212a344aed0220160ba8c1c4b6a8aa6565bed20632a091aeeeb7bfdac67fc6589a6031acbf511c - <= 9000 + <= 009000 => f008000034dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000001 <= 9000 - => e004000096058000002c8000003c800000000000000000000000f88d018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b86400aeef8a02ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c50000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000 - <= 9000 - => e00480000e0000000000000000000000030d40 + => e0040000a4058000002c8000003c800000000000000000000000f88d018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b86400aeef8a02ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000030d40 <= 1b294214de6341a0a63609f5643700c58be4b7aa46a5f56dea8c9ff5ecf4d5228662a3a4c8a6a0714d147b2a98071cfb892ed3f3edd5da049a2608605970b63dc29000 `), ); @@ -1351,6 +1317,7 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkWithdrawEth", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1373,11 +1340,12 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkWithdrawToken", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a0000670455534454dac17f958d2ee523a2206206994597c13d831ec700000006000000013044022078c66ccea3e4dedb15a24ec3c783d7b582cd260daf62fd36afe9a8212a344aed0220160ba8c1c4b6a8aa6565bed20632a091aeeeb7bfdac67fc6589a6031acbf511c - <= 9000 + <= 009000 => f008000034dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000001 <= 9000 => e004000063058000002c8000003c800000000000000000000000f84c018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000a42e1a7d4d02ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c5 @@ -1402,6 +1370,7 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkDepositCancel", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1424,6 +1393,7 @@ describe("Eth app biding", () => { v: "1c", }); }); + test("starkDepositReclaim", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1446,6 +1416,7 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkFullWithdrawal", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1465,6 +1436,7 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkFreeze", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1484,14 +1456,13 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("starkEscapeEth", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` => f00800003400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 <= 9000 - => e004000096058000002c8000003c800000000000000000000000f8ad018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b8849e3adac40000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010101142460171646987f20c714eda4b92812b2 - <= 9000 - => e00480002e2b811f56f27130937c267e29bd9e00000000000000000000000000000000000000000000000000000000000186a0 + => e0040000c4058000002c8000003c800000000000000000000000f8ad018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b8849e3adac40000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010101142460171646987f20c714eda4b92812b22b811f56f27130937c267e29bd9e00000000000000000000000000000000000000000000000000000000000186a0 <= 1c77220f9513431ecb2eeb53edee025eb78f1fd3c194d75f4988462b78bacd88b43e74b88584f9091a4bdb2605ec128e2bda7eaa262891bf83bb7b34acf22c6a9c9000 `), ); @@ -1508,16 +1479,15 @@ describe("Eth app biding", () => { v: "1c", }); }); + test("starkEscapeTokens", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e00a0000670455534454dac17f958d2ee523a2206206994597c13d831ec700000006000000013044022078c66ccea3e4dedb15a24ec3c783d7b582cd260daf62fd36afe9a8212a344aed0220160ba8c1c4b6a8aa6565bed20632a091aeeeb7bfdac67fc6589a6031acbf511c - <= 9000 + <= 009000 => f008000034dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000001 <= 9000 - => e004000096058000002c8000003c800000000000000000000000f8ad018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b8849e3adac40000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010102ce625e94458d39dd0bf3b45a843544dd4a - <= 9000 - => e00480002e14b8169045a3a3d15aa564b936c50000000000000000000000000000000000000000000000000000000000030d40 + => e0040000c4058000002c8000003c800000000000000000000000f8ad018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b8849e3adac40000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010102ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c50000000000000000000000000000000000000000000000000000000000030d40 <= 1c56846c1ec5ce862f0abb59054ae9a5279ddac47953907902a0dada43b9a0e06b35ad5523cf0b01efa3a369c24b80cd003f87a2f725cf40ecc2354290fc8369a29000 `), ); @@ -1539,12 +1509,11 @@ describe("Eth app biding", () => { v: "1c", }); }); + test("starkEscapeVerify", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e004000096058000002c8000003c800000000000000000000000f88d018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b8642dd5300602ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c50000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000 - <= 9000 - => e00480000e0000000000000000000000030d40 + => e0040000a4058000002c8000003c800000000000000000000000f88d018504e3b29200825208940102030405060708090a0b0c0d0e0f1011121314872bd72a24874000b8642dd5300602ce625e94458d39dd0bf3b45a843544dd4a14b8169045a3a3d15aa564b936c500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000030d40 <= 1b372586695f148927a74b6ef4b2e40f42b3a6e44afbd16cb4d3dcec6859aec1d2736da27ba0a716492e96ebd6cbbaec894af5cad24a2c6c3f683ade376f9fdc4f9000 `), ); @@ -1560,6 +1529,7 @@ describe("Eth app biding", () => { v: "1b", }); }); + test("eth2GetPublicKey", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1574,6 +1544,7 @@ describe("Eth app biding", () => { "a0fcd39edaa082bdbf23a0c01568471b8a2bd998c9ae347f7e7690e420bd2f96e436c215422aa86f233f67cbbdfb9b2f", }); }); + test("eth2SetWithdrawalIndex", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` @@ -1585,6 +1556,7 @@ describe("Eth app biding", () => { const result = await eth.eth2SetWithdrawalIndex(1); expect(result).toEqual(true); }); + test("getEIP1024PublicEncryptionKey", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` diff --git a/libs/ledgerjs/packages/hw-app-eth/tests/utils.unit.test.ts b/libs/ledgerjs/packages/hw-app-eth/tests/utils.unit.test.ts new file mode 100644 index 000000000000..947fe7fc50ad --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-eth/tests/utils.unit.test.ts @@ -0,0 +1,341 @@ +import BigNumber from "bignumber.js"; +import * as rlp from "@ethersproject/rlp"; +import { AddressZero } from "@ethersproject/constants"; +import { BigNumber as EthersBigNumber } from "@ethersproject/bignumber"; +import { type Transaction, serialize as serializeTransaction } from "@ethersproject/transactions"; +import { + getChainIdAsUint32, + getParity, + getV, + hexBuffer, + intAsHexBytes, + maybeHexBuffer, + mergeResolutions, + padHexString, + safeChunkTransaction, + splitPath, +} from "../src/utils"; + +const chainIdsToTest = [ + "1", // Always test Ethereum mainnet + "56", // Binance Smart Chain + "134", // Polygon + "109", // just under the limit for the EIP-155 operation to potentially be more than 1 byte + "110", // floor limit for the EIP-155 operation to be more than 1 byte if v is 1 + "111", // floor limit for the EIP-155 operation to be more than 1 byte even if v is 0 + "10000", // Random high value + "2716446429837000", // highest chainId known +]; + +describe("Eth app biding", () => { + describe("Utils", () => { + describe("padHexString", () => { + it("should prevent hex string from being odd length", () => { + expect(padHexString("123")).toEqual("0123"); + }); + }); + + describe("splitPath", () => { + it("should split derivation path correctly and respect hardened paths", () => { + expect(splitPath("44'/60'/123/456/789")).toEqual([ + 44 + 0x80000000, + 60 + 0x80000000, + 123, + 456, + 789, + ]); + }); + }); + + describe("hexBuffer", () => { + it("should convert hex string to buffer", () => { + const buff = Buffer.from("0123", "hex"); + expect(hexBuffer("0x123")).toEqual(buff); + expect(hexBuffer("123")).toEqual(buff); + expect(hexBuffer("0123")).toEqual(buff); + }); + }); + + describe("maybeHexBuffer", () => { + it("should bufferize hex string and return null for empty input", () => { + expect(maybeHexBuffer("0x123")).toEqual(Buffer.from("0123", "hex")); + expect(maybeHexBuffer("")).toEqual(null); + }); + }); + + describe("intAsHexBytes", () => { + it("should convert integer to hex string with correct number of bytes", () => { + expect(intAsHexBytes(123, 1)).toEqual("7b"); + expect(intAsHexBytes(123, 2)).toEqual("007b"); + expect(intAsHexBytes(123, 4)).toEqual("0000007b"); + }); + }); + + describe("mergeResolutions", () => { + it("should merge resolutions", () => { + const resolutions1 = { + nfts: ["nft1", "nft2"], + erc20Tokens: ["erc20Token1", "erc20Token2"], + externalPlugin: [{ payload: "payload1", signature: "signature1" }], + plugin: ["plugin1", "plugin2"], + domains: [ + { + registry: "ens" as const, + domain: "dev.0xkvn.eth", + address: "0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d", + type: "forward" as const, + }, + ], + }; + const resolutions2 = { + nfts: ["nft3", "nft4"], + erc20Tokens: ["erc20Token3", "erc20Token4"], + externalPlugin: [{ payload: "payload2", signature: "signature2" }], + plugin: ["plugin3", "plugin4"], + domains: [ + { + registry: "ens" as const, + domain: "0xkvn.eth", + address: "0xB0xB0b5B0106D69fE64545A60A68C014f7570D3F861", + type: "reverse" as const, + }, + ], + }; + expect(mergeResolutions([resolutions1, resolutions2])).toEqual({ + nfts: ["nft1", "nft2", "nft3", "nft4"], + erc20Tokens: ["erc20Token1", "erc20Token2", "erc20Token3", "erc20Token4"], + externalPlugin: [ + { payload: "payload1", signature: "signature1" }, + { payload: "payload2", signature: "signature2" }, + ], + plugin: ["plugin1", "plugin2", "plugin3", "plugin4"], + domains: [ + { + registry: "ens", + domain: "dev.0xkvn.eth", + address: "0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d", + type: "forward", + }, + { + registry: "ens", + domain: "0xkvn.eth", + address: "0xB0xB0b5B0106D69fE64545A60A68C014f7570D3F861", + type: "reverse", + }, + ], + }); + }); + }); + + describe("getParity", () => { + it("should return the v from the device for typed transactions (EIP-2718)", () => { + expect(getParity(0, new BigNumber(1), 1)).toEqual(0); + expect(getParity(1, new BigNumber(1), 1)).toEqual(1); + expect(getParity(0, new BigNumber(1), 2)).toEqual(0); + expect(getParity(1, new BigNumber(1), 2)).toEqual(1); + }); + + it.each(chainIdsToTest)( + "should return the v from the device for legacy transactions (EIP-155) with chainId: %s", + (chainId: string) => { + const chainIdBN = new BigNumber(chainId); + const chainIdUint4 = parseInt( + Buffer.from(padHexString(chainIdBN.toString(16)), "hex") + .subarray(0, 4) + .toString("hex"), + 16, + ); + const chainIdWithEIP155 = chainIdUint4 * 2 + 35; + + expect([ + getParity(chainIdWithEIP155 % 256, chainIdBN, null), + getParity((chainIdWithEIP155 + 1) % 256, chainIdBN, null), + ]).toEqual([0, 1]); + }, + ); + }); + + describe("getChainIdAsUint32", () => { + it("should return the chainId as a 4 bytes integer", () => { + expect(getChainIdAsUint32(1)).toEqual(1); + expect(getChainIdAsUint32(134)).toEqual(134); + expect(getChainIdAsUint32(new BigNumber(134))).toEqual(134); + expect(getChainIdAsUint32(new BigNumber(10_000_000_000))).toEqual(39062500); + expect(getChainIdAsUint32(10_000_000_000)).toEqual(39062500); + }); + }); + + describe("getV", () => { + it("should return the v value from the device for legacy transactions non providing a chainId", () => { + expect(getV(27, new BigNumber(0), null)).toEqual((27).toString(16)); + expect(getV(28, new BigNumber(0), null)).toEqual((28).toString(16)); + }); + + it.each(chainIdsToTest)( + "should return the v with EIP-155 applied for legacy transactions providing chainId: %s", + (chainId: string) => { + const chainIdBN = new BigNumber(chainId); + const chainIdUint4 = parseInt( + Buffer.from(padHexString(chainIdBN.toString(16)), "hex") + .subarray(0, 4) + .toString("hex"), + 16, + ); + const chainIdWithEIP155 = chainIdUint4 * 2 + 35; + const vEven = chainIdWithEIP155 % 256; + const vOdd = (chainIdWithEIP155 + 1) % 256; + + expect(getV(vEven, chainIdBN, null)).toEqual( + padHexString(chainIdBN.multipliedBy(2).plus(35).toString(16)), + ); + expect(getV(vOdd, chainIdBN, null)).toEqual( + padHexString(chainIdBN.multipliedBy(2).plus(35).plus(1).toString(16)), + ); + }, + ); + + it.each(chainIdsToTest)( + "should return the parity for transactions using EIP-2718 and no matter the chainId: %s", + (chainId: string) => { + const chainIdBN = new BigNumber(chainId); + expect(getV(0, chainIdBN, 1)).toEqual("00"); + expect(getV(1, chainIdBN, 1)).toEqual("01"); + expect(getV(0, chainIdBN, 2)).toEqual("00"); + expect(getV(1, chainIdBN, 2)).toEqual("01"); + }, + ); + + it("should throw an error if the v value is invalid", () => { + expect(() => getV(26, new BigNumber(10_000), null)).toThrow("Invalid v value"); + }); + }); + + describe("safeChunkTransaction", () => { + // Derivation of 44'/60'/0'/0'/0 + // 21B + const derivationPathBuff = Buffer.from("058000002c8000003c800000008000000000000000", "hex"); + + it("should return a single chunk if the transaction is small enough", () => { + const rawTx: Transaction = { + to: AddressZero, + nonce: 0, + value: EthersBigNumber.from(0), + gasPrice: EthersBigNumber.from(1), + gasLimit: EthersBigNumber.from(2), + data: "0x", + chainId: 1, + }; + const serialized = serializeTransaction(rawTx); + const rlpBuff = Buffer.from(serialized.slice(2), "hex"); + + if (rlpBuff.length + derivationPathBuff.length > 255) + throw new Error("Transaction too big"); + + const payload = Buffer.concat([derivationPathBuff, rlpBuff]); + expect(safeChunkTransaction(rlpBuff, derivationPathBuff, rawTx.type)).toEqual([payload]); + }); + + it("should return multiple 255B chunks for typed transactions (EIP-2718)", () => { + const rawTx: Transaction = { + to: AddressZero, + nonce: 0, + value: EthersBigNumber.from(0), + gasPrice: EthersBigNumber.from(1), + gasLimit: EthersBigNumber.from(2), + data: "0x" + new Array(256).fill("00").join(""), + chainId: 1, + type: 1, + }; + const serialized = serializeTransaction(rawTx); + const rlpBuff = Buffer.from(serialized.slice(2), "hex"); + + if (rlpBuff.length + derivationPathBuff.length < 255) + throw new Error("Transaction too small"); + + // Chunk size should be 255B + const payload = Buffer.concat([derivationPathBuff, rlpBuff]); + const chunks = safeChunkTransaction(rlpBuff, derivationPathBuff, rawTx.type); + expect(chunks.length).toEqual(2); + expect(chunks).toEqual([payload.subarray(0, 255), payload.subarray(255)]); + }); + + it("should return multiple variable chunks for legacy transactions to prevent chunking just before the [r,s,v] for a 1 byte chainId", () => { + const rawTx: Transaction = { + to: AddressZero, + nonce: 0, + value: EthersBigNumber.from(0), + gasPrice: EthersBigNumber.from(1), + gasLimit: EthersBigNumber.from(2), + data: "0x" + new Array(458).fill("00").join(""), + chainId: 127, // RLP of VRS should be only 3B as this value is <= 0x7f + type: 0, + }; + const serialized = serializeTransaction(rawTx); + const rlpBuff = Buffer.from(serialized.slice(2), "hex"); + + if (rlpBuff.length + derivationPathBuff.length !== 513) + throw new Error("Transaction not chunking before the [r,s,v]"); + + // Chunk size should be 254B to avoid chunking just before the [r,s,v] + const payload = Buffer.concat([derivationPathBuff, rlpBuff]); + const chunks = safeChunkTransaction(rlpBuff, derivationPathBuff, rawTx.type); + expect(chunks.length).toEqual(3); + expect(chunks).toEqual([ + payload.subarray(0, 254), + payload.subarray(254, 508), + payload.subarray(508), + ]); + }); + + // Above 6 bytes values, ethers will simply fail parsing/serializing the transaction + it.each([1, 2, 3, 4, 5, 6])( + "should return multiple variable chunks for legacy transactions to prevent chunking just before the [r,s,v] for a %s byte(s) chainId", + (chainIdSizeInBytes: number) => { + const chainId = new BigNumber("0x" + new Array(chainIdSizeInBytes).fill("81").join("")); + const encodedVrs = hexBuffer( + rlp.encode(["0x" + chainId.toString(16), "0x", "0x"]), + ).subarray(1); + + for (let i = 1; i <= encodedVrs.length; i++) { + const rawTx: Transaction = { + to: AddressZero, + nonce: 0, + value: EthersBigNumber.from(0), + gasPrice: EthersBigNumber.from(1), + gasLimit: EthersBigNumber.from(2), + data: "0x" + new Array(458 - chainIdSizeInBytes).fill("00").join(""), // This should make a 492B long rlp + chainId: chainId.toNumber(), + type: 0, + }; + const serialized = serializeTransaction(rawTx); + const rlpBuff = Buffer.from(serialized.slice(2), "hex"); + + if (rlpBuff.length + derivationPathBuff.length !== 513) + throw new Error("Transaction not chunking before the [r,s,v]"); + + // Just made from observation, don't treat this as a general rule + const chunkSize = + chainIdSizeInBytes < 3 + ? 255 - chainIdSizeInBytes + : chainIdSizeInBytes < 5 + ? 256 - chainIdSizeInBytes + : 257 - chainIdSizeInBytes; + const chunks = safeChunkTransaction(rlpBuff, derivationPathBuff, rawTx.type); + expect(chunks.length).toEqual(3); + expect( + [`0000` + encodedVrs.toString("hex"), `00` + encodedVrs.toString("hex")].includes( + chunks[2].toString("hex"), + ), + ).toBe(true); + const payload = Buffer.concat([derivationPathBuff, rlpBuff]); + expect(safeChunkTransaction(rlpBuff, derivationPathBuff, rawTx.type)).toEqual([ + payload.subarray(0, chunkSize), + payload.subarray(chunkSize, chunkSize * 2), + payload.subarray(chunkSize * 2), + ]); + } + }, + ); + }); + }); +}); From 289487feaa9fc0502174c7d03620922ab436bcc9 Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:28:03 +0200 Subject: [PATCH 3/8] Refactoring and cleaning --- libs/ledgerjs/packages/hw-app-eth/src/Eth.ts | 36 ++++++------------- .../hw-app-eth/src/services/ledger/nfts.ts | 2 +- .../ledgerjs/packages/hw-app-eth/src/utils.ts | 23 +++++++----- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts b/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts index 24bc560224b9..42c1e7ae2b22 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/Eth.ts @@ -1,26 +1,15 @@ -/******************************************************************************** - * Ledger Node JS API - * (c) 2016-2017 Ledger - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ********************************************************************************/ -/* eslint @typescript-eslint/no-duplicate-enum-values: 1 */ -// FIXME drop: -import type Transport from "@ledgerhq/hw-transport"; +/* eslint-disable @typescript-eslint/no-duplicate-enum-values */ + +import { log } from "@ledgerhq/logs"; import { BigNumber } from "bignumber.js"; -// NB: these are temporary import for the deprecated fallback mechanism +import type Transport from "@ledgerhq/hw-transport"; +import { EIP712Message } from "@ledgerhq/types-live"; +import { parse as parseTransaction } from "@ethersproject/transactions"; import { LedgerEthTransactionResolution, LoadConfig, ResolutionConfig } from "./services/types"; -import { log } from "@ledgerhq/logs"; +import { EthAppNftNotSupported, EthAppPleaseEnableContractData } from "./errors"; +import { signEIP712HashedMessage, signEIP712Message } from "./modules/EIP712"; +import { domainResolutionFlow } from "./modules/Domains"; +import ledgerService from "./services/ledger"; import { safeChunkTransaction, getV, @@ -30,11 +19,6 @@ import { padHexString, splitPath, } from "./utils"; -import { domainResolutionFlow } from "./modules/Domains"; -import ledgerService from "./services/ledger"; -import { EthAppNftNotSupported, EthAppPleaseEnableContractData } from "./errors"; -import { signEIP712HashedMessage, signEIP712Message } from "./modules/EIP712"; -import { EIP712Message } from "@ledgerhq/types-live"; export { ledgerService }; export * from "./utils"; diff --git a/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/nfts.ts b/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/nfts.ts index c48f3f6411dd..e3434e1fd3c6 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/nfts.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/services/ledger/nfts.ts @@ -40,7 +40,7 @@ export const getNFTInfo = async ( ?.reduce((acc, curr) => (acc += String.fromCharCode(parseInt(curr, 16))), ""); // convert hex to string return { - contractAddress: contractAddress, + contractAddress, collectionName: collectionName || "", data: payload, }; diff --git a/libs/ledgerjs/packages/hw-app-eth/src/utils.ts b/libs/ledgerjs/packages/hw-app-eth/src/utils.ts index 12e9b7448c30..669fd9571c45 100644 --- a/libs/ledgerjs/packages/hw-app-eth/src/utils.ts +++ b/libs/ledgerjs/packages/hw-app-eth/src/utils.ts @@ -19,22 +19,27 @@ export const padHexString = (str: string) => { }; export function splitPath(path: string): number[] { - const result: number[] = []; - const components = path.split("/"); - components.forEach(element => { - let number = parseInt(element, 10); - if (isNaN(number)) { + const splittedPath: number[] = []; + + const paths = path.split("/"); + paths.forEach(path => { + let value = parseInt(path, 10); + if (isNaN(value)) { return; // FIXME shouldn't it throws instead? } - if (element.length > 1 && element[element.length - 1] === "'") { - number += 0x80000000; + // Detect hardened paths + if (path.length > 1 && path[path.length - 1] === "'") { + value += 0x80000000; } - result.push(number); + splittedPath.push(value); }); - return result; + + return splittedPath; } export function hexBuffer(str: string): Buffer { + if (!str) return Buffer.alloc(0); + const strWithoutPrefix = str.startsWith("0x") ? str.slice(2) : str; return Buffer.from(padHexString(strWithoutPrefix), "hex"); } From 61ebe44508eedb4b6e3441861d30213b3c67a44c Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:33:33 +0200 Subject: [PATCH 4/8] Remove `ethers` from @ledgerhq/evm-tools as well --- libs/evm-tools/package.json | 7 ++++--- libs/evm-tools/src/message/EIP712/index.ts | 12 ++++-------- pnpm-lock.yaml | 9 ++++++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libs/evm-tools/package.json b/libs/evm-tools/package.json index f55487071526..67685b0bb699 100644 --- a/libs/evm-tools/package.json +++ b/libs/evm-tools/package.json @@ -45,11 +45,12 @@ }, "license": "Apache-2.0", "dependencies": { + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", "@ledgerhq/cryptoassets-evm-signatures": "workspace:^", "@ledgerhq/live-env": "workspace:^", "axios": "1.7.7", - "crypto-js": "4.2.0", - "ethers": "5.7.2" + "crypto-js": "4.2.0" }, "devDependencies": { "@ledgerhq/types-live": "workspace:^", @@ -71,4 +72,4 @@ "test": "jest", "unimported": "unimported" } -} +} \ No newline at end of file diff --git a/libs/evm-tools/src/message/EIP712/index.ts b/libs/evm-tools/src/message/EIP712/index.ts index 84f826bf237b..5bcfeb253b36 100644 --- a/libs/evm-tools/src/message/EIP712/index.ts +++ b/libs/evm-tools/src/message/EIP712/index.ts @@ -1,8 +1,9 @@ -import { ethers } from "ethers"; import axios from "axios"; import SHA224 from "crypto-js/sha224"; import { getEnv } from "@ledgerhq/live-env"; import { EIP712Message } from "@ledgerhq/types-live"; +import { AddressZero } from "@ethersproject/constants"; +import { _TypedDataEncoder as TypedDataEncoder } from "@ethersproject/hash"; import EIP712CAL from "@ledgerhq/cryptoassets-evm-signatures/data/eip712"; import EIP712CALV2 from "@ledgerhq/cryptoassets-evm-signatures/data/eip712_v2"; import { CALServiceEIP712Response, MessageFilters } from "./types"; @@ -57,8 +58,7 @@ export const getFiltersForMessage = async ( calServiceURL?: string | null, ): Promise => { const schemaHash = getSchemaHashForMessage(message); - const verifyingContract = - message.domain?.verifyingContract?.toLowerCase() || ethers.constants.AddressZero; + const verifyingContract = message.domain?.verifyingContract?.toLowerCase() || AddressZero; try { if (calServiceURL) { const { data } = await axios.get(`${calServiceURL}/v1/dapps`, { @@ -197,11 +197,7 @@ export const getEIP712FieldsDisplayedOnNano = async ( displayedInfos.push({ label: "Message hash", - value: ethers.utils._TypedDataEncoder.hashStruct( - messageData.primaryType, - otherTypes, - messageData.message, - ), + value: TypedDataEncoder.hashStruct(messageData.primaryType, otherTypes, messageData.message), }); return displayedInfos; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 747c3e3548d1..24c3a3d3ed81 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3321,6 +3321,12 @@ importers: libs/evm-tools: dependencies: + '@ethersproject/constants': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/hash': + specifier: ^5.7.0 + version: 5.7.0 '@ledgerhq/cryptoassets-evm-signatures': specifier: workspace:^ version: link:../ledgerjs/packages/cryptoassets-evm-signatures @@ -3333,9 +3339,6 @@ importers: crypto-js: specifier: 4.2.0 version: 4.2.0 - ethers: - specifier: 5.7.2 - version: 5.7.2 devDependencies: '@ledgerhq/types-live': specifier: workspace:^ From 29f2ad3e23f7499c2e0882578eace37dc3dc301d Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:08:07 +0200 Subject: [PATCH 5/8] Remove `applyEIP155` helper in `coin-evm` It's now unecessary as `hw-app-eth` has now a correct `v` for legacy txs --- .../coin-evm/docs/signOperation.md | 4 -- .../__tests__/unit/signOperation.unit.test.ts | 40 ++----------------- .../coin-evm/src/signOperation.ts | 29 +------------- 3 files changed, 5 insertions(+), 68 deletions(-) diff --git a/libs/coin-modules/coin-evm/docs/signOperation.md b/libs/coin-modules/coin-evm/docs/signOperation.md index a32c486f8fd4..80b4ce3e8db9 100644 --- a/libs/coin-modules/coin-evm/docs/signOperation.md +++ b/libs/coin-modules/coin-evm/docs/signOperation.md @@ -4,10 +4,6 @@ ## Methods -#### applyEIP155 - -[EIP-155](https://eips.ethereum.org/EIPS/eip-155 "EIP-155") is a standard designed to mitigate replay attacks across various EVM chains by altering the `v` value within the ECDSA signature of a transaction. While the `@ledgerhq/hw-app-eth` library already tries to implement this modification, its result is unstable, therefore, the `applyEIP155` helper function has been created to reapply the EIP depending on the outcome of the Ethereum app binding. - #### buildSignOperation `factory of [standard]` This observable is responsible for applying the last set of transformations to a Ledger Live transaction. These transformations include things such as updating the recipient address, particularly when dealing with ERC20/721/1155 transactions crafted by Ledger Live itself, or applying the correct nonce. diff --git a/libs/coin-modules/coin-evm/src/__tests__/unit/signOperation.unit.test.ts b/libs/coin-modules/coin-evm/src/__tests__/unit/signOperation.unit.test.ts index 6212ff2b172a..b2e7ada8790b 100644 --- a/libs/coin-modules/coin-evm/src/__tests__/unit/signOperation.unit.test.ts +++ b/libs/coin-modules/coin-evm/src/__tests__/unit/signOperation.unit.test.ts @@ -1,15 +1,15 @@ import BigNumber from "bignumber.js"; import { Account } from "@ledgerhq/types-live"; import { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; -import { getCryptoCurrencyById, listCryptoCurrencies } from "@ledgerhq/cryptoassets"; -import type { EvmSigner } from "../../types/signer"; -import { buildSignOperation, applyEIP155 } from "../../signOperation"; import { Transaction as EvmTransaction } from "../../types"; import { makeAccount } from "../fixtures/common.fixtures"; +import { buildSignOperation } from "../../signOperation"; +import { DEFAULT_NONCE } from "../../createTransaction"; import * as nodeApi from "../../api/node/rpc.common"; +import type { EvmSigner } from "../../types/signer"; import { getEstimatedFees } from "../../logic"; -import { DEFAULT_NONCE } from "../../createTransaction"; import { getCoinConfig } from "../../config"; jest.mock("../../config"); @@ -140,37 +140,5 @@ describe("EVM Family", () => { }); }); }); - - describe("applyEIP155", () => { - const chainIds = listCryptoCurrencies(true) - .filter(c => c.family === "evm" && c.ethereumLikeInfo !== undefined) - .map(c => c.ethereumLikeInfo!.chainId) - .sort((a, b) => a - b); - - const possibleHexV = [ - "00", // 0 - ethereum + testnets should always retrun 0/1 from hw-app-eth - "01", // 1 - "1b", // 27 - type 0 transactions from other chains (when chain id > 109) should always return 27/28 - "1c", // 28 - ]; - - chainIds.forEach(chainId => { - possibleHexV.forEach(v => { - it(`should return an EIP155 compatible v for chain id ${chainId} with v = ${parseInt( - v, - 16, - )}`, () => { - const eip155Logic = chainId * 2 + 35; - expect( - [eip155Logic, eip155Logic + 1], // eip155 + parity - ).toContain(applyEIP155(v, chainId)); - }); - }); - - it("should return the value given by the nano as is if we can't figure out parity from it", () => { - expect(applyEIP155("1b39", chainId)).toBe(6969); - }); - }); - }); }); }); diff --git a/libs/coin-modules/coin-evm/src/signOperation.ts b/libs/coin-modules/coin-evm/src/signOperation.ts index 694bd183516a..94bb97911d4a 100644 --- a/libs/coin-modules/coin-evm/src/signOperation.ts +++ b/libs/coin-modules/coin-evm/src/signOperation.ts @@ -10,32 +10,6 @@ import { prepareForSignOperation } from "./prepareTransaction"; import { getSerializedTransaction } from "./transaction"; import { Transaction } from "./types"; -/** - * Transforms the ECDSA signature paremeter v hexadecimal string received - * from the nano into an EIP155 compatible number. - * - * Reminder EIP155 transforms v this way: - * v = chainId * 2 + 35 - * (+ parity 1 or 0) - */ -export const applyEIP155 = (vAsHex: string, chainId: number): number => { - const v = parseInt(vAsHex, 16); - - if (v === 0 || v === 1) { - // if v is 0 or 1, it's already representing parity - return chainId * 2 + 35 + v; - } else if (v === 27 || v === 28) { - const parity = v - 27; // transforming v into 0 or 1 to become the parity - return chainId * 2 + 35 + parity; - } - // When chainId is lower than 109, hw-app-eth *can* return a v with EIP155 already applied - // e.g. bsc's chainId is 56 -> v then equals to 147/148 - // optimism's chainId is 10 -> v equals to 55/56 - // ethereum's chainId is 1 -> v equals to 0/1 - // goerli's chainId is 5 -> v equals to 0/1 - return v; -}; - /** * Sign Transaction with Ledger hardware */ @@ -80,12 +54,11 @@ export const buildSignOperation = o.next({ type: "device-signature-granted" }); // Signature is done - const { chainId = 0 } = account.currency.ethereumLikeInfo || /* istanbul ignore next */ {}; // Create a new serialized tx with the signature now const signature = await getSerializedTransaction(preparedTransaction, { r: "0x" + sig.r, s: "0x" + sig.s, - v: applyEIP155(typeof sig.v === "number" ? sig.v.toString(16) : sig.v, chainId), + v: typeof sig.v === "number" ? sig.v : parseInt(sig.v, 16), }); const operation = buildOptimisticOperation(account, { From 1e5f03de2a64a5868bf5bac91ccbe0de2ba57c71 Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:21:06 +0200 Subject: [PATCH 6/8] Add env var to force using legacy txs w/ EVMs --- libs/coin-modules/coin-evm/src/api/node/ledger.ts | 2 +- libs/coin-modules/coin-evm/src/api/node/rpc.common.ts | 5 ++++- libs/env/src/env.ts | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libs/coin-modules/coin-evm/src/api/node/ledger.ts b/libs/coin-modules/coin-evm/src/api/node/ledger.ts index 835b6fda0f32..02598d249ff2 100644 --- a/libs/coin-modules/coin-evm/src/api/node/ledger.ts +++ b/libs/coin-modules/coin-evm/src/api/node/ledger.ts @@ -244,7 +244,7 @@ export const getFeeData: NodeApi["getFeeData"] = async (currency, transaction) = * cf. libs/coin-evm/src/createTransaction.ts:23 */ options: { - useEIP1559: transaction.type === 2, + useEIP1559: getEnv("EVM_FORCE_LEGACY_TRANSACTIONS") ? false : transaction.type === 2, overrideGasTracker: { type: "ledger", explorerId: node.explorerId }, }, }); diff --git a/libs/coin-modules/coin-evm/src/api/node/rpc.common.ts b/libs/coin-modules/coin-evm/src/api/node/rpc.common.ts index 15d5b51b36bf..9a3ce7b7c84e 100644 --- a/libs/coin-modules/coin-evm/src/api/node/rpc.common.ts +++ b/libs/coin-modules/coin-evm/src/api/node/rpc.common.ts @@ -2,6 +2,7 @@ import { ethers } from "ethers"; import BigNumber from "bignumber.js"; import { log } from "@ledgerhq/logs"; +import { getEnv } from "@ledgerhq/live-env"; import { delay } from "@ledgerhq/live-promise"; import { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; import { makeLRUCache } from "@ledgerhq/live-network/cache"; @@ -151,7 +152,9 @@ export const getGasEstimation: NodeApi["getGasEstimation"] = (account, transacti export const getFeeData: NodeApi["getFeeData"] = currency => withApi(currency, async api => { const block = await api.getBlock("latest"); - const currencySupports1559 = Boolean(block.baseFeePerGas); + const currencySupports1559 = getEnv("EVM_FORCE_LEGACY_TRANSACTIONS") + ? false + : Boolean(block.baseFeePerGas); const feeData = await (async (): Promise< | { diff --git a/libs/env/src/env.ts b/libs/env/src/env.ts index a7a48d4963d4..b4936e40f388 100644 --- a/libs/env/src/env.ts +++ b/libs/env/src/env.ts @@ -828,6 +828,11 @@ const envDefinitions = { parser: floatParser, desc: "Replace transaction max priority fee factor for EIP1559 evm transaction. This value should be 1.1 minimum since this is the minimum increase required by most nodes", }, + EVM_FORCE_LEGACY_TRANSACTIONS: { + def: false, + parser: boolParser, + desc: "Force transaction type 0 on EVM networks", + }, ENABLE_NETWORK_LOGS: { def: false, parser: boolParser, From a32e47b0fd15e08ae542ee46241f73dd08aca0e3 Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:27:32 +0200 Subject: [PATCH 7/8] Changeset --- .changeset/eleven-tomatoes-approve.md | 5 +++++ .changeset/real-donuts-occur.md | 6 ++++++ .changeset/tiny-eagles-teach.md | 5 +++++ 3 files changed, 16 insertions(+) create mode 100644 .changeset/eleven-tomatoes-approve.md create mode 100644 .changeset/real-donuts-occur.md create mode 100644 .changeset/tiny-eagles-teach.md diff --git a/.changeset/eleven-tomatoes-approve.md b/.changeset/eleven-tomatoes-approve.md new file mode 100644 index 000000000000..53e3505be071 --- /dev/null +++ b/.changeset/eleven-tomatoes-approve.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/hw-app-eth": minor +--- + +Refactoring of transaction decoding and fix EIP-155 applied incorrectly for legacy transactions (type 0). The `v` can now be used as is, representing either the EIP-155 value or the parity (0/1) for transactions using EIP-2718. Ethers full library has now also been removes from dependencies to decrease install and bundle sizes. diff --git a/.changeset/real-donuts-occur.md b/.changeset/real-donuts-occur.md new file mode 100644 index 000000000000..84d57c77b9f8 --- /dev/null +++ b/.changeset/real-donuts-occur.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/coin-evm": patch +"@ledgerhq/live-env": patch +--- + +Remove helper `applyEIP155` now that `hw-app-eth` is fixed and returns a valid `v` in all possible cases. Adding a env var `EVM_FORCE_LEGACY_TRANSACTIONS` to force transaction type 0, making this change QA compatible. diff --git a/.changeset/tiny-eagles-teach.md b/.changeset/tiny-eagles-teach.md new file mode 100644 index 000000000000..ee6b05b24026 --- /dev/null +++ b/.changeset/tiny-eagles-teach.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/evm-tools": minor +--- + +Remove `ethers` from dependencies and use sub-librairies instead to reduce the package size. From 552679b83a3d18e18768d6a183e56f027825bb32 Mon Sep 17 00:00:00 2001 From: 0xkvn <44363395+lambertkevin@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:04:20 +0200 Subject: [PATCH 8/8] unimported --- libs/coin-modules/coin-evm/.unimportedrc.json | 13 +++++++-- .../packages/hw-app-eth/.unimportedrc.json | 28 +------------------ 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/libs/coin-modules/coin-evm/.unimportedrc.json b/libs/coin-modules/coin-evm/.unimportedrc.json index 6e87374ec3ea..aa3f50a3d9de 100644 --- a/libs/coin-modules/coin-evm/.unimportedrc.json +++ b/libs/coin-modules/coin-evm/.unimportedrc.json @@ -15,7 +15,12 @@ "src/operation.ts", "src/config.ts" ], - "ignoreUnimported": ["src/__tests__/*", "**/*.test.*", "**/*.spec.*", "src/datasets/ethereum1.ts"], + "ignoreUnimported": [ + "src/__tests__/*", + "**/*.test.*", + "**/*.spec.*", + "src/datasets/ethereum1.ts" + ], "ignoreUnresolved": [ "@ethersproject/bytes", "@ethersproject/logger", @@ -26,6 +31,8 @@ "@ethersproject/strings", "@ethersproject/hash", "@ethersproject/keccak256", - "@ledgerhq/cryptoassets-evm-signatures/data/evm/index" + "@ethersproject/signing-key", + "@ledgerhq/cryptoassets-evm-signatures/data/evm/index", + "bn.js" ] -} +} \ No newline at end of file diff --git a/libs/ledgerjs/packages/hw-app-eth/.unimportedrc.json b/libs/ledgerjs/packages/hw-app-eth/.unimportedrc.json index b1fc83f28f5c..92e3d84044e8 100644 --- a/libs/ledgerjs/packages/hw-app-eth/.unimportedrc.json +++ b/libs/ledgerjs/packages/hw-app-eth/.unimportedrc.json @@ -5,32 +5,6 @@ "ignoreUnused": [ "@ledgerhq/hw-transport-mocker" ], - "ignoreUnresolved": [ - "@ethersproject/abstract-signer", - "@ethersproject/address", - "@ethersproject/base64", - "@ethersproject/basex", - "@ethersproject/bignumber", - "@ethersproject/bytes", - "@ethersproject/constants", - "@ethersproject/contracts", - "@ethersproject/hash", - "@ethersproject/hdnode", - "@ethersproject/json-wallets", - "@ethersproject/keccak256", - "@ethersproject/logger", - "@ethersproject/properties", - "@ethersproject/providers", - "@ethersproject/random", - "@ethersproject/sha2", - "@ethersproject/signing-key", - "@ethersproject/solidity", - "@ethersproject/strings", - "@ethersproject/transactions", - "@ethersproject/units", - "@ethersproject/wallet", - "@ethersproject/web", - "@ethersproject/wordlists" - ], + "ignoreUnresolved": [], "ignoreUnimported": [] } \ No newline at end of file