claim169_core/crypto/software/
ed25519.rs

1#[cfg(feature = "software-crypto")]
2use coset::iana;
3#[cfg(feature = "software-crypto")]
4use ed25519_dalek::{Signature, Signer as DalekSigner, SigningKey, Verifier, VerifyingKey};
5
6#[cfg(feature = "software-crypto")]
7use crate::crypto::traits::{SignatureVerifier, Signer};
8#[cfg(feature = "software-crypto")]
9use crate::error::{CryptoError, CryptoResult};
10
11/// Ed25519 signature verifier using ed25519-dalek
12#[cfg(feature = "software-crypto")]
13pub struct Ed25519Verifier {
14    public_key: VerifyingKey,
15}
16
17/// All-zeros key - identity point, provides no security
18const ED25519_WEAK_KEY_ZEROS: [u8; 32] = [0u8; 32];
19
20/// Small order points that should be rejected
21/// These are the 8 points of small order on Curve25519
22const ED25519_SMALL_ORDER_POINTS: [[u8; 32]; 8] = [
23    // Identity point (neutral element)
24    [
25        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
26        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27        0x00, 0x00,
28    ],
29    // Point of order 2
30    [
31        0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
32        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
33        0xff, 0x7f,
34    ],
35    // Points of order 4
36    [
37        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
38        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
39        0x00, 0x80,
40    ],
41    [
42        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44        0x00, 0x00,
45    ],
46    // Points of order 8
47    [
48        0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67,
49        0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac,
50        0x03, 0x7a,
51    ],
52    [
53        0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67,
54        0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac,
55        0x03, 0xfa,
56    ],
57    [
58        0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98,
59        0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53,
60        0xfc, 0x05,
61    ],
62    [
63        0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98,
64        0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53,
65        0xfc, 0x85,
66    ],
67];
68
69#[cfg(feature = "software-crypto")]
70impl Ed25519Verifier {
71    /// Create a new verifier from raw public key bytes (32 bytes)
72    ///
73    /// Rejects weak keys including all-zeros and small-order points.
74    pub fn from_bytes(bytes: &[u8]) -> CryptoResult<Self> {
75        let bytes: [u8; 32] = bytes.try_into().map_err(|_| {
76            CryptoError::InvalidKeyFormat("Ed25519 public key must be 32 bytes".to_string())
77        })?;
78
79        // Reject all-zeros key
80        if bytes == ED25519_WEAK_KEY_ZEROS {
81            return Err(CryptoError::InvalidKeyFormat(
82                "weak key rejected: all-zeros public key".to_string(),
83            ));
84        }
85
86        // Reject small-order points (potential small subgroup attack)
87        for weak_point in &ED25519_SMALL_ORDER_POINTS {
88            if bytes == *weak_point {
89                return Err(CryptoError::InvalidKeyFormat(
90                    "weak key rejected: small-order point".to_string(),
91                ));
92            }
93        }
94
95        let public_key = VerifyingKey::from_bytes(&bytes)
96            .map_err(|e| CryptoError::InvalidKeyFormat(e.to_string()))?;
97
98        Ok(Self { public_key })
99    }
100
101    /// Create a new verifier from a PEM-encoded public key
102    ///
103    /// Supports both SPKI (SubjectPublicKeyInfo) format with "BEGIN PUBLIC KEY" headers
104    /// and raw base64-encoded 32-byte keys.
105    ///
106    /// Rejects weak keys including all-zeros and small-order points.
107    pub fn from_pem(pem: &str) -> CryptoResult<Self> {
108        use ed25519_dalek::pkcs8::DecodePublicKey;
109
110        let pem = pem.trim();
111
112        // Try to parse as SPKI PEM using proper ASN.1 parsing
113        if pem.contains("BEGIN PUBLIC KEY") {
114            let public_key = VerifyingKey::from_public_key_pem(pem)
115                .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid SPKI PEM: {}", e)))?;
116
117            // Validate against weak keys (same checks as from_bytes)
118            let bytes = public_key.to_bytes();
119
120            if bytes == ED25519_WEAK_KEY_ZEROS {
121                return Err(CryptoError::InvalidKeyFormat(
122                    "weak key rejected: all-zeros public key".to_string(),
123                ));
124            }
125
126            for weak_point in &ED25519_SMALL_ORDER_POINTS {
127                if bytes == *weak_point {
128                    return Err(CryptoError::InvalidKeyFormat(
129                        "weak key rejected: small-order point".to_string(),
130                    ));
131                }
132            }
133
134            return Ok(Self { public_key });
135        }
136
137        // Fallback: try to decode as raw base64 32-byte key
138        use base64::Engine;
139        let bytes = base64::engine::general_purpose::STANDARD
140            .decode(pem)
141            .map_err(|e| CryptoError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
142
143        Self::from_bytes(&bytes)
144    }
145}
146
147#[cfg(feature = "software-crypto")]
148impl SignatureVerifier for Ed25519Verifier {
149    fn verify(
150        &self,
151        algorithm: iana::Algorithm,
152        _key_id: Option<&[u8]>,
153        data: &[u8],
154        signature: &[u8],
155    ) -> CryptoResult<()> {
156        // Verify algorithm is EdDSA
157        if algorithm != iana::Algorithm::EdDSA {
158            return Err(CryptoError::UnsupportedAlgorithm(format!(
159                "{:?}",
160                algorithm
161            )));
162        }
163
164        let signature: [u8; 64] = signature
165            .try_into()
166            .map_err(|_| CryptoError::VerificationFailed)?;
167
168        let signature = Signature::from_bytes(&signature);
169
170        self.public_key
171            .verify(data, &signature)
172            .map_err(|_| CryptoError::VerificationFailed)
173    }
174}
175
176/// Ed25519 signer using ed25519-dalek (for test vector generation)
177///
178/// The underlying `SigningKey` implements `ZeroizeOnDrop` (when the zeroize feature
179/// is enabled on ed25519-dalek), which securely clears the private key from memory
180/// when the signer is dropped.
181#[cfg(feature = "software-crypto")]
182pub struct Ed25519Signer {
183    signing_key: SigningKey,
184}
185
186#[cfg(feature = "software-crypto")]
187impl Ed25519Signer {
188    /// Create a new signer from raw private key bytes (32 bytes)
189    pub fn from_bytes(bytes: &[u8]) -> CryptoResult<Self> {
190        let bytes: [u8; 32] = bytes.try_into().map_err(|_| {
191            CryptoError::InvalidKeyFormat("Ed25519 private key must be 32 bytes".to_string())
192        })?;
193
194        let signing_key = SigningKey::from_bytes(&bytes);
195        Ok(Self { signing_key })
196    }
197
198    /// Generate a new random signing key
199    pub fn generate() -> Self {
200        use rand::rngs::OsRng;
201        let signing_key = SigningKey::generate(&mut OsRng);
202        Self { signing_key }
203    }
204
205    /// Get the verifying (public) key
206    pub fn verifying_key(&self) -> Ed25519Verifier {
207        Ed25519Verifier {
208            public_key: self.signing_key.verifying_key(),
209        }
210    }
211
212    /// Get the public key bytes
213    pub fn public_key_bytes(&self) -> [u8; 32] {
214        self.signing_key.verifying_key().to_bytes()
215    }
216}
217
218#[cfg(feature = "software-crypto")]
219impl Signer for Ed25519Signer {
220    fn sign(
221        &self,
222        algorithm: iana::Algorithm,
223        _key_id: Option<&[u8]>,
224        data: &[u8],
225    ) -> CryptoResult<Vec<u8>> {
226        if algorithm != iana::Algorithm::EdDSA {
227            return Err(CryptoError::UnsupportedAlgorithm(format!(
228                "{:?}",
229                algorithm
230            )));
231        }
232
233        let signature = self.signing_key.sign(data);
234        Ok(signature.to_bytes().to_vec())
235    }
236}
237
238#[cfg(all(test, feature = "software-crypto"))]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_ed25519_sign_verify() {
244        let signer = Ed25519Signer::generate();
245        let verifier = signer.verifying_key();
246
247        let data = b"test message to sign";
248        let signature = signer.sign(iana::Algorithm::EdDSA, None, data).unwrap();
249
250        assert!(verifier
251            .verify(iana::Algorithm::EdDSA, None, data, &signature)
252            .is_ok());
253    }
254
255    #[test]
256    fn test_ed25519_verify_wrong_data() {
257        let signer = Ed25519Signer::generate();
258        let verifier = signer.verifying_key();
259
260        let data = b"original message";
261        let wrong_data = b"tampered message";
262        let signature = signer.sign(iana::Algorithm::EdDSA, None, data).unwrap();
263
264        assert!(verifier
265            .verify(iana::Algorithm::EdDSA, None, wrong_data, &signature)
266            .is_err());
267    }
268
269    #[test]
270    fn test_ed25519_wrong_algorithm() {
271        let signer = Ed25519Signer::generate();
272        let verifier = signer.verifying_key();
273
274        let data = b"test message";
275        let signature = signer.sign(iana::Algorithm::EdDSA, None, data).unwrap();
276
277        // Try to verify with wrong algorithm
278        let result = verifier.verify(iana::Algorithm::ES256, None, data, &signature);
279        assert!(matches!(result, Err(CryptoError::UnsupportedAlgorithm(_))));
280    }
281
282    #[test]
283    fn test_ed25519_from_bytes() {
284        let signer = Ed25519Signer::generate();
285        let public_bytes = signer.public_key_bytes();
286
287        let verifier = Ed25519Verifier::from_bytes(&public_bytes).unwrap();
288
289        let data = b"test data";
290        let signature = signer.sign(iana::Algorithm::EdDSA, None, data).unwrap();
291
292        assert!(verifier
293            .verify(iana::Algorithm::EdDSA, None, data, &signature)
294            .is_ok());
295    }
296
297    #[test]
298    fn test_ed25519_invalid_key_length() {
299        let result = Ed25519Verifier::from_bytes(&[0u8; 16]);
300        assert!(matches!(result, Err(CryptoError::InvalidKeyFormat(_))));
301    }
302
303    #[test]
304    fn test_ed25519_from_pem_spki() {
305        use ed25519_dalek::pkcs8::{spki::der::pem::LineEnding, EncodePublicKey};
306
307        let signer = Ed25519Signer::generate();
308        let pem = signer
309            .signing_key
310            .verifying_key()
311            .to_public_key_pem(LineEnding::LF)
312            .expect("PEM encoding should not fail");
313
314        // Verify PEM has correct SPKI format
315        assert!(pem.contains("-----BEGIN PUBLIC KEY-----"));
316        assert!(pem.contains("-----END PUBLIC KEY-----"));
317
318        // Parse from PEM and verify signature
319        let verifier = Ed25519Verifier::from_pem(&pem).unwrap();
320
321        let data = b"test data for PEM verification";
322        let signature = signer.sign(iana::Algorithm::EdDSA, None, data).unwrap();
323
324        assert!(verifier
325            .verify(iana::Algorithm::EdDSA, None, data, &signature)
326            .is_ok());
327    }
328
329    #[test]
330    fn test_ed25519_from_pem_invalid() {
331        // Invalid PEM should fail
332        let result = Ed25519Verifier::from_pem("not a valid PEM");
333        assert!(result.is_err());
334
335        // Invalid SPKI PEM should fail with clear error
336        let invalid_spki =
337            "-----BEGIN PUBLIC KEY-----\nnotvalidbase64!!!\n-----END PUBLIC KEY-----";
338        let result = Ed25519Verifier::from_pem(invalid_spki);
339        assert!(result.is_err());
340    }
341
342    #[test]
343    fn test_ed25519_from_pem_raw_base64() {
344        // Test fallback to raw base64-encoded 32-byte key
345        let signer = Ed25519Signer::generate();
346        let public_bytes = signer.public_key_bytes();
347
348        use base64::Engine;
349        let raw_base64 = base64::engine::general_purpose::STANDARD.encode(public_bytes);
350
351        let verifier = Ed25519Verifier::from_pem(&raw_base64).unwrap();
352
353        let data = b"test data";
354        let signature = signer.sign(iana::Algorithm::EdDSA, None, data).unwrap();
355
356        assert!(verifier
357            .verify(iana::Algorithm::EdDSA, None, data, &signature)
358            .is_ok());
359    }
360
361    #[test]
362    fn test_ed25519_rejects_all_zeros_key() {
363        let zero_key = [0u8; 32];
364        let result = Ed25519Verifier::from_bytes(&zero_key);
365
366        assert!(result.is_err());
367        match result {
368            Err(CryptoError::InvalidKeyFormat(msg)) => {
369                assert!(
370                    msg.contains("weak key"),
371                    "Error should mention weak key: {}",
372                    msg
373                );
374            }
375            _ => panic!("Expected InvalidKeyFormat error"),
376        }
377    }
378
379    #[test]
380    fn test_ed25519_rejects_small_order_points() {
381        // Test identity point (order 1)
382        let identity = [
383            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
384            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
385            0x00, 0x00, 0x00, 0x00,
386        ];
387
388        let result = Ed25519Verifier::from_bytes(&identity);
389        assert!(result.is_err());
390        match result {
391            Err(CryptoError::InvalidKeyFormat(msg)) => {
392                assert!(
393                    msg.contains("small-order"),
394                    "Error should mention small-order: {}",
395                    msg
396                );
397            }
398            _ => panic!("Expected InvalidKeyFormat error for identity point"),
399        }
400
401        // Test point of order 2
402        let order_2 = [
403            0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
404            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
405            0xff, 0xff, 0xff, 0x7f,
406        ];
407
408        let result = Ed25519Verifier::from_bytes(&order_2);
409        assert!(result.is_err());
410    }
411}