diff --git a/core/src/apps/common/readers.py b/core/src/apps/common/readers.py index 27249f6dd7..b4ed9e7a6e 100644 --- a/core/src/apps/common/readers.py +++ b/core/src/apps/common/readers.py @@ -16,3 +16,11 @@ def read_bitcoin_varint(r: BufferReader) -> int: else: raise ValueError return n + + +def read_uint32_be(r: BufferReader) -> int: + n = r.get() << 24 + n += r.get() << 16 + n += r.get() << 8 + n += r.get() + return n diff --git a/core/src/apps/tezos/get_address.py b/core/src/apps/tezos/get_address.py index b3c7399ccd..00e6b6b801 100644 --- a/core/src/apps/tezos/get_address.py +++ b/core/src/apps/tezos/get_address.py @@ -15,7 +15,7 @@ async def get_address(ctx, msg, keychain): node = keychain.derive(msg.address_n) pk = seed.remove_ed25519_prefix(node.public_key()) - pkh = hashlib.blake2b(pk, outlen=20).digest() + pkh = hashlib.blake2b(pk, outlen=helpers.PUBLIC_KEY_HASH_SIZE).digest() address = helpers.base58_encode_check( pkh, prefix=helpers.TEZOS_ED25519_ADDRESS_PREFIX ) diff --git a/core/src/apps/tezos/helpers.py b/core/src/apps/tezos/helpers.py index 00ed87aa0e..93f80a44f1 100644 --- a/core/src/apps/tezos/helpers.py +++ b/core/src/apps/tezos/helpers.py @@ -1,7 +1,10 @@ from micropython import const +from trezor import wire from trezor.crypto import base58 +from trezor.utils import BufferReader, ensure +from apps.common.readers import read_uint32_be from apps.common.writers import write_bytes_unchecked, write_uint8 TEZOS_AMOUNT_DECIMALS = const(6) @@ -49,6 +52,34 @@ MICHELSON_INSTRUCTION_BYTES = { DO_ENTRYPOINT_TAG = const(2) MICHELSON_SEQUENCE_TAG = const(2) +BRANCH_HASH_SIZE = const(32) +PROPOSAL_HASH_SIZE = const(32) +PUBLIC_KEY_HASH_SIZE = const(20) +TAGGED_PUBKEY_HASH_SIZE = 1 + PUBLIC_KEY_HASH_SIZE +CONTRACT_ID_SIZE = const(22) +ED25519_PUBLIC_KEY_SIZE = const(32) +SECP256K1_PUBLIC_KEY_SIZE = const(33) +P256_PUBLIC_KEY_SIZE = const(33) + +PUBLIC_KEY_TAG_TO_SIZE = { + 0: ED25519_PUBLIC_KEY_SIZE, + 1: SECP256K1_PUBLIC_KEY_SIZE, + 2: P256_PUBLIC_KEY_SIZE, +} + +OP_TAG_ENDORSEMENT = const(0) +OP_TAG_SEED_NONCE_REVELATION = const(1) +OP_TAG_DOUBLE_ENDORSEMENT_EVIDENCE = const(2) +OP_TAG_DOUBLE_BAKING_EVIDENCE = const(3) +OP_TAG_ACTIVATE_ACCOUNT = const(4) +OP_TAG_PROPOSALS = const(5) +OP_TAG_BALLOT = const(6) +OP_TAG_REVEAL = const(107) +OP_TAG_TRANSACTION = const(108) +OP_TAG_ORIGINATION = const(109) +OP_TAG_DELEGATION = const(110) + +EP_TAG_NAMED = const(255) def base58_encode_check(payload, prefix=None): @@ -74,3 +105,29 @@ def write_bool(w: bytearray, boolean: bool): def write_instruction(w: bytearray, instruction: str) -> int: write_bytes_unchecked(w, MICHELSON_INSTRUCTION_BYTES[instruction]) + + +def check_script_size(script: bytes) -> None: + try: + r = BufferReader(script) + n = read_uint32_be(r) + r.read(n) + n = read_uint32_be(r) + ensure(r.remaining_count() == n) + except (AssertionError, EOFError): + raise wire.DataError("Invalid script") + + +def check_tx_params_size(params: bytes) -> None: + try: + r = BufferReader(params) + tag = r.get() + if tag == EP_TAG_NAMED: + n = r.get() + r.read(n) + elif tag > 4: + raise wire.DataError("Unknown entrypoint tag") + n = read_uint32_be(r) + ensure(r.remaining_count() == n) + except (AssertionError, EOFError): + raise wire.DataError("Invalid transaction parameters") diff --git a/core/src/apps/tezos/sign_tx.py b/core/src/apps/tezos/sign_tx.py index 65088264aa..011491c5ea 100644 --- a/core/src/apps/tezos/sign_tx.py +++ b/core/src/apps/tezos/sign_tx.py @@ -1,5 +1,3 @@ -from micropython import const - from trezor import wire from trezor.crypto import hashlib from trezor.crypto.curve import ed25519 @@ -8,12 +6,15 @@ from trezor.messages.TezosSignedTx import TezosSignedTx from apps.common import paths from apps.common.keychain import with_slip44_keychain -from apps.common.writers import write_bytes_unchecked, write_uint8, write_uint32_be +from apps.common.writers import ( + write_bytes_fixed, + write_bytes_unchecked, + write_uint8, + write_uint32_be, +) from . import CURVE, PATTERNS, SLIP44_ID, helpers, layout -PROPOSAL_LENGTH = const(32) - @with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE) async def sign_tx(ctx, msg, keychain): @@ -158,13 +159,20 @@ def _get_ballot(ballot): def _get_operation_bytes(w: bytearray, msg): - write_bytes_unchecked(w, msg.branch) + write_bytes_fixed(w, msg.branch, helpers.BRANCH_HASH_SIZE) # when the account sends first operation in lifetime, # we need to reveal its public key if msg.reveal is not None: _encode_common(w, msg.reveal, "reveal") - write_bytes_unchecked(w, msg.reveal.public_key) + tag = int(msg.reveal.public_key[0]) + + try: + public_key_size = helpers.PUBLIC_KEY_TAG_TO_SIZE[tag] + except KeyError: + raise wire.DataError("Invalid tag in public key") + + write_bytes_fixed(w, msg.reveal.public_key, 1 + public_key_size) # transaction operation if msg.transaction is not None: @@ -189,18 +197,28 @@ def _get_operation_bytes(w: bytearray, msg): else: _encode_manager_to_manager_transfer(w, parameters_manager.transfer) else: - _encode_data_with_bool_prefix(w, msg.transaction.parameters) + if msg.transaction.parameters: + helpers.write_bool(w, True) + helpers.check_tx_params_size(msg.transaction.parameters) + write_bytes_unchecked(w, msg.transaction.parameters) + else: + helpers.write_bool(w, False) # origination operation elif msg.origination is not None: _encode_common(w, msg.origination, "origination") _encode_zarith(w, msg.origination.balance) - _encode_data_with_bool_prefix(w, msg.origination.delegate) + _encode_data_with_bool_prefix( + w, msg.origination.delegate, helpers.TAGGED_PUBKEY_HASH_SIZE + ) + helpers.check_script_size(msg.origination.script) write_bytes_unchecked(w, msg.origination.script) # delegation operation elif msg.delegation is not None: _encode_common(w, msg.delegation, "delegation") - _encode_data_with_bool_prefix(w, msg.delegation.delegate) + _encode_data_with_bool_prefix( + w, msg.delegation.delegate, helpers.TAGGED_PUBKEY_HASH_SIZE + ) elif msg.proposal is not None: _encode_proposal(w, msg.proposal) elif msg.ballot is not None: @@ -209,13 +227,13 @@ def _get_operation_bytes(w: bytearray, msg): def _encode_common(w: bytearray, operation, str_operation): operation_tags = { - "reveal": 107, - "transaction": 108, - "origination": 109, - "delegation": 110, + "reveal": helpers.OP_TAG_REVEAL, + "transaction": helpers.OP_TAG_TRANSACTION, + "origination": helpers.OP_TAG_ORIGINATION, + "delegation": helpers.OP_TAG_DELEGATION, } write_uint8(w, operation_tags[str_operation]) - write_bytes_unchecked(w, operation.source) + write_bytes_fixed(w, operation.source, helpers.TAGGED_PUBKEY_HASH_SIZE) _encode_zarith(w, operation.fee) _encode_zarith(w, operation.counter) _encode_zarith(w, operation.gas_limit) @@ -224,13 +242,13 @@ def _encode_common(w: bytearray, operation, str_operation): def _encode_contract_id(w: bytearray, contract_id): write_uint8(w, contract_id.tag) - write_bytes_unchecked(w, contract_id.hash) + write_bytes_fixed(w, contract_id.hash, helpers.CONTRACT_ID_SIZE - 1) -def _encode_data_with_bool_prefix(w: bytearray, data): +def _encode_data_with_bool_prefix(w: bytearray, data: bytes, expected_length: int): if data: helpers.write_bool(w, True) - write_bytes_unchecked(w, data) + write_bytes_fixed(w, data, expected_length) else: helpers.write_bool(w, False) @@ -248,23 +266,19 @@ def _encode_zarith(w: bytearray, num): def _encode_proposal(w: bytearray, proposal): - proposal_tag = 5 - - write_uint8(w, proposal_tag) - write_bytes_unchecked(w, proposal.source) + write_uint8(w, helpers.OP_TAG_PROPOSALS) + write_bytes_fixed(w, proposal.source, helpers.TAGGED_PUBKEY_HASH_SIZE) write_uint32_be(w, proposal.period) - write_uint32_be(w, len(proposal.proposals) * PROPOSAL_LENGTH) + write_uint32_be(w, len(proposal.proposals) * helpers.PROPOSAL_HASH_SIZE) for proposal_hash in proposal.proposals: - write_bytes_unchecked(w, proposal_hash) + write_bytes_fixed(w, proposal_hash, helpers.PROPOSAL_HASH_SIZE) def _encode_ballot(w: bytearray, ballot): - ballot_tag = 6 - - write_uint8(w, ballot_tag) - write_bytes_unchecked(w, ballot.source) + write_uint8(w, helpers.OP_TAG_BALLOT) + write_bytes_fixed(w, ballot.source, helpers.TAGGED_PUBKEY_HASH_SIZE) write_uint32_be(w, ballot.period) - write_bytes_unchecked(w, ballot.proposal) + write_bytes_fixed(w, ballot.proposal, helpers.PROPOSAL_HASH_SIZE) write_uint8(w, ballot.ballot) @@ -285,9 +299,6 @@ def _encode_natural(w: bytearray, num): def _encode_manager_common(w: bytearray, sequence_length, operation, to_contract=False): - IMPLICIT_ADDRESS_LENGTH = 21 - SMART_CONTRACT_ADDRESS_LENGTH = 22 - # 5 = tag and sequence_length (1 byte + 4 bytes) argument_length = sequence_length + 5 @@ -305,11 +316,11 @@ def _encode_manager_common(w: bytearray, sequence_length, operation, to_contract else: helpers.write_instruction(w, "key_hash") if operation == "PUSH": - write_bytes_unchecked(w, bytes([10])) # byte sequence + write_uint8(w, 10) # byte sequence if to_contract is True: - write_uint32_be(w, SMART_CONTRACT_ADDRESS_LENGTH) + write_uint32_be(w, helpers.CONTRACT_ID_SIZE) else: - write_uint32_be(w, IMPLICIT_ADDRESS_LENGTH) + write_uint32_be(w, helpers.TAGGED_PUBKEY_HASH_SIZE) def _encode_manager_to_implicit_transfer(w: bytearray, manager_transfer): @@ -320,7 +331,9 @@ def _encode_manager_to_implicit_transfer(w: bytearray, manager_transfer): sequence_length = MICHELSON_LENGTH + len(value_natural) _encode_manager_common(w, sequence_length, "PUSH") - write_bytes_unchecked(w, manager_transfer.destination.hash) + write_bytes_fixed( + w, manager_transfer.destination.hash, helpers.TAGGED_PUBKEY_HASH_SIZE + ) helpers.write_instruction(w, "IMPLICIT_ACCOUNT") helpers.write_instruction(w, "PUSH") helpers.write_instruction(w, "mutez") @@ -335,7 +348,7 @@ def _encode_manager_delegation(w: bytearray, delegate): MICHELSON_LENGTH = 42 # length is fixed this time(no variable length fields) _encode_manager_common(w, MICHELSON_LENGTH, "PUSH") - write_bytes_unchecked(w, delegate) + write_bytes_fixed(w, delegate, helpers.TAGGED_PUBKEY_HASH_SIZE) helpers.write_instruction(w, "SOME") helpers.write_instruction(w, "SET_DELEGATE") helpers.write_instruction(w, "CONS") diff --git a/core/tests/test_apps.tezos.encode.py b/core/tests/test_apps.tezos.encode.py index 48dc2cf7c6..00fa9a5579 100644 --- a/core/tests/test_apps.tezos.encode.py +++ b/core/tests/test_apps.tezos.encode.py @@ -25,12 +25,12 @@ class TestTezosEncoding(unittest.TestCase): def test_tezos_encode_data_with_bool_prefix(self): w = bytearray() - _encode_data_with_bool_prefix(w, None) + _encode_data_with_bool_prefix(w, None, 0) self.assertEqual(bytes(w), bytes([0])) data = "afffeb1dc3c0" w = bytearray() - _encode_data_with_bool_prefix(w, unhexlify(data)) + _encode_data_with_bool_prefix(w, unhexlify(data), 6) self.assertEqual(bytes(w), unhexlify("ff" + data)) def test_tezos_encode_bool(self):