from . import openssl
from .curve import CURVE
from .curve_scalar import CurveScalar
from .hashing import Hash
from .keys import SecretKey, PublicKey
from .serializable import Serializable, Deserializable
def digest_for_signing(message: bytes) -> Hash:
# Not using a DST here to make life easier for third-party verifiers
digest = Hash()
digest.update(message)
return digest
[docs]class Signer:
"""
An object possessing the capability to create signatures.
For safety reasons serialization is prohibited.
"""
def __init__(self, secret_key: SecretKey):
self.__secret_key = secret_key
def sign_digest(self, digest: Hash) -> 'Signature':
secret_bn = self.__secret_key.secret_scalar()._backend_bignum
r_int, s_int = openssl.ecdsa_sign(curve=CURVE,
secret_bn=secret_bn,
prehashed_message=digest.finalize(),
hash_algorithm=digest._backend_hash_algorithm)
# Normalize s. This is a non-malleability measure, which OpenSSL doesn't do.
# See Bitcoin's BIP-0062 for more details:
# https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures
# s is public, so no constant-timeness required here
if s_int > (CURVE.order >> 1):
s_int = CURVE.order - s_int
# Already normalized, don't waste time
r = CurveScalar.from_int(r_int, check_normalization=False)
s = CurveScalar.from_int(s_int, check_normalization=False)
return Signature(r, s)
[docs] def sign(self, message: bytes) -> 'Signature':
"""
Hashes and signs the message.
"""
return self.sign_digest(digest_for_signing(message))
[docs] def verifying_key(self) -> PublicKey:
"""
Returns the public verification key corresponding to the secret key used for signing.
"""
return self.__secret_key.public_key()
def __str__(self):
return f"{self.__class__.__name__}:..."
def __hash__(self):
raise RuntimeError(f"{self.__class__.__name__} objects do not support hashing")
def __bytes__(self):
raise RuntimeError(f"{self.__class__.__name__} objects do not support serialization")
[docs]class Signature(Serializable, Deserializable):
"""
Wrapper for ECDSA signatures.
"""
def __init__(self, r: CurveScalar, s: CurveScalar):
self.r = r
self.s = s
def verify_digest(self, verifying_pk: PublicKey, digest: Hash) -> bool:
return openssl.ecdsa_verify(curve=CURVE,
sig_r=int(self.r),
sig_s=int(self.s),
public_point=verifying_pk.point()._backend_point,
prehashed_message=digest.finalize(),
hash_algorithm=digest._backend_hash_algorithm)
[docs] def verify(self, verifying_pk: PublicKey, message: bytes) -> bool:
"""
Returns ``True`` if the ``message`` was signed by someone possessing the secret counterpart
to ``verifying_pk``.
"""
digest = digest_for_signing(message)
return self.verify_digest(verifying_pk, digest)
[docs] @classmethod
def serialized_size(cls):
return CurveScalar.serialized_size() * 2
@classmethod
def _from_exact_bytes(cls, data: bytes):
return cls(*cls._split(data, CurveScalar, CurveScalar))
def __bytes__(self):
return bytes(self.r) + bytes(self.s)
def __str__(self):
return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
[docs] def __eq__(self, other):
return self.r == other.r and self.s == other.s
[docs] def __hash__(self) -> int:
return hash((self.__class__, bytes(self)))