miden_crypto/dsa/eddsa_25519/
mod.rs

1//! Ed25519 (EdDSA) signature implementation using Curve25519 and SHA-512 to hash
2//! the messages when signing.
3
4use alloc::{string::ToString, vec::Vec};
5
6use ed25519_dalek::{Signer, Verifier};
7use miden_crypto_derive::{SilentDebug, SilentDisplay};
8use rand::{CryptoRng, RngCore};
9use thiserror::Error;
10
11use crate::{
12    Felt, SequentialCommit, Word,
13    ecdh::x25519::{EphemeralPublicKey, SharedSecret},
14    utils::{
15        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
16        bytes_to_elements_with_padding,
17    },
18    zeroize::{Zeroize, ZeroizeOnDrop},
19};
20
21#[cfg(test)]
22mod tests;
23
24// CONSTANTS
25// ================================================================================================
26
27/// Length of secret key in bytes
28const SECRET_KEY_BYTES: usize = 32;
29/// Length of public key in bytes
30pub(crate) const PUBLIC_KEY_BYTES: usize = 32;
31/// Length of signature in bytes
32const SIGNATURE_BYTES: usize = 64;
33
34// SECRET KEY
35// ================================================================================================
36
37/// Secret key for EdDSA (Ed25519) signature verification over Curve25519.
38#[derive(Clone, SilentDebug, SilentDisplay)]
39pub struct SecretKey {
40    inner: ed25519_dalek::SigningKey,
41}
42
43impl SecretKey {
44    /// Generates a new random secret key using the OS random number generator.
45    #[cfg(feature = "std")]
46    #[allow(clippy::new_without_default)]
47    pub fn new() -> Self {
48        let mut rng = rand::rng();
49
50        Self::with_rng(&mut rng)
51    }
52
53    /// Generates a new secret key using RNG.
54    pub fn with_rng<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
55        let mut seed = [0u8; SECRET_KEY_BYTES];
56        rand::RngCore::fill_bytes(rng, &mut seed);
57
58        let inner = ed25519_dalek::SigningKey::from_bytes(&seed);
59
60        // Zeroize the seed to prevent leaking secret material
61        seed.zeroize();
62
63        Self { inner }
64    }
65
66    /// Gets the corresponding public key for this secret key.
67    pub fn public_key(&self) -> PublicKey {
68        PublicKey { inner: self.inner.verifying_key() }
69    }
70
71    /// Signs a message (Word) with this secret key.
72    pub fn sign(&self, message: Word) -> Signature {
73        let message_bytes: [u8; 32] = message.into();
74        let sig = self.inner.sign(&message_bytes);
75        Signature { inner: sig }
76    }
77
78    /// Computes a Diffie-Hellman shared secret from this secret key and the ephemeral public key
79    /// generated by the other party.
80    pub fn get_shared_secret(&self, pk_e: EphemeralPublicKey) -> SharedSecret {
81        let shared = self.to_x25519().diffie_hellman(&pk_e.inner);
82        SharedSecret::new(shared)
83    }
84
85    /// Converts this Ed25519 secret key into an [`x25519_dalek::StaticSecret`].
86    ///
87    /// This conversion allows using the same underlying scalar from the Ed25519 secret key
88    /// for X25519 Diffie-Hellman key exchange. The returned `StaticSecret` can then be used
89    /// in key agreement protocols to establish a shared secret with another party's
90    /// X25519 public key.
91    fn to_x25519(&self) -> x25519_dalek::StaticSecret {
92        let mut scalar_bytes = self.inner.to_scalar_bytes();
93        let static_secret = x25519_dalek::StaticSecret::from(scalar_bytes);
94
95        // Zeroize the temporary scalar bytes
96        scalar_bytes.zeroize();
97
98        static_secret
99    }
100}
101
102// SAFETY: The inner `ed25519_dalek::SigningKey` already implements `ZeroizeOnDrop`,
103// which ensures that the secret key material is securely zeroized when dropped.
104impl ZeroizeOnDrop for SecretKey {}
105
106impl PartialEq for SecretKey {
107    fn eq(&self, other: &Self) -> bool {
108        use subtle::ConstantTimeEq;
109        self.inner.to_bytes().ct_eq(&other.inner.to_bytes()).into()
110    }
111}
112
113impl Eq for SecretKey {}
114
115// PUBLIC KEY
116// ================================================================================================
117
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct PublicKey {
120    pub(crate) inner: ed25519_dalek::VerifyingKey,
121}
122
123impl PublicKey {
124    /// Returns a commitment to the public key using the RPO256 hash function.
125    pub fn to_commitment(&self) -> Word {
126        <Self as SequentialCommit>::to_commitment(self)
127    }
128
129    /// Verifies a signature against this public key and message.
130    pub fn verify(&self, message: Word, signature: &Signature) -> bool {
131        let message_bytes: [u8; 32] = message.into();
132        self.inner.verify(&message_bytes, &signature.inner).is_ok()
133    }
134
135    /// Convert to a X25519 public key which can be used in a DH key exchange protocol.
136    ///
137    /// # ⚠️ Security Warning
138    ///
139    /// **Do not reuse the same secret key for both Ed25519 signatures and X25519 key exchange.**
140    /// This conversion is primarily intended for sealed box primitives where an Ed25519 public key
141    /// is used to generate the shared key for encryption given an ephemeral X25519 key pair.
142    ///
143    /// In all other uses, prefer generating dedicated X25519 keys directly.
144    pub(crate) fn to_x25519(&self) -> x25519_dalek::PublicKey {
145        let mont_point = self.inner.to_montgomery();
146        x25519_dalek::PublicKey::from(mont_point.to_bytes())
147    }
148}
149
150impl SequentialCommit for PublicKey {
151    type Commitment = Word;
152
153    fn to_elements(&self) -> Vec<Felt> {
154        bytes_to_elements_with_padding(&self.to_bytes())
155    }
156}
157
158#[derive(Debug, Error)]
159pub enum PublicKeyError {
160    #[error("Could not verify with given public key and signature")]
161    VerificationFailed,
162}
163
164// SIGNATURE
165// ================================================================================================
166
167/// EdDSA (Ed25519) signature
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct Signature {
170    inner: ed25519_dalek::Signature,
171}
172
173impl Signature {
174    /// Verify against (message, public key).
175    pub fn verify(&self, message: Word, pub_key: &PublicKey) -> bool {
176        pub_key.verify(message, self)
177    }
178}
179
180// SERIALIZATION / DESERIALIZATION
181// ================================================================================================
182
183impl Serializable for SecretKey {
184    fn write_into<W: ByteWriter>(&self, target: &mut W) {
185        target.write_bytes(&self.inner.to_bytes());
186    }
187}
188
189impl Deserializable for SecretKey {
190    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
191        let mut bytes: [u8; SECRET_KEY_BYTES] = source.read_array()?;
192        let inner = ed25519_dalek::SigningKey::from_bytes(&bytes);
193        bytes.zeroize();
194
195        Ok(Self { inner })
196    }
197}
198
199impl Serializable for PublicKey {
200    fn write_into<W: ByteWriter>(&self, target: &mut W) {
201        target.write_bytes(&self.inner.to_bytes());
202    }
203}
204
205impl Deserializable for PublicKey {
206    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
207        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
208        let inner = ed25519_dalek::VerifyingKey::from_bytes(&bytes).map_err(|_| {
209            DeserializationError::InvalidValue("Invalid Ed25519 public key".to_string())
210        })?;
211        Ok(Self { inner })
212    }
213}
214
215impl Serializable for Signature {
216    fn write_into<W: ByteWriter>(&self, target: &mut W) {
217        target.write_bytes(&self.inner.to_bytes())
218    }
219}
220
221impl Deserializable for Signature {
222    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
223        let bytes: [u8; SIGNATURE_BYTES] = source.read_array()?;
224        let inner = ed25519_dalek::Signature::from_bytes(&bytes);
225        Ok(Self { inner })
226    }
227}