testudo/src/constraints.rs
src/constraints.rsRaw
1use ark_ec::pairing::Pairing;
2use std::borrow::Borrow;
3
4use crate::{
5 math::Math,
6 sparse_mlpoly::{SparsePolyEntry, SparsePolynomial},
7 unipoly::UniPoly,
8};
9
10use ark_ff::PrimeField;
11
12use ark_crypto_primitives::sponge::{
13 constraints::CryptographicSpongeVar,
14 poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig},
15};
16use ark_poly_commit::multilinear_pc::data_structures::Commitment;
17use ark_r1cs_std::{
18 alloc::{AllocVar, AllocationMode},
19 fields::fp::FpVar,
20 prelude::{EqGadget, FieldVar},
21};
22use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
23
24pub struct PoseidonTranscriptVar<F>
25where
26 F: PrimeField,
27{
28 pub cs: ConstraintSystemRef<F>,
29 pub sponge: PoseidonSpongeVar<F>,
30}
31
32impl<F> PoseidonTranscriptVar<F>
33where
34 F: PrimeField,
35{
36 pub fn new(cs: ConstraintSystemRef<F>, params: &PoseidonConfig<F>, c_var: FpVar<F>) -> Self {
37 let mut sponge = PoseidonSpongeVar::new(cs.clone(), params);
38
39 sponge.absorb(&c_var).unwrap();
40
41 Self { cs, sponge }
42 }
43
44 fn append(&mut self, input: &FpVar<F>) -> Result<(), SynthesisError> {
45 self.sponge.absorb(&input)
46 }
47
48 fn append_vector(&mut self, input_vec: &[FpVar<F>]) -> Result<(), SynthesisError> {
49 for input in input_vec.iter() {
50 self.append(input)?;
51 }
52 Ok(())
53 }
54
55 fn challenge(&mut self) -> Result<FpVar<F>, SynthesisError> {
56 Ok(self.sponge.squeeze_field_elements(1).unwrap().remove(0))
57 }
58
59 fn challenge_scalar_vec(&mut self, len: usize) -> Result<Vec<FpVar<F>>, SynthesisError> {
60 let c_vars = self.sponge.squeeze_field_elements(len).unwrap();
61 Ok(c_vars)
62 }
63}
64
65/// Univariate polynomial in constraint system
66#[derive(Clone)]
67pub struct UniPolyVar<F: PrimeField> {
68 pub coeffs: Vec<FpVar<F>>,
69}
70
71impl<F: PrimeField> AllocVar<UniPoly<F>, F> for UniPolyVar<F> {
72 fn new_variable<T: Borrow<UniPoly<F>>>(
73 cs: impl Into<Namespace<F>>,
74 f: impl FnOnce() -> Result<T, SynthesisError>,
75 mode: AllocationMode,
76 ) -> Result<Self, SynthesisError> {
77 f().and_then(|c| {
78 let cs = cs.into();
79 let cp: &UniPoly<F> = c.borrow();
80 let mut coeffs_var = Vec::new();
81 for coeff in cp.coeffs.iter() {
82 let coeff_var = FpVar::<F>::new_variable(cs.clone(), || Ok(coeff), mode)?;
83 coeffs_var.push(coeff_var);
84 }
85 Ok(Self { coeffs: coeffs_var })
86 })
87 }
88}
89
90impl<F: PrimeField> UniPolyVar<F> {
91 pub fn eval_at_zero(&self) -> FpVar<F> {
92 self.coeffs[0].clone()
93 }
94
95 pub fn eval_at_one(&self) -> FpVar<F> {
96 let mut res = self.coeffs[0].clone();
97 for i in 1..self.coeffs.len() {
98 res = &res + &self.coeffs[i];
99 }
100 res
101 }
102
103 // TODO check if mul without reduce can help
104 pub fn evaluate(&self, r: &FpVar<F>) -> FpVar<F> {
105 let mut eval = self.coeffs[0].clone();
106 let mut power = r.clone();
107
108 for i in 1..self.coeffs.len() {
109 eval += &power * &self.coeffs[i];
110 power *= r;
111 }
112 eval
113 }
114}
115
116/// Circuit gadget that implements the sumcheck verifier
117#[derive(Clone)]
118pub struct SumcheckVerificationCircuit<F: PrimeField> {
119 pub polys: Vec<UniPoly<F>>,
120}
121
122impl<F: PrimeField> SumcheckVerificationCircuit<F> {
123 pub fn verify_sumcheck(
124 poly_vars: &[UniPolyVar<F>],
125 claim_var: &FpVar<F>,
126 transcript_var: &mut PoseidonTranscriptVar<F>,
127 ) -> Result<(FpVar<F>, Vec<FpVar<F>>), SynthesisError> {
128 let mut e_var = claim_var.clone();
129 let mut r_vars: Vec<FpVar<F>> = Vec::new();
130
131 for poly_var in poly_vars.iter() {
132 let res = poly_var.eval_at_one() + poly_var.eval_at_zero();
133 res.enforce_equal(&e_var)?;
134 transcript_var.append_vector(&poly_var.coeffs)?;
135 let r_i_var = transcript_var.challenge()?;
136 r_vars.push(r_i_var.clone());
137 e_var = poly_var.evaluate(&r_i_var.clone());
138 }
139
140 Ok((e_var, r_vars))
141 }
142}
143
144#[derive(Clone)]
145pub struct SparsePolyEntryVar<F: PrimeField> {
146 idx: usize,
147 val_var: FpVar<F>,
148}
149
150impl<F: PrimeField> AllocVar<SparsePolyEntry<F>, F> for SparsePolyEntryVar<F> {
151 fn new_variable<T: Borrow<SparsePolyEntry<F>>>(
152 cs: impl Into<Namespace<F>>,
153 f: impl FnOnce() -> Result<T, SynthesisError>,
154 _mode: AllocationMode,
155 ) -> Result<Self, SynthesisError> {
156 f().and_then(|s| {
157 let cs = cs.into();
158 let spe: &SparsePolyEntry<F> = s.borrow();
159 let val_var = FpVar::<F>::new_witness(cs, || Ok(spe.val))?;
160 Ok(Self {
161 idx: spe.idx,
162 val_var,
163 })
164 })
165 }
166}
167
168#[derive(Clone)]
169pub struct SparsePolynomialVar<F: PrimeField> {
170 Z_var: Vec<SparsePolyEntryVar<F>>,
171}
172
173impl<F: PrimeField> AllocVar<SparsePolynomial<F>, F> for SparsePolynomialVar<F> {
174 fn new_variable<T: Borrow<SparsePolynomial<F>>>(
175 cs: impl Into<Namespace<F>>,
176 f: impl FnOnce() -> Result<T, SynthesisError>,
177 mode: AllocationMode,
178 ) -> Result<Self, SynthesisError> {
179 f().and_then(|s| {
180 let cs = cs.into();
181 let sp: &SparsePolynomial<F> = s.borrow();
182 let mut Z_var = Vec::new();
183 for spe in sp.Z.iter() {
184 let spe_var = SparsePolyEntryVar::new_variable(cs.clone(), || Ok(spe), mode)?;
185 Z_var.push(spe_var);
186 }
187 Ok(Self { Z_var })
188 })
189 }
190}
191
192impl<F: PrimeField> SparsePolynomialVar<F> {
193 fn compute_chi(a: &[bool], r_vars: &[FpVar<F>]) -> FpVar<F> {
194 let mut chi_i_var = FpVar::<F>::one();
195 let one = FpVar::<F>::one();
196 for (i, r_var) in r_vars.iter().enumerate() {
197 if a[i] {
198 chi_i_var *= r_var;
199 } else {
200 chi_i_var *= &one - r_var;
201 }
202 }
203 chi_i_var
204 }
205
206 pub fn evaluate(&self, r_var: &[FpVar<F>]) -> FpVar<F> {
207 let mut sum = FpVar::<F>::zero();
208 for spe_var in self.Z_var.iter() {
209 // potential problem
210 let bits = &spe_var.idx.get_bits(r_var.len());
211 sum += SparsePolynomialVar::compute_chi(bits, r_var) * &spe_var.val_var;
212 }
213 sum
214 }
215}
216
217#[derive(Clone)]
218pub struct R1CSVerificationCircuit<F: PrimeField> {
219 pub num_vars: usize,
220 pub num_cons: usize,
221 pub input: Vec<F>,
222 pub input_as_sparse_poly: SparsePolynomial<F>,
223 pub evals: (F, F, F),
224 pub params: PoseidonConfig<F>,
225 pub prev_challenge: F,
226 pub claims_phase2: (F, F, F, F),
227 pub eval_vars_at_ry: F,
228 pub sc_phase1: SumcheckVerificationCircuit<F>,
229 pub sc_phase2: SumcheckVerificationCircuit<F>,
230 // The point on which the polynomial was evaluated by the prover.
231 pub claimed_rx: Vec<F>,
232 pub claimed_ry: Vec<F>,
233 pub claimed_transcript_sat_state: F,
234}
235
236impl<F: PrimeField> R1CSVerificationCircuit<F> {
237 pub fn new<E: Pairing<ScalarField = F>>(config: &VerifierConfig<E>) -> Self {
238 Self {
239 num_vars: config.num_vars,
240 num_cons: config.num_cons,
241 input: config.input.clone(),
242 input_as_sparse_poly: config.input_as_sparse_poly.clone(),
243 evals: config.evals,
244 params: config.params.clone(),
245 prev_challenge: config.prev_challenge,
246 claims_phase2: config.claims_phase2,
247 eval_vars_at_ry: config.eval_vars_at_ry,
248 sc_phase1: SumcheckVerificationCircuit {
249 polys: config.polys_sc1.clone(),
250 },
251 sc_phase2: SumcheckVerificationCircuit {
252 polys: config.polys_sc2.clone(),
253 },
254 claimed_rx: config.rx.clone(),
255 claimed_ry: config.ry.clone(),
256 claimed_transcript_sat_state: config.transcript_sat_state,
257 }
258 }
259}
260
261/// This section implements the sumcheck verification part of Spartan
262impl<F: PrimeField> ConstraintSynthesizer<F> for R1CSVerificationCircuit<F> {
263 fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> ark_relations::r1cs::Result<()> {
264 let initial_challenge_var = FpVar::<F>::new_input(cs.clone(), || Ok(self.prev_challenge))?;
265 let mut transcript_var =
266 PoseidonTranscriptVar::new(cs.clone(), &self.params, initial_challenge_var);
267
268 let poly_sc1_vars = self
269 .sc_phase1
270 .polys
271 .iter()
272 .map(|p| UniPolyVar::new_variable(cs.clone(), || Ok(p), AllocationMode::Witness).unwrap())
273 .collect::<Vec<UniPolyVar<_>>>();
274
275 let poly_sc2_vars = self
276 .sc_phase2
277 .polys
278 .iter()
279 .map(|p| UniPolyVar::new_variable(cs.clone(), || Ok(p), AllocationMode::Witness).unwrap())
280 .collect::<Vec<UniPolyVar<_>>>();
281
282 let input_vars = self
283 .input
284 .iter()
285 .map(|i| FpVar::<F>::new_variable(cs.clone(), || Ok(i), AllocationMode::Input).unwrap())
286 .collect::<Vec<FpVar<F>>>();
287
288 let claimed_rx_vars = self
289 .claimed_rx
290 .iter()
291 .map(|r| FpVar::<F>::new_variable(cs.clone(), || Ok(r), AllocationMode::Input).unwrap())
292 .collect::<Vec<FpVar<F>>>();
293
294 let claimed_ry_vars = self
295 .claimed_ry
296 .iter()
297 .map(|r| FpVar::<F>::new_variable(cs.clone(), || Ok(r), AllocationMode::Input).unwrap())
298 .collect::<Vec<FpVar<F>>>();
299
300 transcript_var.append_vector(&input_vars)?;
301
302 let num_rounds_x = self.num_cons.log_2();
303 let _num_rounds_y = (2 * self.num_vars).log_2();
304
305 let tau_vars = transcript_var.challenge_scalar_vec(num_rounds_x)?;
306
307 let claim_phase1_var = FpVar::<F>::new_witness(cs.clone(), || Ok(F::zero()))?;
308
309 let (claim_post_phase1_var, rx_var) = SumcheckVerificationCircuit::<F>::verify_sumcheck(
310 &poly_sc1_vars,
311 &claim_phase1_var,
312 &mut transcript_var,
313 )?;
314
315 // The prover sends (rx, ry) to the verifier for the evaluation proof so
316 // the constraints need to ensure it is indeed the result from the first
317 // round of sumcheck verification.
318 for (i, r) in claimed_rx_vars.iter().enumerate() {
319 rx_var[i].enforce_equal(r)?;
320 }
321
322 let (Az_claim, Bz_claim, Cz_claim, prod_Az_Bz_claims) = &self.claims_phase2;
323
324 let Az_claim_var = FpVar::<F>::new_witness(cs.clone(), || Ok(Az_claim))?;
325 let Bz_claim_var = FpVar::<F>::new_witness(cs.clone(), || Ok(Bz_claim))?;
326 let Cz_claim_var = FpVar::<F>::new_witness(cs.clone(), || Ok(Cz_claim))?;
327 let prod_Az_Bz_claim_var = FpVar::<F>::new_witness(cs.clone(), || Ok(prod_Az_Bz_claims))?;
328 let one = FpVar::<F>::one();
329 let prod_vars: Vec<FpVar<F>> = (0..rx_var.len())
330 .map(|i| (&rx_var[i] * &tau_vars[i]) + (&one - &rx_var[i]) * (&one - &tau_vars[i]))
331 .collect();
332 let mut taus_bound_rx_var = FpVar::<F>::one();
333
334 for p_var in prod_vars.iter() {
335 taus_bound_rx_var *= p_var;
336 }
337
338 let expected_claim_post_phase1_var =
339 (&prod_Az_Bz_claim_var - &Cz_claim_var) * &taus_bound_rx_var;
340
341 claim_post_phase1_var.enforce_equal(&expected_claim_post_phase1_var)?;
342
343 let r_A_var = transcript_var.challenge()?;
344 let r_B_var = transcript_var.challenge()?;
345 let r_C_var = transcript_var.challenge()?;
346
347 let claim_phase2_var =
348 &r_A_var * &Az_claim_var + &r_B_var * &Bz_claim_var + &r_C_var * &Cz_claim_var;
349
350 let (claim_post_phase2_var, ry_var) = SumcheckVerificationCircuit::<F>::verify_sumcheck(
351 &poly_sc2_vars,
352 &claim_phase2_var,
353 &mut transcript_var,
354 )?;
355
356 // Because the verifier checks the commitment opening on point ry outside
357 // the circuit, the prover needs to send ry to the verifier (making the
358 // proof size O(log n)). As this point is normally obtained by the verifier
359 // from the second round of sumcheck, the circuit needs to ensure the
360 // claimed point, coming from the prover, is actually the point derived
361 // inside the circuit. These additional checks will be removed
362 // when the commitment verification is done inside the circuit.
363 // Moreover, (rx, ry) will be used in the evaluation proof.
364 for (i, r) in claimed_ry_vars.iter().enumerate() {
365 ry_var[i].enforce_equal(r)?;
366 }
367
368 let input_as_sparse_poly_var = SparsePolynomialVar::new_variable(
369 cs.clone(),
370 || Ok(&self.input_as_sparse_poly),
371 AllocationMode::Witness,
372 )?;
373
374 let poly_input_eval_var = input_as_sparse_poly_var.evaluate(&ry_var[1..]);
375
376 let eval_vars_at_ry_var = FpVar::<F>::new_input(cs.clone(), || Ok(&self.eval_vars_at_ry))?;
377
378 let eval_Z_at_ry_var =
379 (FpVar::<F>::one() - &ry_var[0]) * &eval_vars_at_ry_var + &ry_var[0] * &poly_input_eval_var;
380
381 let (eval_A_r, eval_B_r, eval_C_r) = self.evals;
382
383 let eval_A_r_var = FpVar::<F>::new_input(cs.clone(), || Ok(eval_A_r))?;
384 let eval_B_r_var = FpVar::<F>::new_input(cs.clone(), || Ok(eval_B_r))?;
385 let eval_C_r_var = FpVar::<F>::new_input(cs.clone(), || Ok(eval_C_r))?;
386
387 let scalar_var = &r_A_var * &eval_A_r_var + &r_B_var * &eval_B_r_var + &r_C_var * &eval_C_r_var;
388
389 let expected_claim_post_phase2_var = eval_Z_at_ry_var * scalar_var;
390 claim_post_phase2_var.enforce_equal(&expected_claim_post_phase2_var)?;
391 let expected_transcript_state_var = transcript_var.challenge()?;
392 let claimed_transcript_state_var =
393 FpVar::<F>::new_input(cs, || Ok(self.claimed_transcript_sat_state))?;
394
395 // Ensure that the prover and verifier transcipt views are consistent at
396 // the end of the satisfiability proof.
397 expected_transcript_state_var.enforce_equal(&claimed_transcript_state_var)?;
398 Ok(())
399 }
400}
401
402#[derive(Clone)]
403pub struct VerifierConfig<E: Pairing> {
404 pub comm: Commitment<E>,
405 pub num_vars: usize,
406 pub num_cons: usize,
407 pub input: Vec<E::ScalarField>,
408 pub input_as_sparse_poly: SparsePolynomial<E::ScalarField>,
409 pub evals: (E::ScalarField, E::ScalarField, E::ScalarField),
410 pub params: PoseidonConfig<E::ScalarField>,
411 pub prev_challenge: E::ScalarField,
412 pub claims_phase2: (
413 E::ScalarField,
414 E::ScalarField,
415 E::ScalarField,
416 E::ScalarField,
417 ),
418 pub eval_vars_at_ry: E::ScalarField,
419 pub polys_sc1: Vec<UniPoly<E::ScalarField>>,
420 pub polys_sc2: Vec<UniPoly<E::ScalarField>>,
421 pub rx: Vec<E::ScalarField>,
422 pub ry: Vec<E::ScalarField>,
423 pub transcript_sat_state: E::ScalarField,
424}
425
426// Skeleton for the polynomial commitment verification circuit
427// #[derive(Clone)]
428// pub struct VerifierCircuit {
429// pub inner_circuit: R1CSVerificationCircuit,
430// pub inner_proof: GrothProof<I>,
431// pub inner_vk: PreparedVerifyingKey<I>,
432// pub eval_vars_at_ry: Fr,
433// pub claims_phase2: (Fr, Fr, Fr, Fr),
434// pub ry: Vec<Fr>,
435// pub transcript_sat_state: Scalar,
436// }
437
438// impl VerifierCircuit {
439// pub fn new<R: Rng + CryptoRng>(
440// config: &VerifierConfig,
441// mut rng: &mut R,
442// ) -> Result<Self, SynthesisError> {
443// let inner_circuit = R1CSVerificationCircuit::new(config);
444// let (pk, vk) = Groth16::<I>::setup(inner_circuit.clone(), &mut rng).unwrap();
445// let proof = Groth16::<I>::prove(&pk, inner_circuit.clone(), &mut rng)?;
446// let pvk = Groth16::<I>::process_vk(&vk).unwrap();
447// Ok(Self {
448// inner_circuit,
449// inner_proof: proof,
450// inner_vk: pvk,
451// eval_vars_at_ry: config.eval_vars_at_ry,
452// claims_phase2: config.claims_phase2,
453// ry: config.ry.clone(),
454// transcript_sat_state: config.transcript_sat_state,
455// })
456// }
457// }
458
459// impl ConstraintSynthesizer<Fq> for VerifierCircuit {
460// fn generate_constraints(self, cs: ConstraintSystemRef<Fq>) -> ark_relations::r1cs::Result<()> {
461// let proof_var = ProofVar::<I, IV>::new_witness(cs.clone(), || Ok(self.inner_proof.clone()))?;
462// let (v_A, v_B, v_C, v_AB) = self.claims_phase2;
463// let mut pubs = vec![];
464// pubs.extend(self.ry);
465// pubs.extend(vec![v_A, v_B, v_C, v_AB]);
466// pubs.extend(vec![self.eval_vars_at_ry, self.transcript_sat_state]);
467// let bits = pubs
468// .iter()
469// .map(|c| {
470// let bits: Vec<bool> = BitIteratorLE::new(c.into_bigint().as_ref().to_vec()).collect();
471// Vec::new_witness(cs.clone(), || Ok(bits))
472// })
473// .collect::<Result<Vec<_>, _>>()?;
474// let input_var = BooleanInputVar::<Fr, Fq>::new(bits);
475// let vk_var = PreparedVerifyingKeyVar::new_witness(cs, || Ok(self.inner_vk.clone()))?;
476// Groth16VerifierGadget::verify_with_processed_vk(&vk_var, &input_var, &proof_var)?
477// .enforce_equal(&Boolean::constant(true))?;
478// Ok(())
479// }
480// }