Skip to main content

snarkvm_circuit_environment/helpers/
assignment.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use 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/// A struct that contains public variable assignments, private variable assignments,
28/// and constraint assignments.
29#[derive(Clone, Debug)]
30pub struct Assignment<F: PrimeField> {
31    /// The public variables.
32    public: Arc<[Variable<F>]>,
33    /// The private variables.
34    private: Arc<[Variable<F>]>,
35    /// The constraints.
36    constraints: Arc<[Arc<Constraint<F>>]>,
37    /// The number of constants, public, and private variables in the assignment.
38    num_variables: u64,
39}
40
41impl<F: PrimeField> From<crate::R1CS<F>> for Assignment<F> {
42    /// Converts an R1CS to an assignment.
43    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    /// Returns the public inputs of the assignment.
55    pub const fn public_inputs(&self) -> &Arc<[Variable<F>]> {
56        &self.public
57    }
58
59    /// Returns the private inputs of the assignment.
60    pub const fn private_inputs(&self) -> &Arc<[Variable<F>]> {
61        &self.private
62    }
63
64    /// Returns the constraints of the assignment.
65    pub const fn constraints(&self) -> &Arc<[Arc<Constraint<F>>]> {
66        &self.constraints
67    }
68
69    /// Returns the number of public variables in the assignment.
70    pub fn num_public(&self) -> u64 {
71        self.public.len() as u64
72    }
73
74    /// Returns the number of private variables in the assignment.
75    pub fn num_private(&self) -> u64 {
76        self.private.len() as u64
77    }
78
79    /// Returns the number of constants, public, and private variables in the assignment.
80    pub fn num_variables(&self) -> u64 {
81        self.num_variables
82    }
83
84    /// Returns the number of constraints in the assignment.
85    pub fn num_constraints(&self) -> u64 {
86        self.constraints.len() as u64
87    }
88
89    /// Returns the number of nonzeros in the assignment.
90    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    /// Synthesizes the constraints from the environment into a `snarkvm_algorithms::r1cs`-compliant constraint system.
103    fn generate_constraints<CS: snarkvm_algorithms::r1cs::ConstraintSystem<F>>(
104        &self,
105        cs: &mut CS,
106    ) -> Result<(), snarkvm_algorithms::r1cs::SynthesisError> {
107        /// A struct for tracking the mapping of variables from the virtual machine (first) to the gadget constraint system (second).
108        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        // Ensure the given `cs` is starting off clean.
116        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        // Allocate the public variables.
124        // NOTE: we skip the first public `One` variable because we already allocated it in the `ConstraintSystem` constructor.
125        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        // Allocate the private variables.
143        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        // Enforce all of the constraints.
161        for (i, constraint) in self.constraints.iter().enumerate() {
162            let (a, b, c) = constraint.to_terms();
163            // Converts terms from one linear combination in the first system to the second system.
164            let convert_linear_combination =
165                |lc: &LinearCombination<F>| -> snarkvm_algorithms::r1cs::LinearCombination<F> {
166                    // Initialize a linear combination for the second system.
167                    let mut linear_combination = snarkvm_algorithms::r1cs::LinearCombination::<F>::zero();
168
169                    // Process every term in the linear combination.
170                    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                    // Finally, add the accumulated constant value to the linear combination.
201                    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                    // Return the linear combination of the second system.
211                    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        // Ensure the given `cs` matches in size with the first system.
223        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/// Asserts whether the underlying constraints of the two assignments are equal
232/// regardless of the concrete values taken in the assignments.
233#[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        // Compare the scopes
258        ensure!(
259            constraint1.0 == constraint2.0,
260            "Scopes in constraint {i} do not match: {} vs. {}",
261            constraint1.0,
262            constraint2.0
263        );
264
265        // Compare the constraint terms
266        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    /// Compute 2^EXPONENT - 1, in a purposefully constraint-inefficient manner for testing.
311    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        // Compute 2^EXPONENT - 1, in a purposefully constraint-inefficient manner for testing.
318        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        // Varuna setup, prove, and verify.
364
365        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}