From d30d6859ba810f7700fd0c3b54dbefeb220f1809 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Mon, 26 Mar 2018 15:03:57 +0200 Subject: [PATCH] nem: validators This commit introduces a lot of boundary checking validating if the NEM transaction has all required fields. It is based solely on the T1 mcu code. --- src/apps/nem/get_address.py | 5 +- src/apps/nem/helpers.py | 12 +-- src/apps/nem/signing.py | 11 +- src/apps/nem/validators.py | 210 ++++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 19 deletions(-) create mode 100644 src/apps/nem/validators.py diff --git a/src/apps/nem/get_address.py b/src/apps/nem/get_address.py index a223fdb14..d7660f2f3 100644 --- a/src/apps/nem/get_address.py +++ b/src/apps/nem/get_address.py @@ -1,11 +1,11 @@ from apps.wallet.get_address import _show_address from apps.common import seed from trezor.messages.NEMAddress import NEMAddress -from .helpers import * +from .validators import * async def nem_get_address(ctx, msg): - network = nem_validate_network(msg.network) + network = validate_network(msg.network) node = await seed.derive_node(ctx, msg.address_n, NEM_CURVE) address = node.nem_address(network) @@ -15,4 +15,3 @@ async def nem_get_address(ctx, msg): break return NEMAddress(address=address) - diff --git a/src/apps/nem/helpers.py b/src/apps/nem/helpers.py index 091498e6f..a68961aea 100644 --- a/src/apps/nem/helpers.py +++ b/src/apps/nem/helpers.py @@ -15,14 +15,10 @@ NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE = const(0x2001) NEM_TRANSACTION_TYPE_MOSAIC_CREATION = const(0x4001) NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE = const(0x4002) +NEM_MAX_DIVISIBILITY = const(6) +NEM_MAX_SUPPLY = const(9000000000) + NEM_SALT_SIZE = const(32) AES_BLOCK_SIZE = const(16) NEM_HASH_ALG = 'keccak' - - -def nem_validate_network(network): - if network in (NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN): - return network - if network is None: - return NEM_NETWORK_MAINNET - raise ValueError('Invalid NEM network') +NEM_PUBLIC_KEY_SIZE = const(32) # ed25519 public key diff --git a/src/apps/nem/signing.py b/src/apps/nem/signing.py index 5962cb7c4..44b99c5ce 100644 --- a/src/apps/nem/signing.py +++ b/src/apps/nem/signing.py @@ -1,5 +1,6 @@ from apps.nem.layout import * from apps.nem.transaction import * +from apps.nem.validators import validate from apps.nem import helpers from apps.common import seed from trezor.messages.NEMSignTx import NEMSignTx @@ -10,13 +11,12 @@ from trezor.crypto import random async def nem_sign_tx(ctx, msg: NEMSignTx): - node = await seed.derive_node(ctx, msg.transaction.address_n, NEM_CURVE) + validate(msg) + node = await seed.derive_node(ctx, msg.transaction.address_n, NEM_CURVE) payload, encrypted = _get_payload(msg, node) public_key = _get_public_key(node) - _validate_network(msg.transaction.network) - tx = nem_transaction_create_transfer( msg.transaction.network, msg.transaction.timestamp, @@ -69,8 +69,3 @@ def _nem_encrypt(node, public_key: bytes, payload: bytes) -> bytes: iv = random.bytes(helpers.AES_BLOCK_SIZE) encrypted = node.nem_encrypt(public_key, iv, salt, payload) return iv + salt + encrypted - - -def _validate_network(network): - if network not in [NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN]: - raise ValueError('Invalid NEM network') diff --git a/src/apps/nem/validators.py b/src/apps/nem/validators.py new file mode 100644 index 000000000..e98beef8f --- /dev/null +++ b/src/apps/nem/validators.py @@ -0,0 +1,210 @@ +from apps.nem.helpers import * +from trezor.messages.NEMModificationType import CosignatoryModification_Delete +from trezor.messages.NEMSignTx import NEMAggregateModification +from trezor.messages.NEMSignTx import NEMImportanceTransfer +from trezor.messages.NEMSignTx import NEMMosaicCreation +from trezor.messages.NEMSignTx import NEMMosaicSupplyChange +from trezor.messages.NEMSignTx import NEMProvisionNamespace +from trezor.messages.NEMSignTx import NEMSignTx +from trezor.messages.NEMSignTx import NEMTransactionCommon +from trezor.messages.NEMSignTx import NEMTransfer +from trezor.crypto import nem + + +def validate(msg: NEMSignTx): + + if msg.transaction is None: + raise ValueError('No common provided') + + _validate_single_tx(msg) + _validate_common(msg.transaction) + + if msg.transfer: + _validate_transfer(msg.transfer, msg.transaction.network) + if msg.provision_namespace: + _validate_provision_namespace(msg.provision_namespace, msg.transaction.network) + if msg.mosaic_creation: + _validate_mosaic_creation(msg.mosaic_creation, msg.transaction.network) + if msg.supply_change: + _validate_supply_change(msg.supply_change) + if msg.aggregate_modification: + _validate_aggregate_modification(msg.aggregate_modification, not len(msg.multisig)) + if msg.importance_transfer: + _validate_importance_transfer(msg.importance_transfer) + + +def validate_network(network: int) -> int: + if network is None: + return NEM_NETWORK_MAINNET + _validate_network(network) + return network + + +def _validate_network(network: int): + if network not in [NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN]: + raise ValueError('Invalid NEM network') + + +def _validate_single_tx(msg: NEMSignTx): + # ensure exactly one transaction is provided + tx_count = bool(msg.transfer) + \ + bool(msg.provision_namespace) + \ + bool(msg.mosaic_creation) + \ + bool(msg.supply_change) + \ + bool(msg.aggregate_modification) + \ + bool(msg.importance_transfer) + if tx_count == 0: + raise ValueError('No transaction provided') + if tx_count > 1: + raise ValueError('More than one transaction provided') + + +def _validate_common(common: NEMTransactionCommon, inner: bool=False): + + common.network = validate_network(common.network) + + err = None + if not common.timestamp: + err = 'timestamp' + if not common.fee: + err = 'fee' + if not common.deadline: + err = 'deadline' + + is_signer = common.signer is not None + if inner != is_signer: + if not inner: + raise ValueError('Signer not allowed in outer transaction') + err = 'signer' + + if err: + if inner: + raise ValueError('No ' + err + ' provided in inner transaction') + else: + raise ValueError('No ' + err + ' provided') + + if common.signer: + _validate_public_key(common.signer, 'Invalid signer public key in inner transaction') + + +def _validate_public_key(public_key: bytes, err_msg): + if not public_key: + raise ValueError(err_msg + ' (none provided)') + if len(public_key) != NEM_PUBLIC_KEY_SIZE: + raise ValueError(err_msg + ' (invalid length)') + + +def _validate_importance_transfer(importance_transfer: NEMImportanceTransfer): + if not importance_transfer.mode: + raise ValueError('No mode provided') + _validate_public_key(importance_transfer.public_key, 'Invalid remote account public key provided') + + +def _validate_aggregate_modification(aggregate_modification: NEMAggregateModification, creation: bool=False): + + if creation and len(aggregate_modification.modifications) == 0: + raise ValueError('No modifications provided') + + for m in aggregate_modification.modifications: + if not m.type: + raise ValueError('No modification type provided') + if creation and m.type == CosignatoryModification_Delete: + raise ValueError('Cannot remove cosignatory when converting account') + _validate_public_key(m.public_key, 'Invalid cosignatory public key provided') + + +def _validate_supply_change(supply_change: NEMMosaicSupplyChange): + if not supply_change.namespace: + raise ValueError('No namespace provided') + if not supply_change.mosaic: + raise ValueError('No mosaic provided') + if not supply_change.type: + raise ValueError('No type provided') + if not supply_change.delta: + raise ValueError('No delta provided') + + +def _validate_mosaic_creation(mosaic_creation: NEMMosaicCreation, network: int): + if not mosaic_creation.definition: + raise ValueError('No mosaic definition provided') + if not mosaic_creation.sink: + raise ValueError('No creation sink provided') + if not mosaic_creation.fee: + raise ValueError('No creation sink fee provided') + + if not nem.validate_address(mosaic_creation.sink, network): + raise ValueError('Invalid creation sink address') + + if mosaic_creation.definition.name: + raise ValueError('Name not allowed in mosaic creation transactions') + if mosaic_creation.definition.ticker: + raise ValueError('Ticker not allowed in mosaic creation transactions') + if len(mosaic_creation.definition.networks): + raise ValueError('Networks not allowed in mosaic creation transactions') + + if not mosaic_creation.definition.namespace: + raise ValueError('No mosaic namespace provided') + if not mosaic_creation.definition.mosaic: + raise ValueError('No mosaic name provided') + + if mosaic_creation.definition.levy: + if not mosaic_creation.definition.fee: + raise ValueError('No levy fee provided') + if not mosaic_creation.definition.levy_address: + raise ValueError('No levy address provided') + if not mosaic_creation.definition.levy_namespace: + raise ValueError('No levy namespace provided') + if not mosaic_creation.definition.levy_mosaic: + raise ValueError('No levy mosaic name provided') + + if not mosaic_creation.definition.divisibility: + raise ValueError('No divisibility provided') + if not mosaic_creation.definition.supply: + raise ValueError('No supply provided') + if not mosaic_creation.definition.mutable_supply: + raise ValueError('No supply mutability provided') + if not mosaic_creation.definition.transferable: + raise ValueError('No mosaic transferability provided') + if not mosaic_creation.definition.description: + raise ValueError('No description provided') + + if mosaic_creation.definition.divisibility > NEM_MAX_DIVISIBILITY: + raise ValueError('Invalid divisibility provided') + if mosaic_creation.definition.supply > NEM_MAX_SUPPLY: + raise ValueError('Invalid supply provided') + + if not nem.validate_address(mosaic_creation.definition.levy_address, network): + raise ValueError('Invalid levy address') + + +def _validate_provision_namespace(provision_namespace: NEMProvisionNamespace, network: int): + if not provision_namespace.namespace: + raise ValueError('No namespace provided') + if not provision_namespace.sink: + raise ValueError('No rental sink provided') + if not provision_namespace.fee: + raise ValueError('No rental sink fee provided') + + if not nem.validate_address(provision_namespace.sink, network): + raise ValueError('Invalid rental sink address') + + +def _validate_transfer(transfer: NEMTransfer, network: int): + if transfer.recipient is None: + raise ValueError('No recipient provided') + if transfer.amount is None: + raise ValueError('No amount provided') + + if transfer.public_key: + _validate_public_key(transfer.public_key, 'Invalid recipient public key') + + if not nem.validate_address(transfer.recipient, network): + raise ValueError('Invalid recipient address') + + for m in transfer.mosaics: + if not m.namespace: + raise ValueError('No mosaic namespace provided') + if not m.mosaic: + raise ValueError('No mosaic name provided') + if not m.quantity: + raise ValueError('No mosaic quantity provided')