From f87d931ce72d018acab84d454ff5729bd8094288 Mon Sep 17 00:00:00 2001 From: matejcik Date: Thu, 15 Mar 2018 16:14:24 +0100 Subject: [PATCH] trezorlib: factor out NEM to a separate module --- trezorlib/client.py | 120 ++------------------- trezorlib/nem.py | 140 +++++++++++++++++++++++++ trezorlib/tests/unit_tests/test_nem.py | 38 +++++++ 3 files changed, 184 insertions(+), 114 deletions(-) create mode 100644 trezorlib/nem.py create mode 100644 trezorlib/tests/unit_tests/test_nem.py diff --git a/trezorlib/client.py b/trezorlib/client.py index e3dc5ce31..c73d6ef13 100644 --- a/trezorlib/client.py +++ b/trezorlib/client.py @@ -25,7 +25,6 @@ import time import binascii import hashlib import unicodedata -import json import getpass import warnings @@ -34,6 +33,7 @@ from mnemonic import Mnemonic from . import messages as proto from . import tools from . import mapping +from . import nem from .coins import coins_slip44 from .debuglink import DebugLink from .protobuf import MessageType @@ -718,120 +718,12 @@ class ProtocolMixin(object): @expect(proto.NEMSignedTx) def nem_sign_tx(self, n, transaction): n = self._convert_prime(n) + try: + msg = nem.create_sign_tx(transaction) + except ValueError as e: + raise CallException(e.message) - def common_to_proto(common): - msg = proto.NEMTransactionCommon() - msg.network = (common["version"] >> 24) & 0xFF - msg.timestamp = common["timeStamp"] - msg.fee = common["fee"] - msg.deadline = common["deadline"] - - if "signed" in common: - msg.signer = binascii.unhexlify(common["signer"]) - - return msg - - def transfer_to_proto(transfer): - msg = proto.NEMTransfer() - msg.recipient = transfer["recipient"] - msg.amount = transfer["amount"] - - if "payload" in transfer["message"]: - msg.payload = binascii.unhexlify(transfer["message"]["payload"]) - - if transfer["message"]["type"] == 0x02: - msg.public_key = binascii.unhexlify(transfer["message"]["publicKey"]) - - if "mosaics" in transfer: - msg._extend_mosaics(proto.NEMMosaic( - namespace=mosaic["mosaicId"]["namespaceId"], - mosaic=mosaic["mosaicId"]["name"], - quantity=mosaic["quantity"], - ) for mosaic in transfer["mosaics"]) - return msg - - def aggregate_modification_to_proto(aggregate_modification, msg): - msg._extend_modifications(proto.NEMCosignatoryModification( - type=modification["modificationType"], - public_key=binascii.unhexlify(modification["cosignatoryAccount"]), - ) for modification in aggregate_modification["modifications"]) - - if "minCosignatories" in aggregate_modification: - msg.relative_change = aggregate_modification["minCosignatories"]["relativeChange"] - - def provision_namespace_to_proto(provision_namespace, msg): - msg.namespace = provision_namespace["newPart"] - - if provision_namespace["parent"]: - msg.parent = provision_namespace["parent"] - - msg.sink = provision_namespace["rentalFeeSink"] - msg.fee = provision_namespace["rentalFee"] - - def mosaic_creation_to_proto(mosaic_creation): - msg = proto.NEMMosaicCreation() - msg.definition.namespace = mosaic_creation["mosaicDefinition"]["id"]["namespaceId"] - msg.definition.mosaic = mosaic_creation["mosaicDefinition"]["id"]["name"] - - if mosaic_creation["mosaicDefinition"]["levy"]: - msg.definition.levy = mosaic_creation["mosaicDefinition"]["levy"]["type"] - msg.definition.fee = mosaic_creation["mosaicDefinition"]["levy"]["fee"] - msg.definition.levy_address = mosaic_creation["mosaicDefinition"]["levy"]["recipient"] - msg.definition.levy_namespace = mosaic_creation["mosaicDefinition"]["levy"]["mosaicId"]["namespaceId"] - msg.definition.levy_mosaic = mosaic_creation["mosaicDefinition"]["levy"]["mosaicId"]["name"] - - msg.definition.description = mosaic_creation["mosaicDefinition"]["description"] - - for property in mosaic_creation["mosaicDefinition"]["properties"]: - name = property["name"] - value = json.loads(property["value"]) - - if name == "divisibility": - msg.definition.divisibility = value - elif name == "initialSupply": - msg.definition.supply = value - elif name == "supplyMutable": - msg.definition.mutable_supply = value - elif name == "transferable": - msg.definition.transferable = value - - msg.sink = mosaic_creation["creationFeeSink"] - msg.fee = mosaic_creation["creationFee"] - return msg - - def mosaic_supply_change_to_proto(mosaic_supply_change): - msg = proto.NEMMosaicSupplyChange() - msg.namespace = mosaic_supply_change["mosaicId"]["namespaceId"] - msg.mosaic = mosaic_supply_change["mosaicId"]["name"] - msg.type = mosaic_supply_change["supplyType"] - msg.delta = mosaic_supply_change["delta"] - return msg - - msg = proto.NEMSignTx() - - msg.transaction = common_to_proto(transaction) - msg.transaction._extend_address_n(n) - msg.cosigning = (transaction["type"] == 0x1002) - - if msg.cosigning or transaction["type"] == 0x1004: - transaction = transaction["otherTrans"] - msg.multisig = common_to_proto(transaction) - elif "otherTrans" in transaction: - raise CallException("Transaction does not support inner transaction") - - if transaction["type"] == 0x0101: - msg.transfer = transfer_to_proto(transaction) - elif transaction["type"] == 0x1001: - aggregate_modification_to_proto(transaction, msg.aggregate_modification) - elif transaction["type"] == 0x2001: - provision_namespace_to_proto(transaction, msg.provision_namespace) - elif transaction["type"] == 0x4001: - msg = mosaic_creation_to_proto(transaction) - elif transaction["type"] == 0x4002: - msg.mosaic_supply_change = mosaic_supply_change_to_proto(transaction) - else: - raise CallException("Unknown transaction type") - + msg.address_n = n return self.call(msg) def verify_message(self, coin_name, address, signature, message): diff --git a/trezorlib/nem.py b/trezorlib/nem.py new file mode 100644 index 000000000..df7cb9c76 --- /dev/null +++ b/trezorlib/nem.py @@ -0,0 +1,140 @@ +import binascii +import json +from . import messages as proto + +TYPE_MOSAIC_TRANSFER = 0x0101 +TYPE_IMPORTANCE_TRANSFER = 0x0801 +TYPE_MULTISIG_CHANGE = 0x1001 +TYPE_MULTISIG_SIGN = 0x1002 +TYPE_MULTISIG_TX = 0x1004 +TYPE_PROVISION_NAMESPACE = 0x2001 +TYPE_MOSAIC_DEFINITION_CREATION = 0x4001 +TYPE_MOSAIC_SUPPLY_CHANGE = 0x4002 + + +def create_transaction_common(transaction): + msg = proto.NEMTransactionCommon() + msg.network = (transaction["version"] >> 24) & 0xFF + msg.timestamp = transaction["timeStamp"] + msg.fee = transaction["fee"] + msg.deadline = transaction["deadline"] + + if "signed" in transaction: + msg.signer = binascii.unhexlify(transaction["signer"]) + + return msg + + +def create_transfer(transaction): + msg = proto.NEMTransfer() + msg.recipient = transaction["recipient"] + msg.amount = transaction["amount"] + + if "payload" in transaction["message"]: + msg.payload = binascii.unhexlify(transaction["message"]["payload"]) + + if transaction["message"]["type"] == 0x02: + msg.public_key = binascii.unhexlify(transaction["message"]["publicKey"]) + + if "mosaics" in transaction: + msg.mosaics = [proto.NEMMosaic( + namespace=mosaic["mosaicId"]["namespaceId"], + mosaic=mosaic["mosaicId"]["name"], + quantity=mosaic["quantity"], + ) for mosaic in transaction["mosaics"]] + + return msg + + +def create_aggregate_modification(transactions): + msg = proto.NEMAggregateModification() + msg.modifications = [proto.NEMCosignatoryModification( + type=modification["modificationType"], + public_key=binascii.unhexlify(modification["cosignatoryAccount"]), + ) for modification in transactions["modifications"]] + + if "minCosignatories" in transactions: + msg.relative_change = transactions["minCosignatories"]["relativeChange"] + + return msg + + +def create_provision_namespace(transaction): + msg = proto.NEMProvisionNamespace() + msg.namespace = transaction["newPart"] + + if transaction["parent"]: + msg.parent = transaction["parent"] + + msg.sink = transaction["rentalFeeSink"] + msg.fee = transaction["rentalFee"] + + +def create_mosaic_creation(transaction): + definition = transaction["mosaicDefinition"] + msg = proto.NEMMosaicCreation() + msg.definition = proto.NEMMosaicDefinition() + msg.definition.namespace = definition["id"]["namespaceId"] + msg.definition.mosaic = definition["id"]["name"] + + if definition["levy"]: + msg.definition.levy = definition["levy"]["type"] + msg.definition.fee = definition["levy"]["fee"] + msg.definition.levy_address = definition["levy"]["recipient"] + msg.definition.levy_namespace = definition["levy"]["mosaicId"]["namespaceId"] + msg.definition.levy_mosaic = definition["levy"]["mosaicId"]["name"] + + msg.definition.description = definition["description"] + + for property in definition["properties"]: + name = property["name"] + value = json.loads(property["value"]) + + if name == "divisibility": + msg.definition.divisibility = value + elif name == "initialSupply": + msg.definition.supply = value + elif name == "supplyMutable": + msg.definition.mutable_supply = value + elif name == "transferable": + msg.definition.transferable = value + + msg.sink = transaction["creationFeeSink"] + msg.fee = transaction["creationFee"] + return msg + + +def create_supply_change(transaction): + msg = proto.NEMMosaicSupplyChange() + msg.namespace = transaction["mosaicId"]["namespaceId"] + msg.mosaic = transaction["mosaicId"]["name"] + msg.type = transaction["supplyType"] + msg.delta = transaction["delta"] + return msg + + +def create_sign_tx(transaction): + msg = proto.NEMSignTx() + msg.transaction = create_transaction_common(transaction) + msg.cosigning = (transaction["type"] == TYPE_MULTISIG_SIGN) + + if transaction["type"] in (TYPE_MULTISIG_SIGN, TYPE_MULTISIG_TX): + transaction = transaction["otherTrans"] + msg.multisig = create_transaction_common(transaction) + elif "otherTrans" in transaction: + raise ValueError("Transaction does not support inner transaction") + + if transaction["type"] == TYPE_MOSAIC_TRANSFER: + msg.transfer = create_transfer(transaction) + elif transaction["type"] == TYPE_MULTISIG_CHANGE: + msg.aggregate_modification = create_aggregate_modification(transaction) + elif transaction["type"] == TYPE_PROVISION_NAMESPACE: + msg.provision_namespace = create_provision_namespace(transaction) + elif transaction["type"] == TYPE_MOSAIC_DEFINITION_CREATION: + msg.mosaic_creation = create_mosaic_creation(transaction) + elif transaction["type"] == TYPE_MOSAIC_SUPPLY_CHANGE: + msg.mosaic_supply_change = create_supply_change(transaction) + else: + raise ValueError("Unknown transaction type") + + return msg diff --git a/trezorlib/tests/unit_tests/test_nem.py b/trezorlib/tests/unit_tests/test_nem.py new file mode 100644 index 000000000..d3da852b7 --- /dev/null +++ b/trezorlib/tests/unit_tests/test_nem.py @@ -0,0 +1,38 @@ +import binascii +from trezorlib import nem + + +def test_nem_basic(): + transaction = { + "timeStamp": 76809215, + "amount": 1000000, + "fee": 1000000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_MOSAIC_TRANSFER, + "deadline": 76895615, + "version": (0x98 << 24), + "message": { + "payload": binascii.hexlify(b'hello world'), + "type": 1, + }, + "mosaics": [ + { + "mosaicId": { + "namespaceId": "nem", + "name": "xem", + }, + "quantity": 1000000, + }, + ], + } + + msg = nem.create_sign_tx(transaction) + + # this is basically just a random sampling of expected properties + assert msg.transaction is not None + assert msg.transfer is not None + assert len(msg.transfer.mosaics) == 1 + assert msg.transfer.mosaics[0].namespace == "nem" + + assert msg.aggregate_modification is None + assert msg.provision_namespace is None