1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-20 14:39:22 +00:00

fix(core/tezos): Implement strict length checking.

(cherry picked from commit e7f44ebee8)
This commit is contained in:
Andrew Kozlik 2021-01-14 19:58:18 +01:00 committed by Tomas Susanka
parent 6fd355756c
commit ac939c94aa
5 changed files with 118 additions and 40 deletions

View File

@ -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

View File

@ -16,7 +16,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
)

View File

@ -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")

View File

@ -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")

View File

@ -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):