diff --git a/Cargo.lock b/Cargo.lock index 8a9ad0f7..b8996cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,9 +1138,9 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.0" +version = "3.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ae074ff622614874804868b07d9cb786223082c9fe726a6653608f32f37b02" +checksum = "b8852cecbd17ba45978bbbe43061ebe36a2ae376058c5c172e09f72888f8f7de" dependencies = [ "digest", "rand_core 0.9.2", 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/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/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/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index d09073ae..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, 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_multipart_sign(&[msg]) + } +} + +#[cfg(feature = "signing")] +impl MultipartSigner<(Signature, RecoveryId)> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + 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 5872dff3..51f978b9 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_multipart_sign(&[msg]) + } +} + +impl MultipartSigner> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + 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) } } @@ -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_multipart_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultipartSigner> for SigningKey +where + Self: RandomizedDigestSigner>, + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + fn try_multipart_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_multipart_sign(&[msg]) + } +} + +impl MultipartSigner> for SigningKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + C::Digest: AssociatedOid, + Scalar: Invert>>, + SignatureSize: ArraySize, +{ + 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) } } @@ -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_multipart_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> Result> { + RandomizedMultipartSigner::>::try_multipart_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..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, 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.multipart_verify(&[msg], signature) + } +} + +impl MultipartVerifier> for VerifyingKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + SignatureSize: ArraySize, +{ + 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) } } @@ -189,11 +201,38 @@ where SignatureSize: ArraySize, { fn verify(&self, msg: &[u8], sig: &SignatureWithOid) -> Result<()> { + self.multipart_verify(&[msg], sig) + } +} + +#[cfg(feature = "sha2")] +impl MultipartVerifier> for VerifyingKey +where + C: EcdsaCurve + CurveArithmetic + DigestPrimitive, + SignatureSize: ArraySize, +{ + fn multipart_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 multipart_verify(&self, msg: &[&[u8]], signature: &der::Signature) -> Result<()> { + let signature = Signature::::try_from(signature.clone())?; + MultipartVerifier::>::multipart_verify(self, msg, &signature) + } +} + // // Other trait impls // 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/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]) 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/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index a3ec3b7f..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}; +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_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_multipart_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_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_multipart_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.multipart_verify(&[msg], signature) + } +} + +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/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" 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..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, 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_multipart_sign(&[msg]) + } +} + +impl MultipartSigner> for SigningKey

{ + fn try_multipart_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_multipart_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultipartSigner> for SigningKey

{ + fn try_multipart_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..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, 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.multipart_verify(&[msg], signature) + } +} + +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 } }