From ea05872927aefc4328eec03389601279d2b9fde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Eckel?= Date: Wed, 15 Jun 2022 13:45:09 +0200 Subject: [PATCH] fixed linting errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jürgen Eckel --- planetmint/transactions/common/output.py | 155 +++-- planetmint/transactions/common/transaction.py | 636 ++++++++++-------- planetmint/transactions/common/utils.py | 176 ++--- tests/assets/test_zenroom_signing.py | 410 +++++------ tox.ini | 2 +- 5 files changed, 727 insertions(+), 652 deletions(-) diff --git a/planetmint/transactions/common/output.py b/planetmint/transactions/common/output.py index 394dbf9..df79b1d 100644 --- a/planetmint/transactions/common/output.py +++ b/planetmint/transactions/common/output.py @@ -25,30 +25,30 @@ class Output(object): owners before a Transaction was confirmed. """ - MAX_AMOUNT = 9 * 10 ** 18 + MAX_AMOUNT = 9 * 10**18 def __init__(self, fulfillment, public_keys=None, amount=1): """Create an instance of a :class:`~.Output`. - Args: - fulfillment (:class:`cryptoconditions.Fulfillment`): A - Fulfillment to extract a Condition from. - public_keys (:obj:`list` of :obj:`str`, optional): A list of - owners before a Transaction was confirmed. - amount (int): The amount of Assets to be locked with this - Output. + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to extract a Condition from. + public_keys (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + amount (int): The amount of Assets to be locked with this + Output. - Raises: - TypeError: if `public_keys` is not instance of `list`. + Raises: + TypeError: if `public_keys` is not instance of `list`. """ if not isinstance(public_keys, list) and public_keys is not None: - raise TypeError('`public_keys` must be a list instance or None') + raise TypeError("`public_keys` must be a list instance or None") if not isinstance(amount, int): - raise TypeError('`amount` must be an int') + raise TypeError("`amount` must be an int") if amount < 1: - raise AmountError('`amount` must be greater than 0') + raise AmountError("`amount` must be greater than 0") if amount > self.MAX_AMOUNT: - raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT) + raise AmountError("`amount` must be <= %s" % self.MAX_AMOUNT) self.fulfillment = fulfillment self.amount = amount @@ -61,31 +61,31 @@ class Output(object): def to_dict(self): """Transforms the object to a Python dictionary. - Note: - A dictionary serialization of the Input the Output was - derived from is always provided. + Note: + A dictionary serialization of the Input the Output was + derived from is always provided. - Returns: - dict: The Output as an alternative serialization format. + Returns: + dict: The Output as an alternative serialization format. """ # TODO FOR CC: It must be able to recognize a hashlock condition # and fulfillment! condition = {} try: # TODO verify if a script is returned in case of zenroom fulfillments - condition['details'] = _fulfillment_to_details(self.fulfillment) + condition["details"] = _fulfillment_to_details(self.fulfillment) except AttributeError: pass try: - condition['uri'] = self.fulfillment.condition_uri + condition["uri"] = self.fulfillment.condition_uri except AttributeError: - condition['uri'] = self.fulfillment + condition["uri"] = self.fulfillment output = { - 'public_keys': self.public_keys, - 'condition': condition, - 'amount': str(self.amount), + "public_keys": self.public_keys, + "condition": condition, + "amount": str(self.amount), } return output @@ -93,69 +93,65 @@ class Output(object): def generate(cls, public_keys, amount): """Generates a Output from a specifically formed tuple or list. - Note: - If a ThresholdCondition has to be generated where the threshold - is always the number of subconditions it is split between, a - list of the following structure is sufficient: + Note: + If a ThresholdCondition has to be generated where the threshold + is always the number of subconditions it is split between, a + list of the following structure is sufficient: - [(address|condition)*, [(address|condition)*, ...], ...] + [(address|condition)*, [(address|condition)*, ...], ...] - Args: - public_keys (:obj:`list` of :obj:`str`): The public key of - the users that should be able to fulfill the Condition - that is being created. - amount (:obj:`int`): The amount locked by the Output. + Args: + public_keys (:obj:`list` of :obj:`str`): The public key of + the users that should be able to fulfill the Condition + that is being created. + amount (:obj:`int`): The amount locked by the Output. - Returns: - An Output that can be used in a Transaction. + Returns: + An Output that can be used in a Transaction. - Raises: - TypeError: If `public_keys` is not an instance of `list`. - ValueError: If `public_keys` is an empty list. + Raises: + TypeError: If `public_keys` is not an instance of `list`. + ValueError: If `public_keys` is an empty list. """ threshold = len(public_keys) if not isinstance(amount, int): - raise TypeError('`amount` must be a int') + raise TypeError("`amount` must be a int") if amount < 1: - raise AmountError('`amount` needs to be greater than zero') + raise AmountError("`amount` needs to be greater than zero") if not isinstance(public_keys, list): - raise TypeError('`public_keys` must be an instance of list') + raise TypeError("`public_keys` must be an instance of list") if len(public_keys) == 0: - raise ValueError('`public_keys` needs to contain at least one' - 'owner') + raise ValueError("`public_keys` needs to contain at least one" "owner") elif len(public_keys) == 1 and not isinstance(public_keys[0], list): if isinstance(public_keys[0], Fulfillment): ffill = public_keys[0] - elif isinstance( public_keys[0], ZenroomSha256): - ffill = ZenroomSha256( - public_key=base58.b58decode(public_keys[0])) + elif isinstance(public_keys[0], ZenroomSha256): + ffill = ZenroomSha256(public_key=base58.b58decode(public_keys[0])) else: - ffill = Ed25519Sha256( - public_key=base58.b58decode(public_keys[0])) + ffill = Ed25519Sha256(public_key=base58.b58decode(public_keys[0])) return cls(ffill, public_keys, amount=amount) else: initial_cond = ThresholdSha256(threshold=threshold) - threshold_cond = reduce(cls._gen_condition, public_keys, - initial_cond) + threshold_cond = reduce(cls._gen_condition, public_keys, initial_cond) return cls(threshold_cond, public_keys, amount=amount) @classmethod def _gen_condition(cls, initial, new_public_keys): """Generates ThresholdSha256 conditions from a list of new owners. - Note: - This method is intended only to be used with a reduce function. - For a description on how to use this method, see - :meth:`~.Output.generate`. + Note: + This method is intended only to be used with a reduce function. + For a description on how to use this method, see + :meth:`~.Output.generate`. - Args: - initial (:class:`cryptoconditions.ThresholdSha256`): - A Condition representing the overall root. - new_public_keys (:obj:`list` of :obj:`str`|str): A list of new - owners or a single new owner. + Args: + initial (:class:`cryptoconditions.ThresholdSha256`): + A Condition representing the overall root. + new_public_keys (:obj:`list` of :obj:`str`|str): A list of new + owners or a single new owner. - Returns: - :class:`cryptoconditions.ThresholdSha256`: + Returns: + :class:`cryptoconditions.ThresholdSha256`: """ try: threshold = len(new_public_keys) @@ -166,7 +162,7 @@ class Output(object): ffill = ThresholdSha256(threshold=threshold) reduce(cls._gen_condition, new_public_keys, ffill) elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1: - raise ValueError('Sublist cannot contain single owner') + raise ValueError("Sublist cannot contain single owner") else: try: new_public_keys = new_public_keys.pop() @@ -181,8 +177,7 @@ class Output(object): if isinstance(new_public_keys, Fulfillment): ffill = new_public_keys else: - ffill = Ed25519Sha256( - public_key=base58.b58decode(new_public_keys)) + ffill = Ed25519Sha256(public_key=base58.b58decode(new_public_keys)) initial.add_subfulfillment(ffill) return initial @@ -190,25 +185,25 @@ class Output(object): def from_dict(cls, data): """Transforms a Python dictionary to an Output object. - Note: - To pass a serialization cycle multiple times, a - Cryptoconditions Fulfillment needs to be present in the - passed-in dictionary, as Condition URIs are not serializable - anymore. + Note: + To pass a serialization cycle multiple times, a + Cryptoconditions Fulfillment needs to be present in the + passed-in dictionary, as Condition URIs are not serializable + anymore. - Args: - data (dict): The dict to be transformed. + Args: + data (dict): The dict to be transformed. - Returns: - :class:`~planetmint.transactions.common.transaction.Output` + Returns: + :class:`~planetmint.transactions.common.transaction.Output` """ try: - fulfillment = _fulfillment_from_details(data['condition']['details']) + fulfillment = _fulfillment_from_details(data["condition"]["details"]) except KeyError: # NOTE: Hashlock condition case - fulfillment = data['condition']['uri'] + fulfillment = data["condition"]["uri"] try: - amount = int(data['amount']) + amount = int(data["amount"]) except ValueError: - raise AmountError('Invalid amount: %s' % data['amount']) - return cls(fulfillment, data['public_keys'], amount) + raise AmountError("Invalid amount: %s" % data["amount"]) + return cls(fulfillment, data["public_keys"], amount) diff --git a/planetmint/transactions/common/transaction.py b/planetmint/transactions/common/transaction.py index 3c19ce0..ff8f0e7 100644 --- a/planetmint/transactions/common/transaction.py +++ b/planetmint/transactions/common/transaction.py @@ -18,8 +18,8 @@ import rapidjson import base58 from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256, ZenroomSha256 -from cryptoconditions.exceptions import ( - ParsingError, ASN1DecodeError, ASN1EncodeError) +from cryptoconditions.exceptions import ParsingError, ASN1DecodeError, ASN1EncodeError + try: from hashlib import sha3_256 except ImportError: @@ -27,8 +27,14 @@ except ImportError: from planetmint.transactions.common.crypto import PrivateKey, hash_data from planetmint.transactions.common.exceptions import ( - KeypairMismatchException, InputDoesNotExist, DoubleSpend, - InvalidHash, InvalidSignature, AmountError, AssetIdMismatch) + KeypairMismatchException, + InputDoesNotExist, + DoubleSpend, + InvalidHash, + InvalidSignature, + AmountError, + AssetIdMismatch, +) from planetmint.transactions.common.utils import serialize from .memoize import memoize_from_dict, memoize_to_dict from .input import Input @@ -36,92 +42,113 @@ from .output import Output from .transaction_link import TransactionLink UnspentOutput = namedtuple( - 'UnspentOutput', ( + "UnspentOutput", + ( # TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode()) # 'utxo_hash', # noqa - 'transaction_id', - 'output_index', - 'amount', - 'asset_id', - 'condition_uri', - ) + "transaction_id", + "output_index", + "amount", + "asset_id", + "condition_uri", + ), ) class Transaction(object): """A Transaction is used to create and transfer assets. - Note: - For adding Inputs and Outputs, this class provides methods - to do so. + Note: + For adding Inputs and Outputs, this class provides methods + to do so. - Attributes: - operation (str): Defines the operation of the Transaction. - inputs (:obj:`list` of :class:`~planetmint.transactions.common. - transaction.Input`, optional): Define the assets to - spend. - outputs (:obj:`list` of :class:`~planetmint.transactions.common. - transaction.Output`, optional): Define the assets to lock. - asset (dict): Asset payload for this Transaction. ``CREATE`` - Transactions require a dict with a ``data`` - property while ``TRANSFER`` Transactions require a dict with a - ``id`` property. - metadata (dict): - Metadata to be stored along with the Transaction. - version (string): Defines the version number of a Transaction. + Attributes: + operation (str): Defines the operation of the Transaction. + inputs (:obj:`list` of :class:`~planetmint.transactions.common. + transaction.Input`, optional): Define the assets to + spend. + outputs (:obj:`list` of :class:`~planetmint.transactions.common. + transaction.Output`, optional): Define the assets to lock. + asset (dict): Asset payload for this Transaction. ``CREATE`` + Transactions require a dict with a ``data`` + property while ``TRANSFER`` Transactions require a dict with a + ``id`` property. + metadata (dict): + Metadata to be stored along with the Transaction. + version (string): Defines the version number of a Transaction. """ - CREATE = 'CREATE' - TRANSFER = 'TRANSFER' + CREATE = "CREATE" + TRANSFER = "TRANSFER" ALLOWED_OPERATIONS = (CREATE, TRANSFER) - VERSION = '2.0' + VERSION = "2.0" - def __init__(self, operation, asset, inputs=None, outputs=None, - metadata=None, version=None, hash_id=None, tx_dict=None): + def __init__( + self, + operation, + asset, + inputs=None, + outputs=None, + metadata=None, + version=None, + hash_id=None, + tx_dict=None, + ): """The constructor allows to create a customizable Transaction. - Note: - When no `version` is provided, one is being - generated by this method. + Note: + When no `version` is provided, one is being + generated by this method. - Args: - operation (str): Defines the operation of the Transaction. - asset (dict): Asset payload for this Transaction. - inputs (:obj:`list` of :class:`~planetmint.transactions.common. - transaction.Input`, optional): Define the assets to - outputs (:obj:`list` of :class:`~planetmint.transactions.common. - transaction.Output`, optional): Define the assets to - lock. - metadata (dict): Metadata to be stored along with the - Transaction. - version (string): Defines the version number of a Transaction. - hash_id (string): Hash id of the transaction. + Args: + operation (str): Defines the operation of the Transaction. + asset (dict): Asset payload for this Transaction. + inputs (:obj:`list` of :class:`~planetmint.transactions.common. + transaction.Input`, optional): Define the assets to + outputs (:obj:`list` of :class:`~planetmint.transactions.common. + transaction.Output`, optional): Define the assets to + lock. + metadata (dict): Metadata to be stored along with the + Transaction. + version (string): Defines the version number of a Transaction. + hash_id (string): Hash id of the transaction. """ if operation not in self.ALLOWED_OPERATIONS: - allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) - raise ValueError('`operation` must be one of {}' - .format(allowed_ops)) + allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS) + raise ValueError("`operation` must be one of {}".format(allowed_ops)) # Asset payloads for 'CREATE' operations must be None or # dicts holding a `data` property. Asset payloads for 'TRANSFER' # operations must be dicts holding an `id` property. - if (operation == self.CREATE and - asset is not None and not (isinstance(asset, dict) and 'data' in asset)): - raise TypeError(('`asset` must be None or a dict holding a `data` ' - " property instance for '{}' Transactions".format(operation))) - elif (operation == self.TRANSFER and - not (isinstance(asset, dict) and 'id' in asset)): - raise TypeError(('`asset` must be a dict holding an `id` property ' - 'for \'TRANSFER\' Transactions')) + if ( + operation == self.CREATE + and asset is not None + and not (isinstance(asset, dict) and "data" in asset) + ): + raise TypeError( + ( + "`asset` must be None or a dict holding a `data` " + " property instance for '{}' Transactions".format(operation) + ) + ) + elif operation == self.TRANSFER and not ( + isinstance(asset, dict) and "id" in asset + ): + raise TypeError( + ( + "`asset` must be a dict holding an `id` property " + "for 'TRANSFER' Transactions" + ) + ) if outputs and not isinstance(outputs, list): - raise TypeError('`outputs` must be a list instance or None') + raise TypeError("`outputs` must be a list instance or None") if inputs and not isinstance(inputs, list): - raise TypeError('`inputs` must be a list instance or None') + raise TypeError("`inputs` must be a list instance or None") if metadata is not None and not isinstance(metadata, dict): - raise TypeError('`metadata` must be a dict or None') + raise TypeError("`metadata` must be a dict or None") self.version = version if version is not None else self.VERSION self.operation = operation @@ -141,14 +168,17 @@ class Transaction(object): if self.operation == self.CREATE: self._asset_id = self._id elif self.operation == self.TRANSFER: - self._asset_id = self.asset['id'] - return (UnspentOutput( - transaction_id=self._id, - output_index=output_index, - amount=output.amount, - asset_id=self._asset_id, - condition_uri=output.fulfillment.condition_uri, - ) for output_index, output in enumerate(self.outputs)) + self._asset_id = self.asset["id"] + return ( + UnspentOutput( + transaction_id=self._id, + output_index=output_index, + amount=output.amount, + asset_id=self._asset_id, + condition_uri=output.fulfillment.condition_uri, + ) + for output_index, output in enumerate(self.outputs) + ) @property def spent_outputs(self): @@ -156,10 +186,7 @@ class Transaction(object): is represented as a dictionary containing a transaction id and output index. """ - return ( - input_.fulfills.to_dict() - for input_ in self.inputs if input_.fulfills - ) + return (input_.fulfills.to_dict() for input_ in self.inputs if input_.fulfills) @property def serialized(self): @@ -178,81 +205,83 @@ class Transaction(object): def to_inputs(self, indices=None): """Converts a Transaction's outputs to spendable inputs. - Note: - Takes the Transaction's outputs and derives inputs - from that can then be passed into `Transaction.transfer` as - `inputs`. - A list of integers can be passed to `indices` that - defines which outputs should be returned as inputs. - If no `indices` are passed (empty list or None) all - outputs of the Transaction are returned. + Note: + Takes the Transaction's outputs and derives inputs + from that can then be passed into `Transaction.transfer` as + `inputs`. + A list of integers can be passed to `indices` that + defines which outputs should be returned as inputs. + If no `indices` are passed (empty list or None) all + outputs of the Transaction are returned. - Args: - indices (:obj:`list` of int): Defines which - outputs should be returned as inputs. + Args: + indices (:obj:`list` of int): Defines which + outputs should be returned as inputs. - Returns: - :obj:`list` of :class:`~planetmint.transactions.common.transaction. - Input` + Returns: + :obj:`list` of :class:`~planetmint.transactions.common.transaction. + Input` """ # NOTE: If no indices are passed, we just assume to take all outputs # as inputs. indices = indices or range(len(self.outputs)) return [ - Input(self.outputs[idx].fulfillment, - self.outputs[idx].public_keys, - TransactionLink(self.id, idx)) + Input( + self.outputs[idx].fulfillment, + self.outputs[idx].public_keys, + TransactionLink(self.id, idx), + ) for idx in indices ] def add_input(self, input_): """Adds an input to a Transaction's list of inputs. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`): An Input to be added to the Transaction. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`): An Input to be added to the Transaction. """ if not isinstance(input_, Input): - raise TypeError('`input_` must be a Input instance') + raise TypeError("`input_` must be a Input instance") self.inputs.append(input_) def add_output(self, output): """Adds an output to a Transaction's list of outputs. - Args: - output (:class:`~planetmint.transactions.common.transaction. - Output`): An Output to be added to the - Transaction. + Args: + output (:class:`~planetmint.transactions.common.transaction. + Output`): An Output to be added to the + Transaction. """ if not isinstance(output, Output): - raise TypeError('`output` must be an Output instance or None') + raise TypeError("`output` must be an Output instance or None") self.outputs.append(output) def sign(self, private_keys): """Fulfills a previous Transaction's Output by signing Inputs. - Note: - This method works only for the following Cryptoconditions - currently: - - Ed25519Fulfillment - - ThresholdSha256 - - ZenroomSha256 - Furthermore, note that all keys required to fully sign the - Transaction have to be passed to this method. A subset of all - will cause this method to fail. + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256 + - ZenroomSha256 + Furthermore, note that all keys required to fully sign the + Transaction have to be passed to this method. A subset of all + will cause this method to fail. - Args: - private_keys (:obj:`list` of :obj:`str`): A complete list of - all private keys needed to sign all Fulfillments of this - Transaction. + Args: + private_keys (:obj:`list` of :obj:`str`): A complete list of + all private keys needed to sign all Fulfillments of this + Transaction. - Returns: - :class:`~planetmint.transactions.common.transaction.Transaction` + Returns: + :class:`~planetmint.transactions.common.transaction.Transaction` """ # TODO: Singing should be possible with at least one of all private # keys supplied to this method. if private_keys is None or not isinstance(private_keys, list): - raise TypeError('`private_keys` must be a list instance') + raise TypeError("`private_keys` must be a list instance") # NOTE: Generate public keys from private keys and match them in a # dictionary: @@ -269,8 +298,10 @@ class Transaction(object): # to decode to convert the bytestring into a python str return public_key.decode() - key_pairs = {gen_public_key(PrivateKey(private_key)): - PrivateKey(private_key) for private_key in private_keys} + key_pairs = { + gen_public_key(PrivateKey(private_key)): PrivateKey(private_key) + for private_key in private_keys + } tx_dict = self.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) @@ -286,41 +317,39 @@ class Transaction(object): def _sign_input(cls, input_, message, key_pairs): """Signs a single Input. - Note: - This method works only for the following Cryptoconditions - currently: - - Ed25519Fulfillment - - ThresholdSha256. - - ZenroomSha256 - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`) The Input to be signed. - message (str): The message to be signed - key_pairs (dict): The keys to sign the Transaction with. + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256. + - ZenroomSha256 + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`) The Input to be signed. + message (str): The message to be signed + key_pairs (dict): The keys to sign the Transaction with. """ if isinstance(input_.fulfillment, Ed25519Sha256): - return cls._sign_simple_signature_fulfillment(input_, message, - key_pairs) + return cls._sign_simple_signature_fulfillment(input_, message, key_pairs) elif isinstance(input_.fulfillment, ThresholdSha256): - return cls._sign_threshold_signature_fulfillment(input_, message, - key_pairs) + return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs) elif isinstance(input_.fulfillment, ZenroomSha256): - return cls._sign_threshold_signature_fulfillment(input_, message, - key_pairs) + return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs) else: raise ValueError( - 'Fulfillment couldn\'t be matched to ' - 'Cryptocondition fulfillment type.') + "Fulfillment couldn't be matched to " + "Cryptocondition fulfillment type." + ) @classmethod def _sign_zenroom_fulfillment(cls, input_, message, key_pairs): """Signs a Zenroomful. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`) The input to be signed. - message (str): The message to be signed - key_pairs (dict): The keys to sign the Transaction with. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`) The input to be signed. + message (str): The message to be signed + key_pairs (dict): The keys to sign the Transaction with. """ # NOTE: To eliminate the dangers of accidentally signing a condition by # reference, we remove the reference of input_ here @@ -330,29 +359,32 @@ class Transaction(object): public_key = input_.owners_before[0] message = sha3_256(message.encode()) if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) try: # cryptoconditions makes no assumptions of the encoding of the # message to sign or verify. It only accepts bytestrings input_.fulfillment.sign( - message.digest(), base58.b58decode(key_pairs[public_key].encode())) + message.digest(), base58.b58decode(key_pairs[public_key].encode()) + ) except KeyError: - raise KeypairMismatchException('Public key {} is not a pair to ' - 'any of the private keys' - .format(public_key)) + raise KeypairMismatchException( + "Public key {} is not a pair to " + "any of the private keys".format(public_key) + ) return input_ @classmethod def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs): """Signs a Ed25519Fulfillment. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`) The input to be signed. - message (str): The message to be signed - key_pairs (dict): The keys to sign the Transaction with. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`) The input to be signed. + message (str): The message to be signed + key_pairs (dict): The keys to sign the Transaction with. """ # NOTE: To eliminate the dangers of accidentally signing a condition by # reference, we remove the reference of input_ here @@ -362,35 +394,39 @@ class Transaction(object): public_key = input_.owners_before[0] message = sha3_256(message.encode()) if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) try: # cryptoconditions makes no assumptions of the encoding of the # message to sign or verify. It only accepts bytestrings input_.fulfillment.sign( - message.digest(), base58.b58decode(key_pairs[public_key].encode())) + message.digest(), base58.b58decode(key_pairs[public_key].encode()) + ) except KeyError: - raise KeypairMismatchException('Public key {} is not a pair to ' - 'any of the private keys' - .format(public_key)) + raise KeypairMismatchException( + "Public key {} is not a pair to " + "any of the private keys".format(public_key) + ) return input_ @classmethod def _sign_threshold_signature_fulfillment(cls, input_, message, key_pairs): """Signs a ThresholdSha256. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`) The Input to be signed. - message (str): The message to be signed - key_pairs (dict): The keys to sign the Transaction with. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`) The Input to be signed. + message (str): The message to be signed + key_pairs (dict): The keys to sign the Transaction with. """ input_ = deepcopy(input_) message = sha3_256(message.encode()) if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) for owner_before in set(input_.owners_before): # TODO: CC should throw a KeypairMismatchException, instead of @@ -403,24 +439,24 @@ class Transaction(object): # TODO FOR CC: `get_subcondition` is singular. One would not # expect to get a list back. ccffill = input_.fulfillment - subffills = ccffill.get_subcondition_from_vk( - base58.b58decode(owner_before)) + subffills = ccffill.get_subcondition_from_vk(base58.b58decode(owner_before)) if not subffills: - raise KeypairMismatchException('Public key {} cannot be found ' - 'in the fulfillment' - .format(owner_before)) + raise KeypairMismatchException( + "Public key {} cannot be found " + "in the fulfillment".format(owner_before) + ) try: private_key = key_pairs[owner_before] except KeyError: - raise KeypairMismatchException('Public key {} is not a pair ' - 'to any of the private keys' - .format(owner_before)) + raise KeypairMismatchException( + "Public key {} is not a pair " + "to any of the private keys".format(owner_before) + ) # cryptoconditions makes no assumptions of the encoding of the # message to sign or verify. It only accepts bytestrings for subffill in subffills: - subffill.sign( - message.digest(), base58.b58decode(private_key.encode())) + subffill.sign(message.digest(), base58.b58decode(private_key.encode())) return input_ def inputs_valid(self, outputs=None): @@ -445,85 +481,85 @@ class Transaction(object): # to check for outputs, we're just submitting dummy # values to the actual method. This simplifies it's logic # greatly, as we do not have to check against `None` values. - return self._inputs_valid(['dummyvalue' - for _ in self.inputs]) + return self._inputs_valid(["dummyvalue" for _ in self.inputs]) elif self.operation == self.TRANSFER: - return self._inputs_valid([output.fulfillment.condition_uri - for output in outputs]) + return self._inputs_valid( + [output.fulfillment.condition_uri for output in outputs] + ) else: - allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) - raise TypeError('`operation` must be one of {}' - .format(allowed_ops)) + allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS) + raise TypeError("`operation` must be one of {}".format(allowed_ops)) def _inputs_valid(self, output_condition_uris): """Validates an Input against a given set of Outputs. - Note: - The number of `output_condition_uris` must be equal to the - number of Inputs a Transaction has. + Note: + The number of `output_condition_uris` must be equal to the + number of Inputs a Transaction has. - Args: - output_condition_uris (:obj:`list` of :obj:`str`): A list of - Outputs to check the Inputs against. + Args: + output_condition_uris (:obj:`list` of :obj:`str`): A list of + Outputs to check the Inputs against. - Returns: - bool: If all Outputs are valid. + Returns: + bool: If all Outputs are valid. """ if len(self.inputs) != len(output_condition_uris): - raise ValueError('Inputs and ' - 'output_condition_uris must have the same count') + raise ValueError( + "Inputs and " "output_condition_uris must have the same count" + ) tx_dict = self.tx_dict if self.tx_dict else self.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) - tx_dict['id'] = None + tx_dict["id"] = None tx_serialized = Transaction._to_str(tx_dict) def validate(i, output_condition_uri=None): """Validate input against output condition URI""" - return self._input_valid(self.inputs[i], self.operation, - tx_serialized, output_condition_uri) + return self._input_valid( + self.inputs[i], self.operation, tx_serialized, output_condition_uri + ) - return all(validate(i, cond) - for i, cond in enumerate(output_condition_uris)) + return all(validate(i, cond) for i, cond in enumerate(output_condition_uris)) @lru_cache(maxsize=16384) def _input_valid(self, input_, operation, message, output_condition_uri=None): """Validates a single Input against a single Output. - Note: - In case of a `CREATE` Transaction, this method - does not validate against `output_condition_uri`. + Note: + In case of a `CREATE` Transaction, this method + does not validate against `output_condition_uri`. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`) The Input to be signed. - operation (str): The type of Transaction. - message (str): The fulfillment message. - output_condition_uri (str, optional): An Output to check the - Input against. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`) The Input to be signed. + operation (str): The type of Transaction. + message (str): The fulfillment message. + output_condition_uri (str, optional): An Output to check the + Input against. - Returns: - bool: If the Input is valid. + Returns: + bool: If the Input is valid. """ ccffill = input_.fulfillment try: parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri()) except TypeError as e: - print( f"Exception TypeError : {e}") - return False; + print(f"Exception TypeError : {e}") + return False except ValueError as e: - print( f"Exception ValueError : {e}") - return False; + print(f"Exception ValueError : {e}") + return False except ParsingError as e: - print( f"Exception ParsingError : {e}") - return False; + print(f"Exception ParsingError : {e}") + return False except ASN1DecodeError as e: - print( f"Exception ASN1DecodeError : {e}") - return False; + print(f"Exception ASN1DecodeError : {e}") + return False except ASN1EncodeError as e: - print( f"Exception ASN1EncodeError : {e}") - return False; + print(f"Exception ASN1EncodeError : {e}") + return False if operation == self.CREATE: # NOTE: In the case of a `CREATE` transaction, the @@ -533,13 +569,14 @@ class Transaction(object): output_valid = output_condition_uri == ccffill.condition_uri ffill_valid = False - if isinstance( parsed_ffill, ZenroomSha256 ): + if isinstance(parsed_ffill, ZenroomSha256): ffill_valid = parsed_ffill.validate(message=message) else: message = sha3_256(message.encode()) if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) # NOTE: We pass a timestamp to `.validate`, as in case of a timeout # condition we'll have to validate against it @@ -557,17 +594,17 @@ class Transaction(object): def to_dict(self): """Transforms the object to a Python dictionary. - Returns: - dict: The Transaction as an alternative serialization format. + Returns: + dict: The Transaction as an alternative serialization format. """ return { - 'inputs': [input_.to_dict() for input_ in self.inputs], - 'outputs': [output.to_dict() for output in self.outputs], - 'operation': str(self.operation), - 'metadata': self.metadata, - 'asset': self.asset, - 'version': self.version, - 'id': self._id, + "inputs": [input_.to_dict() for input_ in self.inputs], + "outputs": [output.to_dict() for output in self.outputs], + "operation": str(self.operation), + "metadata": self.metadata, + "asset": self.asset, + "version": self.version, + "id": self._id, } @staticmethod @@ -575,22 +612,22 @@ class Transaction(object): def _remove_signatures(tx_dict): """Takes a Transaction dictionary and removes all signatures. - Args: - tx_dict (dict): The Transaction to remove all signatures from. + Args: + tx_dict (dict): The Transaction to remove all signatures from. - Returns: - dict + Returns: + dict """ # NOTE: We remove the reference since we need `tx_dict` only for the # transaction's hash tx_dict = deepcopy(tx_dict) - for input_ in tx_dict['inputs']: + for input_ in tx_dict["inputs"]: # NOTE: Not all Cryptoconditions return a `signature` key (e.g. # ThresholdSha256), so setting it to `None` in any # case could yield incorrect signatures. This is why we only # set it to `None` if it's set in the dict. - input_['fulfillment'] = None + input_["fulfillment"] = None return tx_dict @staticmethod @@ -602,7 +639,7 @@ class Transaction(object): return self._id def to_hash(self): - return self.to_dict()['id'] + return self.to_dict()["id"] @staticmethod def _to_str(value): @@ -638,40 +675,47 @@ class Transaction(object): transactions = [transactions] # create a set of the transactions' asset ids - asset_ids = {tx.id if tx.operation == tx.CREATE - else tx.asset['id'] - for tx in transactions} + asset_ids = { + tx.id if tx.operation == tx.CREATE else tx.asset["id"] + for tx in transactions + } # check that all the transasctions have the same asset id if len(asset_ids) > 1: - raise AssetIdMismatch(('All inputs of all transactions passed' - ' need to have the same asset id')) + raise AssetIdMismatch( + ( + "All inputs of all transactions passed" + " need to have the same asset id" + ) + ) return asset_ids.pop() @staticmethod def validate_id(tx_body): """Validate the transaction ID of a transaction - Args: - tx_body (dict): The Transaction to be transformed. + Args: + tx_body (dict): The Transaction to be transformed. """ # NOTE: Remove reference to avoid side effects # tx_body = deepcopy(tx_body) tx_body = rapidjson.loads(rapidjson.dumps(tx_body)) try: - proposed_tx_id = tx_body['id'] + proposed_tx_id = tx_body["id"] except KeyError: - raise InvalidHash('No transaction id found!') + raise InvalidHash("No transaction id found!") - tx_body['id'] = None + tx_body["id"] = None tx_body_serialized = Transaction._to_str(tx_body) valid_tx_id = Transaction._to_hash(tx_body_serialized) if proposed_tx_id != valid_tx_id: - err_msg = ("The transaction's id '{}' isn't equal to " - "the hash of its body, i.e. it's not valid.") + err_msg = ( + "The transaction's id '{}' isn't equal to " + "the hash of its body, i.e. it's not valid." + ) raise InvalidHash(err_msg.format(proposed_tx_id)) @classmethod @@ -679,23 +723,35 @@ class Transaction(object): def from_dict(cls, tx, skip_schema_validation=True): """Transforms a Python dictionary to a Transaction object. - Args: - tx_body (dict): The Transaction to be transformed. + Args: + tx_body (dict): The Transaction to be transformed. - Returns: - :class:`~planetmint.transactions.common.transaction.Transaction` + Returns: + :class:`~planetmint.transactions.common.transaction.Transaction` """ - operation = tx.get('operation', Transaction.CREATE) if isinstance(tx, dict) else Transaction.CREATE + operation = ( + tx.get("operation", Transaction.CREATE) + if isinstance(tx, dict) + else Transaction.CREATE + ) cls = Transaction.resolve_class(operation) if not skip_schema_validation: cls.validate_id(tx) cls.validate_schema(tx) - inputs = [Input.from_dict(input_) for input_ in tx['inputs']] - outputs = [Output.from_dict(output) for output in tx['outputs']] - return cls(tx['operation'], tx['asset'], inputs, outputs, - tx['metadata'], tx['version'], hash_id=tx['id'], tx_dict=tx) + inputs = [Input.from_dict(input_) for input_ in tx["inputs"]] + outputs = [Output.from_dict(output) for output in tx["outputs"]] + return cls( + tx["operation"], + tx["asset"], + inputs, + outputs, + tx["metadata"], + tx["version"], + hash_id=tx["id"], + tx_dict=tx, + ) @classmethod def from_db(cls, planet, tx_dict_list): @@ -721,22 +777,22 @@ class Transaction(object): tx_map = {} tx_ids = [] for tx in tx_dict_list: - tx.update({'metadata': None}) - tx_map[tx['id']] = tx - tx_ids.append(tx['id']) + tx.update({"metadata": None}) + tx_map[tx["id"]] = tx + tx_ids.append(tx["id"]) assets = list(planet.get_assets(tx_ids)) for asset in assets: if asset is not None: - tx = tx_map[asset['id']] - del asset['id'] - tx['asset'] = asset + tx = tx_map[asset["id"]] + del asset["id"] + tx["asset"] = asset tx_ids = list(tx_map.keys()) metadata_list = list(planet.get_metadata(tx_ids)) for metadata in metadata_list: - tx = tx_map[metadata['id']] - tx.update({'metadata': metadata.get('metadata')}) + tx = tx_map[metadata["id"]] + tx.update({"metadata": metadata.get("metadata")}) if return_list: tx_list = [] @@ -777,14 +833,13 @@ class Transaction(object): input_tx = ctxn if input_tx is None: - raise InputDoesNotExist("input `{}` doesn't exist" - .format(input_txid)) + raise InputDoesNotExist("input `{}` doesn't exist".format(input_txid)) - spent = planet.get_spent(input_txid, input_.fulfills.output, - current_transactions) + spent = planet.get_spent( + input_txid, input_.fulfills.output, current_transactions + ) if spent: - raise DoubleSpend('input `{}` was already spent' - .format(input_txid)) + raise DoubleSpend("input `{}` was already spent".format(input_txid)) output = input_tx.outputs[input_.fulfills.output] input_conditions.append(output) @@ -797,21 +852,32 @@ class Transaction(object): # validate asset id asset_id = self.get_asset_id(input_txs) - if asset_id != self.asset['id']: - raise AssetIdMismatch(('The asset id of the input does not' - ' match the asset id of the' - ' transaction')) + if asset_id != self.asset["id"]: + raise AssetIdMismatch( + ( + "The asset id of the input does not" + " match the asset id of the" + " transaction" + ) + ) - input_amount = sum([input_condition.amount for input_condition in input_conditions]) - output_amount = sum([output_condition.amount for output_condition in self.outputs]) + input_amount = sum( + [input_condition.amount for input_condition in input_conditions] + ) + output_amount = sum( + [output_condition.amount for output_condition in self.outputs] + ) if output_amount != input_amount: - raise AmountError(('The amount used in the inputs `{}`' - ' needs to be same as the amount used' - ' in the outputs `{}`') - .format(input_amount, output_amount)) + raise AmountError( + ( + "The amount used in the inputs `{}`" + " needs to be same as the amount used" + " in the outputs `{}`" + ).format(input_amount, output_amount) + ) if not self.inputs_valid(input_conditions): - raise InvalidSignature('Transaction signature is invalid.') + raise InvalidSignature("Transaction signature is invalid.") return True diff --git a/planetmint/transactions/common/utils.py b/planetmint/transactions/common/utils.py index 7fab27a..cefae87 100644 --- a/planetmint/transactions/common/utils.py +++ b/planetmint/transactions/common/utils.py @@ -10,17 +10,17 @@ import rapidjson import planetmint from planetmint.transactions.common.exceptions import ValidationError -from cryptoconditions import ThresholdSha256, Ed25519Sha256 +from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256 from planetmint.transactions.common.exceptions import ThresholdTooDeep from cryptoconditions.exceptions import UnsupportedTypeError def gen_timestamp(): """The Unix time, rounded to the nearest second. - See https://en.wikipedia.org/wiki/Unix_time + See https://en.wikipedia.org/wiki/Unix_time - Returns: - str: the Unix time + Returns: + str: the Unix time """ return str(round(time.time())) @@ -28,34 +28,33 @@ def gen_timestamp(): def serialize(data): """Serialize a dict into a JSON formatted string. - This function enforces rules like the separator and order of keys. - This ensures that all dicts are serialized in the same way. + This function enforces rules like the separator and order of keys. + This ensures that all dicts are serialized in the same way. - This is specially important for hashing data. We need to make sure that - everyone serializes their data in the same way so that we do not have - hash mismatches for the same structure due to serialization - differences. + This is specially important for hashing data. We need to make sure that + everyone serializes their data in the same way so that we do not have + hash mismatches for the same structure due to serialization + differences. - Args: - data (dict): dict to serialize + Args: + data (dict): dict to serialize - Returns: - str: JSON formatted string + Returns: + str: JSON formatted string """ - return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, - sort_keys=True) + return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True) def deserialize(data): """Deserialize a JSON formatted string into a dict. - Args: - data (str): JSON formatted string. + Args: + data (str): JSON formatted string. - Returns: - dict: dict resulting from the serialization of a JSON formatted - string. + Returns: + dict: dict resulting from the serialization of a JSON formatted + string. """ return rapidjson.loads(data) @@ -63,22 +62,22 @@ def deserialize(data): def validate_txn_obj(obj_name, obj, key, validation_fun): """Validate value of `key` in `obj` using `validation_fun`. - Args: - obj_name (str): name for `obj` being validated. - obj (dict): dictionary object. - key (str): key to be validated in `obj`. - validation_fun (function): function used to validate the value - of `key`. + Args: + obj_name (str): name for `obj` being validated. + obj (dict): dictionary object. + key (str): key to be validated in `obj`. + validation_fun (function): function used to validate the value + of `key`. - Returns: - None: indicates validation successful + Returns: + None: indicates validation successful - Raises: - ValidationError: `validation_fun` will raise exception on failure + Raises: + ValidationError: `validation_fun` will raise exception on failure """ - backend = planetmint.config['database']['backend'] + backend = planetmint.config["database"]["backend"] - if backend == 'localmongodb': + if backend == "localmongodb": data = obj.get(key, {}) if isinstance(data, dict): validate_all_keys_in_obj(obj_name, data, validation_fun) @@ -97,17 +96,17 @@ def validate_all_items_in_list(obj_name, data, validation_fun): def validate_all_keys_in_obj(obj_name, obj, validation_fun): """Validate all (nested) keys in `obj` by using `validation_fun`. - Args: - obj_name (str): name for `obj` being validated. - obj (dict): dictionary object. - validation_fun (function): function used to validate the value - of `key`. + Args: + obj_name (str): name for `obj` being validated. + obj (dict): dictionary object. + validation_fun (function): function used to validate the value + of `key`. - Returns: - None: indicates validation successful + Returns: + None: indicates validation successful - Raises: - ValidationError: `validation_fun` will raise this error on failure + Raises: + ValidationError: `validation_fun` will raise this error on failure """ for key, value in obj.items(): validation_fun(obj_name, key) @@ -119,16 +118,16 @@ def validate_all_keys_in_obj(obj_name, obj, validation_fun): def validate_all_values_for_key_in_obj(obj, key, validation_fun): """Validate value for all (nested) occurrence of `key` in `obj` - using `validation_fun`. + using `validation_fun`. - Args: - obj (dict): dictionary object. - key (str): key whose value is to be validated. - validation_fun (function): function used to validate the value - of `key`. + Args: + obj (dict): dictionary object. + key (str): key whose value is to be validated. + validation_fun (function): function used to validate the value + of `key`. - Raises: - ValidationError: `validation_fun` will raise this error on failure + Raises: + ValidationError: `validation_fun` will raise this error on failure """ for vkey, value in obj.items(): if vkey == key: @@ -150,22 +149,24 @@ def validate_all_values_for_key_in_list(input_list, key, validation_fun): def validate_key(obj_name, key): """Check if `key` contains ".", "$" or null characters. - https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names + https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names - Args: - obj_name (str): object name to use when raising exception - key (str): key to validated + Args: + obj_name (str): object name to use when raising exception + key (str): key to validated - Returns: - None: validation successful + Returns: + None: validation successful - Raises: - ValidationError: will raise exception in case of regex match. + Raises: + ValidationError: will raise exception in case of regex match. """ - if re.search(r'^[$]|\.|\x00', key): - error_str = ('Invalid key name "{}" in {} object. The ' - 'key name cannot contain characters ' - '".", "$" or null characters').format(key, obj_name) + if re.search(r"^[$]|\.|\x00", key): + error_str = ( + 'Invalid key name "{}" in {} object. The ' + "key name cannot contain characters " + '".", "$" or null characters' + ).format(key, obj_name) raise ValidationError(error_str) @@ -176,27 +177,26 @@ def _fulfillment_to_details(fulfillment): fulfillment: Crypto-conditions Fulfillment object """ - if fulfillment.type_name == 'ed25519-sha-256': + if fulfillment.type_name == "ed25519-sha-256": return { - 'type': 'ed25519-sha-256', - 'public_key': base58.b58encode(fulfillment.public_key).decode(), + "type": "ed25519-sha-256", + "public_key": base58.b58encode(fulfillment.public_key).decode(), } - if fulfillment.type_name == 'threshold-sha-256': + if fulfillment.type_name == "threshold-sha-256": subconditions = [ - _fulfillment_to_details(cond['body']) - for cond in fulfillment.subconditions + _fulfillment_to_details(cond["body"]) for cond in fulfillment.subconditions ] return { - 'type': 'threshold-sha-256', - 'threshold': fulfillment.threshold, - 'subconditions': subconditions, + "type": "threshold-sha-256", + "threshold": fulfillment.threshold, + "subconditions": subconditions, } - if fulfillment.type_name == 'zenroom-sha-256': + if fulfillment.type_name == "zenroom-sha-256": return { - 'type': 'zenroom-sha-256', - 'public_key': base58.b58encode(fulfillment.public_key).decode(), - 'script': base58.b58encode(fulfillment.script).decode(), + "type": "zenroom-sha-256", + "public_key": base58.b58encode(fulfillment.public_key).decode(), + "script": base58.b58encode(fulfillment.script).decode(), } raise UnsupportedTypeError(fulfillment.type_name) @@ -211,20 +211,22 @@ def _fulfillment_from_details(data, _depth=0): if _depth == 100: raise ThresholdTooDeep() - if data['type'] == 'ed25519-sha-256': - public_key = base58.b58decode(data['public_key']) + if data["type"] == "ed25519-sha-256": + public_key = base58.b58decode(data["public_key"]) return Ed25519Sha256(public_key=public_key) - if data['type'] == 'threshold-sha-256': - threshold = ThresholdSha256(data['threshold']) - for cond in data['subconditions']: + if data["type"] == "threshold-sha-256": + threshold = ThresholdSha256(data["threshold"]) + for cond in data["subconditions"]: cond = _fulfillment_from_details(cond, _depth + 1) threshold.add_subfulfillment(cond) return threshold - - if data['type'] == 'zenroom-sha-256': - public_key = base58.b58decode(data['public_key']) - script = base58.b58decode(data['script']) - zenroom = ZenroomSha256( script= script, data=None , keys= {public_key}) - - raise UnsupportedTypeError(data.get('type')) + + if data["type"] == "zenroom-sha-256": + public_key = base58.b58decode(data["public_key"]) + script = base58.b58decode(data["script"]) + # zenroom = ZenroomSha256(script=script, data=None, keys={public_key}) + # TODO: assign to zenroom and evaluate the outcome + ZenroomSha256(script=script, data=None, keys={public_key}) + + raise UnsupportedTypeError(data.get("type")) diff --git a/tests/assets/test_zenroom_signing.py b/tests/assets/test_zenroom_signing.py index 1d435f1..65ad81b 100644 --- a/tests/assets/test_zenroom_signing.py +++ b/tests/assets/test_zenroom_signing.py @@ -2,7 +2,6 @@ import pytest import json import base58 from hashlib import sha3_256 -import cryptoconditions as cc from cryptoconditions.types.ed25519 import Ed25519Sha256 from cryptoconditions.types.zenroom import ZenroomSha256 from planetmint.transactions.common.crypto import generate_key_pair @@ -13,17 +12,15 @@ CONDITION_SCRIPT = """ Given that I have a 'string dictionary' named 'houses' inside 'asset' When I create the signature of 'houses' Then print the 'signature'""" - -FULFILL_SCRIPT = \ - """Scenario 'ecdh': Bob verifies the signature from Alice + +FULFILL_SCRIPT = """Scenario 'ecdh': Bob verifies the signature from Alice Given I have a 'ecdh public key' from 'Alice' Given that I have a 'string dictionary' named 'houses' inside 'asset' Given I have a 'signature' named 'signature' inside 'result' When I verify the 'houses' has a signature in 'signature' by 'Alice' Then print the string 'ok'""" - -SK_TO_PK = \ - """Scenario 'ecdh': Create the keypair + +SK_TO_PK = """Scenario 'ecdh': Create the keypair Given that I am known as '{}' Given I have the 'keyring' When I create the ecdh public key @@ -31,16 +28,13 @@ SK_TO_PK = \ Then print my 'ecdh public key' Then print my 'bitcoin address'""" -GENERATE_KEYPAIR = \ - """Scenario 'ecdh': Create the keypair +GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair Given that I am known as 'Pippo' When I create the ecdh key When I create the bitcoin key Then print data""" -ZENROOM_DATA = { - 'also': 'more data' -} +ZENROOM_DATA = {"also": "more data"} HOUSE_ASSETS = { "data": { @@ -52,292 +46,310 @@ HOUSE_ASSETS = { { "name": "Draco", "team": "Slytherin", - } + }, ], } } -metadata = { - 'units': 300, - 'type': 'KG' -} +metadata = {"units": 300, "type": "KG"} + def test_manual_tx_crafting_ext(): - - producer, buyer, reseller = generate_key_pair(), generate_key_pair(), generate_key_pair() + + producer = generate_key_pair() producer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(producer.public_key)) condition_uri = producer_ed25519.condition.serialize_uri() output = { - 'amount': '3000', - 'condition': { - 'details': { - "type": "ed25519-sha-256", - "public_key": producer.public_key - }, - 'uri': condition_uri, - + "amount": "3000", + "condition": { + "details": {"type": "ed25519-sha-256", "public_key": producer.public_key}, + "uri": condition_uri, }, - 'public_keys': [producer.public_key,], + "public_keys": [ + producer.public_key, + ], } input_ = { - 'fulfillment': None, - 'fulfills': None, - 'owners_before': [producer.public_key,] + "fulfillment": None, + "fulfills": None, + "owners_before": [ + producer.public_key, + ], } - version = '2.0' + version = "2.0" prepared_token_tx = { - 'operation': 'CREATE', - 'asset': HOUSE_ASSETS,#rfid_token, - 'metadata': metadata, - 'outputs': [output,], - 'inputs': [input_,], - 'version': version, - 'id': None, + "operation": "CREATE", + "asset": HOUSE_ASSETS, # rfid_token, + "metadata": metadata, + "outputs": [ + output, + ], + "inputs": [ + input_, + ], + "version": version, + "id": None, } - print( f"prepared: {prepared_token_tx}") - - # Create sha3-256 of message to sign - message = json.dumps( - prepared_token_tx, - sort_keys=True, - separators=(',', ':'), - ensure_ascii=False, - ) - message_hash = sha3_256(message.encode()) - - producer_ed25519.sign(message_hash.digest(), base58.b58decode(producer.private_key)) - - fulfillment_uri = producer_ed25519.serialize_uri() + print(f"prepared: {prepared_token_tx}") - prepared_token_tx['inputs'][0]['fulfillment'] = fulfillment_uri - - json_str_tx = json.dumps( - prepared_token_tx, - sort_keys=True, - separators=(',', ':'), - ensure_ascii=False, - ) - creation_txid = sha3_256(json_str_tx.encode()).hexdigest() - - prepared_token_tx['id'] = creation_txid - - print( f"signed: {prepared_token_tx}") - - from planetmint.transactions.types.assets.create import Create - from planetmint.transactions.types.assets.transfer import Transfer - from planetmint.models import Transaction - from planetmint.transactions.common.exceptions import SchemaValidationError, ValidationError - from flask import current_app - from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_ASYNC - validated = None - try: - tx_obj = Transaction.from_dict(prepared_token_tx) - except SchemaValidationError as e: - assert() - except ValidationError as e: - print(e) - assert() - - from planetmint.lib import Planetmint - planet = Planetmint() - validated = planet.validate_transaction(tx_obj) - print( f"\n\nVALIDATED =====: {validated}") - assert not validated == False - -def test_manual_tx_crafting_ext_zenroom(): - producer= generate_key_pair() - producer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(producer.public_key)) - condition_uri = producer_ed25519.condition.serialize_uri() - output = { - 'amount': '3000', - 'condition': { - 'details': { - "type": "ed25519-sha-256", - "public_key": producer.public_key - }, - 'uri': condition_uri, - - }, - 'public_keys': [producer.public_key,], - } - input_ = { - 'fulfillment': None, - 'fulfills': None, - 'owners_before': [producer.public_key,] - } - version = '2.0' - prepared_token_tx = { - 'operation': 'CREATE', - 'asset': HOUSE_ASSETS,#rfid_token, - 'metadata': metadata, - 'outputs': [output,], - 'inputs': [input_,], - 'version': version, - 'id': None, - } - - print( f"prepared: {prepared_token_tx}") - # Create sha3-256 of message to sign message = json.dumps( prepared_token_tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ) message_hash = sha3_256(message.encode()) - + producer_ed25519.sign(message_hash.digest(), base58.b58decode(producer.private_key)) - + fulfillment_uri = producer_ed25519.serialize_uri() - prepared_token_tx['inputs'][0]['fulfillment'] = fulfillment_uri - + prepared_token_tx["inputs"][0]["fulfillment"] = fulfillment_uri + json_str_tx = json.dumps( prepared_token_tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ) creation_txid = sha3_256(json_str_tx.encode()).hexdigest() - prepared_token_tx['id'] = creation_txid + prepared_token_tx["id"] = creation_txid - print( f"signed: {prepared_token_tx}") + print(f"signed: {prepared_token_tx}") - from planetmint.transactions.types.assets.create import Create - from planetmint.transactions.types.assets.transfer import Transfer from planetmint.models import Transaction - from planetmint.transactions.common.exceptions import SchemaValidationError, ValidationError - from flask import current_app - from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_ASYNC + from planetmint.transactions.common.exceptions import ( + SchemaValidationError, + ValidationError, + ) + validated = None try: tx_obj = Transaction.from_dict(prepared_token_tx) - except SchemaValidationError as e: - assert() + except SchemaValidationError: + assert () except ValidationError as e: print(e) - assert() + assert () from planetmint.lib import Planetmint + planet = Planetmint() validated = planet.validate_transaction(tx_obj) - print( f"\n\nVALIDATED =====: {validated}") - assert not validated == False + print(f"\n\nVALIDATED =====: {validated}") + assert validated == False is False + + +def test_manual_tx_crafting_ext_zenroom(): + producer = generate_key_pair() + producer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(producer.public_key)) + condition_uri = producer_ed25519.condition.serialize_uri() + output = { + "amount": "3000", + "condition": { + "details": {"type": "ed25519-sha-256", "public_key": producer.public_key}, + "uri": condition_uri, + }, + "public_keys": [ + producer.public_key, + ], + } + input_ = { + "fulfillment": None, + "fulfills": None, + "owners_before": [ + producer.public_key, + ], + } + version = "2.0" + prepared_token_tx = { + "operation": "CREATE", + "asset": HOUSE_ASSETS, # rfid_token, + "metadata": metadata, + "outputs": [ + output, + ], + "inputs": [ + input_, + ], + "version": version, + "id": None, + } + + print(f"prepared: {prepared_token_tx}") + + # Create sha3-256 of message to sign + message = json.dumps( + prepared_token_tx, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ) + message_hash = sha3_256(message.encode()) + + producer_ed25519.sign(message_hash.digest(), base58.b58decode(producer.private_key)) + + fulfillment_uri = producer_ed25519.serialize_uri() + + prepared_token_tx["inputs"][0]["fulfillment"] = fulfillment_uri + + json_str_tx = json.dumps( + prepared_token_tx, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ) + creation_txid = sha3_256(json_str_tx.encode()).hexdigest() + + prepared_token_tx["id"] = creation_txid + + print(f"signed: {prepared_token_tx}") + + from planetmint.models import Transaction + from planetmint.transactions.common.exceptions import ( + SchemaValidationError, + ValidationError, + ) + + validated = None + try: + tx_obj = Transaction.from_dict(prepared_token_tx) + except SchemaValidationError: + assert () + except ValidationError as e: + print(e) + assert () + + from planetmint.lib import Planetmint + + planet = Planetmint() + validated = planet.validate_transaction(tx_obj) + print(f"\n\nVALIDATED =====: {validated}") + assert validated == False is False + def test_zenroom_signing(): biolabs = generate_key_pair() - version = '2.0' + version = "2.0" - alice = json.loads(ZenroomSha256.run_zenroom(GENERATE_KEYPAIR).output)['keyring'] - bob = json.loads(ZenroomSha256.run_zenroom(GENERATE_KEYPAIR).output)['keyring'] + alice = json.loads(ZenroomSha256.run_zenroom(GENERATE_KEYPAIR).output)["keyring"] + bob = json.loads(ZenroomSha256.run_zenroom(GENERATE_KEYPAIR).output)["keyring"] - zen_public_keys = json.loads(ZenroomSha256.run_zenroom(SK_TO_PK.format('Alice'), - keys={'keyring': alice}).output) - zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(SK_TO_PK.format('Bob'), - keys={'keyring': bob}).output)) + zen_public_keys = json.loads( + ZenroomSha256.run_zenroom( + SK_TO_PK.format("Alice"), keys={"keyring": alice} + ).output + ) + zen_public_keys.update( + json.loads( + ZenroomSha256.run_zenroom( + SK_TO_PK.format("Bob"), keys={"keyring": bob} + ).output + ) + ) + zenroomscpt = ZenroomSha256( + script=FULFILL_SCRIPT, data=ZENROOM_DATA, keys=zen_public_keys + ) + print(f"zenroom is: {zenroomscpt.script}") - - zenroomscpt = ZenroomSha256(script=FULFILL_SCRIPT, data=ZENROOM_DATA, keys=zen_public_keys) - print(F'zenroom is: {zenroomscpt.script}') - # CRYPTO-CONDITIONS: generate the condition uri - condition_uri_zen = zenroomscpt.condition.serialize_uri() - print(F'\nzenroom condition URI: {condition_uri_zen}') + condition_uri_zen = zenroomscpt.condition.serialize_uri() + print(f"\nzenroom condition URI: {condition_uri_zen}") # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary unsigned_fulfillment_dict_zen = { - 'type': zenroomscpt.TYPE_NAME, - 'public_key': base58.b58encode(biolabs.public_key).decode(), + "type": zenroomscpt.TYPE_NAME, + "public_key": base58.b58encode(biolabs.public_key).decode(), } output = { - 'amount': '10', - 'condition': { - 'details': unsigned_fulfillment_dict_zen, - 'uri': condition_uri_zen, - + "amount": "10", + "condition": { + "details": unsigned_fulfillment_dict_zen, + "uri": condition_uri_zen, }, - 'public_keys': [biolabs.public_key,], + "public_keys": [ + biolabs.public_key, + ], } input_ = { - 'fulfillment': None, - 'fulfills': None, - 'owners_before': [biolabs.public_key,] + "fulfillment": None, + "fulfills": None, + "owners_before": [ + biolabs.public_key, + ], } token_creation_tx = { - 'operation': 'CREATE', - 'asset': HOUSE_ASSETS, - 'metadata': None, - 'outputs': [output,], - 'inputs': [input_,], - 'version': version, - 'id': None, + "operation": "CREATE", + "asset": HOUSE_ASSETS, + "metadata": None, + "outputs": [ + output, + ], + "inputs": [ + input_, + ], + "version": version, + "id": None, } # JSON: serialize the transaction-without-id to a json formatted string message = json.dumps( token_creation_tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ) # major workflow: # we store the fulfill script in the transaction/message (zenroom-sha) # the condition script is used to fulfill the transaction and create the signature - # + # # the server should ick the fulfill script and recreate the zenroom-sha and verify the signature - - message = zenroomscpt.sign(message, CONDITION_SCRIPT, alice) - assert(zenroomscpt.validate(message=message)) + assert zenroomscpt.validate(message=message) message = json.loads(message) fulfillment_uri_zen = zenroomscpt.serialize_uri() - - message['inputs'][0]['fulfillment'] = fulfillment_uri_zen + + message["inputs"][0]["fulfillment"] = fulfillment_uri_zen tx = message - tx['id'] = None - json_str_tx = json.dumps( - tx, - sort_keys=True, - skipkeys=False, - separators=(',', ':') - ) + tx["id"] = None + json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":")) # SHA3: hash the serialized id-less transaction to generate the id shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() - message['id'] = shared_creation_txid + message["id"] = shared_creation_txid - - from planetmint.transactions.types.assets.create import Create - from planetmint.transactions.types.assets.transfer import Transfer from planetmint.models import Transaction - from planetmint.transactions.common.exceptions import SchemaValidationError, ValidationError - from flask import current_app + from planetmint.transactions.common.exceptions import ( + SchemaValidationError, + ValidationError, + ) from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_ASYNC + validated = None try: tx_obj = Transaction.from_dict(message) - except SchemaValidationError as e: - assert() + except SchemaValidationError: + assert () except ValidationError as e: print(e) - assert() + assert () from planetmint.lib import Planetmint + planet = Planetmint() validated = planet.validate_transaction(tx_obj) - + mode = BROADCAST_TX_ASYNC status_code, message = planet.write_transaction(tx_obj, mode) - print( f"\n\nstatus and result : {status_code} + {message}") - print( f"VALIDATED : {validated}") - - + print(f"\n\nstatus and result : {status_code} + {message}") + print(f"VALIDATED : {validated}") + assert (validated == False) is False diff --git a/tox.ini b/tox.ini index 0cc9c26..86badc5 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ extras = None commands = flake8 planetmint tests [flake8] -ignore = E126 E127 W504 E302 E126 E305 +ignore = E126 E127 W504 E302 E126 E305 W503 E712 F401 [testenv:docsroot] basepython = {[base]basepython}