1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-23 15:08:19 +00:00

feat(tests, python): update tests

This commit is contained in:
Martin Novak 2022-11-07 15:14:56 +01:00
parent afce401fa2
commit ebaaa021e9
39 changed files with 1042 additions and 325 deletions

View File

@ -0,0 +1,20 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"name": "missing_extern_definition_GoChain",
"parameters": {
"path": "m/44'/6060'/0'/0/0",
"definitions": {
"slip44": -1
}
},
"result": {
"error": "DataError:.*Forbidden key path"
}
}
]
}

View File

@ -0,0 +1,132 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"name": "builtin_Ethereum_extern_adChain_outdated_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": {
"timestamp": 0
}
},
"result": {
"error": "DataError:.*Definition is outdated"
}
},
{
"name": "extern_Ubiq_extern_Sphere_missing_network",
"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": {
"chain_id": -1
}
},
"result": {
"error": "DataError:.*"
}
},
{
"name": "extern_Ubiq_extern_Sphere_missing_network_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": {
"chain_id": -1,
"to_address": ""
}
},
"result": {
"error": "DataError:.*Forbidden key path"
}
},
{
"name": "extern_Ubiq_extern_Sphere_wrong_network",
"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": {
"chain_id": 1
}
},
"result": {
"error": "DataError:.*Network definition mismatch"
}
},
{
"name": "extern_Ubiq_extern_Sphere_wrong_network_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": {
"chain_id": 1,
"token_chain_id": 1,
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd"
}
},
"result": {
"error": "DataError:.*Network definition mismatch"
}
},
{
"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": {
"error": "DataError:.*Token definition mismatch"
}
}
]
}

View File

@ -0,0 +1,112 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"name": "extern_Ubiq_extern_Sphere_missing_network",
"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": {
"chain_id": -1
}
},
"result": {
"error": "DataError:.*"
}
},
{
"name": "extern_Ubiq_extern_Sphere_missing_network_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": {
"chain_id": -1,
"to_address": ""
}
},
"result": {
"error": "DataError:.*Forbidden key path"
}
},
{
"name": "extern_Ubiq_extern_Sphere_wrong_network",
"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": {
"chain_id": 1
}
},
"result": {
"error": "DataError:.*Network definition mismatch"
}
},
{
"name": "extern_Ubiq_extern_Sphere_wrong_network_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": {
"chain_id": 1,
"token_chain_id": 1,
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd"
}
},
"result": {
"error": "DataError:.*Network definition mismatch"
}
},
{
"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": {
"error": "DataError:.*Token definition mismatch"
}
}
]
}

View File

@ -0,0 +1,32 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"name": "missing_extern_definition_GoChain",
"comment": "Bare minimum EIP-712 message (domain only)",
"parameters": {
"path": "m/44'/6060'/0'/0/0",
"metamask_v4_compat": true,
"data": {
"types": {
"EIP712Domain": []
},
"primaryType": "EIP712Domain",
"message": {},
"domain": {}
},
"message_hash": null,
"domain_separator_hash": "0x6192106f129ce05c9075d319c1fa6ea9b3ae37cbd0c1ef92e2be7137bb07baa1",
"definitions": {
"slip44": -1
}
},
"result": {
"error": "DataError:.*Forbidden key path"
}
}
]
}

View File

@ -0,0 +1,21 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"name": "missing_extern_definition_GoChain",
"parameters": {
"msg": "This is an example of a signed message.",
"path": "m/44'/6060'/0'/0/0",
"definitions": {
"slip44": -1
}
},
"result": {
"error": "DataError:.*Forbidden key path"
}
}
]
}

View File

@ -16,7 +16,7 @@ if TYPE_CHECKING:
DefType = TypeVar("DefType", EthereumNetworkInfo, EthereumTokenInfo)
DEFINITIONS_PUBLIC_KEY = b""
MIN_DATA_VERSION = 1 # TODO: update
MIN_DATA_VERSION = 1 # TODO: update
FORMAT_VERSION = b"trzd1"
if __debug__:

View File

@ -9,8 +9,6 @@ import unittest # noqa: F401
from trezor import utils # noqa: F401
from apps.common.paths import HARDENED
COMMON_FIXTURES_DIR='/'.join(__file__.split('/')[0:-1])+"/../../common/tests/fixtures"
def H_(x: int) -> int:
"""
Shortcut function that "hardens" a number in a BIP44 path.

View File

@ -1,20 +1,14 @@
import uio
from ubinascii import unhexlify # noqa: F401
from apps.ethereum import networks, tokens
from common import COMMON_FIXTURES_DIR
from trezor import messages
from trezor import messages, protobuf
from trezor.enums import EthereumDefinitionType
from trezor.crypto.curve import ed25519
from trezor.crypto.hashlib import sha256
FIXTURES_DEFINITIONS_DIR=COMMON_FIXTURES_DIR + "/ethereum/definitions-latest"
# following constants for "DEFS" were copied from "../../python/src/trezorlib/ethereum.py"
DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE = "by_chain_id"
DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE = "by_slip44"
DEFS_NETWORK_URI_NAME = "network.dat"
DEFS_TOKEN_URI_NAME = "token_{hex_address}.dat"
EXPECTED_FORMAT_VERSION = 1
EXPECTED_DATA_VERSION = 1663054984 # unix epoch time
DEFINITIONS_DEV_PRIVATE_KEY = unhexlify(
"4141414141414141414141414141414141414141414141414141414141414141"
)
NETWORKS = {
@ -34,12 +28,6 @@ NETWORKS = {
}
SLIP44_TO_CHAIN_ID_MAP = {
1: 31,
108: 8,
}
TOKENS = {
# chain_id: { address: token info, address: token info,... }
1: {
@ -95,9 +83,9 @@ def get_reference_ethereum_network_info(chain_id: int | None = None, slip44: int
network = networks.by_slip44(slip44)
if network is None:
cid = SLIP44_TO_CHAIN_ID_MAP.get(slip44)
if cid is not None:
return NETWORKS.get(cid[0])
for _, network in NETWORKS.items():
if network.slip44 == slip44:
return network
return network if network else networks.UNKNOWN_NETWORK
@ -111,48 +99,44 @@ def get_reference_ethereum_token_info(chain_id: int, token_address: str) -> mess
return token if token else tokens.UNKNOWN_TOKEN
def get_encoded_network_definition(
chain_id: int | None = None,
slip44: int | None = None,
) -> bytes | None:
if not ((chain_id is None) != (slip44 is None)): # not XOR
raise ValueError(
"Exactly one of chain_id or slip44 parameters are needed to construct network definition path."
)
path = ""
if chain_id is not None:
path = "/".join([FIXTURES_DEFINITIONS_DIR, DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE, str(chain_id),DEFS_NETWORK_URI_NAME])
def _serialize_eth_info(
info: messages.EthereumNetworkInfo | messages.EthereumTokenInfo,
data_type_num: messages.EthereumDefinitionType,
timestamp: int | None = None,
) -> bytes:
ser = b"trzd1"
ser += data_type_num.to_bytes(1, "big")
if timestamp is not None:
ser += timestamp.to_bytes(4, "big")
else:
path = "/".join([FIXTURES_DEFINITIONS_DIR, DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE, str(slip44), DEFS_NETWORK_URI_NAME])
# set data version to max to avoid "outdated" definition errors
ser += b'\xff' * 4
return _get_definition_from_path(path)
# serialize message
length = protobuf.encoded_length(info)
buffer = bytearray(length)
protobuf.encode(buffer, info)
# write the length of encoded protobuf message
ser += length.to_bytes(2, "big")
ser += buffer
# add Merkle tree proof length and signature
hash = sha256(b"\x00" + ser).digest()
ser += b'\x00'
ser += ed25519.sign(DEFINITIONS_DEV_PRIVATE_KEY, hash)
return ser
def get_encoded_network_definition(
network_info: messages.EthereumNetworkInfo,
timestamp: int | None = None,
) -> bytes:
return _serialize_eth_info(network_info, EthereumDefinitionType.NETWORK, timestamp)
def get_encoded_token_definition(
chain_id: int = None,
token_address: str = None,
) -> bytes | None:
if chain_id is None or token_address is None:
raise ValueError(
"Both chain_id and token_address parameters are needed to construct token definition path."
)
addr = token_address.lower()
if addr.startswith("0x"):
addr = addr[2:]
path = "/".join([FIXTURES_DEFINITIONS_DIR, DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE, str(chain_id), DEFS_TOKEN_URI_NAME.format(hex_address=addr)])
return _get_definition_from_path(path)
def _get_definition_from_path(
path: str,
) -> bytes | None:
try:
with uio.open(path, mode="rb") as f:
b = f.read()
return b
except OSError:
return None
token_info: messages.EthereumTokenInfo,
timestamp: int | None = None,
) -> bytes:
return _serialize_eth_info(token_info, EthereumDefinitionType.TOKEN, timestamp)

View File

@ -12,13 +12,9 @@ if not utils.BITCOIN_ONLY:
EthereumDefinitions,
EthereumNetworkInfo,
EthereumTokenInfo,
EthereumGetAddress,
EthereumGetPublicKey,
EthereumSignMessage,
EthereumSignTx,
EthereumSignTxEIP1559,
EthereumSignTypedData,
EthereumVerifyMessage,
)
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
@ -31,19 +27,20 @@ class TestDecodeDefinition(unittest.TestCase):
# successful decode network
def test_network_definition(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
self.assertEqual(dfs.decode_definition(ubiq_network_encoded, EthereumNetworkInfo), ubiq_network)
# successful decode token
def test_token_definition(self):
# Sphere Token
sphr_token_encoded = get_encoded_token_definition(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token_encoded = get_encoded_token_definition(sphr_token)
self.assertEqual(dfs.decode_definition(sphr_token_encoded, EthereumTokenInfo), sphr_token)
def test_invalid_data(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
invalid_dataset = []
@ -76,45 +73,58 @@ class TestDecodeDefinition(unittest.TestCase):
dfs.decode_definition(bytes(data), EthereumNetworkInfo)
def test_wrong_requested_type(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
with self.assertRaises(wire.DataError):
dfs.decode_definition(ubiq_network_encoded, EthereumTokenInfo)
def test_outdated_definition(self):
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network, 0)
with self.assertRaises(wire.DataError):
dfs.decode_definition(ubiq_network_encoded, EthereumNetworkInfo)
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestGetAndCheckDefinition(unittest.TestCase):
def test_get_network_definition(self):
eth_network_encoded = get_encoded_network_definition(1)
eth_network = get_reference_ethereum_network_info(1)
eth_network_encoded = get_encoded_network_definition(eth_network)
self.assertEqual(dfs.get_and_check_definition(eth_network_encoded, EthereumNetworkInfo, 1), eth_network)
self.assertEqual(dfs.get_and_check_definition(eth_network_encoded, EthereumNetworkInfo), eth_network)
def test_get_token_definition(self):
aave_token_encoded = get_encoded_token_definition(1, "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
aave_token = get_reference_ethereum_token_info(1, "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
aave_token_encoded = get_encoded_token_definition(aave_token)
self.assertEqual(dfs.get_and_check_definition(aave_token_encoded, EthereumTokenInfo, 1), aave_token)
self.assertEqual(dfs.get_and_check_definition(aave_token_encoded, EthereumTokenInfo), aave_token)
def test_invalid_expected_type(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
with self.assertRaises(wire.DataError):
dfs.get_and_check_definition(ubiq_network_encoded, EthereumTokenInfo, 8)
sphr_token_encoded = get_encoded_token_definition(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token_encoded = get_encoded_token_definition(sphr_token)
with self.assertRaises(wire.DataError):
dfs.get_and_check_definition(sphr_token_encoded, EthereumNetworkInfo, 8)
def test_fail_check_chain_id(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
with self.assertRaises(wire.DataError):
dfs.get_and_check_definition(ubiq_network_encoded, EthereumNetworkInfo, 1)
sphr_token_encoded = get_encoded_token_definition(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token_encoded = get_encoded_token_definition(sphr_token)
with self.assertRaises(wire.DataError):
dfs.get_and_check_definition(sphr_token_encoded, EthereumTokenInfo, 1)
def test_invalid_encoded_definition(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
definition = bytearray(ubiq_network_encoded)
# mangle signature - this should have the same effect as it has in "decode_definition" function
definition[-1] += 1
@ -145,10 +155,10 @@ class TestEthereumDefinitions(unittest.TestCase):
eth_network = get_reference_ethereum_network_info(1)
aave_token = get_reference_ethereum_token_info(1, "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
# not built-in
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
sphr_token_encoded = get_encoded_token_definition(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token_encoded = get_encoded_token_definition(sphr_token)
calls_params = [
# no network
@ -182,13 +192,15 @@ class TestEthereumDefinitions(unittest.TestCase):
def test_get_definitions_chain_id_mismatch(self):
# built-in
eth_network_encoded = get_encoded_network_definition(1)
eth_network = get_reference_ethereum_network_info(1)
aave_token_encoded = get_encoded_token_definition(1, "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
eth_network_encoded = get_encoded_network_definition(eth_network)
aave_token = get_reference_ethereum_token_info(1, "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
aave_token_encoded = get_encoded_token_definition(aave_token)
# not built-in
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
sphr_token_encoded = get_encoded_token_definition(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token_encoded = get_encoded_token_definition(sphr_token)
# these variations should have the same result - error on chain id check in encoded definition
calls_params = [
@ -226,10 +238,10 @@ class TestGetDefinitonsFromMsg(unittest.TestCase):
eth_network = get_reference_ethereum_network_info(1)
aave_token = get_reference_ethereum_token_info(1, "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
# not built-in
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
sphr_token_encoded = get_encoded_token_definition(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
sphr_token_encoded = get_encoded_token_definition(sphr_token)
def create_EthereumSignTx_msg(**kwargs):
return EthereumSignTx(
@ -300,8 +312,8 @@ class TestGetDefinitonsFromMsg(unittest.TestCase):
self.get_and_compare_ethereum_definitions(*params)
def test_EthereumSignTypedData_message(self):
ubiq_network_encoded = get_encoded_network_definition(8)
ubiq_network = get_reference_ethereum_network_info(8)
ubiq_network_encoded = get_encoded_network_definition(ubiq_network)
sphr_token = get_reference_ethereum_token_info(8, "20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6")
msg = EthereumSignTypedData(

View File

@ -18,6 +18,7 @@ if not utils.BITCOIN_ONLY:
from ethereum_common import (
construct_network_info,
get_encoded_network_definition,
get_reference_ethereum_network_info,
)
from trezor.messages import (
@ -106,7 +107,7 @@ class TestEthereumKeychain(unittest.TestCase):
wire.DUMMY_CONTEXT,
EthereumGetAddress(
address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED],
encoded_network=get_encoded_network_definition(slip44=60),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(slip44=60)),
),
)
)
@ -115,7 +116,7 @@ class TestEthereumKeychain(unittest.TestCase):
wire.DUMMY_CONTEXT,
EthereumGetAddress(
address_n=[44 | HARDENED, 108 | HARDENED, 0 | HARDENED],
encoded_network=get_encoded_network_definition(slip44=108),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(slip44=108)),
),
)
)
@ -142,7 +143,7 @@ class TestEthereumKeychain(unittest.TestCase):
EthereumSignTypedData(
primary_type="",
address_n=[44 | HARDENED, 60 | HARDENED, 0 | HARDENED],
encoded_network=get_encoded_network_definition(slip44=60),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(slip44=60)),
),
)
)
@ -162,7 +163,7 @@ class TestEthereumKeychain(unittest.TestCase):
EthereumSignTypedData(
primary_type="",
address_n=[44 | HARDENED, 108 | HARDENED, 0 | HARDENED],
encoded_network=get_encoded_network_definition(slip44=108),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(slip44=108)),
),
)
)
@ -197,7 +198,7 @@ class TestEthereumKeychain(unittest.TestCase):
gas_price=b"",
gas_limit=b"",
definitions=EthereumDefinitions(
encoded_network=get_encoded_network_definition(chain_id=1),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(chain_id=1)),
encoded_token=None,
),
),
@ -225,7 +226,7 @@ class TestEthereumKeychain(unittest.TestCase):
gas_price=b"",
gas_limit=b"",
definitions=EthereumDefinitions(
encoded_network=get_encoded_network_definition(chain_id=61),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(chain_id=61)),
encoded_token=None,
),
),
@ -243,7 +244,7 @@ class TestEthereumKeychain(unittest.TestCase):
gas_price=b"",
gas_limit=b"",
definitions=EthereumDefinitions(
encoded_network=get_encoded_network_definition(chain_id=61),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(chain_id=61)),
encoded_token=None,
),
),
@ -260,7 +261,7 @@ class TestEthereumKeychain(unittest.TestCase):
gas_price=b"",
gas_limit=b"",
definitions=EthereumDefinitions(
encoded_network=get_encoded_network_definition(chain_id=2),
encoded_network=get_encoded_network_definition(get_reference_ethereum_network_info(chain_id=2)),
encoded_token=None,
),
),

View File

@ -71,8 +71,6 @@ ETHER_UNITS = {
# So that we can import the web3 library only when really used and reuse the instance
_WEB3_INSTANCE: Optional["web3.Web3"] = None
DEFS_ZIP_FILENAME = "definitions-latest.zip"
def _print_eth_dependencies_and_die() -> NoReturn:
click.echo("Ethereum requirements not installed.")
@ -271,7 +269,7 @@ def cli() -> None:
type=click.Path(
resolve_path=True, dir_okay=False, writable=True, path_type=pathlib.Path
),
default=f"./{DEFS_ZIP_FILENAME}",
default=f"./{ethereum.DEFS_ZIP_FILENAME}",
help="File path to use to save downloaded definitions. Existing file will be overwritten!",
)
def download_definitions(outfile: pathlib.Path) -> None:
@ -281,7 +279,7 @@ def download_definitions(outfile: pathlib.Path) -> None:
# TODO: change once we know the urls
archived_definitions = ethereum.download_from_url(
"https://data.trezor.io/eth_definitions/" + DEFS_ZIP_FILENAME
ethereum.DEFS_BASE_URL + ethereum.DEFS_ZIP_FILENAME
)
# save

View File

@ -31,7 +31,10 @@ if TYPE_CHECKING:
# 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/"
DEFS_URL_LOOKUP_TEMPLATE = DEFS_BASE_URL + "{lookup_type}/{id}/{name}"
DEFS_ZIP_FILENAME = "definitions-latest.zip"
DEFS_ZIP_TOPLEVEL_DIR = pathlib.PurePath("definitions-latest")
DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE = "by_chain_id"
DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE = "by_slip44"
@ -172,13 +175,13 @@ def get_network_definition_url(
)
if chain_id is not None:
return DEFS_BASE_URL.format(
return DEFS_URL_LOOKUP_TEMPLATE.format(
lookup_type=DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE,
id=chain_id,
name=DEFS_NETWORK_URI_NAME,
)
else:
return DEFS_BASE_URL.format(
return DEFS_URL_LOOKUP_TEMPLATE.format(
lookup_type=DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE,
id=slip44,
name=DEFS_NETWORK_URI_NAME,
@ -195,7 +198,7 @@ def get_token_definition_url(chain_id: int = None, token_address: str = None) ->
if addr.startswith("0x"):
addr = addr[2:]
return DEFS_BASE_URL.format(
return DEFS_URL_LOOKUP_TEMPLATE.format(
lookup_type=DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE,
id=chain_id,
name=DEFS_TOKEN_URI_NAME.format(hex_address=addr),

View File

@ -42,7 +42,7 @@ class Node:
"`left` and `right` value must not be None when not using `raw_value`."
)
self.hash = None
self.hash: Optional[bytes] = None
self.left_child = left
self.right_child = right
self.proof_list: List[bytes] = []
@ -59,16 +59,24 @@ class Node:
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"]
self.left_child.add_to_proof_list(right_hash) # type: ignore ["add_to_proof_list" is not a known member of "None"]
self.right_child.add_to_proof_list(left_hash) # type: ignore ["add_to_proof_list" is not a known member of "None"]
return self.hash
def add_to_proof(self, proof: bytes) -> None:
def add_to_proof_list(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"]
self.left_child.add_to_proof_list(proof) # type: ignore ["add_to_proof_list" is not a known member of "None"]
self.right_child.add_to_proof_list(proof) # type: ignore ["add_to_proof_list" is not a known member of "None"]
def get_proof_list(self) -> List[bytes]:
return self.proof_list
def get_hash(self) -> Optional[bytes]:
if self.hash is None:
self.compute_hash()
return self.hash
class MerkleTree:
@ -78,6 +86,11 @@ class MerkleTree:
"""
def __init__(self, values: List[bytes]) -> None:
if values is None or len(values) < 1:
raise ValueError(
"`left` and `right` value must not be None when not using `raw_value`."
)
self.leaves = [Node(raw_value=v) for v in values]
self.height = 0
@ -105,11 +118,13 @@ class MerkleTree:
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
n.raw_value: n.get_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"]
return self.root_node.get_hash() # type: ignore [Expression of type "bytes | None" cannot be assigned to return type "bytes"]

View File

@ -0,0 +1,216 @@
# 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 pytest
from hashlib import sha256
from typing import Dict, List
from trezorlib.merkle_tree import MerkleTree, Node
NODE_VECTORS = (
( # leaf node
# node
Node(
raw_value=bytes.fromhex("dead"),
),
# expected hash
sha256(b"\x00" + bytes.fromhex("dead")).digest()
),
( # node with leaf nodes
# node
Node(
left=Node(
raw_value=bytes.fromhex("dead"),
),
right=Node(
raw_value=bytes.fromhex("beef"),
),
),
# expected hash
sha256(
b"\x01"
+ sha256(b"\x00" + bytes.fromhex("beef")).digest()
+ sha256(b"\x00" + bytes.fromhex("dead")).digest()
).digest()
),
( # node with parent node (left)
# node
Node(
left=Node(
raw_value=bytes.fromhex("dead"),
),
right=Node(
raw_value=bytes.fromhex("beef"),
),
).left_child,
# expected hash
sha256(b"\x00" + bytes.fromhex("dead")).digest()
),
( # node with parent node (right)
# node
Node(
left=Node(
raw_value=bytes.fromhex("dead"),
),
right=Node(
raw_value=bytes.fromhex("beef"),
),
).right_child,
# expected hash
sha256(b"\x00" + bytes.fromhex("beef")).digest()
),
)
NODE_FAILED_VECTORS = (
( # no inputs
# left
None,
# right
None,
# raw_value
None,
),
( # only left child
# left
Node(raw_value=bytes.fromhex("dead")),
# right
None,
# raw_value
None,
),
( # only right child
# left
None,
# right
Node(raw_value=bytes.fromhex("beef")),
# raw_value
None,
),
( # all inputs
# left
Node(raw_value=bytes.fromhex("dead")),
# right
Node(raw_value=bytes.fromhex("beef")),
# raw_value
bytes.fromhex("deadbeef"),
),
)
MERKLE_TREE_VECTORS = (
( # one value
# values
[bytes.fromhex("dead")],
# expected root hash
sha256(b"\x00" + bytes.fromhex("dead")).digest(),
# expected tree height
0,
# expected dict of proof lists
{
bytes.fromhex("dead"): [],
},
),
( # two values
# values
[bytes.fromhex("dead"), bytes.fromhex("beef")],
# expected root hash
sha256(
b"\x01"
+ sha256(b"\x00" + bytes.fromhex("beef")).digest()
+ sha256(b"\x00" + bytes.fromhex("dead")).digest()
).digest(),
# expected tree height
1,
# expected dict of proof lists
{
bytes.fromhex("dead"): [sha256(b"\x00" + bytes.fromhex("beef")).digest()],
bytes.fromhex("beef"): [sha256(b"\x00" + bytes.fromhex("dead")).digest()],
},
),
( # three values
# values
[bytes.fromhex("dead"), bytes.fromhex("beef"), bytes.fromhex("cafe")],
# expected root hash
sha256(
b"\x01"
+ sha256(
b"\x01"
+ sha256(b"\x00" + bytes.fromhex("cafe")).digest()
+ sha256(b"\x00" + bytes.fromhex("beef")).digest()
).digest()
+ sha256(b"\x00" + bytes.fromhex("dead")).digest()
).digest(),
# expected tree height
2,
# expected dict of proof lists
{
bytes.fromhex("dead"): [
sha256(
b"\x01"
+ sha256(b"\x00" + bytes.fromhex("cafe")).digest()
+ sha256(b"\x00" + bytes.fromhex("beef")).digest()
).digest()
],
bytes.fromhex("beef"): [
sha256(b"\x00" + bytes.fromhex("cafe")).digest(),
sha256(b"\x00" + bytes.fromhex("dead")).digest()
],
bytes.fromhex("cafe"): [
sha256(b"\x00" + bytes.fromhex("beef")).digest(),
sha256(b"\x00" + bytes.fromhex("dead")).digest()
],
},
),
)
MERKLE_TREE_FAILED_VECTORS = (
( # no values
# values
[],
),
)
@pytest.mark.parametrize("node, expected_hash", NODE_VECTORS)
def test_node(node: Node, expected_hash: bytes) -> None:
assert node.compute_hash() == expected_hash
assert node.get_hash() == expected_hash
@pytest.mark.parametrize("left, right, raw_value", NODE_FAILED_VECTORS)
def test_node_failed(left: Node, right: Node, raw_value: bytes) -> None:
with pytest.raises(ValueError):
Node(
left=left,
right=right,
raw_value=raw_value,
)
@pytest.mark.parametrize("values, expected_root_hash, expected_height, expected_proofs", MERKLE_TREE_VECTORS)
def test_tree(values: List[bytes], expected_root_hash: bytes, expected_height: int, expected_proofs: Dict[bytes, List[bytes]]) -> None:
mt = MerkleTree(values)
assert mt.get_root_hash() == expected_root_hash
assert mt.get_tree_height() == expected_height
assert mt.get_proofs() == expected_proofs
@pytest.mark.parametrize("values", MERKLE_TREE_FAILED_VECTORS)
def test_tree_failed(values: List[bytes]) -> None:
with pytest.raises(ValueError):
MerkleTree(values)

View File

@ -20,7 +20,6 @@ import pathlib
import click
from trezorlib import ethereum
from trezorlib.cli import ethereum as ethereum_cli
@click.command()
@ -29,7 +28,7 @@ from trezorlib.cli import ethereum as ethereum_cli
type=click.Path(
exists=True, dir_okay=False, resolve_path=True, path_type=pathlib.Path
),
default=f"./{ethereum_cli.DEFS_ZIP_FILENAME}",
default=f"./{ethereum.DEFS_ZIP_FILENAME}",
help="Zip file with stored definitions. Zip file could be obtained using command"
"`trezorctl ethereum download-definitions`.",
)

View File

@ -0,0 +1,187 @@
import io # noqa: F401
from binascii import unhexlify
from hashlib import sha256
from typing import Optional, Union
import ed25519
from trezorlib import messages, protobuf
DEFINITIONS_DEV_PRIVATE_KEY = unhexlify(
"4141414141414141414141414141414141414141414141414141414141414141"
)
NETWORKS = {
# chain_id: network info
1: messages.EthereumNetworkInfo(
chain_id=1,
slip44=60,
shortcut="ETH",
name="Ethereum",
),
8: messages.EthereumNetworkInfo(
chain_id=8,
slip44=108,
shortcut="UBQ",
name="Ubiq",
),
31: messages.EthereumNetworkInfo(
chain_id=31,
slip44=1,
shortcut="tRBTC",
name="RSK Testnet",
),
60: messages.EthereumNetworkInfo(
chain_id=60,
slip44=6060,
shortcut="GO",
name="GoChain",
),
61: messages.EthereumNetworkInfo(
chain_id=61,
slip44=61,
shortcut="ETC",
name="Ethereum Classic",
),
888: messages.EthereumNetworkInfo(
chain_id=888,
slip44=5718350,
shortcut="WAN",
name="Wanchain",
),
28945486: messages.EthereumNetworkInfo(
chain_id=28945486,
slip44=344,
shortcut="AUX",
name="Auxilium Network",
),
3125659152: messages.EthereumNetworkInfo(
chain_id=3125659152,
slip44=164,
shortcut="PIRL",
name="Pirl",
),
11297108109: messages.EthereumNetworkInfo(
chain_id=11297108109,
slip44=60,
shortcut="PALM",
name="Palm",
),
}
TOKENS = {
# chain_id: { address: token info, address: token info,... }
1: {
"d0d6d6c5fe4a677d343cc433536bb717bae167dd": messages.EthereumTokenInfo(
symbol="ADT",
decimals=9,
address=unhexlify("d0d6d6c5fe4a677d343cc433536bb717bae167dd"),
chain_id=1,
name="adChain",
),
"a33e729bf4fdeb868b534e1f20523463d9c46bee": messages.EthereumTokenInfo(
symbol="ICO",
decimals=10,
address=unhexlify("a33e729bf4fdeb868b534e1f20523463d9c46bee"),
chain_id=1,
name="ICO",
),
"dac17f958d2ee523a2206206994597c13d831ec7": messages.EthereumTokenInfo(
symbol="USDT",
decimals=6,
address=unhexlify("dac17f958d2ee523a2206206994597c13d831ec7"),
chain_id=1,
name="Tether",
),
},
8: {
"20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6": messages.EthereumTokenInfo(
symbol="SPHR",
decimals=8,
address=unhexlify("20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6"),
chain_id=8,
name="Sphere",
),
},
}
def get_reference_ethereum_network_info(
chain_id: Optional[int] = None, slip44: Optional[int] = None
) -> Optional[messages.EthereumNetworkInfo]:
if not (chain_id is None) != (slip44 is None): # not XOR
raise ValueError("chain_id and slip44 arguments are exclusive")
# resolve network
if chain_id is not None:
return NETWORKS.get(chain_id)
else: # slip44 is not None
for _, network in NETWORKS.items():
if network.slip44 == slip44:
return network
return None
def get_reference_ethereum_token_info(
chain_id: int, token_address: str
) -> Optional[messages.EthereumTokenInfo]:
if token_address.startswith("0x"):
token_address = token_address[2:]
return TOKENS.get(chain_id, {}).get(token_address)
def _serialize_eth_info(
info: Union[messages.EthereumNetworkInfo, messages.EthereumTokenInfo],
data_type_num: messages.EthereumDefinitionType,
timestamp: Optional[int] = None,
) -> bytes:
ser = b"trzd1"
ser += data_type_num.to_bytes(1, "big")
if timestamp is not None:
ser += timestamp.to_bytes(4, "big")
else:
# set data version to max to avoid "outdated" definition errors
ser += b"\xff" * 4
# serialize message
buf = io.BytesIO()
protobuf.dump_message(buf, info)
msg = buf.getvalue()
# write the length of encoded protobuf message
ser += len(msg).to_bytes(2, "big")
ser += msg
# add Merkle tree proof length and signature
hash = sha256(b"\x00" + ser).digest()
ser += b"\x00"
ser += ed25519.SigningKey(DEFINITIONS_DEV_PRIVATE_KEY).sign(hash)
return ser
def get_encoded_network_definition(
chain_id: Optional[int] = None,
slip44: Optional[int] = None,
timestamp: Optional[int] = None,
) -> Optional[bytes]:
info = get_reference_ethereum_network_info(chain_id, slip44)
if info is None:
return None
return _serialize_eth_info(info, messages.EthereumDefinitionType.NETWORK, timestamp)
def get_encoded_token_definition(
chain_id: int,
token_address: str,
timestamp: Optional[int] = None,
) -> Optional[bytes]:
info = get_reference_ethereum_token_info(chain_id, token_address)
if info is None:
return None
return _serialize_eth_info(info, messages.EthereumDefinitionType.TOKEN, timestamp)

View File

@ -21,7 +21,8 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import UH_, parse_path
from ...common import COMMON_FIXTURES_DIR, parametrize_using_common_fixtures
from ...common import parametrize_using_common_fixtures
from .ethereum_common import get_encoded_network_definition
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
@ -35,11 +36,8 @@ def test_getaddress(client: Client, parameters, result):
"slip44", encoded_network_slip44
)
encoded_network = ethereum.get_definition_from_zip(
COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip",
ethereum.get_network_definition_path(
slip44=encoded_network_slip44,
),
encoded_network = get_encoded_network_definition(
slip44=encoded_network_slip44,
)
assert (
ethereum.get_address(client, address_n, encoded_network=encoded_network)
@ -47,9 +45,18 @@ def test_getaddress(client: Client, parameters, result):
)
def test_getaddress_missing_extern_definitions(client: Client):
path = "m/44'/6060'/0'/0/0" # GoChain
address_n = parse_path(path)
@parametrize_using_common_fixtures("ethereum/getaddress.failed.json")
def test_getaddress_failed(client: Client, parameters, result):
address_n = parse_path(parameters["path"])
encoded_network_slip44 = UH_(address_n[1])
if "definitions" in parameters:
encoded_network_slip44 = parameters["definitions"].get(
"slip44", encoded_network_slip44
)
with pytest.raises(TrezorFailure, match=r"DataError:.*Forbidden key path"):
ethereum.get_address(client, address_n, encoded_network=None)
encoded_network = get_encoded_network_definition(
slip44=encoded_network_slip44,
)
with pytest.raises(TrezorFailure, match=result["error"]):
ethereum.get_address(client, address_n, encoded_network=encoded_network)

View File

@ -21,12 +21,11 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import UH_, parse_path
from ...common import COMMON_FIXTURES_DIR, parametrize_using_common_fixtures
from ...common import parametrize_using_common_fixtures
from .ethereum_common import get_encoded_network_definition
SHOW_MORE = (143, 167)
DEFS_ZIP_FILE_PATH = COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip"
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
@ -42,11 +41,8 @@ def test_ethereum_sign_typed_data(client: Client, parameters, result):
)
defs = ethereum.messages.EthereumDefinitions(
encoded_network=ethereum.get_definition_from_zip(
DEFS_ZIP_FILE_PATH,
ethereum.get_network_definition_path(
slip44=encoded_network_slip44,
),
encoded_network=get_encoded_network_definition(
slip44=encoded_network_slip44,
)
)
@ -62,20 +58,28 @@ def test_ethereum_sign_typed_data(client: Client, parameters, result):
@pytest.mark.skip_t1
def test_ethereum_sign_typed_data_missing_extern_definitions(client: Client):
path = "m/44'/6060'/0'/0/0" # GoChain
@parametrize_using_common_fixtures("ethereum/sign_typed_data.failed.json")
def test_ethereum_sign_typed_data_failed(client: Client, parameters, result):
address_n = parse_path(parameters["path"])
encoded_network_slip44 = UH_(address_n[1])
if "definitions" in parameters:
encoded_network_slip44 = parameters["definitions"].get(
"slip44", encoded_network_slip44
)
with pytest.raises(TrezorFailure, match=r"DataError: Forbidden key path"):
defs = ethereum.messages.EthereumDefinitions(
encoded_network=get_encoded_network_definition(
slip44=encoded_network_slip44,
)
)
with pytest.raises(TrezorFailure, match=result["error"]):
ethereum.sign_typed_data(
client,
parse_path(path),
{
"types": {"EIP712Domain": []},
"primaryType": "EIP712Domain",
"message": {},
"domain": {},
},
metamask_v4_compat=True,
address_n,
parameters["data"],
metamask_v4_compat=parameters["metamask_v4_compat"],
definitions=defs,
)
@ -90,11 +94,8 @@ def test_ethereum_sign_typed_data_blind(client: Client, parameters, result):
"slip44", encoded_network_slip44
)
encoded_network = ethereum.get_definition_from_zip(
DEFS_ZIP_FILE_PATH,
ethereum.get_network_definition_path(
slip44=encoded_network_slip44,
),
encoded_network = get_encoded_network_definition(
slip44=encoded_network_slip44,
)
ret = ethereum.sign_typed_data_hash(
client,
@ -111,18 +112,29 @@ def test_ethereum_sign_typed_data_blind(client: Client, parameters, result):
@pytest.mark.skip_t2
def test_ethereum_sign_typed_data_blind_missing_extern_definitions(client: Client):
path = "m/44'/6060'/0'/0/0" # GoChain
@parametrize_using_common_fixtures("ethereum/sign_typed_data.failed.json")
def test_ethereum_sign_typed_data_blind_failed(client: Client, parameters, result):
address_n = parse_path(parameters["path"])
encoded_network_slip44 = UH_(address_n[1])
if "definitions" in parameters:
encoded_network_slip44 = parameters["definitions"].get(
"slip44", encoded_network_slip44
)
with pytest.raises(TrezorFailure, match=r"DataError:.*Forbidden key path"):
encoded_network = get_encoded_network_definition(
slip44=encoded_network_slip44,
)
with pytest.raises(TrezorFailure, match=result["error"]):
ethereum.sign_typed_data_hash(
client,
parse_path(path),
ethereum.decode_hex(
"0x6192106f129ce05c9075d319c1fa6ea9b3ae37cbd0c1ef92e2be7137bb07baa1"
),
None,
encoded_network=None,
address_n,
ethereum.decode_hex(parameters["domain_separator_hash"]),
# message hash is empty for domain-only hashes
ethereum.decode_hex(parameters["message_hash"])
if parameters["message_hash"]
else None,
encoded_network=encoded_network,
)
@ -209,11 +221,8 @@ def input_flow_cancel(client: Client):
@pytest.mark.skip_t1
def test_ethereum_sign_typed_data_show_more_button(client: Client):
defs = ethereum.messages.EthereumDefinitions(
encoded_network=ethereum.get_definition_from_zip(
DEFS_ZIP_FILE_PATH,
ethereum.get_network_definition_path(
slip44=60,
),
encoded_network=get_encoded_network_definition(
slip44=60,
)
)
@ -232,11 +241,8 @@ def test_ethereum_sign_typed_data_show_more_button(client: Client):
@pytest.mark.skip_t1
def test_ethereum_sign_typed_data_cancel(client: Client):
defs = ethereum.messages.EthereumDefinitions(
encoded_network=ethereum.get_definition_from_zip(
DEFS_ZIP_FILE_PATH,
ethereum.get_network_definition_path(
slip44=60,
),
encoded_network=get_encoded_network_definition(
slip44=60,
)
)

View File

@ -21,7 +21,8 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import UH_, parse_path
from ...common import COMMON_FIXTURES_DIR, parametrize_using_common_fixtures
from ...common import parametrize_using_common_fixtures
from .ethereum_common import get_encoded_network_definition
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
@ -35,24 +36,32 @@ def test_signmessage(client: Client, parameters, result):
"slip44", encoded_network_slip44
)
encoded_network = ethereum.get_definition_from_zip(
COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip",
ethereum.get_network_definition_path(
slip44=encoded_network_slip44,
),
encoded_network = get_encoded_network_definition(
slip44=encoded_network_slip44,
)
res = ethereum.sign_message(client, address_n, parameters["msg"], encoded_network)
assert res.address == result["address"]
assert res.signature.hex() == result["sig"]
def test_signmessage_missing_extern_definitions(client: Client):
path = "m/44'/6060'/0'/0/0" # GoChain
address_n = parse_path(path)
msg = "This is an example of a signed message."
@parametrize_using_common_fixtures("ethereum/signmessage.failed.json")
def test_signmessage_failed(client: Client, parameters, result):
address_n = parse_path(parameters["path"])
encoded_network_slip44 = UH_(address_n[1])
if "definitions" in parameters:
encoded_network_slip44 = parameters["definitions"].get(
"slip44", encoded_network_slip44
)
with pytest.raises(TrezorFailure, match=r"DataError:.*Forbidden key path"):
ethereum.sign_message(client, address_n, msg, encoded_network=None)
encoded_network = get_encoded_network_definition(
slip44=encoded_network_slip44,
)
with pytest.raises(TrezorFailure, match=result["error"]):
ethereum.sign_message(
client, address_n, parameters["msg"], encoded_network=encoded_network
)
@parametrize_using_common_fixtures("ethereum/verifymessage.json")

View File

@ -14,7 +14,9 @@
# 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 functools import partial
import pathlib
import tempfile
from typing import Optional
import pytest
@ -23,42 +25,69 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client, message_filters
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path
from ...common import COMMON_FIXTURES_DIR, parametrize_using_common_fixtures
from ...common import parametrize_using_common_fixtures
from .ethereum_common import (
get_encoded_network_definition,
get_encoded_token_definition,
)
TO_ADDR = "0x1d1c328764a41bda0492b66baa30c4a339ff85ef"
SHOW_ALL = (143, 167)
GO_BACK = (16, 220)
DEFS_ZIP_FILE_PATH = COMMON_FIXTURES_DIR / "ethereum" / "definitions-latest.zip"
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
ONLINE_DEFS_ZIP_FILE = tempfile.NamedTemporaryFile()
with open(ONLINE_DEFS_ZIP_FILE.name, mode="w+b") as tf:
try:
tf.write(
ethereum.download_from_url(
ethereum.DEFS_BASE_URL + ethereum.DEFS_ZIP_FILENAME
)
)
except RuntimeError:
pass
def get_definitions(
parameters: dict,
zip_file: Optional[pathlib.Path] = None,
) -> messages.EthereumDefinitions:
chain_id = parameters["chain_id"]
token_chain_id = parameters["chain_id"]
token_address = parameters.get("to_address")
timestamp = None
if "definitions" in parameters:
chain_id = parameters["definitions"].get("chain_id", chain_id)
token_chain_id = parameters["definitions"].get("token_chain_id", token_chain_id)
token_address = parameters["definitions"].get("to_address", token_address)
timestamp = parameters["definitions"].get("timestamp")
encoded_network = ethereum.get_definition_from_zip(
DEFS_ZIP_FILE_PATH,
ethereum.get_network_definition_path(
chain_id,
),
)
encoded_token = ethereum.get_definition_from_zip(
DEFS_ZIP_FILE_PATH,
ethereum.get_token_definition_path(
token_chain_id,
token_address,
),
)
if zip_file:
encoded_network = ethereum.get_definition_from_zip(
zip_file=zip_file,
path_inside_zip=ethereum.get_network_definition_path(
chain_id=chain_id,
),
)
encoded_token = ethereum.get_definition_from_zip(
zip_file=zip_file,
path_inside_zip=ethereum.get_token_definition_path(
chain_id=token_chain_id,
token_address=token_address,
),
)
else:
encoded_network = get_encoded_network_definition(
chain_id=chain_id,
timestamp=timestamp,
)
encoded_token = get_encoded_token_definition(
chain_id=token_chain_id,
token_address=token_address,
timestamp=timestamp,
)
return messages.EthereumDefinitions(
encoded_network=encoded_network, encoded_token=encoded_token
@ -93,68 +122,54 @@ def test_signtx(client: Client, parameters, result):
assert sig_v == result["sig_v"]
def test_signtx_missing_or_wrong_extern_definitions(client: Client):
chain_id = 8 # Ubiq
to_address = "0x20e3dd746ddf519b23ffbbb6da7a5d33ea6349d6" # Sphere
# TODO: remove xfail when definitions becomes available online
@pytest.mark.xfail(reason="Temporary until definitions becomes available online.")
@parametrize_using_common_fixtures(
"ethereum/sign_tx.json",
"ethereum/sign_tx_eip155.json",
"ethereum/sign_tx_definitions.json",
)
def test_signtx_online_definitions(client: Client, parameters, result):
with client:
sig_v, sig_r, sig_s = ethereum.sign_tx(
client,
n=parse_path(parameters["path"]),
nonce=int(parameters["nonce"], 16),
gas_price=int(parameters["gas_price"], 16),
gas_limit=int(parameters["gas_limit"], 16),
to=parameters["to_address"],
chain_id=parameters["chain_id"],
value=int(parameters["value"], 16),
tx_type=parameters["tx_type"],
data=bytes.fromhex(parameters["data"]),
definitions=get_definitions(
parameters, pathlib.Path(ONLINE_DEFS_ZIP_FILE.name)
),
)
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",
)
expected_v = 2 * parameters["chain_id"] + 35
assert sig_v in (expected_v, expected_v + 1)
assert sig_r.hex() == result["sig_r"]
assert sig_s.hex() == result["sig_s"]
assert sig_v == result["sig_v"]
definitions_vector = [
get_definitions(
{
"chain_id": chain_id,
"to_address": to_address,
"definitions": {
"chain_id": -1,
},
}
), # missing network
get_definitions(
{
"chain_id": -1,
"to_address": "",
}
), # missing network and token
get_definitions(
{
"chain_id": chain_id,
"to_address": to_address,
"definitions": {
"chain_id": 1,
},
}
), # wrong network
get_definitions(
{
"chain_id": 1,
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
}
), # 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:
with pytest.raises(TrezorFailure, match=r"DataError"):
sign_tx_call(definitions=definitions)
@parametrize_using_common_fixtures("ethereum/sign_tx_definitions.failed.json")
def test_signtx_failed(client: Client, parameters, result):
with pytest.raises(TrezorFailure, match=result["error"]):
ethereum.sign_tx(
client,
n=parse_path(parameters["path"]),
nonce=int(parameters["nonce"], 16),
gas_price=int(parameters["gas_price"], 16),
gas_limit=int(parameters["gas_limit"], 16),
to=parameters["to_address"],
chain_id=parameters["chain_id"],
value=int(parameters["value"], 16),
tx_type=parameters["tx_type"],
data=bytes.fromhex(parameters["data"]),
definitions=get_definitions(parameters),
)
@parametrize_using_common_fixtures(
@ -182,69 +197,22 @@ def test_signtx_eip1559(client: Client, parameters, result):
assert sig_v == result["sig_v"]
def test_signtx_eip1559_missing_or_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(
{
"chain_id": chain_id,
"to_address": to_address,
"definitions": {
"chain_id": -1,
},
}
), # missing network
get_definitions(
{
"chain_id": -1,
"to_address": "",
}
), # missing network and token
get_definitions(
{
"chain_id": chain_id,
"to_address": to_address,
"definitions": {
"chain_id": 1,
},
}
), # wrong network
get_definitions(
{
"chain_id": 1,
"to_address": "0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
}
), # 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:
with pytest.raises(TrezorFailure, match=r"DataError"):
sign_tx_eip1559_call(definitions=definitions)
@parametrize_using_common_fixtures("ethereum/sign_tx_eip1559_definitions.failed.json")
def test_signtx_eip1559_failed(client: Client, parameters, result):
with pytest.raises(TrezorFailure, match=result["error"]):
ethereum.sign_tx_eip1559(
client,
n=parse_path(parameters["path"]),
nonce=int(parameters["nonce"], 16),
gas_limit=int(parameters["gas_limit"], 16),
max_gas_fee=int(parameters["max_gas_fee"], 16),
max_priority_fee=int(parameters["max_priority_fee"], 16),
to=parameters["to_address"],
chain_id=parameters["chain_id"],
value=int(parameters["value"], 16),
data=bytes.fromhex(parameters["data"]),
definitions=get_definitions(parameters),
)
def test_sanity_checks(client: Client):

View File

@ -359,7 +359,7 @@
"T1_ethereum-test_sign_typed_data.py::test_ethereum_sign_typed_data_blind[struct_list_non_v4]": "eb17b48c8495ae235345920089c1662f5aa36fbb6e90f0c0c4b270c7bb2f2ea8",
"T1_ethereum-test_sign_typed_data.py::test_ethereum_sign_typed_data_blind[struct_list_v4]": "b0ce2ac7326df63ae12badf47a58db955fe2b17a327fcdc6eee1fd06375a25f7",
"T1_ethereum-test_sign_typed_data.py::test_ethereum_sign_typed_data_blind[structs_arrays_v4]": "a38f3ef2571424b460cd7351cd7263c8fc19684041d57f32f5599588c89046bf",
"T1_ethereum-test_sign_typed_data.py::test_ethereum_sign_typed_data_blind_missing_extern_definitions": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_ethereum-test_sign_typed_data.py::test_ethereum_sign_typed_data_blind_failed[missing_extern-610f2126": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_ethereum-test_sign_verify_message.py::test_signmessage[builtin_Ethereum_do_not_send_network]": "0f1e81d57996381781e8e3c5865875e6d48c29128d2de78f33556d96f29c2785",
"T1_ethereum-test_sign_verify_message.py::test_signmessage[builtin_Ethereum_send_wrong_network]": "0f1e81d57996381781e8e3c5865875e6d48c29128d2de78f33556d96f29c2785",
"T1_ethereum-test_sign_verify_message.py::test_signmessage[extern_GoChain_send_network]": "a72aad838b023cce57d15c42f0e55da11e30ca965e42cab2e5019d6636dde47d",
@ -371,7 +371,7 @@
"T1_ethereum-test_sign_verify_message.py::test_signmessage[parameters7-result7]": "06a642eaad886c01421973efa714c59eb6da8e60d06fc44cc42a5f959ec3dd27",
"T1_ethereum-test_sign_verify_message.py::test_signmessage[parameters8-result8]": "a6b54a1244c1c457de732c2d27371475c02e451a4820f283912a5e3c62fe2842",
"T1_ethereum-test_sign_verify_message.py::test_signmessage[parameters9-result9]": "858c5f14ecf61cbc85acb7d11f85b65a88c3d14f2591f185e1925092a977a7d6",
"T1_ethereum-test_sign_verify_message.py::test_signmessage_missing_extern_definitions": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_ethereum-test_sign_verify_message.py::test_signmessage_failed[missing_extern_definition_GoChain]": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_ethereum-test_sign_verify_message.py::test_verify[parameters0-result0]": "cdec0f79f2abbd90f4346494037f7bb4dd4dccc7c6739b497873d0c5603f2a26",
"T1_ethereum-test_sign_verify_message.py::test_verify[parameters1-result1]": "9e51345ff0d3736f81a5c169768f9a80c555187eef2db3046f5ad12262990466",
"T1_ethereum-test_sign_verify_message.py::test_verify[parameters2-result2]": "e2aff8a36dffd76b0e48256a5012ff191d0ce877a42cf31e1f25f7c3cd786d64",
@ -400,13 +400,10 @@
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_builtin_Tether_send_nothing]": "2ad8d58e1c04bfde465902c6edd2308a392a4afeffbeaf5e32e0ef22380acf75",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_builtin_Tether_send_token]": "2ad8d58e1c04bfde465902c6edd2308a392a4afeffbeaf5e32e0ef22380acf75",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_builtin_Tether_wrong_network]": "2ad8d58e1c04bfde465902c6edd2308a392a4afeffbeaf5e32e0ef22380acf75",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_builtin_Tether_wrong_network_token]": "2ad8d58e1c04bfde465902c6edd2308a392a4afeffbeaf5e32e0ef22380acf75",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_builtin_Tether_wrong_token]": "2ad8d58e1c04bfde465902c6edd2308a392a4afeffbeaf5e32e0ef22380acf75",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_extern_adChain_send_network]": "0143f7c4273964eb5eb8113c7e63674c033bebeca7ae619270b7ca77bb6aed66",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_extern_adChain_send_network_token]": "08e9da493a8ac1f4e7f05bbb8f7c2b2fd97481421899a170ac0ead26b0764a21",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_extern_adChain_send_nothing]": "0143f7c4273964eb5eb8113c7e63674c033bebeca7ae619270b7ca77bb6aed66",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_extern_adChain_send_token]": "08e9da493a8ac1f4e7f05bbb8f7c2b2fd97481421899a170ac0ead26b0764a21",
"T1_ethereum-test_signtx.py::test_signtx[builtin_Ethereum_extern_adChain_wrong_token]": "0143f7c4273964eb5eb8113c7e63674c033bebeca7ae619270b7ca77bb6aed66",
"T1_ethereum-test_signtx.py::test_signtx[data_1]": "8b432aba21bc4344814cceaf693e114b9d3e3d6ceb83c3a6af7c3ed0f9b37449",
"T1_ethereum-test_signtx.py::test_signtx[data_2_bigdata]": "445286b7501ca67dd16dafd7ea09c57cc4a37a642ae50f0c812d74353c37c017",
"T1_ethereum-test_signtx.py::test_signtx[known_erc20_token]": "7ef8d3bbf6e299b41522769a893f72b04571fc7176bc93e5c1701a2b5721fb20",