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:
parent
6fd355756c
commit
ac939c94aa
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user