mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
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`
This commit is contained in:
+13
-693
@@ -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<u8, U32>;
|
||||
@@ -194,683 +191,6 @@ impl AsMut<Shake256> for MuBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// An ML-DSA signing key initialized through a seed
|
||||
#[derive(Clone)]
|
||||
pub struct SigningKey<P: MlDsaParams> {
|
||||
/// The signing key of the key pair
|
||||
signing_key: ExpandedSigningKey<P>,
|
||||
|
||||
/// The seed this signing key was derived from
|
||||
seed: B32,
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> SigningKey<P> {
|
||||
/// The signing key of the key pair
|
||||
pub fn signing_key(&self) -> &ExpandedSigningKey<P> {
|
||||
&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<P: MlDsaParams> fmt::Debug for SigningKey<P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SigningKey").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> signature::Keypair for SigningKey<P> {
|
||||
type VerifyingKey = VerifyingKey<P>;
|
||||
fn verifying_key(&self) -> VerifyingKey<P> {
|
||||
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<P: MlDsaParams> Signer<Signature<P>> for SigningKey<P> {
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, 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<P: MlDsaParams> MultipartSigner<Signature<P>> for SigningKey<P> {
|
||||
fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<Signature<P>, 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<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for SigningKey<P> {
|
||||
fn try_sign_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<Signature<P>, Error> {
|
||||
self.signing_key.try_sign_digest(&f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> PartialEq for SigningKey<P> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> CtEq for SigningKey<P> {
|
||||
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<P: MlDsaParams> {
|
||||
rho: B32,
|
||||
K: B32,
|
||||
tr: B64,
|
||||
s1: Vector<P::L>,
|
||||
s2: Vector<P::K>,
|
||||
t0: Vector<P::K>,
|
||||
|
||||
// Derived values
|
||||
s1_hat: NttVector<P::L>,
|
||||
s2_hat: NttVector<P::K>,
|
||||
t0_hat: NttVector<P::K>,
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> fmt::Debug for ExpandedSigningKey<P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ExpandedSigningKey").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl<P: MlDsaParams> Drop for ExpandedSigningKey<P> {
|
||||
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<P: MlDsaParams> ZeroizeOnDrop for ExpandedSigningKey<P> {}
|
||||
|
||||
impl<P: MlDsaParams> ExpandedSigningKey<P> {
|
||||
fn new(
|
||||
rho: B32,
|
||||
K: B32,
|
||||
tr: B64,
|
||||
s1: Vector<P::L>,
|
||||
s2: Vector<P::K>,
|
||||
t0: Vector<P::K>,
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
) -> 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<P::L>,
|
||||
s2: Vector<P::K>,
|
||||
t0: Vector<P::K>,
|
||||
) -> 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<P>
|
||||
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<P>
|
||||
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::<P::L, P::Gamma1>(&rhopp, kappa);
|
||||
let w = (&self.A_hat * &y.ntt()).ntt_inverse();
|
||||
let w1 = w.high_bits::<P::TwoGamma2>();
|
||||
|
||||
let w1_tilde = P::encode_w1(&w1);
|
||||
let c_tilde = H::default()
|
||||
.absorb(mu)
|
||||
.absorb(&w1_tilde)
|
||||
.squeeze_new::<P::Lambda>();
|
||||
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::<P::TwoGamma2>();
|
||||
|
||||
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::<P>::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::<SpecQ>());
|
||||
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<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
M: &[u8],
|
||||
ctx: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<Signature<P>, Error> {
|
||||
self.raw_sign_randomized(&[M], ctx, rng)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rand_core")]
|
||||
fn raw_sign_randomized<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
Mp: &[&[u8]],
|
||||
ctx: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<Signature<P>, 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<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
mu: &B64,
|
||||
rng: &mut R,
|
||||
) -> Result<Signature<P>, 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<Signature<P>, 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<P> {
|
||||
let rnd = B32::default();
|
||||
self.raw_sign_mu(mu, &rnd)
|
||||
}
|
||||
|
||||
fn raw_sign_deterministic(&self, Mp: &[&[u8]], ctx: &[u8]) -> Result<Signature<P>, 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<P> {
|
||||
let kp: &dyn signature::Keypair<VerifyingKey = VerifyingKey<P>> = 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`].
|
||||
///
|
||||
/// <div class="warning">
|
||||
/// <b>Panics</b>
|
||||
///
|
||||
/// 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.
|
||||
/// </div>
|
||||
// Algorithm 25 skDecode
|
||||
#[deprecated(since = "0.1.0", note = "use `ExpandedSigningKey::from_seed` instead")]
|
||||
pub fn from_expanded(enc: &ExpandedSigningKeyBytes<P>) -> 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<P>
|
||||
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<P: MlDsaParams> Signer<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, 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<P: MlDsaParams> MultipartSigner<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<Signature<P>, 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<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<Signature<P>, 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<P: MlDsaParams> signature::Keypair for ExpandedSigningKey<P> {
|
||||
type VerifyingKey = VerifyingKey<P>;
|
||||
|
||||
/// 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<P: MlDsaParams> RandomizedSigner<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[u8],
|
||||
) -> Result<Signature<P>, 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<P: MlDsaParams> RandomizedMultipartSigner<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[&[u8]],
|
||||
) -> Result<Signature<P>, 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<P: MlDsaParams> RandomizedDigestSigner<Shake256, Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign_digest_with_rng<
|
||||
R: TryCryptoRng + ?Sized,
|
||||
F: Fn(&mut Shake256) -> Result<(), Error>,
|
||||
>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
f: F,
|
||||
) -> Result<Signature<P>, Error> {
|
||||
let mut mu = MuBuilder::new(&self.tr, &[]);
|
||||
f(mu.as_mut())?;
|
||||
let mu = mu.finish();
|
||||
|
||||
self.sign_mu_randomized(&mu, rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> PartialEq for ExpandedSigningKey<P> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> CtEq for ExpandedSigningKey<P> {
|
||||
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<P: ParameterSet> {
|
||||
rho: B32,
|
||||
t1: Vector<P::K>,
|
||||
|
||||
// Derived values
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
t1_2d_hat: NttVector<P::K>,
|
||||
tr: B64,
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> VerifyingKey<P> {
|
||||
fn new(
|
||||
rho: B32,
|
||||
t1: Vector<P::K>,
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
enc: Option<EncodedVerifyingKey<P>>,
|
||||
) -> 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<P::K>, enc: Option<EncodedVerifyingKey<P>>) -> 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<F: FnOnce(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
Mp: F,
|
||||
ctx: &[u8],
|
||||
) -> Result<B64, Error> {
|
||||
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<P>) -> 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<P>) -> 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::<P::Lambda>();
|
||||
|
||||
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<P>) -> 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<P>) -> bool {
|
||||
self.raw_verify_mu(mu, sigma)
|
||||
}
|
||||
|
||||
fn raw_verify_with_context(&self, M: &[&[u8]], ctx: &[u8], sigma: &Signature<P>) -> 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<P::K>) -> EncodedVerifyingKey<P> {
|
||||
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<P> {
|
||||
Self::encode_internal(&self.rho, &self.t1)
|
||||
}
|
||||
|
||||
/// Decode the key from an appropriately sized byte array.
|
||||
// Algorithm 23 pkDecode
|
||||
pub fn decode(enc: &EncodedVerifyingKey<P>) -> 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<P: MlDsaParams> core::hash::Hash for VerifyingKey<P> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.encode().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> signature::Verifier<Signature<P>> for VerifyingKey<P> {
|
||||
fn verify(&self, msg: &[u8], signature: &Signature<P>) -> Result<(), Error> {
|
||||
self.multipart_verify(&[msg], signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> MultipartVerifier<Signature<P>> for VerifyingKey<P> {
|
||||
fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature<P>) -> Result<(), Error> {
|
||||
self.raw_verify_with_context(msg, &[], signature)
|
||||
.then_some(())
|
||||
.ok_or(Error::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> DigestVerifier<Shake256, Signature<P>> for VerifyingKey<P> {
|
||||
fn verify_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
f: F,
|
||||
signature: &Signature<P>,
|
||||
) -> 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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<P: MlDsaParams> {
|
||||
/// The signing key of the key pair
|
||||
pub(crate) signing_key: ExpandedSigningKey<P>,
|
||||
|
||||
/// The seed this signing key was derived from
|
||||
pub(crate) seed: B32,
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> SigningKey<P> {
|
||||
/// The signing key of the key pair
|
||||
pub fn signing_key(&self) -> &ExpandedSigningKey<P> {
|
||||
&self.signing_key
|
||||
}
|
||||
|
||||
/// Serialize the [`Seed`] value: 32-bytes which can be used to reconstruct the
|
||||
/// [`SigningKey`].
|
||||
///
|
||||
/// <div class = "warning">
|
||||
/// <b>Warning</b>
|
||||
///
|
||||
/// This value is key material. Please treat it with care.
|
||||
/// </div>
|
||||
#[inline]
|
||||
pub fn to_seed(&self) -> Seed {
|
||||
self.seed
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> fmt::Debug for SigningKey<P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SigningKey").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> signature::Keypair for SigningKey<P> {
|
||||
type VerifyingKey = VerifyingKey<P>;
|
||||
fn verifying_key(&self) -> VerifyingKey<P> {
|
||||
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<P: MlDsaParams> Signer<Signature<P>> for SigningKey<P> {
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, 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<P: MlDsaParams> MultipartSigner<Signature<P>> for SigningKey<P> {
|
||||
fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<Signature<P>, 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<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for SigningKey<P> {
|
||||
fn try_sign_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<Signature<P>, Error> {
|
||||
self.signing_key.try_sign_digest(&f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> PartialEq for SigningKey<P> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> CtEq for SigningKey<P> {
|
||||
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<P: MlDsaParams> {
|
||||
rho: B32,
|
||||
K: B32,
|
||||
pub(crate) tr: B64,
|
||||
s1: Vector<P::L>,
|
||||
s2: Vector<P::K>,
|
||||
t0: Vector<P::K>,
|
||||
|
||||
// Derived values
|
||||
s1_hat: NttVector<P::L>,
|
||||
s2_hat: NttVector<P::K>,
|
||||
t0_hat: NttVector<P::K>,
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> ExpandedSigningKey<P> {
|
||||
pub(crate) fn new(
|
||||
rho: B32,
|
||||
K: B32,
|
||||
tr: B64,
|
||||
s1: Vector<P::L>,
|
||||
s2: Vector<P::K>,
|
||||
t0: Vector<P::K>,
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
) -> 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<P::L>,
|
||||
s2: Vector<P::K>,
|
||||
t0: Vector<P::K>,
|
||||
) -> 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<P>
|
||||
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<P>
|
||||
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::<P::L, P::Gamma1>(&rhopp, kappa);
|
||||
let w = (&self.A_hat * &y.ntt()).ntt_inverse();
|
||||
let w1 = w.high_bits::<P::TwoGamma2>();
|
||||
|
||||
let w1_tilde = P::encode_w1(&w1);
|
||||
let c_tilde = H::default()
|
||||
.absorb(mu)
|
||||
.absorb(&w1_tilde)
|
||||
.squeeze_new::<P::Lambda>();
|
||||
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::<P::TwoGamma2>();
|
||||
|
||||
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::<P>::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::<SpecQ>());
|
||||
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<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
M: &[u8],
|
||||
ctx: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<Signature<P>, Error> {
|
||||
self.raw_sign_randomized(&[M], ctx, rng)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rand_core")]
|
||||
fn raw_sign_randomized<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
Mp: &[&[u8]],
|
||||
ctx: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<Signature<P>, 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<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
mu: &B64,
|
||||
rng: &mut R,
|
||||
) -> Result<Signature<P>, 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<Signature<P>, 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<P> {
|
||||
let rnd = B32::default();
|
||||
self.raw_sign_mu(mu, &rnd)
|
||||
}
|
||||
|
||||
fn raw_sign_deterministic(&self, Mp: &[&[u8]], ctx: &[u8]) -> Result<Signature<P>, 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<P> {
|
||||
let kp: &dyn signature::Keypair<VerifyingKey = VerifyingKey<P>> = 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`].
|
||||
///
|
||||
/// <div class="warning">
|
||||
/// <b>Panics</b>
|
||||
///
|
||||
/// 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.
|
||||
/// </div>
|
||||
// Algorithm 25 skDecode
|
||||
#[deprecated(since = "0.1.0", note = "use `ExpandedSigningKey::from_seed` instead")]
|
||||
pub fn from_expanded(enc: &ExpandedSigningKeyBytes<P>) -> 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<P>
|
||||
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<P: MlDsaParams> Signer<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, 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<P: MlDsaParams> MultipartSigner<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<Signature<P>, 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<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<Signature<P>, 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<P: MlDsaParams> signature::Keypair for ExpandedSigningKey<P> {
|
||||
type VerifyingKey = VerifyingKey<P>;
|
||||
|
||||
/// 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<P: MlDsaParams> RandomizedSigner<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[u8],
|
||||
) -> Result<Signature<P>, 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<P: MlDsaParams> RandomizedMultipartSigner<Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[&[u8]],
|
||||
) -> Result<Signature<P>, 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<P: MlDsaParams> RandomizedDigestSigner<Shake256, Signature<P>> for ExpandedSigningKey<P> {
|
||||
fn try_sign_digest_with_rng<
|
||||
R: TryCryptoRng + ?Sized,
|
||||
F: Fn(&mut Shake256) -> Result<(), Error>,
|
||||
>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
f: F,
|
||||
) -> Result<Signature<P>, Error> {
|
||||
let mut mu = MuBuilder::new(&self.tr, &[]);
|
||||
f(mu.as_mut())?;
|
||||
let mu = mu.finish();
|
||||
|
||||
self.sign_mu_randomized(&mu, rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> PartialEq for ExpandedSigningKey<P> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> CtEq for ExpandedSigningKey<P> {
|
||||
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<P: MlDsaParams> fmt::Debug for ExpandedSigningKey<P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ExpandedSigningKey").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl<P: MlDsaParams> Drop for ExpandedSigningKey<P> {
|
||||
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<P: MlDsaParams> ZeroizeOnDrop for ExpandedSigningKey<P> {}
|
||||
@@ -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<P: ParameterSet> {
|
||||
rho: B32,
|
||||
t1: Vector<P::K>,
|
||||
|
||||
// Derived values
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
t1_2d_hat: NttVector<P::K>,
|
||||
tr: B64,
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> VerifyingKey<P> {
|
||||
pub(crate) fn new(
|
||||
rho: B32,
|
||||
t1: Vector<P::K>,
|
||||
A_hat: NttMatrix<P::K, P::L>,
|
||||
enc: Option<EncodedVerifyingKey<P>>,
|
||||
) -> 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<P::K>, enc: Option<EncodedVerifyingKey<P>>) -> 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<F: FnOnce(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
Mp: F,
|
||||
ctx: &[u8],
|
||||
) -> Result<B64, Error> {
|
||||
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<P>) -> 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<P>) -> 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::<P::Lambda>();
|
||||
|
||||
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<P>) -> 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<P>) -> bool {
|
||||
self.raw_verify_mu(mu, sigma)
|
||||
}
|
||||
|
||||
fn raw_verify_with_context(&self, M: &[&[u8]], ctx: &[u8], sigma: &Signature<P>) -> 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<P::K>) -> EncodedVerifyingKey<P> {
|
||||
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<P> {
|
||||
Self::encode_internal(&self.rho, &self.t1)
|
||||
}
|
||||
|
||||
/// Decode the key from an appropriately sized byte array.
|
||||
// Algorithm 23 pkDecode
|
||||
pub fn decode(enc: &EncodedVerifyingKey<P>) -> 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<P: MlDsaParams> core::hash::Hash for VerifyingKey<P> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.encode().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> signature::Verifier<Signature<P>> for VerifyingKey<P> {
|
||||
fn verify(&self, msg: &[u8], signature: &Signature<P>) -> Result<(), Error> {
|
||||
self.multipart_verify(&[msg], signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> MultipartVerifier<Signature<P>> for VerifyingKey<P> {
|
||||
fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature<P>) -> Result<(), Error> {
|
||||
self.raw_verify_with_context(msg, &[], signature)
|
||||
.then_some(())
|
||||
.ok_or(Error::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MlDsaParams> DigestVerifier<Shake256, Signature<P>> for VerifyingKey<P> {
|
||||
fn verify_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
|
||||
&self,
|
||||
f: F,
|
||||
signature: &Signature<P>,
|
||||
) -> 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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user