diff --git a/src/apps/stellar/consts.py b/src/apps/stellar/consts.py index 55a0b41e3..cd4c80633 100644 --- a/src/apps/stellar/consts.py +++ b/src/apps/stellar/consts.py @@ -41,6 +41,12 @@ AMOUNT_DIVISIBILITY = const(7) NETWORK_PASSPHRASE_PUBLIC = 'Public Global Stellar Network ; September 2015' NETWORK_PASSPHRASE_TESTNET = 'Test SDF Network ; September 2015' +MEMO_TYPE_NONE = 0 +MEMO_TYPE_TEXT = 1 +MEMO_TYPE_ID = 2 +MEMO_TYPE_HASH = 3 +MEMO_TYPE_RETURN = 4 + def get_op_code(msg) -> int: if msg.__qualname__ not in op_codes: diff --git a/src/apps/stellar/helpers.py b/src/apps/stellar/helpers.py index 7e988c4cc..67f7fc435 100644 --- a/src/apps/stellar/helpers.py +++ b/src/apps/stellar/helpers.py @@ -9,6 +9,13 @@ class UiConfirmInit: 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): @@ -20,6 +27,10 @@ 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)) diff --git a/src/apps/stellar/layout.py b/src/apps/stellar/layout.py index fdf2796de..afa6b3944 100644 --- a/src/apps/stellar/layout.py +++ b/src/apps/stellar/layout.py @@ -1,7 +1,5 @@ from apps.common.confirm import * -from apps.stellar.consts import AMOUNT_DIVISIBILITY -from apps.stellar.consts import NETWORK_PASSPHRASE_PUBLIC -from apps.stellar.consts import NETWORK_PASSPHRASE_TESTNET +from apps.stellar import consts from apps.stellar import helpers from trezor import ui from trezor.messages import ButtonRequestType @@ -24,6 +22,26 @@ async def require_confirm_init(ctx, pubkey: bytes, network_passphrase: str): await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) +async def require_confirm_memo(ctx, memo_type: int, memo_text: str): + if memo_type == consts.MEMO_TYPE_TEXT: + title = 'Memo (TEXT)' + elif memo_type == consts.MEMO_TYPE_ID: + title = 'Memo (ID)' + elif memo_type == consts.MEMO_TYPE_HASH: + title = 'Memo (HASH)' + elif memo_type == consts.MEMO_TYPE_RETURN: + title = 'Memo (RETURN)' + else: # MEMO_TYPE_NONE + title = 'No memo set!' + # todo ugly + memo_text = 'Important: Many exchanges require a memo when depositing' + content = Text('Confirm memo', ui.ICON_CONFIRM, + ui.BOLD, title, + ui.MONO, *split(memo_text), + icon_color=ui.GREEN) + await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + + async def require_confirm_final(ctx, fee: int, num_operations: int): op_str = str(num_operations) + ' operation' if num_operations > 1: @@ -31,7 +49,7 @@ async def require_confirm_final(ctx, fee: int, num_operations: int): content = Text('Final confirm', ui.ICON_SEND, ui.NORMAL, 'Sign this transaction', ui.NORMAL, 'made up of ' + op_str, - ui.BOLD, 'and pay ' + format_amount(fee, AMOUNT_DIVISIBILITY) + ' XLM', + ui.BOLD, 'and pay ' + format_amount(fee, consts.AMOUNT_DIVISIBILITY) + ' XLM', ui.NORMAL, 'for fee?', icon_color=ui.GREEN) # we use SignTx, not ConfirmOutput, for compatibility with T1 @@ -40,16 +58,16 @@ async def require_confirm_final(ctx, fee: int, num_operations: int): def format_address(pubkey: bytes) -> str: address = helpers.address_from_public_key(pubkey) - return split_address(address) + return split(address) -def split_address(address): - return chunks(address, 17) +def split(text): + return chunks(text, 17) def get_network_warning(network_passphrase: str): - if network_passphrase == NETWORK_PASSPHRASE_PUBLIC: + if network_passphrase == consts.NETWORK_PASSPHRASE_PUBLIC: return None - if network_passphrase == NETWORK_PASSPHRASE_TESTNET: + if network_passphrase == consts.NETWORK_PASSPHRASE_TESTNET: return 'testnet network' return 'private network' diff --git a/src/apps/stellar/sign_tx.py b/src/apps/stellar/sign_tx.py index 59e3bfbe1..de8b4f308 100644 --- a/src/apps/stellar/sign_tx.py +++ b/src/apps/stellar/sign_tx.py @@ -1,14 +1,15 @@ from apps.common import seed from apps.stellar.writers import * from apps.stellar.operations import serialize_op -from apps.stellar.consts import op_wire_types -from apps.stellar.layout import require_confirm_init, require_confirm_final +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 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') @@ -20,13 +21,15 @@ async def sign_tx_loop(ctx, msg: StellarSignTx): while True: req = signer.send(res) if isinstance(req, StellarTxOpRequest): - res = await ctx.call(req, *op_wire_types) + res = await ctx.call(req, *consts.op_wire_types) elif isinstance(req, StellarSignedTx): break elif isinstance(req, helpers.UiConfirmInit): - res = await require_confirm_init(ctx, req.pubkey, req.network) + 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 require_confirm_final(ctx, req.fee, req.num_operations) + res = await layout.require_confirm_final(ctx, req.fee, req.num_operations) else: raise TypeError('Stellar: Invalid signing instruction') return req @@ -53,6 +56,9 @@ async def sign_tx(ctx, msg): 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) + write_uint32(w, msg.fee) write_uint64(w, msg.sequence_number) @@ -66,17 +72,26 @@ async def sign_tx(ctx, msg): write_bool(w, False) write_uint32(w, msg.memo_type) - if msg.memo_type == 1: # nothing is needed for memo_type = 0 + 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) - elif msg.memo_type == 2: + 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) - elif msg.memo_type in [3, 4]: + 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 helpers.confirm_memo(msg.memo_type, memo_confirm_text) write_uint32(w, msg.num_operations) for i in range(msg.num_operations): @@ -87,7 +102,6 @@ async def sign_tx(ctx, msg): write_uint32(w, 0) # confirms - await helpers.confirm_init(pubkey, msg.network_passphrase) await helpers.confirm_final(msg.fee, msg.num_operations) # sign