Skip to content

Commit 499df9c

Browse files
committed
v0.4.2 RC1
1 parent 14abbe7 commit 499df9c

File tree

7 files changed

+94
-11
lines changed

7 files changed

+94
-11
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 0.4.2 (2024-10-05)
9+
10+
- Fixed size of SHAKE128 digest in `hash_message()`
11+
- Added sk.get_public_key()
12+
13+
814
## 0.4.1 (2024-09-30)
915

1016
- Now exports the pre-hash function enum

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ workspace = { exclude = ["ct_cm4", "dudect", "fuzz", "wasm"] }
22

33
[package]
44
name = "fips204"
5-
version = "0.4.1"
5+
version = "0.4.2"
66
authors = ["Eric Schorn <[email protected]>"]
77
description = "FIPS 204: Module-Lattice-Based Digital Signature"
88
categories = ["cryptography", "no-std"]

src/hashing.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,8 @@ pub(crate) fn hash_message(message: &[u8], ph: &Ph, phm: &mut [u8; 64]) -> ([u8;
342342
let mut hasher = Shake128::default();
343343
hasher.update(message);
344344
let mut reader = hasher.finalize_xof();
345-
reader.read(phm);
346-
64
345+
reader.read(&mut phm[0..32]);
346+
32
347347
},
348348
),
349349
}

src/lib.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ macro_rules! functionality {
250250

251251
impl Signer for PrivateKey {
252252
type Signature = [u8; SIG_LEN];
253+
type PublicKey = PublicKey;
253254

254255
// Algorithm 2 in Signer trait.
255256
fn try_sign_with_rng(
@@ -276,11 +277,18 @@ macro_rules! functionality {
276277
)?;
277278
Ok(sig)
278279
}
280+
281+
// Documented in traits.rs
282+
fn get_public_key(&self) -> Self::PublicKey {
283+
let pk = crate::ml_dsa::private_to_public::<CTEST, K, L, PK_LEN, SK_LEN>(ETA, &self.0);
284+
PublicKey { 0: pk }
285+
}
279286
}
280287

281288

282289
impl Signer for ExpandedPrivateKey {
283290
type Signature = [u8; SIG_LEN];
291+
type PublicKey = [u8; PK_LEN];
284292

285293
// Algorithm 2 in Signer trait. Rather than an external+internal split, this split of
286294
// start+finish enables the ability of signing with a pre-computeed expanded private
@@ -309,6 +317,11 @@ macro_rules! functionality {
309317
)?;
310318
Ok(sig)
311319
}
320+
321+
// Documented in traits.rs
322+
fn get_public_key(&self) -> Self::PublicKey {
323+
unimplemented!() // Note that ExpandedPublicKey does not currently contain Rho
324+
}
312325
}
313326

314327

@@ -420,18 +433,20 @@ macro_rules! functionality {
420433
#[test]
421434
fn smoke_test() {
422435
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
423-
let message = [0u8, 1, 2, 3, 4, 5, 6, 7];
436+
let message1 = [0u8, 1, 2, 3, 4, 5, 6, 7];
437+
let message2 = [7u8, 7, 7, 7, 7, 7, 7, 7];
424438

425-
for _i in 0..100 {
439+
for _i in 0..32 {
426440
let (pk, sk) = try_keygen_with_rng(&mut rng).unwrap();
427-
let sig = sk.try_sign_with_rng(&mut rng, &message, &[]).unwrap();
428-
let v = pk.verify(&message, &sig, &[]);
429-
assert!(v);
441+
let sig = sk.try_sign_with_rng(&mut rng, &message1, &[]).unwrap();
442+
assert!(pk.verify(&message1, &sig, &[]));
443+
assert!(!pk.verify(&message2, &sig, &[]));
430444
for ph in [Ph::SHA256, Ph::SHA512, Ph::SHAKE128] {
431-
let sig = sk.try_hash_sign_with_rng(&mut rng, &message, &[], &ph).unwrap();
432-
let v = pk.hash_verify(&message, &sig, &[], &ph);
445+
let sig = sk.try_hash_sign_with_rng(&mut rng, &message1, &[], &ph).unwrap();
446+
let v = pk.hash_verify(&message1, &sig, &[], &ph);
433447
assert!(v);
434448
}
449+
assert_eq!(pk.into_bytes(), sk.get_public_key().into_bytes());
435450
}
436451
}
437452
}

src/ml_dsa.rs

+38
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ pub(crate) fn verify_finish<
428428
Ok(left && center && right)
429429
}
430430

431+
431432
/// Algorithm: 6 `ML-DSA.KeyGen_internal()` on page 15.
432433
/// Generates a public-private key pair.
433434
///
@@ -492,3 +493,40 @@ pub(crate) fn key_gen_internal<
492493
// 10: return (pk, sk)
493494
(pk, sk)
494495
}
496+
497+
498+
pub(crate) fn private_to_public<
499+
const CTEST: bool,
500+
const K: usize,
501+
const L: usize,
502+
const PK_LEN: usize,
503+
const SK_LEN: usize,
504+
>(
505+
eta: i32, sk: &[u8; SK_LEN],
506+
) -> [u8; PK_LEN] {
507+
//
508+
// 1: (ρ, K, tr, s_1, s_2, t_0) ← skDecode(sk)
509+
// Code can only arrive here from keygen or a deserialized and validated sk
510+
let (rho, _cap_k, _tr, s_1, s_2, sk_t_0) = sk_decode(eta, sk).unwrap();
511+
512+
// 3: cap_a_hat ← ExpandA(ρ) ▷ A is generated and stored in NTT representation as Â
513+
let cap_a_hat: [[T; L]; K] = expand_a::<CTEST, K, L>(rho);
514+
515+
// 5: t ← NTT−1(cap_a_hat ◦ NTT(s_1)) + s_2 ▷ Compute t = As1 + s2
516+
let t: [R; K] = {
517+
let s_1_hat: [T; L] = ntt(&s_1);
518+
let as1_hat: [T; K] = mat_vec_mul(&cap_a_hat, &s_1_hat);
519+
let t_not_reduced: [R; K] = vec_add(&inv_ntt(&as1_hat), &s_2);
520+
core::array::from_fn(|k| R(core::array::from_fn(|n| full_reduce32(t_not_reduced[k].0[n]))))
521+
};
522+
523+
// 6: (t_1, t_0) ← Power2Round(t, d) ▷ Compress t
524+
let (t_1, pk_t_0): ([R; K], [R; K]) = power2round(&t);
525+
debug_assert_eq!(sk_t_0, pk_t_0); // fuzz target
526+
527+
// 7: pk ← pkEncode(ρ, t_1)
528+
let pk: [u8; PK_LEN] = pk_encode(rho, &t_1);
529+
530+
// 10: return (pk) # , sk)
531+
pk
532+
}

src/traits.rs

+24
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ pub trait KeyGen {
137137
pub trait Signer {
138138
/// The signature is specific to the chosen security parameter set, e.g., ml-dsa-44, ml-dsa-65 or ml-dsa-87
139139
type Signature;
140+
/// The public key that corresponds to the private/secret key
141+
type PublicKey;
140142

141143
/// Attempt to sign the given message, returning a digital signature on success, or an error if
142144
/// something went wrong. This function utilizes the **OS default** random number generator.
@@ -215,6 +217,28 @@ pub trait Signer {
215217
fn try_hash_sign_with_rng(
216218
&self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &Ph,
217219
) -> Result<Self::Signature, &'static str>;
220+
221+
222+
/// Retrieves the public key associated with this private/secret key
223+
/// # Examples
224+
/// ```rust
225+
/// # use std::error::Error;
226+
/// # fn main() -> Result<(), Box<dyn Error>> {
227+
/// use fips204::ml_dsa_65; // Could also be ml_dsa_44 or ml_dsa_87.
228+
/// use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
229+
///
230+
///
231+
/// // Generate both public and secret keys
232+
/// let (pk1, sk) = ml_dsa_65::KG::try_keygen()?; // Generate both public and secret keys
233+
///
234+
///
235+
/// // The public key can be derived from the secret key
236+
/// let pk2 = sk.get_public_key();
237+
/// assert_eq!(pk1.into_bytes(), pk2.into_bytes());
238+
/// # Ok(())
239+
/// # }
240+
/// ```
241+
fn get_public_key(&self) -> Self::PublicKey;
218242
}
219243

220244

src/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub struct ExpandedPublicKey<const K: usize, const L: usize> {
5454

5555

5656
/// Polynomial coefficients in R, with default R0
57-
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
57+
#[derive(Clone, Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
5858
#[repr(align(8))]
5959
pub(crate) struct R(pub(crate) [i32; 256]);
6060
pub(crate) const R0: R = R([0i32; 256]);

0 commit comments

Comments
 (0)