From e5f9f642313bddea1d07b5387f4e7f0a8e6367e4 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 8 May 2026 09:04:40 -0600 Subject: [PATCH] ml-dsa: extract `signing` and `verifying` modules (#1339) Extracts code previously in `lib.rs` related to the following types into two modules: - `signing`: `SigningKey`, `ExpandedSigningKey` - `verifying`: `VerifyingKey` --- ml-dsa/src/lib.rs | 706 +--------------------------------------- ml-dsa/src/pkcs8.rs | 2 - ml-dsa/src/signing.rs | 543 ++++++++++++++++++++++++++++++ ml-dsa/src/verifying.rs | 179 ++++++++++ 4 files changed, 735 insertions(+), 695 deletions(-) create mode 100644 ml-dsa/src/signing.rs create mode 100644 ml-dsa/src/verifying.rs diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index 5a5e606..71ff63d 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -34,32 +34,36 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "pkcs8")] +pub mod pkcs8; + mod algebra; mod crypto; mod encode; mod hint; mod ntt; mod param; -pub mod pkcs8; mod sampling; +mod signing; +mod verifying; -pub use crate::param::{ - EncodedSignature, EncodedVerifyingKey, ExpandedSigningKeyBytes, MlDsaParams, +pub use crate::{ + param::{EncodedSignature, EncodedVerifyingKey, ExpandedSigningKeyBytes, MlDsaParams}, + signing::{ExpandedSigningKey, SigningKey}, + verifying::VerifyingKey, }; pub use signature::{self, Error}; -use crate::algebra::{AlgebraExt, Elem, NttMatrix, NttVector, Vector}; +use crate::algebra::{AlgebraExt, Vector}; use crate::crypto::H; use crate::hint::Hint; use crate::ntt::{Ntt, NttInverse}; -use crate::param::{ParameterSet, QMinus1, SamplingSize, SpecQ}; -use crate::sampling::{expand_a, expand_mask, expand_s, sample_in_ball}; +use crate::param::{ParameterSet, QMinus1, SamplingSize}; +use crate::sampling::{expand_a, expand_s}; use core::{ convert::{TryFrom, TryInto}, - fmt, ops::{Deref, DerefMut}, }; -use ctutils::{Choice, CtEq}; use hybrid_array::{ Array, typenum::{ @@ -69,16 +73,9 @@ use hybrid_array::{ }; use module_lattice::Truncate; use sha3::Shake256; -use signature::{DigestSigner, DigestVerifier, MultipartSigner, MultipartVerifier, Signer}; #[cfg(feature = "rand_core")] -use { - rand_core::{CryptoRng, TryCryptoRng}, - signature::{RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner}, -}; - -#[cfg(feature = "zeroize")] -use zeroize::{Zeroize, ZeroizeOnDrop}; +use rand_core::CryptoRng; /// A 32-byte array, defined here for brevity because it is used several times pub type B32 = Array; @@ -194,683 +191,6 @@ impl AsMut for MuBuilder { } } -/// An ML-DSA signing key initialized through a seed -#[derive(Clone)] -pub struct SigningKey { - /// The signing key of the key pair - signing_key: ExpandedSigningKey

, - - /// The seed this signing key was derived from - seed: B32, -} - -impl SigningKey

{ - /// The signing key of the key pair - pub fn signing_key(&self) -> &ExpandedSigningKey

{ - &self.signing_key - } - - /// Serialize the [`Seed`] value: 32-bytes which can be used to reconstruct the - /// [`SigningKey`]. - /// - /// # ⚠️ Warning! - /// - /// This value is key material. Please treat it with care. - #[inline] - pub fn to_seed(&self) -> Seed { - self.seed - } -} - -impl fmt::Debug for SigningKey

{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SigningKey").finish_non_exhaustive() - } -} - -impl signature::Keypair for SigningKey

{ - type VerifyingKey = VerifyingKey

; - fn verifying_key(&self) -> VerifyingKey

{ - self.signing_key.verifying_key() - } -} - -/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and -/// only supports signing with an empty context string. -impl Signer> for SigningKey

{ - fn try_sign(&self, msg: &[u8]) -> Result, Error> { - 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. -impl MultipartSigner> for SigningKey

{ - fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { - self.signing_key.raw_sign_deterministic(msg, &[]) - } -} - -/// The `DigestSigner` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA -/// with a pre-computed μ, and only supports signing with an empty context string. -impl DigestSigner> for SigningKey

{ - fn try_sign_digest Result<(), Error>>( - &self, - f: F, - ) -> Result, Error> { - self.signing_key.try_sign_digest(&f) - } -} - -impl PartialEq for SigningKey

{ - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -impl CtEq for SigningKey

{ - fn ct_eq(&self, other: &Self) -> Choice { - self.signing_key - .ct_eq(&other.signing_key) - .and(self.seed.ct_eq(&other.seed)) - } -} - -/// An ML-DSA signing key -#[derive(Clone)] -pub struct ExpandedSigningKey { - rho: B32, - K: B32, - tr: B64, - s1: Vector, - s2: Vector, - t0: Vector, - - // Derived values - s1_hat: NttVector, - s2_hat: NttVector, - t0_hat: NttVector, - A_hat: NttMatrix, -} - -impl fmt::Debug for ExpandedSigningKey

{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ExpandedSigningKey").finish_non_exhaustive() - } -} - -#[cfg(feature = "zeroize")] -impl Drop for ExpandedSigningKey

{ - fn drop(&mut self) { - self.rho.zeroize(); - self.K.zeroize(); - self.tr.zeroize(); - self.s1.zeroize(); - self.s2.zeroize(); - self.t0.zeroize(); - self.s1_hat.zeroize(); - self.s2_hat.zeroize(); - self.t0_hat.zeroize(); - } -} - -#[cfg(feature = "zeroize")] -impl ZeroizeOnDrop for ExpandedSigningKey

{} - -impl ExpandedSigningKey

{ - fn new( - rho: B32, - K: B32, - tr: B64, - s1: Vector, - s2: Vector, - t0: Vector, - A_hat: NttMatrix, - ) -> Self { - let s1_hat = s1.ntt(); - let s2_hat = s2.ntt(); - let t0_hat = t0.ntt(); - - Self { - rho, - K, - tr, - s1, - s2, - t0, - - s1_hat, - s2_hat, - t0_hat, - A_hat, - } - } - - #[inline] - fn new_expand_a( - rho: B32, - K: B32, - tr: B64, - s1: Vector, - s2: Vector, - t0: Vector, - ) -> Self { - let A_hat = expand_a(&rho); - Self::new(rho, K, tr, s1, s2, t0, A_hat) - } - - /// Deterministically generate a signing key from the specified seed. - /// - /// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204, but only returns a - /// signing key. - #[must_use] - pub fn from_seed(seed: &Seed) -> Self { - let kp = P::from_seed(seed); - kp.signing_key - } - - /// This method reflects the ML-DSA.Sign_internal algorithm from FIPS 204. It does not - /// include the domain separator that distinguishes between the normal and pre-hashed cases, - /// and it does not separate the context string from the rest of the message. - // 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, - { - let mu = MuBuilder::internal(&self.tr, Mp); - self.raw_sign_mu(&mu, rnd) - } - - fn raw_sign_mu(&self, mu: &B64, rnd: &B32) -> Signature

- where - P: MlDsaParams, - { - // Compute the private random seed - let rhopp: B64 = H::default() - .absorb(&self.K) - .absorb(rnd) - .absorb(mu) - .squeeze_new(); - - // Rejection sampling loop - for kappa in (0..u16::MAX).step_by(P::L::USIZE) { - let y = expand_mask::(&rhopp, kappa); - let w = (&self.A_hat * &y.ntt()).ntt_inverse(); - let w1 = w.high_bits::(); - - let w1_tilde = P::encode_w1(&w1); - let c_tilde = H::default() - .absorb(mu) - .absorb(&w1_tilde) - .squeeze_new::(); - let c = sample_in_ball(&c_tilde, P::TAU); - let c_hat = c.ntt(); - - let cs1 = (&c_hat * &self.s1_hat).ntt_inverse(); - let cs2 = (&c_hat * &self.s2_hat).ntt_inverse(); - - let z = &y + &cs1; - let r0 = (&w - &cs2).low_bits::(); - - if z.infinity_norm() >= P::GAMMA1_MINUS_BETA - || r0.infinity_norm() >= P::GAMMA2_MINUS_BETA - { - continue; - } - - let ct0 = (&c_hat * &self.t0_hat).ntt_inverse(); - let minus_ct0 = -&ct0; - let w_cs2_ct0 = &(&w - &cs2) + &ct0; - let h = Hint::

::new(&minus_ct0, &w_cs2_ct0); - - if ct0.infinity_norm() >= P::Gamma2::U32 || h.hamming_weight() > P::Omega::USIZE { - continue; - } - - let z = MaybeBox::new(z.mod_plus_minus::()); - return Signature { c_tilde, z, h }; - } - - unreachable!("Rejection sampling failed to find a valid signature"); - } - - /// This method reflects the randomized ML-DSA.Sign algorithm. - /// - /// # Errors - /// - /// This method will return an opaque error if the context string is more than 255 bytes long, - /// or if it fails to get enough randomness. - // Algorithm 2 ML-DSA.Sign - #[cfg(feature = "rand_core")] - pub fn sign_randomized( - &self, - M: &[u8], - ctx: &[u8], - rng: &mut R, - ) -> Result, Error> { - self.raw_sign_randomized(&[M], ctx, rng) - } - - #[cfg(feature = "rand_core")] - fn raw_sign_randomized( - &self, - Mp: &[&[u8]], - ctx: &[u8], - rng: &mut R, - ) -> Result, Error> { - if ctx.len() > 255 { - return Err(Error::new()); - } - - let mut rnd = B32::default(); - rng.try_fill_bytes(&mut rnd).map_err(|_| Error::new())?; - - let mu = MuBuilder::new(&self.tr, ctx).message(Mp); - Ok(self.raw_sign_mu(&mu, &rnd)) - } - - /// This method reflects the randomized ML-DSA.Sign algorithm with a pre-computed μ. - /// - /// # Errors - /// - /// This method can return an opaque error if it fails to get enough randomness. - // Algorithm 2 ML-DSA.Sign (optional pre-computed μ variant) - #[cfg(feature = "rand_core")] - pub fn sign_mu_randomized( - &self, - mu: &B64, - rng: &mut R, - ) -> Result, Error> { - let mut rnd = B32::default(); - rng.try_fill_bytes(&mut rnd).map_err(|_| Error::new())?; - - Ok(self.raw_sign_mu(mu, &rnd)) - } - - /// This method reflects the optional deterministic variant of the ML-DSA.Sign algorithm. - /// - /// # Errors - /// - /// 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) - } - - /// This method reflects the optional deterministic variant of the ML-DSA.Sign algorithm with a - /// pre-computed μ. - // Algorithm 2 ML-DSA.Sign (optional deterministic and pre-computed μ variant) - pub fn sign_mu_deterministic(&self, mu: &B64) -> Signature

{ - let rnd = B32::default(); - self.raw_sign_mu(mu, &rnd) - } - - fn raw_sign_deterministic(&self, Mp: &[&[u8]], ctx: &[u8]) -> Result, Error> { - if ctx.len() > 255 { - return Err(Error::new()); - } - - let mu = MuBuilder::new(&self.tr, ctx).message(Mp); - Ok(self.sign_mu_deterministic(&mu)) - } - - /// This auxiliary function derives a `VerifyingKey` from a bare - /// `ExpandedSigningKey` (even in the absence of the original seed). - /// - /// This is a utility function that is useful when importing the private key - /// from an external source which does not export the seed and does not - /// provide the precomputed public key associated with the private key - /// itself. - /// - /// `ExpandedSigningKey` implements `signature::Keypair`: this inherent method is - /// retained for convenience, so it is available for callers even when the - /// `signature::Keypair` trait is out-of-scope. - pub fn verifying_key(&self) -> VerifyingKey

{ - let kp: &dyn signature::Keypair> = self; - kp.verifying_key() - } - - /// DEPRECATED: decode the key from an appropriately sized byte array. - /// - /// Note that this form is deprecated in practice; prefer to use [`ExpandedSigningKey::from_seed`]. - /// - ///

- /// Panics - /// - /// This API does not validate expanded signing keys and can potentially panic if keys are - /// malformed or maliciously generated. - /// - /// To avoid panics, use [`ExpandedSigningKey::from_seed`] instead. - ///
- // Algorithm 25 skDecode - #[deprecated(since = "0.1.0", note = "use `ExpandedSigningKey::from_seed` instead")] - pub fn from_expanded(enc: &ExpandedSigningKeyBytes

) -> Self - where - P: MlDsaParams, - { - let (rho, K, tr, s1_enc, s2_enc, t0_enc) = P::split_sk(enc); - Self::new_expand_a( - rho.clone(), - K.clone(), - tr.clone(), - P::decode_s1(s1_enc), - P::decode_s2(s2_enc), - P::decode_t0(t0_enc), - ) - } - - /// DEPRECATED: encode the key in a fixed-size byte array. - /// - /// Note that this form is deprecated in practice; prefer to use [`SigningKey:to_seed`]. - // Algorithm 24 skEncode - #[deprecated(since = "0.1.0", note = "use `SigningKey::to_seed` instead")] - pub fn to_expanded(&self) -> ExpandedSigningKeyBytes

- where - P: MlDsaParams, - { - let s1_enc = P::encode_s1(&self.s1); - let s2_enc = P::encode_s2(&self.s2); - let t0_enc = P::encode_t0(&self.t0); - P::concat_sk( - self.rho.clone(), - self.K.clone(), - self.tr.clone(), - s1_enc, - s2_enc, - t0_enc, - ) - } -} - -/// The `Signer` implementation for `ExpandedSigningKey` 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 [`ExpandedSigningKey::sign_deterministic`] method. -impl Signer> for ExpandedSigningKey

{ - fn try_sign(&self, msg: &[u8]) -> Result, Error> { - self.try_multipart_sign(&[msg]) - } -} - -/// The `Signer` implementation for `ExpandedSigningKey` 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 [`ExpandedSigningKey::sign_deterministic`] method. -impl MultipartSigner> for ExpandedSigningKey

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

{ - fn try_sign_digest Result<(), Error>>( - &self, - f: F, - ) -> Result, Error> { - let mut mu = MuBuilder::new(&self.tr, &[]); - f(mu.as_mut())?; - let mu = mu.finish(); - - Ok(self.sign_mu_deterministic(&mu)) - } -} - -/// The [`signature::KeyPair`] implementation for `ExpandedSigningKey` allows to derive a `VerifyingKey` from -/// a bare `ExpandedSigningKey` (even in the absence of the original seed). -impl signature::Keypair for ExpandedSigningKey

{ - type VerifyingKey = VerifyingKey

; - - /// This is a utility function that is useful when importing the private key - /// from an external source which does not export the seed and does not - /// provide the precomputed public key associated with the private key - /// itself. - fn verifying_key(&self) -> Self::VerifyingKey { - let As1 = &self.A_hat * &self.s1_hat; - let t = &As1.ntt_inverse() + &self.s2; - - /* Discard t0 */ - let (t1, _) = t.power2round(); - - VerifyingKey::new(self.rho.clone(), t1, self.A_hat.clone(), None) - } -} - -/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty -/// context string. If you would like to include a context string, use the -/// [`ExpandedSigningKey::sign_randomized`] method. -#[cfg(feature = "rand_core")] -impl RandomizedSigner> for ExpandedSigningKey

{ - fn try_sign_with_rng( - &self, - rng: &mut R, - msg: &[u8], - ) -> Result, Error> { - self.try_multipart_sign_with_rng(rng, &[msg]) - } -} - -/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty -/// context string. If you would like to include a context string, use the -/// [`ExpandedSigningKey::sign_randomized`] method. -#[cfg(feature = "rand_core")] -impl RandomizedMultipartSigner> for ExpandedSigningKey

{ - fn try_multipart_sign_with_rng( - &self, - rng: &mut R, - msg: &[&[u8]], - ) -> Result, Error> { - self.raw_sign_randomized(msg, &[], rng) - } -} - -/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty -/// context string. If you would like to include a context string, use the -/// [`ExpandedSigningKey::sign_mu_randomized`] method. -#[cfg(feature = "rand_core")] -impl RandomizedDigestSigner> for ExpandedSigningKey

{ - fn try_sign_digest_with_rng< - R: TryCryptoRng + ?Sized, - F: Fn(&mut Shake256) -> Result<(), Error>, - >( - &self, - rng: &mut R, - f: F, - ) -> Result, Error> { - let mut mu = MuBuilder::new(&self.tr, &[]); - f(mu.as_mut())?; - let mu = mu.finish(); - - self.sign_mu_randomized(&mu, rng) - } -} - -impl PartialEq for ExpandedSigningKey

{ - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -impl CtEq for ExpandedSigningKey

{ - fn ct_eq(&self, other: &Self) -> Choice { - self.rho - .ct_eq(&other.rho) - .and(self.K.ct_eq(&other.K)) - .and(self.tr.ct_eq(&other.tr)) - .and(self.s1.ct_eq(&other.s1)) - .and(self.s2.ct_eq(&other.s2)) - .and(self.t0.ct_eq(&other.t0)) - } -} - -/// An ML-DSA verification key -#[derive(Clone, Debug, PartialEq)] -pub struct VerifyingKey { - rho: B32, - t1: Vector, - - // Derived values - A_hat: NttMatrix, - t1_2d_hat: NttVector, - tr: B64, -} - -impl VerifyingKey

{ - fn new( - rho: B32, - t1: Vector, - A_hat: NttMatrix, - enc: Option>, - ) -> Self { - let enc = enc.unwrap_or_else(|| Self::encode_internal(&rho, &t1)); - - let t1_2d_hat = (Elem::new(1 << 13) * &t1).ntt(); - let tr: B64 = H::default().absorb(&enc).squeeze_new(); - - Self { - rho, - t1, - A_hat, - t1_2d_hat, - tr, - } - } - - #[inline] - fn new_expand_a(rho: B32, t1: Vector, enc: Option>) -> Self { - let A_hat = expand_a(&rho); - Self::new(rho, t1, A_hat, enc) - } - - /// Computes µ according to FIPS 204 for use in ML-DSA.Sign and ML-DSA.Verify. - /// - /// # Errors - /// - /// Returns [`Error`] if the given `Mp` returns one. - pub fn compute_mu Result<(), Error>>( - &self, - Mp: F, - ctx: &[u8], - ) -> Result { - let mut mu = MuBuilder::new(&self.tr, ctx); - Mp(mu.as_mut())?; - Ok(mu.finish()) - } - - /// This algorithm reflects the ML-DSA.Verify_internal algorithm from FIPS 204. It does not - /// include the domain separator that distinguishes between the normal and pre-hashed cases, - /// 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, M: &[u8], sigma: &Signature

) -> bool - where - P: MlDsaParams, - { - let mu = MuBuilder::internal(&self.tr, &[M]); - self.raw_verify_mu(&mu, sigma) - } - - fn raw_verify_mu(&self, mu: &B64, sigma: &Signature

) -> bool - where - P: MlDsaParams, - { - // Reconstruct w - let c = sample_in_ball(&sigma.c_tilde, P::TAU); - - let z_hat = sigma.z.ntt(); - let c_hat = c.ntt(); - let Az_hat = &self.A_hat * &z_hat; - let ct1_2d_hat = &c_hat * &self.t1_2d_hat; - - let wp_approx = (&Az_hat - &ct1_2d_hat).ntt_inverse(); - let w1p = sigma.h.use_hint(&wp_approx); - - let w1p_tilde = P::encode_w1(&w1p); - let cp_tilde = H::default() - .absorb(mu) - .absorb(&w1p_tilde) - .squeeze_new::(); - - sigma.c_tilde == cp_tilde - } - - /// This algorithm reflects 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) - } - - /// This algorithm reflects the ML-DSA.Verify algorithm with a pre-computed μ from FIPS 204. - // Algorithm 3 ML-DSA.Verify (optional pre-computed μ variant) - pub fn verify_mu(&self, mu: &B64, sigma: &Signature

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

) -> bool { - if ctx.len() > 255 { - return false; - } - - let mu = MuBuilder::new(&self.tr, ctx).message(M); - self.verify_mu(&mu, sigma) - } - - fn encode_internal(rho: &B32, t1: &Vector) -> EncodedVerifyingKey

{ - let t1_enc = P::encode_t1(t1); - P::concat_vk(rho.clone(), t1_enc) - } - - /// Encode the key in a fixed-size byte array. - // Algorithm 22 pkEncode - pub fn encode(&self) -> EncodedVerifyingKey

{ - Self::encode_internal(&self.rho, &self.t1) - } - - /// Decode the key from an appropriately sized byte array. - // Algorithm 23 pkDecode - pub fn decode(enc: &EncodedVerifyingKey

) -> Self { - let (rho, t1_enc) = P::split_vk(enc); - let t1 = P::decode_t1(t1_enc); - Self::new_expand_a(rho.clone(), t1, Some(enc.clone())) - } -} - -impl core::hash::Hash for VerifyingKey

{ - fn hash(&self, state: &mut H) { - self.encode().hash(state); - } -} - -impl signature::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> { - self.raw_verify_with_context(msg, &[], signature) - .then_some(()) - .ok_or(Error::new()) - } -} - -impl DigestVerifier> for VerifyingKey

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

, - ) -> Result<(), Error> { - let mut mu = MuBuilder::new(&self.tr, &[]); - f(mu.as_mut())?; - let mu = mu.finish(); - - self.raw_verify_mu(&mu, signature) - .then_some(()) - .ok_or(Error::new()) - } -} - /// `MlDsa44` is the parameter set for security category 2. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct MlDsa44; diff --git a/ml-dsa/src/pkcs8.rs b/ml-dsa/src/pkcs8.rs index 11b1c10..6b6424d 100644 --- a/ml-dsa/src/pkcs8.rs +++ b/ml-dsa/src/pkcs8.rs @@ -1,7 +1,5 @@ //! PKCS#8 private key encoding support. -#![cfg(feature = "pkcs8")] - use crate::{ EncodedVerifyingKey, ExpandedSigningKey, KeyGen, MlDsa44, MlDsa65, MlDsa87, MlDsaParams, Signature, SigningKey, VerifyingKey, diff --git a/ml-dsa/src/signing.rs b/ml-dsa/src/signing.rs new file mode 100644 index 0000000..696e4e7 --- /dev/null +++ b/ml-dsa/src/signing.rs @@ -0,0 +1,543 @@ +//! ML-DSA `SigningKey` and `ExpandedSigningKey`. +//! +//! These types implement signature generation. + +use crate::{ + B32, B64, ExpandedSigningKeyBytes, KeyGen, MaybeBox, MlDsaParams, MuBuilder, Seed, Signature, + VerifyingKey, + algebra::{AlgebraExt, NttMatrix, NttVector, Vector}, + crypto::H, + hint::Hint, + ntt::{Ntt, NttInverse}, + param::SpecQ, + sampling::{expand_a, expand_mask, sample_in_ball}, +}; +use core::fmt; +use ctutils::{Choice, CtEq}; +use hybrid_array::typenum::Unsigned; +use sha3::Shake256; +use signature::{DigestSigner, Error, MultipartSigner, Signer}; + +#[cfg(feature = "rand_core")] +use { + rand_core::TryCryptoRng, + signature::{RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner}, +}; + +#[cfg(feature = "zeroize")] +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// An ML-DSA signing key. +/// +/// This type is initialized through a [`Seed`]. +// TODO(tarcieri): reduce field-level visibility. +#[derive(Clone)] +pub struct SigningKey { + /// The signing key of the key pair + pub(crate) signing_key: ExpandedSigningKey

, + + /// The seed this signing key was derived from + pub(crate) seed: B32, +} + +impl SigningKey

{ + /// The signing key of the key pair + pub fn signing_key(&self) -> &ExpandedSigningKey

{ + &self.signing_key + } + + /// Serialize the [`Seed`] value: 32-bytes which can be used to reconstruct the + /// [`SigningKey`]. + /// + ///

+ /// Warning + /// + /// This value is key material. Please treat it with care. + ///
+ #[inline] + pub fn to_seed(&self) -> Seed { + self.seed + } +} + +impl fmt::Debug for SigningKey

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SigningKey").finish_non_exhaustive() + } +} + +impl signature::Keypair for SigningKey

{ + type VerifyingKey = VerifyingKey

; + fn verifying_key(&self) -> VerifyingKey

{ + self.signing_key.verifying_key() + } +} + +/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and +/// only supports signing with an empty context string. +impl Signer> for SigningKey

{ + fn try_sign(&self, msg: &[u8]) -> Result, Error> { + 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. +impl MultipartSigner> for SigningKey

{ + fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { + self.signing_key.raw_sign_deterministic(msg, &[]) + } +} + +/// The `DigestSigner` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA +/// with a pre-computed μ, and only supports signing with an empty context string. +impl DigestSigner> for SigningKey

{ + fn try_sign_digest Result<(), Error>>( + &self, + f: F, + ) -> Result, Error> { + self.signing_key.try_sign_digest(&f) + } +} + +impl PartialEq for SigningKey

{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl CtEq for SigningKey

{ + fn ct_eq(&self, other: &Self) -> Choice { + self.signing_key + .ct_eq(&other.signing_key) + .and(self.seed.ct_eq(&other.seed)) + } +} + +/// An ML-DSA signing key +#[derive(Clone)] +pub struct ExpandedSigningKey { + rho: B32, + K: B32, + pub(crate) tr: B64, + s1: Vector, + s2: Vector, + t0: Vector, + + // Derived values + s1_hat: NttVector, + s2_hat: NttVector, + t0_hat: NttVector, + A_hat: NttMatrix, +} + +impl ExpandedSigningKey

{ + pub(crate) fn new( + rho: B32, + K: B32, + tr: B64, + s1: Vector, + s2: Vector, + t0: Vector, + A_hat: NttMatrix, + ) -> Self { + let s1_hat = s1.ntt(); + let s2_hat = s2.ntt(); + let t0_hat = t0.ntt(); + + Self { + rho, + K, + tr, + s1, + s2, + t0, + + s1_hat, + s2_hat, + t0_hat, + A_hat, + } + } + + #[inline] + fn new_expand_a( + rho: B32, + K: B32, + tr: B64, + s1: Vector, + s2: Vector, + t0: Vector, + ) -> Self { + let A_hat = expand_a(&rho); + Self::new(rho, K, tr, s1, s2, t0, A_hat) + } + + /// Deterministically generate a signing key from the specified seed. + /// + /// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204, but only returns a + /// signing key. + #[must_use] + pub fn from_seed(seed: &Seed) -> Self { + let kp = P::from_seed(seed); + kp.signing_key + } + + /// This method reflects the ML-DSA.Sign_internal algorithm from FIPS 204. It does not + /// include the domain separator that distinguishes between the normal and pre-hashed cases, + /// and it does not separate the context string from the rest of the message. + // 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, + { + let mu = MuBuilder::internal(&self.tr, Mp); + self.raw_sign_mu(&mu, rnd) + } + + pub(crate) fn raw_sign_mu(&self, mu: &B64, rnd: &B32) -> Signature

+ where + P: MlDsaParams, + { + // Compute the private random seed + let rhopp: B64 = H::default() + .absorb(&self.K) + .absorb(rnd) + .absorb(mu) + .squeeze_new(); + + // Rejection sampling loop + for kappa in (0..u16::MAX).step_by(P::L::USIZE) { + let y = expand_mask::(&rhopp, kappa); + let w = (&self.A_hat * &y.ntt()).ntt_inverse(); + let w1 = w.high_bits::(); + + let w1_tilde = P::encode_w1(&w1); + let c_tilde = H::default() + .absorb(mu) + .absorb(&w1_tilde) + .squeeze_new::(); + let c = sample_in_ball(&c_tilde, P::TAU); + let c_hat = c.ntt(); + + let cs1 = (&c_hat * &self.s1_hat).ntt_inverse(); + let cs2 = (&c_hat * &self.s2_hat).ntt_inverse(); + + let z = &y + &cs1; + let r0 = (&w - &cs2).low_bits::(); + + if z.infinity_norm() >= P::GAMMA1_MINUS_BETA + || r0.infinity_norm() >= P::GAMMA2_MINUS_BETA + { + continue; + } + + let ct0 = (&c_hat * &self.t0_hat).ntt_inverse(); + let minus_ct0 = -&ct0; + let w_cs2_ct0 = &(&w - &cs2) + &ct0; + let h = Hint::

::new(&minus_ct0, &w_cs2_ct0); + + if ct0.infinity_norm() >= P::Gamma2::U32 || h.hamming_weight() > P::Omega::USIZE { + continue; + } + + let z = MaybeBox::new(z.mod_plus_minus::()); + return Signature { c_tilde, z, h }; + } + + unreachable!("Rejection sampling failed to find a valid signature"); + } + + /// This method reflects the randomized ML-DSA.Sign algorithm. + /// + /// # Errors + /// + /// This method will return an opaque error if the context string is more than 255 bytes long, + /// or if it fails to get enough randomness. + // Algorithm 2 ML-DSA.Sign + #[cfg(feature = "rand_core")] + pub fn sign_randomized( + &self, + M: &[u8], + ctx: &[u8], + rng: &mut R, + ) -> Result, Error> { + self.raw_sign_randomized(&[M], ctx, rng) + } + + #[cfg(feature = "rand_core")] + fn raw_sign_randomized( + &self, + Mp: &[&[u8]], + ctx: &[u8], + rng: &mut R, + ) -> Result, Error> { + if ctx.len() > 255 { + return Err(Error::new()); + } + + let mut rnd = B32::default(); + rng.try_fill_bytes(&mut rnd).map_err(|_| Error::new())?; + + let mu = MuBuilder::new(&self.tr, ctx).message(Mp); + Ok(self.raw_sign_mu(&mu, &rnd)) + } + + /// This method reflects the randomized ML-DSA.Sign algorithm with a pre-computed μ. + /// + /// # Errors + /// + /// This method can return an opaque error if it fails to get enough randomness. + // Algorithm 2 ML-DSA.Sign (optional pre-computed μ variant) + #[cfg(feature = "rand_core")] + pub fn sign_mu_randomized( + &self, + mu: &B64, + rng: &mut R, + ) -> Result, Error> { + let mut rnd = B32::default(); + rng.try_fill_bytes(&mut rnd).map_err(|_| Error::new())?; + + Ok(self.raw_sign_mu(mu, &rnd)) + } + + /// This method reflects the optional deterministic variant of the ML-DSA.Sign algorithm. + /// + /// # Errors + /// + /// 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) + } + + /// This method reflects the optional deterministic variant of the ML-DSA.Sign algorithm with a + /// pre-computed μ. + // Algorithm 2 ML-DSA.Sign (optional deterministic and pre-computed μ variant) + pub fn sign_mu_deterministic(&self, mu: &B64) -> Signature

{ + let rnd = B32::default(); + self.raw_sign_mu(mu, &rnd) + } + + fn raw_sign_deterministic(&self, Mp: &[&[u8]], ctx: &[u8]) -> Result, Error> { + if ctx.len() > 255 { + return Err(Error::new()); + } + + let mu = MuBuilder::new(&self.tr, ctx).message(Mp); + Ok(self.sign_mu_deterministic(&mu)) + } + + /// This auxiliary function derives a `VerifyingKey` from a bare + /// `ExpandedSigningKey` (even in the absence of the original seed). + /// + /// This is a utility function that is useful when importing the private key + /// from an external source which does not export the seed and does not + /// provide the precomputed public key associated with the private key + /// itself. + /// + /// `ExpandedSigningKey` implements `signature::Keypair`: this inherent method is + /// retained for convenience, so it is available for callers even when the + /// `signature::Keypair` trait is out-of-scope. + pub fn verifying_key(&self) -> VerifyingKey

{ + let kp: &dyn signature::Keypair> = self; + kp.verifying_key() + } + + /// DEPRECATED: decode the key from an appropriately sized byte array. + /// + /// Note that this form is deprecated in practice; prefer to use [`ExpandedSigningKey::from_seed`]. + /// + ///

+ /// Panics + /// + /// This API does not validate expanded signing keys and can potentially panic if keys are + /// malformed or maliciously generated. + /// + /// To avoid panics, use [`ExpandedSigningKey::from_seed`] instead. + ///
+ // Algorithm 25 skDecode + #[deprecated(since = "0.1.0", note = "use `ExpandedSigningKey::from_seed` instead")] + pub fn from_expanded(enc: &ExpandedSigningKeyBytes

) -> Self + where + P: MlDsaParams, + { + let (rho, K, tr, s1_enc, s2_enc, t0_enc) = P::split_sk(enc); + Self::new_expand_a( + rho.clone(), + K.clone(), + tr.clone(), + P::decode_s1(s1_enc), + P::decode_s2(s2_enc), + P::decode_t0(t0_enc), + ) + } + + /// DEPRECATED: encode the key in a fixed-size byte array. + /// + /// Note that this form is deprecated in practice; prefer to use [`SigningKey:to_seed`]. + // Algorithm 24 skEncode + #[deprecated(since = "0.1.0", note = "use `SigningKey::to_seed` instead")] + pub fn to_expanded(&self) -> ExpandedSigningKeyBytes

+ where + P: MlDsaParams, + { + let s1_enc = P::encode_s1(&self.s1); + let s2_enc = P::encode_s2(&self.s2); + let t0_enc = P::encode_t0(&self.t0); + P::concat_sk( + self.rho.clone(), + self.K.clone(), + self.tr.clone(), + s1_enc, + s2_enc, + t0_enc, + ) + } +} + +/// The `Signer` implementation for `ExpandedSigningKey` 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 [`ExpandedSigningKey::sign_deterministic`] method. +impl Signer> for ExpandedSigningKey

{ + fn try_sign(&self, msg: &[u8]) -> Result, Error> { + self.try_multipart_sign(&[msg]) + } +} + +/// The `Signer` implementation for `ExpandedSigningKey` 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 [`ExpandedSigningKey::sign_deterministic`] method. +impl MultipartSigner> for ExpandedSigningKey

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

{ + fn try_sign_digest Result<(), Error>>( + &self, + f: F, + ) -> Result, Error> { + let mut mu = MuBuilder::new(&self.tr, &[]); + f(mu.as_mut())?; + let mu = mu.finish(); + + Ok(self.sign_mu_deterministic(&mu)) + } +} + +/// The [`signature::KeyPair`] implementation for `ExpandedSigningKey` allows to derive a `VerifyingKey` from +/// a bare `ExpandedSigningKey` (even in the absence of the original seed). +impl signature::Keypair for ExpandedSigningKey

{ + type VerifyingKey = VerifyingKey

; + + /// This is a utility function that is useful when importing the private key + /// from an external source which does not export the seed and does not + /// provide the precomputed public key associated with the private key + /// itself. + fn verifying_key(&self) -> Self::VerifyingKey { + let As1 = &self.A_hat * &self.s1_hat; + let t = &As1.ntt_inverse() + &self.s2; + + /* Discard t0 */ + let (t1, _) = t.power2round(); + + VerifyingKey::new(self.rho.clone(), t1, self.A_hat.clone(), None) + } +} + +/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty +/// context string. If you would like to include a context string, use the +/// [`ExpandedSigningKey::sign_randomized`] method. +#[cfg(feature = "rand_core")] +impl RandomizedSigner> for ExpandedSigningKey

{ + fn try_sign_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result, Error> { + self.try_multipart_sign_with_rng(rng, &[msg]) + } +} + +/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty +/// context string. If you would like to include a context string, use the +/// [`ExpandedSigningKey::sign_randomized`] method. +#[cfg(feature = "rand_core")] +impl RandomizedMultipartSigner> for ExpandedSigningKey

{ + fn try_multipart_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> Result, Error> { + self.raw_sign_randomized(msg, &[], rng) + } +} + +/// The `RandomizedSigner` implementation for `ExpandedSigningKey` only supports signing with an empty +/// context string. If you would like to include a context string, use the +/// [`ExpandedSigningKey::sign_mu_randomized`] method. +#[cfg(feature = "rand_core")] +impl RandomizedDigestSigner> for ExpandedSigningKey

{ + fn try_sign_digest_with_rng< + R: TryCryptoRng + ?Sized, + F: Fn(&mut Shake256) -> Result<(), Error>, + >( + &self, + rng: &mut R, + f: F, + ) -> Result, Error> { + let mut mu = MuBuilder::new(&self.tr, &[]); + f(mu.as_mut())?; + let mu = mu.finish(); + + self.sign_mu_randomized(&mu, rng) + } +} + +impl PartialEq for ExpandedSigningKey

{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl CtEq for ExpandedSigningKey

{ + fn ct_eq(&self, other: &Self) -> Choice { + self.rho + .ct_eq(&other.rho) + .and(self.K.ct_eq(&other.K)) + .and(self.tr.ct_eq(&other.tr)) + .and(self.s1.ct_eq(&other.s1)) + .and(self.s2.ct_eq(&other.s2)) + .and(self.t0.ct_eq(&other.t0)) + } +} + +impl fmt::Debug for ExpandedSigningKey

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExpandedSigningKey").finish_non_exhaustive() + } +} + +#[cfg(feature = "zeroize")] +impl Drop for ExpandedSigningKey

{ + fn drop(&mut self) { + self.rho.zeroize(); + self.K.zeroize(); + self.tr.zeroize(); + self.s1.zeroize(); + self.s2.zeroize(); + self.t0.zeroize(); + self.s1_hat.zeroize(); + self.s2_hat.zeroize(); + self.t0_hat.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for ExpandedSigningKey

{} diff --git a/ml-dsa/src/verifying.rs b/ml-dsa/src/verifying.rs new file mode 100644 index 0000000..f45cf5f --- /dev/null +++ b/ml-dsa/src/verifying.rs @@ -0,0 +1,179 @@ +//! ML-DSA signature verification. + +use crate::{ + B32, B64, EncodedVerifyingKey, MlDsaParams, MuBuilder, Signature, + algebra::{Elem, NttMatrix, NttVector, Vector}, + crypto::H, + ntt::{Ntt, NttInverse}, + param::ParameterSet, + sampling::{expand_a, sample_in_ball}, +}; +use sha3::Shake256; +use signature::{DigestVerifier, Error, MultipartVerifier}; + +/// An ML-DSA verification key. +#[derive(Clone, Debug, PartialEq)] +pub struct VerifyingKey { + rho: B32, + t1: Vector, + + // Derived values + A_hat: NttMatrix, + t1_2d_hat: NttVector, + tr: B64, +} + +impl VerifyingKey

{ + pub(crate) fn new( + rho: B32, + t1: Vector, + A_hat: NttMatrix, + enc: Option>, + ) -> Self { + let enc = enc.unwrap_or_else(|| Self::encode_internal(&rho, &t1)); + + let t1_2d_hat = (Elem::new(1 << 13) * &t1).ntt(); + let tr: B64 = H::default().absorb(&enc).squeeze_new(); + + Self { + rho, + t1, + A_hat, + t1_2d_hat, + tr, + } + } + + #[inline] + fn new_expand_a(rho: B32, t1: Vector, enc: Option>) -> Self { + let A_hat = expand_a(&rho); + Self::new(rho, t1, A_hat, enc) + } + + /// Computes µ according to FIPS 204 for use in ML-DSA.Sign and ML-DSA.Verify. + /// + /// # Errors + /// + /// Returns [`Error`] if the given `Mp` returns one. + pub fn compute_mu Result<(), Error>>( + &self, + Mp: F, + ctx: &[u8], + ) -> Result { + let mut mu = MuBuilder::new(&self.tr, ctx); + Mp(mu.as_mut())?; + Ok(mu.finish()) + } + + /// This algorithm reflects the ML-DSA.Verify_internal algorithm from FIPS 204. It does not + /// include the domain separator that distinguishes between the normal and pre-hashed cases, + /// 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, M: &[u8], sigma: &Signature

) -> bool + where + P: MlDsaParams, + { + let mu = MuBuilder::internal(&self.tr, &[M]); + self.raw_verify_mu(&mu, sigma) + } + + pub(crate) fn raw_verify_mu(&self, mu: &B64, sigma: &Signature

) -> bool + where + P: MlDsaParams, + { + // Reconstruct w + let c = sample_in_ball(&sigma.c_tilde, P::TAU); + + let z_hat = sigma.z.ntt(); + let c_hat = c.ntt(); + let Az_hat = &self.A_hat * &z_hat; + let ct1_2d_hat = &c_hat * &self.t1_2d_hat; + + let wp_approx = (&Az_hat - &ct1_2d_hat).ntt_inverse(); + let w1p = sigma.h.use_hint(&wp_approx); + + let w1p_tilde = P::encode_w1(&w1p); + let cp_tilde = H::default() + .absorb(mu) + .absorb(&w1p_tilde) + .squeeze_new::(); + + sigma.c_tilde == cp_tilde + } + + /// This algorithm reflects 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) + } + + /// This algorithm reflects the ML-DSA.Verify algorithm with a pre-computed μ from FIPS 204. + // Algorithm 3 ML-DSA.Verify (optional pre-computed μ variant) + pub fn verify_mu(&self, mu: &B64, sigma: &Signature

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

) -> bool { + if ctx.len() > 255 { + return false; + } + + let mu = MuBuilder::new(&self.tr, ctx).message(M); + self.verify_mu(&mu, sigma) + } + + pub(crate) fn encode_internal(rho: &B32, t1: &Vector) -> EncodedVerifyingKey

{ + let t1_enc = P::encode_t1(t1); + P::concat_vk(rho.clone(), t1_enc) + } + + /// Encode the key in a fixed-size byte array. + // Algorithm 22 pkEncode + pub fn encode(&self) -> EncodedVerifyingKey

{ + Self::encode_internal(&self.rho, &self.t1) + } + + /// Decode the key from an appropriately sized byte array. + // Algorithm 23 pkDecode + pub fn decode(enc: &EncodedVerifyingKey

) -> Self { + let (rho, t1_enc) = P::split_vk(enc); + let t1 = P::decode_t1(t1_enc); + Self::new_expand_a(rho.clone(), t1, Some(enc.clone())) + } +} + +impl core::hash::Hash for VerifyingKey

{ + fn hash(&self, state: &mut H) { + self.encode().hash(state); + } +} + +impl signature::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> { + self.raw_verify_with_context(msg, &[], signature) + .then_some(()) + .ok_or(Error::new()) + } +} + +impl DigestVerifier> for VerifyingKey

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

, + ) -> Result<(), Error> { + let mut mu = MuBuilder::new(&self.tr, &[]); + f(mu.as_mut())?; + let mu = mu.finish(); + + self.raw_verify_mu(&mu, signature) + .then_some(()) + .ok_or(Error::new()) + } +}