|
|
|
from micropython import const
|
|
|
|
|
|
|
|
from trezor import wire
|
|
|
|
from trezor.crypto import hashlib
|
|
|
|
from trezor.crypto.curve import ed25519
|
|
|
|
from trezor.messages import TezosBallotType, TezosContractType
|
|
|
|
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 . import CURVE, SLIP44_ID, helpers, layout
|
|
|
|
|
|
|
|
PROPOSAL_LENGTH = const(32)
|
|
|
|
|
|
|
|
|
|
|
|
@with_slip44_keychain(SLIP44_ID, CURVE, allow_testnet=True)
|
|
|
|
async def sign_tx(ctx, msg, keychain):
|
|
|
|
await paths.validate_path(
|
|
|
|
ctx, helpers.validate_full_path, keychain, msg.address_n, CURVE
|
|
|
|
)
|
|
|
|
|
|
|
|
node = keychain.derive(msg.address_n)
|
|
|
|
|
|
|
|
if msg.transaction is not None:
|
|
|
|
# if the tranasction oprtation is used to execute code on a smart contract
|
|
|
|
if msg.transaction.parameters_manager is not None:
|
|
|
|
parameters_manager = msg.transaction.parameters_manager
|
|
|
|
|
|
|
|
# operation to delegate from a smart contract with manager.tz
|
|
|
|
if parameters_manager.set_delegate is not None:
|
|
|
|
delegate = _get_address_by_tag(parameters_manager.set_delegate)
|
|
|
|
await layout.require_confirm_delegation_baker(ctx, delegate)
|
|
|
|
await layout.require_confirm_set_delegate(ctx, msg.transaction.fee)
|
|
|
|
|
|
|
|
# operation to remove delegate from the smart contract with manager.tz
|
|
|
|
elif parameters_manager.cancel_delegate is not None:
|
|
|
|
address = _get_address_from_contract(msg.transaction.destination)
|
|
|
|
await layout.require_confirm_delegation_manager_withdraw(ctx, address)
|
|
|
|
await layout.require_confirm_manager_remove_delegate(
|
|
|
|
ctx, msg.transaction.fee
|
|
|
|
)
|
|
|
|
|
|
|
|
# operation to transfer tokens from a smart contract to an implicit account or a smart contract
|
|
|
|
elif parameters_manager.transfer is not None:
|
|
|
|
to = _get_address_from_contract(parameters_manager.transfer.destination)
|
|
|
|
await layout.require_confirm_tx(
|
|
|
|
ctx, to, parameters_manager.transfer.amount
|
|
|
|
)
|
|
|
|
await layout.require_confirm_fee(
|
|
|
|
ctx, parameters_manager.transfer.amount, msg.transaction.fee
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
# transactions from an implicit account
|
|
|
|
to = _get_address_from_contract(msg.transaction.destination)
|
|
|
|
await layout.require_confirm_tx(ctx, to, msg.transaction.amount)
|
|
|
|
await layout.require_confirm_fee(
|
|
|
|
ctx, msg.transaction.amount, msg.transaction.fee
|
|
|
|
)
|
|
|
|
|
|
|
|
elif msg.origination is not None:
|
|
|
|
source = _get_address_by_tag(msg.origination.source)
|
|
|
|
await layout.require_confirm_origination(ctx, source)
|
|
|
|
|
|
|
|
# if we are immediately delegating contract
|
|
|
|
if msg.origination.delegate is not None:
|
|
|
|
delegate = _get_address_by_tag(msg.origination.delegate)
|
|
|
|
await layout.require_confirm_delegation_baker(ctx, delegate)
|
|
|
|
|
|
|
|
await layout.require_confirm_origination_fee(
|
|
|
|
ctx, msg.origination.balance, msg.origination.fee
|
|
|
|
)
|
|
|
|
|
|
|
|
elif msg.delegation is not None:
|
|
|
|
source = _get_address_by_tag(msg.delegation.source)
|
|
|
|
|
|
|
|
delegate = None
|
|
|
|
if msg.delegation.delegate is not None:
|
|
|
|
delegate = _get_address_by_tag(msg.delegation.delegate)
|
|
|
|
|
|
|
|
if delegate is not None and source != delegate:
|
|
|
|
await layout.require_confirm_delegation_baker(ctx, delegate)
|
|
|
|
await layout.require_confirm_set_delegate(ctx, msg.delegation.fee)
|
|
|
|
# if account registers itself as a delegate
|
|
|
|
else:
|
|
|
|
await layout.require_confirm_register_delegate(
|
|
|
|
ctx, source, msg.delegation.fee
|
|
|
|
)
|
|
|
|
|
|
|
|
elif msg.proposal is not None:
|
|
|
|
proposed_protocols = [_get_protocol_hash(p) for p in msg.proposal.proposals]
|
|
|
|
await layout.require_confirm_proposals(ctx, proposed_protocols)
|
|
|
|
|
|
|
|
elif msg.ballot is not None:
|
|
|
|
proposed_protocol = _get_protocol_hash(msg.ballot.proposal)
|
|
|
|
submitted_ballot = _get_ballot(msg.ballot.ballot)
|
|
|
|
await layout.require_confirm_ballot(ctx, proposed_protocol, submitted_ballot)
|
|
|
|
|
|
|
|
else:
|
|
|
|
raise wire.DataError("Invalid operation")
|
|
|
|
|
|
|
|
w = bytearray()
|
|
|
|
_get_operation_bytes(w, msg)
|
|
|
|
|
|
|
|
opbytes = bytes(w)
|
|
|
|
|
|
|
|
# watermark 0x03 is prefix for transactions, delegations, originations, reveals...
|
|
|
|
watermark = bytes([3])
|
|
|
|
wm_opbytes = watermark + opbytes
|
|
|
|
wm_opbytes_hash = hashlib.blake2b(wm_opbytes, outlen=32).digest()
|
|
|
|
|
|
|
|
signature = ed25519.sign(node.private_key(), wm_opbytes_hash)
|
|
|
|
|
|
|
|
sig_op_contents = opbytes + signature
|
|
|
|
sig_op_contents_hash = hashlib.blake2b(sig_op_contents, outlen=32).digest()
|
|
|
|
ophash = helpers.base58_encode_check(sig_op_contents_hash, prefix="o")
|
|
|
|
|
|
|
|
sig_prefixed = helpers.base58_encode_check(
|
|
|
|
signature, prefix=helpers.TEZOS_SIGNATURE_PREFIX
|
|
|
|
)
|
|
|
|
|
|
|
|
return TezosSignedTx(
|
|
|
|
signature=sig_prefixed, sig_op_contents=sig_op_contents, operation_hash=ophash
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _get_address_by_tag(address_hash):
|
|
|
|
prefixes = ["tz1", "tz2", "tz3"]
|
|
|
|
tag = int(address_hash[0])
|
|
|
|
|
|
|
|
if 0 <= tag < len(prefixes):
|
|
|
|
return helpers.base58_encode_check(address_hash[1:], prefix=prefixes[tag])
|
|
|
|
raise wire.DataError("Invalid tag in address hash")
|
|
|
|
|
|
|
|
|
|
|
|
def _get_address_from_contract(address):
|
|
|
|
if address.tag == TezosContractType.Implicit:
|
|
|
|
return _get_address_by_tag(address.hash)
|
|
|
|
|
|
|
|
elif address.tag == TezosContractType.Originated:
|
|
|
|
return helpers.base58_encode_check(
|
|
|
|
address.hash[:-1], prefix=helpers.TEZOS_ORIGINATED_ADDRESS_PREFIX
|
|
|
|
)
|
|
|
|
|
|
|
|
raise wire.DataError("Invalid contract type")
|
|
|
|
|
|
|
|
|
|
|
|
def _get_protocol_hash(proposal):
|
|
|
|
return helpers.base58_encode_check(proposal, prefix="P")
|
|
|
|
|
|
|
|
|
|
|
|
def _get_ballot(ballot):
|
|
|
|
if ballot == TezosBallotType.Yay:
|
|
|
|
return "yay"
|
|
|
|
elif ballot == TezosBallotType.Nay:
|
|
|
|
return "nay"
|
|
|
|
elif ballot == TezosBallotType.Pass:
|
|
|
|
return "pass"
|
|
|
|
|
|
|
|
|
|
|
|
def _get_operation_bytes(w: bytearray, msg):
|
|
|
|
write_bytes_unchecked(w, msg.branch)
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
# transaction operation
|
|
|
|
if msg.transaction is not None:
|
|
|
|
_encode_common(w, msg.transaction, "transaction")
|
|
|
|
_encode_zarith(w, msg.transaction.amount)
|
|
|
|
_encode_contract_id(w, msg.transaction.destination)
|
|
|
|
|
|
|
|
# support delegation and transfer from the old scriptless contracts (now with manager.tz script)
|
|
|
|
if msg.transaction.parameters_manager is not None:
|
|
|
|
parameters_manager = msg.transaction.parameters_manager
|
|
|
|
|
|
|
|
if parameters_manager.set_delegate is not None:
|
|
|
|
_encode_manager_delegation(w, parameters_manager.set_delegate)
|
|
|
|
elif parameters_manager.cancel_delegate is not None:
|
|
|
|
_encode_manager_delegation_remove(w)
|
|
|
|
elif parameters_manager.transfer is not None:
|
|
|
|
if (
|
|
|
|
parameters_manager.transfer.destination.tag
|
|
|
|
== TezosContractType.Implicit
|
|
|
|
):
|
|
|
|
_encode_manager_to_implicit_transfer(w, parameters_manager.transfer)
|
|
|
|
else:
|
|
|
|
_encode_manager_to_manager_transfer(w, parameters_manager.transfer)
|
|
|
|
else:
|
|
|
|
_encode_data_with_bool_prefix(w, msg.transaction.parameters)
|
|
|
|
# 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)
|
|
|
|
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)
|
|
|
|
elif msg.proposal is not None:
|
|
|
|
_encode_proposal(w, msg.proposal)
|
|
|
|
elif msg.ballot is not None:
|
|
|
|
_encode_ballot(w, msg.ballot)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_common(w: bytearray, operation, str_operation):
|
|
|
|
operation_tags = {
|
|
|
|
"reveal": 107,
|
|
|
|
"transaction": 108,
|
|
|
|
"origination": 109,
|
|
|
|
"delegation": 110,
|
|
|
|
}
|
|
|
|
write_uint8(w, operation_tags[str_operation])
|
|
|
|
write_bytes_unchecked(w, operation.source)
|
|
|
|
_encode_zarith(w, operation.fee)
|
|
|
|
_encode_zarith(w, operation.counter)
|
|
|
|
_encode_zarith(w, operation.gas_limit)
|
|
|
|
_encode_zarith(w, operation.storage_limit)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_contract_id(w: bytearray, contract_id):
|
|
|
|
write_uint8(w, contract_id.tag)
|
|
|
|
write_bytes_unchecked(w, contract_id.hash)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_data_with_bool_prefix(w: bytearray, data):
|
|
|
|
if data:
|
|
|
|
helpers.write_bool(w, True)
|
|
|
|
write_bytes_unchecked(w, data)
|
|
|
|
else:
|
|
|
|
helpers.write_bool(w, False)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_zarith(w: bytearray, num):
|
|
|
|
while True:
|
|
|
|
byte = num & 127
|
|
|
|
num = num >> 7
|
|
|
|
|
|
|
|
if num == 0:
|
|
|
|
write_uint8(w, byte)
|
|
|
|
break
|
|
|
|
|
|
|
|
write_uint8(w, 128 | byte)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_proposal(w: bytearray, proposal):
|
|
|
|
proposal_tag = 5
|
|
|
|
|
|
|
|
write_uint8(w, proposal_tag)
|
|
|
|
write_bytes_unchecked(w, proposal.source)
|
|
|
|
write_uint32_be(w, proposal.period)
|
|
|
|
write_uint32_be(w, len(proposal.proposals) * PROPOSAL_LENGTH)
|
|
|
|
for proposal_hash in proposal.proposals:
|
|
|
|
write_bytes_unchecked(w, proposal_hash)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_ballot(w: bytearray, ballot):
|
|
|
|
ballot_tag = 6
|
|
|
|
|
|
|
|
write_uint8(w, ballot_tag)
|
|
|
|
write_bytes_unchecked(w, ballot.source)
|
|
|
|
write_uint32_be(w, ballot.period)
|
|
|
|
write_bytes_unchecked(w, ballot.proposal)
|
|
|
|
write_uint8(w, ballot.ballot)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_natural(w: bytearray, num):
|
|
|
|
# encode a natural integer with its signed bit on position 7
|
|
|
|
# as we do not expect negative numbers in a transfer operation the bit is never set
|
|
|
|
natural_tag = 0
|
|
|
|
write_uint8(w, natural_tag)
|
|
|
|
|
|
|
|
byte = num & 63
|
|
|
|
modified = num >> 6
|
|
|
|
|
|
|
|
if modified == 0:
|
|
|
|
write_uint8(w, byte)
|
|
|
|
else:
|
|
|
|
write_uint8(w, 128 | byte)
|
|
|
|
_encode_zarith(w, modified)
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
helpers.write_bool(w, True)
|
|
|
|
write_uint8(w, helpers.DO_ENTRYPOINT_TAG)
|
|
|
|
write_uint32_be(w, argument_length)
|
|
|
|
write_uint8(w, helpers.MICHELSON_SEQUENCE_TAG)
|
|
|
|
write_uint32_be(w, sequence_length)
|
|
|
|
helpers.write_instruction(w, "DROP")
|
|
|
|
helpers.write_instruction(w, "NIL")
|
|
|
|
helpers.write_instruction(w, "operation")
|
|
|
|
helpers.write_instruction(w, operation)
|
|
|
|
if to_contract is True:
|
|
|
|
helpers.write_instruction(w, "address")
|
|
|
|
else:
|
|
|
|
helpers.write_instruction(w, "key_hash")
|
|
|
|
if operation == "PUSH":
|
|
|
|
write_bytes_unchecked(w, bytes([10])) # byte sequence
|
|
|
|
if to_contract is True:
|
|
|
|
write_uint32_be(w, SMART_CONTRACT_ADDRESS_LENGTH)
|
|
|
|
else:
|
|
|
|
write_uint32_be(w, IMPLICIT_ADDRESS_LENGTH)
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_manager_to_implicit_transfer(w: bytearray, manager_transfer):
|
|
|
|
MICHELSON_LENGTH = 48
|
|
|
|
|
|
|
|
value_natural = bytearray()
|
|
|
|
_encode_natural(value_natural, manager_transfer.amount)
|
|
|
|
sequence_length = MICHELSON_LENGTH + len(value_natural)
|
|
|
|
|
|
|
|
_encode_manager_common(w, sequence_length, "PUSH")
|
|
|
|
write_bytes_unchecked(w, manager_transfer.destination.hash)
|
|
|
|
helpers.write_instruction(w, "IMPLICIT_ACCOUNT")
|
|
|
|
helpers.write_instruction(w, "PUSH")
|
|
|
|
helpers.write_instruction(w, "mutez")
|
|
|
|
_encode_natural(w, manager_transfer.amount)
|
|
|
|
helpers.write_instruction(w, "UNIT")
|
|
|
|
helpers.write_instruction(w, "TRANSFER_TOKENS")
|
|
|
|
helpers.write_instruction(w, "CONS")
|
|
|
|
|
|
|
|
|
|
|
|
# smart_contract_delegation
|
|
|
|
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)
|
|
|
|
helpers.write_instruction(w, "SOME")
|
|
|
|
helpers.write_instruction(w, "SET_DELEGATE")
|
|
|
|
helpers.write_instruction(w, "CONS")
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_manager_delegation_remove(w: bytearray):
|
|
|
|
MICHELSON_LENGTH = 14 # length is fixed this time(no variable length fields)
|
|
|
|
_encode_manager_common(w, MICHELSON_LENGTH, "NONE")
|
|
|
|
helpers.write_instruction(w, "SET_DELEGATE")
|
|
|
|
helpers.write_instruction(w, "CONS")
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_manager_to_manager_transfer(w: bytearray, manager_transfer):
|
|
|
|
MICHELSON_LENGTH = 77
|
|
|
|
|
|
|
|
value_natural = bytearray()
|
|
|
|
_encode_natural(value_natural, manager_transfer.amount)
|
|
|
|
sequence_length = MICHELSON_LENGTH + len(value_natural)
|
|
|
|
|
|
|
|
_encode_manager_common(w, sequence_length, "PUSH", to_contract=True)
|
|
|
|
_encode_contract_id(w, manager_transfer.destination)
|
|
|
|
helpers.write_instruction(w, "CONTRACT")
|
|
|
|
helpers.write_instruction(w, "unit")
|
|
|
|
helpers.write_instruction(w, "ASSERT_SOME")
|
|
|
|
helpers.write_instruction(w, "PUSH")
|
|
|
|
helpers.write_instruction(w, "mutez")
|
|
|
|
_encode_natural(w, manager_transfer.amount)
|
|
|
|
helpers.write_instruction(w, "UNIT")
|
|
|
|
helpers.write_instruction(w, "TRANSFER_TOKENS")
|
|
|
|
helpers.write_instruction(w, "CONS")
|