Update sign_tx

Add certificates, withdrawals and metadata hash
pull/1153/head
gabrielkerekes 4 years ago committed by Pavol Rusnak
parent a25444efd1
commit 7bf5cab840

@ -256,11 +256,11 @@ def _validate_base_address_staking_info(
"Base address needs either a staking path or a staking key hash!"
)
if staking_key_hash is None and not _is_staking_path(staking_path):
if staking_key_hash is None and not is_staking_path(staking_path):
raise wire.DataError("Invalid staking path!")
def _is_staking_path(path: List[int]) -> bool:
def is_staking_path(path: List[int]) -> bool:
"""
Validates path to match 1852'/1815'/a'/2/0. Path must
be a valid Cardano path. It must have a Shelley purpose
@ -322,7 +322,7 @@ def _derive_enterprise_address(
def _derive_reward_address(
keychain: seed.Keychain, path: List[int], network_id: int,
) -> bytes:
if not _is_staking_path(path):
if not is_staking_path(path):
raise wire.DataError("Invalid path for reward address!")
header = _create_address_header(CardanoAddressType.REWARD, network_id)

@ -2,3 +2,5 @@ from trezor import wire
INVALID_ADDRESS = wire.ProcessError("Invalid address")
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
INVALID_CERTIFICATE = wire.ProcessError("Invalid certificate")
INVALID_WITHDRAWAL = wire.ProcessError("Invalid withdrawal")

@ -1,5 +1,8 @@
from micropython import const
if False:
from typing import List
def variable_length_encode(number: int) -> bytes:
"""

@ -1,7 +1,11 @@
from ubinascii import hexlify
from trezor import ui
from trezor.messages import ButtonRequestType, CardanoAddressType
from trezor.messages import (
ButtonRequestType,
CardanoAddressType,
CardanoCertificateType,
)
from trezor.strings import format_amount
from trezor.ui.button import ButtonDefault
from trezor.ui.scroll import Paginated
@ -12,11 +16,16 @@ from apps.common.confirm import confirm, require_confirm, require_hold_to_confir
from apps.common.layout import address_n_to_str, show_warning
from .helpers import protocol_magics
from .helpers.utils import to_account_path
if False:
from typing import List
from trezor import wire
from trezor.messages import CardanoBlockchainPointerType
from trezor.messages import (
CardanoBlockchainPointerType,
CardanoTxCertificateType,
CardanoTxWithdrawalType,
)
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
@ -28,6 +37,12 @@ ADDRESS_TYPE_NAMES = {
CardanoAddressType.REWARD: "Reward",
}
CERTIFICATE_TYPE_NAMES = {
CardanoCertificateType.STAKE_REGISTRATION: "Stake key registration",
CardanoCertificateType.STAKE_DEREGISTRATION: "Stake key deregistration",
CardanoCertificateType.STAKE_DELEGATION: "Stake delegation",
}
def format_coin_amount(amount: int) -> str:
return "%s %s" % (format_amount(amount, 6), "ADA")
@ -130,6 +145,48 @@ async def confirm_transaction(ctx, amount: int, fee: int, protocol_magic: int):
await require_hold_to_confirm(ctx, Paginated([t1, t2]))
async def confirm_certificate(
ctx: wire.Context, certificate: CardanoTxCertificateType
) -> bool:
pages = []
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
t1.normal("Confirm:")
t1.bold(CERTIFICATE_TYPE_NAMES[certificate.type])
t1.normal("for account:")
t1.bold(address_n_to_str(to_account_path(certificate.path)))
pages.append(t1)
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
t2.normal("to pool:")
t2.bold(hexlify(certificate.pool).decode())
pages.append(t2)
await require_confirm(ctx, Paginated(pages))
async def confirm_withdrawal(
ctx: wire.Context, withdrawal: CardanoTxWithdrawalType
) -> bool:
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
t1.normal("Confirm withdrawal")
t1.normal("for account:")
t1.bold(address_n_to_str(to_account_path(withdrawal.path)))
t1.normal("Amount:")
t1.bold(format_coin_amount(withdrawal.amount))
await require_confirm(ctx, t1)
async def confirm_metadata_hash(ctx: wire.Context, metadata_hash: bytes) -> bool:
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
t1.normal("Confirm metadata hash:")
t1.bold(hexlify(metadata_hash).decode())
await require_confirm(ctx, t1)
async def show_address(
ctx: wire.Context,
address: str,

@ -3,7 +3,8 @@ from micropython import const
from trezor import log, wire
from trezor.crypto import hashlib
from trezor.crypto.curve import ed25519
from trezor.messages import CardanoAddressParametersType
from trezor.messages import CardanoAddressType, CardanoCertificateType
from trezor.messages.CardanoAddressParametersType import CardanoAddressParametersType
from trezor.messages.CardanoSignedTx import CardanoSignedTx
from apps.common import cbor
@ -15,15 +16,26 @@ from .address import (
derive_address_bytes,
derive_human_readable_address,
get_address_bytes_unsafe,
get_public_key_hash,
is_staking_path,
validate_full_path,
validate_output_address,
)
from .byron_address import get_address_attributes
from .helpers import network_ids, protocol_magics, staking_use_cases
from .helpers import (
INVALID_CERTIFICATE,
INVALID_WITHDRAWAL,
network_ids,
protocol_magics,
staking_use_cases,
)
from .helpers.utils import to_account_path
from .layout import (
confirm_certificate,
confirm_metadata_hash,
confirm_sending,
confirm_transaction,
confirm_withdrawal,
show_warning_tx_different_staking_account,
show_warning_tx_no_staking_info,
show_warning_tx_pointer_address,
@ -36,6 +48,8 @@ if False:
from trezor.messages.CardanoSignTx import CardanoSignTx
from trezor.messages.CardanoTxInputType import CardanoTxInputType
from trezor.messages.CardanoTxOutputType import CardanoTxOutputType
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType
# the maximum allowed change address. this should be large enough for normal
# use and still allow to quickly brute-force the correct bip32 path
@ -45,6 +59,9 @@ BIP_PATH_LENGTH = const(5)
LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000
POOL_HASH_SIZE = 28
METADATA_HASH_SIZE = 32
@seed.with_keychain
async def sign_tx(
@ -60,6 +77,11 @@ async def sign_tx(
await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
_validate_certificates(msg.certificates)
_validate_withdrawals(msg.withdrawals)
if msg.metadata_hash and len(msg.metadata_hash) != METADATA_HASH_SIZE:
raise wire.ProcessError("Invalid metadata hash")
# display the transaction in UI
await _show_tx(ctx, keychain, msg)
@ -117,12 +139,41 @@ def _validate_outputs(
raise wire.ProcessError("Total transaction amount is out of range!")
def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None:
for certificate in certificates:
if not is_staking_path(certificate.path):
raise INVALID_CERTIFICATE
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
if certificate.pool is None or len(certificate.pool) != POOL_HASH_SIZE:
raise INVALID_CERTIFICATE
def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None:
for withdrawal in withdrawals:
if not is_staking_path(withdrawal.path):
raise INVALID_WITHDRAWAL
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY:
raise INVALID_WITHDRAWAL
def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, bytes]:
tx_body = _build_tx_body(keychain, msg)
tx_hash = _hash_tx_body(tx_body)
witnesses = _build_witnesses(keychain, msg.inputs, tx_hash, msg.protocol_magic)
witnesses = _build_witnesses(
keychain,
msg.inputs,
msg.certificates,
msg.withdrawals,
tx_hash,
msg.protocol_magic,
)
# We always set transaction metadata to None, even if metadata
# hash is set. Metadata aren't sent to Trezor and the None
# should be replaced by the SW wallet if metadata exist.
serialized_tx = cbor.encode([tx_body, witnesses, None])
return serialized_tx, tx_hash
@ -141,6 +192,21 @@ def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
3: msg.ttl,
}
if msg.certificates:
certificates_for_cbor = _build_certificates(keychain, msg.certificates)
tx_body[4] = certificates_for_cbor
if msg.withdrawals:
withdrawals_for_cbor = _build_withdrawals(
keychain, msg.withdrawals, msg.protocol_magic, msg.network_id
)
tx_body[5] = withdrawals_for_cbor
# tx_body[6] is for protocol updates, which we don't support
if msg.metadata_hash:
tx_body[7] = msg.metadata_hash
return tx_body
@ -170,6 +236,50 @@ def _build_outputs(
return result
def _build_certificates(
keychain: seed.Keychain, certificates: List[CardanoTxCertificateType]
) -> List[Tuple]:
result = []
for certificate in certificates:
public_key_hash = get_public_key_hash(keychain, certificate.path)
stake_credential = [0, public_key_hash]
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
certificate_for_cbor = (
certificate.type,
stake_credential,
certificate.pool,
)
else:
certificate_for_cbor = (certificate.type, stake_credential)
result.append(certificate_for_cbor)
return result
def _build_withdrawals(
keychain: seed.Keychain,
withdrawals: List[CardanoTxWithdrawalType],
protocol_magic: int,
network_id: int,
) -> Dict[bytes, int]:
result = {}
for withdrawal in withdrawals:
reward_address = derive_address_bytes(
keychain,
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD, address_n=withdrawal.path,
),
protocol_magic,
network_id,
)
result[reward_address] = withdrawal.amount
return result
def _hash_tx_body(tx_body: Dict) -> bytes:
tx_body_cbor = cbor.encode(tx_body)
return hashlib.blake2b(data=tx_body_cbor, outlen=32).digest()
@ -178,10 +288,14 @@ def _hash_tx_body(tx_body: Dict) -> bytes:
def _build_witnesses(
keychain: seed.Keychain,
inputs: List[CardanoTxInputType],
certificates: List[CardanoTxCertificateType],
withdrawals: List[CardanoTxWithdrawalType],
tx_body_hash: bytes,
protocol_magic: int,
) -> Dict:
shelley_witnesses = _build_shelley_witnesses(keychain, inputs, tx_body_hash)
shelley_witnesses = _build_shelley_witnesses(
keychain, inputs, certificates, withdrawals, tx_body_hash
)
byron_witnesses = _build_byron_witnesses(
keychain, inputs, tx_body_hash, protocol_magic
)
@ -189,33 +303,59 @@ def _build_witnesses(
# use key 0 for shelley witnesses and key 2 for byron witnesses
# according to the spec in shelley.cddl in cardano-ledger-specs
witnesses = {}
if len(shelley_witnesses) > 0:
if shelley_witnesses:
witnesses[0] = shelley_witnesses
if len(byron_witnesses) > 0:
if byron_witnesses:
witnesses[2] = byron_witnesses
return witnesses
def _build_shelley_witnesses(
keychain: seed.Keychain, inputs: List[CardanoTxInputType], tx_body_hash: bytes,
keychain: seed.Keychain,
inputs: List[CardanoTxInputType],
certificates: List[CardanoTxCertificateType],
withdrawals: List[CardanoTxWithdrawalType],
tx_body_hash: bytes,
) -> List[Tuple[bytes, bytes]]:
shelley_witnesses = []
paths = set()
for input in inputs:
if not is_shelley_path(input.address_n):
continue
paths.add(tuple(input.address_n))
for certificate in certificates:
if not _is_certificate_witness_required(certificate.type):
continue
paths.add(tuple(certificate.path))
for withdrawal in withdrawals:
paths.add(tuple(withdrawal.path))
node = keychain.derive(input.address_n)
public_key = remove_ed25519_prefix(node.public_key())
signature = ed25519.sign_ext(
node.private_key(), node.private_key_ext(), tx_body_hash
)
shelley_witnesses.append((public_key, signature))
for path in paths:
witness = _build_shelley_witness(keychain, tx_body_hash, list(path))
shelley_witnesses.append(witness)
return shelley_witnesses
def _build_shelley_witness(
keychain: seed.Keychain, tx_body_hash: bytes, path: List[int]
) -> List[Tuple[bytes, bytes]]:
node = keychain.derive(path)
signature = ed25519.sign_ext(
node.private_key(), node.private_key_ext(), tx_body_hash
)
public_key = remove_ed25519_prefix(node.public_key())
return public_key, signature
def _is_certificate_witness_required(certificate_type: int) -> bool:
return certificate_type != CardanoCertificateType.STAKE_REGISTRATION
def _build_byron_witnesses(
keychain: seed.Keychain,
inputs: List[CardanoTxInputType],
@ -245,6 +385,16 @@ async def _show_tx(
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
) -> None:
total_amount = await _show_outputs(ctx, keychain, msg)
for certificate in msg.certificates:
await confirm_certificate(ctx, certificate)
for withdrawal in msg.withdrawals:
await confirm_withdrawal(ctx, withdrawal)
if msg.metadata_hash:
await confirm_metadata_hash(ctx, msg.metadata_hash)
await confirm_transaction(ctx, total_amount, msg.fee, msg.protocol_magic)

Loading…
Cancel
Save