mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-02 11:58:32 +00:00
feat(common, core, python, tests): Eth definitions in zip file
This commit is contained in:
parent
aa835a692d
commit
865765db91
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,4 +12,5 @@ proto.gv*
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
crypto/tests/libtrezor-crypto.so.dSYM/
|
crypto/tests/libtrezor-crypto.so.dSYM/
|
||||||
/definitions-latest
|
/definitions-latest
|
||||||
|
/definitions-latest.zip
|
||||||
definitions-cache.json
|
definitions-cache.json
|
||||||
|
BIN
common/tests/fixtures/ethereum/definitions-latest.zip
vendored
Normal file
BIN
common/tests/fixtures/ethereum/definitions-latest.zip
vendored
Normal file
Binary file not shown.
@ -90,30 +90,6 @@
|
|||||||
"sig_s": "2200f34790df5a71f7349eb941f11c294e480dadcf15e323851ae43845ee9a80"
|
"sig_s": "2200f34790df5a71f7349eb941f11c294e480dadcf15e323851ae43845ee9a80"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_builtin_Tether_wrong_network_token",
|
|
||||||
"parameters": {
|
|
||||||
"chain_id": 1,
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_price": "0x14",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"value": "0x0",
|
|
||||||
"to_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
|
||||||
"tx_type": null,
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"definitions": {
|
|
||||||
"chain_id": 8,
|
|
||||||
"token_chain_id": 8,
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 38,
|
|
||||||
"sig_r": "a82c902c3c4d82e9a3d3894bf1298b14217c0d6149faaff26e254b7753063158",
|
|
||||||
"sig_s": "2200f34790df5a71f7349eb941f11c294e480dadcf15e323851ae43845ee9a80"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "builtin_Ethereum_builtin_Tether_wrong_network",
|
"name": "builtin_Ethereum_builtin_Tether_wrong_network",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -136,29 +112,6 @@
|
|||||||
"sig_s": "2200f34790df5a71f7349eb941f11c294e480dadcf15e323851ae43845ee9a80"
|
"sig_s": "2200f34790df5a71f7349eb941f11c294e480dadcf15e323851ae43845ee9a80"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_builtin_Tether_wrong_token",
|
|
||||||
"parameters": {
|
|
||||||
"chain_id": 1,
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_price": "0x14",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"value": "0x0",
|
|
||||||
"to_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
|
||||||
"tx_type": null,
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"definitions": {
|
|
||||||
"token_chain_id": 8,
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 38,
|
|
||||||
"sig_r": "a82c902c3c4d82e9a3d3894bf1298b14217c0d6149faaff26e254b7753063158",
|
|
||||||
"sig_s": "2200f34790df5a71f7349eb941f11c294e480dadcf15e323851ae43845ee9a80"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "builtin_Ethereum_extern_adChain_send_network_token",
|
"name": "builtin_Ethereum_extern_adChain_send_network_token",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -245,29 +198,6 @@
|
|||||||
"sig_s": "2c08ebde47552e7d407767710c4fd48d8c66e71f28004d87170a430ecf7d56a6"
|
"sig_s": "2c08ebde47552e7d407767710c4fd48d8c66e71f28004d87170a430ecf7d56a6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_extern_adChain_wrong_token",
|
|
||||||
"parameters": {
|
|
||||||
"chain_id": 1,
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_price": "0x14",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"value": "0x0",
|
|
||||||
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
|
||||||
"tx_type": null,
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"definitions": {
|
|
||||||
"token_chain_id": 8,
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 38,
|
|
||||||
"sig_r": "78e9bab4abda13d3cdc2e6dee69e96a7215ff4254b2b9a2321f970941925250c",
|
|
||||||
"sig_s": "2c08ebde47552e7d407767710c4fd48d8c66e71f28004d87170a430ecf7d56a6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "extern_Ubiq_extern_Sphere_send_network_token",
|
"name": "extern_Ubiq_extern_Sphere_send_network_token",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -308,29 +238,6 @@
|
|||||||
"sig_r": "1db04aa641665618467a2e40f840da081601c266050f951c8bb8688efd43a7d9",
|
"sig_r": "1db04aa641665618467a2e40f840da081601c266050f951c8bb8688efd43a7d9",
|
||||||
"sig_s": "5f4bb9b86814cb8647b7aad6e23c73ffbfb284109068d5d1061583bff252ec82"
|
"sig_s": "5f4bb9b86814cb8647b7aad6e23c73ffbfb284109068d5d1061583bff252ec82"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extern_Ubiq_extern_Sphere_wrong_token",
|
|
||||||
"parameters": {
|
|
||||||
"chain_id": 8,
|
|
||||||
"path": "m/44'/108'/0'/0/0",
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_price": "0x14",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"value": "0x0",
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6",
|
|
||||||
"tx_type": null,
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"definitions": {
|
|
||||||
"token_chain_id": 1,
|
|
||||||
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 52,
|
|
||||||
"sig_r": "1db04aa641665618467a2e40f840da081601c266050f951c8bb8688efd43a7d9",
|
|
||||||
"sig_s": "5f4bb9b86814cb8647b7aad6e23c73ffbfb284109068d5d1061583bff252ec82"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -90,75 +90,6 @@
|
|||||||
"sig_s": "47f36b4c02bad110bf7eb4fd71d6cd12bca7443bad3f31700a404302dbbb6e1c"
|
"sig_s": "47f36b4c02bad110bf7eb4fd71d6cd12bca7443bad3f31700a404302dbbb6e1c"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_builtin_Tether_wrong_network_token",
|
|
||||||
"parameters": {
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"to_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
|
||||||
"chain_id": 1,
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"max_gas_fee": "0x14",
|
|
||||||
"max_priority_fee": "0x1",
|
|
||||||
"value": "0x0",
|
|
||||||
"definitions": {
|
|
||||||
"chain_id": 8,
|
|
||||||
"token_chain_id": 8,
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 1,
|
|
||||||
"sig_r": "8b5002dc26252120e89c5194a72fb96663930c3bb709487bec72132a0c32033f",
|
|
||||||
"sig_s": "47f36b4c02bad110bf7eb4fd71d6cd12bca7443bad3f31700a404302dbbb6e1c"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_builtin_Tether_wrong_network",
|
|
||||||
"parameters": {
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"to_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
|
||||||
"chain_id": 1,
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"max_gas_fee": "0x14",
|
|
||||||
"max_priority_fee": "0x1",
|
|
||||||
"value": "0x0",
|
|
||||||
"definitions": {
|
|
||||||
"chain_id": 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 1,
|
|
||||||
"sig_r": "8b5002dc26252120e89c5194a72fb96663930c3bb709487bec72132a0c32033f",
|
|
||||||
"sig_s": "47f36b4c02bad110bf7eb4fd71d6cd12bca7443bad3f31700a404302dbbb6e1c"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_builtin_Tether_wrong_token",
|
|
||||||
"parameters": {
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"to_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
|
||||||
"chain_id": 1,
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"max_gas_fee": "0x14",
|
|
||||||
"max_priority_fee": "0x1",
|
|
||||||
"value": "0x0",
|
|
||||||
"definitions": {
|
|
||||||
"token_chain_id": 8,
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 1,
|
|
||||||
"sig_r": "8b5002dc26252120e89c5194a72fb96663930c3bb709487bec72132a0c32033f",
|
|
||||||
"sig_s": "47f36b4c02bad110bf7eb4fd71d6cd12bca7443bad3f31700a404302dbbb6e1c"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "builtin_Ethereum_extern_adChain_send_network_token",
|
"name": "builtin_Ethereum_extern_adChain_send_network_token",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -245,29 +176,6 @@
|
|||||||
"sig_s": "60758c000ee9d276972191c200ef92d395cecf0a9586d65449c73057ae561883"
|
"sig_s": "60758c000ee9d276972191c200ef92d395cecf0a9586d65449c73057ae561883"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "builtin_Ethereum_extern_adChain_wrong_token",
|
|
||||||
"parameters": {
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"path": "m/44'/60'/0'/0/0",
|
|
||||||
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
|
||||||
"chain_id": 1,
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"max_gas_fee": "0x14",
|
|
||||||
"max_priority_fee": "0x1",
|
|
||||||
"value": "0x0",
|
|
||||||
"definitions": {
|
|
||||||
"token_chain_id": 8,
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 0,
|
|
||||||
"sig_r": "a29ac8d36f0b49b12fcde74878593a75b4990062e8c722da602d4a48733382a2",
|
|
||||||
"sig_s": "60758c000ee9d276972191c200ef92d395cecf0a9586d65449c73057ae561883"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "extern_Ubiq_extern_Sphere_send_network_token",
|
"name": "extern_Ubiq_extern_Sphere_send_network_token",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -308,29 +216,6 @@
|
|||||||
"sig_r": "1607bb4422b8cd139228f0d4f48521f6b249f2daeb7a254f9e9e998fd8dd6b68",
|
"sig_r": "1607bb4422b8cd139228f0d4f48521f6b249f2daeb7a254f9e9e998fd8dd6b68",
|
||||||
"sig_s": "4a17515b78d7d70fc8df8b17f602f0cd57a0afe2417cc893cd3cda706ab018fa"
|
"sig_s": "4a17515b78d7d70fc8df8b17f602f0cd57a0afe2417cc893cd3cda706ab018fa"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extern_Ubiq_extern_Sphere_wrong_token",
|
|
||||||
"parameters": {
|
|
||||||
"data": "a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"path": "m/44'/108'/0'/0/0",
|
|
||||||
"to_address": "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6",
|
|
||||||
"chain_id": 8,
|
|
||||||
"nonce": "0x0",
|
|
||||||
"gas_limit": "0x14",
|
|
||||||
"max_gas_fee": "0x14",
|
|
||||||
"max_priority_fee": "0x1",
|
|
||||||
"value": "0x0",
|
|
||||||
"definitions": {
|
|
||||||
"token_chain_id": 1,
|
|
||||||
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"sig_v": 1,
|
|
||||||
"sig_r": "1607bb4422b8cd139228f0d4f48521f6b249f2daeb7a254f9e9e998fd8dd6b68",
|
|
||||||
"sig_s": "4a17515b78d7d70fc8df8b17f602f0cd57a0afe2417cc893cd3cda706ab018fa"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
|
import zipfile
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, TextIO, cast
|
from typing import Any, TextIO, cast
|
||||||
@ -23,8 +23,8 @@ from requests.adapters import HTTPAdapter
|
|||||||
from urllib3.util.retry import Retry
|
from urllib3.util.retry import Retry
|
||||||
|
|
||||||
from coin_info import Coin, Coins, load_json
|
from coin_info import Coin, Coins, load_json
|
||||||
from merkle_tree import MerkleTree
|
|
||||||
from trezorlib import protobuf
|
from trezorlib import protobuf
|
||||||
|
from trezorlib.merkle_tree import MerkleTree
|
||||||
from trezorlib.messages import (
|
from trezorlib.messages import (
|
||||||
EthereumDefinitionType,
|
EthereumDefinitionType,
|
||||||
EthereumNetworkInfo,
|
EthereumNetworkInfo,
|
||||||
@ -459,12 +459,11 @@ def check_tokens_collisions(tokens: list[dict], old_tokens: list[dict] | None) -
|
|||||||
|
|
||||||
|
|
||||||
def check_bytes_size(
|
def check_bytes_size(
|
||||||
value: bytes, max_size: int, label: str, prompt: bool = True
|
actual_size: int, max_size: int, label: str, prompt: bool = True
|
||||||
) -> tuple[bool, bool]:
|
) -> tuple[bool, bool]:
|
||||||
"""Check value (of type bytes) size and return tuple - size check and user response"""
|
"""Check the actual size and return tuple - size check result and user response"""
|
||||||
encoded_size = len(value)
|
if actual_size > max_size:
|
||||||
if encoded_size > max_size:
|
title = f"Bytes in {label} definition is too long ({actual_size} > {max_size})"
|
||||||
title = f"Bytes in {label} definition is too long ({encoded_size} > {max_size})"
|
|
||||||
title += " and will be removed from the results" if not prompt else ""
|
title += " and will be removed from the results" if not prompt else ""
|
||||||
print(f"== {title} ==")
|
print(f"== {title} ==")
|
||||||
|
|
||||||
@ -1093,10 +1092,12 @@ def prepare_definitions(
|
|||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-o",
|
"-o",
|
||||||
"--outdir",
|
"--outfile",
|
||||||
type=click.Path(resolve_path=True, file_okay=False, path_type=pathlib.Path),
|
type=click.Path(
|
||||||
default="./definitions-latest",
|
resolve_path=True, dir_okay=False, writable=True, path_type=pathlib.Path
|
||||||
help="Path where the generated definitions will be saved. Path will be erased!",
|
),
|
||||||
|
default="./definitions-latest.zip",
|
||||||
|
help="File where the generated definitions will be saved in zip format. Any existing file will be overwritten!",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-k",
|
"-k",
|
||||||
@ -1107,15 +1108,22 @@ def prepare_definitions(
|
|||||||
@click.option(
|
@click.option(
|
||||||
"-s",
|
"-s",
|
||||||
"--signedroot",
|
"--signedroot",
|
||||||
help="Signed Merkle tree root hash to be added",
|
help="Signed Merkle tree root hash to be added.",
|
||||||
|
)
|
||||||
|
@click.option("-v", "--verbose", is_flag=True, help="Display more info.")
|
||||||
|
@click.option(
|
||||||
|
"-p",
|
||||||
|
"--include-proof",
|
||||||
|
is_flag=True,
|
||||||
|
help="Include Merkle tree proofs into binary blobs.",
|
||||||
)
|
)
|
||||||
@click.option("-v", "--verbose", is_flag=True, help="Display more info")
|
|
||||||
def sign_definitions(
|
def sign_definitions(
|
||||||
deffile: pathlib.Path,
|
deffile: pathlib.Path,
|
||||||
outdir: pathlib.Path,
|
outfile: pathlib.Path,
|
||||||
publickey: TextIO,
|
publickey: TextIO,
|
||||||
signedroot: str,
|
signedroot: str,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
include_proof: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Generate signed Ethereum definitions for python-trezor and others.
|
"""Generate signed Ethereum definitions for python-trezor and others.
|
||||||
If ran without `--publickey` and/or `--signedroot` it prints the computed Merkle tree root hash.
|
If ran without `--publickey` and/or `--signedroot` it prints the computed Merkle tree root hash.
|
||||||
@ -1128,49 +1136,10 @@ def sign_definitions(
|
|||||||
"Options `--publickey` and `--signedroot` must be used together."
|
"Options `--publickey` and `--signedroot` must be used together."
|
||||||
)
|
)
|
||||||
|
|
||||||
def save_definition(directory: pathlib.Path, keys: list[str], data: bytes):
|
|
||||||
complete_file_path = directory / ("_".join(keys) + ".dat")
|
|
||||||
|
|
||||||
if complete_file_path.exists():
|
|
||||||
raise click.ClickException(
|
|
||||||
f'Definition "{complete_file_path}" already generated - attempt to generate another definition.'
|
|
||||||
)
|
|
||||||
|
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(complete_file_path, mode="wb+") as f:
|
|
||||||
f.write(data)
|
|
||||||
|
|
||||||
def generate_token_def(token: Coin):
|
|
||||||
if token["address"] is not None and token["chain_id"] is not None:
|
|
||||||
# save token definition
|
|
||||||
save_definition(
|
|
||||||
outdir / "by_chain_id" / str(token["chain_id"]),
|
|
||||||
["token", token["address"][2:].lower()],
|
|
||||||
token["serialized"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_network_def(network: Coin):
|
|
||||||
if network["chain_id"] is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# create path for networks identified by chain and slip44 ids
|
|
||||||
network_dir = outdir / "by_chain_id" / str(network["chain_id"])
|
|
||||||
slip44_dir = outdir / "by_slip44" / str(network["slip44"])
|
|
||||||
# save network definition
|
|
||||||
save_definition(network_dir, ["network"], network["serialized"])
|
|
||||||
|
|
||||||
try:
|
|
||||||
# for slip44 == 60 save only Ethereum
|
|
||||||
if network["slip44"] != 60 or network["chain_id"] == 1:
|
|
||||||
save_definition(slip44_dir, ["network"], network["serialized"])
|
|
||||||
except click.ClickException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# load prepared definitions
|
# load prepared definitions
|
||||||
timestamp, networks, tokens = _load_prepared_definitions(deffile)
|
timestamp, networks, tokens = _load_prepared_definitions(deffile)
|
||||||
|
|
||||||
# serialize definitions
|
# serialize definitions
|
||||||
definitions_by_serialization: dict[bytes, dict] = {}
|
|
||||||
for network in networks:
|
for network in networks:
|
||||||
ser = serialize_eth_info(
|
ser = serialize_eth_info(
|
||||||
eth_info_from_dict(network, EthereumNetworkInfo),
|
eth_info_from_dict(network, EthereumNetworkInfo),
|
||||||
@ -1178,7 +1147,6 @@ def sign_definitions(
|
|||||||
timestamp,
|
timestamp,
|
||||||
)
|
)
|
||||||
network["serialized"] = ser
|
network["serialized"] = ser
|
||||||
definitions_by_serialization[ser] = network
|
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
ser = serialize_eth_info(
|
ser = serialize_eth_info(
|
||||||
eth_info_from_dict(token, EthereumTokenInfo),
|
eth_info_from_dict(token, EthereumTokenInfo),
|
||||||
@ -1186,13 +1154,15 @@ def sign_definitions(
|
|||||||
timestamp,
|
timestamp,
|
||||||
)
|
)
|
||||||
token["serialized"] = ser
|
token["serialized"] = ser
|
||||||
definitions_by_serialization[ser] = token
|
|
||||||
|
# sort encoded definitions
|
||||||
|
sorted_defs = [network["serialized"] for network in networks] + [
|
||||||
|
token["serialized"] for token in tokens
|
||||||
|
]
|
||||||
|
sorted_defs.sort()
|
||||||
|
|
||||||
# build Merkle tree
|
# build Merkle tree
|
||||||
mt = MerkleTree(
|
mt = MerkleTree(sorted_defs)
|
||||||
[network["serialized"] for network in networks]
|
|
||||||
+ [token["serialized"] for token in tokens]
|
|
||||||
)
|
|
||||||
|
|
||||||
# print or check tree root hash
|
# print or check tree root hash
|
||||||
if publickey is None:
|
if publickey is None:
|
||||||
@ -1209,43 +1179,104 @@ def sign_definitions(
|
|||||||
f"Provided `--signedroot` value is not valid for computed Merkle tree root hash ({hexlify(mt.get_root_hash())})."
|
f"Provided `--signedroot` value is not valid for computed Merkle tree root hash ({hexlify(mt.get_root_hash())})."
|
||||||
)
|
)
|
||||||
|
|
||||||
# update definitions
|
def save_definition(
|
||||||
signedroot_bytes = bytes.fromhex(signedroot)
|
path: pathlib.PurePath, keys: list[str], data: bytes, zip_file: zipfile.ZipFile
|
||||||
for ser, proof in mt.get_proofs().items():
|
):
|
||||||
definition = definitions_by_serialization[ser]
|
complete_path = path / ("_".join(keys) + ".dat")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if zip_file.getinfo(str(complete_path)):
|
||||||
|
logging.warning(
|
||||||
|
f'Definition "{complete_path}" already generated - attempt to generate another definition.'
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
zip_file.writestr(str(complete_path), data)
|
||||||
|
|
||||||
|
def generate_token_def(
|
||||||
|
token: Coin, base_path: pathlib.PurePath, zip_file: zipfile.ZipFile
|
||||||
|
):
|
||||||
|
if token["address"] is not None and token["chain_id"] is not None:
|
||||||
|
# save token definition
|
||||||
|
save_definition(
|
||||||
|
base_path / "by_chain_id" / str(token["chain_id"]),
|
||||||
|
["token", token["address"][2:].lower()],
|
||||||
|
token["serialized"],
|
||||||
|
zip_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_network_def(
|
||||||
|
network: Coin, base_path: pathlib.PurePath, zip_file: zipfile.ZipFile
|
||||||
|
):
|
||||||
|
if network["chain_id"] is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# create path for networks identified by chain and slip44 ids
|
||||||
|
network_dir = base_path / "by_chain_id" / str(network["chain_id"])
|
||||||
|
slip44_dir = base_path / "by_slip44" / str(network["slip44"])
|
||||||
|
# save network definition
|
||||||
|
save_definition(network_dir, ["network"], network["serialized"], zip_file)
|
||||||
|
|
||||||
|
# for slip44 == 60 save only Ethereum and for slip44 == 1 save only Goerli
|
||||||
|
if network["slip44"] not in (60, 1) or network["chain_id"] in (1, 420):
|
||||||
|
save_definition(slip44_dir, ["network"], network["serialized"], zip_file)
|
||||||
|
|
||||||
|
def add_proof_to_def(definition: dict) -> None:
|
||||||
|
proof = proofs_dict[definition["serialized"]]
|
||||||
# append number of hashes in proof
|
# append number of hashes in proof
|
||||||
definition["serialized"] += len(proof).to_bytes(1, "big")
|
definition["serialized"] += len(proof).to_bytes(1, "big")
|
||||||
# append proof itself
|
# append proof itself
|
||||||
for p in proof:
|
for p in proof:
|
||||||
definition["serialized"] += p
|
definition["serialized"] += p
|
||||||
# append signed tree root hash
|
|
||||||
definition["serialized"] += signedroot_bytes
|
|
||||||
|
|
||||||
# clear defs directory
|
# add proofs (if requested) and signed tree root hash, check serialized size of the definitions and add it to a zip
|
||||||
if outdir.exists():
|
signed_root_bytes = bytes.fromhex(signedroot)
|
||||||
shutil.rmtree(outdir)
|
|
||||||
outdir.mkdir(parents=True)
|
|
||||||
|
|
||||||
# check serialized size of the definitions and generate a file for it
|
# update definitions
|
||||||
for network in networks:
|
proofs_dict = mt.get_proofs()
|
||||||
check, _ = check_bytes_size(
|
|
||||||
network["serialized"],
|
|
||||||
1024,
|
|
||||||
f"network {network['name']} (chain_id={network['chain_id']})",
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
if check:
|
|
||||||
generate_network_def(network)
|
|
||||||
|
|
||||||
for token in tokens:
|
base_path = pathlib.PurePath("definitions-latest")
|
||||||
check, _ = check_bytes_size(
|
with zipfile.ZipFile(outfile, mode="w") as zip_file:
|
||||||
token["serialized"],
|
for network in networks:
|
||||||
1024,
|
if include_proof:
|
||||||
f"token {token['name']} (chain_id={token['chain_id']}, address={token['address']})",
|
add_proof_to_def(network)
|
||||||
False,
|
# append signed tree root hash
|
||||||
)
|
network["serialized"] += signed_root_bytes
|
||||||
if check:
|
|
||||||
generate_token_def(token)
|
network_serialized_length = len(network["serialized"])
|
||||||
|
if not include_proof:
|
||||||
|
# consider size of the proofs that will be added later by user before sending to the device
|
||||||
|
network_serialized_length += mt.get_tree_height() * 32
|
||||||
|
|
||||||
|
check, _ = check_bytes_size(
|
||||||
|
network_serialized_length,
|
||||||
|
1024,
|
||||||
|
f"network {network['name']} (chain_id={network['chain_id']})",
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
if check:
|
||||||
|
generate_network_def(network, base_path, zip_file)
|
||||||
|
|
||||||
|
for token in tokens:
|
||||||
|
if include_proof:
|
||||||
|
add_proof_to_def(token)
|
||||||
|
# append signed tree root hash
|
||||||
|
token["serialized"] += signed_root_bytes
|
||||||
|
|
||||||
|
token_serialized_length = len(token["serialized"])
|
||||||
|
if not include_proof:
|
||||||
|
# consider size of the proofs that will be added later by user before sending to the device
|
||||||
|
token_serialized_length += mt.get_tree_height() * 32
|
||||||
|
|
||||||
|
check, _ = check_bytes_size(
|
||||||
|
token_serialized_length,
|
||||||
|
1024,
|
||||||
|
f"token {token['name']} (chain_id={token['chain_id']}, address={token['address']})",
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
if check:
|
||||||
|
generate_token_def(token, base_path, zip_file)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
try:
|
|
||||||
from trezor.crypto.hashlib import sha256
|
|
||||||
except ImportError:
|
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
|
||||||
"""
|
|
||||||
Single node of Merkle tree.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self: "Node",
|
|
||||||
*,
|
|
||||||
left: Optional["Node"] = None,
|
|
||||||
right: Optional["Node"] = None,
|
|
||||||
raw_value: Optional[bytes] = None,
|
|
||||||
) -> None:
|
|
||||||
self.is_leaf = raw_value is not None
|
|
||||||
self.raw_value = raw_value
|
|
||||||
|
|
||||||
if self.is_leaf and (left is not None or right is not None):
|
|
||||||
raise ValueError(
|
|
||||||
"Cannot use `raw_value` together with `left` and/or `right` value."
|
|
||||||
)
|
|
||||||
|
|
||||||
self.hash = None
|
|
||||||
self.left_child = left
|
|
||||||
self.right_child = right
|
|
||||||
self.proof_list: list[bytes] = []
|
|
||||||
|
|
||||||
def compute_hash(self) -> bytes:
|
|
||||||
if not self.hash:
|
|
||||||
if self.is_leaf:
|
|
||||||
self.hash = sha256(b"\x00" + self.raw_value).digest()
|
|
||||||
else:
|
|
||||||
left_hash = self.left_child.compute_hash()
|
|
||||||
right_hash = self.right_child.compute_hash()
|
|
||||||
hash_a = min(left_hash, right_hash)
|
|
||||||
hash_b = max(left_hash, right_hash)
|
|
||||||
self.hash = sha256(b"\x01" + hash_a + hash_b).digest()
|
|
||||||
|
|
||||||
# distribute proof
|
|
||||||
self.left_child.add_to_proof(right_hash)
|
|
||||||
self.right_child.add_to_proof(left_hash)
|
|
||||||
|
|
||||||
return self.hash
|
|
||||||
|
|
||||||
def add_to_proof(self, proof: bytes) -> None:
|
|
||||||
self.proof_list.append(proof)
|
|
||||||
if not self.is_leaf:
|
|
||||||
self.left_child.add_to_proof(proof)
|
|
||||||
self.right_child.add_to_proof(proof)
|
|
||||||
|
|
||||||
|
|
||||||
class MerkleTree:
|
|
||||||
"""
|
|
||||||
Simple Merkle tree that implements the building of Merkle tree itself and generate proofs
|
|
||||||
for leaf nodes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, values: list[bytes]) -> None:
|
|
||||||
self.leaves = [Node(raw_value=v) for v in values]
|
|
||||||
|
|
||||||
# build the tree
|
|
||||||
current_level = [n for n in self.leaves]
|
|
||||||
while len(current_level) > 1:
|
|
||||||
# build one level of the tree
|
|
||||||
next_level = []
|
|
||||||
while len(current_level) // 2:
|
|
||||||
left_node = current_level.pop(0)
|
|
||||||
right_node = current_level.pop(0)
|
|
||||||
next_level.append(Node(left=left_node, right=right_node))
|
|
||||||
|
|
||||||
if len(current_level) == 1:
|
|
||||||
# odd number of nodes on current level so last node will be "joined" on another level
|
|
||||||
next_level.append(current_level.pop(0))
|
|
||||||
|
|
||||||
# switch levels and continue
|
|
||||||
current_level = next_level
|
|
||||||
|
|
||||||
# set root and compute hash
|
|
||||||
self.root_node = current_level[0]
|
|
||||||
self.root_node.compute_hash()
|
|
||||||
|
|
||||||
def get_proofs(self) -> dict[bytes, list[bytes]]:
|
|
||||||
return {n.raw_value: n.proof_list for n in self.leaves}
|
|
||||||
|
|
||||||
def get_root_hash(self) -> bytes:
|
|
||||||
return self.root_node.hash
|
|
@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|||||||
DefType = TypeVar("DefType", EthereumNetworkInfo, EthereumTokenInfo)
|
DefType = TypeVar("DefType", EthereumNetworkInfo, EthereumTokenInfo)
|
||||||
|
|
||||||
DEFINITIONS_PUBLIC_KEY = b""
|
DEFINITIONS_PUBLIC_KEY = b""
|
||||||
MIN_DATA_VERSION = 1
|
MIN_DATA_VERSION = 1 # TODO: update
|
||||||
FORMAT_VERSION = b"trzd1"
|
FORMAT_VERSION = b"trzd1"
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
@ -99,7 +99,7 @@ def get_reference_ethereum_network_info(chain_id: int | None = None, slip44: int
|
|||||||
if cid is not None:
|
if cid is not None:
|
||||||
return NETWORKS.get(cid[0])
|
return NETWORKS.get(cid[0])
|
||||||
|
|
||||||
return network if network else etworks.UNKNOWN_NETWORK
|
return network if network else networks.UNKNOWN_NETWORK
|
||||||
|
|
||||||
|
|
||||||
def get_reference_ethereum_token_info(chain_id: int, token_address: str) -> messages.EthereumTokenInfo:
|
def get_reference_ethereum_token_info(chain_id: int, token_address: str) -> messages.EthereumTokenInfo:
|
||||||
|
@ -11,3 +11,7 @@ Definitions are binary encoded and have a special format:
|
|||||||
1. length of the Merkle tree proof - number of hashes in the proof (unsigned integer, 1 byte)
|
1. length of the Merkle tree proof - number of hashes in the proof (unsigned integer, 1 byte)
|
||||||
2. proof - individual hashes used to compute Merkle tree root hash (plain bits, N*32 bytes)
|
2. proof - individual hashes used to compute Merkle tree root hash (plain bits, N*32 bytes)
|
||||||
3. signed Merkle tree root hash (plain bits, 64 bytes)
|
3. signed Merkle tree root hash (plain bits, 64 bytes)
|
||||||
|
|
||||||
|
This complete format must be used when definition is send to the device, but we generate definitions
|
||||||
|
without parts 3.1 (Merkle tree proof length) and 3.2 (proof itself). For more information look
|
||||||
|
at the [External definitions documentation](../ethereum-definitions.md#external-definitions)
|
||||||
|
@ -12,17 +12,28 @@ Location of these definitions is for:
|
|||||||
* networks - [`networks.json`](https://github.com/trezor/trezor-firmware/blob/master/common/defs/ethereum/networks.json)
|
* networks - [`networks.json`](https://github.com/trezor/trezor-firmware/blob/master/common/defs/ethereum/networks.json)
|
||||||
* tokens - [`tokens.json`](https://github.com/trezor/trezor-firmware/blob/master/common/defs/ethereum/tokens.json)
|
* tokens - [`tokens.json`](https://github.com/trezor/trezor-firmware/blob/master/common/defs/ethereum/tokens.json)
|
||||||
|
|
||||||
Built-in definitions are written by hand and are not subject to any generation described
|
Sources for built-in definitions (namely files `../../common/defs/ethereum/networks.json`,
|
||||||
below.
|
`../../common/defs/ethereum/tokens.json`) are written by hand and are not subject
|
||||||
|
to any generation described below.
|
||||||
|
|
||||||
## External definitions
|
## External definitions
|
||||||
|
|
||||||
Generated binary blobs are first saved locally (by the script that generates them)
|
Generated binary blobs are first saved locally (by the script that generates them)
|
||||||
and then published online. By "external" is meant definitions not hardcoded in firmware.
|
and then published online. By "external" is meant all the definitions for which the binary
|
||||||
|
blobs are generated - so basically all definitions, because blobs are generated
|
||||||
|
also for built-in definitions.
|
||||||
|
|
||||||
#### File structure
|
Generated blobs are saved as a zip file. To save some space, zipped definitions
|
||||||
Saved definitions (binary blobs) are stored in directory (e.g. `definitions-latest/`)
|
does not contain the Merkle tree "proof part" (proof length and the proof itself -
|
||||||
with specific file structure:
|
see [definition binary format](communication/ethereum-definitions-binary-format.md)
|
||||||
|
for more details). Merkle tree proof is the most repetitive part of the definitions
|
||||||
|
and it can be easily computed when needed. Using this script
|
||||||
|
[`python/tools/eth_defs_unpack.py`](https://github.com/trezor/trezor-firmware/blob/master/python/tools/eth_defs_unpack.py)
|
||||||
|
the zip with definitions will be unpacked and all the definitions will be completed.
|
||||||
|
|
||||||
|
#### Zip/directory file structure
|
||||||
|
Saved definitions (binary blobs) are stored in zip file with specific file
|
||||||
|
structure (same as the structure of completed unpacked definitions):
|
||||||
````
|
````
|
||||||
definitions-latest/
|
definitions-latest/
|
||||||
├── by_chain_id/
|
├── by_chain_id/
|
||||||
@ -57,7 +68,7 @@ Notice that token definitions are only accessible by `CHAIN_ID` and `TOKEN_ADDRE
|
|||||||
Generated binary definitions are available online at [publicly accessible website](https://data.trezor.io/eth_definitions) # TODO: update url.
|
Generated binary definitions are available online at [publicly accessible website](https://data.trezor.io/eth_definitions) # TODO: update url.
|
||||||
|
|
||||||
To get the desired definition (one at a time), URL can be composed in multiple ways
|
To get the desired definition (one at a time), URL can be composed in multiple ways
|
||||||
and the structure is the same as it is described in the [file structure](#file-structure)
|
and the structure is the same as it is described in the [Zip/directory file structure](#zipdirectory-file-structure)
|
||||||
section. Base URL format is `https://data.trezor.io/eth_definitions/LOOKUP_TYPE/ID/NAME`
|
section. Base URL format is `https://data.trezor.io/eth_definitions/LOOKUP_TYPE/ID/NAME`
|
||||||
where:
|
where:
|
||||||
* `LOOKUP_TYPE` is one of `by_chain_id` or `by_slip44`
|
* `LOOKUP_TYPE` is one of `by_chain_id` or `by_slip44`
|
||||||
@ -65,7 +76,7 @@ where:
|
|||||||
* `NAME` is either:
|
* `NAME` is either:
|
||||||
* `network.dat` for network definition at given chain ID or SLIP44 ID or
|
* `network.dat` for network definition at given chain ID or SLIP44 ID or
|
||||||
* `token_"TOKEN_ADDRESS".dat` for token definition at given chain ID and token address
|
* `token_"TOKEN_ADDRESS".dat` for token definition at given chain ID and token address
|
||||||
(see [file structure](#file-structure) section on how to format the token address)
|
(see [Zip/directory file structure](#zipdirectory-file-structure) section on how to format the token address)
|
||||||
|
|
||||||
Definitions could be also downloaded by one request in a ZIP file at https://data.trezor.io/eth_definitions/definitions.zip # TODO: update url.
|
Definitions could be also downloaded by one request in a ZIP file at https://data.trezor.io/eth_definitions/definitions.zip # TODO: update url.
|
||||||
|
|
||||||
@ -73,7 +84,7 @@ Definitions could be also downloaded by one request in a ZIP file at https://dat
|
|||||||
|
|
||||||
Automatic manipulation with the definitions is implemented in `trezorctl` tool.
|
Automatic manipulation with the definitions is implemented in `trezorctl` tool.
|
||||||
All Ethereum commands that do work with definitions contains contains options
|
All Ethereum commands that do work with definitions contains contains options
|
||||||
to automatically download online definitions or use locally stored definitions.
|
to automatically download online definition(s) or use locally stored definition(s).
|
||||||
For more info look at the `trezorctl` [documentation]
|
For more info look at the `trezorctl` [documentation]
|
||||||
(https://github.com/trezor/trezor-firmware/blob/master/python/docs/README.rst).
|
(https://github.com/trezor/trezor-firmware/blob/master/python/docs/README.rst).
|
||||||
|
|
||||||
@ -125,8 +136,9 @@ and the root hash is computed.
|
|||||||
and the root hash is computed again. Then this hash is verified against signed hash
|
and the root hash is computed again. Then this hash is verified against signed hash
|
||||||
from previous step using public key to ensure that nothing has changed. If everything
|
from previous step using public key to ensure that nothing has changed. If everything
|
||||||
is ok the binary encoded (see
|
is ok the binary encoded (see
|
||||||
[definition binary format](communication/ethereum-definitions-binary-format.md) section)
|
[definition binary format](communication/ethereum-definitions-binary-format.md) document)
|
||||||
definitions are generated to directory with specific [file structure](#file-structure).
|
definitions are generated to zip file with specific file structure
|
||||||
|
(see [Zip/directory file structure](#zipdirectory-file-structure) section).
|
||||||
|
|
||||||
### 3. Publish the definitions
|
### 3. Publish the definitions
|
||||||
|
|
||||||
|
@ -18,9 +18,7 @@ import json
|
|||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tarfile
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from io import BytesIO
|
|
||||||
from typing import (
|
from typing import (
|
||||||
NoReturn,
|
NoReturn,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
@ -73,6 +71,8 @@ ETHER_UNITS = {
|
|||||||
# So that we can import the web3 library only when really used and reuse the instance
|
# So that we can import the web3 library only when really used and reuse the instance
|
||||||
_WEB3_INSTANCE: Optional["web3.Web3"] = None
|
_WEB3_INSTANCE: Optional["web3.Web3"] = None
|
||||||
|
|
||||||
|
DEFS_ZIP_FILENAME = "definitions-latest.zip"
|
||||||
|
|
||||||
|
|
||||||
def _print_eth_dependencies_and_die() -> NoReturn:
|
def _print_eth_dependencies_and_die() -> NoReturn:
|
||||||
click.echo("Ethereum requirements not installed.")
|
click.echo("Ethereum requirements not installed.")
|
||||||
@ -157,7 +157,7 @@ def _erc20_contract(token_address: str, to_address: str, amount: int) -> str:
|
|||||||
"outputs": [{"name": "", "type": "bool"}],
|
"outputs": [{"name": "", "type": "bool"}],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
contract = _get_web3().eth.contract(address=token_address, abi=min_abi) # type: ignore [Argument of type "str" cannot be assigned to parameter "address" of type "Address | ChecksumAddress | ENS" in function "contract"]
|
contract = _get_web3().eth.contract(address=token_address, abi=min_abi)
|
||||||
return contract.encodeABI("transfer", [to_address, amount])
|
return contract.encodeABI("transfer", [to_address, amount])
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ def _format_access_list(
|
|||||||
|
|
||||||
|
|
||||||
def _get_ethereum_definitions(
|
def _get_ethereum_definitions(
|
||||||
definitions_dir: Optional[pathlib.Path] = None,
|
definitions_zip: Optional[pathlib.Path] = None,
|
||||||
network_def_file: Optional[BinaryIO] = None,
|
network_def_file: Optional[BinaryIO] = None,
|
||||||
token_def_file: Optional[BinaryIO] = None,
|
token_def_file: Optional[BinaryIO] = None,
|
||||||
download_definitions: bool = False,
|
download_definitions: bool = False,
|
||||||
@ -181,7 +181,7 @@ def _get_ethereum_definitions(
|
|||||||
count_of_options_used = sum(
|
count_of_options_used = sum(
|
||||||
bool(o)
|
bool(o)
|
||||||
for o in (
|
for o in (
|
||||||
definitions_dir,
|
definitions_zip,
|
||||||
(network_def_file or token_def_file),
|
(network_def_file or token_def_file),
|
||||||
download_definitions,
|
download_definitions,
|
||||||
)
|
)
|
||||||
@ -196,16 +196,16 @@ def _get_ethereum_definitions(
|
|||||||
slip44 = UH_(slip44_hardened)
|
slip44 = UH_(slip44_hardened)
|
||||||
|
|
||||||
defs = ethereum.messages.EthereumDefinitions()
|
defs = ethereum.messages.EthereumDefinitions()
|
||||||
if definitions_dir is not None:
|
if definitions_zip is not None:
|
||||||
if chain_id is not None or slip44 is not None:
|
if chain_id is not None or slip44 is not None:
|
||||||
defs.encoded_network = ethereum.get_definition_from_path(
|
defs.encoded_network = ethereum.get_definition_from_zip(
|
||||||
ethereum.get_network_definition_path(definitions_dir, chain_id, slip44)
|
definitions_zip,
|
||||||
|
ethereum.get_network_definition_path(chain_id, slip44),
|
||||||
)
|
)
|
||||||
if chain_id is not None and token_address is not None:
|
if chain_id is not None and token_address is not None:
|
||||||
defs.encoded_token = ethereum.get_definition_from_path(
|
defs.encoded_token = ethereum.get_definition_from_zip(
|
||||||
ethereum.get_token_definition_path(
|
definitions_zip,
|
||||||
definitions_dir, chain_id, token_address
|
ethereum.get_token_definition_path(chain_id, token_address),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
elif network_def_file is not None or token_def_file is not None:
|
elif network_def_file is not None or token_def_file is not None:
|
||||||
if network_def_file is not None:
|
if network_def_file is not None:
|
||||||
@ -232,30 +232,30 @@ def _get_ethereum_definitions(
|
|||||||
# commands start here
|
# commands start here
|
||||||
|
|
||||||
|
|
||||||
definitions_dir_option = click.option(
|
definitions_zip_option = click.option(
|
||||||
"--definitions-dir",
|
"--definitions-zip",
|
||||||
type=click.Path(
|
type=click.Path(
|
||||||
exists=True, file_okay=False, resolve_path=True, path_type=pathlib.Path
|
exists=True, dir_okay=False, resolve_path=True, path_type=pathlib.Path
|
||||||
),
|
),
|
||||||
help="Directory with stored definitions. Directory structure must be the same as the command "
|
help="Zip file with stored definitions. Zip file could be obtained using command "
|
||||||
"`trezorctl ethereum download-definitions` outputs.it is when. Mutually exclusive with `--network-def`, `--token-def` "
|
"`trezorctl ethereum download-definitions`. Mutually exclusive with `--network-def`, `--token-def` "
|
||||||
"and `--download-definitions`.",
|
"and `--download-definitions`.",
|
||||||
)
|
)
|
||||||
network_def_option = click.option(
|
network_def_option = click.option(
|
||||||
"--network-def",
|
"--network-def",
|
||||||
type=click.File(mode="rb"),
|
type=click.File(mode="rb"),
|
||||||
help="Binary file with network definition. Mutually exclusive with `--definitions-dir` and `--download-definitions`.",
|
help="Binary file with network definition. Mutually exclusive with `--definitions-zip` and `--download-definitions`.",
|
||||||
)
|
)
|
||||||
token_def_options = click.option(
|
token_def_options = click.option(
|
||||||
"--token-def",
|
"--token-def",
|
||||||
type=click.File(mode="rb"),
|
type=click.File(mode="rb"),
|
||||||
help="Binary file with token definition. Mutually exclusive with `--definitions-dir` and `--download-definitions`.",
|
help="Binary file with token definition. Mutually exclusive with `--definitions-zip` and `--download-definitions`.",
|
||||||
)
|
)
|
||||||
download_definitions_option = click.option(
|
download_definitions_option = click.option(
|
||||||
"--download-definitions",
|
"--download-definitions",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
help="Automatically download required definitions from `data.trezor.io/definitions/???` and use them. "
|
help="Automatically download required definitions from `data.trezor.io/definitions/???` and use them. "
|
||||||
"Mutually exclusive with `--definitions-dir`, `--network-def` and `--token-def`.", # TODO: add link?, replace this ur with function used to download defs
|
"Mutually exclusive with `--definitions-zip`, `--network-def` and `--token-def`.", # TODO: add link?, replace this ur with function used to download defs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -267,35 +267,32 @@ def cli() -> None:
|
|||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"-o",
|
"-o",
|
||||||
"--outdir",
|
"--outfile",
|
||||||
type=click.Path(resolve_path=True, file_okay=False, path_type=pathlib.Path),
|
type=click.Path(
|
||||||
default="./definitions-latest",
|
resolve_path=True, dir_okay=False, writable=True, path_type=pathlib.Path
|
||||||
|
),
|
||||||
|
default=f"./{DEFS_ZIP_FILENAME}",
|
||||||
|
help="File path to use to save downloaded definitions. Existing file will be overwritten!",
|
||||||
)
|
)
|
||||||
@click.option("-u", "--unpack", is_flag=True)
|
def download_definitions(outfile: pathlib.Path) -> None:
|
||||||
def download_definitions(outdir: pathlib.Path, unpack: bool) -> None:
|
"""Download all Ethereum network and token definitions stored in zip file
|
||||||
"""Download all Ethereum network and token definitions and save them."""
|
and save them to `outfile`.
|
||||||
archive_filename = "definitions.tar.gz"
|
"""
|
||||||
|
|
||||||
# TODO: change once we know the urls
|
# TODO: change once we know the urls
|
||||||
archived_definitions = ethereum.download_from_url(
|
archived_definitions = ethereum.download_from_url(
|
||||||
"https://data.trezor.io/eth_definitions/" + archive_filename
|
"https://data.trezor.io/eth_definitions/" + DEFS_ZIP_FILENAME
|
||||||
)
|
)
|
||||||
|
|
||||||
# unpack and/or save
|
# save
|
||||||
if unpack:
|
with open(outfile, mode="wb+") as f:
|
||||||
# TODO: implement once we know archive format
|
f.write(archived_definitions)
|
||||||
file_io = BytesIO(archived_definitions)
|
|
||||||
with tarfile.open(fileobj=file_io, mode="r:gz") as tar:
|
|
||||||
tar.extractall(outdir)
|
|
||||||
else:
|
|
||||||
with open(archive_filename, mode="wb+") as f:
|
|
||||||
f.write(archived_definitions)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
||||||
@click.option("-d", "--show-display", is_flag=True)
|
@click.option("-d", "--show-display", is_flag=True)
|
||||||
@definitions_dir_option
|
@definitions_zip_option
|
||||||
@network_def_option
|
@network_def_option
|
||||||
@download_definitions_option
|
@download_definitions_option
|
||||||
@with_client
|
@with_client
|
||||||
@ -303,14 +300,14 @@ def get_address(
|
|||||||
client: "TrezorClient",
|
client: "TrezorClient",
|
||||||
address: str,
|
address: str,
|
||||||
show_display: bool,
|
show_display: bool,
|
||||||
definitions_dir: pathlib.Path,
|
definitions_zip: pathlib.Path,
|
||||||
network_def: BinaryIO,
|
network_def: BinaryIO,
|
||||||
download_definitions: bool,
|
download_definitions: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Get Ethereum address in hex encoding."""
|
"""Get Ethereum address in hex encoding."""
|
||||||
address_n = tools.parse_path(address)
|
address_n = tools.parse_path(address)
|
||||||
defs = _get_ethereum_definitions(
|
defs = _get_ethereum_definitions(
|
||||||
definitions_dir=definitions_dir,
|
definitions_zip=definitions_zip,
|
||||||
network_def_file=network_def,
|
network_def_file=network_def,
|
||||||
download_definitions=download_definitions,
|
download_definitions=download_definitions,
|
||||||
slip44_hardened=address_n[1],
|
slip44_hardened=address_n[1],
|
||||||
@ -391,7 +388,7 @@ def get_public_node(
|
|||||||
)
|
)
|
||||||
@click.argument("to_address")
|
@click.argument("to_address")
|
||||||
@click.argument("amount", callback=_amount_to_int)
|
@click.argument("amount", callback=_amount_to_int)
|
||||||
@definitions_dir_option
|
@definitions_zip_option
|
||||||
@network_def_option
|
@network_def_option
|
||||||
@token_def_options
|
@token_def_options
|
||||||
@download_definitions_option
|
@download_definitions_option
|
||||||
@ -413,7 +410,7 @@ def sign_tx(
|
|||||||
max_priority_fee: Optional[int],
|
max_priority_fee: Optional[int],
|
||||||
access_list: List[ethereum.messages.EthereumAccessList],
|
access_list: List[ethereum.messages.EthereumAccessList],
|
||||||
eip2718_type: Optional[int],
|
eip2718_type: Optional[int],
|
||||||
definitions_dir: pathlib.Path,
|
definitions_zip: pathlib.Path,
|
||||||
network_def: BinaryIO,
|
network_def: BinaryIO,
|
||||||
token_def: BinaryIO,
|
token_def: BinaryIO,
|
||||||
download_definitions: bool,
|
download_definitions: bool,
|
||||||
@ -457,7 +454,7 @@ def sign_tx(
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
defs = _get_ethereum_definitions(
|
defs = _get_ethereum_definitions(
|
||||||
definitions_dir=definitions_dir,
|
definitions_zip=definitions_zip,
|
||||||
network_def_file=network_def,
|
network_def_file=network_def,
|
||||||
token_def_file=token_def,
|
token_def_file=token_def,
|
||||||
download_definitions=download_definitions,
|
download_definitions=download_definitions,
|
||||||
@ -476,7 +473,7 @@ def sign_tx(
|
|||||||
amount = 0
|
amount = 0
|
||||||
# to_address has changed, reload definitions
|
# to_address has changed, reload definitions
|
||||||
defs = _get_ethereum_definitions(
|
defs = _get_ethereum_definitions(
|
||||||
definitions_dir=definitions_dir,
|
definitions_zip=definitions_zip,
|
||||||
network_def_file=network_def,
|
network_def_file=network_def,
|
||||||
token_def_file=token_def,
|
token_def_file=token_def,
|
||||||
download_definitions=download_definitions,
|
download_definitions=download_definitions,
|
||||||
@ -588,7 +585,7 @@ def sign_tx(
|
|||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
||||||
@click.argument("message")
|
@click.argument("message")
|
||||||
@definitions_dir_option
|
@definitions_zip_option
|
||||||
@network_def_option
|
@network_def_option
|
||||||
@download_definitions_option
|
@download_definitions_option
|
||||||
@with_client
|
@with_client
|
||||||
@ -596,14 +593,14 @@ def sign_message(
|
|||||||
client: "TrezorClient",
|
client: "TrezorClient",
|
||||||
address: str,
|
address: str,
|
||||||
message: str,
|
message: str,
|
||||||
definitions_dir: pathlib.Path,
|
definitions_zip: pathlib.Path,
|
||||||
network_def: BinaryIO,
|
network_def: BinaryIO,
|
||||||
download_definitions: bool,
|
download_definitions: bool,
|
||||||
) -> Dict[str, str]:
|
) -> Dict[str, str]:
|
||||||
"""Sign message with Ethereum address."""
|
"""Sign message with Ethereum address."""
|
||||||
address_n = tools.parse_path(address)
|
address_n = tools.parse_path(address)
|
||||||
defs = _get_ethereum_definitions(
|
defs = _get_ethereum_definitions(
|
||||||
definitions_dir=definitions_dir,
|
definitions_zip=definitions_zip,
|
||||||
network_def_file=network_def,
|
network_def_file=network_def,
|
||||||
download_definitions=download_definitions,
|
download_definitions=download_definitions,
|
||||||
slip44_hardened=address_n[1],
|
slip44_hardened=address_n[1],
|
||||||
@ -625,7 +622,7 @@ def sign_message(
|
|||||||
help="Be compatible with Metamask's signTypedData_v4 implementation",
|
help="Be compatible with Metamask's signTypedData_v4 implementation",
|
||||||
)
|
)
|
||||||
@click.argument("file", type=click.File("r"))
|
@click.argument("file", type=click.File("r"))
|
||||||
@definitions_dir_option
|
@definitions_zip_option
|
||||||
@network_def_option
|
@network_def_option
|
||||||
@token_def_options
|
@token_def_options
|
||||||
@download_definitions_option
|
@download_definitions_option
|
||||||
@ -635,7 +632,7 @@ def sign_typed_data(
|
|||||||
address: str,
|
address: str,
|
||||||
metamask_v4_compat: bool,
|
metamask_v4_compat: bool,
|
||||||
file: TextIO,
|
file: TextIO,
|
||||||
definitions_dir: pathlib.Path,
|
definitions_zip: pathlib.Path,
|
||||||
network_def: BinaryIO,
|
network_def: BinaryIO,
|
||||||
token_def: BinaryIO,
|
token_def: BinaryIO,
|
||||||
download_definitions: bool,
|
download_definitions: bool,
|
||||||
@ -649,7 +646,7 @@ def sign_typed_data(
|
|||||||
address_n = tools.parse_path(address)
|
address_n = tools.parse_path(address)
|
||||||
data = json.loads(file.read())
|
data = json.loads(file.read())
|
||||||
defs = _get_ethereum_definitions(
|
defs = _get_ethereum_definitions(
|
||||||
definitions_dir=definitions_dir,
|
definitions_zip=definitions_zip,
|
||||||
network_def_file=network_def,
|
network_def_file=network_def,
|
||||||
token_def_file=token_def,
|
token_def_file=token_def,
|
||||||
download_definitions=download_definitions,
|
download_definitions=download_definitions,
|
||||||
@ -689,7 +686,7 @@ def verify_message(
|
|||||||
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
||||||
@click.argument("domain_hash_hex")
|
@click.argument("domain_hash_hex")
|
||||||
@click.argument("message_hash_hex")
|
@click.argument("message_hash_hex")
|
||||||
@definitions_dir_option
|
@definitions_zip_option
|
||||||
@network_def_option
|
@network_def_option
|
||||||
@download_definitions_option
|
@download_definitions_option
|
||||||
@with_client
|
@with_client
|
||||||
@ -698,7 +695,7 @@ def sign_typed_data_hash(
|
|||||||
address: str,
|
address: str,
|
||||||
domain_hash_hex: str,
|
domain_hash_hex: str,
|
||||||
message_hash_hex: str,
|
message_hash_hex: str,
|
||||||
definitions_dir: pathlib.Path,
|
definitions_zip: pathlib.Path,
|
||||||
network_def: BinaryIO,
|
network_def: BinaryIO,
|
||||||
download_definitions: bool,
|
download_definitions: bool,
|
||||||
) -> Dict[str, str]:
|
) -> Dict[str, str]:
|
||||||
@ -713,7 +710,7 @@ def sign_typed_data_hash(
|
|||||||
domain_hash = ethereum.decode_hex(domain_hash_hex)
|
domain_hash = ethereum.decode_hex(domain_hash_hex)
|
||||||
message_hash = ethereum.decode_hex(message_hash_hex) if message_hash_hex else None
|
message_hash = ethereum.decode_hex(message_hash_hex) if message_hash_hex else None
|
||||||
defs = _get_ethereum_definitions(
|
defs = _get_ethereum_definitions(
|
||||||
definitions_dir=definitions_dir,
|
definitions_zip=definitions_zip,
|
||||||
network_def_file=network_def,
|
network_def_file=network_def,
|
||||||
download_definitions=download_definitions,
|
download_definitions=download_definitions,
|
||||||
slip44_hardened=address_n[1],
|
slip44_hardened=address_n[1],
|
||||||
|
@ -16,11 +16,12 @@
|
|||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import zipfile
|
||||||
from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from . import exceptions, messages
|
from . import exceptions, merkle_tree, messages
|
||||||
from .tools import expect, prepare_message_bytes, session
|
from .tools import expect, prepare_message_bytes, session
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -31,6 +32,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
# TODO: change once we know the urls
|
# TODO: change once we know the urls
|
||||||
DEFS_BASE_URL = "https://data.trezor.io/eth_definitions/{lookup_type}/{id}/{name}"
|
DEFS_BASE_URL = "https://data.trezor.io/eth_definitions/{lookup_type}/{id}/{name}"
|
||||||
|
DEFS_ZIP_TOPLEVEL_DIR = pathlib.PurePath("definitions-latest")
|
||||||
DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE = "by_chain_id"
|
DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE = "by_chain_id"
|
||||||
DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE = "by_slip44"
|
DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE = "by_slip44"
|
||||||
DEFS_NETWORK_URI_NAME = "network.dat"
|
DEFS_NETWORK_URI_NAME = "network.dat"
|
||||||
@ -201,10 +203,9 @@ def get_token_definition_url(chain_id: int = None, token_address: str = None) ->
|
|||||||
|
|
||||||
|
|
||||||
def get_network_definition_path(
|
def get_network_definition_path(
|
||||||
base_path: pathlib.Path,
|
|
||||||
chain_id: Optional[int] = None,
|
chain_id: Optional[int] = None,
|
||||||
slip44: Optional[int] = None,
|
slip44: Optional[int] = None,
|
||||||
) -> pathlib.Path:
|
) -> pathlib.PurePath:
|
||||||
if not ((chain_id is None) != (slip44 is None)): # not XOR
|
if not ((chain_id is None) != (slip44 is None)): # not XOR
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Exactly one of chain_id or slip44 parameters are needed to construct network definition path."
|
"Exactly one of chain_id or slip44 parameters are needed to construct network definition path."
|
||||||
@ -212,14 +213,14 @@ def get_network_definition_path(
|
|||||||
|
|
||||||
if chain_id is not None:
|
if chain_id is not None:
|
||||||
return (
|
return (
|
||||||
base_path
|
DEFS_ZIP_TOPLEVEL_DIR
|
||||||
/ DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE
|
/ DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE
|
||||||
/ str(chain_id)
|
/ str(chain_id)
|
||||||
/ DEFS_NETWORK_URI_NAME
|
/ DEFS_NETWORK_URI_NAME
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return (
|
return (
|
||||||
base_path
|
DEFS_ZIP_TOPLEVEL_DIR
|
||||||
/ DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE
|
/ DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE
|
||||||
/ str(slip44)
|
/ str(slip44)
|
||||||
/ DEFS_NETWORK_URI_NAME
|
/ DEFS_NETWORK_URI_NAME
|
||||||
@ -227,10 +228,9 @@ def get_network_definition_path(
|
|||||||
|
|
||||||
|
|
||||||
def get_token_definition_path(
|
def get_token_definition_path(
|
||||||
base_path: pathlib.Path,
|
|
||||||
chain_id: int = None,
|
chain_id: int = None,
|
||||||
token_address: str = None,
|
token_address: str = None,
|
||||||
) -> pathlib.Path:
|
) -> pathlib.PurePath:
|
||||||
if chain_id is None or token_address is None:
|
if chain_id is None or token_address is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Both chain_id and token_address parameters are needed to construct token definition path."
|
"Both chain_id and token_address parameters are needed to construct token definition path."
|
||||||
@ -241,21 +241,62 @@ def get_token_definition_path(
|
|||||||
addr = addr[2:]
|
addr = addr[2:]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
base_path
|
DEFS_ZIP_TOPLEVEL_DIR
|
||||||
/ DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE
|
/ DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE
|
||||||
/ str(chain_id)
|
/ str(chain_id)
|
||||||
/ DEFS_TOKEN_URI_NAME.format(hex_address=addr)
|
/ DEFS_TOKEN_URI_NAME.format(hex_address=addr)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_definition_from_path(
|
def get_all_completed_definitions_from_zip(
|
||||||
path: pathlib.Path,
|
zip_file: pathlib.Path,
|
||||||
) -> Optional[bytes]:
|
) -> Dict[str, Optional[bytes]]:
|
||||||
if not path.exists() or not path.is_file():
|
if not zip_file.exists() or not zip_file.is_file():
|
||||||
return None
|
return {}
|
||||||
|
|
||||||
with open(path, mode="rb") as f:
|
all_definitions_dict = {}
|
||||||
return f.read()
|
mt_leaves = []
|
||||||
|
signature = None
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_file) as zf:
|
||||||
|
names = zf.namelist()
|
||||||
|
signature = zf.read(names[0])[-64:]
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
all_definitions_dict[name] = zf.read(name)[:-64]
|
||||||
|
if name.startswith(
|
||||||
|
str(DEFS_ZIP_TOPLEVEL_DIR / DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE)
|
||||||
|
):
|
||||||
|
mt_leaves.append(all_definitions_dict[name])
|
||||||
|
|
||||||
|
# sort encoded definitions
|
||||||
|
mt_leaves.sort()
|
||||||
|
|
||||||
|
# build Merkle tree
|
||||||
|
mt = merkle_tree.MerkleTree(mt_leaves)
|
||||||
|
proofs = mt.get_proofs()
|
||||||
|
|
||||||
|
# complete definitions
|
||||||
|
for path, definition in all_definitions_dict.items():
|
||||||
|
proof = proofs[definition]
|
||||||
|
# append number of hashes in proof
|
||||||
|
all_definitions_dict[path] += len(proof).to_bytes(1, "big")
|
||||||
|
# append proof itself
|
||||||
|
for p in proof:
|
||||||
|
all_definitions_dict[path] += p
|
||||||
|
# append signed tree root hash
|
||||||
|
all_definitions_dict[path] += signature
|
||||||
|
|
||||||
|
return all_definitions_dict
|
||||||
|
|
||||||
|
|
||||||
|
def get_definition_from_zip(
|
||||||
|
zip_file: pathlib.Path,
|
||||||
|
path_inside_zip: pathlib.PurePath,
|
||||||
|
) -> Optional[bytes]:
|
||||||
|
all_defs = get_all_completed_definitions_from_zip(zip_file)
|
||||||
|
|
||||||
|
return all_defs.get(str(path_inside_zip))
|
||||||
|
|
||||||
|
|
||||||
# ====== Client functions ====== #
|
# ====== Client functions ====== #
|
||||||
|
115
python/src/trezorlib/merkle_tree.py
Executable file
115
python/src/trezorlib/merkle_tree.py
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
# This file is part of the Trezor project.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012-2022 SatoshiLabs and contributors
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3
|
||||||
|
# as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the License along with this library.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
from hashlib import sha256
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
"""
|
||||||
|
Single node of Merkle tree.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self: "Node",
|
||||||
|
*,
|
||||||
|
left: Optional["Node"] = None,
|
||||||
|
right: Optional["Node"] = None,
|
||||||
|
raw_value: Optional[bytes] = None,
|
||||||
|
) -> None:
|
||||||
|
self.raw_value = raw_value
|
||||||
|
|
||||||
|
if self.raw_value and (left is not None or right is not None):
|
||||||
|
raise ValueError(
|
||||||
|
"Cannot use `raw_value` together with `left` and/or `right` value."
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.raw_value and (left is None or right is None):
|
||||||
|
raise ValueError(
|
||||||
|
"`left` and `right` value must not be None when not using `raw_value`."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.hash = None
|
||||||
|
self.left_child = left
|
||||||
|
self.right_child = right
|
||||||
|
self.proof_list: List[bytes] = []
|
||||||
|
|
||||||
|
def compute_hash(self) -> bytes:
|
||||||
|
if not self.hash:
|
||||||
|
if self.raw_value:
|
||||||
|
self.hash = sha256(b"\x00" + self.raw_value).digest()
|
||||||
|
else:
|
||||||
|
left_hash = self.left_child.compute_hash() # type: ignore ["compute_hash" is not a known member of "None"]
|
||||||
|
right_hash = self.right_child.compute_hash() # type: ignore ["compute_hash" is not a known member of "None"]
|
||||||
|
hash_a = min(left_hash, right_hash)
|
||||||
|
hash_b = max(left_hash, right_hash)
|
||||||
|
self.hash = sha256(b"\x01" + hash_a + hash_b).digest()
|
||||||
|
|
||||||
|
# distribute proof
|
||||||
|
self.left_child.add_to_proof(right_hash) # type: ignore ["add_to_proof" is not a known member of "None"]
|
||||||
|
self.right_child.add_to_proof(left_hash) # type: ignore ["add_to_proof" is not a known member of "None"]
|
||||||
|
|
||||||
|
return self.hash
|
||||||
|
|
||||||
|
def add_to_proof(self, proof: bytes) -> None:
|
||||||
|
self.proof_list.append(proof)
|
||||||
|
if not self.raw_value:
|
||||||
|
self.left_child.add_to_proof(proof) # type: ignore ["add_to_proof" is not a known member of "None"]
|
||||||
|
self.right_child.add_to_proof(proof) # type: ignore ["add_to_proof" is not a known member of "None"]
|
||||||
|
|
||||||
|
|
||||||
|
class MerkleTree:
|
||||||
|
"""
|
||||||
|
Simple Merkle tree that implements the building of Merkle tree itself and generate proofs
|
||||||
|
for leaf nodes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, values: List[bytes]) -> None:
|
||||||
|
self.leaves = [Node(raw_value=v) for v in values]
|
||||||
|
self.height = 0
|
||||||
|
|
||||||
|
# build the tree
|
||||||
|
current_level = [n for n in self.leaves]
|
||||||
|
while len(current_level) > 1:
|
||||||
|
# build one level of the tree
|
||||||
|
next_level = []
|
||||||
|
while len(current_level) // 2:
|
||||||
|
left_node = current_level.pop()
|
||||||
|
right_node = current_level.pop()
|
||||||
|
next_level.append(Node(left=left_node, right=right_node))
|
||||||
|
|
||||||
|
if len(current_level) == 1:
|
||||||
|
# odd number of nodes on current level so last node will be "joined" on another level
|
||||||
|
next_level.append(current_level.pop())
|
||||||
|
|
||||||
|
# switch levels and continue
|
||||||
|
self.height += 1
|
||||||
|
current_level = next_level
|
||||||
|
|
||||||
|
# set root and compute hash
|
||||||
|
self.root_node = current_level[0]
|
||||||
|
self.root_node.compute_hash()
|
||||||
|
|
||||||
|
def get_proofs(self) -> Dict[bytes, List[bytes]]:
|
||||||
|
return {
|
||||||
|
n.raw_value: n.proof_list for n in self.leaves if n.raw_value is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_tree_height(self) -> int:
|
||||||
|
return self.height
|
||||||
|
|
||||||
|
def get_root_hash(self) -> bytes:
|
||||||
|
return self.root_node.hash # type: ignore [Expression of type "bytes | None" cannot be assigned to return type "bytes"]
|
67
python/tools/eth_defs_unpack.py
Executable file
67
python/tools/eth_defs_unpack.py
Executable file
@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is part of the Trezor project.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012-2022 SatoshiLabs and contributors
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3
|
||||||
|
# as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the License along with this library.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import click
|
||||||
|
|
||||||
|
from trezorlib import ethereum
|
||||||
|
from trezorlib.cli import ethereum as ethereum_cli
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option(
|
||||||
|
"-z", "--definitions-zip",
|
||||||
|
type=click.Path(
|
||||||
|
exists=True, dir_okay=False, resolve_path=True, path_type=pathlib.Path
|
||||||
|
),
|
||||||
|
default=f"./{ethereum_cli.DEFS_ZIP_FILENAME}",
|
||||||
|
help="Zip file with stored definitions. Zip file could be obtained using command"
|
||||||
|
"`trezorctl ethereum download-definitions`.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-o",
|
||||||
|
"--outdir",
|
||||||
|
type=click.Path(
|
||||||
|
resolve_path=True, file_okay=False, writable=True, path_type=pathlib.Path
|
||||||
|
),
|
||||||
|
default="./",
|
||||||
|
help="Directory path where the definitions will be unpacked. Zip file contains top level"
|
||||||
|
f"dir \"{ethereum.DEFS_ZIP_TOPLEVEL_DIR}\" and this will be unpacked to desired \"outdir\""
|
||||||
|
"path. Any colliding directories will be overwritten!",
|
||||||
|
)
|
||||||
|
def unpack_definitions(
|
||||||
|
definitions_zip: pathlib.Path,
|
||||||
|
outdir: pathlib.Path,
|
||||||
|
) -> None:
|
||||||
|
"""Script that unpacks and completes (insert missing Merkle Tree proofs
|
||||||
|
into the definitions) the Ethereum definitions (networks and tokens).
|
||||||
|
"""
|
||||||
|
all_defs = ethereum.get_all_completed_definitions_from_zip(definitions_zip)
|
||||||
|
|
||||||
|
for path, definition in all_defs.items():
|
||||||
|
if not definition:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filepath = outdir / path
|
||||||
|
filepath.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(filepath, mode="wb+") as f:
|
||||||
|
f.write(definition)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unpack_definitions()
|
@ -35,11 +35,11 @@ def test_getaddress(client: Client, parameters, result):
|
|||||||
"slip44", encoded_network_slip44
|
"slip44", encoded_network_slip44
|
||||||
)
|
)
|
||||||
|
|
||||||
encoded_network = ethereum.get_definition_from_path(
|
encoded_network = ethereum.get_definition_from_zip(
|
||||||
|
COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip",
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
base_path=COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
slip44=encoded_network_slip44,
|
slip44=encoded_network_slip44,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
ethereum.get_address(client, address_n, encoded_network=encoded_network)
|
ethereum.get_address(client, address_n, encoded_network=encoded_network)
|
||||||
|
@ -25,6 +25,8 @@ from ...common import COMMON_FIXTURES_DIR, parametrize_using_common_fixtures
|
|||||||
|
|
||||||
SHOW_MORE = (143, 167)
|
SHOW_MORE = (143, 167)
|
||||||
|
|
||||||
|
DEFS_ZIP_FILE_PATH = COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip"
|
||||||
|
|
||||||
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
|
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
|
||||||
|
|
||||||
|
|
||||||
@ -40,11 +42,11 @@ def test_ethereum_sign_typed_data(client: Client, parameters, result):
|
|||||||
)
|
)
|
||||||
|
|
||||||
defs = ethereum.messages.EthereumDefinitions(
|
defs = ethereum.messages.EthereumDefinitions(
|
||||||
encoded_network=ethereum.get_definition_from_path(
|
encoded_network=ethereum.get_definition_from_zip(
|
||||||
|
DEFS_ZIP_FILE_PATH,
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
base_path=COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
slip44=encoded_network_slip44,
|
slip44=encoded_network_slip44,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,11 +90,11 @@ def test_ethereum_sign_typed_data_blind(client: Client, parameters, result):
|
|||||||
"slip44", encoded_network_slip44
|
"slip44", encoded_network_slip44
|
||||||
)
|
)
|
||||||
|
|
||||||
encoded_network = ethereum.get_definition_from_path(
|
encoded_network = ethereum.get_definition_from_zip(
|
||||||
|
DEFS_ZIP_FILE_PATH,
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
base_path=COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
slip44=encoded_network_slip44,
|
slip44=encoded_network_slip44,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
ret = ethereum.sign_typed_data_hash(
|
ret = ethereum.sign_typed_data_hash(
|
||||||
client,
|
client,
|
||||||
@ -207,11 +209,11 @@ def input_flow_cancel(client: Client):
|
|||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
def test_ethereum_sign_typed_data_show_more_button(client: Client):
|
def test_ethereum_sign_typed_data_show_more_button(client: Client):
|
||||||
defs = ethereum.messages.EthereumDefinitions(
|
defs = ethereum.messages.EthereumDefinitions(
|
||||||
encoded_network=ethereum.get_definition_from_path(
|
encoded_network=ethereum.get_definition_from_zip(
|
||||||
|
DEFS_ZIP_FILE_PATH,
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
base_path=COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
slip44=60,
|
slip44=60,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -230,11 +232,11 @@ def test_ethereum_sign_typed_data_show_more_button(client: Client):
|
|||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
def test_ethereum_sign_typed_data_cancel(client: Client):
|
def test_ethereum_sign_typed_data_cancel(client: Client):
|
||||||
defs = ethereum.messages.EthereumDefinitions(
|
defs = ethereum.messages.EthereumDefinitions(
|
||||||
encoded_network=ethereum.get_definition_from_path(
|
encoded_network=ethereum.get_definition_from_zip(
|
||||||
|
DEFS_ZIP_FILE_PATH,
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
base_path=COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
slip44=60,
|
slip44=60,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ def test_signmessage(client: Client, parameters, result):
|
|||||||
"slip44", encoded_network_slip44
|
"slip44", encoded_network_slip44
|
||||||
)
|
)
|
||||||
|
|
||||||
encoded_network = ethereum.get_definition_from_path(
|
encoded_network = ethereum.get_definition_from_zip(
|
||||||
|
COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip",
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
base_path=COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
slip44=encoded_network_slip44,
|
slip44=encoded_network_slip44,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
res = ethereum.sign_message(client, address_n, parameters["msg"], encoded_network)
|
res = ethereum.sign_message(client, address_n, parameters["msg"], encoded_network)
|
||||||
assert res.address == result["address"]
|
assert res.address == result["address"]
|
||||||
|
@ -29,6 +29,8 @@ TO_ADDR = "0x1d1c328764a41bda0492b66baa30c4a339ff85ef"
|
|||||||
SHOW_ALL = (143, 167)
|
SHOW_ALL = (143, 167)
|
||||||
GO_BACK = (16, 220)
|
GO_BACK = (16, 220)
|
||||||
|
|
||||||
|
DEFS_ZIP_FILE_PATH = COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip"
|
||||||
|
|
||||||
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
|
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
|
||||||
|
|
||||||
|
|
||||||
@ -37,25 +39,25 @@ def get_definitions(
|
|||||||
) -> messages.EthereumDefinitions:
|
) -> messages.EthereumDefinitions:
|
||||||
chain_id = parameters["chain_id"]
|
chain_id = parameters["chain_id"]
|
||||||
token_chain_id = parameters["chain_id"]
|
token_chain_id = parameters["chain_id"]
|
||||||
token_address = parameters["to_address"]
|
token_address = parameters.get("to_address")
|
||||||
|
|
||||||
if "definitions" in parameters:
|
if "definitions" in parameters:
|
||||||
chain_id = parameters["definitions"].get("chain_id", chain_id)
|
chain_id = parameters["definitions"].get("chain_id", chain_id)
|
||||||
token_chain_id = parameters["definitions"].get("token_chain_id", token_chain_id)
|
token_chain_id = parameters["definitions"].get("token_chain_id", token_chain_id)
|
||||||
token_address = parameters["definitions"].get("to_address", token_address)
|
token_address = parameters["definitions"].get("to_address", token_address)
|
||||||
|
|
||||||
encoded_network = ethereum.get_definition_from_path(
|
encoded_network = ethereum.get_definition_from_zip(
|
||||||
|
DEFS_ZIP_FILE_PATH,
|
||||||
ethereum.get_network_definition_path(
|
ethereum.get_network_definition_path(
|
||||||
COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
chain_id,
|
chain_id,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
encoded_token = ethereum.get_definition_from_path(
|
encoded_token = ethereum.get_definition_from_zip(
|
||||||
|
DEFS_ZIP_FILE_PATH,
|
||||||
ethereum.get_token_definition_path(
|
ethereum.get_token_definition_path(
|
||||||
COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest",
|
|
||||||
token_chain_id,
|
token_chain_id,
|
||||||
token_address,
|
token_address,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return messages.EthereumDefinitions(
|
return messages.EthereumDefinitions(
|
||||||
@ -91,7 +93,7 @@ def test_signtx(client: Client, parameters, result):
|
|||||||
assert sig_v == result["sig_v"]
|
assert sig_v == result["sig_v"]
|
||||||
|
|
||||||
|
|
||||||
def test_signtx_missing_extern_definitions(client: Client):
|
def test_signtx_missing_or_wrong_extern_definitions(client: Client):
|
||||||
chain_id = 8 # Ubiq
|
chain_id = 8 # Ubiq
|
||||||
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
|
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
|
||||||
|
|
||||||
@ -124,31 +126,6 @@ def test_signtx_missing_extern_definitions(client: Client):
|
|||||||
"to_address": "",
|
"to_address": "",
|
||||||
}
|
}
|
||||||
), # missing network and token
|
), # missing network and token
|
||||||
]
|
|
||||||
|
|
||||||
for definitions in definitions_vector:
|
|
||||||
with pytest.raises(TrezorFailure, match=r"DataError:.*Forbidden key path"):
|
|
||||||
sign_tx_call(definitions=definitions)
|
|
||||||
|
|
||||||
|
|
||||||
def test_signtx_wrong_extern_definitions(client: Client):
|
|
||||||
chain_id = 8 # Ubiq
|
|
||||||
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
|
|
||||||
|
|
||||||
sign_tx_call = partial(
|
|
||||||
ethereum.sign_tx,
|
|
||||||
client,
|
|
||||||
n=parse_path("m/44'/108'/0'/0/0"),
|
|
||||||
nonce=0,
|
|
||||||
gas_price=20,
|
|
||||||
gas_limit=20,
|
|
||||||
to=to_address,
|
|
||||||
chain_id=chain_id,
|
|
||||||
value=0,
|
|
||||||
data=b"a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
)
|
|
||||||
|
|
||||||
definitions_vector = [
|
|
||||||
get_definitions(
|
get_definitions(
|
||||||
{
|
{
|
||||||
"chain_id": chain_id,
|
"chain_id": chain_id,
|
||||||
@ -164,13 +141,19 @@ def test_signtx_wrong_extern_definitions(client: Client):
|
|||||||
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
||||||
}
|
}
|
||||||
), # wrong network and token
|
), # wrong network and token
|
||||||
|
get_definitions(
|
||||||
|
{
|
||||||
|
"chain_id": chain_id,
|
||||||
|
"definitions": {
|
||||||
|
"token_chain_id": 1,
|
||||||
|
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
), # wrong token
|
||||||
]
|
]
|
||||||
|
|
||||||
for definitions in definitions_vector:
|
for definitions in definitions_vector:
|
||||||
with pytest.raises(
|
with pytest.raises(TrezorFailure, match=r"DataError"):
|
||||||
TrezorFailure,
|
|
||||||
match=r"DataError:.*Network definition mismatch",
|
|
||||||
):
|
|
||||||
sign_tx_call(definitions=definitions)
|
sign_tx_call(definitions=definitions)
|
||||||
|
|
||||||
|
|
||||||
@ -199,7 +182,7 @@ def test_signtx_eip1559(client: Client, parameters, result):
|
|||||||
assert sig_v == result["sig_v"]
|
assert sig_v == result["sig_v"]
|
||||||
|
|
||||||
|
|
||||||
def test_signtx_eip1559_missing_extern_definitions(client: Client):
|
def test_signtx_eip1559_missing_or_wrong_extern_definitions(client: Client):
|
||||||
chain_id = 8 # Ubiq
|
chain_id = 8 # Ubiq
|
||||||
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
|
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
|
||||||
|
|
||||||
@ -233,32 +216,6 @@ def test_signtx_eip1559_missing_extern_definitions(client: Client):
|
|||||||
"to_address": "",
|
"to_address": "",
|
||||||
}
|
}
|
||||||
), # missing network and token
|
), # missing network and token
|
||||||
]
|
|
||||||
|
|
||||||
for definitions in definitions_vector:
|
|
||||||
with pytest.raises(TrezorFailure, match=r"DataError:.*Forbidden key path"):
|
|
||||||
sign_tx_eip1559_call(definitions=definitions)
|
|
||||||
|
|
||||||
|
|
||||||
def test_signtx_eip1559_wrong_extern_definitions(client: Client):
|
|
||||||
chain_id = 8 # Ubiq
|
|
||||||
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
|
|
||||||
|
|
||||||
sign_tx_eip1559_call = partial(
|
|
||||||
ethereum.sign_tx_eip1559,
|
|
||||||
client,
|
|
||||||
n=parse_path("m/44'/108'/0'/0/0"),
|
|
||||||
nonce=0,
|
|
||||||
gas_limit=20,
|
|
||||||
max_gas_fee=20,
|
|
||||||
max_priority_fee=1,
|
|
||||||
to=to_address,
|
|
||||||
chain_id=chain_id,
|
|
||||||
value=0,
|
|
||||||
data=b"a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
)
|
|
||||||
|
|
||||||
definitions_vector = [
|
|
||||||
get_definitions(
|
get_definitions(
|
||||||
{
|
{
|
||||||
"chain_id": chain_id,
|
"chain_id": chain_id,
|
||||||
@ -274,13 +231,19 @@ def test_signtx_eip1559_wrong_extern_definitions(client: Client):
|
|||||||
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
||||||
}
|
}
|
||||||
), # wrong network and token
|
), # wrong network and token
|
||||||
|
get_definitions(
|
||||||
|
{
|
||||||
|
"chain_id": chain_id,
|
||||||
|
"definitions": {
|
||||||
|
"token_chain_id": 1,
|
||||||
|
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
), # wrong token
|
||||||
]
|
]
|
||||||
|
|
||||||
for definitions in definitions_vector:
|
for definitions in definitions_vector:
|
||||||
with pytest.raises(
|
with pytest.raises(TrezorFailure, match=r"DataError"):
|
||||||
TrezorFailure,
|
|
||||||
match=r"DataError:.*Network definition mismatch",
|
|
||||||
):
|
|
||||||
sign_tx_eip1559_call(definitions=definitions)
|
sign_tx_eip1559_call(definitions=definitions)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user