fips204/
traits.rs

1use crate::types::Ph;
2use rand_core::{CryptoRng, CryptoRngCore, RngCore};
3#[cfg(feature = "default-rng")]
4use rand_core::OsRng;
5
6
7/// The `KeyGen` trait is defined to allow trait objects for keygen.
8pub trait KeyGen {
9    /// An expanded public key containing precomputed elements to increase (repeated)
10    /// verify performance. Derived from the public key.
11    type PublicKey;
12
13    /// An expanded private key containing precomputed elements to increase (repeated)
14    /// signing performance. Derived from the private key.
15    type PrivateKey;
16
17
18    /// Generates a public and private key pair specific to this security parameter set.
19    /// This function utilizes the **OS default** random number generator. This function operates
20    /// in constant-time relative to secret data (which specifically excludes the OS random
21    /// number generator internals, the `rho` value stored in the public key, and the hash-derived
22    /// `rho_prime` values that are rejection-sampled/expanded into the internal `s_1` and `s_2` values).
23    ///
24    /// # Errors
25    /// Returns an error when the random number generator fails.
26    ///
27    /// # Examples
28    /// ```rust
29    /// # use std::error::Error;
30    /// # fn main() -> Result<(), Box<dyn Error>> {
31    /// # #[cfg(all(feature = "ml-dsa-44", feature = "default-rng"))] {
32    /// use fips204::ml_dsa_44; // Could also be ml_dsa_65 or ml_dsa_87.
33    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
34    ///
35    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
36    ///
37    /// // Generate key pair and signature
38    /// let (pk, sk) = ml_dsa_44::KG::try_keygen()?; // Generate both public and secret keys
39    /// let sig = sk.try_sign(&message, &[0])?; // Use the secret key to generate a message signature
40    /// # }
41    /// # Ok(())}
42    /// ```
43    #[cfg(feature = "default-rng")]
44    fn try_keygen() -> Result<(Self::PublicKey, Self::PrivateKey), &'static str> {
45        Self::try_keygen_with_rng(&mut OsRng)
46    }
47
48
49    /// Generates a public and private key pair specific to this security parameter set.
50    /// This function utilizes the **provided** random number generator. This function operates
51    /// in constant-time relative to secret data (which specifically excludes the provided random
52    /// number generator internals, the `rho` value stored in the public key, and the hash-derived
53    /// `rho_prime` values that are rejection-sampled/expanded into the internal `s_1` and `s_2` values).
54    ///
55    /// # Errors
56    /// Returns an error when the random number generator fails.
57    ///
58    /// # Examples
59    /// ```rust
60    /// # use std::error::Error;
61    /// # fn main() -> Result<(), Box<dyn Error>> {
62    /// # #[cfg(all(feature = "ml-dsa-44", feature = "default-rng"))] {
63    /// use fips204::ml_dsa_44; // Could also be ml_dsa_65 or ml_dsa_87.
64    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
65    /// use rand_chacha::rand_core::SeedableRng;
66    ///
67    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
68    /// let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
69    ///
70    /// // Generate key pair and signature
71    /// let (pk, sk) = ml_dsa_44::KG::try_keygen_with_rng(&mut rng)?;  // Generate both public and secret keys
72    /// let sig = sk.try_sign(&message, &[0])?;  // Use the secret key to generate a message signature
73    /// }
74    /// # Ok(())}
75    /// ```
76    fn try_keygen_with_rng(
77        rng: &mut impl CryptoRngCore,
78    ) -> Result<(Self::PublicKey, Self::PrivateKey), &'static str>;
79
80
81    /// Generates an public and private key key pair specific to this security parameter set
82    /// based on a provided seed. <br>
83    /// This function operates in constant-time relative to secret data (which specifically excludes
84    /// the the `rho` value stored in the public key and the hash-derived `rho_prime` values that are
85    /// rejection-sampled/expanded into the internal `s_1` and `s_2` values).
86    ///
87    /// # Examples
88    /// ```rust
89    /// # use std::error::Error;
90    /// # fn main() -> Result<(), Box<dyn Error>> {
91    /// # #[cfg(feature = "ml-dsa-44")] {
92    /// use crate::fips204::RngCore;
93    /// use fips204::ml_dsa_44; // Could also be ml_dsa_65 or ml_dsa_87.
94    /// use fips204::traits::{KeyGen, Signer, Verifier};
95    /// use rand_core::OsRng;
96    ///
97    /// // The signor gets the xi seed from the OS random number generator
98    /// let mut xi = [0u8; 32];
99    /// OsRng.fill_bytes(&mut xi);
100    /// ///
101    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
102    ///
103    /// // Generate key pair and signature
104    /// let (pk, sk) = ml_dsa_44::KG::keygen_from_seed(&xi); // Generate both public and secret keys
105    /// let sig = sk.try_sign(&message, &[0])?; // Use the secret key to generate a message signature
106    ///
107    /// let res = pk.verify(&message, &sig, &[0]);
108    /// assert!(res); // Signature accepted
109    /// # }
110    /// # Ok(())}
111    /// ```
112    #[must_use]
113    fn keygen_from_seed(xi: &[u8; 32]) -> (Self::PublicKey, Self::PrivateKey);
114}
115
116
117/// The Signer trait is implemented for the `PrivateKey` struct on each of the security parameter sets.
118pub trait Signer {
119    /// The signature is specific to the chosen security parameter set, e.g., ml-dsa-44, ml-dsa-65 or ml-dsa-87
120    type Signature;
121
122    /// The public key that corresponds to the private/secret key
123    type PublicKey;
124
125
126    /// Attempt to sign the given message, returning a digital signature on success, or an error if
127    /// something went wrong. This function utilizes the **OS default** random number generator.
128    /// This function operates in constant-time relative to secret data (which specifically excludes
129    /// the OS default random number generator internals, the `rho` value this is stored in the public
130    /// key, the hash-derived `rho_prime` values that are rejection-sampled/expanded into the internal
131    /// `s_1` and `s_2` values, and the main signing rejection loop as noted in section 5.5 of
132    /// <https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf>).
133    ///
134    /// # Errors
135    /// Returns an error when the random number generator fails or the `ctx` is longer than 255 bytes; propagates internal errors.
136    ///
137    /// # Examples
138    /// ```rust
139    /// # use std::error::Error;
140    /// # fn main() -> Result<(), Box<dyn Error>> {
141    /// # #[cfg(all(feature = "ml-dsa-65", feature = "default-rng"))] {
142    /// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
143    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
144    ///
145    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
146    ///
147    /// // Generate key pair and signature
148    /// let (pk, sk) = ml_dsa_65::KG::try_keygen()?; // Generate both public and secret keys
149    /// let sig = sk.try_sign(&message, &[0])?; // Use the secret key to generate a message signature
150    /// let v = pk.verify(&message, &sig, &[0]); // Use the public to verify message signature
151    /// assert!(v);
152    /// # }
153    /// # Ok(())}
154    /// ```
155    #[cfg(feature = "default-rng")]
156    fn try_sign(&self, message: &[u8], ctx: &[u8]) -> Result<Self::Signature, &'static str> {
157        self.try_sign_with_rng(&mut OsRng, message, ctx)
158    }
159
160
161    /// Attempt to sign the given message, returning a digital signature on success, or an error if
162    /// something went wrong. This function utilizes the **provided** random number generator.
163    /// This function operates in constant-time relative to secret data (which specifically excludes
164    /// the provided random number generator internals, the `rho` value (also) stored in the public
165    /// key, the hash-derived `rho_prime` value that is rejection-sampled/expanded into the internal
166    /// `s_1` and `s_2` values, and the main signing rejection loop as noted in section 5.5 of
167    /// <https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf>.
168    ///
169    /// # Errors
170    /// Returns an error when the random number generator fails or the `ctx` is longer than 255 bytes; propagates internal errors.
171    ///
172    /// # Examples
173    /// ```rust
174    /// # use std::error::Error;
175    /// # fn main() -> Result<(), Box<dyn Error>> {
176    /// # #[cfg(feature = "ml-dsa-65")] {
177    /// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
178    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
179    /// use rand_chacha::rand_core::SeedableRng;
180    ///
181    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
182    /// let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
183    ///
184    /// // Generate key pair and signature
185    /// let (pk, sk) = ml_dsa_65::KG::try_keygen_with_rng(&mut rng)?;  // Generate both public and secret keys
186    /// let sig = sk.try_sign_with_rng(&mut rng, &message, &[0])?;  // Use the secret key to generate a message signature
187    /// let v = pk.verify(&message, &sig, &[0]); // Use the public to verify message signature
188    /// assert!(v);
189    /// # }
190    /// # Ok(())}
191    /// ```
192    fn try_sign_with_rng(
193        &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8],
194    ) -> Result<Self::Signature, &'static str>;
195
196
197    /// Attempt to sign the given message, returning a digital signature on success, or an error if
198    /// something went wrong. This function utilizes the **provided seed to support (less common)
199    /// deterministic signatures**. This function operates in constant-time relative to secret data
200    /// (which specifically excludes the `rho` value stored in the public key, the hash-derived
201    /// `rho_prime` value that is rejection-sampled/expanded into the internal `s_1` and `s_2` values,
202    /// and the main signing rejection loop as noted in section 5.5 of
203    /// <https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf>.
204    ///
205    /// # Errors
206    /// Returns an error when the `ctx` is longer than 255 bytes; propagates internal errors.
207    ///
208    /// # Examples
209    /// ```rust
210    /// # use std::error::Error;
211    /// # fn main() -> Result<(), Box<dyn Error>> {
212    /// # #[cfg(feature = "ml-dsa-65")] {
213    /// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
214    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
215    /// use rand_chacha::rand_core::SeedableRng;
216    ///
217    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
218    /// let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
219    ///
220    /// // Generate key pair and signature
221    /// let (pk, sk) = ml_dsa_65::KG::try_keygen_with_rng(&mut rng)?;  // Generate both public and secret keys
222    /// let sig = sk.try_sign_with_seed(&[0u8;32], &message, &[0])?;  // Use the secret key to generate a message signature
223    /// let v = pk.verify(&message, &sig, &[0]); // Use the public to verify message signature
224    /// assert!(v);
225    /// # }
226    /// # Ok(())}
227    /// ```
228    fn try_sign_with_seed(
229        &self, seed: &[u8; 32], message: &[u8], ctx: &[u8],
230    ) -> Result<Self::Signature, &'static str> {
231        self.try_sign_with_rng(&mut DummyRng {data: *seed}, message, ctx)
232    }
233
234
235    /// Attempt to sign the hash of the given message, returning a digital signature on success,
236    /// or an error if something went wrong. This function utilizes the **default OS** random number
237    /// generator and allows for several hash algorithms. This function operates in constant-time
238    /// relative to secret data (which specifically excludes the provided random number generator
239    /// internals, the `rho` value (also) stored in the public key, the hash-derived `rho_prime`
240    /// value that is rejection-sampled/expanded into the internal `s_1` and `s_2` values, and the
241    /// main signing rejection loop as noted in section 5.5 of
242    /// <https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf>.
243    ///
244    /// # Errors
245    /// Returns an error when the random number generator fails or the `ctx` is longer than 255 bytes; propagates internal errors.
246    #[cfg(feature = "default-rng")]
247    fn try_hash_sign(
248        &self, message: &[u8], ctx: &[u8], ph: &Ph,
249    ) -> Result<Self::Signature, &'static str> {
250        self.try_hash_sign_with_rng(&mut OsRng, message, ctx, ph)
251    }
252
253
254    /// Attempt to sign the hash of the given message, returning a digital signature on success,
255    /// or an error if something went wrong. This function utilizes the **provided** random number
256    /// generator and allows for several hash algorithms. This function operates in constant-time
257    /// relative to secret data (which specifically excludes the provided random number generator
258    /// internals, the `rho` value (also) stored in the public key, the hash-derived `rho_prime`
259    /// value that is rejection-sampled/expanded into the internal `s_1` and `s_2` values, and the
260    /// main signing rejection loop as noted in section 5.5 of
261    /// <https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf>.
262    ///
263    /// # Errors
264    /// Returns an error when the random number generator fails or the `ctx` is longer than 255 bytes; propagates internal errors.
265    fn try_hash_sign_with_rng(
266        &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &Ph,
267    ) -> Result<Self::Signature, &'static str>;
268
269
270    /// Attempt to sign the hash of the given message, returning a digital signature on success,
271    /// something went wrong. This function utilizes the **provided seed to support (less common)
272    /// deterministic signatures**. This function operates in constant-time relative to secret data
273    /// (which specifically excludes the `rho` value stored in the public key, the hash-derived
274    /// `rho_prime` value that is rejection-sampled/expanded into the internal `s_1` and `s_2` values,
275    /// and the main signing rejection loop as noted in section 5.5 of
276    /// <https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf>.
277    ///
278    /// # Errors
279    /// Returns an error when the `ctx` is longer than 255 bytes; propagates internal errors.
280    fn try_hash_sign_with_seed(
281        &self, seed: &[u8;32], message: &[u8], ctx: &[u8], ph: &Ph,
282    ) -> Result<Self::Signature, &'static str> {
283        self.try_hash_sign_with_rng(&mut DummyRng {data: *seed}, message, ctx, ph)
284    }
285
286
287    /// Retrieves the public key associated with this private/secret key
288    ///
289    /// # Examples
290    /// ```rust
291    /// # use std::error::Error;
292    /// # fn main() -> Result<(), Box<dyn Error>> {
293    /// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
294    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
295    ///
296    ///
297    /// // Generate both public and secret keys
298    /// let (pk1, sk) = ml_dsa_65::KG::try_keygen()?; // Generate both public and secret keys
299    ///
300    ///
301    /// // The public key can be derived from the secret key
302    /// let pk2 = sk.get_public_key();
303    /// assert_eq!(pk1.into_bytes(), pk2.into_bytes());
304    /// # Ok(())
305    /// # }
306    /// ```
307    fn get_public_key(&self) -> Self::PublicKey;
308}
309
310// This is for the deterministic signing functions; will be refactored more nicely
311struct DummyRng { data: [u8; 32] }
312
313impl RngCore for DummyRng {
314    fn next_u32(&mut self) -> u32 { unimplemented!() }
315
316    fn next_u64(&mut self) -> u64 { unimplemented!() }
317
318    fn fill_bytes(&mut self, _out: &mut [u8]) { unimplemented!() }
319
320    fn try_fill_bytes(&mut self, out: &mut [u8]) -> Result<(), rand_core::Error> {
321        out.copy_from_slice(&self.data);
322        Ok(())
323    }
324}
325
326impl CryptoRng for DummyRng {}
327
328
329/// The Verifier trait is implemented for `PublicKey` on each of the security parameter sets.
330pub trait Verifier {
331    /// The signature is specific to the chosen security parameter set, e.g., ml-dsa-44, ml-dsa-65
332    /// or ml-dsa-87
333    type Signature;
334
335    /// Verifies a digital signature on a message with respect to a `PublicKey`. As this function
336    /// operates on purely public data, it need/does not provide constant-time assurances.
337    ///
338    /// # Examples
339    /// ```rust
340    /// # use std::error::Error;
341    /// # fn main() -> Result<(), Box<dyn Error>> {
342    /// # #[cfg(all(feature = "ml-dsa-65", feature = "default-rng"))] {
343    /// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
344    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
345    ///
346    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
347    ///
348    /// // Generate key pair and signature
349    /// let (pk, sk) = ml_dsa_65::KG::try_keygen()?; // Generate both public and secret keys
350    /// let sig = sk.try_sign(&message, &[0])?; // Use the secret key to generate a message signature
351    /// let v = pk.verify(&message, &sig, &[0]); // Use the public to verify message signature
352    /// assert!(v);
353    /// # }
354    /// # Ok(())}
355    /// ```
356    fn verify(&self, message: &[u8], signature: &Self::Signature, ctx: &[u8]) -> bool;
357
358
359    /// Verifies a digital signature on the hash of a message with respect to a `PublicKey`. As this
360    /// function operates on purely public data, it need/does not provide constant-time assurances.
361    fn hash_verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8], ph: &Ph) -> bool;
362}
363
364
365/// The `SerDes` trait provides for validated serialization and deserialization of fixed- and correctly-size elements.
366///
367/// Note that FIPS 204 currently states that outside of exact length checks "ML-DSA is not designed to require any
368/// additional public-key validity checks" (perhaps "...designed not to require..." would be better). Nonetheless, a
369/// `Result()` is returned during all deserialization operations to preserve the ability to add future checks (and for
370/// symmetry across structures). Note that for the current implementation, both of the private and public key
371/// deserialization routines invoke an internal decode that catches over-sized coefficients (for early detection).
372pub trait SerDes {
373    /// The fixed-size byte array to be serialized or deserialized
374    type ByteArray;
375
376
377    /// Produces a byte array of fixed-size specific to the struct being serialized.
378    ///
379    /// # Examples
380    /// ```rust
381    /// # use std::error::Error;
382    /// # fn main() -> Result<(), Box<dyn Error>> {
383    /// # #[cfg(all(feature = "ml-dsa-65", feature = "default-rng"))] {
384    /// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
385    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
386    ///
387    /// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
388    ///
389    /// // Generate key pair and signature
390    /// let (pk, sk) = ml_dsa_65::KG::try_keygen()?; // Generate both public and secret keys
391    /// let pk_bytes = pk.into_bytes(); // Serialize the public key
392    /// let sk_bytes = sk.into_bytes(); // Serialize the private key
393    /// # }
394    /// # Ok(())}
395    /// ```
396    fn into_bytes(self) -> Self::ByteArray;
397
398
399    /// Consumes a byte array of fixed-size specific to the struct being deserialized; performs validation
400    ///
401    /// # Errors
402    /// Returns an error on malformed input.
403    ///
404    /// # Examples
405    /// ```rust
406    /// # use std::error::Error;
407    /// # fn main() -> Result<(), Box<dyn Error>> {
408    /// # #[cfg(all(feature = "ml-dsa-87", feature = "default-rng"))] {
409    /// use fips204::ml_dsa_87; // Could also be ml_dsa_44 or ml_dsa_65.
410    /// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
411    ///
412    /// // Generate key pair and signature
413    /// let (pk, sk) = ml_dsa_87::try_keygen()?; // Generate both public and secret keys
414    /// let pk_bytes = pk.into_bytes(); // Serialize the public key
415    /// let sk_bytes = sk.into_bytes(); // Serialize the private key
416    /// let pk2 = ml_dsa_87::PublicKey::try_from_bytes(pk_bytes)?;
417    /// let sk2 = ml_dsa_87::PrivateKey::try_from_bytes(sk_bytes)?;
418    /// # }
419    /// # Ok(())}
420    /// ```
421    fn try_from_bytes(ba: Self::ByteArray) -> Result<Self, &'static str>
422    where
423        Self: Sized;
424}