diff --git a/src/apps/stellar/consts.py b/src/apps/stellar/consts.py index 87c9bd606d..55a0b41e39 100644 --- a/src/apps/stellar/consts.py +++ b/src/apps/stellar/consts.py @@ -33,6 +33,14 @@ op_wire_types = [ ASSET_TYPE_CREDIT_ALPHANUM4 = const(1) ASSET_TYPE_CREDIT_ALPHANUM12 = const(2) +# https://www.stellar.org/developers/guides/concepts/accounts.html#balance +# https://github.com/stellar/go/blob/master/amount/main.go +AMOUNT_DIVISIBILITY = const(7) + +# https://github.com/stellar/go/blob/master/network/main.go +NETWORK_PASSPHRASE_PUBLIC = 'Public Global Stellar Network ; September 2015' +NETWORK_PASSPHRASE_TESTNET = 'Test SDF Network ; September 2015' + def get_op_code(msg) -> int: if msg.__qualname__ not in op_codes: diff --git a/src/apps/stellar/get_public_key.py b/src/apps/stellar/get_public_key.py index 9bd5958abf..2768f556c2 100644 --- a/src/apps/stellar/get_public_key.py +++ b/src/apps/stellar/get_public_key.py @@ -1,13 +1,12 @@ from apps.common import seed from apps.common.confirm import confirm +from apps.stellar import helpers from trezor import ui from trezor.messages.StellarPublicKey import StellarPublicKey from trezor.messages.StellarGetPublicKey import StellarGetPublicKey from trezor.messages import ButtonRequestType from trezor.ui.text import Text from trezor.utils import chunks -from trezor.crypto import base32 -import ustruct STELLAR_CURVE = 'ed25519' @@ -17,7 +16,7 @@ async def get_public_key(ctx, msg: StellarGetPublicKey): pubkey = seed.remove_ed25519_public_key_prefix(node.public_key()) # todo better? while True: - if await _show(ctx, _address_from_public_key(pubkey)): + if await _show(ctx, helpers.address_from_public_key(pubkey)): break return StellarPublicKey(public_key=pubkey) @@ -39,35 +38,3 @@ async def _show(ctx, address: str): def _split_address(address: str): return chunks(address, 17) - - -def _address_from_public_key(pubkey: bytes): - """Returns the base32-encoded version of public key bytes (G...)""" - - address = bytearray() - address.append(6 << 3) # version -> 'G' - address.extend(pubkey) - address.extend(ustruct.pack("> (7 - i) & 1) == 1) - c15 = ((crc >> 15 & 1) == 1) - crc <<= 1 - if c15 ^ bit: - crc ^= polynomial - - return crc & 0xffff diff --git a/src/apps/stellar/helpers.py b/src/apps/stellar/helpers.py new file mode 100644 index 0000000000..7e988c4cce --- /dev/null +++ b/src/apps/stellar/helpers.py @@ -0,0 +1,56 @@ +from trezor.crypto import base32 +import ustruct + + +class UiConfirmInit: + + def __init__(self, pubkey: bytes, network: str): + self.pubkey = pubkey + self.network = network + + +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_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...)""" + + address = bytearray() + address.append(6 << 3) # version -> 'G' + address.extend(pubkey) + address.extend(ustruct.pack("> (7 - i) & 1) == 1) + c15 = ((crc >> 15 & 1) == 1) + crc <<= 1 + if c15 ^ bit: + crc ^= polynomial + + return crc & 0xffff diff --git a/src/apps/stellar/layout.py b/src/apps/stellar/layout.py new file mode 100644 index 0000000000..fdf2796deb --- /dev/null +++ b/src/apps/stellar/layout.py @@ -0,0 +1,55 @@ +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 helpers +from trezor import ui +from trezor.messages import ButtonRequestType +from trezor.ui.text import Text +from trezor.utils import chunks, format_amount + + +async def require_confirm_init(ctx, pubkey: bytes, network_passphrase: str): + content = Text('Confirm Stellar', ui.ICON_SEND, + ui.NORMAL, 'Initialize singing with', + ui.MONO, *format_address(pubkey), + icon_color=ui.GREEN) + await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + network = get_network_warning(network_passphrase) + if network: + content = Text('Confirm network', ui.ICON_CONFIRM, + ui.NORMAL, 'Transaction is on', + ui.BOLD, network, + 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: + op_str += 's' + 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.NORMAL, 'for fee?', + icon_color=ui.GREEN) + # we use SignTx, not ConfirmOutput, for compatibility with T1 + await require_hold_to_confirm(ctx, content, ButtonRequestType.SignTx) + + +def format_address(pubkey: bytes) -> str: + address = helpers.address_from_public_key(pubkey) + return split_address(address) + + +def split_address(address): + return chunks(address, 17) + + +def get_network_warning(network_passphrase: str): + if network_passphrase == NETWORK_PASSPHRASE_PUBLIC: + return None + if network_passphrase == 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 6b7f5422da..59e3bfbe15 100644 --- a/src/apps/stellar/sign_tx.py +++ b/src/apps/stellar/sign_tx.py @@ -2,6 +2,8 @@ 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 helpers from trezor.messages.StellarSignTx import StellarSignTx from trezor.messages.StellarTxOpRequest import StellarTxOpRequest from trezor.messages.StellarSignedTx import StellarSignedTx @@ -21,6 +23,10 @@ async def sign_tx_loop(ctx, msg: StellarSignTx): res = await ctx.call(req, *op_wire_types) elif isinstance(req, StellarSignedTx): break + elif isinstance(req, helpers.UiConfirmInit): + res = await require_confirm_init(ctx, req.pubkey, req.network) + elif isinstance(req, helpers.UiConfirmFinal): + res = await require_confirm_final(ctx, req.fee, req.num_operations) else: raise TypeError('Stellar: Invalid signing instruction') return req @@ -77,18 +83,13 @@ async def sign_tx(ctx, msg): op = yield StellarTxOpRequest() serialize_op(w, op) - # # Determine what type of network this transaction is for - todo used for layout - # if msg.network_passphrase == "Public Global Stellar Network ; September 2015": - # network_type = 1 - # elif msg.network_passphrase == "Test SDF Network ; September 2015": - # network_type = 2 - # else: - # network_type = 3 - # # todo use network_type in layout - # 4 null bytes representing a (currently unused) empty union write_uint32(w, 0) + # confirms + await helpers.confirm_init(pubkey, msg.network_passphrase) + await helpers.confirm_final(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) digest = sha256(w).digest()