dcrypt_sign/eddsa/ed25519/
mod.rs

1//! Ed25519 signature scheme implementation
2//!
3//! This is a complete implementation of Ed25519 as specified in RFC 8032,
4//! with full Curve25519 arithmetic operations included.
5
6use super::constants::{ED25519_PUBLIC_KEY_SIZE, ED25519_SECRET_KEY_SIZE, ED25519_SIGNATURE_SIZE};
7use dcrypt_algorithms::hash::sha2::Sha512;
8use dcrypt_algorithms::hash::HashFunction;
9use dcrypt_api::{error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait};
10use dcrypt_internal::constant_time::ct_eq;
11use rand::{CryptoRng, RngCore};
12use zeroize::{Zeroize, Zeroizing};
13
14// Import curve operations from the refactored modules
15use super::operations;
16
17/// Ed25519 signature scheme
18///
19/// # Security Considerations
20///
21/// - Always use a cryptographically secure RNG for key generation
22/// - Protect secret keys using platform security features when available
23/// - Verify public key authenticity through secure channels
24/// - Never reuse seeds across different applications or purposes
25/// - Clear sensitive data from memory after use (automatic for secret keys)
26pub struct Ed25519;
27
28/// Ed25519 public key (32 bytes)
29///
30/// # Security
31///
32/// This type contains public key material that can be shared freely.
33/// However, you must ensure the key's authenticity through secure channels
34/// to prevent man-in-the-middle attacks.
35#[derive(Clone, Zeroize)]
36pub struct Ed25519PublicKey(pub [u8; ED25519_PUBLIC_KEY_SIZE]);
37
38// Implement Debug for Ed25519PublicKey without exposing key material
39impl core::fmt::Debug for Ed25519PublicKey {
40    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        f.debug_struct("Ed25519PublicKey")
42            .field("algorithm", &"Ed25519")
43            .finish()
44    }
45}
46
47/// Ed25519 secret key
48///
49/// # Security
50///
51/// This type contains secret key material that must be kept confidential:
52/// - Store securely (encrypted at rest)
53/// - Transmit securely (TLS/encrypted channels)  
54/// - Clear from memory after use (automatic via Drop)
55/// - Never log or display the key material
56///
57/// The internal representation includes both the seed and expanded key material.
58/// Only the seed needs to be stored for persistence.
59#[derive(Clone)]
60pub struct Ed25519SecretKey {
61    /// The original 32-byte seed
62    seed: [u8; ED25519_SECRET_KEY_SIZE],
63    /// The expanded key material (64 bytes from SHA-512)
64    expanded: [u8; 64],
65}
66
67impl Zeroize for Ed25519SecretKey {
68    fn zeroize(&mut self) {
69        self.seed.zeroize();
70        self.expanded.zeroize();
71    }
72}
73
74impl Drop for Ed25519SecretKey {
75    fn drop(&mut self) {
76        self.zeroize();
77    }
78}
79
80// Implement Debug without exposing key material
81impl core::fmt::Debug for Ed25519SecretKey {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        f.debug_struct("Ed25519SecretKey")
84            .field("algorithm", &"Ed25519")
85            .finish()
86    }
87}
88
89/// Ed25519 signature (64 bytes: R || s)
90#[derive(Clone, Zeroize)]
91pub struct Ed25519Signature(pub [u8; ED25519_SIGNATURE_SIZE]);
92
93impl core::fmt::Debug for Ed25519Signature {
94    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95        f.debug_struct("Ed25519Signature")
96            .field("length", &self.0.len())
97            .finish()
98    }
99}
100
101// Public key methods
102impl Ed25519PublicKey {
103    /// Create a public key from bytes
104    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
105        if bytes.len() != ED25519_PUBLIC_KEY_SIZE {
106            return Err(ApiError::InvalidKey {
107                context: "Ed25519PublicKey::from_bytes",
108                #[cfg(feature = "std")]
109                message: format!("Invalid key size: expected {}, got {}", ED25519_PUBLIC_KEY_SIZE, bytes.len()),
110            });
111        }
112        let mut key = [0u8; ED25519_PUBLIC_KEY_SIZE];
113        key.copy_from_slice(bytes);
114        Ok(Ed25519PublicKey(key))
115    }
116    
117    /// Convert public key to bytes
118    pub fn to_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] {
119        self.0
120    }
121}
122
123// Secret key methods
124impl Ed25519SecretKey {
125    /// Create a secret key from a 32-byte seed
126    ///
127    /// This is useful when loading keys from storage. The seed is expanded
128    /// using SHA-512 and the appropriate bits are clamped as per RFC 8032.
129    ///
130    /// # Security
131    ///
132    /// - Only use seeds from trusted sources
133    /// - Ensure seeds were generated with a cryptographic RNG
134    /// - Never use predictable or low-entropy seeds
135    /// - Validate seed integrity if loading from storage
136    ///
137    /// # Example
138    ///
139    /// ```
140    /// use dcrypt_sign::eddsa::{Ed25519, Ed25519SecretKey};
141    /// use dcrypt_api::Signature;
142    ///
143    /// # fn main() -> dcrypt_api::Result<()> {
144    /// // Load seed from storage (example uses fixed bytes)
145    /// let seed = [42u8; 32];
146    /// 
147    /// // Reconstruct secret key
148    /// let secret = Ed25519SecretKey::from_seed(&seed)?;
149    /// 
150    /// // Can now derive public key
151    /// let public = secret.public_key()?;
152    /// 
153    /// // Or use for signing
154    /// let message = b"test";
155    /// let signature = Ed25519::sign(message, &secret)?;
156    /// # Ok(())
157    /// # }
158    /// ```
159    pub fn from_seed(seed: &[u8; ED25519_SECRET_KEY_SIZE]) -> ApiResult<Self> {
160        // Expand seed using SHA-512
161        let mut hasher = Sha512::new();
162        hasher.update(seed).map_err(ApiError::from)?;
163        let hash = hasher.finalize().map_err(ApiError::from)?;
164        
165        let mut expanded = [0u8; 64];
166        expanded.copy_from_slice(hash.as_ref());
167        
168        // Apply Ed25519 clamping to scalar (first 32 bytes)
169        expanded[0] &= 248;  // Clear bits 0, 1, 2
170        expanded[31] &= 127; // Clear bit 255
171        expanded[31] |= 64;  // Set bit 254
172        
173        Ok(Ed25519SecretKey {
174            seed: *seed,
175            expanded,
176        })
177    }
178    
179    /// Get the 32-byte seed value
180    ///
181    /// This is the original random seed before expansion. This is what
182    /// should be stored when saving keys to persistent storage.
183    ///
184    /// # Security  
185    ///
186    /// - Encrypt seeds before storing to disk
187    /// - Use secure key derivation if password-protecting
188    /// - Clear seed arrays from memory after use
189    /// - Never log or transmit seeds over insecure channels
190    pub fn seed(&self) -> &[u8; ED25519_SECRET_KEY_SIZE] {
191        &self.seed
192    }
193    
194    /// Export the seed as a Zeroizing vector for secure handling
195    pub fn export_seed(&self) -> Zeroizing<Vec<u8>> {
196        Zeroizing::new(self.seed.to_vec())
197    }
198    
199    /// Get the public key corresponding to this secret key
200    ///
201    /// This derives the public key on-demand from the secret key material.
202    /// The derivation is deterministic, so calling this multiple times
203    /// will always return the same public key.
204    ///
205    /// # Example
206    ///
207    /// ```
208    /// use dcrypt_sign::eddsa::Ed25519;
209    /// use dcrypt_api::Signature;
210    /// use rand::rngs::OsRng;
211    ///
212    /// # fn main() -> dcrypt_api::Result<()> {
213    /// let mut rng = OsRng;
214    /// let (_, secret) = Ed25519::keypair(&mut rng)?;
215    /// 
216    /// // Get public key from secret key
217    /// let public = secret.public_key()?;
218    /// 
219    /// // Can use it for verification
220    /// let message = b"test message";
221    /// let signature = Ed25519::sign(message, &secret)?;
222    /// assert!(Ed25519::verify(message, &signature, &public).is_ok());
223    /// # Ok(())
224    /// # }
225    /// ```
226    pub fn public_key(&self) -> ApiResult<Ed25519PublicKey> {
227        Ed25519::derive_public_from_secret(self)
228    }
229}
230
231// Signature methods
232impl Ed25519Signature {
233    /// Create a signature from bytes
234    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
235        if bytes.len() != ED25519_SIGNATURE_SIZE {
236            return Err(ApiError::InvalidSignature {
237                context: "Ed25519Signature::from_bytes",
238                #[cfg(feature = "std")]
239                message: format!("Invalid signature size: expected {}, got {}", ED25519_SIGNATURE_SIZE, bytes.len()),
240            });
241        }
242        let mut sig = [0u8; ED25519_SIGNATURE_SIZE];
243        sig.copy_from_slice(bytes);
244        Ok(Ed25519Signature(sig))
245    }
246    
247    /// Convert signature to bytes
248    pub fn to_bytes(&self) -> [u8; ED25519_SIGNATURE_SIZE] {
249        self.0
250    }
251}
252
253impl SignatureTrait for Ed25519 {
254    type PublicKey = Ed25519PublicKey;
255    type SecretKey = Ed25519SecretKey;
256    type SignatureData = Ed25519Signature;
257    type KeyPair = (Self::PublicKey, Self::SecretKey);
258
259    fn name() -> &'static str {
260        "Ed25519"
261    }
262
263    /// Generate an Ed25519 key pair
264    ///
265    /// This follows the key generation process from RFC 8032:
266    /// 1. Generate a 32-byte random seed
267    /// 2. Hash the seed with SHA-512 to get 64 bytes
268    /// 3. Clear/set specific bits in the first 32 bytes (scalar clamping)
269    /// 4. Use the clamped scalar to derive the public key
270    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
271        // Step 1: Generate random 32-byte seed
272        let mut seed = [0u8; ED25519_SECRET_KEY_SIZE];
273        rng.fill_bytes(&mut seed);
274
275        // Step 2: Expand seed using SHA-512
276        let mut hasher = Sha512::new();
277        hasher.update(&seed).map_err(ApiError::from)?;
278        let hash = hasher.finalize().map_err(ApiError::from)?;
279
280        let mut expanded = [0u8; 64];
281        expanded.copy_from_slice(hash.as_ref());
282
283        // Step 3: Apply Ed25519 clamping to scalar (first 32 bytes)
284        expanded[0] &= 248; // Clear bits 0, 1, 2
285        expanded[31] &= 127; // Clear bit 255
286        expanded[31] |= 64; // Set bit 254
287
288        // Step 4: Derive public key A = \[scalar\]B
289        let mut public_key = [0u8; ED25519_PUBLIC_KEY_SIZE];
290        operations::derive_public_key(&expanded[0..32], &mut public_key).map_err(|e| {
291            ApiError::InvalidParameter {
292                context: "Ed25519 keypair generation",
293                #[cfg(feature = "std")]
294                message: format!("Failed to derive public key: {}", e),
295            }
296        })?;
297
298        Ok((
299            Ed25519PublicKey(public_key),
300            Ed25519SecretKey { seed, expanded },
301        ))
302    }
303
304    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
305        keypair.0.clone()
306    }
307
308    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
309        keypair.1.clone()
310    }
311
312    /// Sign a message using Ed25519
313    ///
314    /// The signing process follows RFC 8032:
315    /// 1. r = SHA-512(prefix || message) mod L
316    /// 2. R = \[r\]B
317    /// 3. k = SHA-512(R || A || message) mod L
318    /// 4. s = (r + k*a) mod L
319    /// 5. Return (R, s)
320    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
321        // Extract scalar and prefix from expanded secret key
322        let scalar = &secret_key.expanded[0..32];
323        let prefix = &secret_key.expanded[32..64];
324
325        // Step 1: Compute r = SHA-512(prefix || message) mod L
326        let mut hasher = Sha512::new();
327        hasher.update(prefix).map_err(ApiError::from)?;
328        hasher.update(message).map_err(ApiError::from)?;
329        let r_hash = hasher.finalize().map_err(ApiError::from)?;
330
331        let mut r = [0u8; 32];
332        operations::reduce_512_to_scalar(r_hash.as_ref(), &mut r);
333
334        // Step 2: Compute R = \[r\]B
335        let r_point = operations::scalar_mult_base(&r);
336
337        // Step 3: Get public key A (we recompute it, but could cache)
338        let mut public_key = [0u8; 32];
339        operations::derive_public_key(scalar, &mut public_key).map_err(|e| {
340            ApiError::InvalidParameter {
341                context: "Ed25519 signing",
342                #[cfg(feature = "std")]
343                message: format!("Failed to derive public key: {}", e),
344            }
345        })?;
346
347        // Step 4: Compute k = SHA-512(R || A || message) mod L
348        let mut hasher = Sha512::new();
349        hasher.update(&r_point).map_err(ApiError::from)?;
350        hasher.update(&public_key).map_err(ApiError::from)?;
351        hasher.update(message).map_err(ApiError::from)?;
352        let k_hash = hasher.finalize().map_err(ApiError::from)?;
353
354        let mut k = [0u8; 32];
355        operations::reduce_512_to_scalar(k_hash.as_ref(), &mut k);
356
357        // Step 5: Compute s = (r + k*a) mod L
358        let mut s = [0u8; 32];
359        operations::compute_s(&r, &k, scalar, &mut s);
360
361        // Step 6: Construct signature (R || s)
362        let mut signature = [0u8; ED25519_SIGNATURE_SIZE];
363        signature[0..32].copy_from_slice(&r_point);
364        signature[32..64].copy_from_slice(&s);
365
366        Ok(Ed25519Signature(signature))
367    }
368
369    /// Verify an Ed25519 signature
370    ///
371    /// The verification process checks that:
372    /// \[s\]B = R + \[k\]A
373    /// where k = SHA-512(R || A || message) mod L
374    fn verify(
375        message: &[u8],
376        signature: &Self::SignatureData,
377        public_key: &Self::PublicKey,
378    ) -> ApiResult<()> {
379        // Input validation
380        if public_key.0.len() != ED25519_PUBLIC_KEY_SIZE {
381            return Err(ApiError::InvalidKey {
382                context: "Ed25519 verify",
383                #[cfg(feature = "std")]
384                message: "Invalid public key size".to_string(),
385            });
386        }
387
388        if signature.0.len() != ED25519_SIGNATURE_SIZE {
389            return Err(ApiError::InvalidSignature {
390                context: "Ed25519 verify",
391                #[cfg(feature = "std")]
392                message: "Invalid signature size".to_string(),
393            });
394        }
395
396        // Parse signature as (R, s)
397        let r_bytes = &signature.0[0..32];
398        let s_bytes = &signature.0[32..64];
399
400        // Basic validation: s should not be all zeros
401        if s_bytes.iter().all(|&b| b == 0) {
402            return Err(ApiError::InvalidSignature {
403                context: "Ed25519 verify",
404                #[cfg(feature = "std")]
405                message: "Invalid s value in signature (all zeros)".to_string(),
406            });
407        }
408
409        // Compute k = SHA-512(R || A || message) mod L
410        let mut hasher = Sha512::new();
411        hasher.update(r_bytes).map_err(ApiError::from)?;
412        hasher.update(&public_key.0).map_err(ApiError::from)?;
413        hasher.update(message).map_err(ApiError::from)?;
414        let k_hash = hasher.finalize().map_err(ApiError::from)?;
415
416        let mut k = [0u8; 32];
417        operations::reduce_512_to_scalar(k_hash.as_ref(), &mut k);
418
419        // Verify the signature equation: \[s\]B = R + \[k\]A
420        let mut check = [0u8; 32];
421        operations::verify_equation(s_bytes, r_bytes, &k, &public_key.0, &mut check).map_err(
422            |e| ApiError::InvalidSignature {
423                context: "Ed25519 verify",
424                #[cfg(feature = "std")]
425                message: format!("Signature verification failed: {}", e),
426            },
427        )?;
428
429        // Check result using constant-time comparison
430        if !ct_eq(check, [1u8; 32]) {
431            return Err(ApiError::InvalidSignature {
432                context: "Ed25519 verify",
433                #[cfg(feature = "std")]
434                message: "Signature verification equation failed".to_string(),
435            });
436        }
437
438        Ok(())
439    }
440}
441
442impl Ed25519 {
443    /// Derive the public key from an existing secret key
444    ///
445    /// This is useful when you have a secret key loaded from storage
446    /// and need to reconstruct the corresponding public key.
447    ///
448    /// # Example
449    ///
450    /// ```
451    /// use dcrypt_sign::eddsa::Ed25519;
452    /// use dcrypt_api::Signature;
453    /// use rand::rngs::OsRng;
454    ///
455    /// # fn main() -> dcrypt_api::Result<()> {
456    /// let mut rng = OsRng;
457    /// let (original_public, secret) = Ed25519::keypair(&mut rng)?;
458    /// 
459    /// // Later, derive public key from secret
460    /// let derived_public = Ed25519::derive_public_from_secret(&secret)?;
461    /// 
462    /// assert_eq!(original_public.0, derived_public.0);
463    /// # Ok(())
464    /// # }
465    /// ```
466    pub fn derive_public_from_secret(
467        secret_key: &Ed25519SecretKey
468    ) -> ApiResult<Ed25519PublicKey> {
469        // Extract the clamped scalar from the expanded key material
470        let scalar = &secret_key.expanded[0..32];
471        
472        // Derive the public key A = [scalar]B
473        let mut public_key_bytes = [0u8; ED25519_PUBLIC_KEY_SIZE];
474        operations::derive_public_key(scalar, &mut public_key_bytes)
475            .map_err(|e| ApiError::InvalidParameter {
476                context: "Ed25519::derive_public_from_secret",
477                #[cfg(feature = "std")]
478                message: format!("Failed to derive public key: {}", e),
479            })?;
480        
481        Ok(Ed25519PublicKey(public_key_bytes))
482    }
483}
484
485// Implement the optional serialization trait
486#[cfg(feature = "serialization")]
487use dcrypt_api::SignatureSerialize;
488
489#[cfg(feature = "serialization")]
490impl SignatureSerialize for Ed25519 {
491    const PUBLIC_KEY_SIZE: usize = ED25519_PUBLIC_KEY_SIZE;
492    const SECRET_KEY_SIZE: usize = ED25519_SECRET_KEY_SIZE;
493    const SIGNATURE_SIZE: usize = ED25519_SIGNATURE_SIZE;
494
495    fn serialize_public_key(key: &Self::PublicKey) -> Vec<u8> {
496        key.0.to_vec()
497    }
498    
499    fn deserialize_public_key(bytes: &[u8]) -> ApiResult<Self::PublicKey> {
500        Ed25519PublicKey::from_bytes(bytes)
501    }
502    
503    fn serialize_secret_key(key: &Self::SecretKey) -> Zeroizing<Vec<u8>> {
504        key.export_seed()
505    }
506    
507    fn deserialize_secret_key(bytes: &[u8]) -> ApiResult<Self::SecretKey> {
508        if bytes.len() != ED25519_SECRET_KEY_SIZE {
509            return Err(ApiError::InvalidKey {
510                context: "Ed25519::deserialize_secret_key",
511                #[cfg(feature = "std")]
512                message: format!("Invalid seed size: expected {}, got {}", ED25519_SECRET_KEY_SIZE, bytes.len()),
513            });
514        }
515        let mut seed = [0u8; ED25519_SECRET_KEY_SIZE];
516        seed.copy_from_slice(bytes);
517        Ed25519SecretKey::from_seed(&seed)
518    }
519    
520    fn serialize_signature(sig: &Self::SignatureData) -> Vec<u8> {
521        sig.0.to_vec()
522    }
523    
524    fn deserialize_signature(bytes: &[u8]) -> ApiResult<Self::SignatureData> {
525        Ed25519Signature::from_bytes(bytes)
526    }
527}
528
529// Implement the optional key derivation trait
530#[cfg(feature = "key_derivation")]
531use dcrypt_api::SignatureDerive;
532
533#[cfg(feature = "key_derivation")]
534impl SignatureDerive for Ed25519 {
535    const MIN_SEED_SIZE: usize = ED25519_SECRET_KEY_SIZE;
536
537    fn derive_keypair(seed: &[u8]) -> ApiResult<Self::KeyPair> {
538        if seed.len() < Self::MIN_SEED_SIZE {
539            return Err(ApiError::InvalidParameter {
540                context: "Ed25519::derive_keypair",
541                #[cfg(feature = "std")]
542                message: format!("Seed too short: minimum {} bytes required", Self::MIN_SEED_SIZE),
543            });
544        }
545        
546        let mut seed_array = [0u8; ED25519_SECRET_KEY_SIZE];
547        seed_array.copy_from_slice(&seed[..ED25519_SECRET_KEY_SIZE]);
548        
549        let secret = Ed25519SecretKey::from_seed(&seed_array)?;
550        let public = secret.public_key()?;
551        Ok((public, secret))
552    }
553    
554    fn derive_public_key(secret_key: &Self::SecretKey) -> ApiResult<Self::PublicKey> {
555        secret_key.public_key()
556    }
557}
558
559#[cfg(test)]
560mod tests;