diff --git a/src/apps/nem/helpers.py b/src/apps/nem/helpers.py index a68961aea4..22a23fd865 100644 --- a/src/apps/nem/helpers.py +++ b/src/apps/nem/helpers.py @@ -22,3 +22,6 @@ NEM_SALT_SIZE = const(32) AES_BLOCK_SIZE = const(16) NEM_HASH_ALG = 'keccak' NEM_PUBLIC_KEY_SIZE = const(32) # ed25519 public key + +NEM_MAX_PLAIN_PAYLOAD_SIZE = const(1024) +NEM_MAX_ENCRYPTED_PAYLOAD_SIZE = const(960) diff --git a/src/apps/nem/layout.py b/src/apps/nem/layout.py index e9f9216966..a972399ee6 100644 --- a/src/apps/nem/layout.py +++ b/src/apps/nem/layout.py @@ -2,28 +2,80 @@ from apps.common.confirm import * from trezor import ui from trezor.messages import ButtonRequestType from trezor.messages.NEMMosaicDefinition import NEMMosaicDefinition +from trezor.messages import NEMMosaicLevy from trezor.ui.text import Text from trezor.ui.scroll import Scrollpage, animate_swipe, paginate from trezor.utils import chunks, format_amount, split_words from .helpers import * -async def require_confirm_tx(ctx, recipient, value): - content = Text('Confirm sending', ui.ICON_SEND, - ui.BOLD, format_amount(value, NEM_MAX_DIVISIBILITY) + ' NEM', +async def require_confirm_action(ctx, action: str): + content = Text('Confirm action', ui.ICON_SEND, + ui.NORMAL, *split_words(action, 18), + icon_color=ui.GREEN) + await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + + +async def require_confirm_fee(ctx, action: str, fee: int): + content = Text('Confirm fee', ui.ICON_SEND, + ui.NORMAL, action, + ui.BOLD, format_amount(fee, NEM_MAX_DIVISIBILITY) + ' XEM', + icon_color=ui.GREEN) + await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + + +async def require_confirm_transfer(ctx, recipient, value): + content = Text('Confirm transfer', ui.ICON_SEND, + ui.BOLD, 'Send ' + format_amount(value, NEM_MAX_DIVISIBILITY) + ' XEM', ui.NORMAL, 'to', ui.MONO, *split_address(recipient), icon_color=ui.GREEN) + await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + + +async def require_confirm_address(ctx, action: str, address: str): + content = Text('Confirm address', ui.ICON_SEND, + ui.NORMAL, action, + ui.MONO, *split_address(address), + icon_color=ui.GREEN) + await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + + +async def require_confirm_final(ctx, fee: int): + content = Text('Final confirm', ui.ICON_SEND, + ui.NORMAL, 'Sign this transaction', + ui.BOLD, 'and pay ' + format_amount(fee, NEM_MAX_DIVISIBILITY) + ' XEM', + ui.NORMAL, 'for transaction fee?', + icon_color=ui.GREEN) await require_hold_to_confirm(ctx, content, ButtonRequestType.SignTx) # we use SignTx, not ConfirmOutput, for compatibility with T1 -async def require_confirm_action(ctx, action: str): - content = Text('Confirm sending', ui.ICON_SEND, - ui.NORMAL, *split_words(action, 18), - icon_color=ui.GREEN) +async def require_confirm_payload(ctx, payload: bytes, encrypt=False): + payload = str(payload, 'utf-8') + + if len(payload) > 48: + payload = payload[:48] + '..' + print(len(payload)) + if encrypt: + content = Text('Confirm payload', ui.ICON_SEND, + ui.BOLD, 'Encrypted:', + ui.NORMAL, *split_words(payload, 22), + icon_color=ui.GREEN) + else: + content = Text('Confirm payload', ui.ICON_SEND, + ui.BOLD, 'Unencrypted:', + ui.NORMAL, *split_words(payload, 22), + icon_color=ui.RED) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) +async def require_confirm_properties(ctx, definition: NEMMosaicDefinition): + properties = _get_mosaic_properties(definition) + first_page = const(0) + paginator = paginate(_show_page, len(properties), first_page, properties) + await ctx.wait(paginator) + + @ui.layout async def _show_page(page: int, page_count: int, content): content = Scrollpage(content[page], page, page_count) @@ -34,13 +86,6 @@ async def _show_page(page: int, page_count: int, content): await animate_swipe() -async def require_confirm_properties(ctx, definition: NEMMosaicDefinition): - properties = _get_mosaic_properties(definition) - first_page = const(0) - paginator = paginate(_show_page, len(properties), first_page, properties) - await ctx.wait(paginator) - - def _get_mosaic_properties(definition: NEMMosaicDefinition): properties = [] if definition.description: @@ -63,31 +108,34 @@ def _get_mosaic_properties(definition: NEMMosaicDefinition): if definition.supply: t = Text('Confirm properties', ui.ICON_SEND, ui.BOLD, 'Initial supply:', - ui.NORMAL, format_amount(definition.supply, definition.divisibility), + ui.NORMAL, str(definition.supply), ui.NORMAL, imm) - properties.append(t) - return properties - - -async def require_confirm_final(ctx, action: str, fee: int): - content = Text('Confirm sending', ui.ICON_SEND, - ui.NORMAL, 'Create ', action, - ui.BOLD, 'paying ' + format_amount(fee, NEM_MAX_DIVISIBILITY) + ' XEM', - ui.NORMAL, 'for transaction fee?', - icon_color=ui.GREEN) - await require_hold_to_confirm(ctx, content, ButtonRequestType.SignTx) # we use SignTx, not ConfirmOutput, for compatibility with T1 - - -async def require_confirm_payload(ctx, payload: bytes, encrypt=False): - payload = str(payload, 'utf-8') - if encrypt: - content = Text('Send encrypted?', ui.ICON_SEND, - ui.NORMAL, *split_words(payload, 18)) else: - content = Text('Send unencrypted?', ui.ICON_SEND, - ui.NORMAL, *split_words(payload, 18), - icon_color=ui.RED) - await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + t = Text('Confirm properties', ui.ICON_SEND, + ui.BOLD, 'Initial supply:', + ui.NORMAL, imm) + properties.append(t) + if definition.levy: + t = Text('Confirm properties', ui.ICON_SEND, + ui.BOLD, 'Levy recipient:', + ui.MONO, *split_address(definition.levy_address)) + properties.append(t) + t = Text('Confirm properties', ui.ICON_SEND, + ui.BOLD, 'Levy namespace:', + ui.NORMAL, definition.levy_namespace, + ui.BOLD, 'Levy mosaic:', + ui.NORMAL, definition.levy_mosaic) + properties.append(t) + if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute: + levy_type = 'absolute' + else: + levy_type = 'percentile' + t = Text('Confirm properties', ui.ICON_SEND, + ui.BOLD, 'Levy type:', + ui.NORMAL, levy_type) + properties.append(t) + + return properties def split_address(data): diff --git a/src/apps/nem/signing.py b/src/apps/nem/signing.py index f68e71e2c8..af2304b87b 100644 --- a/src/apps/nem/signing.py +++ b/src/apps/nem/signing.py @@ -4,10 +4,14 @@ from apps.nem.mosaic import * from apps.nem.validators import validate from apps.nem import helpers from apps.common import seed +from trezor.messages import NEMSupplyChangeType +from trezor.messages import NEMModificationType +from trezor.messages import NEMImportanceTransferMode from trezor.messages.NEMSignTx import NEMSignTx from trezor.messages.NEMSignedTx import NEMSignedTx from trezor.crypto.curve import ed25519 from trezor.crypto import random +from trezor.crypto import nem async def nem_sign_tx(ctx, msg: NEMSignTx): @@ -39,7 +43,13 @@ async def nem_sign_tx(ctx, msg: NEMSignTx): async def _importance_transfer(ctx, node, msg: NEMSignTx): - # todo confirms! + if msg.importance_transfer.mode == NEMImportanceTransferMode.ImportanceTransfer_Activate: + m = 'Activate' + else: + m = 'Deactivate' + await require_confirm_action(ctx, m + ' remote harvesting?') + await require_confirm_final(ctx, msg.transaction.fee) + w = nem_transaction_create_importance_transfer( msg.transaction.network, msg.transaction.timestamp, @@ -53,28 +63,52 @@ async def _importance_transfer(ctx, node, msg: NEMSignTx): async def _aggregate_modification(ctx, node, msg: NEMSignTx): - # todo confirms! - w = nem_transaction_create_aggregate_modification( - msg.transaction.network, - msg.transaction.timestamp, - _get_public_key(node), - msg.transaction.fee, - msg.transaction.deadline, - len(msg.aggregate_modification.modifications), - msg.aggregate_modification.relative_change - ) + if not msg.multisig: + await require_confirm_action(ctx, 'Convert account to multisig account?') + w = nem_transaction_create_aggregate_modification( + msg.transaction.network, + msg.transaction.timestamp, + _get_public_key(node), + msg.transaction.fee, + msg.transaction.deadline, + len(msg.aggregate_modification.modifications), + msg.aggregate_modification.relative_change + ) for m in msg.aggregate_modification.modifications: + if m.type == NEMModificationType.CosignatoryModification_Add: + action = 'Add' + else: + action = 'Remove' + address = nem.compute_address(m.public_key, msg.transaction.network) + await require_confirm_address(ctx, action + ' cosignatory?', address) nem_transaction_write_cosignatory_modification(w, m.type, m.public_key) if msg.aggregate_modification.relative_change: + if not msg.multisig: + action = 'Set minimum cosignatories to ' + else: + action = 'Modify the number of cosignatories by ' + await require_confirm_action(ctx, action + str(msg.aggregate_modification.relative_change) + '?') + nem_transaction_write_minimum_cosignatories(w, msg.aggregate_modification.relative_change) + await require_confirm_final(ctx, msg.transaction.fee) return w async def _supply_change(ctx, node, msg: NEMSignTx): - # todo confirms! + await require_confirm_action(ctx, 'Modify supply for "' + msg.supply_change.mosaic + '" under namespace "' + + msg.supply_change.namespace + '"?') + if msg.supply_change.type == NEMSupplyChangeType.SupplyChange_Decrease: + ask_msg = 'Decrease supply by ' + str(msg.supply_change.delta) + ' whole units?' + elif msg.supply_change.type == NEMSupplyChangeType.SupplyChange_Increase: + ask_msg = 'Increase supply by ' + str(msg.supply_change.delta) + ' whole units?' + else: + raise ValueError('Invalid supply change type') + await require_confirm_action(ctx, ask_msg) + + await require_confirm_final(ctx, msg.transaction.fee) return nem_transaction_create_mosaic_supply_change( msg.transaction.network, msg.transaction.timestamp, @@ -91,10 +125,10 @@ async def _supply_change(ctx, node, msg: NEMSignTx): async def _mosaic_creation(ctx, node, msg: NEMSignTx) -> bytearray: await require_confirm_action(ctx, 'Create mosaic "' + msg.mosaic_creation.definition.mosaic + '" under namespace "' + msg.mosaic_creation.definition.namespace + '"?') - # todo confirm levy! await require_confirm_properties(ctx, msg.mosaic_creation.definition) - await require_confirm_final(ctx, 'mosaic', msg.transaction.fee) + await require_confirm_fee(ctx, 'Confirm creation fee', msg.mosaic_creation.fee) + await require_confirm_final(ctx, msg.transaction.fee) return nem_transaction_create_mosaic_creation( msg.transaction.network, msg.transaction.timestamp, @@ -118,8 +152,14 @@ async def _mosaic_creation(ctx, node, msg: NEMSignTx) -> bytearray: async def _provision_namespace(ctx, node, msg: NEMSignTx) -> bytearray: - await require_confirm_action(ctx, 'Create provision namespace "' + msg.provision_namespace.namespace + '"?') - await require_confirm_final(ctx, 'provision namespace', msg.transaction.fee) + if msg.provision_namespace.parent: + await require_confirm_action(ctx, 'Create namespace "' + msg.provision_namespace.namespace + '"' + + 'under namespace "' + msg.provision_namespace.parent + '"?') + else: + await require_confirm_action(ctx, 'Create namespace "' + msg.provision_namespace.namespace + '"?') + await require_confirm_fee(ctx, 'Confirm rental fee', msg.provision_namespace.fee) + + await require_confirm_final(ctx, msg.transaction.fee) return nem_transaction_create_provision_namespace( msg.transaction.network, msg.transaction.timestamp, @@ -148,14 +188,16 @@ async def _transfer(ctx, node, msg: NEMSignTx) -> bytes: ) for mosaic in msg.transfer.mosaics: + await require_confirm_action(ctx, 'Confirm transfer of ' + str(mosaic.quantity) + + ' raw units of ' + mosaic.namespace + '.' + mosaic.mosaic) nem_transaction_write_mosaic(tx, mosaic.namespace, mosaic.mosaic, mosaic.quantity) - if payload: # confirm unencrypted + await require_confirm_transfer(ctx, msg.transfer.recipient, msg.transfer.amount) + + if payload: await require_confirm_payload(ctx, msg.transfer.payload, encrypted) - await require_confirm_action(ctx, 'todo') # todo! - await require_confirm_tx(ctx, msg.transfer.recipient, msg.transfer.amount) - + await require_confirm_final(ctx, msg.transaction.fee) return tx diff --git a/src/apps/nem/validators.py b/src/apps/nem/validators.py index f8b407b629..216e15053c 100644 --- a/src/apps/nem/validators.py +++ b/src/apps/nem/validators.py @@ -19,6 +19,11 @@ def validate(msg: NEMSignTx): _validate_single_tx(msg) _validate_common(msg.transaction) + if msg.multisig: + _validate_multisig(msg.multisig, msg.transaction.network) + if not msg.multisig and msg.cosigning: + raise ValueError('No multisig transaction to cosign') + if msg.transfer: _validate_transfer(msg.transfer, msg.transaction.network) if msg.provision_namespace: @@ -28,7 +33,7 @@ def validate(msg: NEMSignTx): if msg.supply_change: _validate_supply_change(msg.supply_change) if msg.aggregate_modification: - _validate_aggregate_modification(msg.aggregate_modification, msg.multisig is not None) + _validate_aggregate_modification(msg.aggregate_modification, msg.multisig is None) if msg.importance_transfer: _validate_importance_transfer(msg.importance_transfer) @@ -100,6 +105,11 @@ def _validate_importance_transfer(importance_transfer: NEMImportanceTransfer): _validate_public_key(importance_transfer.public_key, 'Invalid remote account public key provided') +def _validate_multisig(multisig: NEMTransactionCommon, network: int): + if multisig.network != network: + raise ValueError('Inner transaction network is different') + + def _validate_aggregate_modification(aggregate_modification: NEMAggregateModification, creation: bool=False): if creation and len(aggregate_modification.modifications) == 0: @@ -208,6 +218,12 @@ def _validate_transfer(transfer: NEMTransfer, network: int): if transfer.public_key is not None: _validate_public_key(transfer.public_key, 'Invalid recipient public key') + if transfer.payload: + if len(transfer.payload) > NEM_MAX_PLAIN_PAYLOAD_SIZE: + raise ValueError('Payload too large') + if transfer.public_key and len(transfer.payload) > NEM_MAX_ENCRYPTED_PAYLOAD_SIZE: + raise ValueError('Payload too large') + if not nem.validate_address(transfer.recipient, network): raise ValueError('Invalid recipient address')