diff --git a/slh-dsa/Cargo.toml b/slh-dsa/Cargo.toml index 1432ee58..0fdc6708 100644 --- a/slh-dsa/Cargo.toml +++ b/slh-dsa/Cargo.toml @@ -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" diff --git a/slh-dsa/src/hashes.rs b/slh-dsa/src/hashes.rs index 7fd00a0a..2915fdf7 100644 --- a/slh-dsa/src/hashes.rs +++ b/slh-dsa/src/hashes.rs @@ -7,15 +7,26 @@ 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; +} + /// 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; @@ -23,16 +34,16 @@ pub(crate) trait HashSuite: Sized + Clone + Debug + PartialEq + Eq { fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array; + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result>; /// Hashes a message using a given randomizer fn h_msg( rand: &Array, pk_seed: &PkSeed, pk_root: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array; + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result>; /// PRF that is used to generate the secret values in WOTS+ and FORS private keys. fn prf_sk( @@ -76,7 +87,11 @@ mod tests { let opt_rand = Array::::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); } @@ -87,7 +102,11 @@ mod tests { let pk_root = Array::::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); } diff --git a/slh-dsa/src/hashes/sha2.rs b/slh-dsa/src/hashes/sha2.rs index a828722d..211091da 100644 --- a/slh-dsa/src/hashes/sha2.rs +++ b/slh-dsa/src/hashes/sha2.rs @@ -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 @@ -44,6 +45,36 @@ pub struct Sha2L1 { _m: core::marker::PhantomData, } +/// `Digest` implementation [`Sha2L1`] and [`Sha2L35`]. +pub struct Sha2Digest(Inner); + +enum Inner { + Hmac(Hmac), + Hash(H), +} + +impl Update for Sha2Digest { + 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>> Sha2Digest { + fn finalize(self) -> Output { + match self.0 { + Inner::Hmac(hmac) => hmac.finalize().into_bytes(), + Inner::Hash(hash) => hash.finalize(), + } + } +} + +impl HashDigest for Sha2L1 { + type Digest = Sha2Digest; +} + impl HashSuite for Sha2L1 where N: core::ops::Add, @@ -61,35 +92,31 @@ where fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array { - let mut mac = Hmac::::new_from_slice(sk_prf.as_ref()).unwrap(); + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result> { + let mut mac = Sha2Digest(Inner::Hmac( + Hmac::::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, pk_seed: &PkSeed, pk_root: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array { - let mut h = Sha256::new(); + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result> { + 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::(&seed) + Ok(mgf1::(&seed)) } fn prf_sk( @@ -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())); let hash = sha.finalize(); Array::clone_from_slice(&hash[..Self::N::USIZE]) } @@ -210,6 +238,10 @@ pub struct Sha2L35 { _m: core::marker::PhantomData, } +impl HashDigest for Sha2L35 { + type Digest = Sha2Digest; +} + impl HashSuite for Sha2L35 where N: core::ops::Add, @@ -229,35 +261,31 @@ where fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array { - let mut mac = Hmac::::new_from_slice(sk_prf.as_ref()).unwrap(); + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result> { + let mut mac = Sha2Digest(Inner::Hmac( + Hmac::::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, pk_seed: &PkSeed, pk_root: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array { - let mut h = Sha512::new(); + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result> { + 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::(&seed) + Ok(mgf1::(&seed)) } fn prf_sk( @@ -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]) } diff --git a/slh-dsa/src/hashes/shake.rs b/slh-dsa/src/hashes/shake.rs index 12c176b7..6b4b3a6c 100644 --- a/slh-dsa/src/hashes/shake.rs +++ b/slh-dsa/src/hashes/shake.rs @@ -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; @@ -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 @@ -24,6 +25,10 @@ pub struct Shake { _m: core::marker::PhantomData, } +impl HashDigest for Shake { + type Digest = Shake256; +} + impl HashSuite for Shake where N: Debug + Clone + PartialEq + Eq, @@ -35,37 +40,31 @@ where fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array { + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result> { 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::::default(); hasher.finalize_xof_into(&mut output); - output + Ok(output) } fn h_msg( rand: &Array, pk_seed: &PkSeed, pk_root: &Array, - msg: &[&[impl AsRef<[u8]>]], - ) -> Array { + msg: &impl Fn(&mut Self::Digest) -> Result<()>, + ) -> Result> { 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::::default(); hasher.finalize_xof_into(&mut output); - output + Ok(output) } fn prf_sk( @@ -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); } diff --git a/slh-dsa/src/signing_key.rs b/slh-dsa/src/signing_key.rs index dd492f60..2f0ba191 100644 --- a/slh-dsa/src/signing_key.rs +++ b/slh-dsa/src/signing_key.rs @@ -4,9 +4,11 @@ use crate::util::split_digest; use crate::verifying_key::VerifyingKey; use crate::{ParameterSet, PkSeed, Sha2L1, Sha2L35, Shake, VerifyingKeyLen}; use ::signature::{ - Error, KeypairRef, MultipartSigner, RandomizedMultipartSigner, RandomizedSigner, Signer, + DigestSigner, Error, KeypairRef, MultipartSigner, RandomizedDigestSigner, + RandomizedMultipartSigner, RandomizedSigner, Signer, rand_core::{CryptoRng, TryCryptoRng}, }; +use digest::Update; use hybrid_array::{Array, ArraySize}; use pkcs8::{ der::AnyRef, @@ -133,10 +135,24 @@ impl SigningKey

{ /// Published for KAT validation purposes but not intended for general use. /// opt_rand must be a P::N length slice, panics otherwise. pub fn slh_sign_internal(&self, msg: &[&[u8]], opt_rand: Option<&[u8]>) -> Signature

{ - self.raw_slh_sign_internal(&[msg], opt_rand) + self.raw_slh_sign_internal( + |digest| { + for msg in msg { + digest.update(msg); + } + + Ok(()) + }, + opt_rand, + ) + .unwrap() } - fn raw_slh_sign_internal(&self, msg: &[&[&[u8]]], opt_rand: Option<&[u8]>) -> Signature

{ + fn raw_slh_sign_internal( + &self, + msg: impl Fn(&mut P::Digest) -> Result<(), Error>, + opt_rand: Option<&[u8]>, + ) -> Result, Error> { let rand = opt_rand .unwrap_or(&self.verifying_key.pk_seed.0) .try_into() @@ -145,9 +161,9 @@ impl SigningKey

{ let sk_seed = &self.sk_seed; let pk_seed = &self.verifying_key.pk_seed; - let randomizer = P::prf_msg(&self.sk_prf, rand, msg); + let randomizer = P::prf_msg(&self.sk_prf, rand, &msg)?; - let digest = P::h_msg(&randomizer, pk_seed, &self.verifying_key.pk_root, msg); + let digest = P::h_msg(&randomizer, pk_seed, &self.verifying_key.pk_root, &msg)?; let (md, idx_tree, idx_leaf) = split_digest::

(&digest); let adrs = ForsTree::new(idx_tree, idx_leaf); let fors_sig = P::fors_sign(md, sk_seed, pk_seed, &adrs); @@ -155,11 +171,11 @@ impl SigningKey

{ let fors_pk = P::fors_pk_from_sig(&fors_sig, md, pk_seed, &adrs); let ht_sig = P::ht_sign(&fors_pk, sk_seed, pk_seed, idx_tree, idx_leaf); - Signature { + Ok(Signature { randomizer, fors_sig, ht_sig, - } + }) } /// Implements [slh-sign] as defined in FIPS-205, using a context string. @@ -172,20 +188,34 @@ impl SigningKey

{ ctx: &[u8], opt_rand: Option<&[u8]>, ) -> Result, Error> { - self.raw_try_sign_with_context(&[msg], ctx, opt_rand) + self.raw_try_sign_with_context( + |digest| { + digest.update(msg); + Ok(()) + }, + ctx, + opt_rand, + ) } fn raw_try_sign_with_context( &self, - msg: &[&[u8]], + msg: impl Fn(&mut P::Digest) -> Result<(), Error>, ctx: &[u8], opt_rand: Option<&[u8]>, ) -> Result, Error> { let ctx_len = u8::try_from(ctx.len()).map_err(|_| Error::new())?; let ctx_len_bytes = ctx_len.to_be_bytes(); - let ctx_msg = [&[&[0], &ctx_len_bytes, ctx], msg]; - Ok(self.raw_slh_sign_internal(&ctx_msg, opt_rand)) + self.raw_slh_sign_internal( + |digest| { + digest.update(&[0]); + digest.update(&ctx_len_bytes); + digest.update(ctx); + msg(digest) + }, + opt_rand, + ) } /// Serialize the signing key to a new stack-allocated array @@ -237,7 +267,26 @@ impl Signer> for SigningKey

{ impl MultipartSigner> for SigningKey

{ fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { - self.raw_try_sign_with_context(msg, &[], None) + self.raw_try_sign_with_context( + |digest| { + for msg in msg { + digest.update(msg); + } + + Ok(()) + }, + &[], + None, + ) + } +} + +impl DigestSigner> for SigningKey

{ + fn try_sign_digest Result<(), Error>>( + &self, + f: F, + ) -> Result, Error> { + self.raw_try_sign_with_context(f, &[], None) } } @@ -260,7 +309,33 @@ impl RandomizedMultipartSigner> for SigningKey

let mut randomizer = Array::::default(); rng.try_fill_bytes(randomizer.as_mut_slice()) .map_err(|_| signature::Error::new())?; - self.raw_try_sign_with_context(msg, &[], Some(&randomizer)) + self.raw_try_sign_with_context( + |digest| { + for msg in msg { + digest.update(msg); + } + + Ok(()) + }, + &[], + Some(&randomizer), + ) + } +} + +impl RandomizedDigestSigner> for SigningKey

{ + fn try_sign_digest_with_rng< + R: TryCryptoRng + ?Sized, + F: Fn(&mut P::Digest) -> Result<(), Error>, + >( + &self, + rng: &mut R, + f: F, + ) -> Result, Error> { + let mut randomizer = Array::::default(); + rng.try_fill_bytes(randomizer.as_mut_slice()) + .map_err(|_| signature::Error::new())?; + self.raw_try_sign_with_context(f, &[], Some(&randomizer)) } } diff --git a/slh-dsa/src/verifying_key.rs b/slh-dsa/src/verifying_key.rs index d5931563..1477b085 100644 --- a/slh-dsa/src/verifying_key.rs +++ b/slh-dsa/src/verifying_key.rs @@ -5,7 +5,8 @@ use crate::Shake; use crate::address::ForsTree; use crate::signature_encoding::Signature; use crate::util::split_digest; -use ::signature::{Error, MultipartVerifier, Verifier}; +use ::signature::{DigestVerifier, Error, MultipartVerifier, Verifier}; +use digest::Update; use hybrid_array::{Array, ArraySize}; use pkcs8::{der, spki}; use rand_core::CryptoRng; @@ -60,12 +61,21 @@ impl VerifyingKey

{ msg: &[&[u8]], signature: &Signature

, ) -> Result<(), Error> { - self.raw_slh_verify_internal(&[msg], signature) + self.raw_slh_verify_internal( + |digest| { + for msg in msg { + digest.update(msg); + } + + Ok(()) + }, + signature, + ) } fn raw_slh_verify_internal( &self, - msg: &[&[&[u8]]], + msg: impl Fn(&mut P::Digest) -> Result<(), Error>, signature: &Signature

, ) -> Result<(), Error> { let pk_seed = &self.pk_seed; @@ -73,7 +83,7 @@ impl VerifyingKey

{ let fors_sig = &signature.fors_sig; let ht_sig = &signature.ht_sig; - let digest = P::h_msg(randomizer, pk_seed, &self.pk_root, msg); + let digest = P::h_msg(randomizer, pk_seed, &self.pk_root, &msg)?; let (md, idx_tree, idx_leaf) = split_digest::

(&digest); let adrs = ForsTree::new(idx_tree, idx_leaf); @@ -93,20 +103,34 @@ impl VerifyingKey

{ ctx: &[u8], signature: &Signature

, ) -> Result<(), Error> { - self.raw_try_verify_with_context(&[msg], ctx, signature) + self.raw_try_verify_with_context( + |digest| { + digest.update(msg); + Ok(()) + }, + ctx, + signature, + ) } fn raw_try_verify_with_context( &self, - msg: &[&[u8]], + msg: impl Fn(&mut P::Digest) -> Result<(), Error>, ctx: &[u8], signature: &Signature

, ) -> Result<(), Error> { let ctx_len = u8::try_from(ctx.len()).map_err(|_| Error::new())?; let ctx_len_bytes = ctx_len.to_be_bytes(); - let ctx_msg = [&[&[0], &ctx_len_bytes, ctx], msg]; - self.raw_slh_verify_internal(&ctx_msg, signature) // TODO - context processing + self.raw_slh_verify_internal( + |digest| { + digest.update(&[0]); + digest.update(&ctx_len_bytes); + digest.update(ctx); + msg(digest) + }, + signature, + ) // TODO - context processing } /// Serialize the verifying key to a new stack-allocated array @@ -174,7 +198,27 @@ impl Verifier> for VerifyingKey

{ impl MultipartVerifier> for VerifyingKey

{ fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature

) -> Result<(), Error> { - self.raw_try_verify_with_context(msg, &[], signature) // TODO - context processing + self.raw_try_verify_with_context( + |digest| { + for msg in msg { + digest.update(msg); + } + + Ok(()) + }, + &[], + signature, + ) // TODO - context processing + } +} + +impl DigestVerifier> for VerifyingKey

{ + fn verify_digest Result<(), Error>>( + &self, + f: F, + signature: &Signature

, + ) -> Result<(), Error> { + self.raw_try_verify_with_context(f, &[], signature) } }