From 717b63fbe433dad965d1e8afbe9f3444a2f1be5e Mon Sep 17 00:00:00 2001 From: Moul <moul@moul.re> Date: Tue, 22 Jun 2021 21:41:54 +0200 Subject: [PATCH] [mod] #173: Make Transaction handy with single and multi-sign Store SigningKey and Signature arguments/attributes in Lists. Allow to pass an instance or a list of SigningKey and pubkey for single and multi signing in Transaction.{__init__(),multi_sign(),check_signatures()} if instance: store in a list Overwrite Document.{sign(),check_signature()} as undefined. Disable pylint error at Transaction class scope: Raise Exception instead of NotImplentedError to not having to disable pylint:W0223 https://stackoverflow.com/a/22224042 Do not erase previous signatures when signing To allow different key owner to sign additonally the Tx doc --- duniterpy/documents/transaction.py | 71 ++++++++++++++---------------- examples/send_transaction.py | 5 ++- tests/key/test_verifying_key.py | 2 +- 3 files changed, 36 insertions(+), 42 deletions(-) diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py index 3e4e3ea1..21e4ba10 100644 --- a/duniterpy/documents/transaction.py +++ b/duniterpy/documents/transaction.py @@ -549,7 +549,7 @@ class Transaction(Document): outputs: List[OutputSource], comment: str, time: Optional[int] = None, - signing_key: SigningKey = None, + signing_keys: Optional[Union[SigningKey, List[SigningKey]]] = None, version: int = VERSION, currency: str = G1_CURRENCY_CODENAME, ) -> None: @@ -564,7 +564,7 @@ class Transaction(Document): :param outputs: List of OutputSource instances :param comment: Comment field :param time: time when the transaction enters the blockchain - :param signing_key: SigningKey instance to sign the document (default=None) + :param signing_keys: SigningKey or list of SigningKey instances to sign the document (default=None) :param version: Document version (default=transaction.VERSION) :param currency: Currency codename (default=constants.CURRENCY_CODENAME_G1) """ @@ -579,8 +579,8 @@ class Transaction(Document): self.time = time self.signatures: List[str] = list() - if signing_key is not None: - self.sign(signing_key) + if signing_keys: + self.multi_sign(signing_keys) def __eq__(self, other: Any) -> bool: """ @@ -591,7 +591,6 @@ class Transaction(Document): return ( self.version == other.version and self.currency == other.currency - and self.signature == other.signature and self.signatures == other.signatures and self.blockstamp == other.blockstamp and self.locktime == other.locktime @@ -608,7 +607,6 @@ class Transaction(Document): ( self.version, self.currency, - self.signature, self.signatures, self.blockstamp, self.locktime, @@ -744,10 +742,7 @@ Comment: {comment} ) # return transaction with signatures - if len(signatures) > 1: - transaction.signatures = signatures - else: - transaction.signature = signatures[0] + transaction.signatures = signatures return transaction @classmethod @@ -837,11 +832,7 @@ Comment: {comment} ) # return transaction with signatures - if len(signatures) > 1: - transaction.signatures = signatures - else: - transaction.signature = signatures[0] - + transaction.signatures = signatures return transaction def raw(self) -> str: @@ -917,11 +908,8 @@ Currency: {1} doc += "{0}\n".format(o.inline()) if self.comment != "": doc += "{0}\n".format(self.comment) - if self.signature is not None: - doc += "{0}\n".format(self.signature) - else: - for signature in self.signatures: - doc += "{0}\n".format(signature) + for signature in self.signatures: + doc += "{0}\n".format(signature) return doc @@ -931,44 +919,49 @@ Currency: {1} :return: """ - if self.signature is None and len(self.signatures) == 0: + if not self.signatures: raise MalformedDocumentError("No signature, can not create raw format") - raw = self.raw() - if self.signature is not None: - signed_raw = raw + self.signature + "\n" - else: - signed_raw = raw - for signature in self.signatures: - signed_raw += "{0}\n".format(signature) + signed_raw = self.raw() + for signature in self.signatures: + signed_raw += f"{signature}\n" return signed_raw - def multi_sign(self, keys: List[SigningKey]) -> None: + def sign(self, key: SigningKey) -> None: + raise Exception("sign() is not implemented, use multi_sign()") + + def multi_sign(self, keys: Union[SigningKey, List[SigningKey]]) -> None: """ Sign the current document with multiple keys - Warning : current signatures will be replaced with the new ones. - - :param keys: List of libnacl keys instance + :param keys: Libnacl key or list of them """ - self.signatures = list() + if isinstance(keys, SigningKey): + keys = [keys] + for key in keys: signature = base64.b64encode(key.signature(bytes(self.raw(), "ascii"))) logging.debug("Signature : \n%s", signature.decode("ascii")) self.signatures.append(signature.decode("ascii")) - def check_signatures(self, pubkeys: List[str]): + def check_signature(self, pubkey: str): + raise Exception("check_signature() is not implemented, use check_signatures()") + + def check_signatures(self, pubkeys: Union[str, List[str]]): """ Check if the signatures matches the pubkeys - :param pubkeys: List of Base58 public keys + :param pubkeys: Base58 public key or list of them :return: """ - if len(self.signatures) == 0: + if not self.signatures: raise Exception("No signatures, can not check signatures") + if isinstance(pubkeys, str): + pubkeys = [pubkeys] + if len(self.signatures) != len(pubkeys): raise Exception("Number of pubkeys not equal to number of signatures") @@ -998,7 +991,7 @@ class SimpleTransaction(Transaction): outputs: List[OutputSource], comment: str, time: int = 0, - signing_key: SigningKey = None, + signing_keys: Optional[Union[SigningKey, List[SigningKey]]] = None, version: int = VERSION, currency: str = G1_CURRENCY_CODENAME, ) -> None: @@ -1013,7 +1006,7 @@ class SimpleTransaction(Transaction): :param outputs: List of OutputSource instances :param comment: Comment field :param time: time when the transaction enters the blockchain (default=0) - :param signing_key: SigningKey instance to sign the document (default=None) + :param signing_keys: SigningKey instance to sign the document (default=None) :param version: Document version (default=transaction.VERSION) :param currency: Currency codename (default=constants.CURRENCY_CODENAME_G1) """ @@ -1026,7 +1019,7 @@ class SimpleTransaction(Transaction): outputs, comment, time=time, - signing_key=signing_key, + signing_keys=signing_keys, version=version, currency=currency, ) diff --git a/examples/send_transaction.py b/examples/send_transaction.py index 7560facd..1ddf631b 100644 --- a/examples/send_transaction.py +++ b/examples/send_transaction.py @@ -15,6 +15,7 @@ import getpass import urllib +from typing import List, Union from duniterpy.api import bma from duniterpy.api.client import Client @@ -42,7 +43,7 @@ def get_transaction_document( source: dict, from_pubkey: str, to_pubkey: str, - signing_key: SigningKey, + signing_keys: Union[SigningKey, List[SigningKey]], ) -> Transaction: """ Return a Transaction document @@ -96,7 +97,7 @@ def get_transaction_document( unlocks=unlocks, outputs=outputs, comment="", - signing_key=signing_key, + signing_keys=signing_keys, currency=current_block["currency"], ) diff --git a/tests/key/test_verifying_key.py b/tests/key/test_verifying_key.py index c6ffb7ae..38016937 100644 --- a/tests/key/test_verifying_key.py +++ b/tests/key/test_verifying_key.py @@ -147,4 +147,4 @@ Solde huile Millepertuis rgjOmzFH5h+hkDbJLk1b88X7Z83HMgTa5rBckeMSdF/yZtItN3zMn09MphcXjffdrKcK+MebwoisLJqV+jXrDg== """ tx = Transaction.from_compact(transaction_document, "g1") - self.assertTrue(tx.check_signature(tx.issuers[0])) + self.assertTrue(tx.check_signatures(tx.issuers)) -- GitLab