1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
# Copyright 2014-2022 Vincent Texier <vit@free.fr>
#
# DuniterPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DuniterPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import hashlib
import logging
import re
from typing import Any, Dict, Optional, Pattern, Type, TypeVar
from ..constants import SIGNATURE_REGEX
from ..key import SigningKey, VerifyingKey
class MalformedDocumentError(Exception):
"""
Malformed document exception
"""
def __init__(self, field_name: str) -> None:
"""
Init exception instance
:param field_name: Name of the wrong field
"""
super().__init__(f"Could not parse field {field_name}")
# required to type hint cls in classmethod
DocumentType = TypeVar("DocumentType", bound="Document")
class Document:
re_version = re.compile("Version: ([0-9]+)\n")
re_currency = re.compile("Currency: ([^\n]+)\n")
re_signature = re.compile(f"({SIGNATURE_REGEX})\n")
fields_parsers: Dict[str, Pattern] = {
"Version": re_version,
"Currency": re_currency,
"Signature": re_signature,
}
def __init__(self, version: int, currency: str) -> None:
"""
Init Document instance
:param version: Version of the Document
:param currency: Name of the currency
"""
self.version = version
self.currency = currency
self.signature: Optional[str] = None
def __eq__(self, other: Any) -> bool:
"""
Check Document instances equality
"""
if not isinstance(other, Document):
return NotImplemented
return (
self.version == other.version
and self.currency == other.currency
and self.signature == other.signature
)
def __hash__(self) -> int:
return hash(
(
self.version,
self.currency,
self.signature,
)
)
@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) from AttributeError
return value
def sign(self, key: SigningKey) -> None:
"""
Sign the current document with key
:param key: Libnacl key instance
"""
signature = base64.b64encode(key.signature(bytes(self.raw(), "ascii")))
logging.debug("Signature:\n%s", signature.decode("ascii"))
self.signature = signature.decode("ascii")
def raw(self) -> str:
"""
Returns the raw document in string format
"""
raise NotImplementedError("raw() is not implemented")
def signed_raw(self) -> str:
"""
:return:
"""
if self.signature is None:
raise MalformedDocumentError(
"Signature is not defined, can not create signed raw format"
)
return f"{self.raw()}{self.signature}\n"
@property
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()
def check_signature(self, pubkey: str) -> bool:
"""
Check if the signature is from pubkey
:param pubkey: Base58 public key
:return:
"""
if self.signature is None:
raise Exception("Signature is not defined, can not check signature")
verifying_key = VerifyingKey(pubkey)
return verifying_key.check_signature(self.raw(), self.signature)
|