1use crate::{Constraint, LinearCombination, Variable};
17use snarkvm_fields::PrimeField;
18
19use indexmap::IndexMap;
20use std::sync::Arc;
21
22use super::R1CS;
23
24#[cfg(any(test, feature = "test"))]
25use anyhow::{Result, bail, ensure};
26
27#[derive(Clone, Debug)]
30pub struct Assignment<F: PrimeField> {
31 public: Arc<[Variable<F>]>,
33 private: Arc<[Variable<F>]>,
35 constraints: Arc<[Arc<Constraint<F>>]>,
37 num_variables: u64,
39}
40
41impl<F: PrimeField> From<crate::R1CS<F>> for Assignment<F> {
42 fn from(r1cs: crate::R1CS<F>) -> Self {
44 #[cfg(feature = "save_r1cs_hashes")]
45 r1cs.save_hash();
46
47 let R1CS { public, private, constraints, num_variables, .. } = r1cs;
48
49 Self { public: public.into(), private: private.into(), constraints: constraints.into(), num_variables }
50 }
51}
52
53impl<F: PrimeField> Assignment<F> {
54 pub const fn public_inputs(&self) -> &Arc<[Variable<F>]> {
56 &self.public
57 }
58
59 pub const fn private_inputs(&self) -> &Arc<[Variable<F>]> {
61 &self.private
62 }
63
64 pub const fn constraints(&self) -> &Arc<[Arc<Constraint<F>>]> {
66 &self.constraints
67 }
68
69 pub fn num_public(&self) -> u64 {
71 self.public.len() as u64
72 }
73
74 pub fn num_private(&self) -> u64 {
76 self.private.len() as u64
77 }
78
79 pub fn num_variables(&self) -> u64 {
81 self.num_variables
82 }
83
84 pub fn num_constraints(&self) -> u64 {
86 self.constraints.len() as u64
87 }
88
89 pub fn num_nonzeros(&self) -> (u64, u64, u64) {
91 self.constraints
92 .iter()
93 .map(|constraint| {
94 let (a, b, c) = constraint.to_terms();
95 (a.num_nonzeros(), b.num_nonzeros(), c.num_nonzeros())
96 })
97 .fold((0, 0, 0), |(a, b, c), (x, y, z)| (a.saturating_add(x), b.saturating_add(y), c.saturating_add(z)))
98 }
99}
100
101impl<F: PrimeField> snarkvm_algorithms::r1cs::ConstraintSynthesizer<F> for Assignment<F> {
102 fn generate_constraints<CS: snarkvm_algorithms::r1cs::ConstraintSystem<F>>(
104 &self,
105 cs: &mut CS,
106 ) -> Result<(), snarkvm_algorithms::r1cs::SynthesisError> {
107 struct Converter {
109 public: IndexMap<u64, snarkvm_algorithms::r1cs::Variable>,
110 private: IndexMap<u64, snarkvm_algorithms::r1cs::Variable>,
111 }
112
113 let mut converter = Converter { public: Default::default(), private: Default::default() };
114
115 assert_eq!(1, cs.num_public_variables());
117 assert_eq!(0, cs.num_private_variables());
118 assert_eq!(0, cs.num_constraints());
119
120 let result = converter.public.insert(0, CS::one());
121 assert!(result.is_none(), "Overwrote an existing public variable in the converter");
122
123 for (i, variable) in self.public.iter().skip(1).enumerate() {
126 let (index, value) = variable.index_value();
127 assert_eq!((i + 1) as u64, index, "Public vars in first system must be processed in lexicographic order");
128
129 let gadget = cs.alloc_input(|| format!("Public {i}"), || Ok(value))?;
130
131 assert_eq!(
132 snarkvm_algorithms::r1cs::Index::Public(index as usize),
133 gadget.get_unchecked(),
134 "Public variables in the second system must match the first system (with an off-by-1 for the public case)"
135 );
136
137 let result = converter.public.insert(index, gadget);
138
139 assert!(result.is_none(), "Overwrote an existing public variable in the converter");
140 }
141
142 for (i, variable) in self.private.iter().enumerate() {
144 let (index, value) = variable.index_value();
145 assert_eq!(i as u64, index, "Private variables in first system must be processed in lexicographic order");
146
147 let gadget = cs.alloc(|| format!("Private {i}"), || Ok(value))?;
148
149 assert_eq!(
150 snarkvm_algorithms::r1cs::Index::Private(i),
151 gadget.get_unchecked(),
152 "Private variables in the second system must match the first system"
153 );
154
155 let result = converter.private.insert(index, gadget);
156
157 assert!(result.is_none(), "Overwrote an existing private variable in the converter");
158 }
159
160 for (i, constraint) in self.constraints.iter().enumerate() {
162 let (a, b, c) = constraint.to_terms();
163 let convert_linear_combination =
165 |lc: &LinearCombination<F>| -> snarkvm_algorithms::r1cs::LinearCombination<F> {
166 let mut linear_combination = snarkvm_algorithms::r1cs::LinearCombination::<F>::zero();
168
169 for (variable, coefficient) in lc.to_terms() {
171 match variable {
172 Variable::Constant(_) => {
173 unreachable!(
174 "Failed during constraint translation. The first system by definition cannot have constant variables in the terms"
175 )
176 }
177 Variable::Public(index_value) => {
178 let (index, _) = index_value.as_ref();
179 let gadget = converter.public.get(index).unwrap();
180 assert_eq!(
181 snarkvm_algorithms::r1cs::Index::Public(*index as usize),
182 gadget.get_unchecked(),
183 "Failed during constraint translation. The public variable in the second system must match the first system (with an off-by-1 for the public case)"
184 );
185 linear_combination += (*coefficient, *gadget);
186 }
187 Variable::Private(index_value) => {
188 let (index, _) = index_value.as_ref();
189 let gadget = converter.private.get(index).unwrap();
190 assert_eq!(
191 snarkvm_algorithms::r1cs::Index::Private(*index as usize),
192 gadget.get_unchecked(),
193 "Failed during constraint translation. The private variable in the second system must match the first system"
194 );
195 linear_combination += (*coefficient, *gadget);
196 }
197 }
198 }
199
200 if !lc.to_constant().is_zero() {
202 linear_combination += (
203 lc.to_constant(),
204 snarkvm_algorithms::r1cs::Variable::new_unchecked(snarkvm_algorithms::r1cs::Index::Public(
205 0,
206 )),
207 );
208 }
209
210 linear_combination
212 };
213
214 cs.enforce(
215 || format!("Constraint {i}"),
216 |lc| lc + convert_linear_combination(a),
217 |lc| lc + convert_linear_combination(b),
218 |lc| lc + convert_linear_combination(c),
219 );
220 }
221
222 assert_eq!(self.num_public(), cs.num_public_variables() as u64);
224 assert_eq!(self.num_private(), cs.num_private_variables() as u64);
225 assert_eq!(self.num_constraints(), cs.num_constraints() as u64);
226
227 Ok(())
228 }
229}
230
231#[cfg(any(test, feature = "test"))]
234pub fn compare_constraints<F: PrimeField>(assignment_1: &Assignment<F>, assignment_2: &Assignment<F>) -> Result<()> {
235 ensure!(
236 assignment_1.num_public() == assignment_2.num_public(),
237 "Number of public variables in the assignments do not match: {} vs. {}",
238 assignment_1.num_public(),
239 assignment_2.num_public()
240 );
241 ensure!(
242 assignment_1.num_private() == assignment_2.num_private(),
243 "Number of private variables in the assignments do not match: {} vs. {}",
244 assignment_1.num_private(),
245 assignment_2.num_private()
246 );
247 ensure!(
248 assignment_1.num_constraints() == assignment_2.num_constraints(),
249 "Number of constraints in the assignments do not match: {} vs. {}",
250 assignment_1.num_constraints(),
251 assignment_2.num_constraints()
252 );
253
254 for (i, (constraint1, constraint2)) in
255 assignment_1.constraints().iter().zip(assignment_2.constraints().iter()).enumerate()
256 {
257 ensure!(
259 constraint1.0 == constraint2.0,
260 "Scopes in constraint {i} do not match: {} vs. {}",
261 constraint1.0,
262 constraint2.0
263 );
264
265 let (lc1a, lc1b, lc1c) = constraint1.to_terms();
267 let (lc2a, lc2b, lc2c) = constraint2.to_terms();
268
269 for ((lc1, lc2), letter) in [(lc1a, lc2a), (lc1b, lc2b), (lc1c, lc2c)].iter().zip(["A", "B", "C"].iter()) {
270 ensure!(
271 lc1.to_constant() == lc2.to_constant(),
272 "Constants in constraint {i} do not match: {} vs. {}",
273 lc1.to_constant(),
274 lc2.to_constant()
275 );
276
277 for (var1, var2) in lc1.to_terms().iter().zip(lc2.to_terms().iter()) {
278 ensure!(var1.1 == var2.1, "Coefficients in constraint {letter}{i} do not match: {var1:?} vs. {var2:?}");
279 match (&var1.0, &var2.0) {
280 (Variable::Constant(v1), Variable::Constant(v2)) => {
281 ensure!(
282 v1 == v2,
283 "Constant terms in constraint {letter}{i} do not match: {var1:?} vs. {var2:?}"
284 );
285 }
286 (Variable::Public(index_value1), Variable::Public(index_value2))
287 | (Variable::Private(index_value1), Variable::Private(index_value2)) => {
288 ensure!(
289 index_value1.0 == index_value2.0,
290 "Indices in variable terms in constraint {letter}{i} do not match: {var1:?} vs. {var2:?}"
291 );
292 }
293 _ => {
294 bail!("Terms in constraint {letter}{i} have incompatible types: {var1:?} vs. {var2:?}");
295 }
296 }
297 }
298 }
299 }
300
301 Ok(())
302}
303
304#[cfg(test)]
305mod tests {
306 use snarkvm_algorithms::{AlgebraicSponge, SNARK, r1cs::ConstraintSynthesizer, snark::varuna::VarunaVersion};
307 use snarkvm_circuit::prelude::*;
308 use snarkvm_curves::bls12_377::Fr;
309
310 fn create_example_circuit<E: Environment>() -> Field<E> {
312 let one = snarkvm_console_types::Field::<E::Network>::one();
313 let two = one + one;
314
315 const EXPONENT: u64 = 64;
316
317 let mut candidate = Field::<E>::new(Mode::Public, one);
319 let mut accumulator = Field::new(Mode::Private, two);
320 for _ in 0..EXPONENT {
321 candidate += &accumulator;
322 accumulator *= Field::new(Mode::Private, two);
323 }
324
325 assert_eq!((accumulator - Field::one()).eject_value(), candidate.eject_value());
326 assert_eq!(2, E::num_public());
327 assert_eq!(2 * EXPONENT + 1, E::num_private());
328 assert_eq!(EXPONENT, E::num_constraints());
329 assert!(E::is_satisfied());
330
331 candidate
332 }
333
334 #[test]
335 fn test_constraint_converter() {
336 let _candidate_output = create_example_circuit::<Circuit>();
337 let assignment = Circuit::eject_assignment_and_reset();
338 assert_eq!(0, Circuit::num_constants());
339 assert_eq!(1, Circuit::num_public());
340 assert_eq!(0, Circuit::num_private());
341 assert_eq!(0, Circuit::num_constraints());
342
343 let mut cs = snarkvm_algorithms::r1cs::TestConstraintSystem::new();
344 assignment.generate_constraints(&mut cs).unwrap();
345 {
346 use snarkvm_algorithms::r1cs::ConstraintSystem;
347 assert_eq!(assignment.num_public(), cs.num_public_variables() as u64);
348 assert_eq!(assignment.num_private(), cs.num_private_variables() as u64);
349 assert_eq!(assignment.num_constraints(), cs.num_constraints() as u64);
350 assert!(cs.is_satisfied());
351 }
352 }
353
354 #[test]
355 fn test_varuna() {
356 let _candidate_output = create_example_circuit::<Circuit>();
357 let assignment = Circuit::eject_assignment_and_reset();
358 assert_eq!(0, Circuit::num_constants());
359 assert_eq!(1, Circuit::num_public());
360 assert_eq!(0, Circuit::num_private());
361 assert_eq!(0, Circuit::num_constraints());
362
363 use snarkvm_algorithms::{
366 crypto_hash::PoseidonSponge,
367 snark::varuna::{VarunaHidingMode, VarunaSNARK, ahp::AHPForR1CS},
368 };
369 use snarkvm_curves::bls12_377::{Bls12_377, Fq};
370 use snarkvm_utilities::rand::TestRng;
371
372 type FS = PoseidonSponge<Fq, 2, 1>;
373 type VarunaInst = VarunaSNARK<Bls12_377, FS, VarunaHidingMode>;
374
375 let rng = &mut TestRng::default();
376
377 let max_degree = AHPForR1CS::<Fr, VarunaHidingMode>::max_degree(200, 200, 300).unwrap();
378 let universal_srs = VarunaInst::universal_setup(max_degree).unwrap();
379 let universal_prover = &universal_srs.to_universal_prover().unwrap();
380 let universal_verifier = &universal_srs.to_universal_verifier().unwrap();
381 let fs_pp = FS::sample_parameters();
382
383 let (index_pk, index_vk) = VarunaInst::circuit_setup(&universal_srs, &assignment).unwrap();
384 let varuna_version = VarunaVersion::V2;
385 println!("Called circuit setup");
386
387 let proof = VarunaInst::prove(universal_prover, &fs_pp, &index_pk, varuna_version, &assignment, rng).unwrap();
388 println!("Called prover");
389
390 let one = <Circuit as Environment>::BaseField::one();
391 assert!(VarunaInst::verify(universal_verifier, &fs_pp, &index_vk, varuna_version, [one, one], &proof).unwrap());
392 println!("Called verifier");
393 println!("\nShould not verify (i.e. verifier messages should print below):");
394 assert!(
395 !VarunaInst::verify(universal_verifier, &fs_pp, &index_vk, varuna_version, [one, one + one], &proof)
396 .unwrap()
397 );
398 }
399}