A Python port of the Invisible Internet Project (I2P)
at main 121 lines 3.9 kB view raw
1"""EdDSA — Ed25519 signing and verification. 2 3Ported from net.i2p.crypto.eddsa.EdDSAEngine. 4Wraps Python's cryptography library for Ed25519 operations. 5""" 6 7from cryptography.hazmat.primitives.asymmetric.ed25519 import ( 8 Ed25519PrivateKey, 9 Ed25519PublicKey, 10) 11from cryptography.exceptions import InvalidSignature 12 13 14class EdDSAEngine: 15 """Ed25519 signature engine. 16 17 Provides sign/verify operations using Ed25519-SHA-512 (RFC 8032). 18 Uses one-shot mode internally (avoids the double-hashing issue 19 of the Java standard Signature API). 20 """ 21 22 SIGNATURE_LENGTH = 64 23 PUBLIC_KEY_LENGTH = 32 24 PRIVATE_KEY_LENGTH = 32 25 26 @staticmethod 27 def sign(data: bytes, private_key: bytes, 28 offset: int = 0, length: int = -1) -> bytes: 29 """Sign data with Ed25519 private key. 30 31 Args: 32 data: data to sign 33 private_key: 32-byte Ed25519 private key (seed) 34 offset: start offset in data 35 length: number of bytes to sign (-1 for all from offset) 36 37 Returns: 38 64-byte signature 39 """ 40 if length < 0: 41 length = len(data) - offset 42 key = Ed25519PrivateKey.from_private_bytes(private_key[:32]) 43 return key.sign(data[offset : offset + length]) 44 45 @staticmethod 46 def verify(data: bytes, signature: bytes, public_key: bytes, 47 offset: int = 0, length: int = -1) -> bool: 48 """Verify an Ed25519 signature. 49 50 Args: 51 data: data that was signed 52 signature: 64-byte signature 53 public_key: 32-byte Ed25519 public key 54 offset: start offset in data 55 length: number of bytes that were signed 56 57 Returns: 58 True if signature is valid. 59 """ 60 if length < 0: 61 length = len(data) - offset 62 try: 63 key = Ed25519PublicKey.from_public_bytes(public_key[:32]) 64 key.verify(signature, data[offset : offset + length]) 65 return True 66 except (InvalidSignature, ValueError): 67 return False 68 69 # Aliases matching Java API 70 sign_one_shot = sign 71 verify_one_shot = verify 72 73 74class EdDSAKeyPair: 75 """Ed25519 key pair generation and management.""" 76 77 __slots__ = ("_private_key", "_public_key", "_private_bytes", "_public_bytes") 78 79 def __init__(self, private_key: Ed25519PrivateKey) -> None: 80 self._private_key = private_key 81 self._public_key = private_key.public_key() 82 self._private_bytes = private_key.private_bytes_raw() 83 self._public_bytes = self._public_key.public_bytes_raw() 84 85 @classmethod 86 def generate(cls) -> "EdDSAKeyPair": 87 """Generate a new Ed25519 key pair.""" 88 return cls(Ed25519PrivateKey.generate()) 89 90 @classmethod 91 def from_private_bytes(cls, seed: bytes) -> "EdDSAKeyPair": 92 """Create key pair from 32-byte private key seed.""" 93 return cls(Ed25519PrivateKey.from_private_bytes(seed[:32])) 94 95 @property 96 def private_key(self) -> bytes: 97 """32-byte private key seed.""" 98 return self._private_bytes 99 100 @property 101 def public_key(self) -> bytes: 102 """32-byte public key.""" 103 return self._public_bytes 104 105 def sign(self, data: bytes) -> bytes: 106 """Sign data, returns 64-byte signature.""" 107 return self._private_key.sign(data) 108 109 def verify(self, data: bytes, signature: bytes) -> bool: 110 """Verify signature against public key.""" 111 try: 112 self._public_key.verify(signature, data) 113 return True 114 except (InvalidSignature, ValueError): 115 return False 116 117 118def public_key_from_private(private_key: bytes) -> bytes: 119 """Derive 32-byte public key from 32-byte private key seed.""" 120 key = Ed25519PrivateKey.from_private_bytes(private_key[:32]) 121 return key.public_key().public_bytes_raw()