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_packed_u32_elements,
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    ///
126    /// The commitment is computed by first converting the public key to field elements (4 bytes
127    /// per element), and then computing a sequential hash of the elements.
128    pub fn to_commitment(&self) -> Word {
129        <Self as SequentialCommit>::to_commitment(self)
130    }
131
132    /// Verifies a signature against this public key and message.
133    pub fn verify(&self, message: Word, signature: &Signature) -> bool {
134        let message_bytes: [u8; 32] = message.into();
135        self.inner.verify(&message_bytes, &signature.inner).is_ok()
136    }
137
138    /// Convert to a X25519 public key which can be used in a DH key exchange protocol.
139    ///
140    /// # ⚠️ Security Warning
141    ///
142    /// **Do not reuse the same secret key for both Ed25519 signatures and X25519 key exchange.**
143    /// This conversion is primarily intended for sealed box primitives where an Ed25519 public key
144    /// is used to generate the shared key for encryption given an ephemeral X25519 key pair.
145    ///
146    /// In all other uses, prefer generating dedicated X25519 keys directly.
147    pub(crate) fn to_x25519(&self) -> x25519_dalek::PublicKey {
148        let mont_point = self.inner.to_montgomery();
149        x25519_dalek::PublicKey::from(mont_point.to_bytes())
150    }
151}
152
153impl SequentialCommit for PublicKey {
154    type Commitment = Word;
155
156    fn to_elements(&self) -> Vec<Felt> {
157        bytes_to_packed_u32_elements(&self.to_bytes())
158    }
159}
160
161#[derive(Debug, Error)]
162pub enum PublicKeyError {
163    #[error("Could not verify with given public key and signature")]
164    VerificationFailed,
165}
166
167// SIGNATURE
168// ================================================================================================
169
170/// EdDSA (Ed25519) signature
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct Signature {
173    inner: ed25519_dalek::Signature,
174}
175
176impl Signature {
177    /// Verify against (message, public key).
178    pub fn verify(&self, message: Word, pub_key: &PublicKey) -> bool {
179        pub_key.verify(message, self)
180    }
181}
182
183// SERIALIZATION / DESERIALIZATION
184// ================================================================================================
185
186impl Serializable for SecretKey {
187    fn write_into<W: ByteWriter>(&self, target: &mut W) {
188        target.write_bytes(&self.inner.to_bytes());
189    }
190}
191
192impl Deserializable for SecretKey {
193    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
194        let mut bytes: [u8; SECRET_KEY_BYTES] = source.read_array()?;
195        let inner = ed25519_dalek::SigningKey::from_bytes(&bytes);
196        bytes.zeroize();
197
198        Ok(Self { inner })
199    }
200}
201
202impl Serializable for PublicKey {
203    fn write_into<W: ByteWriter>(&self, target: &mut W) {
204        target.write_bytes(&self.inner.to_bytes());
205    }
206}
207
208impl Deserializable for PublicKey {
209    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
210        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
211        let inner = ed25519_dalek::VerifyingKey::from_bytes(&bytes).map_err(|_| {
212            DeserializationError::InvalidValue("Invalid Ed25519 public key".to_string())
213        })?;
214        Ok(Self { inner })
215    }
216}
217
218impl Serializable for Signature {
219    fn write_into<W: ByteWriter>(&self, target: &mut W) {
220        target.write_bytes(&self.inner.to_bytes())
221    }
222}
223
224impl Deserializable for Signature {
225    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
226        let bytes: [u8; SIGNATURE_BYTES] = source.read_array()?;
227        let inner = ed25519_dalek::Signature::from_bytes(&bytes);
228        Ok(Self { inner })
229    }
230}