Skip to main content

dcrypt_sign/eddsa/ed25519/
mod.rs

1//! Ed25519 signatures backed by `ed25519-dalek`.
2//!
3//! The previous in-tree Edwards arithmetic accepted weak public keys and used
4//! secret-dependent branches during scalar multiplication. Keeping dcrypt's
5//! byte-oriented API while delegating the primitive to dalek provides strict
6//! point/scalar decoding and a backend designed for constant-time secret
7//! arithmetic. Compiler-, target-, and operation-specific review is still
8//! required for a concrete side-channel claim.
9
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12
13use super::constants::{ED25519_PUBLIC_KEY_SIZE, ED25519_SECRET_KEY_SIZE, ED25519_SIGNATURE_SIZE};
14use curve25519_dalek::edwards::CompressedEdwardsY;
15use dcrypt_api::{error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait};
16use ed25519_dalek::{Signature as DalekSignature, Signer, SigningKey, VerifyingKey};
17use rand::{CryptoRng, RngCore};
18use zeroize::{Zeroize, Zeroizing};
19
20/// Ed25519 signature scheme.
21pub struct Ed25519;
22
23/// Canonically encoded, non-weak Ed25519 public key.
24#[derive(Clone, Zeroize)]
25pub struct Ed25519PublicKey(pub [u8; ED25519_PUBLIC_KEY_SIZE]);
26
27impl core::fmt::Debug for Ed25519PublicKey {
28    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29        f.debug_struct("Ed25519PublicKey")
30            .field("algorithm", &"Ed25519")
31            .finish()
32    }
33}
34
35/// Ed25519 secret seed, wiped on drop.
36#[derive(Clone)]
37pub struct Ed25519SecretKey {
38    seed: [u8; ED25519_SECRET_KEY_SIZE],
39}
40
41impl Zeroize for Ed25519SecretKey {
42    fn zeroize(&mut self) {
43        self.seed.zeroize();
44    }
45}
46
47impl Drop for Ed25519SecretKey {
48    fn drop(&mut self) {
49        self.zeroize();
50    }
51}
52
53impl core::fmt::Debug for Ed25519SecretKey {
54    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55        f.debug_struct("Ed25519SecretKey")
56            .field("algorithm", &"Ed25519")
57            .finish()
58    }
59}
60
61/// Ed25519 signature bytes (`R || S`).
62#[derive(Clone, Zeroize)]
63pub struct Ed25519Signature(pub [u8; ED25519_SIGNATURE_SIZE]);
64
65impl core::fmt::Debug for Ed25519Signature {
66    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67        f.debug_struct("Ed25519Signature")
68            .field("length", &self.0.len())
69            .finish()
70    }
71}
72
73fn invalid_key(context: &'static str, message: &'static str) -> ApiError {
74    ApiError::InvalidKey {
75        context,
76        #[cfg(feature = "std")]
77        message: message.to_string(),
78    }
79}
80
81fn invalid_signature(context: &'static str, message: &'static str) -> ApiError {
82    ApiError::InvalidSignature {
83        context,
84        #[cfg(feature = "std")]
85        message: message.to_string(),
86    }
87}
88
89fn validate_canonical_prime_order_point(bytes: &[u8; 32], context: &'static str) -> ApiResult<()> {
90    let point = CompressedEdwardsY(*bytes)
91        .decompress()
92        .ok_or_else(|| invalid_key(context, "point encoding does not decompress"))?;
93    if point.compress().to_bytes() != *bytes {
94        return Err(invalid_key(context, "point encoding is non-canonical"));
95    }
96    if point.is_small_order() || !point.is_torsion_free() {
97        return Err(invalid_key(
98            context,
99            "point is not in the prime-order subgroup",
100        ));
101    }
102    Ok(())
103}
104
105impl Ed25519PublicKey {
106    /// Parse a canonical public key and reject every small-order key.
107    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
108        let key_bytes: [u8; ED25519_PUBLIC_KEY_SIZE] =
109            bytes.try_into().map_err(|_| ApiError::InvalidLength {
110                context: "Ed25519PublicKey::from_bytes",
111                expected: ED25519_PUBLIC_KEY_SIZE,
112                actual: bytes.len(),
113            })?;
114        validate_canonical_prime_order_point(&key_bytes, "Ed25519PublicKey::from_bytes")?;
115        VerifyingKey::from_bytes(&key_bytes).map_err(|_| {
116            invalid_key(
117                "Ed25519PublicKey::from_bytes",
118                "public key is not a canonical Edwards point",
119            )
120        })?;
121        Ok(Self(key_bytes))
122    }
123
124    pub fn to_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] {
125        self.0
126    }
127
128    fn verifying_key(&self) -> ApiResult<VerifyingKey> {
129        validate_canonical_prime_order_point(&self.0, "Ed25519 verify")?;
130        let key = VerifyingKey::from_bytes(&self.0)
131            .map_err(|_| invalid_key("Ed25519 verify", "public key encoding is invalid"))?;
132        Ok(key)
133    }
134}
135
136impl Ed25519SecretKey {
137    pub fn from_seed(seed: &[u8; ED25519_SECRET_KEY_SIZE]) -> ApiResult<Self> {
138        Ok(Self { seed: *seed })
139    }
140
141    pub fn seed(&self) -> &[u8; ED25519_SECRET_KEY_SIZE] {
142        &self.seed
143    }
144
145    pub fn export_seed(&self) -> Zeroizing<Vec<u8>> {
146        Zeroizing::new(self.seed.to_vec())
147    }
148
149    pub fn public_key(&self) -> ApiResult<Ed25519PublicKey> {
150        Ed25519::derive_public_from_secret(self)
151    }
152
153    fn signing_key(&self) -> SigningKey {
154        SigningKey::from_bytes(&self.seed)
155    }
156}
157
158impl Ed25519Signature {
159    pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
160        let signature: [u8; ED25519_SIGNATURE_SIZE] =
161            bytes.try_into().map_err(|_| ApiError::InvalidLength {
162                context: "Ed25519Signature::from_bytes",
163                expected: ED25519_SIGNATURE_SIZE,
164                actual: bytes.len(),
165            })?;
166        Ok(Self(signature))
167    }
168
169    pub fn to_bytes(&self) -> [u8; ED25519_SIGNATURE_SIZE] {
170        self.0
171    }
172}
173
174impl SignatureTrait for Ed25519 {
175    type PublicKey = Ed25519PublicKey;
176    type SecretKey = Ed25519SecretKey;
177    type SignatureData = Ed25519Signature;
178    type KeyPair = (Self::PublicKey, Self::SecretKey);
179
180    fn name() -> &'static str {
181        "Ed25519"
182    }
183
184    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
185        let mut seed = [0u8; ED25519_SECRET_KEY_SIZE];
186        rng.fill_bytes(&mut seed);
187        let secret = Ed25519SecretKey::from_seed(&seed)?;
188        seed.zeroize();
189        let public = secret.public_key()?;
190        Ok((public, secret))
191    }
192
193    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
194        keypair.0.clone()
195    }
196
197    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
198        keypair.1.clone()
199    }
200
201    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
202        let signature: DalekSignature = secret_key.signing_key().sign(message);
203        Ok(Ed25519Signature(signature.to_bytes()))
204    }
205
206    fn verify(
207        message: &[u8],
208        signature: &Self::SignatureData,
209        public_key: &Self::PublicKey,
210    ) -> ApiResult<()> {
211        let verifying_key = public_key.verifying_key()?;
212        let r_bytes: [u8; 32] = signature.0[..32]
213            .try_into()
214            .map_err(|_| invalid_signature("Ed25519 verify", "invalid R encoding"))?;
215        validate_canonical_prime_order_point(&r_bytes, "Ed25519 signature R")
216            .map_err(|_| invalid_signature("Ed25519 verify", "R is non-canonical or torsion"))?;
217        let signature = DalekSignature::from_bytes(&signature.0);
218        verifying_key
219            .verify_strict(message, &signature)
220            .map_err(|_| invalid_signature("Ed25519 verify", "strict verification failed"))
221    }
222}
223
224impl Ed25519 {
225    pub fn derive_public_from_secret(secret_key: &Ed25519SecretKey) -> ApiResult<Ed25519PublicKey> {
226        let bytes = secret_key.signing_key().verifying_key().to_bytes();
227        Ed25519PublicKey::from_bytes(&bytes)
228    }
229}
230
231#[cfg(test)]
232mod tests;