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}