A Python port of the Invisible Internet Project (I2P)
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()