1use alloc::vec::Vec;
6use rand_core::CryptoRng;
7use subtle::ConstantTimeEq;
8use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
9
10use pakery_core::crypto::CpaceGroup;
11use pakery_core::SharedSecret;
12
13use crate::ciphersuite::Spake2PlusCiphersuite;
14use crate::encoding::build_transcript;
15use crate::error::Spake2PlusError;
16use crate::transcript::derive_key_schedule;
17
18#[derive(Zeroize, ZeroizeOnDrop)]
20pub struct ProverState<C: Spake2PlusCiphersuite> {
21 x: <C::Group as CpaceGroup>::Scalar,
22 w0: <C::Group as CpaceGroup>::Scalar,
23 w1: <C::Group as CpaceGroup>::Scalar,
24 share_p_bytes: Vec<u8>,
25 context: Vec<u8>,
26 id_prover: Vec<u8>,
27 id_verifier: Vec<u8>,
28 #[zeroize(skip)]
29 _marker: core::marker::PhantomData<C>,
30}
31
32#[derive(Zeroize, ZeroizeOnDrop)]
34pub struct ProverOutput {
35 #[zeroize(skip)]
37 pub session_key: SharedSecret,
38 pub confirm_p: Vec<u8>,
40}
41
42impl ProverOutput {
43 #[must_use]
58 pub fn into_session_key(mut self) -> SharedSecret {
59 core::mem::replace(&mut self.session_key, SharedSecret::new(Vec::new()))
60 }
61
62 #[must_use]
72 pub fn into_confirm_p(mut self) -> Vec<u8> {
73 core::mem::take(&mut self.confirm_p)
74 }
75}
76
77pub struct Prover<C: Spake2PlusCiphersuite>(core::marker::PhantomData<C>);
79
80impl<C: Spake2PlusCiphersuite> Prover<C> {
81 pub fn start(
88 w0: &<C::Group as CpaceGroup>::Scalar,
89 w1: &<C::Group as CpaceGroup>::Scalar,
90 context: &[u8],
91 id_prover: &[u8],
92 id_verifier: &[u8],
93 rng: &mut impl CryptoRng,
94 ) -> Result<(Vec<u8>, ProverState<C>), Spake2PlusError> {
95 let x = C::Group::random_scalar(rng);
96 Self::start_inner(w0.clone(), w1.clone(), x, context, id_prover, id_verifier)
97 }
98
99 #[cfg(feature = "test-utils")]
107 pub fn start_with_scalar(
108 w0: &<C::Group as CpaceGroup>::Scalar,
109 w1: &<C::Group as CpaceGroup>::Scalar,
110 x: &<C::Group as CpaceGroup>::Scalar,
111 context: &[u8],
112 id_prover: &[u8],
113 id_verifier: &[u8],
114 ) -> Result<(Vec<u8>, ProverState<C>), Spake2PlusError> {
115 Self::start_inner(
116 w0.clone(),
117 w1.clone(),
118 x.clone(),
119 context,
120 id_prover,
121 id_verifier,
122 )
123 }
124
125 fn start_inner(
126 w0: <C::Group as CpaceGroup>::Scalar,
127 w1: <C::Group as CpaceGroup>::Scalar,
128 x: <C::Group as CpaceGroup>::Scalar,
129 context: &[u8],
130 id_prover: &[u8],
131 id_verifier: &[u8],
132 ) -> Result<(Vec<u8>, ProverState<C>), Spake2PlusError> {
133 let m = C::Group::from_bytes(C::M_BYTES)?;
135
136 let x_g = C::Group::basepoint_mul(&x);
138 let w0_m = m.scalar_mul(&w0);
139 let share_p = x_g.add(&w0_m);
140
141 let share_p_bytes = share_p.to_bytes();
142 pakery_core::ct::declassify(&share_p_bytes);
144
145 let state = ProverState {
146 x,
147 w0,
148 w1,
149 share_p_bytes: share_p_bytes.clone(),
150 context: context.to_vec(),
151 id_prover: id_prover.to_vec(),
152 id_verifier: id_verifier.to_vec(),
153 _marker: core::marker::PhantomData,
154 };
155
156 Ok((share_p_bytes, state))
157 }
158}
159
160impl<C: Spake2PlusCiphersuite> ProverState<C> {
161 pub fn finish(
167 self,
168 share_v_bytes: &[u8],
169 confirm_v: &[u8],
170 ) -> Result<ProverOutput, Spake2PlusError> {
171 let share_v = C::Group::from_bytes(share_v_bytes)?;
173 if share_v.is_identity() {
174 return Err(Spake2PlusError::IdentityPoint);
175 }
176
177 let n = C::Group::from_bytes(C::N_BYTES)?;
179
180 let w0_n = n.scalar_mul(&self.w0);
182 let tmp = share_v.add(&w0_n.negate());
183
184 let z = tmp.scalar_mul(&self.x);
186
187 let v = tmp.scalar_mul(&self.w1);
189
190 if z.is_identity() {
192 return Err(Spake2PlusError::IdentityPoint);
193 }
194 if v.is_identity() {
195 return Err(Spake2PlusError::IdentityPoint);
196 }
197
198 let z_bytes = Zeroizing::new(z.to_bytes());
199 let v_bytes = Zeroizing::new(v.to_bytes());
200 let w0_bytes = Zeroizing::new(C::Group::scalar_to_bytes(&self.w0));
201 pakery_core::ct::mark_secret(&z_bytes);
205 pakery_core::ct::mark_secret(&v_bytes);
206 pakery_core::ct::mark_secret(&w0_bytes);
207
208 let m = C::Group::from_bytes(C::M_BYTES)?;
213 let n_point = C::Group::from_bytes(C::N_BYTES)?;
214 let m_bytes = m.to_bytes();
215 let n_bytes = n_point.to_bytes();
216
217 let tt = build_transcript(
219 &self.context,
220 &self.id_prover,
221 &self.id_verifier,
222 &m_bytes,
223 &n_bytes,
224 &self.share_p_bytes,
225 share_v_bytes,
226 &z_bytes,
227 &v_bytes,
228 &w0_bytes,
229 );
230
231 let mut ks = derive_key_schedule::<C>(&tt, &self.share_p_bytes, share_v_bytes)?;
233
234 if !pakery_core::ct::declassify_choice(ks.confirm_v.ct_eq(confirm_v)) {
238 return Err(Spake2PlusError::ConfirmationFailed);
239 }
240
241 let confirm_p = core::mem::take(&mut ks.confirm_p);
242 pakery_core::ct::declassify(&confirm_p);
244 Ok(ProverOutput {
245 session_key: core::mem::replace(&mut ks.session_key, SharedSecret::new(Vec::new())),
246 confirm_p,
247 })
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::test_mocks::MockSuite;
255 use alloc::vec;
256
257 #[test]
260 fn state_zeroize_clears_all_secret_fields() {
261 let mut state = ProverState::<MockSuite> {
262 x: [0xAA; 32],
263 w0: [0xBB; 32],
264 w1: [0xCC; 32],
265 share_p_bytes: vec![0xDD; 32],
266 context: vec![0xEE; 8],
267 id_prover: vec![0xFF; 8],
268 id_verifier: vec![0x11; 8],
269 _marker: core::marker::PhantomData,
270 };
271 state.zeroize();
272 assert_eq!(state.x, [0u8; 32]);
273 assert_eq!(state.w0, [0u8; 32]);
274 assert_eq!(state.w1, [0u8; 32]);
275 assert!(state.share_p_bytes.is_empty());
276 assert!(state.context.is_empty());
277 assert!(state.id_prover.is_empty());
278 assert!(state.id_verifier.is_empty());
279 }
280
281 #[test]
284 fn output_zeroize_clears_all_secret_fields() {
285 let mut output = ProverOutput {
286 session_key: SharedSecret::new(vec![0xAA; 32]),
287 confirm_p: vec![0xBB; 64],
288 };
289 output.zeroize();
290 assert!(output.confirm_p.is_empty());
291 assert_eq!(output.session_key.as_bytes(), &[0xAA; 32]);
293 }
294}