diff --git a/core/src/apps/ethereum/definitions.py b/core/src/apps/ethereum/definitions.py index 8ae583f08..3ec34afee 100644 --- a/core/src/apps/ethereum/definitions.py +++ b/core/src/apps/ethereum/definitions.py @@ -1,17 +1,8 @@ from typing import TYPE_CHECKING -from trezor import protobuf, utils -from trezor.crypto.curve import ed25519 -from trezor.crypto.hashlib import sha256 -from trezor.enums import EthereumDefinitionType from trezor.messages import EthereumNetworkInfo, EthereumTokenInfo from trezor.wire import DataError -from apps.common import readers - -from . import definitions_constants as consts, networks, tokens -from .networks import UNKNOWN_NETWORK - if TYPE_CHECKING: from typing import TypeVar from typing_extensions import Self @@ -20,8 +11,17 @@ if TYPE_CHECKING: def decode_definition(definition: bytes, expected_type: type[DefType]) -> DefType: + from trezor.crypto.cosi import verify as cosi_verify + from trezor.crypto.hashlib import sha256 + from trezor.enums import EthereumDefinitionType + from trezor.protobuf import decode as protobuf_decode + from trezor.utils import BufferReader + + from apps.common import readers + from . import definitions_constants as consts + # check network definition - r = utils.BufferReader(definition) + r = BufferReader(definition) expected_type_number = EthereumDefinitionType.NETWORK # TODO: can't check equality of MsgDefObjs now, so we check the name if expected_type.MESSAGE_NAME == EthereumTokenInfo.MESSAGE_NAME: @@ -59,7 +59,8 @@ def decode_definition(definition: bytes, expected_type: type[DefType]) -> DefTyp hasher.update(hash_b) hash = hasher.digest() - signed_tree_root = r.read_memoryview(64) + sigmask = r.get() + signature = r.read_memoryview(64) if r.remaining_count(): raise DataError("Invalid Ethereum definition") @@ -68,22 +69,18 @@ def decode_definition(definition: bytes, expected_type: type[DefType]) -> DefTyp raise DataError("Invalid Ethereum definition") # verify signature - if not ed25519.verify(consts.DEFINITIONS_PUBLIC_KEY, signed_tree_root, hash): - error_msg = DataError("Invalid definition signature") - if __debug__: - # check against dev key - if not ed25519.verify( - consts.DEFINITIONS_DEV_PUBLIC_KEY, - signed_tree_root, - hash, - ): - raise error_msg - else: - raise error_msg + result = cosi_verify(signature, hash, consts.THRESHOLD, consts.PUBLIC_KEYS, sigmask) + if __debug__: + debug_result = cosi_verify( + signature, hash, consts.THRESHOLD, consts.DEV_PUBLIC_KEYS, sigmask + ) + result = result or debug_result + if not result: + raise DataError("Invalid definition signature") # decode it if it's OK try: - return protobuf.decode(payload, expected_type, True) + return protobuf_decode(payload, expected_type, True) except ValueError: raise DataError("Invalid Ethereum definition") @@ -107,14 +104,16 @@ class Definitions: chain_id: int | None = None, slip44: int | None = None, ) -> Self: + from .networks import UNKNOWN_NETWORK, by_chain_id, by_slip44 + network = UNKNOWN_NETWORK tokens: dict[bytes, EthereumTokenInfo] = {} # if we have a built-in definition, use it if chain_id is not None: - network = networks.by_chain_id(chain_id) + network = by_chain_id(chain_id) elif slip44 is not None: - network = networks.by_slip44(slip44) + network = by_slip44(slip44) else: # ignore encoded definitions if we can't match them to request details return cls(UNKNOWN_NETWORK, {}) @@ -143,12 +142,14 @@ class Definitions: return cls(network, tokens) def get_token(self, address: bytes) -> EthereumTokenInfo: + from .tokens import token_by_chain_address, UNKNOWN_TOKEN + # if we have a built-in definition, use it - token = tokens.token_by_chain_address(self.network.chain_id, address) + token = token_by_chain_address(self.network.chain_id, address) if token is not None: return token if address in self._tokens: return self._tokens[address] - return tokens.UNKNOWN_TOKEN + return UNKNOWN_TOKEN diff --git a/core/src/apps/ethereum/definitions_constants.py b/core/src/apps/ethereum/definitions_constants.py index edc550099..8797c1fab 100644 --- a/core/src/apps/ethereum/definitions_constants.py +++ b/core/src/apps/ethereum/definitions_constants.py @@ -2,13 +2,19 @@ # (by running `make templates` in `core`) # do not edit manually! -from ubinascii import unhexlify +THRESHOLD = 2 +PUBLIC_KEYS = ( + b"\x43\x34\x99\x63\x43\x62\x3e\x46\x2f\x0f\xc9\x33\x11\xfe\xf1\x48\x4c\xa2\x3d\x2f\xf1\xee\xc6\xdf\x1f\xa8\xeb\x7e\x35\x73\xb3\xdb", + b"\xa9\xa2\x2c\xc2\x65\xa0\xcb\x1d\x6c\xb3\x29\xbc\x0e\x60\xbc\x45\xdf\x76\xb9\xab\x28\xfb\x87\xb6\x11\x36\xfe\xaf\x8d\x8f\xdc\x96", + b"\xb8\xd2\xb2\x1d\xe2\x71\x24\xf0\x51\x1f\x90\x3a\xe7\xe6\x0e\x07\x96\x18\x10\xa0\xb8\xf2\x8e\xa7\x55\xfa\x50\x36\x7a\x8a\x2b\x8b", +) -DEFINITIONS_PUBLIC_KEY = b"" MIN_DATA_VERSION = 1669892465 FORMAT_VERSION = b"trzd1" if __debug__: - DEFINITIONS_DEV_PUBLIC_KEY = unhexlify( - "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d" + DEV_PUBLIC_KEYS = ( + b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44", + b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44", + b"\x97\xf7\x13\x5a\x9a\x26\x90\xe7\x3b\xeb\x26\x55\x6f\x1c\xb1\x63\xbe\xa2\x53\x2a\xff\xa1\xe7\x78\x24\x30\xbe\x98\xc0\xe5\x68\x12", ) diff --git a/core/src/apps/ethereum/definitions_constants.py.mako b/core/src/apps/ethereum/definitions_constants.py.mako index 6d2288930..9623dee0c 100644 --- a/core/src/apps/ethereum/definitions_constants.py.mako +++ b/core/src/apps/ethereum/definitions_constants.py.mako @@ -2,13 +2,19 @@ # (by running `make templates` in `core`) # do not edit manually! -from ubinascii import unhexlify +THRESHOLD = 2 +PUBLIC_KEYS = ( + b"\x43\x34\x99\x63\x43\x62\x3e\x46\x2f\x0f\xc9\x33\x11\xfe\xf1\x48\x4c\xa2\x3d\x2f\xf1\xee\xc6\xdf\x1f\xa8\xeb\x7e\x35\x73\xb3\xdb", + b"\xa9\xa2\x2c\xc2\x65\xa0\xcb\x1d\x6c\xb3\x29\xbc\x0e\x60\xbc\x45\xdf\x76\xb9\xab\x28\xfb\x87\xb6\x11\x36\xfe\xaf\x8d\x8f\xdc\x96", + b"\xb8\xd2\xb2\x1d\xe2\x71\x24\xf0\x51\x1f\x90\x3a\xe7\xe6\x0e\x07\x96\x18\x10\xa0\xb8\xf2\x8e\xa7\x55\xfa\x50\x36\x7a\x8a\x2b\x8b", +) -DEFINITIONS_PUBLIC_KEY = b"" MIN_DATA_VERSION = ${ethereum_defs_timestamp} FORMAT_VERSION = b"trzd1" if __debug__: - DEFINITIONS_DEV_PUBLIC_KEY = unhexlify( - "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d" + DEV_PUBLIC_KEYS = ( + b"\x68\x46\x0e\xbe\xf3\xb1\x38\x16\x4e\xc7\xfd\x86\x10\xe9\x58\x00\xdf\x75\x98\xf7\x0f\x2f\x2e\xa7\xdb\x51\x72\xac\x74\xeb\xc1\x44", + b"\x8d\x4a\xbe\x07\x4f\xef\x92\x29\xd3\xb4\x41\xdf\xea\x4f\x98\xf8\x05\xb1\xa2\xb3\xa0\x6a\xe6\x45\x81\x0e\xfe\xce\x77\xfd\x50\x44", + b"\x97\xf7\x13\x5a\x9a\x26\x90\xe7\x3b\xeb\x26\x55\x6f\x1c\xb1\x63\xbe\xa2\x53\x2a\xff\xa1\xe7\x78\x24\x30\xbe\x98\xc0\xe5\x68\x12", ) diff --git a/core/tests/ethereum_common.py b/core/tests/ethereum_common.py index 6d8ee5a29..a8b31c0d6 100644 --- a/core/tests/ethereum_common.py +++ b/core/tests/ethereum_common.py @@ -2,12 +2,11 @@ from ubinascii import unhexlify # noqa: F401 from trezor import messages, protobuf from trezor.enums import EthereumDefinitionType +from trezor.crypto import cosi from trezor.crypto.curve import ed25519 from trezor.crypto.hashlib import sha256 -DEFINITIONS_DEV_PRIVATE_KEY = unhexlify( - "4141414141414141414141414141414141414141414141414141414141414141" -) +PRIVATE_KEYS_DEV = [byte * 32 for byte in (b"\xdd", b"\xde", b"\xdf")] def make_network( @@ -60,7 +59,11 @@ def make_payload( return payload -def sign_payload(payload: bytes, merkle_neighbors: list[bytes]) -> tuple[bytes, bytes]: +def sign_payload( + payload: bytes, + merkle_neighbors: list[bytes], + threshold: int = 3, +) -> tuple[bytes, bytes]: digest = sha256(b"\x00" + payload).digest() merkle_proof = [] for item in merkle_neighbors: @@ -69,8 +72,29 @@ def sign_payload(payload: bytes, merkle_neighbors: list[bytes]) -> tuple[bytes, merkle_proof.append(digest) merkle_proof = len(merkle_proof).to_bytes(1, "little") + b"".join(merkle_proof) - signature = ed25519.sign(DEFINITIONS_DEV_PRIVATE_KEY, digest) - return merkle_proof, signature + + nonces, commits, pubkeys = [], [], [] + for i, private_key in enumerate(PRIVATE_KEYS_DEV[:threshold]): + nonce, commit = cosi.commit() + pubkey = ed25519.publickey(private_key) + nonces.append(nonce) + commits.append(commit) + pubkeys.append(pubkey) + + global_commit = cosi.combine_publickeys(commits) + global_pubkey = cosi.combine_publickeys(pubkeys) + sigmask = 0 + signatures = [] + for i, nonce in enumerate(nonces): + sigmask |= 1 << i + sig = cosi.sign( + PRIVATE_KEYS_DEV[i], digest, nonce, global_commit, global_pubkey + ) + signatures.append(sig) + + signature = cosi.combine_signatures(global_commit, signatures) + sigmask_byte = sigmask.to_bytes(1, "little") + return merkle_proof, sigmask_byte + signature def encode_network( diff --git a/core/tests/test_apps.ethereum.definitions.py b/core/tests/test_apps.ethereum.definitions.py index 85a6395ba..370b616a0 100644 --- a/core/tests/test_apps.ethereum.definitions.py +++ b/core/tests/test_apps.ethereum.definitions.py @@ -56,6 +56,11 @@ class TestDecodeDefinition(unittest.TestCase): bad_signature = signature[:-1] + b"\xff" self.assertFailed(payload + proof + bad_signature) + def test_not_enough_signatures(self): + payload = make_payload() + proof, signature = sign_payload(payload, [], threshold=1) + self.assertFailed(payload + proof + signature) + def test_missing_signature(self): payload = make_payload() proof, signature = sign_payload(payload, [])