Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[LIVE-14459] Bugfix - Fix invalid EIP-155 v and refactor transaction decoding #8175

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-tomatoes-approve.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions .changeset/real-donuts-occur.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/tiny-eagles-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/evm-tools": minor
---

Remove `ethers` from dependencies and use sub-librairies instead to reduce the package size.
13 changes: 10 additions & 3 deletions libs/coin-modules/coin-evm/.unimportedrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
]
}
}
4 changes: 0 additions & 4 deletions libs/coin-modules/coin-evm/docs/signOperation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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);
});
});
});
});
});
2 changes: 1 addition & 1 deletion libs/coin-modules/coin-evm/src/api/node/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
},
});
Expand Down
5 changes: 4 additions & 1 deletion libs/coin-modules/coin-evm/src/api/node/rpc.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<
| {
Expand Down
29 changes: 1 addition & 28 deletions libs/coin-modules/coin-evm/src/signOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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, {
Expand Down
5 changes: 5 additions & 0 deletions libs/env/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions libs/evm-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:^",
Expand All @@ -71,4 +72,4 @@
"test": "jest",
"unimported": "unimported"
}
}
}
12 changes: 4 additions & 8 deletions libs/evm-tools/src/message/EIP712/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -57,8 +58,7 @@ export const getFiltersForMessage = async (
calServiceURL?: string | null,
): Promise<MessageFilters | undefined> => {
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<CALServiceEIP712Response>(`${calServiceURL}/v1/dapps`, {
Expand Down Expand Up @@ -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;
Expand Down
28 changes: 1 addition & 27 deletions libs/ledgerjs/packages/hw-app-eth/.unimportedrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
}
9 changes: 6 additions & 3 deletions libs/ledgerjs/packages/hw-app-eth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:^",
Expand All @@ -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",
Expand Down
Loading
Loading