"""DSA — Signature type registry, key generator, and sign/verify dispatcher. Ported from net.i2p.crypto.DSAEngine, net.i2p.crypto.SigType, and net.i2p.crypto.KeyGenerator. Dispatches to the correct algorithm based on SigType: - DSA_SHA1 (code 0): Legacy DSA with SHA-1 [keygen only, sign/verify not implemented] - ECDSA_SHA256_P256 (code 1): ECDSA with NIST P-256 - ECDSA_SHA384_P384 (code 2): ECDSA with NIST P-384 - ECDSA_SHA512_P521 (code 3): ECDSA with NIST P-521 - EdDSA_SHA512_Ed25519 (code 7): Ed25519 - RedDSA_SHA512_Ed25519 (code 11): RedDSA (same key format as Ed25519) """ from __future__ import annotations import enum from cryptography.hazmat.primitives.asymmetric.ec import ( ECDSA, SECP256R1, SECP384R1, SECP521R1, EllipticCurvePrivateKey, EllipticCurvePublicKey, generate_private_key, ) from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey, Ed25519PublicKey, ) from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, encode_dss_signature, ) from cryptography.hazmat.primitives.hashes import SHA256, SHA384, SHA512 from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, PrivateFormat, NoEncryption, ) from cryptography.exceptions import InvalidSignature class SigType(enum.Enum): """I2P signature type registry. Each member defines the algorithm, key sizes, signature size, and hash algorithm for one signature type code. """ # code pubkey_len privkey_len sig_len hash_algo DSA_SHA1 = (0, 128, 20, 40, "sha1") ECDSA_SHA256_P256 = (1, 64, 32, 64, "sha256") ECDSA_SHA384_P384 = (2, 96, 48, 96, "sha384") ECDSA_SHA512_P521 = (3, 132, 66, 132, "sha512") EdDSA_SHA512_Ed25519 = (7, 32, 32, 64, "sha512") RedDSA_SHA512_Ed25519 = (11, 32, 32, 64, "sha512") def __init__(self, code: int, pubkey_len: int, privkey_len: int, sig_len: int, hash_algo: str) -> None: self._code = code self._pubkey_len = pubkey_len self._privkey_len = privkey_len self._sig_len = sig_len self._hash_algo = hash_algo @property def code(self) -> int: return self._code @property def pubkey_len(self) -> int: return self._pubkey_len @property def privkey_len(self) -> int: return self._privkey_len @property def sig_len(self) -> int: return self._sig_len @property def hash_algo(self) -> str: return self._hash_algo @classmethod def by_code(cls, code: int) -> SigType | None: """Look up a SigType by its integer code, or None.""" for st in cls: if st.code == code: return st return None _ECDSA_CURVES = { SigType.ECDSA_SHA256_P256: (SECP256R1, SHA256, 32), SigType.ECDSA_SHA384_P384: (SECP384R1, SHA384, 48), SigType.ECDSA_SHA512_P521: (SECP521R1, SHA512, 66), } def _ecdsa_sig_to_fixed(der_sig: bytes, component_len: int) -> bytes: """Convert DER-encoded ECDSA signature to fixed-length (r||s).""" r, s = decode_dss_signature(der_sig) return r.to_bytes(component_len, "big") + s.to_bytes(component_len, "big") def _fixed_to_ecdsa_sig(fixed: bytes, component_len: int) -> bytes: """Convert fixed-length (r||s) to DER-encoded ECDSA signature.""" r = int.from_bytes(fixed[:component_len], "big") s = int.from_bytes(fixed[component_len:2 * component_len], "big") return encode_dss_signature(r, s) class KeyGenerator: """Generate key pairs for any SigType.""" @staticmethod def generate(sig_type: SigType) -> tuple[bytes, bytes]: """Generate a key pair. Args: sig_type: the signature type Returns: (public_key_bytes, private_key_bytes) """ if sig_type in (SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519): ed_key = Ed25519PrivateKey.generate() pub = ed_key.public_key().public_bytes_raw() priv = ed_key.private_bytes_raw() return pub, priv if sig_type in _ECDSA_CURVES: curve_cls, _, _ = _ECDSA_CURVES[sig_type] ec_key = generate_private_key(curve_cls()) # Raw public key: uncompressed point without 0x04 prefix pub_uncompressed = ec_key.public_key().public_bytes( Encoding.X962, PublicFormat.UncompressedPoint ) pub = pub_uncompressed[1:] # strip 0x04 prefix # Extract raw private scalar priv_num = ec_key.private_numbers().private_value priv_bytes = priv_num.to_bytes(sig_type.privkey_len, "big") return pub, priv_bytes if sig_type == SigType.DSA_SHA1: from cryptography.hazmat.primitives.asymmetric.dsa import ( generate_parameters, generate_private_key as dsa_gen_key, ) params = generate_parameters(key_size=1024) dsa_key = params.generate_private_key() pub_num = dsa_key.public_key().public_numbers() dsa_priv_num = dsa_key.private_numbers() pub = pub_num.y.to_bytes(128, "big") priv = dsa_priv_num.x.to_bytes(20, "big") return pub, priv raise ValueError(f"Unsupported SigType: {sig_type}") class DSAEngine: """Signature dispatcher — routes sign/verify to the correct algorithm.""" @staticmethod def sign(data: bytes, private_key: bytes, sig_type: SigType) -> bytes: """Sign data using the specified signature type. Args: data: message to sign private_key: raw private key bytes sig_type: signature algorithm Returns: Fixed-length signature (sig_type.sig_len bytes) """ if sig_type in (SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519): ed_key = Ed25519PrivateKey.from_private_bytes(private_key[:32]) return ed_key.sign(data) if sig_type in _ECDSA_CURVES: curve_cls, hash_cls, component_len = _ECDSA_CURVES[sig_type] # Reconstruct private key from raw scalar from cryptography.hazmat.primitives.asymmetric.ec import ( derive_private_key, ) d = int.from_bytes(private_key[:sig_type.privkey_len], "big") ec_key = derive_private_key(d, curve_cls()) der_sig = ec_key.sign(data, ECDSA(hash_cls())) return _ecdsa_sig_to_fixed(der_sig, component_len) raise ValueError(f"sign not implemented for {sig_type}") @staticmethod def verify(data: bytes, signature: bytes, public_key: bytes, sig_type: SigType) -> bool: """Verify a signature. Args: data: message that was signed signature: fixed-length signature bytes public_key: raw public key bytes sig_type: signature algorithm Returns: True if signature is valid """ try: if sig_type in (SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519): ed_pub = Ed25519PublicKey.from_public_bytes(public_key[:32]) ed_pub.verify(signature, data) return True if sig_type in _ECDSA_CURVES: curve_cls, hash_cls, component_len = _ECDSA_CURVES[sig_type] # Reconstruct public key from raw x||y point = b"\x04" + public_key[:sig_type.pubkey_len] ec_pub = EllipticCurvePublicKey.from_encoded_point( curve_cls(), point ) der_sig = _fixed_to_ecdsa_sig(signature, component_len) ec_pub.verify(der_sig, data, ECDSA(hash_cls())) return True except (InvalidSignature, ValueError, OverflowError): return False raise ValueError(f"verify not implemented for {sig_type}")