labrador/prover.rs
1//! Labrador Prover
2//!
3//! This module implements the **prover** side of the protocol sketched in
4//! Fig. 2 of the paper (page 17). All algebra is carried out over the ring
5//! \(R_q = \mathbb{Z}_q[x]/(x^d+1)\), where `q` is 32-bit modulo and `d` is polynomial degree.
6//!
7//! **Important — security parameters**: The protocol is parameterised by
8//! *rank* `n`, *multiplicity* `r`, *operator norm* `β`, decomposition depths `t_1,t_2`, and
9//! security parameter `λ` = [`env_params::SECURITY_PARAMETER`].
10
11use crate::commitments::{
12 ajtai_commitment, common_instances::AjtaiInstances, outer_commitments,
13 outer_commitments::DecompositionParameters, CommitError,
14};
15use crate::core::{
16 aggregate::FunctionsAggregation, aggregate::ZeroConstantFunctionsAggregation,
17 garbage_polynomials, inner_product, jl::Projection,
18};
19use crate::relation::{
20 env_params::{self, EnvironmentParameters},
21 statement::Statement,
22 witness::Witness,
23};
24use crate::ring::{rq_matrix::RqMatrix, rq_vector::RqVector, zq::Zq};
25use crate::transcript::{LabradorTranscript, Sponge};
26use thiserror::Error;
27
28#[derive(Debug, Error)]
29pub enum ProverError {
30 /// Indicates that the L2 norm (squared) of the witness exceeded the allowed threshold.
31 #[error("invalid witness size: norm_squared {norm_squared}, allowed {allowed}")]
32 WitnessL2NormViolated { norm_squared: Zq, allowed: Zq },
33 #[error("Invalid Projection of index {index}. Expected {expected}, got {computed}")]
34 ProjectionError {
35 index: usize,
36 expected: Zq,
37 computed: Zq,
38 },
39 #[error("commitment failure")]
40 CommitError(#[from] ajtai_commitment::CommitError),
41 #[error("decomposition failure")]
42 DecompositionError(#[from] outer_commitments::DecompositionError),
43}
44
45/// Implements the steps executed by the prover \(\mathcal{P}\) in the paper.
46pub struct LabradorProver<'a> {
47 /// System‑wide environment parameters like rank and multiplicity.
48 params: &'a EnvironmentParameters,
49 /// Common reference string (CRS) holding the commitment matrices
50 /// \(\mathbf{A},\mathbf{B},\mathbf{C},\mathbf{D}\).
51 crs: &'a AjtaiInstances,
52 /// Secret witness vectors \(\{\mathbf{s}_1,\dots,\mathbf{s}_r\}\).
53 witness: &'a Witness,
54 /// Public relation statement.
55 st: &'a Statement,
56 /* --- aggregation instances ---------------------------------------------------------- */
57 constant_aggregator: ZeroConstantFunctionsAggregation<'a>,
58 funcs_aggregator: FunctionsAggregation<'a>,
59}
60
61impl<'a> LabradorProver<'a> {
62 /// Constructs a new prover instance.
63 pub fn new(
64 params: &'a EnvironmentParameters,
65 crs: &'a AjtaiInstances,
66 witness: &'a Witness,
67 st: &'a Statement,
68 ) -> Self {
69 Self {
70 params,
71 crs,
72 witness,
73 st,
74 constant_aggregator: ZeroConstantFunctionsAggregation::new(params),
75 funcs_aggregator: FunctionsAggregation::new(params),
76 }
77 }
78
79 // -----------------------------------------------------------------------------
80 // Step 1.a — Ajtai commitments t_i
81 // -----------------------------------------------------------------------------
82 /// Computes the **Ajtai commitments**
83 /// \[\mathbf{t}_i = \mathbf{A}\,\mathbf{s}_i\\].
84 ///
85 /// # Errors
86 /// Returns [`CommitError`] if the commitment scheme fails.
87 fn compute_vector_ti(&self) -> Result<RqMatrix, CommitError> {
88 let commitments = self
89 .witness
90 .s
91 .iter()
92 .cloned()
93 .map(|s_i| self.crs.commitment_scheme_a.commit(&s_i))
94 .collect::<Result<Vec<_>, CommitError>>()?;
95
96 Ok(RqMatrix::new(commitments, false))
97 }
98
99 // -----------------------------------------------------------------------------
100 // Step 1.b — First outer commitment u_1
101 // -----------------------------------------------------------------------------
102
103 /// Computes the *first outer commitment* \(\mathbf{u}_1\).
104 ///
105 /// Internally this function
106 /// 1. calls [`compute_vector_ti`] to obtain all \(\mathbf{t}_i\),
107 /// 2. evaluates the garbage polynomials \(\mathbf{g}_{ij}=\langle\mathbf{s}_i,\mathbf{s}_j\rangle\),
108 /// 4. finally aggregates and commit using [`compute_u1`].
109 ///
110 /// The resulting value is
111 /// \[
112 /// \mathbf{u}_1
113 /// = \sum_{i=1}^r \sum_{k=0}^{t_1-1} \mathbf{B}_{ik}\,\mathbf{t}_i^{(k)}
114 /// + \sum_{i\le j} \sum_{k=0}^{t_2-1}
115 /// \mathbf{C}_{ijk}\,\mathbf{g}_{ij}^{(k)}.
116 /// \]
117 ///
118 /// The commitment is recorded inside the transcript so the verifier can later
119 /// retrieve it.
120 fn compute_u1<S: Sponge>(
121 &mut self,
122 transcript: &mut LabradorTranscript<S>,
123 ) -> Result<(RqMatrix, RqMatrix), ProverError> {
124 let t_i = self.compute_vector_ti()?;
125 let garbage_polynomial_g = garbage_polynomials::compute_g(&self.witness.s);
126 let commitment_u1 = outer_commitments::compute_u1(
127 self.crs,
128 &t_i,
129 DecompositionParameters::new(self.params.b, self.params.t_1)?,
130 &garbage_polynomial_g,
131 DecompositionParameters::new(self.params.b, self.params.t_2)?,
132 );
133 transcript.set_u1(commitment_u1);
134 Ok((t_i, garbage_polynomial_g))
135 }
136
137 // -----------------------------------------------------------------------------
138 // Step 2 — JL projections
139 // -----------------------------------------------------------------------------
140
141 /// Computes the Johnson–Lindenstrauss projections `p`.
142 ///
143 /// \[p_j = \sum_{i=1}^r \langle {\pi}_i^{(j)},\mathbf{s}_i\rangle\]
144 ///
145 /// |p| = `2 * SECURITY_PARAMETER`
146 ///
147 /// p is recorded inside the transcript so the verifier can later
148 /// retrieve it. Projection instance is returned for use in next steps of the prover.
149 fn compute_p<S: Sponge>(&self, transcript: &mut LabradorTranscript<S>) -> Projection {
150 let projections = transcript.generate_projections(
151 env_params::SECURITY_PARAMETER,
152 self.params.rank,
153 self.params.multiplicity,
154 );
155 let vector_p = projections.compute_batch_projection(&self.witness.s);
156 transcript.set_vector_p(vector_p);
157 projections
158 }
159
160 // -----------------------------------------------------------------------------
161 // Step 3 — Aggregation of zero‑constant functions
162 // -----------------------------------------------------------------------------
163
164 /// Computes the aggregated triples \((a''^{(k)},\varphi''_i,b''^{(k)})\).
165 ///
166 /// We sample vectors *ψ* and *ω* from the sponge and call the helper struct
167 /// [`ZeroConstantFunctionsAggregation`] for the computation.
168 fn compute_b_double_prime<S: Sponge>(
169 &mut self,
170 transcript: &mut LabradorTranscript<S>,
171 projections: &Projection,
172 ) {
173 // --- sample randomisers ------------------------------------------------------
174 let vector_psi =
175 transcript.generate_vector_psi(self.params.const_agg_length, self.params.constraint_l);
176 let vector_omega = transcript
177 .generate_vector_omega(self.params.const_agg_length, env_params::SECURITY_PARAMETER);
178 // --- perform aggregations -------------------------------------------
179 self.constant_aggregator
180 .calculate_agg_a_double_prime(&vector_psi, &self.st.a_ct);
181 self.constant_aggregator.calculate_agg_phi_double_prime(
182 &self.st.phi_ct,
183 &projections.get_conjugated_projection_matrices(),
184 &vector_psi,
185 &vector_omega,
186 );
187 let b_ct_aggr = self
188 .constant_aggregator
189 .calculate_agg_b_double_prime(&self.witness.s);
190 transcript.set_vector_b_ct_aggr(b_ct_aggr);
191 }
192
193 // -----------------------------------------------------------------------------
194 // Step 4 — Second outer commitment u_2
195 // -----------------------------------------------------------------------------
196
197 /// Computes the *second outer commitment* \(\mathbf{u}_2\).
198 ///
199 /// First we create an *aggregated* vector of functions (via
200 /// [`FunctionsAggregation`]). We then evaluate the garbage polynomials
201 /// \(h_{ij} = \langle\tilde{\varphi}_i,\tilde{\mathbf{s}}_j\rangle
202 /// + \langle\varphi_i^{\prime(j)},\tilde{\mathbf{s}}_i\rangle)
203 /// and finally commit using the CRS matrices \(\mathbf{D}_{ijk}\):
204 ///
205 /// \[
206 /// \mathbf{u}_2 = \sum_{i\le j}\sum_{k=0}^{t_1-1}
207 /// \mathbf{D}_{ijk}\,h_{ij}^{(k)}.
208 /// \]
209 /// * Note: As dividing by two is not supported in our scheme (Zq is not necessarily a filed), h_ij in the implementation does not have 1/2 factor in the paper. In subsequent verifications, this is considered.
210 fn compute_u2<S: Sponge>(
211 &mut self,
212 transcript: &mut LabradorTranscript<S>,
213 ) -> Result<RqMatrix, ProverError> {
214 // --- sample randomisers ------------------------------------------------------
215 let alpha_vector = transcript.generate_rq_vector(self.params.constraint_k);
216 let beta_vector = transcript.generate_rq_vector(self.params.const_agg_length);
217 // --- perform aggregations -------------------------------------------
218 self.funcs_aggregator.calculate_aggr_phi(
219 &self.st.phi_constraint,
220 self.constant_aggregator.get_phi_double_prime(),
221 &alpha_vector,
222 &beta_vector,
223 );
224
225 let garbage_polynomial_h =
226 garbage_polynomials::compute_h(&self.witness.s, self.funcs_aggregator.get_appr_phi());
227 let commitment_u2 = outer_commitments::compute_u2(
228 self.crs,
229 &garbage_polynomial_h,
230 DecompositionParameters::new(self.params.b, self.params.t_1)?,
231 );
232 transcript.set_u2(commitment_u2);
233 Ok(garbage_polynomial_h)
234 }
235
236 // -----------------------------------------------------------------------------
237 // Step 5 — Linear combination z
238 // -----------------------------------------------------------------------------
239
240 /// Computes the random linear combination
241 /// \[\mathbf{z}=\sum_{i=1}^r c_i\,\mathbf{s}_i\] with coefficients
242 /// `c_i ← C` (uniformly random challenges).
243 fn compute_z<S: Sponge>(&mut self, transcript: &mut LabradorTranscript<S>) -> RqVector {
244 let challenges =
245 transcript.generate_challenges(env_params::OPERATOR_NORM, self.params.multiplicity);
246 let z = inner_product::compute_linear_combination(&self.witness.s, challenges.elements());
247 z
248 }
249
250 /// Executes **all** prover steps (Fig. 2, page 17).
251 ///
252 /// On success the returned [`LabradorTranscript`] contains every message that
253 /// needs to be sent to the verifier.
254 pub fn prove<S: Sponge>(&mut self) -> Result<LabradorTranscript<S>, ProverError> {
255 // Initialise transcript and sponge -----------------------------------------------
256 let mut transcript = LabradorTranscript::new(S::default());
257
258 // --- Step 1: Outer Commitment u1 ------------------------------------------------
259 let (t_i, garbage_polynomial_g) = self.compute_u1(&mut transcript)?;
260
261 // --- Step 2: JL Projections -----------------------------------------------------
262 let projections = self.compute_p(&mut transcript);
263
264 // --- Step 3: Aggregations + ----------------------------------------------------
265 self.compute_b_double_prime(&mut transcript, &projections);
266
267 // --- Step 4: Outer Commitment u2 ------------------------------------------------
268 let garbage_polynomial_h = self.compute_u2(&mut transcript)?;
269
270 // --- Step 5: Amortization -------------------------------------------------------
271 let z = self.compute_z(&mut transcript);
272
273 // --- Finalise Transcript: Add t_i, g_ij, h_ij to transcript----------------------
274 transcript.set_recursive_part(z, t_i, garbage_polynomial_g, garbage_polynomial_h);
275
276 Ok(transcript)
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::transcript::sponges::shake::ShakeSponge;
284
285 #[test]
286 fn test_prove() {
287 // set up example environment parameters, use default set for testing.
288 let ep_1 = EnvironmentParameters::default();
289 // generate a random witness based on environment parameters above
290 let witness_1 = Witness::new(ep_1.rank, ep_1.multiplicity, ep_1.beta_sq);
291 // generate public statement based on witness_1
292 let st: Statement = Statement::new(&witness_1, &ep_1);
293 // generate the common reference string matrices A, B, C, D
294 let crs: AjtaiInstances = AjtaiInstances::new(&ep_1);
295
296 // create a new prover
297 let mut prover = LabradorProver::new(&ep_1, &crs, &witness_1, &st);
298 let _: LabradorTranscript<ShakeSponge> = prover.prove().unwrap();
299 }
300}