A Python port of the Invisible Internet Project (I2P)
at main 84 lines 2.7 kB view raw
1"""EdDSA blinding primitives for I2P LS2 blinded destinations. 2 3Uses libsodium (via pynacl) for Ed25519 scalar and point operations. 4Required for EncryptedLeaseSet and blinded destination support. 5""" 6 7from __future__ import annotations 8 9import hashlib 10 11try: 12 from nacl.bindings import ( 13 crypto_core_ed25519_scalar_reduce, 14 crypto_core_ed25519_scalar_add, 15 crypto_core_ed25519_add, 16 crypto_scalarmult_ed25519_base_noclamp, 17 ) 18 HAS_NACL = True 19except ImportError: 20 HAS_NACL = False 21 22 23class EdDSABlinding: 24 """Static methods for Ed25519 blinding operations. 25 26 Blinding allows a destination to publish encrypted LeaseSets 27 where the published key is unrelated to the real signing key 28 without knowledge of the blinding factor (alpha). 29 """ 30 31 @staticmethod 32 def _check_nacl() -> None: 33 if not HAS_NACL: 34 raise RuntimeError( 35 "pynacl with libsodium is required for EdDSA blinding" 36 ) 37 38 @staticmethod 39 def seed_to_scalar(seed: bytes) -> bytes: 40 """Expand a 32-byte Ed25519 seed to a clamped scalar. 41 42 Ed25519 private keys in I2P are 32-byte seeds. To use them 43 with libsodium's scalar functions, we must expand via SHA-512 44 and clamp per RFC 8032. 45 """ 46 expanded = hashlib.sha512(seed).digest()[:32] 47 scalar = bytearray(expanded) 48 scalar[0] &= 0xF8 # clear bottom 3 bits 49 scalar[31] &= 0x7F # clear top bit 50 scalar[31] |= 0x40 # set second-to-top bit 51 return bytes(scalar) 52 53 @staticmethod 54 def reduce(data_64: bytes) -> bytes: 55 """Reduce 64 bytes to a 32-byte Ed25519 scalar mod L.""" 56 EdDSABlinding._check_nacl() 57 return crypto_core_ed25519_scalar_reduce(data_64) 58 59 @staticmethod 60 def blind_public(pub: bytes, alpha_pub: bytes) -> bytes: 61 """Add two Ed25519 points (public keys). 62 63 blinded_pub = pub + alpha_pub (point addition) 64 """ 65 EdDSABlinding._check_nacl() 66 return crypto_core_ed25519_add(pub, alpha_pub) 67 68 @staticmethod 69 def blind_private(priv_scalar: bytes, alpha_scalar: bytes) -> bytes: 70 """Add two Ed25519 scalars. 71 72 blinded_priv = priv_scalar + alpha_scalar (mod L) 73 """ 74 EdDSABlinding._check_nacl() 75 return crypto_core_ed25519_scalar_add(priv_scalar, alpha_scalar) 76 77 @staticmethod 78 def scalar_mult_base(scalar: bytes) -> bytes: 79 """Compute scalar * basepoint (no clamping). 80 81 Returns the 32-byte public key corresponding to the scalar. 82 """ 83 EdDSABlinding._check_nacl() 84 return crypto_scalarmult_ed25519_base_noclamp(scalar)