Skip to main content

pakery_spake2plus/
verifier.rs

1//! SPAKE2+ Verifier (server) state machine.
2//!
3//! The Verifier stores `(w0, L)` where `L = w1*G`. It does not know
4//! the password or `w1` directly.
5
6use alloc::vec::Vec;
7use rand_core::CryptoRngCore;
8use subtle::ConstantTimeEq;
9use zeroize::{Zeroize, Zeroizing};
10
11use pakery_core::crypto::CpaceGroup;
12use pakery_core::SharedSecret;
13
14use crate::ciphersuite::Spake2PlusCiphersuite;
15use crate::encoding::build_transcript;
16use crate::error::Spake2PlusError;
17use crate::transcript::{derive_key_schedule, Spake2PlusOutput};
18
19/// State held by the Verifier between sending (shareV, confirmV) and receiving confirmP.
20pub struct VerifierState {
21    expected_confirm_p: Vec<u8>,
22    session_key: SharedSecret,
23}
24
25impl Drop for VerifierState {
26    fn drop(&mut self) {
27        self.expected_confirm_p.zeroize();
28        // session_key has its own ZeroizeOnDrop via SharedSecret
29    }
30}
31
32impl VerifierState {
33    /// Finish the SPAKE2+ protocol by verifying the Prover's confirmation MAC.
34    pub fn finish(mut self, confirm_p: &[u8]) -> Result<Spake2PlusOutput, Spake2PlusError> {
35        if !bool::from(self.expected_confirm_p.ct_eq(confirm_p)) {
36            return Err(Spake2PlusError::ConfirmationFailed);
37        }
38
39        // Move session_key out; the placeholder empty secret is dropped
40        // (and zeroized) when `self` drops.
41        let session_key =
42            core::mem::replace(&mut self.session_key, SharedSecret::new(alloc::vec![]));
43        Ok(Spake2PlusOutput { session_key })
44    }
45}
46
47/// SPAKE2+ Verifier: processes the Prover's first message and generates the response.
48pub struct Verifier<C: Spake2PlusCiphersuite>(core::marker::PhantomData<C>);
49
50impl<C: Spake2PlusCiphersuite> Verifier<C> {
51    /// Start the SPAKE2+ protocol as the Verifier.
52    ///
53    /// `w0` is the password-derived scalar stored during registration.
54    /// `l_bytes` is the verifier point `L = w1*G` stored during registration.
55    ///
56    /// Returns `(shareV_bytes, confirmV, state)` where `shareV_bytes` and `confirmV`
57    /// are sent to the Prover.
58    pub fn start(
59        share_p_bytes: &[u8],
60        w0: &<C::Group as CpaceGroup>::Scalar,
61        l_bytes: &[u8],
62        context: &[u8],
63        id_prover: &[u8],
64        id_verifier: &[u8],
65        rng: &mut impl CryptoRngCore,
66    ) -> Result<(Vec<u8>, Vec<u8>, VerifierState), Spake2PlusError> {
67        let y = C::Group::random_scalar(rng);
68        Self::start_inner(
69            share_p_bytes,
70            w0,
71            l_bytes,
72            &y,
73            context,
74            id_prover,
75            id_verifier,
76        )
77    }
78
79    /// Start with a deterministic scalar (for testing).
80    ///
81    /// # Security
82    ///
83    /// Using a non-random scalar completely breaks security.
84    /// This method is gated behind the `test-utils` feature and must
85    /// only be used for RFC test vector validation.
86    #[cfg(feature = "test-utils")]
87    pub fn start_with_scalar(
88        share_p_bytes: &[u8],
89        w0: &<C::Group as CpaceGroup>::Scalar,
90        l_bytes: &[u8],
91        y: &<C::Group as CpaceGroup>::Scalar,
92        context: &[u8],
93        id_prover: &[u8],
94        id_verifier: &[u8],
95    ) -> Result<(Vec<u8>, Vec<u8>, VerifierState), Spake2PlusError> {
96        Self::start_inner(
97            share_p_bytes,
98            w0,
99            l_bytes,
100            y,
101            context,
102            id_prover,
103            id_verifier,
104        )
105    }
106
107    fn start_inner(
108        share_p_bytes: &[u8],
109        w0: &<C::Group as CpaceGroup>::Scalar,
110        l_bytes: &[u8],
111        y: &<C::Group as CpaceGroup>::Scalar,
112        context: &[u8],
113        id_prover: &[u8],
114        id_verifier: &[u8],
115    ) -> Result<(Vec<u8>, Vec<u8>, VerifierState), Spake2PlusError> {
116        // Decode shareP and reject identity (defense-in-depth)
117        let share_p = C::Group::from_bytes(share_p_bytes)?;
118        if share_p.is_identity() {
119            return Err(Spake2PlusError::IdentityPoint);
120        }
121
122        // Decode M from ciphersuite constants
123        let m = C::Group::from_bytes(C::M_BYTES)?;
124
125        // Decode L (verifier point)
126        let l = C::Group::from_bytes(l_bytes)?;
127
128        // Decode N from ciphersuite constants
129        let n = C::Group::from_bytes(C::N_BYTES)?;
130
131        // shareV = y*G + w0*N
132        let y_g = C::Group::basepoint_mul(y);
133        let w0_n = n.scalar_mul(w0);
134        let share_v = y_g.add(&w0_n);
135
136        let share_v_bytes = share_v.to_bytes();
137
138        // tmp = shareP - w0*M (= x*G)
139        let w0_m = m.scalar_mul(w0);
140        let tmp = share_p.add(&w0_m.negate());
141
142        // Z = y * tmp (= y*x*G)
143        let z = tmp.scalar_mul(y);
144
145        // V = y * L (= y*w1*G)
146        let v = l.scalar_mul(y);
147
148        // Check Z != identity, V != identity
149        if z.is_identity() {
150            return Err(Spake2PlusError::IdentityPoint);
151        }
152        if v.is_identity() {
153            return Err(Spake2PlusError::IdentityPoint);
154        }
155
156        let z_bytes = Zeroizing::new(z.to_bytes());
157        let v_bytes = Zeroizing::new(v.to_bytes());
158        let w0_bytes = Zeroizing::new(C::Group::scalar_to_bytes(w0));
159
160        // Use canonical group element encoding for M and N in the transcript
161        // (same encoding as all other group elements, e.g. uncompressed for P-256).
162        let m_bytes = m.to_bytes();
163        let n_bytes = n.to_bytes();
164
165        // Build transcript TT (10 fields)
166        let tt = build_transcript(
167            context,
168            id_prover,
169            id_verifier,
170            &m_bytes,
171            &n_bytes,
172            share_p_bytes,
173            &share_v_bytes,
174            &z_bytes,
175            &v_bytes,
176            &w0_bytes,
177        );
178
179        // Derive key schedule
180        let mut ks = derive_key_schedule::<C>(&tt, share_p_bytes, &share_v_bytes)?;
181
182        let state = VerifierState {
183            expected_confirm_p: core::mem::take(&mut ks.confirm_p),
184            session_key: core::mem::replace(&mut ks.session_key, SharedSecret::new(Vec::new())),
185        };
186
187        Ok((share_v_bytes, core::mem::take(&mut ks.confirm_v), state))
188    }
189}