121 lines
3.6 KiB
Python
121 lines
3.6 KiB
Python
# built-in dependencies
|
|
import random
|
|
from typing import Optional, Tuple
|
|
|
|
# 3rd party dependencies
|
|
from lightecc import LightECC
|
|
from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
|
|
|
|
# project dependencies
|
|
from lightdsa.commons import transformation
|
|
from lightdsa.interfaces.signatures import Signature
|
|
from lightdsa.commons.logger import Logger
|
|
|
|
logger = Logger(module="lightdsa/algorithms/ecdsa.py")
|
|
|
|
|
|
class ECDSA(Signature):
|
|
def __init__(
|
|
self,
|
|
keys: Optional[dict] = None,
|
|
key_size: Optional[int] = None,
|
|
hash_algorithm: Optional[str] = None,
|
|
form_name: Optional[str] = "weierstrass",
|
|
curve_name: Optional[str] = "secp256k1",
|
|
):
|
|
"""
|
|
Elliptic Curve Digital Signature Algorithm (ECDSA)
|
|
https://sefiks.com/2018/02/16/elegant-signatures-with-elliptic-curve-cryptography/
|
|
"""
|
|
self.key_size = key_size
|
|
self.form_name = form_name or "weierstrass"
|
|
self.curve_name = curve_name or "secp256k1"
|
|
self.curve = LightECC(self.form_name, self.curve_name)
|
|
self.keys = keys or self.generate_keys(key_size or self.curve.n.bit_length())
|
|
self.hash_algorithm = hash_algorithm
|
|
|
|
self.hash_algorithm = transformation.get_hash_algorithm(self.curve.n)
|
|
|
|
def generate_keys(self, key_size: int) -> dict:
|
|
"""
|
|
Generate ECDSA keys
|
|
Args:
|
|
key_size (int): size of the key
|
|
Returns:
|
|
dict: private and public
|
|
e.g. keys = {
|
|
"private_key": {
|
|
"ka": int
|
|
},
|
|
"public_key": {
|
|
"Qa": Tuple[int, int]
|
|
}
|
|
}
|
|
"""
|
|
keys = {}
|
|
keys["private_key"] = {}
|
|
keys["public_key"] = {}
|
|
|
|
# private key
|
|
ka = random.getrandbits(key_size)
|
|
keys["private_key"]["ka"] = ka
|
|
|
|
# public key
|
|
Qa = ka * self.curve.G
|
|
keys["public_key"]["Qa"] = Qa.get_point()
|
|
|
|
logger.debug("ECDSA keys generated")
|
|
|
|
return keys
|
|
|
|
def sign(self, message: int) -> Tuple[int, int]:
|
|
"""
|
|
Sign a message with ECDSA
|
|
Args:
|
|
message (int): message to sign
|
|
Returns:
|
|
signature (Tuple[int, int]): signature
|
|
"""
|
|
# this must be a random, otherwise private can be extracted from multiple signatures
|
|
random_key = random.getrandbits(self.curve.n.bit_length())
|
|
R = random_key * self.curve.G
|
|
|
|
hashed_message = transformation.hashify(message, algorithm=self.hash_algorithm)
|
|
private_key = self.keys["private_key"]["ka"]
|
|
|
|
r = R.x
|
|
s = (
|
|
(hashed_message + r * private_key)
|
|
* pow(random_key, -1, self.curve.n)
|
|
% self.curve.n
|
|
)
|
|
|
|
return (r, s)
|
|
|
|
def verify(self, message: int, signature: Tuple[int, int]) -> bool:
|
|
"""
|
|
Verify a message with ECDSA
|
|
Args:
|
|
message (int): message to verify
|
|
signature (Tuple[int, int]): signature
|
|
Returns:
|
|
bool: True if signature is valid, False otherwise
|
|
"""
|
|
hashed_message = transformation.hashify(message, algorithm=self.hash_algorithm)
|
|
|
|
Qa = self.keys["public_key"]["Qa"]
|
|
public_key = EllipticCurvePoint(Qa[0], Qa[1], self.curve.curve)
|
|
|
|
r, s = signature
|
|
w = pow(s, -1, self.curve.n)
|
|
|
|
u1 = ((hashed_message * w) % self.curve.n) * self.curve.G
|
|
u2 = ((r * w) % self.curve.n) * public_key
|
|
|
|
checkpoint = u1 + u2
|
|
|
|
if checkpoint.x != r:
|
|
raise ValueError("Signature is invalid")
|
|
|
|
return True
|