Skip to content
This repository has been archived by the owner on Jun 27, 2022. It is now read-only.

[WIP] Add automated preparation of Ethereum device app for plugins and ERC20 #587

Merged
merged 30 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
166191b
PoC: Add automatic support for Ethereum plugins
TamtamHero Mar 22, 2021
38c72de
add compat with old nano apps
machard Apr 26, 2021
cc9a04d
same same setExternalPlugin
machard Apr 26, 2021
01d0107
Support signed plugins
TamtamHero May 4, 2021
71ba9dc
fix crypto-assets imports
machard May 5, 2021
53100ee
update paths
machard May 5, 2021
6c6daa8
remove useless yarn.lock
machard May 5, 2021
aeba097
setup
machard May 6, 2021
1b02a4e
safety
machard May 6, 2021
7a696dc
step
machard May 7, 2021
1241f1e
doc
machard May 7, 2021
e5617ac
update json + handle erc20ofInterests
machard May 7, 2021
a6bdca5
remove todo
machard May 7, 2021
4547ead
add logs
machard May 7, 2021
cdd0d27
test attemp
machard May 10, 2021
e348025
test ok
machard May 10, 2021
68dddc8
fix link
machard May 10, 2021
9413041
remove rlp
machard May 10, 2021
97c291d
remove console log
machard May 10, 2021
12cbdd0
Merge branch 'master' into auto-plugin
gre May 10, 2021
3f1b39d
Merge branch 'master' into auto-plugin
gre May 10, 2021
1b7cd40
prettify json import
machard May 10, 2021
8a9a4f2
Merge branch 'auto-plugin' of github.com:TamtamHero/ledgerjs into aut…
machard May 10, 2021
1f2c4fd
fix test
machard May 11, 2021
034ad45
ci
gre May 11, 2021
e79b7fc
update cal
machard May 11, 2021
138ed82
Merge branch 'auto-plugin' of github.com:TamtamHero/ledgerjs into aut…
machard May 11, 2021
71bb241
ensure lowercase
machard May 11, 2021
148e00a
Merge branch 'master' into auto-plugin
gre May 14, 2021
f036341
Merge branch 'master' into auto-plugin
gre May 17, 2021
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
13,920 changes: 13,920 additions & 0 deletions packages/cryptoassets/data/dapps/ethereum.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/cryptoassets/data/erc20-signatures.js

Large diffs are not rendered by default.

301 changes: 10 additions & 291 deletions packages/cryptoassets/data/erc20.js

Large diffs are not rendered by default.

293 changes: 6 additions & 287 deletions packages/cryptoassets/data/exchange/erc20.js

Large diffs are not rendered by default.

59 changes: 42 additions & 17 deletions packages/hw-app-eth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,62 @@ Ledger Hardware Wallet ETH JavaScript bindings.

#### Table of Contents

- [byContractAddress](#bycontractaddress)
- [getInfosForContractMethod](#getinfosforcontractmethod)
- [Parameters](#parameters)
- [byContractAddress](#bycontractaddress)
- [Parameters](#parameters-1)
- [list](#list)
- [Eth](#eth)
- [Parameters](#parameters-1)
- [Parameters](#parameters-2)
- [Examples](#examples)
- [getAddress](#getaddress)
- [Parameters](#parameters-2)
- [Parameters](#parameters-3)
- [Examples](#examples-1)
- [provideERC20TokenInformation](#provideerc20tokeninformation)
- [Parameters](#parameters-3)
- [Parameters](#parameters-4)
- [Examples](#examples-2)
- [signTransaction](#signtransaction)
- [Parameters](#parameters-4)
- [Parameters](#parameters-5)
- [Examples](#examples-3)
- [getAppConfiguration](#getappconfiguration)
- [signPersonalMessage](#signpersonalmessage)
- [Parameters](#parameters-5)
- [Parameters](#parameters-6)
- [Examples](#examples-4)
- [signEIP712HashedMessage](#signeip712hashedmessage)
- [Parameters](#parameters-6)
- [Parameters](#parameters-7)
- [Examples](#examples-5)
- [starkGetPublicKey](#starkgetpublickey)
- [Parameters](#parameters-7)
- [starkSignOrder](#starksignorder)
- [Parameters](#parameters-8)
- [starkSignOrder_v2](#starksignorder_v2)
- [starkSignOrder](#starksignorder)
- [Parameters](#parameters-9)
- [starkSignTransfer](#starksigntransfer)
- [starkSignOrder_v2](#starksignorder_v2)
- [Parameters](#parameters-10)
- [starkSignTransfer_v2](#starksigntransfer_v2)
- [starkSignTransfer](#starksigntransfer)
- [Parameters](#parameters-11)
- [starkProvideQuantum](#starkprovidequantum)
- [starkSignTransfer_v2](#starksigntransfer_v2)
- [Parameters](#parameters-12)
- [starkProvideQuantum_v2](#starkprovidequantum_v2)
- [starkProvideQuantum](#starkprovidequantum)
- [Parameters](#parameters-13)
- [starkUnsafeSign](#starkunsafesign)
- [starkProvideQuantum_v2](#starkprovidequantum_v2)
- [Parameters](#parameters-14)
- [eth2GetPublicKey](#eth2getpublickey)
- [starkUnsafeSign](#starkunsafesign)
- [Parameters](#parameters-15)
- [eth2GetPublicKey](#eth2getpublickey)
- [Parameters](#parameters-16)
- [Examples](#examples-6)
- [eth2SetWithdrawalIndex](#eth2setwithdrawalindex)
- [Parameters](#parameters-16)
- [Parameters](#parameters-17)
- [setExternalPlugin](#setexternalplugin)
- [Parameters](#parameters-18)

### getInfosForContractMethod

Retrieve the metadatas a given contract address and a method selector

#### Parameters

- `contractAddress`
- `selector`

### byContractAddress

Expand Down Expand Up @@ -364,3 +377,15 @@ It shall be run before the ETH 2 deposit transaction is signed. If not called, t
- `withdrawalIndex` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** index path in the EIP 2334 path m/12381/3600/withdrawalIndex/0

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** True if the method was executed successfully

#### setExternalPlugin

Set the name of the plugin that should be used to parse the next transaction

##### Parameters

- `pluginName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** string containing the name of the plugin, must have length between 1 and 30 bytes
- `contractAddress` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
- `selector` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** True if the method was executed successfully
2 changes: 1 addition & 1 deletion packages/hw-app-eth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@ledgerhq/errors": "^5.50.0",
"@ledgerhq/hw-transport": "^5.51.1",
"bignumber.js": "^9.0.1",
"rlp": "^2.2.6"
"ethers": "^5.1.4"
},
"devDependencies": {
"flow-bin": "^0.149.0"
Expand Down
132 changes: 118 additions & 14 deletions packages/hw-app-eth/src/Eth.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import { splitPath, foreach } from "./utils";
import { EthAppPleaseEnableContractData } from "@ledgerhq/errors";
import type Transport from "@ledgerhq/hw-transport";
import { BigNumber } from "bignumber.js";
import { encode, decode } from "rlp";
import { ethers } from "ethers";
import { byContractAddress } from "./erc20";
import { getInfosForContractMethod } from "./contracts";

export type StarkQuantizationType =
| "eth"
Expand Down Expand Up @@ -87,6 +89,7 @@ export default class Eth {
"starkUnsafeSign",
"eth2GetPublicKey",
"eth2SetWithdrawalIndex",
"setExternalPlugin",
],
scrambleKey
);
Expand Down Expand Up @@ -167,17 +170,7 @@ export default class Eth {
* const signed = await appEth.signTransaction(path, rawTxHex)
*/
provideERC20TokenInformation({ data }: { data: Buffer }): Promise<boolean> {
return this.transport.send(0xe0, 0x0a, 0x00, 0x00, data).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6d00) {
// this case happen for older version of ETH app, since older app version had the ERC20 data hardcoded, it's fine to assume it worked.
// we return a flag to know if the call was effective or not
return false;
}
throw e;
}
);
return _provideERC20TokenInformation.call(this, { data });
}

/**
Expand All @@ -199,11 +192,18 @@ export default class Eth {
let toSend = [];
let response;
// Check if the TX is encoded following EIP 155
let rlpTx = decode(rawTx);
let rlpTx = ethers.utils.RLP.decode("0x" + rawTxHex).map((hex) =>
Buffer.from(hex.slice(2), "hex")
);

let rlpOffset = 0;
let chainIdPrefix = "";
if (rlpTx.length > 6) {
let rlpVrs = encode(rlpTx.slice(-3));
let rlpVrs = Buffer.from(
ethers.utils.RLP.encode(rlpTx.slice(-3)).slice(2),
"hex"
);

rlpOffset = rawTx.length - (rlpVrs.length - 1);
const chainIdSrc = rlpTx[6];
const chainIdBuf = Buffer.alloc(4);
Expand Down Expand Up @@ -240,6 +240,51 @@ export default class Eth {
toSend.push(buffer);
offset += chunkSize;
}

rlpTx = ethers.utils.RLP.decode("0x" + rawTxHex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes signTransactionLargeChainID2 test fail with "data array too short".
Should we catch silently this case or does the test needs to be fixed ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also just before this part, we modify rawTx in case its an EIP155 transaction.
Should we use the updated rawTx instead of the original rawTxHex?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea for signTransactionLargeChainID2
But I'd say yes, you should use the updated rawTx here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rawTx is actually not updated so its ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signTransactionLargeChainID2 fixed


const decodedTx = {
data: rlpTx[5],
to: rlpTx[3],
};

if (decodedTx.data.length >= 10) {
const erc20Info = byContractAddress(decodedTx.to);
if (erc20Info) {
_provideERC20TokenInformation.call(this, erc20Info);
}

const selector = decodedTx.data.substring(0, 10);
const infos = getInfosForContractMethod(decodedTx.to, selector);

if (infos) {
let { plugin, payload, signature, erc20OfInterest, abi } = infos;

if (erc20OfInterest?.length && abi) {
const contract = new ethers.utils.Interface(abi);
const args = contract.parseTransaction(decodedTx).args;

erc20OfInterest.forEach((path) => {
const address = path.split(".").reduce((value, seg) => {
if (seg === "-1" && Array.isArray(value)) {
return value[value.length - 1];
}
return value[seg];
}, args);

const erc20Info = byContractAddress(address);
if (erc20Info) {
_provideERC20TokenInformation.call(this, erc20Info);
}
});
}

if (plugin) {
_setExternalPlugin.call(this, payload, signature);
}
}
}

return foreach(toSend, (data, i) =>
this.transport
.send(0xe0, 0x04, i === 0 ? 0x00 : 0x80, 0x00, data)
Expand Down Expand Up @@ -1002,4 +1047,63 @@ eth.signPersonalMessage("44'/60'/0'/0/0", Buffer.from("test").toString("hex")).t
}
);
}

/**
* Set the name of the plugin that should be used to parse the next transaction
*
* @param pluginName string containing the name of the plugin, must have length between 1 and 30 bytes
* @return True if the method was executed successfully
*/
setExternalPlugin(
pluginName: string,
contractAddress: string,
selector: string
): Promise<boolean> {
return _setExternalPlugin.call(this, pluginName, contractAddress, selector);
}
}

// PRIVATE

function _provideERC20TokenInformation({
data,
}: {
data: Buffer,
}): Promise<boolean> {
return this.transport.send(0xe0, 0x0a, 0x00, 0x00, data).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6d00) {
// this case happen for older version of ETH app, since older app version had the ERC20 data hardcoded, it's fine to assume it worked.
// we return a flag to know if the call was effective or not
return false;
}
throw e;
}
);
}

function _setExternalPlugin(
payload: string,
signature: string
): Promise<boolean> {
let payloadBuffer = Buffer.from(payload, "hex");
let signatureBuffer = Buffer.from(signature, "hex");
let buffer = Buffer.concat([payloadBuffer, signatureBuffer]);
return this.transport.send(0xe0, 0x12, 0x00, 0x00, buffer).then(
() => true,
(e) => {
if (e && e.statusCode === 0x6a80) {
// this case happen when the plugin name is too short or too long
return false;
} else if (e && e.statusCode === 0x6984) {
// this case happen when the plugin requested is not installed on the device
return false;
} else if (e && e.statusCode === 0x6d00) {
// this case happen for older version of ETH app
return false;
}
throw e;
}
);
}
26 changes: 26 additions & 0 deletions packages/hw-app-eth/src/contracts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import data from "@ledgerhq/cryptoassets/data/dapps/ethereum";

/**
* Retrieve the metadatas a given contract address and a method selector
*/

export const getInfosForContractMethod = (contractAddress, selector) => {
const lcSelector = selector.toLowerCase();
const lcContractAddress = contractAddress.toLowerCase();

if (lcContractAddress in data) {
const contractSelectors = data[lcContractAddress];

if (lcSelector in contractSelectors) {
return {
payload: contractSelectors[lcSelector]["serialized_data"],
signature: contractSelectors[lcSelector]["signature"],
plugin: contractSelectors[lcSelector]["plugin"],
erc20OfInterest: contractSelectors[lcSelector]["erc20OfInterest"],
abi: contractSelectors["abi"],
};
}
}
};

exports.getInfosForContractMethod = getInfosForContractMethod;
Loading