"""EdDSA blinding primitives for I2P LS2 blinded destinations. Uses libsodium (via pynacl) for Ed25519 scalar and point operations. Required for EncryptedLeaseSet and blinded destination support. """ from __future__ import annotations import hashlib try: from nacl.bindings import ( crypto_core_ed25519_scalar_reduce, crypto_core_ed25519_scalar_add, crypto_core_ed25519_add, crypto_scalarmult_ed25519_base_noclamp, ) HAS_NACL = True except ImportError: HAS_NACL = False class EdDSABlinding: """Static methods for Ed25519 blinding operations. Blinding allows a destination to publish encrypted LeaseSets where the published key is unrelated to the real signing key without knowledge of the blinding factor (alpha). """ @staticmethod def _check_nacl() -> None: if not HAS_NACL: raise RuntimeError( "pynacl with libsodium is required for EdDSA blinding" ) @staticmethod def seed_to_scalar(seed: bytes) -> bytes: """Expand a 32-byte Ed25519 seed to a clamped scalar. Ed25519 private keys in I2P are 32-byte seeds. To use them with libsodium's scalar functions, we must expand via SHA-512 and clamp per RFC 8032. """ expanded = hashlib.sha512(seed).digest()[:32] scalar = bytearray(expanded) scalar[0] &= 0xF8 # clear bottom 3 bits scalar[31] &= 0x7F # clear top bit scalar[31] |= 0x40 # set second-to-top bit return bytes(scalar) @staticmethod def reduce(data_64: bytes) -> bytes: """Reduce 64 bytes to a 32-byte Ed25519 scalar mod L.""" EdDSABlinding._check_nacl() return crypto_core_ed25519_scalar_reduce(data_64) @staticmethod def blind_public(pub: bytes, alpha_pub: bytes) -> bytes: """Add two Ed25519 points (public keys). blinded_pub = pub + alpha_pub (point addition) """ EdDSABlinding._check_nacl() return crypto_core_ed25519_add(pub, alpha_pub) @staticmethod def blind_private(priv_scalar: bytes, alpha_scalar: bytes) -> bytes: """Add two Ed25519 scalars. blinded_priv = priv_scalar + alpha_scalar (mod L) """ EdDSABlinding._check_nacl() return crypto_core_ed25519_scalar_add(priv_scalar, alpha_scalar) @staticmethod def scalar_mult_base(scalar: bytes) -> bytes: """Compute scalar * basepoint (no clamping). Returns the 32-byte public key corresponding to the scalar. """ EdDSABlinding._check_nacl() return crypto_scalarmult_ed25519_base_noclamp(scalar)