From a26aaec9535bf2ccfe9389eb4a0867ee59236610 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Tue, 5 Jun 2018 16:29:12 +0200 Subject: [PATCH] stellar: Context is used instead of loop --- src/apps/stellar/__init__.py | 4 +- src/apps/stellar/helpers.py | 33 ----- src/apps/stellar/operations/serialize.py | 165 +++++++++++++++++++++++ src/apps/stellar/sign_tx.py | 51 ++----- 4 files changed, 179 insertions(+), 74 deletions(-) create mode 100644 src/apps/stellar/operations/serialize.py diff --git a/src/apps/stellar/__init__.py b/src/apps/stellar/__init__.py index a1a032999..28f380a1a 100644 --- a/src/apps/stellar/__init__.py +++ b/src/apps/stellar/__init__.py @@ -9,8 +9,8 @@ def dispatch_StellarGetPublicKey(*args, **kwargs): def dispatch_StellarSignTx(*args, **kwargs): - from .sign_tx import sign_tx_loop - return sign_tx_loop(*args, **kwargs) + from .sign_tx import sign_tx + return sign_tx(*args, **kwargs) def boot(): diff --git a/src/apps/stellar/helpers.py b/src/apps/stellar/helpers.py index 67f7fc435..51cb7a105 100644 --- a/src/apps/stellar/helpers.py +++ b/src/apps/stellar/helpers.py @@ -2,39 +2,6 @@ from trezor.crypto import base32 import ustruct -class UiConfirmInit: - - def __init__(self, pubkey: bytes, network: str): - self.pubkey = pubkey - self.network = network - - -class UiConfirmMemo: - - def __init__(self, memo_type: int, memo_text: str): - self.memo_type = memo_type - self.memo_text = memo_text - - -class UiConfirmFinal: - - def __init__(self, fee: int, num_operations: int): - self.fee = fee - self.num_operations = num_operations - - -def confirm_init(pubkey: bytes, network: str): - return (yield UiConfirmInit(pubkey, network)) - - -def confirm_memo(memo_type: int, memo_text: str): - return (yield UiConfirmMemo(memo_type, memo_text)) - - -def confirm_final(fee: int, num_operations: int): - return (yield UiConfirmFinal(fee, num_operations)) - - def address_from_public_key(pubkey: bytes): """Returns the base32-encoded version of public key bytes (G...)""" diff --git a/src/apps/stellar/operations/serialize.py b/src/apps/stellar/operations/serialize.py new file mode 100644 index 000000000..16ace5cdc --- /dev/null +++ b/src/apps/stellar/operations/serialize.py @@ -0,0 +1,165 @@ +from trezor.messages.StellarAccountMergeOp import StellarAccountMergeOp +from trezor.messages.StellarAssetType import StellarAssetType +from trezor.messages.StellarAllowTrustOp import StellarAllowTrustOp +from trezor.messages.StellarBumpSequenceOp import StellarBumpSequenceOp +from trezor.messages.StellarChangeTrustOp import StellarChangeTrustOp +from trezor.messages.StellarCreateAccountOp import StellarCreateAccountOp +from trezor.messages.StellarCreatePassiveOfferOp import StellarCreatePassiveOfferOp +from trezor.messages.StellarManageDataOp import StellarManageDataOp +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) + + +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) + _serialize_asset_code(w, msg.asset_type, msg.asset_code) + + write_bool(w, msg.is_authorized) + + +def serialize_bump_sequence_op(w, msg: StellarBumpSequenceOp): + write_uint64(w, msg.bump_to) + + +def serialize_change_trust_op(w, msg: StellarChangeTrustOp): + _serialize_asset(w, msg.asset) + 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) + + +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) + + +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)) + if msg.value: + write_uint32(w, len(msg.value)) + 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) + + +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) + + _serialize_asset(w, msg.destination_asset) + write_uint64(w, msg.destination_amount) + 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) + _serialize_asset(w, msg.asset) + write_uint64(w, msg.amount) + + +def serialize_set_options_op(w, msg: StellarSetOptionsOp): + # inflation destination + write_bool(w, bool(msg.inflation_destination_account)) + if msg.inflation_destination_account: + write_pubkey(w, msg.inflation_destination_account) + + # clear flags + write_bool(w, bool(msg.clear_flags)) + if msg.clear_flags: + write_uint32(w, msg.clear_flags) + + # set flags + write_bool(w, bool(msg.set_flags)) + if msg.set_flags: + write_uint32(w, msg.set_flags) + + # account thresholds + write_bool(w, bool(msg.master_weight)) + if msg.master_weight: + write_uint32(w, msg.master_weight) + + write_bool(w, bool(msg.low_threshold)) + if msg.low_threshold: + write_uint32(w, msg.low_threshold) + + write_bool(w, bool(msg.medium_threshold)) + if msg.medium_threshold: + write_uint32(w, msg.medium_threshold) + + write_bool(w, bool(msg.high_threshold)) + if msg.high_threshold: + write_uint32(w, msg.high_threshold) + + # home domain + 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) + + # signer + 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) + + +def serialize_account(w, source_account: bytes): + if source_account is None: + write_bool(w, False) + return + 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: + return # nothing is needed + elif asset_type == ASSET_TYPE_ALPHANUM4: + # pad with zeros to 4 chars + write_bytes(w, code + bytearray([0] * (4 - len(code)))) + elif asset_type == ASSET_TYPE_ALPHANUM12: + # pad with zeros to 12 chars + 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) + return + write_uint32(w, asset.type) + _serialize_asset_code(w, asset.type, asset.code) + write_pubkey(w, asset.issuer) diff --git a/src/apps/stellar/sign_tx.py b/src/apps/stellar/sign_tx.py index de8b4f308..fd05abc99 100644 --- a/src/apps/stellar/sign_tx.py +++ b/src/apps/stellar/sign_tx.py @@ -1,6 +1,6 @@ from apps.common import seed from apps.stellar.writers import * -from apps.stellar.operations import serialize_op +from apps.stellar.operations import operation from apps.stellar import layout from apps.stellar import consts from apps.stellar import helpers @@ -11,31 +11,8 @@ from trezor.crypto.curve import ed25519 from trezor.crypto.hashlib import sha256 from ubinascii import hexlify -STELLAR_CURVE = 'ed25519' -TX_TYPE = bytearray('\x00\x00\x00\x02') - - -async def sign_tx_loop(ctx, msg: StellarSignTx): - signer = sign_tx(msg, msg) - res = None - while True: - req = signer.send(res) - if isinstance(req, StellarTxOpRequest): - res = await ctx.call(req, *consts.op_wire_types) - elif isinstance(req, StellarSignedTx): - break - elif isinstance(req, helpers.UiConfirmInit): - res = await layout.require_confirm_init(ctx, req.pubkey, req.network) - elif isinstance(req, helpers.UiConfirmMemo): - res = await layout.require_confirm_memo(ctx, req.memo_type, req.memo_text) - elif isinstance(req, helpers.UiConfirmFinal): - res = await layout.require_confirm_final(ctx, req.fee, req.num_operations) - else: - raise TypeError('Stellar: Invalid signing instruction') - return req - - -async def sign_tx(ctx, msg): + +async def sign_tx(ctx, msg: StellarSignTx): if msg.num_operations == 0: raise ValueError('Stellar: At least one operation is required') @@ -48,16 +25,16 @@ async def sign_tx(ctx, msg): w = bytearray() write_bytes(w, network_passphrase_hash) - write_bytes(w, TX_TYPE) + write_bytes(w, consts.TX_TYPE) - node = await seed.derive_node(ctx, msg.address_n, STELLAR_CURVE) + 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 helpers.confirm_init(pubkey, msg.network_passphrase) + await layout.require_confirm_init(ctx, pubkey, msg.network_passphrase) write_uint32(w, msg.fee) write_uint64(w, msg.sequence_number) @@ -91,18 +68,18 @@ async def sign_tx(ctx, msg): memo_confirm_text = hexlify(msg.memo_hash).decode() else: raise ValueError('Stellar invalid memo type') - await helpers.confirm_memo(msg.memo_type, memo_confirm_text) + 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 = yield StellarTxOpRequest() - serialize_op(w, op) + 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) - # confirms - await helpers.confirm_final(msg.fee, msg.num_operations) + # final confirm + await layout.require_confirm_final(ctx, msg.fee, msg.num_operations) # sign # (note that the signature does not include the 4-byte hint since it can be calculated from the public key) @@ -110,11 +87,7 @@ async def sign_tx(ctx, msg): signature = ed25519.sign(node.private_key(), digest) # Add the public key for verification that the right account was used for signing - resp = StellarSignedTx() - resp.public_key = pubkey - resp.signature = signature - - yield resp + return StellarSignedTx(pubkey, signature) def node_derive(root, address_n: list):