snarkvm_circuit_environment/helpers/
assignment.rs1use crate::{Constraint, LinearCombination, Variable};
17use snarkvm_fields::PrimeField;
18
19use indexmap::IndexMap;
20use std::sync::Arc;
21
22use super::R1CS;
23
24#[derive(Clone, Debug)]
27pub struct Assignment<F: PrimeField> {
28 public: Arc<[Variable<F>]>,
30 private: Arc<[Variable<F>]>,
32 constraints: Arc<[Arc<Constraint<F>>]>,
34 num_variables: u64,
36}
37
38impl<F: PrimeField> From<crate::R1CS<F>> for Assignment<F> {
39 fn from(r1cs: crate::R1CS<F>) -> Self {
41 #[cfg(feature = "save_r1cs_hashes")]
42 r1cs.save_hash();
43
44 let R1CS { public, private, constraints, num_variables, .. } = r1cs;
45
46 Self { public: public.into(), private: private.into(), constraints: constraints.into(), num_variables }
47 }
48}
49
50impl<F: PrimeField> Assignment<F> {
51 pub const fn public_inputs(&self) -> &Arc<[Variable<F>]> {
53 &self.public
54 }
55
56 pub const fn private_inputs(&self) -> &Arc<[Variable<F>]> {
58 &self.private
59 }
60
61 pub const fn constraints(&self) -> &Arc<[Arc<Constraint<F>>]> {
63 &self.constraints
64 }
65
66 pub fn num_public(&self) -> u64 {
68 self.public.len() as u64
69 }
70
71 pub fn num_private(&self) -> u64 {
73 self.private.len() as u64
74 }
75
76 pub fn num_variables(&self) -> u64 {
78 self.num_variables
79 }
80
81 pub fn num_constraints(&self) -> u64 {
83 self.constraints.len() as u64
84 }
85
86 pub fn num_nonzeros(&self) -> (u64, u64, u64) {
88 self.constraints
89 .iter()
90 .map(|constraint| {
91 let (a, b, c) = constraint.to_terms();
92 (a.num_nonzeros(), b.num_nonzeros(), c.num_nonzeros())
93 })
94 .fold((0, 0, 0), |(a, b, c), (x, y, z)| (a.saturating_add(x), b.saturating_add(y), c.saturating_add(z)))
95 }
96}
97
98impl<F: PrimeField> snarkvm_algorithms::r1cs::ConstraintSynthesizer<F> for Assignment<F> {
99 fn generate_constraints<CS: snarkvm_algorithms::r1cs::ConstraintSystem<F>>(
101 &self,
102 cs: &mut CS,
103 ) -> Result<(), snarkvm_algorithms::r1cs::SynthesisError> {
104 struct Converter {
106 public: IndexMap<u64, snarkvm_algorithms::r1cs::Variable>,
107 private: IndexMap<u64, snarkvm_algorithms::r1cs::Variable>,
108 }
109
110 let mut converter = Converter { public: Default::default(), private: Default::default() };
111
112 assert_eq!(1, cs.num_public_variables());
114 assert_eq!(0, cs.num_private_variables());
115 assert_eq!(0, cs.num_constraints());
116
117 let result = converter.public.insert(0, CS::one());
118 assert!(result.is_none(), "Overwrote an existing public variable in the converter");
119
120 for (i, variable) in self.public.iter().skip(1).enumerate() {
123 let (index, value) = variable.index_value();
124 assert_eq!((i + 1) as u64, index, "Public vars in first system must be processed in lexicographic order");
125
126 let gadget = cs.alloc_input(|| format!("Public {i}"), || Ok(value))?;
127
128 assert_eq!(
129 snarkvm_algorithms::r1cs::Index::Public(index as usize),
130 gadget.get_unchecked(),
131 "Public variables in the second system must match the first system (with an off-by-1 for the public case)"
132 );
133
134 let result = converter.public.insert(index, gadget);
135
136 assert!(result.is_none(), "Overwrote an existing public variable in the converter");
137 }
138
139 for (i, variable) in self.private.iter().enumerate() {
141 let (index, value) = variable.index_value();
142 assert_eq!(i as u64, index, "Private variables in first system must be processed in lexicographic order");
143
144 let gadget = cs.alloc(|| format!("Private {i}"), || Ok(value))?;
145
146 assert_eq!(
147 snarkvm_algorithms::r1cs::Index::Private(i),
148 gadget.get_unchecked(),
149 "Private variables in the second system must match the first system"
150 );
151
152 let result = converter.private.insert(index, gadget);
153
154 assert!(result.is_none(), "Overwrote an existing private variable in the converter");
155 }
156
157 for (i, constraint) in self.constraints.iter().enumerate() {
159 let (a, b, c) = constraint.to_terms();
160 let convert_linear_combination =
162 |lc: &LinearCombination<F>| -> snarkvm_algorithms::r1cs::LinearCombination<F> {
163 let mut linear_combination = snarkvm_algorithms::r1cs::LinearCombination::<F>::zero();
165
166 for (variable, coefficient) in lc.to_terms() {
168 match variable {
169 Variable::Constant(_) => {
170 unreachable!(
171 "Failed during constraint translation. The first system by definition cannot have constant variables in the terms"
172 )
173 }
174 Variable::Public(index_value) => {
175 let (index, _) = index_value.as_ref();
176 let gadget = converter.public.get(index).unwrap();
177 assert_eq!(
178 snarkvm_algorithms::r1cs::Index::Public(*index as usize),
179 gadget.get_unchecked(),
180 "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)"
181 );
182 linear_combination += (*coefficient, *gadget);
183 }
184 Variable::Private(index_value) => {
185 let (index, _) = index_value.as_ref();
186 let gadget = converter.private.get(index).unwrap();
187 assert_eq!(
188 snarkvm_algorithms::r1cs::Index::Private(*index as usize),
189 gadget.get_unchecked(),
190 "Failed during constraint translation. The private variable in the second system must match the first system"
191 );
192 linear_combination += (*coefficient, *gadget);
193 }
194 }
195 }
196
197 if !lc.to_constant().is_zero() {
199 linear_combination += (
200 lc.to_constant(),
201 snarkvm_algorithms::r1cs::Variable::new_unchecked(snarkvm_algorithms::r1cs::Index::Public(
202 0,
203 )),
204 );
205 }
206
207 linear_combination
209 };
210
211 cs.enforce(
212 || format!("Constraint {i}"),
213 |lc| lc + convert_linear_combination(a),
214 |lc| lc + convert_linear_combination(b),
215 |lc| lc + convert_linear_combination(c),
216 );
217 }
218
219 assert_eq!(self.num_public(), cs.num_public_variables() as u64);
221 assert_eq!(self.num_private(), cs.num_private_variables() as u64);
222 assert_eq!(self.num_constraints(), cs.num_constraints() as u64);
223
224 Ok(())
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use snarkvm_algorithms::{AlgebraicSponge, SNARK, r1cs::ConstraintSynthesizer, snark::varuna::VarunaVersion};
231 use snarkvm_circuit::prelude::*;
232 use snarkvm_curves::bls12_377::Fr;
233
234 fn create_example_circuit<E: Environment>() -> Field<E> {
236 let one = snarkvm_console_types::Field::<E::Network>::one();
237 let two = one + one;
238
239 const EXPONENT: u64 = 64;
240
241 let mut candidate = Field::<E>::new(Mode::Public, one);
243 let mut accumulator = Field::new(Mode::Private, two);
244 for _ in 0..EXPONENT {
245 candidate += &accumulator;
246 accumulator *= Field::new(Mode::Private, two);
247 }
248
249 assert_eq!((accumulator - Field::one()).eject_value(), candidate.eject_value());
250 assert_eq!(2, E::num_public());
251 assert_eq!(2 * EXPONENT + 1, E::num_private());
252 assert_eq!(EXPONENT, E::num_constraints());
253 assert!(E::is_satisfied());
254
255 candidate
256 }
257
258 #[test]
259 fn test_constraint_converter() {
260 let _candidate_output = create_example_circuit::<Circuit>();
261 let assignment = Circuit::eject_assignment_and_reset();
262 assert_eq!(0, Circuit::num_constants());
263 assert_eq!(1, Circuit::num_public());
264 assert_eq!(0, Circuit::num_private());
265 assert_eq!(0, Circuit::num_constraints());
266
267 let mut cs = snarkvm_algorithms::r1cs::TestConstraintSystem::new();
268 assignment.generate_constraints(&mut cs).unwrap();
269 {
270 use snarkvm_algorithms::r1cs::ConstraintSystem;
271 assert_eq!(assignment.num_public(), cs.num_public_variables() as u64);
272 assert_eq!(assignment.num_private(), cs.num_private_variables() as u64);
273 assert_eq!(assignment.num_constraints(), cs.num_constraints() as u64);
274 assert!(cs.is_satisfied());
275 }
276 }
277
278 #[test]
279 fn test_varuna() {
280 let _candidate_output = create_example_circuit::<Circuit>();
281 let assignment = Circuit::eject_assignment_and_reset();
282 assert_eq!(0, Circuit::num_constants());
283 assert_eq!(1, Circuit::num_public());
284 assert_eq!(0, Circuit::num_private());
285 assert_eq!(0, Circuit::num_constraints());
286
287 use snarkvm_algorithms::{
290 crypto_hash::PoseidonSponge,
291 snark::varuna::{VarunaHidingMode, VarunaSNARK, ahp::AHPForR1CS},
292 };
293 use snarkvm_curves::bls12_377::{Bls12_377, Fq};
294 use snarkvm_utilities::rand::TestRng;
295
296 type FS = PoseidonSponge<Fq, 2, 1>;
297 type VarunaInst = VarunaSNARK<Bls12_377, FS, VarunaHidingMode>;
298
299 let rng = &mut TestRng::default();
300
301 let max_degree = AHPForR1CS::<Fr, VarunaHidingMode>::max_degree(200, 200, 300).unwrap();
302 let universal_srs = VarunaInst::universal_setup(max_degree).unwrap();
303 let universal_prover = &universal_srs.to_universal_prover().unwrap();
304 let universal_verifier = &universal_srs.to_universal_verifier().unwrap();
305 let fs_pp = FS::sample_parameters();
306
307 let (index_pk, index_vk) = VarunaInst::circuit_setup(&universal_srs, &assignment).unwrap();
308 let varuna_version = VarunaVersion::V2;
309 println!("Called circuit setup");
310
311 let proof = VarunaInst::prove(universal_prover, &fs_pp, &index_pk, varuna_version, &assignment, rng).unwrap();
312 println!("Called prover");
313
314 let one = <Circuit as Environment>::BaseField::one();
315 assert!(VarunaInst::verify(universal_verifier, &fs_pp, &index_vk, varuna_version, [one, one], &proof).unwrap());
316 println!("Called verifier");
317 println!("\nShould not verify (i.e. verifier messages should print below):");
318 assert!(
319 !VarunaInst::verify(universal_verifier, &fs_pp, &index_vk, varuna_version, [one, one + one], &proof)
320 .unwrap()
321 );
322 }
323}