Skip to content
Snippets Groups Projects
Commit e685da93 authored by Vincent Texier's avatar Vincent Texier
Browse files

issue #52 add type hinting and change some method signatures (break BC!)

Document sign, raw and signed_raw methods does not have arguments anymore
Certification sign, raw and signed_raw are renamed
Revocation sign, raw and signed_raw are renamed
verifying_key.py verify_document signature has changed
parent e118a6fb
No related branches found
No related tags found
No related merge requests found
import base64 import base64
import hashlib import hashlib
import re import re
from typing import Union, TypeVar, Type, Optional, List from typing import Union, TypeVar, Type, Optional, List, Sequence
from .certification import Identity, Certification, Revocation from .certification import Identity, Certification, Revocation
from .document import Document, MalformedDocumentError from .document import Document, MalformedDocumentError
...@@ -37,6 +37,8 @@ class BlockUID: ...@@ -37,6 +37,8 @@ class BlockUID:
:param blockid: The block id :param blockid: The block id
""" """
data = BlockUID.re_block_uid.match(blockid) data = BlockUID.re_block_uid.match(blockid)
if data is None:
raise MalformedDocumentError("BlockUID")
try: try:
number = int(data.group(1)) number = int(data.group(1))
except AttributeError: except AttributeError:
...@@ -52,19 +54,29 @@ class BlockUID: ...@@ -52,19 +54,29 @@ class BlockUID:
def __str__(self) -> str: def __str__(self) -> str:
return "{0}-{1}".format(self.number, self.sha_hash) return "{0}-{1}".format(self.number, self.sha_hash)
def __eq__(self, other: Type[BlockUIDType]) -> bool: def __eq__(self, other: object) -> bool:
if not isinstance(other, BlockUID):
return False
return self.number == other.number and self.sha_hash == other.sha_hash return self.number == other.number and self.sha_hash == other.sha_hash
def __lt__(self, other: Type[BlockUIDType]) -> bool: def __lt__(self, other: object) -> bool:
if not isinstance(other, BlockUID):
return False
return self.number < other.number return self.number < other.number
def __gt__(self, other: Type[BlockUIDType]) -> bool: def __gt__(self, other: object) -> bool:
if not isinstance(other, BlockUID):
return False
return self.number > other.number return self.number > other.number
def __le__(self, other: Type[BlockUIDType]) -> bool: def __le__(self, other: object) -> bool:
if not isinstance(other, BlockUID):
return False
return self.number <= other.number return self.number <= other.number
def __ge__(self, other: Type[BlockUIDType]) -> bool: def __ge__(self, other: object) -> bool:
if not isinstance(other, BlockUID):
return False
return self.number >= other.number return self.number >= other.number
def __hash__(self) -> int: def __hash__(self) -> int:
...@@ -215,9 +227,9 @@ The class Block handles Block documents. ...@@ -215,9 +227,9 @@ The class Block handles Block documents.
issuers_frame: int, issuers_frame: int,
issuers_frame_var: int, issuers_frame_var: int,
different_issuers_count: int, different_issuers_count: int,
prev_hash: str, prev_hash: Optional[str],
prev_issuer: str, prev_issuer: Optional[str],
parameters: str, parameters: Optional[Sequence[str]],
members_count: int, members_count: int,
identities: List[Identity], identities: List[Identity],
joiners: List[Membership], joiners: List[Membership],
...@@ -297,7 +309,7 @@ The class Block handles Block documents. ...@@ -297,7 +309,7 @@ The class Block handles Block documents.
self.noonce = noonce self.noonce = noonce
@property @property
def blockUID(self) -> BlockUIDType: def blockUID(self) -> BlockUID:
return BlockUID(self.number, self.proof_of_work()) return BlockUID(self.number, self.proof_of_work())
@classmethod @classmethod
...@@ -326,9 +338,10 @@ The class Block handles Block documents. ...@@ -326,9 +338,10 @@ The class Block handles Block documents.
mediantime = int(Block.parse_field("MedianTime", lines[n])) mediantime = int(Block.parse_field("MedianTime", lines[n]))
n += 1 n += 1
ud = Block.re_universaldividend.match(lines[n]) ud_match = Block.re_universaldividend.match(lines[n])
unit_base = None ud = None
if ud is not None: unit_base = 0
if ud_match is not None:
ud = int(Block.parse_field("UD", lines[n])) ud = int(Block.parse_field("UD", lines[n]))
n += 1 n += 1
...@@ -354,19 +367,25 @@ The class Block handles Block documents. ...@@ -354,19 +367,25 @@ The class Block handles Block documents.
prev_hash = None prev_hash = None
prev_issuer = None prev_issuer = None
if number > 0: if number > 0:
prev_hash = Block.parse_field("PreviousHash", lines[n]) prev_hash = str(Block.parse_field("PreviousHash", lines[n]))
n += 1 n += 1
prev_issuer = Block.parse_field("PreviousIssuer", lines[n]) prev_issuer = str(Block.parse_field("PreviousIssuer", lines[n]))
n += 1 n += 1
parameters = None parameters = None
if number == 0: if number == 0:
try: try:
if version >= 10: if version >= 10:
parameters = Block.re_parameters_v10.match(lines[n]).groups() params_match = Block.re_parameters_v10.match(lines[n])
if params_match is None:
raise MalformedDocumentError("Parameters")
parameters = params_match.groups()
else: else:
parameters = Block.re_parameters.match(lines[n]).groups() params_match = Block.re_parameters.match(lines[n])
if params_match is None:
raise MalformedDocumentError("Parameters")
parameters = params_match.groups()
n += 1 n += 1
except AttributeError: except AttributeError:
raise MalformedDocumentError("Parameters") raise MalformedDocumentError("Parameters")
...@@ -421,7 +440,9 @@ The class Block handles Block documents. ...@@ -421,7 +440,9 @@ The class Block handles Block documents.
if Block.re_excluded.match(lines[n]): if Block.re_excluded.match(lines[n]):
n += 1 n += 1
while Block.re_certifications.match(lines[n]) is None: while Block.re_certifications.match(lines[n]) is None:
exclusion = Block.re_exclusion.match(lines[n]).group(1) exclusion_match = Block.re_exclusion.match(lines[n])
if exclusion_match is not None:
exclusion = exclusion_match.group(1)
excluded.append(exclusion) excluded.append(exclusion)
n += 1 n += 1
...@@ -502,7 +523,7 @@ IssuersFrameVar: {1} ...@@ -502,7 +523,7 @@ IssuersFrameVar: {1}
DifferentIssuersCount: {2} DifferentIssuersCount: {2}
""".format(self.issuers_frame, self.issuers_frame_var, self.different_issuers_count) """.format(self.issuers_frame, self.issuers_frame_var, self.different_issuers_count)
if self.number == 0: if self.number == 0 and self.parameters is not None:
str_params = ":".join([str(p) for p in self.parameters]) str_params = ":".join([str(p) for p in self.parameters])
doc += "Parameters: {0}\n".format(str_params) doc += "Parameters: {0}\n".format(str_params)
else: else:
...@@ -571,17 +592,27 @@ Nonce: {nonce} ...@@ -571,17 +592,27 @@ Nonce: {nonce}
signing = base64.b64encode(key.signature(bytes(signed, 'ascii'))) signing = base64.b64encode(key.signature(bytes(signed, 'ascii')))
self.signatures = [signing.decode("ascii")] self.signatures = [signing.decode("ascii")]
def __eq__(self, other: Type[BlockType]) -> bool: def __eq__(self, other: object) -> bool:
if not isinstance(other, Block):
return False
return self.blockUID == other.blockUID return self.blockUID == other.blockUID
def __lt__(self, other: Type[BlockType]) -> bool: def __lt__(self, other: object) -> bool:
if not isinstance(other, Block):
return False
return self.blockUID < other.blockUID return self.blockUID < other.blockUID
def __gt__(self, other: Type[BlockType]) -> bool: def __gt__(self, other: object) -> bool:
if not isinstance(other, Block):
return False
return self.blockUID > other.blockUID return self.blockUID > other.blockUID
def __le__(self, other: Type[BlockType]) -> bool: def __le__(self, other: object) -> bool:
if not isinstance(other, Block):
return False
return self.blockUID <= other.blockUID return self.blockUID <= other.blockUID
def __ge__(self, other: Type[BlockType]) -> bool: def __ge__(self, other: object) -> bool:
if not isinstance(other, Block):
return False
return self.blockUID >= other.blockUID return self.blockUID >= other.blockUID
import base64 import base64
import logging import logging
import re import re
from typing import Optional, TypeVar, Type, List
from duniterpy.documents import BlockUID
from ..constants import PUBKEY_REGEX, SIGNATURE_REGEX, BLOCK_ID_REGEX, BLOCK_UID_REGEX, UID_REGEX from ..constants import PUBKEY_REGEX, SIGNATURE_REGEX, BLOCK_ID_REGEX, BLOCK_UID_REGEX, UID_REGEX
from .document import Document, MalformedDocumentError from .document import Document, MalformedDocumentError
# required to type hint cls in classmethod
IdentityType = TypeVar('IdentityType', bound='Identity')
class Identity(Document): class Identity(Document):
""" """
...@@ -29,16 +34,17 @@ class Identity(Document): ...@@ -29,16 +34,17 @@ class Identity(Document):
"Timestamp": re_timestamp "Timestamp": re_timestamp
}} }}
def __init__(self, version, currency, pubkey, uid, ts, signature): def __init__(self, version: int, currency: str, pubkey: str, uid: str, ts: BlockUID,
signature: Optional[str]) -> None:
""" """
Create an identity document Create an identity document
:param int version: Version of the document :param version: Version of the document
:param str currency: Name of the currency :param currency: Name of the currency
:param str pubkey: Public key of the account linked to the identity :param pubkey: Public key of the account linked to the identity
:param str uid: Unique identifier :param uid: Unique identifier
:param BlockUID ts: Block timestamp :param ts: Block timestamp
:param str|None signature: Signature of the document :param signature: Signature of the document
""" """
if signature: if signature:
super().__init__(version, currency, [signature]) super().__init__(version, currency, [signature])
...@@ -49,7 +55,14 @@ class Identity(Document): ...@@ -49,7 +55,14 @@ class Identity(Document):
self.uid = uid self.uid = uid
@classmethod @classmethod
def from_inline(cls, version, currency, inline): def from_inline(cls: Type[IdentityType], version: int, currency: str, inline: str) -> IdentityType:
"""
Return Identity instance from inline Identity string
:param version: Document version number
:param currency: Name of the currency
:param inline: Inline string of the Identity
:return:
"""
from .block import BlockUID from .block import BlockUID
selfcert_data = Identity.re_inline.match(inline) selfcert_data = Identity.re_inline.match(inline)
...@@ -63,7 +76,12 @@ class Identity(Document): ...@@ -63,7 +76,12 @@ class Identity(Document):
return cls(version, currency, pubkey, uid, ts, signature) return cls(version, currency, pubkey, uid, ts, signature)
@classmethod @classmethod
def from_signed_raw(cls, signed_raw): def from_signed_raw(cls: Type[IdentityType], signed_raw: str) -> IdentityType:
"""
Return Identity instance from a signed_raw string
:param signed_raw: Signed raw document
:return:
"""
from .block import BlockUID from .block import BlockUID
n = 0 n = 0
...@@ -91,7 +109,11 @@ class Identity(Document): ...@@ -91,7 +109,11 @@ class Identity(Document):
return cls(version, currency, pubkey, uid, ts, signature) return cls(version, currency, pubkey, uid, ts, signature)
def raw(self): def raw(self) -> str:
"""
Return a raw document of the Identity
:return:
"""
return """Version: {version} return """Version: {version}
Type: Identity Type: Identity
Currency: {currency} Currency: {currency}
...@@ -104,7 +126,11 @@ Timestamp: {timestamp} ...@@ -104,7 +126,11 @@ Timestamp: {timestamp}
uid=self.uid, uid=self.uid,
timestamp=self.timestamp) timestamp=self.timestamp)
def inline(self): def inline(self) -> str:
"""
Return an inline string of the Identity
:return:
"""
return "{pubkey}:{signature}:{timestamp}:{uid}".format( return "{pubkey}:{signature}:{timestamp}:{uid}".format(
pubkey=self.pubkey, pubkey=self.pubkey,
signature=self.signatures[0], signature=self.signatures[0],
...@@ -112,6 +138,10 @@ Timestamp: {timestamp} ...@@ -112,6 +138,10 @@ Timestamp: {timestamp}
uid=self.uid) uid=self.uid)
# required to type hint cls in classmethod
CertificationType = TypeVar('CertificationType', bound='Certification')
class Certification(Document): class Certification(Document):
""" """
A document describing a certification. A document describing a certification.
...@@ -142,17 +172,17 @@ class Certification(Document): ...@@ -142,17 +172,17 @@ class Certification(Document):
"IdtyTimestamp": re_idty_timestamp "IdtyTimestamp": re_idty_timestamp
}} }}
def __init__(self, version, currency, pubkey_from, pubkey_to, def __init__(self, version: int, currency: str, pubkey_from: str, pubkey_to: str,
timestamp, signature): timestamp: BlockUID, signature: str) -> None:
""" """
Constructor Constructor
:param int version: the UCP version :param version: the UCP version
:param str currency: the currency of the blockchain :param currency: the currency of the blockchain
:param str pubkey_from: :param pubkey_from: Pubkey of the certifier
:param str pubkey_to: :param pubkey_to: Pubkey of the certified
:param BlockUID timestamp: the blockuid :param timestamp: the blockuid
:param str signature: the signature of the document :param signature: the signature of the document
""" """
super().__init__(version, currency, [signature]) super().__init__(version, currency, [signature])
self.pubkey_from = pubkey_from self.pubkey_from = pubkey_from
...@@ -160,7 +190,13 @@ class Certification(Document): ...@@ -160,7 +190,13 @@ class Certification(Document):
self.timestamp = timestamp self.timestamp = timestamp
@classmethod @classmethod
def from_signed_raw(cls, signed_raw): def from_signed_raw(cls: Type[CertificationType], signed_raw: str) -> CertificationType:
"""
Return Certification instance from signed raw document
:param signed_raw: Signed raw document
:return:
"""
from .block import BlockUID from .block import BlockUID
n = 0 n = 0
...@@ -198,13 +234,15 @@ class Certification(Document): ...@@ -198,13 +234,15 @@ class Certification(Document):
return cls(version, currency, pubkey_from, pubkey_to, timestamp, signature) return cls(version, currency, pubkey_from, pubkey_to, timestamp, signature)
@classmethod @classmethod
def from_inline(cls, version, currency, blockhash, inline): def from_inline(cls: Type[CertificationType], version: int, currency: str, blockhash: Optional[str],
inline: str) -> CertificationType:
""" """
From inline version in block Return Certification instance from inline document
:param version:
:param currency: :param version: Version of document
:param blockhash: :param currency: Name of the currency
:param inline: :param blockhash: Hash of the block
:param inline: Inline document
:return: :return:
""" """
from .block import BlockUID from .block import BlockUID
...@@ -214,7 +252,7 @@ class Certification(Document): ...@@ -214,7 +252,7 @@ class Certification(Document):
pubkey_from = cert_data.group(1) pubkey_from = cert_data.group(1)
pubkey_to = cert_data.group(2) pubkey_to = cert_data.group(2)
blockid = int(cert_data.group(3)) blockid = int(cert_data.group(3))
if blockid == 0: if blockid == 0 or blockhash is None:
timestamp = BlockUID.empty() timestamp = BlockUID.empty()
else: else:
timestamp = BlockUID(blockid, blockhash) timestamp = BlockUID(blockid, blockhash)
...@@ -222,10 +260,11 @@ class Certification(Document): ...@@ -222,10 +260,11 @@ class Certification(Document):
signature = cert_data.group(4) signature = cert_data.group(4)
return cls(version, currency, pubkey_from, pubkey_to, timestamp, signature) return cls(version, currency, pubkey_from, pubkey_to, timestamp, signature)
def raw(self, selfcert): def raw_for_certified(self, certified: Identity) -> str:
""" """
Return a raw document of the self-certification of the Identity
:param Identity selfcert: :param Identity certified: Identity document instance
:return: :return:
""" """
return """Version: {version} return """Version: {version}
...@@ -240,34 +279,53 @@ CertTimestamp: {timestamp} ...@@ -240,34 +279,53 @@ CertTimestamp: {timestamp}
""".format(version=self.version, """.format(version=self.version,
currency=self.currency, currency=self.currency,
issuer=self.pubkey_from, issuer=self.pubkey_from,
certified_pubkey=selfcert.pubkey, certified_pubkey=certified.pubkey,
certified_uid=selfcert.uid, certified_uid=certified.uid,
certified_ts=selfcert.timestamp, certified_ts=certified.timestamp,
certified_signature=selfcert.signatures[0], certified_signature=certified.signatures[0],
timestamp=self.timestamp) timestamp=self.timestamp)
def sign(self, selfcert, keys): def sign_for_certified(self, certified: Identity, keys: list) -> None:
""" """
Sign the current document. Sign the current document with the keys for the certified Identity given
Warning : current signatures will be replaced with the new ones. Warning : current signatures will be replaced with the new ones.
:param certified: Identity instance certified
:param keys: List of libnacl key instances
""" """
self.signatures = [] self.signatures = []
for key in keys: for key in keys:
signing = base64.b64encode(key.signature(bytes(self.raw(selfcert), 'ascii'))) signing = base64.b64encode(key.signature(bytes(self.raw_for_certified(certified), 'ascii')))
logging.debug("Signature : \n{0}".format(signing.decode("ascii"))) logging.debug("Signature : \n{0}".format(signing.decode("ascii")))
self.signatures.append(signing.decode("ascii")) self.signatures.append(signing.decode("ascii"))
def signed_raw(self, selfcert): def signed_raw_for_certified(self, certified: Identity) -> str:
raw = self.raw(selfcert) """
Return signed raw document of the certification for the certified Identity instance
:param certified: Certified Identity instance
:return:
"""
raw = self.raw_for_certified(certified)
signed = "\n".join(self.signatures) signed = "\n".join(self.signatures)
signed_raw = raw + signed + "\n" signed_raw = raw + signed + "\n"
return signed_raw return signed_raw
def inline(self): def inline(self) -> str:
"""
Return inline document string
:return:
"""
return "{0}:{1}:{2}:{3}".format(self.pubkey_from, self.pubkey_to, return "{0}:{1}:{2}:{3}".format(self.pubkey_from, self.pubkey_to,
self.timestamp.number, self.signatures[0]) self.timestamp.number, self.signatures[0])
# required to type hint cls in classmethod
RevocationType = TypeVar('RevocationType', bound='Revocation')
class Revocation(Document): class Revocation(Document):
""" """
A document describing a self-revocation. A document describing a self-revocation.
...@@ -291,20 +349,27 @@ class Revocation(Document): ...@@ -291,20 +349,27 @@ class Revocation(Document):
"IdtySignature": re_idtysignature, "IdtySignature": re_idtysignature,
}} }}
def __init__(self, version, currency, pubkey, signature): def __init__(self, version: int, currency: str, pubkey: str, signature: str) -> None:
""" """
Constructor Init Revocation instance
:param version: Version number
:param currency: Name of the currency
:param pubkey: Public key of the issuer
:param signature: Signature
""" """
super().__init__(version, currency, [signature]) super().__init__(version, currency, [signature])
self.pubkey = pubkey self.pubkey = pubkey
@classmethod @classmethod
def from_inline(cls, version, currency, inline): def from_inline(cls: Type[RevocationType], version: int, currency: str, inline: str) -> RevocationType:
""" """
From inline version in block Return Revocation document instance from inline string
:param int version:
:param str currency: :param version: Version number
:param str inline: :param currency: Name of the currency
:param inline: Inline document
:return: :return:
""" """
cert_data = Revocation.re_inline.match(inline) cert_data = Revocation.re_inline.match(inline)
...@@ -315,11 +380,12 @@ class Revocation(Document): ...@@ -315,11 +380,12 @@ class Revocation(Document):
return cls(version, currency, pubkey, signature) return cls(version, currency, pubkey, signature)
@classmethod @classmethod
def from_signed_raw(cls, signed_raw): def from_signed_raw(cls: Type[RevocationType], signed_raw: str) -> RevocationType:
""" """
Instanciates a revocation from a signed raw file Return Revocation document instance from a signed raw string
:param str signed_raw: raw document file in duniter format
:return: a revocation instance :param signed_raw: raw document file in duniter format
:return:
""" """
lines = signed_raw.splitlines(True) lines = signed_raw.splitlines(True)
n = 0 n = 0
...@@ -342,7 +408,13 @@ class Revocation(Document): ...@@ -342,7 +408,13 @@ class Revocation(Document):
return cls(version, currency, issuer, signature) return cls(version, currency, issuer, signature)
@staticmethod @staticmethod
def extract_self_cert(signed_raw): def extract_self_cert(signed_raw: str) -> Identity:
"""
Return self-certified Identity instance from the signed raw Revocation document
:param signed_raw: Signed raw document string
:return:
"""
lines = signed_raw.splitlines(True) lines = signed_raw.splitlines(True)
n = 0 n = 0
...@@ -369,13 +441,19 @@ class Revocation(Document): ...@@ -369,13 +441,19 @@ class Revocation(Document):
return Identity(version, currency, issuer, unique_id, timestamp, signature) return Identity(version, currency, issuer, unique_id, timestamp, signature)
def inline(self): def inline(self) -> str:
"""
Return inline document string
:return:
"""
return "{0}:{1}".format(self.pubkey, self.signatures[0]) return "{0}:{1}".format(self.pubkey, self.signatures[0])
def raw(self, selfcert): def raw_for_revoked(self, revoked: Identity) -> str:
""" """
Return Revocation raw document string from Identity instance
:param Identity selfcert: :param Identity revoked: Identity instance
:return: :return:
""" """
return """Version: {version} return """Version: {version}
...@@ -387,23 +465,33 @@ IdtyTimestamp: {timestamp} ...@@ -387,23 +465,33 @@ IdtyTimestamp: {timestamp}
IdtySignature: {signature} IdtySignature: {signature}
""".format(version=self.version, """.format(version=self.version,
currency=self.currency, currency=self.currency,
pubkey=selfcert.pubkey, pubkey=revoked.pubkey,
uid=selfcert.uid, uid=revoked.uid,
timestamp=selfcert.timestamp, timestamp=revoked.timestamp,
signature=selfcert.signatures[0]) signature=revoked.signatures[0])
def sign(self, selfcert, keys): def sign_for_revoked(self, revoked: Identity, keys: list) -> None:
""" """
Sign the current document. Sign the current document.
Warning : current signatures will be replaced with the new ones. Warning : current signatures will be replaced with the new ones.
:param revoked: Identity instance
:param keys: List of libnacl key instances
:return:
""" """
self.signatures = [] self.signatures = []
for key in keys: for key in keys:
signing = base64.b64encode(key.signature(bytes(self.raw(selfcert), 'ascii'))) signing = base64.b64encode(key.signature(bytes(self.raw_for_revoked(revoked), 'ascii')))
self.signatures.append(signing.decode("ascii")) self.signatures.append(signing.decode("ascii"))
def signed_raw(self, selfcert): def signed_raw_for_revoked(self, revoked: Identity) -> str:
raw = self.raw(selfcert) """
Return Revocation signed raw document string for revoked Identity instance
:param revoked: Identity instance
:return:
"""
raw = self.raw_for_revoked(revoked)
signed = "\n".join(self.signatures) signed = "\n".join(self.signatures)
signed_raw = raw + signed + "\n" signed_raw = raw + signed + "\n"
return signed_raw return signed_raw
...@@ -2,6 +2,7 @@ import base64 ...@@ -2,6 +2,7 @@ import base64
import hashlib import hashlib
import logging import logging
import re import re
from typing import TypeVar, Type, Any, List
from ..constants import SIGNATURE_REGEX from ..constants import SIGNATURE_REGEX
...@@ -11,10 +12,19 @@ class MalformedDocumentError(Exception): ...@@ -11,10 +12,19 @@ class MalformedDocumentError(Exception):
Malformed document exception Malformed document exception
""" """
def __init__(self, field_name): def __init__(self, field_name: str) -> None:
"""
Init exception instance
:param field_name: Name of the wrong field
"""
super().__init__("Could not parse field {0}".format(field_name)) super().__init__("Could not parse field {0}".format(field_name))
# required to type hint cls in classmethod
DocumentType = TypeVar('DocumentType', bound='Document')
class Document: class Document:
re_version = re.compile("Version: ([0-9]+)\n") re_version = re.compile("Version: ([0-9]+)\n")
re_currency = re.compile("Currency: ([^\n]+)\n") re_currency = re.compile("Currency: ([^\n]+)\n")
...@@ -26,21 +36,14 @@ class Document: ...@@ -26,21 +36,14 @@ class Document:
"Signature": re_signature "Signature": re_signature
} }
@classmethod def __init__(self, version: int, currency: str, signatures: List[str]) -> None:
def parse_field(cls, field_name, line):
""" """
Init Document instance
:param field_name: :param version: Version of the Document
:param line: :param currency: Name of the currency
:return: :param signatures: List of signatures
""" """
try:
value = cls.fields_parsers[field_name].match(line).group(1)
except AttributeError:
raise MalformedDocumentError(field_name)
return value
def __init__(self, version, currency, signatures):
if version < 2: if version < 2:
raise MalformedDocumentError("Version 1 documents are not handled by duniterpy>0.2") raise MalformedDocumentError("Version 1 documents are not handled by duniterpy>0.2")
self.version = version self.version = version
...@@ -50,10 +53,31 @@ class Document: ...@@ -50,10 +53,31 @@ class Document:
else: else:
self.signatures = [] self.signatures = []
def sign(self, keys): @classmethod
def parse_field(cls: Type[DocumentType], field_name: str, line: str) -> Any:
"""
Parse a document field with regular expression and return the value
:param field_name: Name of the field
:param line: Line string to parse
:return:
"""
try:
match = cls.fields_parsers[field_name].match(line)
if match is None:
raise AttributeError
value = match.group(1)
except AttributeError:
raise MalformedDocumentError(field_name)
return value
def sign(self, keys: list) -> None:
""" """
Sign the current document. Sign the current document.
Warning : current signatures will be replaced with the new ones. Warning : current signatures will be replaced with the new ones.
:param keys: List of libnacl keys instance
""" """
self.signatures = [] self.signatures = []
for key in keys: for key in keys:
...@@ -61,16 +85,17 @@ class Document: ...@@ -61,16 +85,17 @@ class Document:
logging.debug("Signature : \n{0}".format(signing.decode("ascii"))) logging.debug("Signature : \n{0}".format(signing.decode("ascii")))
self.signatures.append(signing.decode("ascii")) self.signatures.append(signing.decode("ascii"))
def raw(self, **kwargs): def raw(self) -> str:
""" """
Returns the raw document in string format Returns the raw document in string format
""" """
raise NotImplementedError() raise NotImplementedError()
def signed_raw(self, *args): def signed_raw(self) -> str:
""" """
If keys are None, returns the raw + current signatures If keys are None, returns the raw + current signatures
If keys are present, returns the raw signed by these keys If keys are present, returns the raw signed by these keys
:return:
""" """
raw = self.raw() raw = self.raw()
signed = "\n".join(self.signatures) signed = "\n".join(self.signatures)
...@@ -78,5 +103,10 @@ class Document: ...@@ -78,5 +103,10 @@ class Document:
return signed_raw return signed_raw
@property @property
def sha_hash(self): def sha_hash(self) -> str:
"""
Return uppercase hex sha256 hash from signed raw document
:return:
"""
return hashlib.sha256(self.signed_raw().encode("ascii")).hexdigest().upper() return hashlib.sha256(self.signed_raw().encode("ascii")).hexdigest().upper()
...@@ -17,6 +17,7 @@ class VerifyingKey(libnacl.sign.Verifier): ...@@ -17,6 +17,7 @@ class VerifyingKey(libnacl.sign.Verifier):
""" """
Class to verify documents Class to verify documents
""" """
def __init__(self, pubkey: str) -> None: def __init__(self, pubkey: str) -> None:
""" """
Creates a Verify class from base58 pubkey Creates a Verify class from base58 pubkey
...@@ -25,14 +26,14 @@ class VerifyingKey(libnacl.sign.Verifier): ...@@ -25,14 +26,14 @@ class VerifyingKey(libnacl.sign.Verifier):
key = libnacl.encode.hex_encode(Base58Encoder.decode(pubkey)) key = libnacl.encode.hex_encode(Base58Encoder.decode(pubkey))
super().__init__(key) super().__init__(key)
def verify_document(self, document: Document, **kwargs) -> bool: def verify_document(self, document: Document) -> bool:
""" """
Check specified document Check specified document
:param duniterpy.documents.Document document: :param duniterpy.documents.Document document:
:return: :return:
""" """
signature = base64.b64decode(document.signatures[0]) signature = base64.b64decode(document.signatures[0])
prepended = signature + bytes(document.raw(**kwargs), 'ascii') prepended = signature + bytes(document.raw(), 'ascii')
try: try:
self.verify(prepended) self.verify(prepended)
...@@ -66,4 +67,3 @@ class VerifyingKey(libnacl.sign.Verifier): ...@@ -66,4 +67,3 @@ class VerifyingKey(libnacl.sign.Verifier):
:return str: :return str:
""" """
return self.verify(message).decode('utf-8') return self.verify(message).decode('utf-8')
...@@ -87,8 +87,8 @@ def get_signed_raw_revocation_document(identity: Identity, salt: str, password: ...@@ -87,8 +87,8 @@ def get_signed_raw_revocation_document(identity: Identity, salt: str, password:
revocation = Revocation(PROTOCOL_VERSION, identity.currency, identity.pubkey, "") revocation = Revocation(PROTOCOL_VERSION, identity.currency, identity.pubkey, "")
key = SigningKey(salt, password) key = SigningKey(salt, password)
revocation.sign(identity, [key]) revocation.sign_for_revoked(identity, [key])
return revocation.signed_raw(identity) return revocation.signed_raw_for_revoked(identity)
async def main(): async def main():
......
...@@ -80,7 +80,7 @@ def get_certification_document(current_block: dict, self_cert_document: Identity ...@@ -80,7 +80,7 @@ def get_certification_document(current_block: dict, self_cert_document: Identity
) )
# sign document # sign document
key = SigningKey(salt, password) key = SigningKey(salt, password)
certification.sign(self_cert_document, [key]) certification.sign_for_certified(self_cert_document, [key])
return certification return certification
...@@ -118,7 +118,7 @@ async def main(): ...@@ -118,7 +118,7 @@ async def main():
certification = get_certification_document(current_block, identity, pubkey_from, salt, password) certification = get_certification_document(current_block, identity, pubkey_from, salt, password)
# Here we request for the path wot/certify # Here we request for the path wot/certify
response = await client(bma.wot.certify, certification.signed_raw(identity)) response = await client(bma.wot.certify, certification.signed_raw_for_certified(identity))
if response.status == 200: if response.status == 200:
print(await response.text()) print(await response.text())
......
...@@ -107,10 +107,10 @@ IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6B ...@@ -107,10 +107,10 @@ IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6B
CertTimestamp: 36-1076F10A7397715D2BEE82579861999EA1F274AC CertTimestamp: 36-1076F10A7397715D2BEE82579861999EA1F274AC
SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk
""" """
self.assertEqual(certification.signed_raw(selfcert), result) self.assertEqual(certification.signed_raw_for_certified(selfcert), result)
from_raw = Certification.from_signed_raw(certification.signed_raw(selfcert)) from_raw = Certification.from_signed_raw(certification.signed_raw_for_certified(selfcert))
self.assertEqual(from_raw.signed_raw(selfcert), result) self.assertEqual(from_raw.signed_raw_for_certified(selfcert), result)
def test_revokation_from_inline(self): def test_revokation_from_inline(self):
version = 2 version = 2
...@@ -139,7 +139,7 @@ IdtyTimestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD ...@@ -139,7 +139,7 @@ IdtyTimestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk
""" """
self.assertEqual(revokation.signed_raw(selfcert), result) self.assertEqual(revokation.signed_raw_for_revoked(selfcert), result)
def test_revokation_from_signed_raw(self): def test_revokation_from_signed_raw(self):
signed_raw = """Version: 2 signed_raw = """Version: 2
...@@ -153,4 +153,4 @@ SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneN ...@@ -153,4 +153,4 @@ SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneN
""" """
revocation = Revocation.from_signed_raw(signed_raw) revocation = Revocation.from_signed_raw(signed_raw)
selfcert = Revocation.extract_self_cert(signed_raw) selfcert = Revocation.extract_self_cert(signed_raw)
self.assertEqual(revocation.signed_raw(selfcert), signed_raw) self.assertEqual(revocation.signed_raw_for_revoked(selfcert), signed_raw)
...@@ -47,7 +47,7 @@ class Test_Membership(unittest.TestCase): ...@@ -47,7 +47,7 @@ class Test_Membership(unittest.TestCase):
def test_fromraw_toraw(self): def test_fromraw_toraw(self):
membership = Membership.from_signed_raw(membership_raw) membership = Membership.from_signed_raw(membership_raw)
rendered_membership = membership.signed_raw() rendered_membership = membership.signed_raw_for_certified()
from_rendered_membership = Membership.from_signed_raw(rendered_membership) from_rendered_membership = Membership.from_signed_raw(rendered_membership)
self.assertEqual(from_rendered_membership.issuer, "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk") self.assertEqual(from_rendered_membership.issuer, "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk")
self.assertEqual(from_rendered_membership.membership_ts.number, 0) self.assertEqual(from_rendered_membership.membership_ts.number, 0)
......
...@@ -59,7 +59,7 @@ class TestPeer(unittest.TestCase): ...@@ -59,7 +59,7 @@ class TestPeer(unittest.TestCase):
def test_fromraw_toraw(self): def test_fromraw_toraw(self):
peer = Peer.from_signed_raw(rawpeer) peer = Peer.from_signed_raw(rawpeer)
rendered_peer = peer.signed_raw() rendered_peer = peer.signed_raw_for_certified()
from_rendered_peer = Peer.from_signed_raw(rendered_peer) from_rendered_peer = Peer.from_signed_raw(rendered_peer)
self.assertEqual(from_rendered_peer.currency, "beta_brousouf") self.assertEqual(from_rendered_peer.currency, "beta_brousouf")
...@@ -87,9 +87,9 @@ class TestPeer(unittest.TestCase): ...@@ -87,9 +87,9 @@ class TestPeer(unittest.TestCase):
self.assertEqual(from_rendered_peer.signatures[0], self.assertEqual(from_rendered_peer.signatures[0],
"dkaXIiCYUJtCg8Feh/BKvPYf4uFH9CJ/zY6J4MlA9BsjmcMe4YAblvNt/gJy31b1aGq3ue3h14mLMCu84rraDg==") "dkaXIiCYUJtCg8Feh/BKvPYf4uFH9CJ/zY6J4MlA9BsjmcMe4YAblvNt/gJy31b1aGq3ue3h14mLMCu84rraDg==")
self.assertEqual(rawpeer, from_rendered_peer.signed_raw()) self.assertEqual(rawpeer, from_rendered_peer.signed_raw_for_certified())
def test_incorrect(self): def test_incorrect(self):
peer = Peer.from_signed_raw(test_weird_ipv6_peer) peer = Peer.from_signed_raw(test_weird_ipv6_peer)
rendered_peer = peer.signed_raw() rendered_peer = peer.signed_raw_for_certified()
Peer.from_signed_raw(rendered_peer) Peer.from_signed_raw(rendered_peer)
...@@ -299,7 +299,7 @@ class Test_Transaction(unittest.TestCase): ...@@ -299,7 +299,7 @@ class Test_Transaction(unittest.TestCase):
def test_fromraw_toraw(self): def test_fromraw_toraw(self):
tx = Transaction.from_signed_raw(tx_raw) tx = Transaction.from_signed_raw(tx_raw)
rendered_tx = tx.signed_raw() rendered_tx = tx.signed_raw_for_certified()
from_rendered_tx = Transaction.from_signed_raw(rendered_tx) from_rendered_tx = Transaction.from_signed_raw(rendered_tx)
self.assertEqual(tx.version, 2) self.assertEqual(tx.version, 2)
...@@ -366,7 +366,7 @@ class Test_Transaction(unittest.TestCase): ...@@ -366,7 +366,7 @@ class Test_Transaction(unittest.TestCase):
def test_fromraw_toraw_v3(self): def test_fromraw_toraw_v3(self):
tx = Transaction.from_signed_raw(tx_raw_v3) tx = Transaction.from_signed_raw(tx_raw_v3)
rendered_tx = tx.signed_raw() rendered_tx = tx.signed_raw_for_certified()
from_rendered_tx = Transaction.from_signed_raw(rendered_tx) from_rendered_tx = Transaction.from_signed_raw(rendered_tx)
self.assertEqual(tx.version, 3) self.assertEqual(tx.version, 3)
...@@ -435,7 +435,7 @@ class Test_Transaction(unittest.TestCase): ...@@ -435,7 +435,7 @@ class Test_Transaction(unittest.TestCase):
def test_compact_change(self): def test_compact_change(self):
tx = Transaction.from_compact("gtest", compact_change) tx = Transaction.from_compact("gtest", compact_change)
rendered_tx = tx.signed_raw() rendered_tx = tx.signed_raw_for_certified()
from_rendered_tx = Transaction.from_signed_raw(rendered_tx) from_rendered_tx = Transaction.from_signed_raw(rendered_tx)
def test_reduce_base(self): def test_reduce_base(self):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment