Skip to content

Commit 8e1f24d

Browse files
committed
keygen from seed
1 parent 678e741 commit 8e1f24d

16 files changed

+103
-1949
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ The Rust [Documentation][docs-link] lives under each **Module** corresponding to
5454

5555
## Notes
5656

57-
* This crate is fully functional and corresponds to FIPS 204 (August 13, 2024).
57+
* This crate is fully functional and corresponds to FIPS 204 (August 13, 2024).
58+
* **BEWARE:** As of September 27, 2024 NIST has not release external/hash test vectors!
5859
* Constant-time assurances target the source-code level only on MSRV, with confirmation via
5960
manual review/inspection, the embedded target, and the `dudect` dynamic tests.
6061
* Note that FIPS 204 places specific requirements on randomness per section 3.5.1, hence the exposed `RNG`.
6162
* Requires Rust **1.70** or higher. The minimum supported Rust version may be changed in the future, but
6263
it will be done with a minor version bump (when the major version is larger than 0)..
6364
* All on-by-default features of this library are covered by `SemVer`.
64-
* The FIPS 204 standard and this software is considered experimental -- USE AT YOUR OWN RISK!
65+
* The FIPS 204 standard and this software should be considered experimental -- USE AT YOUR OWN RISK!
6566

6667
## License
6768

src/hashing.rs

+35-52
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,10 @@
33
use crate::conversion::{bit_unpack, coeff_from_half_byte, coeff_from_three_bytes};
44
use crate::helpers::{bit_length, is_in_range};
55
use crate::types::{Ph, R, R0, T, T0};
6+
use sha2::{Digest, Sha256, Sha512};
67
use sha3::digest::{ExtendableOutput, Update, XofReader};
78
use sha3::{Shake128, Shake256};
89

9-
// use sha3::Shake256Core;
10-
// use sha3::digest::core_api::CoreWrapper;
11-
//
12-
// pub(crate) fn hhh_xof(v: &[&[u8]]) -> CoreWrapper<Shake256Core> {
13-
// let mut hasher = Shake256::default();
14-
// v.iter().for_each(|b| hasher.update(b));
15-
// hasher
16-
// }
17-
1810

1911
/// # Function H(v,d) of (8.1) on page 29.
2012
/// Takes a reference to a list of byte-slice references and runs them through Shake256.
@@ -343,48 +335,39 @@ pub(crate) fn expand_mask<const L: usize>(gamma1: i32, rho: &[u8; 64], mu: u16)
343335

344336
pub(crate) fn hash_message(message: &[u8], ph: &Ph, phm: &mut [u8; 64]) -> ([u8; 11], usize) {
345337
match ph {
346-
Ph::SHA256 => {
347-
use sha2::{Digest, Sha256};
348-
(
349-
[
350-
0x06u8, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
351-
],
352-
{
353-
let mut hasher = Sha256::new();
354-
Digest::update(&mut hasher, message); //hasher.update(message);
355-
phm[0..32].copy_from_slice(&hasher.finalize());
356-
32
357-
},
358-
)
359-
}
360-
Ph::SHA512 => {
361-
use sha2::{Digest, Sha512};
362-
(
363-
[
364-
0x06u8, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
365-
],
366-
{
367-
let mut hasher = Sha512::new();
368-
Digest::update(&mut hasher, message); //hasher.update(message);
369-
phm.copy_from_slice(&hasher.finalize());
370-
64
371-
},
372-
)
373-
}
374-
Ph::SHAKE128 => {
375-
(
376-
[
377-
0x06u8, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B,
378-
],
379-
{
380-
//use sha3::digest::{ExtendableOutput, Update, XofReader}; // some collide with sha2
381-
let mut hasher = Shake128::default();
382-
hasher.update(message);
383-
let mut reader = hasher.finalize_xof();
384-
reader.read(phm);
385-
64
386-
},
387-
)
388-
}
338+
Ph::SHA256 => (
339+
[
340+
0x06u8, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
341+
],
342+
{
343+
let mut hasher = Sha256::new();
344+
Digest::update(&mut hasher, message);
345+
phm[0..32].copy_from_slice(&hasher.finalize());
346+
32
347+
},
348+
),
349+
Ph::SHA512 => (
350+
[
351+
0x06u8, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
352+
],
353+
{
354+
let mut hasher = Sha512::new();
355+
Digest::update(&mut hasher, message);
356+
phm.copy_from_slice(&hasher.finalize());
357+
64
358+
},
359+
),
360+
Ph::SHAKE128 => (
361+
[
362+
0x06u8, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B,
363+
],
364+
{
365+
let mut hasher = Shake128::default();
366+
hasher.update(message);
367+
let mut reader = hasher.finalize_xof();
368+
reader.read(phm);
369+
64
370+
},
371+
),
389372
}
390373
}

src/lib.rs

+22-62
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ macro_rules! functionality {
223223
Ok((PublicKey { 0: pk }, PrivateKey { 0: sk }))
224224
}
225225

226+
227+
fn keygen_from_seed(xi: &[u8; 32]) -> (Self::PublicKey, Self::PrivateKey) {
228+
// let (pk, sk) = ml_dsa::key_gen::<CTEST, K, L, PK_LEN, SK_LEN>(rng, ETA)?;
229+
// Ok((PublicKey { 0: pk }, PrivateKey { 0: sk }))
230+
let (pk, sk) = ml_dsa::key_gen_internal::<CTEST, K, L, PK_LEN, SK_LEN>(ETA, xi);
231+
(PublicKey { 0: pk }, PrivateKey { 0: sk })
232+
}
233+
226234
// A portion of algorithm 1 in KeyGen trait -- expanded private key for faster signing
227235
fn gen_expanded_private(sk: &PrivateKey) -> Result<Self::ExpandedPrivateKey, &'static str> {
228236
let esk = ml_dsa::sign_start::<CTEST, K, L, SK_LEN>(ETA, &sk.0)?;
@@ -247,18 +255,7 @@ macro_rules! functionality {
247255
ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long");
248256
let esk = ml_dsa::sign_start::<CTEST, K, L, SK_LEN>(ETA, &self.0)?;
249257
let sig = ml_dsa::sign_finish::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
250-
rng,
251-
BETA,
252-
GAMMA1,
253-
GAMMA2,
254-
OMEGA,
255-
TAU,
256-
&esk,
257-
message,
258-
ctx,
259-
&[],
260-
&[],
261-
false,
258+
rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &esk, message, ctx, &[], &[], false
262259
)?;
263260
Ok(sig)
264261
}
@@ -272,7 +269,7 @@ macro_rules! functionality {
272269
let mut phm = [0u8; 64]; // hashers don't all play well with each other
273270
let (oid, phm_len) = hash_message(message, ph, &mut phm);
274271
let sig = ml_dsa::sign_finish::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
275-
rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &esk, message, ctx, &oid, &phm[0..phm_len], false,
272+
rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &esk, message, ctx, &oid, &phm[0..phm_len], false
276273
)?;
277274
Ok(sig)
278275
}
@@ -290,18 +287,7 @@ macro_rules! functionality {
290287
) -> Result<Self::Signature, &'static str> {
291288
ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long");
292289
let sig = ml_dsa::sign_finish::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
293-
rng,
294-
BETA,
295-
GAMMA1,
296-
GAMMA2,
297-
OMEGA,
298-
TAU,
299-
&self,
300-
message,
301-
ctx,
302-
&[],
303-
&[],
304-
false,
290+
rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &[], &[], false
305291
)?;
306292
Ok(sig)
307293
}
@@ -316,7 +302,7 @@ macro_rules! functionality {
316302
let mut phm = [0u8; 64]; // hashers don't all play well with each other
317303
let (oid, phm_len) = hash_message(message, ph, &mut phm);
318304
let sig = ml_dsa::sign_finish::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
319-
rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &oid, &phm[0..phm_len], false,
305+
rng, BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &oid, &phm[0..phm_len], false
320306
)?;
321307
Ok(sig)
322308
}
@@ -331,59 +317,33 @@ macro_rules! functionality {
331317
if ctx.len() > 255 {
332318
return false;
333319
};
334-
let epk = ml_dsa::verify_start(&self.0);
335-
if epk.is_err() {
320+
let Ok(epk) = ml_dsa::verify_start(&self.0) else {
336321
return false;
337322
};
338-
let res = ml_dsa::verify_finish::<K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
339-
BETA,
340-
GAMMA1,
341-
GAMMA2,
342-
OMEGA,
343-
TAU,
344-
&epk.unwrap(),
345-
&message,
346-
&sig,
347-
ctx,
348-
&[],
349-
&[],
350-
false,
351-
);
352-
if res.is_err() {
323+
let Ok(res) = ml_dsa::verify_finish::<K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
324+
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &epk, &message, &sig, ctx, &[], &[], false
325+
) else {
353326
return false;
354327
};
355-
res.unwrap()
328+
res
356329
}
357330

358331
// Algorithm 5 in Verifier trait.
359332
fn hash_verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8], ph: &Ph) -> bool {
360333
if ctx.len() > 255 {
361334
return false;
362335
};
363-
let epk = ml_dsa::verify_start(&self.0);
364-
if epk.is_err() {
336+
let Ok(epk) = ml_dsa::verify_start(&self.0) else {
365337
return false;
366338
};
367339
let mut phm = [0u8; 64]; // hashers don't all play well with each other
368340
let (oid, phm_len) = hash_message(message, ph, &mut phm);
369-
let res = ml_dsa::verify_finish::<K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
370-
BETA,
371-
GAMMA1,
372-
GAMMA2,
373-
OMEGA,
374-
TAU,
375-
&epk.unwrap(),
376-
&message,
377-
&sig,
378-
ctx,
379-
&oid,
380-
&phm[0..phm_len],
381-
false,
382-
);
383-
if res.is_err() {
341+
let Ok(res) = ml_dsa::verify_finish::<K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
342+
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &epk, &message, &sig, ctx, &oid, &phm[0..phm_len], false
343+
) else {
384344
return false;
385345
};
386-
res.unwrap()
346+
res
387347
}
388348
}
389349

src/ml_dsa.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub(crate) fn key_gen<
3939
let mut xi = [0u8; 32];
4040
rng.try_fill_bytes(&mut xi).map_err(|_| "Random number generator failed")?;
4141

42-
Ok(key_gen_internal::<CTEST, K, L, PK_LEN, SK_LEN>(eta, xi))
42+
Ok(key_gen_internal::<CTEST, K, L, PK_LEN, SK_LEN>(eta, &xi))
4343
}
4444

4545

@@ -444,7 +444,7 @@ pub(crate) fn key_gen_internal<
444444
const PK_LEN: usize,
445445
const SK_LEN: usize,
446446
>(
447-
eta: i32, xi: [u8; 32],
447+
eta: i32, xi: &[u8; 32],
448448
) -> ([u8; PK_LEN], [u8; SK_LEN]) {
449449
//
450450
// 1: ξ ← {0,1}^{256} ▷ Choose random seed
@@ -453,7 +453,7 @@ pub(crate) fn key_gen_internal<
453453

454454
// WRONG: 2: (ρ, ρ′, K) ∈ {0,1}^{256} × {0,1}^{512} × {0,1}^{256} ← H(ξ, 1024) ▷ Expand seed
455455
// 1: (𝜌, 𝜌′, 𝐾) ∈ 𝔹32 × 𝔹64 × 𝔹32 ← H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128)
456-
let mut h2 = h_xof(&[&xi, &[K.to_le_bytes()[0]], &[L.to_le_bytes()[0]]]);
456+
let mut h2 = h_xof(&[xi, &[K.to_le_bytes()[0]], &[L.to_le_bytes()[0]]]);
457457
let mut rho = [0u8; 32];
458458
h2.read(&mut rho);
459459
let mut rho_prime = [0u8; 64];

src/traits.rs

+34
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,40 @@ pub trait KeyGen {
7676
rng: &mut impl CryptoRngCore,
7777
) -> Result<(Self::PublicKey, Self::PrivateKey), &'static str>;
7878

79+
/// Generates an public and private key key pair specific to this security parameter set
80+
/// based on a provided seed. <br>
81+
/// This function operates in constant-time relative to secret data (which specifically excludes
82+
/// the the `rho` value stored in the public key and the hash-derived `rho_prime` values that are
83+
/// rejection-sampled/expanded into the internal `s_1` and `s_2` values).
84+
/// # Examples
85+
/// ```rust
86+
/// # use std::error::Error;
87+
/// # fn main() -> Result<(), Box<dyn Error>> {
88+
/// # #[cfg(feature = "ml-dsa-44")] {
89+
/// use crate::fips204::RngCore;
90+
/// use fips204::ml_dsa_44; // Could also be ml_dsa_65 or ml_dsa_87.
91+
/// use fips204::traits::{KeyGen, Signer, Verifier};
92+
/// use rand_core::OsRng;
93+
///
94+
/// // The signor gets the xi seed from the OS random number generator
95+
/// let mut xi = [0u8; 32];
96+
/// OsRng.fill_bytes(&mut xi);
97+
/// ///
98+
/// let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
99+
///
100+
/// // Generate key pair and signature
101+
/// let (pk, sk) = ml_dsa_44::KG::keygen_from_seed(&xi); // Generate both public and secret keys
102+
/// let sig = sk.try_sign(&message, &[0])?; // Use the secret key to generate a message signature
103+
///
104+
/// let res = pk.verify(&message, &sig, &[0]);
105+
/// assert!(res); // Signature accepted
106+
/// # }
107+
/// # Ok(())}
108+
/// ```
109+
#[must_use]
110+
fn keygen_from_seed(_xi: &[u8; 32]) -> (Self::PublicKey, Self::PrivateKey);
111+
112+
79113
/// Generates an expanded private key from the normal/compressed private key.
80114
/// This supports improved signing performance. This function operates in constant-time
81115
/// relative to secret data (which specifically excludes the provided random `rho`

0 commit comments

Comments
 (0)