From 5f92081da04df4be2989c0813daa6fe95eeb627f Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 1 Jun 2025 20:51:03 +0200 Subject: [PATCH 1/6] Implement `MultiPartSigner/Verifier` for ML-DSA and SLH-DSA --- Cargo.lock | 3 +- Cargo.toml | 3 ++ ml-dsa/src/lib.rs | 65 ++++++++++++++++++++++++++++++------ slh-dsa/src/hashes.rs | 8 ++--- slh-dsa/src/hashes/sha2.rs | 22 ++++++++---- slh-dsa/src/hashes/shake.rs | 10 ++++-- slh-dsa/src/signing_key.rs | 39 +++++++++++++++++++--- slh-dsa/src/verifying_key.rs | 31 ++++++++++++++--- 8 files changed, 147 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a9ad0f7..ace98ea3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,8 +1139,7 @@ dependencies = [ [[package]] name = "signature" version = "3.0.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ae074ff622614874804868b07d9cb786223082c9fe726a6653608f32f37b02" +source = "git+https://github.com/RustCrypto/traits?rev=ce4a05bc4266dd1d4c7c713fb7f9c211c76375ec#ce4a05bc4266dd1d4c7c713fb7f9c211c76375ec" dependencies = [ "digest", "rand_core 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index 57f708b7..a7a824b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,6 @@ lms-signature = { path = "./lms" } ml-dsa = { path = "./ml-dsa" } rfc6979 = { path = "./rfc6979" } slh-dsa = { path = "./slh-dsa" } + +# https://github.com/RustCrypto/traits/pull/1880 +signature = { git = "https://github.com/RustCrypto/traits", rev = "ce4a05bc4266dd1d4c7c713fb7f9c211c76375ec" } diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index a3ec3b7f..1dd7bb1e 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -89,7 +89,7 @@ use core::fmt; pub use crate::param::{EncodedSignature, EncodedSigningKey, EncodedVerifyingKey, MlDsaParams}; pub use crate::util::B32; -pub use signature::{self, Error}; +pub use signature::{self, Error, MultiPartSigner, MultiPartVerifier}; /// An ML-DSA signature #[derive(Clone, PartialEq, Debug)] @@ -168,10 +168,10 @@ where // This method takes a slice of slices so that we can accommodate the varying calculations (direct // for test vectors, 0... for sign/sign_deterministic, 1... for the pre-hashed version) without // having to allocate memory for components. -fn message_representative(tr: &[u8], Mp: &[&[u8]]) -> B64 { +fn message_representative(tr: &[u8], Mp: &[&[&[u8]]]) -> B64 { let mut h = H::default().absorb(tr); - for m in Mp { + for m in Mp.iter().copied().flatten() { h = h.absorb(m); } @@ -245,7 +245,15 @@ where /// only supports signing with an empty context string. impl signature::Signer> for KeyPair

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.signing_key.sign_deterministic(msg, &[]) + self.try_multi_part_sign(&[msg]) + } +} + +/// The `Signer` implementation for `KeyPair` uses the optional deterministic variant of ML-DSA, and +/// only supports signing with an empty context string. +impl MultiPartSigner> for KeyPair

{ + fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result, Error> { + self.signing_key.raw_sign_deterministic(msg, &[]) } } @@ -350,6 +358,13 @@ impl SigningKey

{ // Algorithm 7 ML-DSA.Sign_internal // TODO(RLB) Only expose based on a feature. Tests need access, but normal code shouldn't. pub fn sign_internal(&self, Mp: &[&[u8]], rnd: &B32) -> Signature

+ where + P: MlDsaParams, + { + self.raw_sign_internal(&[Mp], rnd) + } + + fn raw_sign_internal(&self, Mp: &[&[&[u8]]], rnd: &B32) -> Signature

where P: MlDsaParams, { @@ -440,13 +455,17 @@ impl SigningKey

{ /// This method will return an opaque error if the context string is more than 255 bytes long. // Algorithm 2 ML-DSA.Sign (optional deterministic variant) pub fn sign_deterministic(&self, M: &[u8], ctx: &[u8]) -> Result, Error> { + self.raw_sign_deterministic(&[M], ctx) + } + + fn raw_sign_deterministic(&self, M: &[&[u8]], ctx: &[u8]) -> Result, Error> { if ctx.len() > 255 { return Err(Error::new()); } let rnd = B32::default(); - let Mp = &[&[0], &[Truncate::truncate(ctx.len())], ctx, M]; - Ok(self.sign_internal(Mp, &rnd)) + let Mp = &[&[&[0], &[Truncate::truncate(ctx.len())], ctx], M]; + Ok(self.raw_sign_internal(Mp, &rnd)) } /// Encode the key in a fixed-size byte array. @@ -492,7 +511,16 @@ impl SigningKey

{ /// string, use the [`SigningKey::sign_deterministic`] method. impl signature::Signer> for SigningKey

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.sign_deterministic(msg, &[]) + self.try_multi_part_sign(&[msg]) + } +} + +/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and +/// only supports signing with an empty context string. If you would like to include a context +/// string, use the [`SigningKey::sign_deterministic`] method. +impl MultiPartSigner> for SigningKey

{ + fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result, Error> { + self.raw_sign_deterministic(msg, &[]) } } @@ -576,6 +604,13 @@ impl VerifyingKey

{ /// and it does not separate the context string from the rest of the message. // Algorithm 8 ML-DSA.Verify_internal pub fn verify_internal(&self, Mp: &[&[u8]], sigma: &Signature

) -> bool + where + P: MlDsaParams, + { + self.raw_verify_internal(&[Mp], sigma) + } + + fn raw_verify_internal(&self, Mp: &[&[&[u8]]], sigma: &Signature

) -> bool where P: MlDsaParams, { @@ -605,12 +640,16 @@ impl VerifyingKey

{ /// This algorithm reflect the ML-DSA.Verify algorithm from FIPS 204. // Algorithm 3 ML-DSA.Verify pub fn verify_with_context(&self, M: &[u8], ctx: &[u8], sigma: &Signature

) -> bool { + self.raw_verify_with_context(&[M], ctx, sigma) + } + + fn raw_verify_with_context(&self, M: &[&[u8]], ctx: &[u8], sigma: &Signature

) -> bool { if ctx.len() > 255 { return false; } - let Mp = &[&[0], &[Truncate::truncate(ctx.len())], ctx, M]; - self.verify_internal(Mp, sigma) + let Mp = &[&[&[0], &[Truncate::truncate(ctx.len())], ctx], M]; + self.raw_verify_internal(Mp, sigma) } fn encode_internal(rho: &B32, t1: &Vector) -> EncodedVerifyingKey

{ @@ -635,7 +674,13 @@ impl VerifyingKey

{ impl signature::Verifier> for VerifyingKey

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

) -> Result<(), Error> { - self.verify_with_context(msg, &[], signature) + self.multi_part_verify(&[msg], signature) + } +} + +impl MultiPartVerifier> for VerifyingKey

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

) -> Result<(), Error> { + self.raw_verify_with_context(msg, &[], signature) .then_some(()) .ok_or(Error::new()) } diff --git a/slh-dsa/src/hashes.rs b/slh-dsa/src/hashes.rs index 24546a11..7fd00a0a 100644 --- a/slh-dsa/src/hashes.rs +++ b/slh-dsa/src/hashes.rs @@ -23,7 +23,7 @@ pub(crate) trait HashSuite: Sized + Clone + Debug + PartialEq + Eq { fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array; /// Hashes a message using a given randomizer @@ -31,7 +31,7 @@ pub(crate) trait HashSuite: Sized + Clone + Debug + PartialEq + Eq { rand: &Array, pk_seed: &PkSeed, pk_root: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array; /// PRF that is used to generate the secret values in WOTS+ and FORS private keys. @@ -76,7 +76,7 @@ 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, &[&[msg]]); assert_eq!(result.as_slice(), expected); } @@ -87,7 +87,7 @@ 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, &[&[msg]]); assert_eq!(result.as_slice(), expected); } diff --git a/slh-dsa/src/hashes/sha2.rs b/slh-dsa/src/hashes/sha2.rs index d9959cde..a828722d 100644 --- a/slh-dsa/src/hashes/sha2.rs +++ b/slh-dsa/src/hashes/sha2.rs @@ -61,11 +61,13 @@ where fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array { let mut mac = 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]) @@ -75,13 +77,16 @@ where rand: &Array, pk_seed: &PkSeed, pk_root: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array { let mut h = Sha256::new(); h.update(rand); h.update(pk_seed); h.update(pk_root); - msg.iter().for_each(|msg_part| h.update(msg_part.as_ref())); + msg.iter() + .copied() + .flatten() + .for_each(|msg_part| h.update(msg_part.as_ref())); let result = Array(h.finalize().into()); let seed = rand.clone().concat(pk_seed.0.clone()).concat(result); mgf1::(&seed) @@ -224,11 +229,13 @@ where fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array { let mut mac = 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]) @@ -238,13 +245,16 @@ where rand: &Array, pk_seed: &PkSeed, pk_root: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array { let mut h = Sha512::new(); h.update(rand); h.update(pk_seed); h.update(pk_root); - msg.iter().for_each(|msg_part| h.update(msg_part.as_ref())); + msg.iter() + .copied() + .flatten() + .for_each(|msg_part| h.update(msg_part.as_ref())); let result = Array(h.finalize().into()); let seed = rand.clone().concat(pk_seed.0.clone()).concat(result); mgf1::(&seed) diff --git a/slh-dsa/src/hashes/shake.rs b/slh-dsa/src/hashes/shake.rs index 0bc59657..12c176b7 100644 --- a/slh-dsa/src/hashes/shake.rs +++ b/slh-dsa/src/hashes/shake.rs @@ -35,12 +35,14 @@ where fn prf_msg( sk_prf: &SkPrf, opt_rand: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array { 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())); let mut output = Array::::default(); hasher.finalize_xof_into(&mut output); @@ -51,13 +53,15 @@ where rand: &Array, pk_seed: &PkSeed, pk_root: &Array, - msg: &[impl AsRef<[u8]>], + msg: &[&[impl AsRef<[u8]>]], ) -> Array { 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())); let mut output = Array::::default(); hasher.finalize_xof_into(&mut output); @@ -276,7 +280,7 @@ 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, &[&[msg]]); assert_eq!(result.as_slice(), expected); } diff --git a/slh-dsa/src/signing_key.rs b/slh-dsa/src/signing_key.rs index e78a3c8e..c5e5aa1c 100644 --- a/slh-dsa/src/signing_key.rs +++ b/slh-dsa/src/signing_key.rs @@ -4,7 +4,7 @@ use crate::util::split_digest; use crate::verifying_key::VerifyingKey; use crate::{ParameterSet, PkSeed, Sha2L1, Sha2L35, Shake, VerifyingKeyLen}; use ::signature::{ - Error, KeypairRef, RandomizedSigner, Signer, + Error, KeypairRef, MultiPartSigner, RandomizedMultiPartSigner, RandomizedSigner, Signer, rand_core::{CryptoRng, TryCryptoRng}, }; use hybrid_array::{Array, ArraySize}; @@ -133,6 +133,10 @@ 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) + } + + fn raw_slh_sign_internal(&self, msg: &[&[&[u8]]], opt_rand: Option<&[u8]>) -> Signature

{ let rand = opt_rand .unwrap_or(&self.verifying_key.pk_seed.0) .try_into() @@ -167,12 +171,21 @@ impl SigningKey

{ msg: &[u8], ctx: &[u8], opt_rand: Option<&[u8]>, + ) -> Result, Error> { + self.raw_try_sign_with_context(&[msg], ctx, opt_rand) + } + + fn raw_try_sign_with_context( + &self, + msg: &[&[u8]], + 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.slh_sign_internal(&ctx_msg, opt_rand)) + let ctx_msg = [&[&[0], &ctx_len_bytes, ctx], msg]; + Ok(self.raw_slh_sign_internal(&ctx_msg, opt_rand)) } /// Serialize the signing key to a new stack-allocated array @@ -218,7 +231,13 @@ impl TryFrom<&[u8]> for SigningKey

{ impl Signer> for SigningKey

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.try_sign_with_context(msg, &[], None) + self.try_multi_part_sign(&[msg]) + } +} + +impl MultiPartSigner> for SigningKey

{ + fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result, Error> { + self.raw_try_sign_with_context(msg, &[], None) } } @@ -228,10 +247,20 @@ impl RandomizedSigner> for SigningKey

{ rng: &mut R, msg: &[u8], ) -> Result, signature::Error> { + self.try_multi_part_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultiPartSigner> for SigningKey

{ + fn try_multi_part_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> Result, Error> { let mut randomizer = Array::::default(); rng.try_fill_bytes(randomizer.as_mut_slice()) .map_err(|_| signature::Error::new())?; - self.try_sign_with_context(msg, &[], Some(&randomizer)) + self.raw_try_sign_with_context(msg, &[], Some(&randomizer)) } } diff --git a/slh-dsa/src/verifying_key.rs b/slh-dsa/src/verifying_key.rs index 1c39d5f3..659d914a 100644 --- a/slh-dsa/src/verifying_key.rs +++ b/slh-dsa/src/verifying_key.rs @@ -5,7 +5,7 @@ use crate::Shake; use crate::address::ForsTree; use crate::signature_encoding::Signature; use crate::util::split_digest; -use ::signature::{Error, Verifier}; +use ::signature::{Error, MultiPartVerifier, Verifier}; use hybrid_array::{Array, ArraySize}; use pkcs8::{der, spki}; use rand_core::CryptoRng; @@ -59,6 +59,14 @@ impl VerifyingKey

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

, + ) -> Result<(), Error> { + self.raw_slh_verify_internal(&[msg], signature) + } + + fn raw_slh_verify_internal( + &self, + msg: &[&[&[u8]]], + signature: &Signature

, ) -> Result<(), Error> { let pk_seed = &self.pk_seed; let randomizer = &signature.randomizer; @@ -84,12 +92,21 @@ impl VerifyingKey

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

, + ) -> Result<(), Error> { + self.raw_try_verify_with_context(&[msg], ctx, signature) + } + + fn raw_try_verify_with_context( + &self, + msg: &[&[u8]], + 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.slh_verify_internal(&ctx_msg, signature) // TODO - context processing + let ctx_msg = [&[&[0], &ctx_len_bytes, ctx], msg]; + self.raw_slh_verify_internal(&ctx_msg, signature) // TODO - context processing } /// Serialize the verifying key to a new stack-allocated array @@ -151,7 +168,13 @@ impl TryFrom<&[u8]> for VerifyingKey

{ impl Verifier> for VerifyingKey

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

) -> Result<(), Error> { - self.try_verify_with_context(msg, &[], signature) // TODO - context processing + self.multi_part_verify(&[msg], signature) + } +} + +impl MultiPartVerifier> for VerifyingKey

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

) -> Result<(), Error> { + self.raw_try_verify_with_context(msg, &[], signature) // TODO - context processing } } From ea896d28a4d540e52ebb0bebcafffebe73ecdd54 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 1 Jun 2025 21:21:48 +0200 Subject: [PATCH 2/6] Implement `MultiPartSigner/Verifier` for ECDSA --- ecdsa/src/recovery.rs | 18 +++++++++-- ecdsa/src/signing.rs | 73 +++++++++++++++++++++++++++++++++++++++--- ecdsa/src/verifying.rs | 65 +++++++++++++++++++++++++++++++++---- 3 files changed, 144 insertions(+), 12 deletions(-) diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index d09073ae..9275f63e 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -7,7 +7,7 @@ use { crate::{SigningKey, hazmat::sign_prehashed_rfc6979}, elliptic_curve::{FieldBytes, subtle::CtOption}, signature::{ - DigestSigner, RandomizedDigestSigner, Signer, + DigestSigner, MultiPartSigner, RandomizedDigestSigner, Signer, digest::FixedOutput, hazmat::{PrehashSigner, RandomizedPrehashSigner}, rand_core::TryCryptoRng, @@ -291,7 +291,21 @@ where SignatureSize: ArraySize, { fn try_sign(&self, msg: &[u8]) -> Result<(Signature, RecoveryId)> { - self.sign_recoverable(msg) + self.try_multi_part_sign(&[msg]) + } +} + +#[cfg(feature = "signing")] +impl MultiPartSigner<(Signature, RecoveryId)> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result<(Signature, RecoveryId)> { + let mut digest = C::Digest::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.sign_digest_recoverable(digest) } } diff --git a/ecdsa/src/signing.rs b/ecdsa/src/signing.rs index 5872dff3..88014e28 100644 --- a/ecdsa/src/signing.rs +++ b/ecdsa/src/signing.rs @@ -15,7 +15,8 @@ use elliptic_curve::{ zeroize::{Zeroize, ZeroizeOnDrop}, }; use signature::{ - DigestSigner, RandomizedDigestSigner, RandomizedSigner, Signer, + DigestSigner, MultiPartSigner, RandomizedDigestSigner, RandomizedMultiPartSigner, + RandomizedSigner, Signer, hazmat::{PrehashSigner, RandomizedPrehashSigner}, rand_core::{CryptoRng, TryCryptoRng}, }; @@ -176,7 +177,20 @@ where SignatureSize: ArraySize, { fn try_sign(&self, msg: &[u8]) -> Result> { - self.try_sign_digest(C::Digest::new_with_prefix(msg)) + self.try_multi_part_sign(&[msg]) + } +} + +impl MultiPartSigner> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + fn try_multi_part_sign(&self, msg: &[&[u8]]) -> core::result::Result, Error> { + let mut digest = C::Digest::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.try_sign_digest(digest) } } @@ -234,7 +248,25 @@ where rng: &mut R, msg: &[u8], ) -> Result> { - self.try_sign_digest_with_rng(rng, C::Digest::new_with_prefix(msg)) + self.try_multi_part_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultiPartSigner> for SigningKey +where + Self: RandomizedDigestSigner>, + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + fn try_multi_part_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> Result> { + let mut digest = C::Digest::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.try_sign_digest_with_rng(rng, digest) } } @@ -260,7 +292,21 @@ where SignatureSize: ArraySize, { fn try_sign(&self, msg: &[u8]) -> Result> { - self.try_sign_digest(C::Digest::new_with_prefix(msg)) + self.try_multi_part_sign(&[msg]) + } +} + +impl MultiPartSigner> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + C::Digest: AssociatedOid, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result> { + let mut digest = C::Digest::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.try_sign_digest(digest) } } @@ -364,6 +410,25 @@ where } } +#[cfg(feature = "der")] +impl RandomizedMultiPartSigner> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, + der::MaxSize: ArraySize, + as Add>::Output: Add + ArraySize, +{ + fn try_multi_part_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> Result> { + RandomizedMultiPartSigner::>::try_multi_part_sign_with_rng(self, rng, msg) + .map(Into::into) + } +} + // // Other trait impls // diff --git a/ecdsa/src/verifying.rs b/ecdsa/src/verifying.rs index 8ea4485c..3097feae 100644 --- a/ecdsa/src/verifying.rs +++ b/ecdsa/src/verifying.rs @@ -13,7 +13,7 @@ use elliptic_curve::{ sec1::{self, CompressedPoint, EncodedPoint, FromEncodedPoint, ToEncodedPoint}, }; use signature::{ - DigestVerifier, Verifier, + DigestVerifier, MultiPartVerifier, Verifier, digest::{Digest, FixedOutput}, hazmat::PrehashVerifier, }; @@ -178,7 +178,19 @@ where SignatureSize: ArraySize, { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<()> { - self.verify_digest(C::Digest::new_with_prefix(msg), signature) + self.multi_part_verify(&[msg], signature) + } +} + +impl MultiPartVerifier> for VerifyingKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + SignatureSize: ArraySize, +{ + fn multi_part_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<()> { + let mut digest = C::Digest::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.verify_digest(digest, signature) } } @@ -189,11 +201,38 @@ where SignatureSize: ArraySize, { fn verify(&self, msg: &[u8], sig: &SignatureWithOid) -> Result<()> { + self.multi_part_verify(&[msg], sig) + } +} + +#[cfg(feature = "sha2")] +impl MultiPartVerifier> for VerifyingKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + SignatureSize: ArraySize, +{ + fn multi_part_verify(&self, msg: &[&[u8]], sig: &SignatureWithOid) -> Result<()> { match sig.oid() { - ECDSA_SHA224_OID => self.verify_prehash(&Sha224::digest(msg), sig.signature()), - ECDSA_SHA256_OID => self.verify_prehash(&Sha256::digest(msg), sig.signature()), - ECDSA_SHA384_OID => self.verify_prehash(&Sha384::digest(msg), sig.signature()), - ECDSA_SHA512_OID => self.verify_prehash(&Sha512::digest(msg), sig.signature()), + ECDSA_SHA224_OID => { + let mut digest = Sha224::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) + } + ECDSA_SHA256_OID => { + let mut digest = Sha256::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) + } + ECDSA_SHA384_OID => { + let mut digest = Sha384::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) + } + ECDSA_SHA512_OID => { + let mut digest = Sha512::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.verify_prehash(&digest.finalize(), sig.signature()) + } _ => Err(Error::new()), } } @@ -242,6 +281,20 @@ where } } +#[cfg(feature = "der")] +impl MultiPartVerifier> for VerifyingKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + SignatureSize: ArraySize, + der::MaxSize: ArraySize, + as Add>::Output: Add + ArraySize, +{ + fn multi_part_verify(&self, msg: &[&[u8]], signature: &der::Signature) -> Result<()> { + let signature = Signature::::try_from(signature.clone())?; + MultiPartVerifier::>::multi_part_verify(self, msg, &signature) + } +} + // // Other trait impls // From 8ad0c59b5fb8b5c9c40aea3ef0faf6532a17c5a9 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 1 Jun 2025 21:28:36 +0200 Subject: [PATCH 3/6] Update to new names --- Cargo.lock | 2 +- Cargo.toml | 2 +- ecdsa/src/recovery.rs | 8 ++++---- ecdsa/src/signing.rs | 26 +++++++++++++------------- ecdsa/src/verifying.rs | 20 ++++++++++---------- ml-dsa/src/lib.rs | 20 ++++++++++---------- slh-dsa/src/signing_key.rs | 14 +++++++------- slh-dsa/src/verifying_key.rs | 8 ++++---- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ace98ea3..752328b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "signature" version = "3.0.0-rc.0" -source = "git+https://github.com/RustCrypto/traits?rev=ce4a05bc4266dd1d4c7c713fb7f9c211c76375ec#ce4a05bc4266dd1d4c7c713fb7f9c211c76375ec" +source = "git+https://github.com/RustCrypto/traits?rev=f05989fadee8bb73d2c278f40c7ba943b0582977#f05989fadee8bb73d2c278f40c7ba943b0582977" dependencies = [ "digest", "rand_core 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index a7a824b0..8cb88fa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,4 @@ rfc6979 = { path = "./rfc6979" } slh-dsa = { path = "./slh-dsa" } # https://github.com/RustCrypto/traits/pull/1880 -signature = { git = "https://github.com/RustCrypto/traits", rev = "ce4a05bc4266dd1d4c7c713fb7f9c211c76375ec" } +signature = { git = "https://github.com/RustCrypto/traits", rev = "f05989fadee8bb73d2c278f40c7ba943b0582977" } diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index 9275f63e..7836cb5f 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -7,7 +7,7 @@ use { crate::{SigningKey, hazmat::sign_prehashed_rfc6979}, elliptic_curve::{FieldBytes, subtle::CtOption}, signature::{ - DigestSigner, MultiPartSigner, RandomizedDigestSigner, Signer, + DigestSigner, MultipartSigner, RandomizedDigestSigner, Signer, digest::FixedOutput, hazmat::{PrehashSigner, RandomizedPrehashSigner}, rand_core::TryCryptoRng, @@ -291,18 +291,18 @@ where SignatureSize: ArraySize, { fn try_sign(&self, msg: &[u8]) -> Result<(Signature, RecoveryId)> { - self.try_multi_part_sign(&[msg]) + self.try_multipart_sign(&[msg]) } } #[cfg(feature = "signing")] -impl MultiPartSigner<(Signature, RecoveryId)> for SigningKey +impl MultipartSigner<(Signature, RecoveryId)> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result<(Signature, RecoveryId)> { + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<(Signature, RecoveryId)> { let mut digest = C::Digest::new(); msg.iter().for_each(|slice| digest.update(slice)); self.sign_digest_recoverable(digest) diff --git a/ecdsa/src/signing.rs b/ecdsa/src/signing.rs index 88014e28..51f978b9 100644 --- a/ecdsa/src/signing.rs +++ b/ecdsa/src/signing.rs @@ -15,7 +15,7 @@ use elliptic_curve::{ zeroize::{Zeroize, ZeroizeOnDrop}, }; use signature::{ - DigestSigner, MultiPartSigner, RandomizedDigestSigner, RandomizedMultiPartSigner, + DigestSigner, MultipartSigner, RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner, Signer, hazmat::{PrehashSigner, RandomizedPrehashSigner}, rand_core::{CryptoRng, TryCryptoRng}, @@ -177,17 +177,17 @@ where SignatureSize: ArraySize, { fn try_sign(&self, msg: &[u8]) -> Result> { - self.try_multi_part_sign(&[msg]) + self.try_multipart_sign(&[msg]) } } -impl MultiPartSigner> for SigningKey +impl MultipartSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_multi_part_sign(&self, msg: &[&[u8]]) -> core::result::Result, Error> { + fn try_multipart_sign(&self, msg: &[&[u8]]) -> core::result::Result, Error> { let mut digest = C::Digest::new(); msg.iter().for_each(|slice| digest.update(slice)); self.try_sign_digest(digest) @@ -248,18 +248,18 @@ where rng: &mut R, msg: &[u8], ) -> Result> { - self.try_multi_part_sign_with_rng(rng, &[msg]) + self.try_multipart_sign_with_rng(rng, &[msg]) } } -impl RandomizedMultiPartSigner> for SigningKey +impl RandomizedMultipartSigner> for SigningKey where Self: RandomizedDigestSigner>, C: EcdsaCurve + CurveArithmetic + DigestPrimitive, Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_multi_part_sign_with_rng( + fn try_multipart_sign_with_rng( &self, rng: &mut R, msg: &[&[u8]], @@ -292,18 +292,18 @@ where SignatureSize: ArraySize, { fn try_sign(&self, msg: &[u8]) -> Result> { - self.try_multi_part_sign(&[msg]) + self.try_multipart_sign(&[msg]) } } -impl MultiPartSigner> for SigningKey +impl MultipartSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, C::Digest: AssociatedOid, Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result> { + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result> { let mut digest = C::Digest::new(); msg.iter().for_each(|slice| digest.update(slice)); self.try_sign_digest(digest) @@ -411,7 +411,7 @@ where } #[cfg(feature = "der")] -impl RandomizedMultiPartSigner> for SigningKey +impl RandomizedMultipartSigner> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, Scalar: Invert>>, @@ -419,12 +419,12 @@ where der::MaxSize: ArraySize, as Add>::Output: Add + ArraySize, { - fn try_multi_part_sign_with_rng( + fn try_multipart_sign_with_rng( &self, rng: &mut R, msg: &[&[u8]], ) -> Result> { - RandomizedMultiPartSigner::>::try_multi_part_sign_with_rng(self, rng, msg) + RandomizedMultipartSigner::>::try_multipart_sign_with_rng(self, rng, msg) .map(Into::into) } } diff --git a/ecdsa/src/verifying.rs b/ecdsa/src/verifying.rs index 3097feae..cf3cc03f 100644 --- a/ecdsa/src/verifying.rs +++ b/ecdsa/src/verifying.rs @@ -13,7 +13,7 @@ use elliptic_curve::{ sec1::{self, CompressedPoint, EncodedPoint, FromEncodedPoint, ToEncodedPoint}, }; use signature::{ - DigestVerifier, MultiPartVerifier, Verifier, + DigestVerifier, MultipartVerifier, Verifier, digest::{Digest, FixedOutput}, hazmat::PrehashVerifier, }; @@ -178,16 +178,16 @@ where SignatureSize: ArraySize, { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<()> { - self.multi_part_verify(&[msg], signature) + self.multipart_verify(&[msg], signature) } } -impl MultiPartVerifier> for VerifyingKey +impl MultipartVerifier> for VerifyingKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, SignatureSize: ArraySize, { - fn multi_part_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<()> { + fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<()> { let mut digest = C::Digest::new(); msg.iter().for_each(|slice| digest.update(slice)); self.verify_digest(digest, signature) @@ -201,17 +201,17 @@ where SignatureSize: ArraySize, { fn verify(&self, msg: &[u8], sig: &SignatureWithOid) -> Result<()> { - self.multi_part_verify(&[msg], sig) + self.multipart_verify(&[msg], sig) } } #[cfg(feature = "sha2")] -impl MultiPartVerifier> for VerifyingKey +impl MultipartVerifier> for VerifyingKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, SignatureSize: ArraySize, { - fn multi_part_verify(&self, msg: &[&[u8]], sig: &SignatureWithOid) -> Result<()> { + fn multipart_verify(&self, msg: &[&[u8]], sig: &SignatureWithOid) -> Result<()> { match sig.oid() { ECDSA_SHA224_OID => { let mut digest = Sha224::new(); @@ -282,16 +282,16 @@ where } #[cfg(feature = "der")] -impl MultiPartVerifier> for VerifyingKey +impl MultipartVerifier> for VerifyingKey where C: EcdsaCurve + CurveArithmetic + DigestPrimitive, SignatureSize: ArraySize, der::MaxSize: ArraySize, as Add>::Output: Add + ArraySize, { - fn multi_part_verify(&self, msg: &[&[u8]], signature: &der::Signature) -> Result<()> { + fn multipart_verify(&self, msg: &[&[u8]], signature: &der::Signature) -> Result<()> { let signature = Signature::::try_from(signature.clone())?; - MultiPartVerifier::>::multi_part_verify(self, msg, &signature) + MultipartVerifier::>::multipart_verify(self, msg, &signature) } } diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index 1dd7bb1e..dfa7e05b 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -89,7 +89,7 @@ use core::fmt; pub use crate::param::{EncodedSignature, EncodedSigningKey, EncodedVerifyingKey, MlDsaParams}; pub use crate::util::B32; -pub use signature::{self, Error, MultiPartSigner, MultiPartVerifier}; +pub use signature::{self, Error, MultipartSigner, MultipartVerifier}; /// An ML-DSA signature #[derive(Clone, PartialEq, Debug)] @@ -245,14 +245,14 @@ where /// only supports signing with an empty context string. impl signature::Signer> for KeyPair

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.try_multi_part_sign(&[msg]) + self.try_multipart_sign(&[msg]) } } /// The `Signer` implementation for `KeyPair` uses the optional deterministic variant of ML-DSA, and /// only supports signing with an empty context string. -impl MultiPartSigner> for KeyPair

{ - fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result, Error> { +impl MultipartSigner> for KeyPair

{ + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { self.signing_key.raw_sign_deterministic(msg, &[]) } } @@ -511,15 +511,15 @@ impl SigningKey

{ /// string, use the [`SigningKey::sign_deterministic`] method. impl signature::Signer> for SigningKey

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.try_multi_part_sign(&[msg]) + self.try_multipart_sign(&[msg]) } } /// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and /// only supports signing with an empty context string. If you would like to include a context /// string, use the [`SigningKey::sign_deterministic`] method. -impl MultiPartSigner> for SigningKey

{ - fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result, Error> { +impl MultipartSigner> for SigningKey

{ + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { self.raw_sign_deterministic(msg, &[]) } } @@ -674,12 +674,12 @@ impl VerifyingKey

{ impl signature::Verifier> for VerifyingKey

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

) -> Result<(), Error> { - self.multi_part_verify(&[msg], signature) + self.multipart_verify(&[msg], signature) } } -impl MultiPartVerifier> for VerifyingKey

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

) -> Result<(), Error> { +impl MultipartVerifier> for VerifyingKey

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

) -> Result<(), Error> { self.raw_verify_with_context(msg, &[], signature) .then_some(()) .ok_or(Error::new()) diff --git a/slh-dsa/src/signing_key.rs b/slh-dsa/src/signing_key.rs index c5e5aa1c..dd492f60 100644 --- a/slh-dsa/src/signing_key.rs +++ b/slh-dsa/src/signing_key.rs @@ -4,7 +4,7 @@ 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, + Error, KeypairRef, MultipartSigner, RandomizedMultipartSigner, RandomizedSigner, Signer, rand_core::{CryptoRng, TryCryptoRng}, }; use hybrid_array::{Array, ArraySize}; @@ -231,12 +231,12 @@ impl TryFrom<&[u8]> for SigningKey

{ impl Signer> for SigningKey

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.try_multi_part_sign(&[msg]) + self.try_multipart_sign(&[msg]) } } -impl MultiPartSigner> for SigningKey

{ - fn try_multi_part_sign(&self, msg: &[&[u8]]) -> Result, Error> { +impl MultipartSigner> for SigningKey

{ + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { self.raw_try_sign_with_context(msg, &[], None) } } @@ -247,12 +247,12 @@ impl RandomizedSigner> for SigningKey

{ rng: &mut R, msg: &[u8], ) -> Result, signature::Error> { - self.try_multi_part_sign_with_rng(rng, &[msg]) + self.try_multipart_sign_with_rng(rng, &[msg]) } } -impl RandomizedMultiPartSigner> for SigningKey

{ - fn try_multi_part_sign_with_rng( +impl RandomizedMultipartSigner> for SigningKey

{ + fn try_multipart_sign_with_rng( &self, rng: &mut R, msg: &[&[u8]], diff --git a/slh-dsa/src/verifying_key.rs b/slh-dsa/src/verifying_key.rs index 659d914a..b5ae1528 100644 --- a/slh-dsa/src/verifying_key.rs +++ b/slh-dsa/src/verifying_key.rs @@ -5,7 +5,7 @@ use crate::Shake; use crate::address::ForsTree; use crate::signature_encoding::Signature; use crate::util::split_digest; -use ::signature::{Error, MultiPartVerifier, Verifier}; +use ::signature::{Error, MultipartVerifier, Verifier}; use hybrid_array::{Array, ArraySize}; use pkcs8::{der, spki}; use rand_core::CryptoRng; @@ -168,12 +168,12 @@ impl TryFrom<&[u8]> for VerifyingKey

{ impl Verifier> for VerifyingKey

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

) -> Result<(), Error> { - self.multi_part_verify(&[msg], signature) + self.multipart_verify(&[msg], signature) } } -impl MultiPartVerifier> for VerifyingKey

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

) -> Result<(), Error> { +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 } } From 39477a3674baef2b35a57c9568139fc2be5a2708 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 2 Jun 2025 09:34:26 +0200 Subject: [PATCH 4/6] Implement `MultiPartSigner/Verifier` for DSA and LMS --- Cargo.lock | 2 +- Cargo.toml | 2 +- dsa/src/signing_key.rs | 11 +++++++++-- dsa/src/verifying_key.rs | 16 ++++++++++++++-- lms/src/lms/private.rs | 15 +++++++++++++-- lms/src/lms/public.rs | 10 ++++++++-- lms/src/ots/private.rs | 26 ++++++++++++++++++-------- lms/src/ots/public.rs | 16 ++++++++++++++-- lms/src/ots/signature.rs | 23 ++++++++++++++++------- 9 files changed, 94 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 752328b7..5322250d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "signature" version = "3.0.0-rc.0" -source = "git+https://github.com/RustCrypto/traits?rev=f05989fadee8bb73d2c278f40c7ba943b0582977#f05989fadee8bb73d2c278f40c7ba943b0582977" +source = "git+https://github.com/RustCrypto/traits?rev=33d3c531068dd25c9ac237bf3cdd17e399e63c81#33d3c531068dd25c9ac237bf3cdd17e399e63c81" dependencies = [ "digest", "rand_core 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index 8cb88fa1..73adb5bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,4 @@ rfc6979 = { path = "./rfc6979" } slh-dsa = { path = "./slh-dsa" } # https://github.com/RustCrypto/traits/pull/1880 -signature = { git = "https://github.com/RustCrypto/traits", rev = "f05989fadee8bb73d2c278f40c7ba943b0582977" } +signature = { git = "https://github.com/RustCrypto/traits", rev = "33d3c531068dd25c9ac237bf3cdd17e399e63c81" } diff --git a/dsa/src/signing_key.rs b/dsa/src/signing_key.rs index ad63e18d..c52aa8b8 100644 --- a/dsa/src/signing_key.rs +++ b/dsa/src/signing_key.rs @@ -22,7 +22,7 @@ use pkcs8::{ #[cfg(feature = "hazmat")] use signature::rand_core::CryptoRng; use signature::{ - DigestSigner, RandomizedDigestSigner, Signer, + DigestSigner, MultipartSigner, RandomizedDigestSigner, Signer, hazmat::{PrehashSigner, RandomizedPrehashSigner}, rand_core::TryCryptoRng, }; @@ -149,7 +149,14 @@ impl ZeroizeOnDrop for SigningKey {} impl Signer for SigningKey { fn try_sign(&self, msg: &[u8]) -> Result { - let digest = sha2::Sha256::new_with_prefix(msg); + self.try_multipart_sign(&[msg]) + } +} + +impl MultipartSigner for SigningKey { + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result { + let mut digest = sha2::Sha256::new(); + msg.iter().for_each(|slice| digest.update(slice)); self.try_sign_digest(digest) } } diff --git a/dsa/src/verifying_key.rs b/dsa/src/verifying_key.rs index fe9d2c38..d8a3f082 100644 --- a/dsa/src/verifying_key.rs +++ b/dsa/src/verifying_key.rs @@ -17,7 +17,7 @@ use pkcs8::{ }, spki, }; -use signature::{DigestVerifier, Verifier, hazmat::PrehashVerifier}; +use signature::{DigestVerifier, MultipartVerifier, Verifier, hazmat::PrehashVerifier}; /// DSA public key. #[derive(Clone, Debug, PartialEq, PartialOrd)] @@ -108,7 +108,19 @@ impl VerifyingKey { impl Verifier for VerifyingKey { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - self.verify_digest(sha2::Sha256::new_with_prefix(msg), signature) + self.multipart_verify(&[msg], signature) + } +} + +impl MultipartVerifier for VerifyingKey { + fn multipart_verify( + &self, + msg: &[&[u8]], + signature: &Signature, + ) -> Result<(), signature::Error> { + let mut digest = sha2::Sha256::new(); + msg.iter().for_each(|slice| digest.update(slice)); + self.verify_digest(digest, signature) } } diff --git a/lms/src/lms/private.rs b/lms/src/lms/private.rs index ec857ddc..9fa214a2 100644 --- a/lms/src/lms/private.rs +++ b/lms/src/lms/private.rs @@ -8,7 +8,7 @@ use crate::types::{Identifier, Typecode}; use digest::{Digest, Output, OutputSizeUser}; use hybrid_array::{Array, ArraySize}; use rand_core::{CryptoRng, TryCryptoRng}; -use signature::{Error, RandomizedSignerMut}; +use signature::{Error, RandomizedMultipartSignerMut, RandomizedSignerMut}; use core::array::TryFromSliceError; use std::cmp::Ordering; @@ -109,6 +109,17 @@ impl RandomizedSignerMut> for SigningKey { &mut self, rng: &mut R, msg: &[u8], + ) -> Result, Error> { + self.try_multipart_sign_with_rng(rng, &[msg]) + } +} + +// this implements the algorithm from Appendix D in +impl RandomizedMultipartSignerMut> for SigningKey { + fn try_multipart_sign_with_rng( + &mut self, + rng: &mut R, + msg: &[&[u8]], ) -> Result, Error> { if self.q >= Mode::LEAVES { return Err(Error::from_source(LmsOutOfPrivateKeys {})); @@ -116,7 +127,7 @@ impl RandomizedSignerMut> for SigningKey { let mut ots_priv_key = OtsPrivateKey::::new_from_seed(self.q, self.id, &self.seed); - let ots_sig = ots_priv_key.try_sign_with_rng(rng, msg)?; + let ots_sig = ots_priv_key.try_multipart_sign_with_rng(rng, msg)?; let r = (1 << Mode::H) + self.q; diff --git a/lms/src/lms/public.rs b/lms/src/lms/public.rs index fdf20f00..0c1a27d7 100644 --- a/lms/src/lms/public.rs +++ b/lms/src/lms/public.rs @@ -9,7 +9,7 @@ use crate::types::Typecode; use crate::{constants::D_INTR, lms::LmsMode}; use digest::{Digest, OutputSizeUser}; use hybrid_array::{Array, ArraySize}; -use signature::{Error, Verifier}; +use signature::{Error, MultipartVerifier, Verifier}; use typenum::{Sum, U24}; //use crate::signature::Signature as Signature; @@ -57,12 +57,18 @@ impl VerifyingKey { impl Verifier> for VerifyingKey { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + self.multipart_verify(&[msg], signature) + } +} + +impl MultipartVerifier> for VerifyingKey { + fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<(), Error> { // Compute the LMS Public Key Candidate Tc from the signature, // message, identifier, pubtype, and ots_typecode, using // Algorithm 6a. let key_candidate = signature .lmots_sig - .recover_pubkey(self.id, signature.q, msg); + .raw_recover_pubkey(self.id, signature.q, msg); let mut node_num = signature.q + Mode::LEAVES; let mut tmp = Mode::Hasher::new() diff --git a/lms/src/ots/private.rs b/lms/src/ots/private.rs index 21beb511..fdc22de2 100644 --- a/lms/src/ots/private.rs +++ b/lms/src/ots/private.rs @@ -8,7 +8,7 @@ use crate::types::Identifier; use digest::{Digest, Output}; use hybrid_array::Array; use rand_core::{CryptoRng, TryCryptoRng}; -use signature::{Error, RandomizedSignerMut}; +use signature::{Error, RandomizedMultipartSignerMut, RandomizedSignerMut}; use zeroize::Zeroize; //use std::mem::MaybeUninit; @@ -100,6 +100,16 @@ impl RandomizedSignerMut> for SigningKey &mut self, rng: &mut R, msg: &[u8], + ) -> Result, Error> { + self.try_multipart_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultipartSignerMut> for SigningKey { + fn try_multipart_sign_with_rng( + &mut self, + rng: &mut R, + msg: &[&[u8]], ) -> Result, Error> { if !self.valid { return Err(Error::from_source(LmsOtsInvalidPrivateKey {})); @@ -110,13 +120,13 @@ impl RandomizedSignerMut> for SigningKey rng.try_fill_bytes(&mut c).map_err(|_| Error::new())?; // Q is the randomized message hash - let q = Mode::Hasher::new() - .chain_update(self.id) - .chain_update(self.q.to_be_bytes()) - .chain_update(D_MESG) - .chain_update(&c) - .chain_update(msg) - .finalize(); + let mut q_hasher = Mode::Hasher::new(); + q_hasher.update(self.id); + q_hasher.update(self.q.to_be_bytes()); + q_hasher.update(D_MESG); + q_hasher.update(&c); + msg.iter().for_each(|slice| q_hasher.update(slice)); + let q = q_hasher.finalize(); // Y is the signature. We iterate over the message hash and checksum expanded into Winternitz coefficients let y = Mode::expand(&q).into_iter().enumerate().map(|(i, a)| { diff --git a/lms/src/ots/public.rs b/lms/src/ots/public.rs index d17ea246..9d7ac205 100644 --- a/lms/src/ots/public.rs +++ b/lms/src/ots/public.rs @@ -9,7 +9,7 @@ use crate::types::Identifier; use digest::{Output, OutputSizeUser}; use hybrid_array::{Array, ArraySize}; -use signature::{Error, Verifier}; +use signature::{Error, MultipartVerifier, Verifier}; use std::cmp::Ordering; use std::ops::Add; use typenum::{Sum, U2, U24}; @@ -48,9 +48,21 @@ where { // this implements algorithm 4a of https://datatracker.ietf.org/doc/html/rfc8554#section-4.6 fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + self.multipart_verify(&[msg], signature) + } +} + +impl MultipartVerifier> for VerifyingKey +where + // required to concat Q and cksm(Q) + ::OutputSize: Add, + Sum<::OutputSize, U2>: ArraySize, +{ + // this implements algorithm 4a of https://datatracker.ietf.org/doc/html/rfc8554#section-4.6 + fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<(), Error> { // If the public key is not at least four bytes long, return INVALID. // We are calling this method on a valid public key so there's no worry here. - let kc = signature.recover_pubkey(self.id, self.q, msg); + let kc = signature.raw_recover_pubkey(self.id, self.q, msg); // 4. If Kc is equal to K, return VALID; otherwise, return INVALID. if self.k == kc.k { Ok(()) diff --git a/lms/src/ots/signature.rs b/lms/src/ots/signature.rs index 0e014c3c..68dd295a 100644 --- a/lms/src/ots/signature.rs +++ b/lms/src/ots/signature.rs @@ -106,16 +106,25 @@ impl Signature { /// algorithm 4b of the LMS RFC. The signature will always be valid for /// the returned public key candidate. pub fn recover_pubkey(&self, id: Identifier, q: u32, msg: &[u8]) -> VerifyingKey { + self.raw_recover_pubkey(id, q, &[msg]) + } + + pub(crate) fn raw_recover_pubkey( + &self, + id: Identifier, + q: u32, + msg: &[&[u8]], + ) -> VerifyingKey { // algorithm 4b // Q = H(I || u32str(q) || u16str(D_MESG) || C || message) - let msg_hash = Mode::Hasher::new() - .chain_update(id) - .chain_update(q.to_be_bytes()) - .chain_update(D_MESG) - .chain_update(&self.c) - .chain_update(msg) - .finalize(); + let mut msg_hasher = Mode::Hasher::new(); + msg_hasher.update(id); + msg_hasher.update(q.to_be_bytes()); + msg_hasher.update(D_MESG); + msg_hasher.update(&self.c); + msg.iter().for_each(|slice| msg_hasher.update(slice)); + let msg_hash = msg_hasher.finalize(); // first part of // Kc = H(I || u32str(q) || u16str(D_PBLC) || z[0] || z[1] || ... || z[p-1]) From 8e648780eef67c74f168fec57a406b487bbc7ca5 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 3 Jun 2025 00:09:20 +0200 Subject: [PATCH 5/6] Update to `master` --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5322250d..fe3ea686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "signature" version = "3.0.0-rc.0" -source = "git+https://github.com/RustCrypto/traits?rev=33d3c531068dd25c9ac237bf3cdd17e399e63c81#33d3c531068dd25c9ac237bf3cdd17e399e63c81" +source = "git+https://github.com/RustCrypto/traits#f73c1a23dde6847d0706bf06418f012580637dd6" dependencies = [ "digest", "rand_core 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index 73adb5bf..8cd9b3b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,4 @@ rfc6979 = { path = "./rfc6979" } slh-dsa = { path = "./slh-dsa" } # https://github.com/RustCrypto/traits/pull/1880 -signature = { git = "https://github.com/RustCrypto/traits", rev = "33d3c531068dd25c9ac237bf3cdd17e399e63c81" } +signature = { git = "https://github.com/RustCrypto/traits" } From 4ed73202122aeff61aa2862d4f95777f085a632a Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 3 Jun 2025 08:05:56 +0200 Subject: [PATCH 6/6] Bump `signature` to v3.0.0-rc.1 --- Cargo.lock | 5 +++-- Cargo.toml | 3 --- dsa/Cargo.toml | 2 +- ecdsa/Cargo.toml | 2 +- ed25519/Cargo.toml | 2 +- lms/Cargo.toml | 2 +- ml-dsa/Cargo.toml | 2 +- slh-dsa/Cargo.toml | 2 +- 8 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe3ea686..b8996cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,8 +1138,9 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.0" -source = "git+https://github.com/RustCrypto/traits#f73c1a23dde6847d0706bf06418f012580637dd6" +version = "3.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8852cecbd17ba45978bbbe43061ebe36a2ae376058c5c172e09f72888f8f7de" dependencies = [ "digest", "rand_core 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index 8cd9b3b9..57f708b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,3 @@ lms-signature = { path = "./lms" } ml-dsa = { path = "./ml-dsa" } rfc6979 = { path = "./rfc6979" } slh-dsa = { path = "./slh-dsa" } - -# https://github.com/RustCrypto/traits/pull/1880 -signature = { git = "https://github.com/RustCrypto/traits" } diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index cbbf2035..2407e494 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -22,7 +22,7 @@ crypto-primes = { version = "=0.7.0-pre.1", default-features = false } pkcs8 = { version = "0.11.0-rc.1", default-features = false, features = ["alloc"] } rfc6979 = { version = "0.5.0-rc.0" } sha2 = { version = "0.11.0-rc.0", default-features = false } -signature = { version = "3.0.0-rc.0", default-features = false, features = ["alloc", "digest", "rand_core"] } +signature = { version = "3.0.0-rc.1", default-features = false, features = ["alloc", "digest", "rand_core"] } zeroize = { version = "1", default-features = false } [dev-dependencies] diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index 8da8273c..b101259b 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -18,7 +18,7 @@ rust-version = "1.85" [dependencies] elliptic-curve = { version = "0.14.0-rc.3", default-features = false, features = ["sec1"] } -signature = { version = "3.0.0-rc.0", default-features = false, features = ["rand_core"] } +signature = { version = "3.0.0-rc.1", default-features = false, features = ["rand_core"] } zeroize = { version = "1.5", default-features = false } # optional dependencies diff --git a/ed25519/Cargo.toml b/ed25519/Cargo.toml index 5f01afe2..52027cc9 100644 --- a/ed25519/Cargo.toml +++ b/ed25519/Cargo.toml @@ -18,7 +18,7 @@ edition = "2024" rust-version = "1.85" [dependencies] -signature = { version = "3.0.0-rc.0", default-features = false } +signature = { version = "3.0.0-rc.1", default-features = false } # optional dependencies pkcs8 = { version = "0.11.0-rc.2", optional = true } diff --git a/lms/Cargo.toml b/lms/Cargo.toml index 327c2695..cea13322 100644 --- a/lms/Cargo.toml +++ b/lms/Cargo.toml @@ -18,7 +18,7 @@ rand = "0.9.0" sha2 = "0.11.0-rc.0" static_assertions = "1.1.0" rand_core = "0.9.0" -signature = { version = "3.0.0-rc.0", features = ["alloc", "digest", "rand_core"] } +signature = { version = "3.0.0-rc.1", features = ["alloc", "digest", "rand_core"] } typenum = { version = "1.17.0", features = ["const-generics"] } zeroize = "1.8.1" diff --git a/ml-dsa/Cargo.toml b/ml-dsa/Cargo.toml index 445bc58d..082540a5 100644 --- a/ml-dsa/Cargo.toml +++ b/ml-dsa/Cargo.toml @@ -36,7 +36,7 @@ hybrid-array = { version = "0.3", features = ["extra-sizes"] } num-traits = "0.2.19" rand_core = { version = "0.9", optional = true } sha3 = "0.11.0-rc.0" -signature = "3.0.0-rc.0" +signature = "3.0.0-rc.1" zeroize = { version = "1.8.1", optional = true, default-features = false } const-oid = { version = "0.10", features = ["db"], optional = true } diff --git a/slh-dsa/Cargo.toml b/slh-dsa/Cargo.toml index 26e71407..36ba7d9c 100644 --- a/slh-dsa/Cargo.toml +++ b/slh-dsa/Cargo.toml @@ -21,7 +21,7 @@ typenum = { version = "1.17.0", features = ["const-generics"] } sha3 = { version = "0.11.0-rc.0", default-features = false } zerocopy = { version = "0.7.34", features = ["derive"] } rand_core = { version = "0.9.2" } -signature = { version = "3.0.0-rc.0", features = ["rand_core"] } +signature = { version = "3.0.0-rc.1", features = ["rand_core"] } hmac = "0.13.0-prc.0" sha2 = { version = "0.11.0-rc.0", default-features = false } digest = "0.11.0-rc.0"