From d678f777d1b51c255c4d61ba9e7cace282e153a5 Mon Sep 17 00:00:00 2001
From: Vincent Texier <vit@free.fr>
Date: Thu, 17 Jun 2021 20:00:19 +0200
Subject: [PATCH] [enh] #95 add optional signing_key argument in Document
 sub-classes

argument is Optional[SigningKey] with default=None
---
 duniterpy/documents/block.py         |  5 +++++
 duniterpy/documents/certification.py |  5 +++++
 duniterpy/documents/identity.py      | 16 ++++++++++++++--
 duniterpy/documents/membership.py    |  9 ++++++++-
 duniterpy/documents/peer.py          |  9 ++++++++-
 duniterpy/documents/revocation.py    | 10 +++++++++-
 duniterpy/documents/transaction.py   |  5 +++++
 examples/save_revoke_document.py     |  7 +++----
 examples/send_certification.py       | 17 +++++++----------
 examples/send_identity.py            |  4 +---
 examples/send_membership.py          |  4 +---
 examples/send_transaction.py         | 13 ++++++++-----
 12 files changed, 74 insertions(+), 30 deletions(-)

diff --git a/duniterpy/documents/block.py b/duniterpy/documents/block.py
index c6252fcb..2efc7cff 100644
--- a/duniterpy/documents/block.py
+++ b/duniterpy/documents/block.py
@@ -176,6 +176,7 @@ class Block(Document):
         transactions: List[Transaction],
         inner_hash: str,
         nonce: int,
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Constructor
@@ -206,6 +207,7 @@ class Block(Document):
         :param transactions: transactions documents
         :param inner_hash: the block hash
         :param nonce: the nonce value of the block
+        :param signing_key: SigningKey instance to sign the document (default=None)
         """
         super().__init__(version, currency)
 
@@ -247,6 +249,9 @@ class Block(Document):
         self.inner_hash = inner_hash
         self.nonce = nonce
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     @property
     def blockUID(self) -> BlockUID:
         """
diff --git a/duniterpy/documents/certification.py b/duniterpy/documents/certification.py
index b878e2c5..3a027d02 100644
--- a/duniterpy/documents/certification.py
+++ b/duniterpy/documents/certification.py
@@ -63,6 +63,7 @@ class Certification(Document):
         pubkey_from: str,
         identity: Union[Identity, str],
         timestamp: BlockUID,
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Constructor
@@ -72,6 +73,7 @@ class Certification(Document):
         :param pubkey_from: Pubkey of the certifier
         :param identity: Document instance of the certified identity or identity pubkey string
         :param timestamp: the blockuid
+        :param signing_key: SigningKey instance to sign the document (default=None)
         """
         super().__init__(version, currency)
         self.pubkey_from = pubkey_from
@@ -79,6 +81,9 @@ class Certification(Document):
         self.pubkey_to = identity.pubkey if isinstance(identity, Identity) else identity
         self.timestamp = timestamp
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     @classmethod
     def from_signed_raw(
         cls: Type[CertificationType], signed_raw: str
diff --git a/duniterpy/documents/identity.py b/duniterpy/documents/identity.py
index 0e51d331..7d996f94 100644
--- a/duniterpy/documents/identity.py
+++ b/duniterpy/documents/identity.py
@@ -17,10 +17,12 @@ import re
 from typing import Type, TypeVar
 
 from ..constants import BLOCK_UID_REGEX, PUBKEY_REGEX, SIGNATURE_REGEX, UID_REGEX
+
+# required to type hint cls in classmethod
+from ..key import SigningKey
 from .block_uid import BlockUID
 from .document import Document, MalformedDocumentError
 
-# required to type hint cls in classmethod
 IdentityType = TypeVar("IdentityType", bound="Identity")
 
 
@@ -77,7 +79,13 @@ class Identity(Document):
     }
 
     def __init__(
-        self, version: int, currency: str, pubkey: str, uid: str, timestamp: BlockUID
+        self,
+        version: int,
+        currency: str,
+        pubkey: str,
+        uid: str,
+        timestamp: BlockUID,
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Create an identity document
@@ -87,6 +95,7 @@ class Identity(Document):
         :param pubkey:  Public key of the account linked to the identity
         :param uid: Unique identifier
         :param timestamp: BlockUID instance
+        :param signing_key: SigningKey instance to sign the document (default=None)
         """
         super().__init__(version, currency)
 
@@ -94,6 +103,9 @@ class Identity(Document):
         self.timestamp = timestamp
         self.uid = uid
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     @classmethod
     def from_inline(
         cls: Type[IdentityType], version: int, currency: str, inline: str
diff --git a/duniterpy/documents/membership.py b/duniterpy/documents/membership.py
index fe4ebe82..0bfb9071 100644
--- a/duniterpy/documents/membership.py
+++ b/duniterpy/documents/membership.py
@@ -17,10 +17,12 @@ import re
 from typing import Type, TypeVar
 
 from ..constants import BLOCK_UID_REGEX, PUBKEY_REGEX, SIGNATURE_REGEX
+
+# required to type hint cls in classmethod
+from ..key import SigningKey
 from .block_uid import BlockUID
 from .document import Document, MalformedDocumentError
 
-# required to type hint cls in classmethod
 MembershipType = TypeVar("MembershipType", bound="Membership")
 
 
@@ -82,6 +84,7 @@ class Membership(Document):
         membership_type: str,
         uid: str,
         identity_ts: BlockUID,
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Create a membership document
@@ -93,6 +96,7 @@ class Membership(Document):
         :param membership_type: "IN" or "OUT" to enter or quit the community
         :param uid: Unique identifier of the identity
         :param identity_ts:  BlockUID of the identity
+        :param signing_key: SigningKey instance to sign the document (default=None)
         """
         super().__init__(version, currency)
 
@@ -102,6 +106,9 @@ class Membership(Document):
         self.uid = uid
         self.identity_ts = identity_ts
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     @classmethod
     def from_inline(
         cls: Type[MembershipType],
diff --git a/duniterpy/documents/peer.py b/duniterpy/documents/peer.py
index 3189b154..a7c0ce62 100644
--- a/duniterpy/documents/peer.py
+++ b/duniterpy/documents/peer.py
@@ -19,10 +19,12 @@ from typing import List, Type, TypeVar
 from duniterpy.api.endpoint import Endpoint, endpoint
 
 from ..constants import BLOCK_HASH_REGEX, PUBKEY_REGEX
+
+# required to type hint cls in classmethod
+from ..key import SigningKey
 from .block_uid import BlockUID
 from .document import Document, MalformedDocumentError
 
-# required to type hint cls in classmethod
 PeerType = TypeVar("PeerType", bound="Peer")
 
 
@@ -69,6 +71,7 @@ class Peer(Document):
         pubkey: str,
         block_uid: BlockUID,
         endpoints: List[Endpoint],
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Init Peer instance
@@ -78,6 +81,7 @@ class Peer(Document):
         :param pubkey: Public key of the issuer
         :param block_uid: BlockUID instance timestamp
         :param endpoints: List of endpoints string
+        :param signing_key: SigningKey instance to sign the document (default=None)
         """
         super().__init__(version, currency)
 
@@ -85,6 +89,9 @@ class Peer(Document):
         self.blockUID = block_uid
         self.endpoints: List[Endpoint] = endpoints
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     @classmethod
     def from_signed_raw(cls: Type[PeerType], raw: str) -> PeerType:
         """
diff --git a/duniterpy/documents/revocation.py b/duniterpy/documents/revocation.py
index 789a147e..1e030941 100644
--- a/duniterpy/documents/revocation.py
+++ b/duniterpy/documents/revocation.py
@@ -61,7 +61,11 @@ class Revocation(Document):
     }
 
     def __init__(
-        self, version: int, currency: str, identity: Union[Identity, str]
+        self,
+        version: int,
+        currency: str,
+        identity: Union[Identity, str],
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Init Revocation instance
@@ -69,12 +73,16 @@ class Revocation(Document):
         :param version: Version number
         :param currency: Name of the currency
         :param identity: Identity instance or identity pubkey
+        :param signing_key: SigningKey instance to sign the document (default=None)
         """
         super().__init__(version, currency)
 
         self.identity = identity if isinstance(identity, Identity) else None
         self.pubkey = identity.pubkey if isinstance(identity, Identity) else identity
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     @classmethod
     def from_inline(
         cls: Type[RevocationType], version: int, currency: str, inline: str
diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py
index f837e4b9..6be78625 100644
--- a/duniterpy/documents/transaction.py
+++ b/duniterpy/documents/transaction.py
@@ -548,6 +548,7 @@ class Transaction(Document):
         outputs: List[OutputSource],
         comment: str,
         time: Optional[int] = None,
+        signing_key: SigningKey = None,
     ) -> None:
         """
         Init Transaction instance
@@ -562,6 +563,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)
         """
         super().__init__(version, currency)
         self.blockstamp = blockstamp
@@ -574,6 +576,9 @@ class Transaction(Document):
         self.time = time
         self.signatures: List[str] = list()
 
+        if signing_key is not None:
+            self.sign(signing_key)
+
     def __eq__(self, other: Any) -> bool:
         """
         Check Transaction instances equality
diff --git a/examples/save_revoke_document.py b/examples/save_revoke_document.py
index c67a478b..33b27450 100644
--- a/examples/save_revoke_document.py
+++ b/examples/save_revoke_document.py
@@ -84,10 +84,9 @@ def get_signed_raw_revocation_document(
 
     :rtype: str
     """
-    revocation = Revocation(PROTOCOL_VERSION, identity.currency, identity)
-
     key = SigningKey.from_credentials(salt, password)
-    revocation.sign(key)
+    revocation = Revocation(PROTOCOL_VERSION, identity.currency, identity, key)
+
     return revocation.signed_raw()
 
 
@@ -122,7 +121,7 @@ def save_revoke_document():
     # capture current block to get currency name
     current_block = client(bma.blockchain.current)
 
-    # create our Identity document to sign the Certification document
+    # create our Identity document to sign the revocation document
     identity = get_identity_document(client, current_block, pubkey)
     if identity is None:
         print("Identity not found for pubkey {0}".format(pubkey))
diff --git a/examples/send_certification.py b/examples/send_certification.py
index 81d19ec1..2d20578e 100644
--- a/examples/send_certification.py
+++ b/examples/send_certification.py
@@ -57,14 +57,14 @@ def get_identity_document(
 
 
 def get_certification_document(
-    current_block: dict, self_cert_document: Identity, from_pubkey: str
+    current_block: dict, identity: Identity, signing_key: SigningKey
 ) -> Certification:
     """
     Create and return a Certification document
 
     :param current_block: Current block data
-    :param self_cert_document: Identity document
-    :param from_pubkey: Pubkey of the certifier
+    :param identity: Identity document instance
+    :param signing_key: Signing key of the certifier
 
     :rtype: Certification
     """
@@ -72,9 +72,10 @@ def get_certification_document(
     return Certification(
         version=10,
         currency=current_block["currency"],
-        pubkey_from=from_pubkey,
-        identity=self_cert_document,
+        pubkey_from=signing_key.pubkey,
+        identity=identity,
         timestamp=BlockUID(current_block["number"], current_block["hash"]),
+        signing_key=signing_key,
     )
 
 
@@ -97,7 +98,6 @@ def send_certification():
 
     # create key from credentials
     key = SigningKey.from_credentials(salt, password)
-    pubkey_from = key.pubkey
 
     # prompt entry
     pubkey_to = input("Enter pubkey to certify: ")
@@ -113,10 +113,7 @@ def send_certification():
         return
 
     # send the Certification document to the node
-    certification = get_certification_document(current_block, identity, pubkey_from)
-
-    # sign document
-    certification.sign([key])
+    certification = get_certification_document(current_block, identity, key)
 
     # Here we request for the path wot/certify
     try:
diff --git a/examples/send_identity.py b/examples/send_identity.py
index cefcecc5..4b58bfc8 100644
--- a/examples/send_identity.py
+++ b/examples/send_identity.py
@@ -57,11 +57,9 @@ def get_identity_document(
         pubkey=key.pubkey,
         uid=uid,
         timestamp=timestamp,
+        signing_key=key,
     )
 
-    # sign document
-    identity.sign(key)
-
     return identity
 
 
diff --git a/examples/send_membership.py b/examples/send_membership.py
index 3d49beb9..e47526cd 100644
--- a/examples/send_membership.py
+++ b/examples/send_membership.py
@@ -65,11 +65,9 @@ def get_membership_document(
         membership_type=membership_type,
         uid=uid,
         identity_ts=identity_timestamp,
+        signing_key=key,
     )
 
-    # sign document
-    membership.sign(key)
-
     return membership
 
 
diff --git a/examples/send_transaction.py b/examples/send_transaction.py
index 983c17a2..537ba3fa 100644
--- a/examples/send_transaction.py
+++ b/examples/send_transaction.py
@@ -42,7 +42,11 @@ TRANSACTION_VERSION = 10
 
 
 def get_transaction_document(
-    current_block: dict, source: dict, from_pubkey: str, to_pubkey: str
+    current_block: dict,
+    source: dict,
+    from_pubkey: str,
+    to_pubkey: str,
+    signing_key: SigningKey,
 ) -> Transaction:
     """
     Return a Transaction document
@@ -51,6 +55,7 @@ def get_transaction_document(
     :param source: Source to send
     :param from_pubkey: Public key of the issuer
     :param to_pubkey: Public key of the receiver
+    :param signing_key: Signing key of the issuer
 
     :return: Transaction
     """
@@ -97,6 +102,7 @@ def get_transaction_document(
         unlocks=unlocks,
         outputs=outputs,
         comment="",
+        signing_key=signing_key,
     )
 
     return transaction
@@ -141,12 +147,9 @@ def send_transaction():
 
     # create the transaction document
     transaction = get_transaction_document(
-        current_block, source, pubkey_from, pubkey_to
+        current_block, source, pubkey_from, pubkey_to, key
     )
 
-    # sign document
-    transaction.sign([key])
-
     # send the Transaction document to the node
     try:
         client(bma.tx.process, transaction.signed_raw())
-- 
GitLab