Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion slh-dsa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ typenum = { version = "1.17", features = ["const-generics"] }
sha3 = { version = "0.11.0-rc.0", default-features = false }
zerocopy = { version = "0.8", features = ["derive"] }
rand_core = { version = "0.9" }
signature = { version = "3.0.0-rc.4", features = ["rand_core"] }
signature = { version = "3.0.0-rc.4", features = ["digest", "rand_core"] }
hmac = "0.13.0-rc.1"
sha2 = { version = "0.11.0-rc.2", default-features = false }
digest = "0.11.0-rc.1"
Expand Down
33 changes: 26 additions & 7 deletions slh-dsa/src/hashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,43 @@ mod shake;

use core::fmt::Debug;

use digest::Update;
use hybrid_array::{Array, ArraySize};
use signature::Result;

pub use sha2::*;
pub use shake::*;

use crate::{PkSeed, SkPrf, SkSeed, address::Address};

/// `Digest` parameter for [`DigestSigner`] and [`DigestVerifier`].
///
/// [`DigestSigner`]: signature::DigestSigner
/// [`DigestVerifier`]: signature::DigestVerifier
pub trait HashDigest {
/// The associated `Digest` type.
type Digest: Update;
}
Comment on lines +23 to +26
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had type Digest on HashSuite directly, but that made it publicly not accessible.

So I introduced HashDigest just to make it publicly accessible if users ever need to access the type.


/// A trait specifying the hash functions described in FIPS-205 section 10
pub(crate) trait HashSuite: Sized + Clone + Debug + PartialEq + Eq {
pub(crate) trait HashSuite: HashDigest + Sized + Clone + Debug + PartialEq + Eq {
type N: ArraySize + Debug + Clone + PartialEq + Eq;
type M: ArraySize + Debug + Clone + PartialEq + Eq;

/// Pseudorandom function that generates the randomizer for the randomized hashing of the message to be signed.
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::N>;
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::N>>;

/// Hashes a message using a given randomizer
fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::M>;
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::M>>;

/// PRF that is used to generate the secret values in WOTS+ and FORS private keys.
fn prf_sk(
Expand Down Expand Up @@ -76,7 +87,11 @@ mod tests {
let opt_rand = Array::<u8, H::N>::from_fn(|_| 1);
let msg = [2u8; 32];

let result = H::prf_msg(&sk_prf, &opt_rand, &[&[msg]]);
let result = H::prf_msg(&sk_prf, &opt_rand, &|digest| {
digest.update(&msg);
Ok(())
})
.unwrap();

assert_eq!(result.as_slice(), expected);
}
Expand All @@ -87,7 +102,11 @@ mod tests {
let pk_root = Array::<u8, H::N>::from_fn(|_| 2);
let msg = [3u8; 32];

let result = H::h_msg(&rand, &pk_seed, &pk_root, &[&[msg]]);
let result = H::h_msg(&rand, &pk_seed, &pk_root, &|digest| {
digest.update(&msg);
Ok(())
})
.unwrap();

assert_eq!(result.as_slice(), expected);
}
Expand Down
111 changes: 70 additions & 41 deletions slh-dsa/src/hashes/sha2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@

use core::fmt::Debug;

use crate::hashes::HashSuite;
use crate::hashes::{HashDigest, HashSuite};
use crate::{
ParameterSet, address::Address, fors::ForsParams, hypertree::HypertreeParams, wots::WotsParams,
xmss::XmssParams,
};
use crate::{PkSeed, SkPrf, SkSeed};
use const_oid::db::fips205;
use digest::{Digest, KeyInit, Mac};
use hmac::Hmac;
use digest::{Digest, KeyInit, Mac, Output, OutputSizeUser, Update};
use hmac::{EagerHash, Hmac};
use hybrid_array::{Array, ArraySize};
use sha2::{Sha256, Sha512};
use signature::Result;
use typenum::{Diff, Sum, U, U16, U24, U30, U32, U34, U39, U42, U47, U49, U64, U128};

/// Implementation of the MGF1 XOF
Expand Down Expand Up @@ -44,6 +45,36 @@ pub struct Sha2L1<N, M> {
_m: core::marker::PhantomData<M>,
}

/// `Digest` implementation [`Sha2L1`] and [`Sha2L35`].
pub struct Sha2Digest<H: EagerHash>(Inner<H>);

enum Inner<H: EagerHash> {
Hmac(Hmac<H>),
Hash(H),
}

impl<H: EagerHash> Update for Sha2Digest<H> {
fn update(&mut self, data: &[u8]) {
match &mut self.0 {
Inner::Hmac(hmac) => Mac::update(hmac, data),
Inner::Hash(hash) => Digest::update(hash, data),
}
}
}

impl<H: EagerHash<Core: OutputSizeUser<OutputSize = H::OutputSize>>> Sha2Digest<H> {
fn finalize(self) -> Output<H> {
match self.0 {
Inner::Hmac(hmac) => hmac.finalize().into_bytes(),
Inner::Hash(hash) => hash.finalize(),
}
}
}

impl<N: ArraySize, M: ArraySize> HashDigest for Sha2L1<N, M> {
type Digest = Sha2Digest<Sha256>;
}

impl<N: ArraySize, M: ArraySize> HashSuite for Sha2L1<N, M>
where
N: core::ops::Add<N>,
Expand All @@ -61,35 +92,31 @@ where
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::N> {
let mut mac = Hmac::<Sha256>::new_from_slice(sk_prf.as_ref()).unwrap();
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::N>> {
let mut mac = Sha2Digest(Inner::Hmac(
Hmac::<Sha256>::new_from_slice(sk_prf.as_ref()).unwrap(),
));
mac.update(opt_rand.as_slice());
msg.iter()
.copied()
.flatten()
.for_each(|msg_part| mac.update(msg_part.as_ref()));
let result = mac.finalize().into_bytes();
Array::clone_from_slice(&result[..Self::N::USIZE])
msg(&mut mac)?;
let result = mac.finalize();
Ok(Array::clone_from_slice(&result[..Self::N::USIZE]))
}

fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::M> {
let mut h = Sha256::new();
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::M>> {
let mut h = Sha2Digest(Inner::Hash(Sha256::new()));
h.update(rand);
h.update(pk_seed);
h.update(pk_seed.as_ref());
h.update(pk_root);
msg.iter()
.copied()
.flatten()
.for_each(|msg_part| h.update(msg_part.as_ref()));
msg(&mut h)?;
let result = Array(h.finalize().into());
let seed = rand.clone().concat(pk_seed.0.clone()).concat(result);
mgf1::<Sha256, Self::M>(&seed)
Ok(mgf1::<Sha256, Self::M>(&seed))
}

fn prf_sk(
Expand Down Expand Up @@ -117,7 +144,8 @@ where
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed());
m.iter().for_each(|x| sha.update(x.as_slice()));
m.iter()
.for_each(|x| Update::update(&mut sha, x.as_slice()));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hitting the same problem as usual. This time really unclear how to avoid it without loosing access to all the QoL methods of Digest.

let hash = sha.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
Expand Down Expand Up @@ -210,6 +238,10 @@ pub struct Sha2L35<N, M> {
_m: core::marker::PhantomData<M>,
}

impl<N: ArraySize, M: ArraySize> HashDigest for Sha2L35<N, M> {
type Digest = Sha2Digest<Sha512>;
}

impl<N: ArraySize, M: ArraySize> HashSuite for Sha2L35<N, M>
where
N: core::ops::Add<N>,
Expand All @@ -229,35 +261,31 @@ where
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::N> {
let mut mac = Hmac::<Sha512>::new_from_slice(sk_prf.as_ref()).unwrap();
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::N>> {
let mut mac = Sha2Digest(Inner::Hmac(
Hmac::<Sha512>::new_from_slice(sk_prf.as_ref()).unwrap(),
));
mac.update(opt_rand.as_slice());
msg.iter()
.copied()
.flatten()
.for_each(|msg_part| mac.update(msg_part.as_ref()));
let result = mac.finalize().into_bytes();
Array::clone_from_slice(&result[..Self::N::USIZE])
msg(&mut mac)?;
let result = mac.finalize();
Ok(Array::clone_from_slice(&result[..Self::N::USIZE]))
}

fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::M> {
let mut h = Sha512::new();
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::M>> {
let mut h = Sha2Digest(Inner::Hash(Sha512::new()));
h.update(rand);
h.update(pk_seed);
h.update(pk_seed.as_ref());
h.update(pk_root);
msg.iter()
.copied()
.flatten()
.for_each(|msg_part| h.update(msg_part.as_ref()));
msg(&mut h)?;
let result = Array(h.finalize().into());
let seed = rand.clone().concat(pk_seed.0.clone()).concat(result);
mgf1::<Sha512, Self::M>(&seed)
Ok(mgf1::<Sha512, Self::M>(&seed))
}

fn prf_sk(
Expand Down Expand Up @@ -285,7 +313,8 @@ where
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed());
m.iter().for_each(|x| sha.update(x.as_slice()));
m.iter()
.for_each(|x| Update::update(&mut sha, x.as_slice()));
let hash = sha.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
Expand Down
35 changes: 19 additions & 16 deletions slh-dsa/src/hashes/shake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::fmt::Debug;

use crate::address::Address;
use crate::fors::ForsParams;
use crate::hashes::HashSuite;
use crate::hashes::{HashDigest, HashSuite};
use crate::hypertree::HypertreeParams;
use crate::wots::WotsParams;
use crate::xmss::XmssParams;
Expand All @@ -13,6 +13,7 @@ use hybrid_array::typenum::consts::{U16, U30, U32};
use hybrid_array::typenum::{U24, U34, U39, U42, U47, U49};
use hybrid_array::{Array, ArraySize};
use sha3::Shake256;
use signature::Result;
use typenum::U;

/// Implementation of the component hash functions using SHAKE256
Expand All @@ -24,6 +25,10 @@ pub struct Shake<N, M> {
_m: core::marker::PhantomData<M>,
}

impl<N: ArraySize, M: ArraySize> HashDigest for Shake<N, M> {
type Digest = Shake256;
}

impl<N: ArraySize, M: ArraySize> HashSuite for Shake<N, M>
where
N: Debug + Clone + PartialEq + Eq,
Expand All @@ -35,37 +40,31 @@ where
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::N> {
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::N>> {
let mut hasher = Shake256::default();
hasher.update(sk_prf.as_ref());
hasher.update(opt_rand.as_slice());
msg.iter()
.copied()
.flatten()
.for_each(|msg_part| hasher.update(msg_part.as_ref()));
msg(&mut hasher)?;
let mut output = Array::<u8, Self::N>::default();
hasher.finalize_xof_into(&mut output);
output
Ok(output)
}

fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: &[&[impl AsRef<[u8]>]],
) -> Array<u8, Self::M> {
msg: &impl Fn(&mut Self::Digest) -> Result<()>,
) -> Result<Array<u8, Self::M>> {
let mut hasher = Shake256::default();
hasher.update(rand.as_slice());
hasher.update(pk_seed.as_ref());
hasher.update(pk_root.as_ref());
msg.iter()
.copied()
.flatten()
.for_each(|msg_part| hasher.update(msg_part.as_ref()));
msg(&mut hasher)?;
let mut output = Array::<u8, Self::M>::default();
hasher.finalize_xof_into(&mut output);
output
Ok(output)
}

fn prf_sk(
Expand Down Expand Up @@ -280,7 +279,11 @@ mod tests {

let expected = hex!("bc5c062307df0a41aeeae19ad655f7b2");

let result = H::prf_msg(&sk_prf, &opt_rand, &[&[msg]]);
let result = H::prf_msg(&sk_prf, &opt_rand, &|digest| {
digest.update(&msg);
Ok(())
})
.unwrap();

assert_eq!(result.as_slice(), expected);
}
Expand Down
Loading