Skip to main content

ml_dsa/
verifying.rs

1//! ML-DSA signature verification.
2
3use crate::{
4    B32, B64, EncodedVerifyingKey, MlDsaParams, MuBuilder, Signature,
5    algebra::{Elem, NttMatrix, NttVector, Vector},
6    crypto::H,
7    ntt::{Ntt, NttInverse},
8    param::ParameterSet,
9    param::VerifyingKeySize,
10    sampling::{expand_a, sample_in_ball},
11};
12use common::{Key, KeyExport, KeyInit, KeySizeUser};
13use module_lattice::MaybeBox;
14use shake::Shake256;
15use signature::{DigestVerifier, Error, MultipartVerifier};
16
17/// An ML-DSA verification key.
18#[derive(Clone, Debug, PartialEq)]
19pub struct VerifyingKey<P: ParameterSet> {
20    /// Public seed used to deterministically re-expand `A_hat`.
21    rho: B32,
22
23    /// High bits of the public key polynomial `t`.
24    t1: MaybeBox<Vector<P::K>>,
25
26    /// Precomputed expanded values.
27    precomputed_values: MaybeBox<PrecomputedValues<P>>,
28}
29
30/// Cached values derived from `rho` and `t1` at key construction time to avoid re-expanding them
31/// when verifying signatures.
32#[derive(Clone, Debug, PartialEq)]
33struct PrecomputedValues<P: ParameterSet> {
34    /// Expanded public matrix in NTT domain.
35    A_hat: NttMatrix<P::K, P::L>,
36
37    /// `2ᵈ ⋅ t1` which can be reused in signature verification.
38    t1_2d_hat: NttVector<P::K>,
39
40    /// Hash of the encoded public key, used to bind messages to the key this was precomputed from.
41    tr: B64,
42}
43
44impl<P: MlDsaParams> PrecomputedValues<P> {
45    fn new(t1: &Vector<P::K>, enc: &EncodedVerifyingKey<P>, A_hat: NttMatrix<P::K, P::L>) -> Self {
46        let t1_2d_hat = (Elem::new(1 << 13) * t1).ntt();
47        let tr = H::default().absorb(enc).squeeze_new();
48
49        Self {
50            A_hat,
51            t1_2d_hat,
52            tr,
53        }
54    }
55}
56
57impl<P: MlDsaParams> VerifyingKey<P> {
58    pub(crate) fn new(
59        rho: B32,
60        t1: Vector<P::K>,
61        A_hat: NttMatrix<P::K, P::L>,
62        enc: Option<EncodedVerifyingKey<P>>,
63    ) -> Self {
64        let enc = enc.unwrap_or_else(|| Self::encode_internal(&rho, &t1));
65        let precomputed_values = PrecomputedValues::new(&t1, &enc, A_hat);
66
67        Self {
68            rho,
69            t1: MaybeBox::new(t1),
70            precomputed_values: MaybeBox::new(precomputed_values),
71        }
72    }
73
74    #[inline]
75    fn new_expand_a(rho: B32, t1: Vector<P::K>, enc: Option<EncodedVerifyingKey<P>>) -> Self {
76        let A_hat = expand_a(&rho);
77        Self::new(rho, t1, A_hat, enc)
78    }
79
80    /// Computes µ according to FIPS 204 for use in `ML-DSA.Sign` and `ML-DSA.Verify`.
81    ///
82    /// # Errors
83    /// Returns [`Error`] if the context string is more than 255 bytes long or the given `Mp`
84    /// returns one. An oversized context is rejected before `Mp` is called.
85    pub fn compute_mu<F: FnOnce(&mut Shake256) -> Result<(), Error>>(
86        &self,
87        Mp: F,
88        ctx: &[u8],
89    ) -> Result<B64, Error> {
90        if ctx.len() > 255 {
91            return Err(Error::new());
92        }
93
94        let mut mu = MuBuilder::new(&self.precomputed_values.tr, ctx);
95        Mp(mu.as_mut())?;
96        Ok(mu.finish())
97    }
98
99    /// Implementation of Algorithm 8: `ML-DSA.Verify_internal` algorithm from FIPS 204.
100    ///
101    /// It does not include the domain separator that distinguishes between the normal and
102    /// pre-hashed cases, and it does not separate the context string from the rest of the message.
103    pub fn verify_internal(&self, M: &[u8], sigma: &Signature<P>) -> bool
104    where
105        P: MlDsaParams,
106    {
107        let mu = MuBuilder::internal(&self.precomputed_values.tr, &[M]);
108        self.raw_verify_mu(&mu, sigma)
109    }
110
111    pub(crate) fn raw_verify_mu(&self, mu: &B64, sigma: &Signature<P>) -> bool
112    where
113        P: MlDsaParams,
114    {
115        // Reconstruct w
116        let c = sample_in_ball(&sigma.c_tilde, P::TAU);
117
118        let z_hat = sigma.z.ntt();
119        let c_hat = c.ntt();
120        let Az_hat = &self.precomputed_values.A_hat * &z_hat;
121        let ct1_2d_hat = &c_hat * &self.precomputed_values.t1_2d_hat;
122
123        let wp_approx = (&Az_hat - &ct1_2d_hat).ntt_inverse();
124        let w1p = sigma.h.use_hint(&wp_approx);
125
126        let w1p_tilde = P::encode_w1(&w1p);
127        let cp_tilde = H::default()
128            .absorb(mu)
129            .absorb(&w1p_tilde)
130            .squeeze_new::<P::Lambda>();
131
132        sigma.c_tilde == cp_tilde
133    }
134
135    /// Implementation of Algorithm 3: `ML-DSA.Verify` from FIPS 204.
136    pub fn verify_with_context(&self, M: &[u8], ctx: &[u8], sigma: &Signature<P>) -> bool {
137        self.raw_verify_with_context(&[M], ctx, sigma)
138    }
139
140    /// Implementation of Algorithm 3: `ML-DSA.Verify` from FIPS 204 with a pre-computed μ.
141    pub fn verify_mu(&self, mu: &B64, sigma: &Signature<P>) -> bool {
142        self.raw_verify_mu(mu, sigma)
143    }
144
145    fn raw_verify_with_context(&self, M: &[&[u8]], ctx: &[u8], sigma: &Signature<P>) -> bool {
146        if ctx.len() > 255 {
147            return false;
148        }
149
150        let mu = MuBuilder::new(&self.precomputed_values.tr, ctx).message(M);
151        self.verify_mu(&mu, sigma)
152    }
153
154    pub(crate) fn encode_internal(rho: &B32, t1: &Vector<P::K>) -> EncodedVerifyingKey<P> {
155        let t1_enc = P::encode_t1(t1);
156        P::concat_vk(rho.clone(), t1_enc)
157    }
158
159    /// Encode the key in a fixed-size byte array.
160    ///
161    /// Implementation of Algorithm 22: `pkEncode` from FIPS 204.
162    #[must_use]
163    pub fn encode(&self) -> EncodedVerifyingKey<P> {
164        Self::encode_internal(&self.rho, &self.t1)
165    }
166
167    /// Decode the key from an appropriately sized byte array.
168    ///
169    /// Implementation of Algorithm 23: `pkDecode` from FIPS 204.
170    pub fn decode(enc: &EncodedVerifyingKey<P>) -> Self {
171        let (rho, t1_enc) = P::split_vk(enc);
172        let t1 = P::decode_t1(t1_enc);
173        Self::new_expand_a(rho.clone(), t1, Some(enc.clone()))
174    }
175}
176
177impl<P: MlDsaParams> KeySizeUser for VerifyingKey<P> {
178    type KeySize = VerifyingKeySize<P>;
179}
180
181impl<P: MlDsaParams> KeyInit for VerifyingKey<P> {
182    fn new(key: &Key<Self>) -> Self {
183        Self::decode(key)
184    }
185}
186
187impl<P: MlDsaParams> KeyExport for VerifyingKey<P> {
188    fn to_bytes(&self) -> Key<Self> {
189        self.encode()
190    }
191}
192
193impl<P: MlDsaParams> core::hash::Hash for VerifyingKey<P> {
194    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
195        self.encode().hash(state);
196    }
197}
198
199impl<P: MlDsaParams> signature::Verifier<Signature<P>> for VerifyingKey<P> {
200    fn verify(&self, msg: &[u8], signature: &Signature<P>) -> Result<(), Error> {
201        self.multipart_verify(&[msg], signature)
202    }
203}
204
205impl<P: MlDsaParams> MultipartVerifier<Signature<P>> for VerifyingKey<P> {
206    fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature<P>) -> Result<(), Error> {
207        self.raw_verify_with_context(msg, &[], signature)
208            .then_some(())
209            .ok_or(Error::new())
210    }
211}
212
213impl<P: MlDsaParams> DigestVerifier<Shake256, Signature<P>> for VerifyingKey<P> {
214    fn verify_digest<F: Fn(&mut Shake256) -> Result<(), Error>>(
215        &self,
216        f: F,
217        signature: &Signature<P>,
218    ) -> Result<(), Error> {
219        let mut mu = MuBuilder::new(&self.precomputed_values.tr, &[]);
220        f(mu.as_mut())?;
221        let mu = mu.finish();
222
223        self.raw_verify_mu(&mu, signature)
224            .then_some(())
225            .ok_or(Error::new())
226    }
227}