snarkvm_circuit_environment/helpers/
assignment.rs

1// Copyright (c) 2019-2025 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/// A struct that contains public variable assignments, private variable assignments,
25/// and constraint assignments.
26#[derive(Clone, Debug)]
27pub struct Assignment<F: PrimeField> {
28    /// The public variables.
29    public: Arc<[Variable<F>]>,
30    /// The private variables.
31    private: Arc<[Variable<F>]>,
32    /// The constraints.
33    constraints: Arc<[Arc<Constraint<F>>]>,
34    /// The number of constants, public, and private variables in the assignment.
35    num_variables: u64,
36}
37
38impl<F: PrimeField> From<crate::R1CS<F>> for Assignment<F> {
39    /// Converts an R1CS to an assignment.
40    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    /// Returns the public inputs of the assignment.
52    pub const fn public_inputs(&self) -> &Arc<[Variable<F>]> {
53        &self.public
54    }
55
56    /// Returns the private inputs of the assignment.
57    pub const fn private_inputs(&self) -> &Arc<[Variable<F>]> {
58        &self.private
59    }
60
61    /// Returns the constraints of the assignment.
62    pub const fn constraints(&self) -> &Arc<[Arc<Constraint<F>>]> {
63        &self.constraints
64    }
65
66    /// Returns the number of public variables in the assignment.
67    pub fn num_public(&self) -> u64 {
68        self.public.len() as u64
69    }
70
71    /// Returns the number of private variables in the assignment.
72    pub fn num_private(&self) -> u64 {
73        self.private.len() as u64
74    }
75
76    /// Returns the number of constants, public, and private variables in the assignment.
77    pub fn num_variables(&self) -> u64 {
78        self.num_variables
79    }
80
81    /// Returns the number of constraints in the assignment.
82    pub fn num_constraints(&self) -> u64 {
83        self.constraints.len() as u64
84    }
85
86    /// Returns the number of nonzeros in the assignment.
87    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    /// Synthesizes the constraints from the environment into a `snarkvm_algorithms::r1cs`-compliant constraint system.
100    fn generate_constraints<CS: snarkvm_algorithms::r1cs::ConstraintSystem<F>>(
101        &self,
102        cs: &mut CS,
103    ) -> Result<(), snarkvm_algorithms::r1cs::SynthesisError> {
104        /// A struct for tracking the mapping of variables from the virtual machine (first) to the gadget constraint system (second).
105        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        // Ensure the given `cs` is starting off clean.
113        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        // Allocate the public variables.
121        // NOTE: we skip the first public `One` variable because we already allocated it in the `ConstraintSystem` constructor.
122        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        // Allocate the private variables.
140        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        // Enforce all of the constraints.
158        for (i, constraint) in self.constraints.iter().enumerate() {
159            let (a, b, c) = constraint.to_terms();
160            // Converts terms from one linear combination in the first system to the second system.
161            let convert_linear_combination =
162                |lc: &LinearCombination<F>| -> snarkvm_algorithms::r1cs::LinearCombination<F> {
163                    // Initialize a linear combination for the second system.
164                    let mut linear_combination = snarkvm_algorithms::r1cs::LinearCombination::<F>::zero();
165
166                    // Process every term in the linear combination.
167                    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                    // Finally, add the accumulated constant value to the linear combination.
198                    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                    // Return the linear combination of the second system.
208                    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        // Ensure the given `cs` matches in size with the first system.
220        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    /// Compute 2^EXPONENT - 1, in a purposefully constraint-inefficient manner for testing.
235    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        // Compute 2^EXPONENT - 1, in a purposefully constraint-inefficient manner for testing.
242        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        // Varuna setup, prove, and verify.
288
289        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}