From 501ea6bb2c3488320ee90c4d9bc9ea251c242999 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Wed, 6 Jun 2018 16:02:13 +0200 Subject: [PATCH] stellar: refactoring --- src/apps/stellar/README.md | 5 + src/apps/stellar/consts.py | 1 + src/apps/stellar/get_public_key.py | 2 +- src/apps/stellar/layout.py | 3 +- src/apps/stellar/operations/__init__.py | 80 +++++++------- src/apps/stellar/operations/layout.py | 79 +++++++------ src/apps/stellar/operations/serialize.py | 114 +++++++++---------- src/apps/stellar/sign_tx.py | 135 ++++++++++++----------- 8 files changed, 216 insertions(+), 203 deletions(-) diff --git a/src/apps/stellar/README.md b/src/apps/stellar/README.md index 717b1d4b13..c1146e2b2f 100644 --- a/src/apps/stellar/README.md +++ b/src/apps/stellar/README.md @@ -9,3 +9,8 @@ REVIEWER = Jan Pochyla ----- TODO + + # Stellar transactions consist of sha256 of: + # - sha256(network passphrase) + # - 4-byte unsigned big-endian int type constant (2 for tx) + # - public key diff --git a/src/apps/stellar/consts.py b/src/apps/stellar/consts.py index dcb886036e..2c4c9885db 100644 --- a/src/apps/stellar/consts.py +++ b/src/apps/stellar/consts.py @@ -34,6 +34,7 @@ op_wire_types = [ wire_types.StellarSetOptionsOp, ] +# https://github.com/stellar/go/blob/e0ffe19f58879d3c31e2976b97a5bf10e13a337b/xdr/xdr_generated.go#L584 ASSET_TYPE_NATIVE = const(0) ASSET_TYPE_ALPHANUM4 = const(1) ASSET_TYPE_ALPHANUM12 = const(2) diff --git a/src/apps/stellar/get_public_key.py b/src/apps/stellar/get_public_key.py index 2768f556c2..45f402b3d0 100644 --- a/src/apps/stellar/get_public_key.py +++ b/src/apps/stellar/get_public_key.py @@ -36,5 +36,5 @@ async def _show(ctx, address: str): cancel_style=ui.BTN_KEY) -def _split_address(address: str): +def _split_address(address: str): # todo merge with NEM return chunks(address, 17) diff --git a/src/apps/stellar/layout.py b/src/apps/stellar/layout.py index 44f8c95082..46fb4f0bcb 100644 --- a/src/apps/stellar/layout.py +++ b/src/apps/stellar/layout.py @@ -1,4 +1,4 @@ -from apps.common.confirm import * +from apps.common.confirm import require_confirm, require_hold_to_confirm from apps.stellar import consts from apps.stellar import helpers from trezor import ui @@ -66,6 +66,7 @@ def format_address(pubkey: bytes) -> str: return helpers.address_from_public_key(pubkey) +# todo merge with nem def split(text): return utils.chunks(text, 17) diff --git a/src/apps/stellar/operations/__init__.py b/src/apps/stellar/operations/__init__.py index 48ef64aecf..7459249ed7 100644 --- a/src/apps/stellar/operations/__init__.py +++ b/src/apps/stellar/operations/__init__.py @@ -1,44 +1,46 @@ -from apps.stellar.operations.serialize import * -from apps.stellar.operations.layout import * +from apps.stellar.operations import serialize +from apps.stellar.operations import layout +from apps.stellar import consts +from apps.stellar import writers async def operation(ctx, w, op): if op.source_account: - await confirm_source_account(ctx, op.source_account) - serialize_account(w, op.source_account) - write_uint32(w, get_op_code(op)) - if isinstance(op, StellarAccountMergeOp): - await confirm_account_merge_op(ctx, op) - serialize_account_merge_op(w, op) - elif isinstance(op, StellarAllowTrustOp): - await confirm_allow_trust_op(ctx, op) - serialize_allow_trust_op(w, op) - elif isinstance(op, StellarBumpSequenceOp): - await confirm_bump_sequence_op(ctx, op) - serialize_bump_sequence_op(w, op) - elif isinstance(op, StellarChangeTrustOp): - await confirm_change_trust_op(ctx, op) - serialize_change_trust_op(w, op) - elif isinstance(op, StellarCreateAccountOp): - await confirm_create_account_op(ctx, op) - serialize_create_account_op(w, op) - elif isinstance(op, StellarCreatePassiveOfferOp): - await confirm_create_passive_offer_op(ctx, op) - serialize_create_passive_offer_op(w, op) - elif isinstance(op, StellarManageDataOp): - await confirm_manage_data_op(ctx, op) - serialize_manage_data_op(w, op) - elif isinstance(op, StellarManageOfferOp): - await confirm_manage_offer_op(ctx, op) - serialize_manage_offer_op(w, op) - elif isinstance(op, StellarPathPaymentOp): - await confirm_path_payment_op(ctx, op) - serialize_path_payment_op(w, op) - elif isinstance(op, StellarPaymentOp): - await confirm_payment_op(ctx, op) - serialize_payment_op(w, op) - elif isinstance(op, StellarSetOptionsOp): - await confirm_set_options_op(ctx, op) - serialize_set_options_op(w, op) + await layout.confirm_source_account(ctx, op.source_account) + serialize.serialize_account(w, op.source_account) + writers.write_uint32(w, consts.get_op_code(op)) + if isinstance(op, serialize.StellarAccountMergeOp): + await layout.confirm_account_merge_op(ctx, op) + serialize.serialize_account_merge_op(w, op) + elif isinstance(op, serialize.StellarAllowTrustOp): + await layout.confirm_allow_trust_op(ctx, op) + serialize.serialize_allow_trust_op(w, op) + elif isinstance(op, serialize.StellarBumpSequenceOp): + await layout.confirm_bump_sequence_op(ctx, op) + serialize.serialize_bump_sequence_op(w, op) + elif isinstance(op, serialize.StellarChangeTrustOp): + await layout.confirm_change_trust_op(ctx, op) + serialize.serialize_change_trust_op(w, op) + elif isinstance(op, serialize.StellarCreateAccountOp): + await layout.confirm_create_account_op(ctx, op) + serialize.serialize_create_account_op(w, op) + elif isinstance(op, serialize.StellarCreatePassiveOfferOp): + await layout.confirm_create_passive_offer_op(ctx, op) + serialize.serialize_create_passive_offer_op(w, op) + elif isinstance(op, serialize.StellarManageDataOp): + await layout.confirm_manage_data_op(ctx, op) + serialize.serialize_manage_data_op(w, op) + elif isinstance(op, serialize.StellarManageOfferOp): + await layout.confirm_manage_offer_op(ctx, op) + serialize.serialize_manage_offer_op(w, op) + elif isinstance(op, serialize.StellarPathPaymentOp): + await layout.confirm_path_payment_op(ctx, op) + serialize.serialize_path_payment_op(w, op) + elif isinstance(op, serialize.StellarPaymentOp): + await layout.confirm_payment_op(ctx, op) + serialize.serialize_payment_op(w, op) + elif isinstance(op, serialize.StellarSetOptionsOp): + await layout.confirm_set_options_op(ctx, op) + serialize.serialize_set_options_op(w, op) else: - raise ValueError('Stellar: unknown operation') + raise ValueError('serialize.Stellar: unknown operation') diff --git a/src/apps/stellar/operations/layout.py b/src/apps/stellar/operations/layout.py index 7932e1a569..1a292dd337 100644 --- a/src/apps/stellar/operations/layout.py +++ b/src/apps/stellar/operations/layout.py @@ -1,7 +1,7 @@ -from apps.stellar.layout import * +from apps.stellar.layout import split, format_address, format_amount, ui, trim_to_rows, require_confirm +from apps.stellar import consts from trezor.messages import ButtonRequestType from trezor.ui.text import Text - from trezor.messages.StellarAccountMergeOp import StellarAccountMergeOp from trezor.messages.StellarAssetType import StellarAssetType from trezor.messages.StellarAllowTrustOp import StellarAllowTrustOp @@ -27,12 +27,12 @@ async def confirm_source_account(ctx, source_account: bytes): async def confirm_allow_trust_op(ctx, op: StellarAllowTrustOp): if op.is_authorized: - text = 'Allow' + text = 'Allow Trust' else: - text = 'Revoke' + text = 'Revoke Trust' content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, text + ' Trust', - ui.NORMAL, "of '" + op.asset_code + "' by:", + ui.BOLD, text, + ui.NORMAL, 'of %s by:' % op.asset_code, ui.MONO, *split(trim_to_rows(format_address(op.trusted_account), 3)), icon_color=ui.GREEN) @@ -59,13 +59,13 @@ async def confirm_bump_sequence_op(ctx, op: StellarBumpSequenceOp): async def confirm_change_trust_op(ctx, op: StellarChangeTrustOp): if op.limit == 0: - text = 'Delete' + text = 'Delete Trust' else: - text = 'Add' + text = 'Add Trust' content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, text + ' Trust', - ui.NORMAL, 'Asset: ' + op.asset.code, - ui.NORMAL, 'Amount: ' + format_amount(op.limit, ticker=False), + ui.BOLD, text, + ui.NORMAL, 'Asset: %s' % op.asset.code, + ui.NORMAL, 'Amount: %s' % format_amount(op.limit, ticker=False), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await confirm_asset_issuer(ctx, op.asset) @@ -74,7 +74,7 @@ async def confirm_change_trust_op(ctx, op: StellarChangeTrustOp): async def confirm_create_account_op(ctx, op: StellarCreateAccountOp): content = Text('Confirm operation', ui.ICON_CONFIRM, ui.BOLD, 'Create Account', - ui.NORMAL, 'with ' + format_amount(op.starting_balance), + ui.NORMAL, 'with %s' % format_amount(op.starting_balance), ui.MONO, *split(trim_to_rows(format_address(op.new_account), 3)), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) @@ -96,32 +96,30 @@ async def confirm_manage_offer_op(ctx, op: StellarManageOfferOp): text = 'Delete' else: text = 'Update' - text += ' #' + str(op.offer_id) + text += ' #%d' % op.offer_id await _confirm_offer(ctx, text, op) -# todo scale? this is inconsistent with T1 (op.amount should? use divisibility) async def _confirm_offer(ctx, text, op): content = Text('Confirm operation', ui.ICON_CONFIRM, ui.BOLD, text, - ui.NORMAL, 'Sell ' + str(op.amount) + ' ' + op.selling_asset.code, - ui.NORMAL, 'For ' + str(op.price_n / op.price_d), - ui.NORMAL, 'Per ' + format_asset_code(op.buying_asset), + ui.NORMAL, 'Sell %s %s' % (format_amount(op.amount, ticker=False), op.selling_asset.code), + ui.NORMAL, 'For %f' % (op.price_n / op.price_d), + ui.NORMAL, 'Per %s' % format_asset_code(op.buying_asset), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await confirm_asset_issuer(ctx, op.selling_asset) await confirm_asset_issuer(ctx, op.buying_asset) -# todo done async def confirm_manage_data_op(ctx, op: StellarManageDataOp): from trezor.crypto.hashlib import sha256 if op.value: - text = 'Set' + title = 'Set' else: - text = 'Clear' + title = 'Clear' content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, text + ' data value key', + ui.BOLD, '%s data value key' % title, ui.MONO, *split(op.key), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) @@ -129,7 +127,7 @@ async def confirm_manage_data_op(ctx, op: StellarManageDataOp): digest = sha256(op.value).digest() digest_str = hexlify(digest).decode() content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, ' Value (SHA-256):', + ui.BOLD, 'Value (SHA-256):', ui.MONO, *split(digest_str), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) @@ -137,8 +135,8 @@ async def confirm_manage_data_op(ctx, op: StellarManageDataOp): async def confirm_path_payment_op(ctx, op: StellarPathPaymentOp): content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, 'Path Pay ' + format_amount(op.destination_amount, ticker=False), - ui.BOLD, format_asset_code(op.destination_asset) + ' to:', + ui.BOLD, 'Path Pay %s' % format_amount(op.destination_amount, ticker=False), + ui.BOLD, '%s to:' % format_asset_code(op.destination_asset), ui.MONO, *split(trim_to_rows(format_address(op.destination_account), 3)), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) @@ -157,8 +155,8 @@ async def confirm_path_payment_op(ctx, op: StellarPathPaymentOp): async def confirm_payment_op(ctx, op: StellarPaymentOp): content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, 'Pay ' + format_amount(op.amount, ticker=False), - ui.BOLD, format_asset_code(op.asset) + ' to:', + ui.BOLD, 'Pay %s' % format_amount(op.amount, ticker=False), + ui.BOLD, '%s to:' % format_asset_code(op.asset), ui.MONO, *split(trim_to_rows(format_address(op.destination_account), 3)), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) @@ -201,23 +199,22 @@ async def confirm_set_options_op(ctx, op: StellarSetOptionsOp): await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) if op.signer_type is not None: if op.signer_weight > 0: - text = 'Add Signer' + text = 'Add Signer (%s)' else: - text = 'Remove Signer' + text = 'Remove Signer (%s)' if op.signer_type == consts.SIGN_TYPE_ACCOUNT: - text += ' (acc)' content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, text, + ui.BOLD, text % 'acc', ui.MONO, *split(format_address(op.signer_key)), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) - elif op.signer_type in [consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH]: + elif op.signer_type in (consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH): if op.signer_type == consts.SIGN_TYPE_PRE_AUTH: - text += ' (auth)' + signer_type = 'auth' else: - text += ' (hash)' + signer_type = 'hash' content = Text('Confirm operation', ui.ICON_CONFIRM, - ui.BOLD, text, + ui.BOLD, text % signer_type, ui.MONO, *split(hexlify(op.signer_key).decode()), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) @@ -225,20 +222,20 @@ async def confirm_set_options_op(ctx, op: StellarSetOptionsOp): raise ValueError('Stellar: invalid signer type') -def _format_thresholds(op: StellarSetOptionsOp) -> (): +def _format_thresholds(op: StellarSetOptionsOp) -> tuple: text = () if op.master_weight is not None: - text += ('Master Weight: ' + str(op.master_weight), ) + text += ('Master Weight: %d' % op.master_weight, ) if op.low_threshold is not None: - text += ('Low: ' + str(op.low_threshold), ) + text += ('Low: %d' % op.low_threshold, ) if op.medium_threshold is not None: - text += ('Medium: ' + str(op.medium_threshold), ) + text += ('Medium: %d' % op.medium_threshold, ) if op.high_threshold is not None: - text += ('High: ' + str(op.high_threshold), ) + text += ('High: %d' % op.high_threshold, ) return text -def _format_flags(flags: int) -> (): +def _format_flags(flags: int) -> tuple: if flags > consts.FLAGS_MAX_SIZE: raise ValueError('Stellar: invalid') text = () @@ -261,7 +258,7 @@ async def confirm_asset_issuer(ctx, asset: StellarAssetType): if asset is None or asset.type == consts.ASSET_TYPE_NATIVE: return content = Text('Confirm issuer', ui.ICON_CONFIRM, - ui.BOLD, asset.code + ' issuer:', + ui.BOLD, '%s issuer:' % asset.code, ui.MONO, *split(format_address(asset.issuer)), icon_color=ui.GREEN) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) diff --git a/src/apps/stellar/operations/serialize.py b/src/apps/stellar/operations/serialize.py index 16ace5cdce..248dd1bf0d 100644 --- a/src/apps/stellar/operations/serialize.py +++ b/src/apps/stellar/operations/serialize.py @@ -1,3 +1,5 @@ +from apps.stellar import writers +from apps.stellar import consts from trezor.messages.StellarAccountMergeOp import StellarAccountMergeOp from trezor.messages.StellarAssetType import StellarAssetType from trezor.messages.StellarAllowTrustOp import StellarAllowTrustOp @@ -10,156 +12,154 @@ from trezor.messages.StellarManageOfferOp import StellarManageOfferOp from trezor.messages.StellarPathPaymentOp import StellarPathPaymentOp from trezor.messages.StellarPaymentOp import StellarPaymentOp from trezor.messages.StellarSetOptionsOp import StellarSetOptionsOp -from apps.stellar.consts import * -from apps.stellar.writers import * def serialize_account_merge_op(w, msg: StellarAccountMergeOp): - write_pubkey(w, msg.destination_account) + writers.write_pubkey(w, msg.destination_account) def serialize_allow_trust_op(w, msg: StellarAllowTrustOp): # trustor account (the account being allowed to access the asset) - write_pubkey(w, msg.trusted_account) - write_uint32(w, msg.asset_type) + writers.write_pubkey(w, msg.trusted_account) + writers.write_uint32(w, msg.asset_type) _serialize_asset_code(w, msg.asset_type, msg.asset_code) - write_bool(w, msg.is_authorized) + writers.write_bool(w, msg.is_authorized) def serialize_bump_sequence_op(w, msg: StellarBumpSequenceOp): - write_uint64(w, msg.bump_to) + writers.write_uint64(w, msg.bump_to) def serialize_change_trust_op(w, msg: StellarChangeTrustOp): _serialize_asset(w, msg.asset) - write_uint64(w, msg.limit) + writers.write_uint64(w, msg.limit) def serialize_create_account_op(w, msg: StellarCreateAccountOp): - write_pubkey(w, msg.new_account) - write_uint64(w, msg.starting_balance) + writers.write_pubkey(w, msg.new_account) + writers.write_uint64(w, msg.starting_balance) def serialize_create_passive_offer_op(w, msg: StellarCreatePassiveOfferOp): _serialize_asset(w, msg.selling_asset) _serialize_asset(w, msg.buying_asset) - write_uint64(w, msg.amount) - write_uint32(w, msg.price_n) - write_uint32(w, msg.price_d) + writers.write_uint64(w, msg.amount) + writers.write_uint32(w, msg.price_n) + writers.write_uint32(w, msg.price_d) def serialize_manage_data_op(w, msg: StellarManageDataOp): if len(msg.key) > 64: raise ValueError('Stellar: max length of a key is 64 bytes') - write_string(w, msg.key) - write_bool(w, bool(msg.value)) + writers.write_string(w, msg.key) + writers.write_bool(w, bool(msg.value)) if msg.value: - write_uint32(w, len(msg.value)) - write_bytes(w, msg.value) + writers.write_uint32(w, len(msg.value)) + writers.write_bytes(w, msg.value) def serialize_manage_offer_op(w, msg: StellarManageOfferOp): _serialize_asset(w, msg.selling_asset) _serialize_asset(w, msg.buying_asset) - write_uint64(w, msg.amount) # amount to sell - write_uint32(w, msg.price_n) # numerator - write_uint32(w, msg.price_d) # denominator - write_uint64(w, msg.offer_id) + writers.write_uint64(w, msg.amount) # amount to sell + writers.write_uint32(w, msg.price_n) # numerator + writers.write_uint32(w, msg.price_d) # denominator + writers.write_uint64(w, msg.offer_id) def serialize_path_payment_op(w, msg: StellarPathPaymentOp): _serialize_asset(w, msg.send_asset) - write_uint64(w, msg.send_max) - write_pubkey(w, msg.destination_account) + writers.write_uint64(w, msg.send_max) + writers.write_pubkey(w, msg.destination_account) _serialize_asset(w, msg.destination_asset) - write_uint64(w, msg.destination_amount) - write_uint32(w, len(msg.paths)) + writers.write_uint64(w, msg.destination_amount) + writers.write_uint32(w, len(msg.paths)) for p in msg.paths: _serialize_asset(w, p) def serialize_payment_op(w, msg: StellarPaymentOp): - write_pubkey(w, msg.destination_account) + writers.write_pubkey(w, msg.destination_account) _serialize_asset(w, msg.asset) - write_uint64(w, msg.amount) + writers.write_uint64(w, msg.amount) def serialize_set_options_op(w, msg: StellarSetOptionsOp): # inflation destination - write_bool(w, bool(msg.inflation_destination_account)) + writers.write_bool(w, bool(msg.inflation_destination_account)) if msg.inflation_destination_account: - write_pubkey(w, msg.inflation_destination_account) + writers.write_pubkey(w, msg.inflation_destination_account) # clear flags - write_bool(w, bool(msg.clear_flags)) + writers.write_bool(w, bool(msg.clear_flags)) if msg.clear_flags: - write_uint32(w, msg.clear_flags) + writers.write_uint32(w, msg.clear_flags) # set flags - write_bool(w, bool(msg.set_flags)) + writers.write_bool(w, bool(msg.set_flags)) if msg.set_flags: - write_uint32(w, msg.set_flags) + writers.write_uint32(w, msg.set_flags) # account thresholds - write_bool(w, bool(msg.master_weight)) + writers.write_bool(w, bool(msg.master_weight)) if msg.master_weight: - write_uint32(w, msg.master_weight) + writers.write_uint32(w, msg.master_weight) - write_bool(w, bool(msg.low_threshold)) + writers.write_bool(w, bool(msg.low_threshold)) if msg.low_threshold: - write_uint32(w, msg.low_threshold) + writers.write_uint32(w, msg.low_threshold) - write_bool(w, bool(msg.medium_threshold)) + writers.write_bool(w, bool(msg.medium_threshold)) if msg.medium_threshold: - write_uint32(w, msg.medium_threshold) + writers.write_uint32(w, msg.medium_threshold) - write_bool(w, bool(msg.high_threshold)) + writers.write_bool(w, bool(msg.high_threshold)) if msg.high_threshold: - write_uint32(w, msg.high_threshold) + writers.write_uint32(w, msg.high_threshold) # home domain - write_bool(w, bool(msg.home_domain)) + writers.write_bool(w, bool(msg.home_domain)) if msg.home_domain: if len(msg.home_domain) > 32: raise ValueError('Stellar: max length of a home domain is 32 bytes') - write_string(w, msg.home_domain) + writers.write_string(w, msg.home_domain) # signer - write_bool(w, bool(msg.signer_type)) + writers.write_bool(w, bool(msg.signer_type)) if msg.signer_type: # signer type - write_uint32(w, msg.signer_type) - write_bytes(w, msg.signer_key) - write_uint32(w, msg.signer_weight) + writers.write_uint32(w, msg.signer_type) + writers.write_bytes(w, msg.signer_key) + writers.write_uint32(w, msg.signer_weight) def serialize_account(w, source_account: bytes): if source_account is None: - write_bool(w, False) + writers.write_bool(w, False) return - write_pubkey(w, source_account) + writers.write_pubkey(w, source_account) def _serialize_asset_code(w, asset_type: int, asset_code: str): code = bytearray(asset_code) - if asset_type == ASSET_TYPE_NATIVE: + if asset_type == consts.ASSET_TYPE_NATIVE: return # nothing is needed - elif asset_type == ASSET_TYPE_ALPHANUM4: + elif asset_type == consts.ASSET_TYPE_ALPHANUM4: # pad with zeros to 4 chars - write_bytes(w, code + bytearray([0] * (4 - len(code)))) - elif asset_type == ASSET_TYPE_ALPHANUM12: + writers.write_bytes(w, code + bytearray([0] * (4 - len(code)))) + elif asset_type == consts.ASSET_TYPE_ALPHANUM12: # pad with zeros to 12 chars - write_bytes(w, code + bytearray([0] * (12 - len(code)))) + writers.write_bytes(w, code + bytearray([0] * (12 - len(code)))) else: raise ValueError('Stellar: invalid asset type') def _serialize_asset(w, asset: StellarAssetType): if asset is None: - write_uint32(w, 0) + writers.write_uint32(w, 0) return - write_uint32(w, asset.type) + writers.write_uint32(w, asset.type) _serialize_asset_code(w, asset.type, asset.code) - write_pubkey(w, asset.issuer) + writers.write_pubkey(w, asset.issuer) diff --git a/src/apps/stellar/sign_tx.py b/src/apps/stellar/sign_tx.py index fd05abc998..e1503ecd04 100644 --- a/src/apps/stellar/sign_tx.py +++ b/src/apps/stellar/sign_tx.py @@ -1,9 +1,8 @@ from apps.common import seed -from apps.stellar.writers import * +from apps.stellar import writers from apps.stellar.operations import operation from apps.stellar import layout from apps.stellar import consts -from apps.stellar import helpers from trezor.messages.StellarSignTx import StellarSignTx from trezor.messages.StellarTxOpRequest import StellarTxOpRequest from trezor.messages.StellarSignedTx import StellarSignedTx @@ -16,73 +15,17 @@ async def sign_tx(ctx, msg: StellarSignTx): if msg.num_operations == 0: raise ValueError('Stellar: At least one operation is required') - network_passphrase_hash = sha256(msg.network_passphrase).digest() - - # Stellar transactions consist of sha256 of: - # - sha256(network passphrase) - # - 4-byte unsigned big-endian int type constant (2 for tx) - # - public key - - w = bytearray() - write_bytes(w, network_passphrase_hash) - write_bytes(w, consts.TX_TYPE) - node = await seed.derive_node(ctx, msg.address_n, consts.STELLAR_CURVE) pubkey = seed.remove_ed25519_public_key_prefix(node.public_key()) - write_pubkey(w, pubkey) - if msg.source_account != pubkey: - raise ValueError('Stellar: source account does not match address_n') - # confirm init - await layout.require_confirm_init(ctx, pubkey, msg.network_passphrase) - - write_uint32(w, msg.fee) - write_uint64(w, msg.sequence_number) - - # timebounds are only present if timebounds_start or timebounds_end is non-zero - if msg.timebounds_start or msg.timebounds_end: - write_bool(w, True) - # timebounds are sent as uint32s since that's all we can display, but they must be hashed as 64bit - write_uint64(w, msg.timebounds_start) - write_uint64(w, msg.timebounds_end) - else: - write_bool(w, False) - - write_uint32(w, msg.memo_type) - if msg.memo_type == consts.MEMO_TYPE_NONE: - # nothing is serialized - memo_confirm_text = '' - elif msg.memo_type == consts.MEMO_TYPE_TEXT: - # Text: 4 bytes (size) + up to 28 bytes - if len(msg.memo_text) > 28: - raise ValueError('Stellar: max length of a memo text is 28 bytes') - write_string(w, msg.memo_text) - memo_confirm_text = msg.memo_text - elif msg.memo_type == consts.MEMO_TYPE_ID: - # ID: 64 bit unsigned integer - write_uint64(w, msg.memo_id) - memo_confirm_text = str(msg.memo_id) - elif msg.memo_type in [consts.MEMO_TYPE_HASH, consts.MEMO_TYPE_RETURN]: - # Hash/Return: 32 byte hash - write_bytes(w, bytearray(msg.memo_hash)) - memo_confirm_text = hexlify(msg.memo_hash).decode() - else: - raise ValueError('Stellar invalid memo type') - await layout.require_confirm_memo(ctx, msg.memo_type, memo_confirm_text) - - write_uint32(w, msg.num_operations) - for i in range(msg.num_operations): - op = await ctx.call(StellarTxOpRequest(), *consts.op_wire_types) - await operation(ctx, w, op) - - # 4 null bytes representing a (currently unused) empty union - write_uint32(w, 0) - - # final confirm - await layout.require_confirm_final(ctx, msg.fee, msg.num_operations) + w = bytearray() + await _init(ctx, w, pubkey, msg) + _timebounds(w, msg.timebounds_start, msg.timebounds_end) + await _memo(ctx, w, msg) + await _operations(ctx, w, msg.num_operations) + await _final(ctx, w, msg) # sign - # (note that the signature does not include the 4-byte hint since it can be calculated from the public key) digest = sha256(w).digest() signature = ed25519.sign(node.private_key(), digest) @@ -90,6 +33,70 @@ async def sign_tx(ctx, msg: StellarSignTx): return StellarSignedTx(pubkey, signature) +async def _final(ctx, w: bytearray, msg: StellarSignTx): + # 4 null bytes representing a (currently unused) empty union + writers.write_uint32(w, 0) + # final confirm + await layout.require_confirm_final(ctx, msg.fee, msg.num_operations) + + +async def _init(ctx, w: bytearray, pubkey: bytes, msg: StellarSignTx): + network_passphrase_hash = sha256(msg.network_passphrase).digest() + writers.write_bytes(w, network_passphrase_hash) + writers.write_bytes(w, consts.TX_TYPE) + + writers.write_pubkey(w, pubkey) + if msg.source_account != pubkey: + raise ValueError('Stellar: source account does not match address_n') + writers.write_uint32(w, msg.fee) + writers.write_uint64(w, msg.sequence_number) + + # confirm init + await layout.require_confirm_init(ctx, pubkey, msg.network_passphrase) + + +def _timebounds(w: bytearray, start: int, end: int): + # timebounds are only present if timebounds_start or timebounds_end is non-zero + if start or end: + writers.write_bool(w, True) + # timebounds are sent as uint32s since that's all we can display, but they must be hashed as 64bit + writers.write_uint64(w, start) + writers.write_uint64(w, end) + else: + writers.write_bool(w, False) + + +async def _operations(ctx, w: bytearray, num_operations: int): + writers.write_uint32(w, num_operations) + for i in range(num_operations): + op = await ctx.call(StellarTxOpRequest(), *consts.op_wire_types) + await operation(ctx, w, op) + + +async def _memo(ctx, w: bytearray, msg: StellarSignTx): + writers.write_uint32(w, msg.memo_type) + if msg.memo_type == consts.MEMO_TYPE_NONE: + # nothing is serialized + memo_confirm_text = '' + elif msg.memo_type == consts.MEMO_TYPE_TEXT: + # Text: 4 bytes (size) + up to 28 bytes + if len(msg.memo_text) > 28: + raise ValueError('Stellar: max length of a memo text is 28 bytes') + writers.write_string(w, msg.memo_text) + memo_confirm_text = msg.memo_text + elif msg.memo_type == consts.MEMO_TYPE_ID: + # ID: 64 bit unsigned integer + writers.write_uint64(w, msg.memo_id) + memo_confirm_text = str(msg.memo_id) + elif msg.memo_type in (consts.MEMO_TYPE_HASH, consts.MEMO_TYPE_RETURN): + # Hash/Return: 32 byte hash + writers.write_bytes(w, bytearray(msg.memo_hash)) + memo_confirm_text = hexlify(msg.memo_hash).decode() + else: + raise ValueError('Stellar invalid memo type') + await layout.require_confirm_memo(ctx, msg.memo_type, memo_confirm_text) + + def node_derive(root, address_n: list): node = root.clone() node.derive_path(address_n)