diff --git a/duniterpy/documents/certification.py b/duniterpy/documents/certification.py index 3bd30154d956b4b742cfddfe81092d2de121e252..2016dbe525b9e521f6bb3f56245a1cfe25b51a11 100644 --- a/duniterpy/documents/certification.py +++ b/duniterpy/documents/certification.py @@ -1,7 +1,7 @@ import base64 import logging import re -from typing import Optional, TypeVar, Type +from typing import Optional, TypeVar, Type, Union from .block_uid import BlockUID from ..constants import PUBKEY_REGEX, SIGNATURE_REGEX, BLOCK_ID_REGEX, BLOCK_UID_REGEX, UID_REGEX from .document import Document, MalformedDocumentError @@ -136,8 +136,6 @@ Timestamp: {timestamp} # required to type hint cls in classmethod CertificationType = TypeVar('CertificationType', bound='Certification') -# todo: certification document should be created with the certified Identity document in arguments - class Certification(Document): """ @@ -169,7 +167,7 @@ class Certification(Document): "IdtyTimestamp": re_idty_timestamp }} - def __init__(self, version: int, currency: str, pubkey_from: str, pubkey_to: str, + def __init__(self, version: int, currency: str, pubkey_from: str, identity: Union[Identity, str], timestamp: BlockUID, signature: str) -> None: """ Constructor @@ -177,13 +175,14 @@ class Certification(Document): :param version: the UCP version :param currency: the currency of the blockchain :param pubkey_from: Pubkey of the certifier - :param pubkey_to: Pubkey of the certified + :param identity: Document instance of the certified identity or identity pubkey string :param timestamp: the blockuid :param signature: the signature of the document """ super().__init__(version, currency, [signature]) self.pubkey_from = pubkey_from - self.pubkey_to = pubkey_to + self.identity = identity if isinstance(identity, Identity) else None + self.pubkey_to = identity.pubkey if isinstance(identity, Identity) else identity self.timestamp = timestamp @classmethod @@ -209,16 +208,16 @@ class Certification(Document): pubkey_from = Certification.parse_field("Issuer", lines[n]) n += 1 - pubkey_to = Certification.parse_field("IdtyIssuer", lines[n]) + identity_pubkey = Certification.parse_field("IdtyIssuer", lines[n]) n += 1 - Certification.parse_field("IdtyUniqueID", lines[n]) + identity_uid = Certification.parse_field("IdtyUniqueID", lines[n]) n += 1 - BlockUID.from_str(Certification.parse_field("IdtyTimestamp", lines[n])) + identity_timestamp = BlockUID.from_str(Certification.parse_field("IdtyTimestamp", lines[n])) n += 1 - Certification.parse_field("IdtySignature", lines[n]) + identity_signature = Certification.parse_field("IdtySignature", lines[n]) n += 1 timestamp = BlockUID.from_str(Certification.parse_field("CertTimestamp", lines[n])) @@ -226,7 +225,9 @@ class Certification(Document): signature = Certification.parse_field("Signature", lines[n]) - return cls(version, currency, pubkey_from, pubkey_to, timestamp, signature) + identity = Identity(version, currency, identity_pubkey, identity_uid, identity_timestamp, identity_signature) + + return cls(version, currency, pubkey_from, identity, timestamp, signature) @classmethod def from_inline(cls: Type[CertificationType], version: int, currency: str, blockhash: Optional[str], @@ -234,6 +235,9 @@ class Certification(Document): """ Return Certification instance from inline document + Only self.pubkey_to is populated. + You must populate self.identity with an Identity instance to use raw/sign/signed_raw methods + :param version: Version of document :param currency: Name of the currency :param blockhash: Hash of the block @@ -254,13 +258,13 @@ class Certification(Document): signature = cert_data.group(4) return cls(version, currency, pubkey_from, pubkey_to, timestamp, signature) - def raw_for_certified(self, certified: Identity) -> str: + def raw(self) -> str: """ - Return a raw document of the self-certification of the Identity - - :param Identity certified: Identity document instance - :return: + Return a raw document of the certification """ + if not isinstance(self.identity, Identity): + raise MalformedDocumentError("Can not return full certification document created from inline") + return """Version: {version} Type: Certification Currency: {currency} @@ -273,35 +277,39 @@ CertTimestamp: {timestamp} """.format(version=self.version, currency=self.currency, issuer=self.pubkey_from, - certified_pubkey=certified.pubkey, - certified_uid=certified.uid, - certified_ts=certified.timestamp, - certified_signature=certified.signatures[0], + certified_pubkey=self.identity.pubkey, + certified_uid=self.identity.uid, + certified_ts=self.identity.timestamp, + certified_signature=self.identity.signatures[0], timestamp=self.timestamp) - def sign_for_certified(self, certified: Identity, keys: list) -> None: + def sign(self, keys: list) -> None: """ Sign the current document with the keys for the certified Identity given Warning : current signatures will be replaced with the new ones. - :param certified: Identity instance certified :param keys: List of libnacl key instances """ + if not isinstance(self.identity, Identity): + raise MalformedDocumentError("Can not return full certification document created from inline") + self.signatures = [] for key in keys: - signing = base64.b64encode(key.signature(bytes(self.raw_for_certified(certified), 'ascii'))) + signing = base64.b64encode(key.signature(bytes(self.raw(), 'ascii'))) logging.debug("Signature : \n{0}".format(signing.decode("ascii"))) self.signatures.append(signing.decode("ascii")) - def signed_raw_for_certified(self, certified: Identity) -> str: + def signed_raw(self) -> str: """ Return signed raw document of the certification for the certified Identity instance - :param certified: Certified Identity instance :return: """ - raw = self.raw_for_certified(certified) + if not isinstance(self.identity, Identity): + raise MalformedDocumentError("Can not return full certification document created from inline") + + raw = self.raw() signed = "\n".join(self.signatures) signed_raw = raw + signed + "\n" return signed_raw @@ -319,6 +327,7 @@ CertTimestamp: {timestamp} # required to type hint cls in classmethod RevocationType = TypeVar('RevocationType', bound='Revocation') + # todo: Revocation document should be created with the revoked Identity document in arguments diff --git a/examples/send_certification.py b/examples/send_certification.py index 59c96a24c5a965996ac42ee5bb380ea3647a6d78..f76d46f22f0d85779452b82d75992de078a5b3c0 100644 --- a/examples/send_certification.py +++ b/examples/send_certification.py @@ -70,17 +70,12 @@ def get_certification_document(current_block: dict, self_cert_document: Identity :rtype: Certification """ # construct Certification Document - certification = Certification( - version=10, - currency=current_block['currency'], - pubkey_from=from_pubkey, - pubkey_to=self_cert_document.pubkey, - timestamp=BlockUID(current_block['number'], current_block['hash']), - signature="" - ) + certification = Certification(version=10, currency=current_block['currency'], pubkey_from=from_pubkey, + identity=self_cert_document, + timestamp=BlockUID(current_block['number'], current_block['hash']), signature="") # sign document key = SigningKey(salt, password) - certification.sign_for_certified(self_cert_document, [key]) + certification.sign([key]) return certification @@ -118,7 +113,7 @@ async def main(): certification = get_certification_document(current_block, identity, pubkey_from, salt, password) # Here we request for the path wot/certify - response = await client(bma.wot.certify, certification.signed_raw_for_certified(identity)) + response = await client(bma.wot.certify, certification.signed_raw()) if response.status == 200: print(await response.text()) diff --git a/tests/documents/test_certification.py b/tests/documents/test_certification.py index f9380da9063272fc425ca3eaf0698d695cad9623..9bc3ad1c9c0a7e63a089d45a7b27589dcab3a35b 100644 --- a/tests/documents/test_certification.py +++ b/tests/documents/test_certification.py @@ -91,11 +91,11 @@ J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBf pubkey_to = "HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd" timestamp = BlockUID(36, "1076F10A7397715D2BEE82579861999EA1F274AC") signature = "SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk" - selfcert = Identity(version, currency, pubkey_to, "lolcat", + identity = Identity(version, currency, pubkey_to, "lolcat", BlockUID(32, "DB30D958EE5CB75186972286ED3F4686B8A1C2CD"), "J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci") - certification = Certification(version, currency, pubkey_from, pubkey_to, timestamp, signature) + certification = Certification(version, currency, pubkey_from, identity, timestamp, signature) result = """Version: 2 Type: Certification @@ -108,10 +108,10 @@ IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6B CertTimestamp: 36-1076F10A7397715D2BEE82579861999EA1F274AC SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk """ - self.assertEqual(certification.signed_raw_for_certified(selfcert), result) + self.assertEqual(certification.signed_raw(), result) - from_raw = Certification.from_signed_raw(certification.signed_raw_for_certified(selfcert)) - self.assertEqual(from_raw.signed_raw_for_certified(selfcert), result) + from_raw = Certification.from_signed_raw(certification.signed_raw()) + self.assertEqual(from_raw.signed_raw(), result) def test_revokation_from_inline(self): version = 2