snarkvm_circuit_environment/
circuit.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::{Mode, helpers::Constraint, *};
17
18use core::{
19    cell::{Cell, RefCell},
20    fmt,
21};
22
23type Field = <console::MainnetV0 as console::Environment>::Field;
24
25thread_local! {
26    static VARIABLE_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
27    static CONSTRAINT_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
28    pub(super) static CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new());
29    static IN_WITNESS: Cell<bool> = const { Cell::new(false) };
30    static ZERO: LinearCombination<Field> = LinearCombination::zero();
31    static ONE: LinearCombination<Field> = LinearCombination::one();
32}
33
34#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
35pub struct Circuit;
36
37impl Environment for Circuit {
38    type Affine = <console::MainnetV0 as console::Environment>::Affine;
39    type BaseField = Field;
40    type Network = console::MainnetV0;
41    type ScalarField = <console::MainnetV0 as console::Environment>::Scalar;
42
43    /// Returns the `zero` constant.
44    fn zero() -> LinearCombination<Self::BaseField> {
45        ZERO.with(|zero| zero.clone())
46    }
47
48    /// Returns the `one` constant.
49    fn one() -> LinearCombination<Self::BaseField> {
50        ONE.with(|one| one.clone())
51    }
52
53    /// Returns a new variable of the given mode and value.
54    fn new_variable(mode: Mode, value: Self::BaseField) -> Variable<Self::BaseField> {
55        IN_WITNESS.with(|in_witness| {
56            // Ensure we are not in witness mode.
57            if !in_witness.get() {
58                // Ensure that we do not surpass the variable limit for the circuit.
59                VARIABLE_LIMIT.with(|variable_limit| {
60                    if let Some(limit) = variable_limit.get() {
61                        if Self::num_variables() > limit {
62                            Self::halt(format!("Surpassed the variable limit ({limit})"))
63                        }
64                    }
65                });
66                CIRCUIT.with(|circuit| match mode {
67                    Mode::Constant => circuit.borrow_mut().new_constant(value),
68                    Mode::Public => circuit.borrow_mut().new_public(value),
69                    Mode::Private => circuit.borrow_mut().new_private(value),
70                })
71            } else {
72                Self::halt("Tried to initialize a new variable in witness mode")
73            }
74        })
75    }
76
77    /// Returns a new witness of the given mode and value.
78    fn new_witness<Fn: FnOnce() -> Output::Primitive, Output: Inject>(mode: Mode, logic: Fn) -> Output {
79        IN_WITNESS.with(|in_witness| {
80            // Set the entire environment to witness mode.
81            in_witness.replace(true);
82
83            // Run the logic.
84            let output = logic();
85
86            // Return the entire environment from witness mode.
87            in_witness.replace(false);
88
89            Inject::new(mode, output)
90        })
91    }
92
93    // /// Appends the given scope to the current environment.
94    // fn push_scope(name: &str) {
95    //     CIRCUIT.with(|circuit| {
96    //         // Set the entire environment to the new scope.
97    //         match Self::cs().push_scope(name) {
98    //             Ok(()) => (),
99    //             Err(error) => Self::halt(error),
100    //         }
101    //     })
102    // }
103    //
104    // /// Removes the given scope from the current environment.
105    // fn pop_scope(name: &str) {
106    //     CIRCUIT.with(|circuit| {
107    //         // Return the entire environment to the previous scope.
108    //         match Self::cs().pop_scope(name) {
109    //             Ok(scope) => {
110    //                 scope
111    //             }
112    //             Err(error) => Self::halt(error),
113    //         }
114    //     })
115    // }
116
117    /// Enters a new scope for the environment.
118    fn scope<S: Into<String>, Fn, Output>(name: S, logic: Fn) -> Output
119    where
120        Fn: FnOnce() -> Output,
121    {
122        IN_WITNESS.with(|in_witness| {
123            // Ensure we are not in witness mode.
124            if !in_witness.get() {
125                CIRCUIT.with(|circuit| {
126                    // Set the entire environment to the new scope.
127                    let name = name.into();
128                    if let Err(error) = circuit.borrow_mut().push_scope(&name) {
129                        Self::halt(error)
130                    }
131
132                    // Run the logic.
133                    let output = logic();
134
135                    // Return the entire environment to the previous scope.
136                    if let Err(error) = circuit.borrow_mut().pop_scope(name) {
137                        Self::halt(error)
138                    }
139
140                    output
141                })
142            } else {
143                Self::halt("Tried to initialize a new scope in witness mode")
144            }
145        })
146    }
147
148    /// Adds one constraint enforcing that `(A * B) == C`.
149    fn enforce<Fn, A, B, C>(constraint: Fn)
150    where
151        Fn: FnOnce() -> (A, B, C),
152        A: Into<LinearCombination<Self::BaseField>>,
153        B: Into<LinearCombination<Self::BaseField>>,
154        C: Into<LinearCombination<Self::BaseField>>,
155    {
156        IN_WITNESS.with(|in_witness| {
157            // Ensure we are not in witness mode.
158            if !in_witness.get() {
159                CIRCUIT.with(|circuit| {
160                    // Ensure that we do not surpass the constraint limit for the circuit.
161                    CONSTRAINT_LIMIT.with(|constraint_limit| {
162                        if let Some(limit) = constraint_limit.get() {
163                            if circuit.borrow().num_constraints() > limit {
164                                Self::halt(format!("Surpassed the constraint limit ({limit})"))
165                            }
166                        }
167                    });
168
169                    let (a, b, c) = constraint();
170                    let (a, b, c) = (a.into(), b.into(), c.into());
171
172                    // Ensure the constraint is not comprised of constants.
173                    match a.is_constant() && b.is_constant() && c.is_constant() {
174                        true => {
175                            // Evaluate the constant constraint.
176                            assert_eq!(
177                                a.value() * b.value(),
178                                c.value(),
179                                "Constant constraint failed: ({a} * {b}) =?= {c}"
180                            );
181
182                            // match self.counter.scope().is_empty() {
183                            //     true => println!("Enforced constraint with constant terms: ({} * {}) =?= {}", a, b, c),
184                            //     false => println!(
185                            //         "Enforced constraint with constant terms ({}): ({} * {}) =?= {}",
186                            //         self.counter.scope(), a, b, c
187                            //     ),
188                            // }
189                        }
190                        false => {
191                            // Construct the constraint object.
192                            let constraint = Constraint(circuit.borrow().scope(), a, b, c);
193                            // Append the constraint.
194                            circuit.borrow_mut().enforce(constraint)
195                        }
196                    }
197                });
198            } else {
199                Self::halt("Tried to add a new constraint in witness mode")
200            }
201        })
202    }
203
204    /// Returns `true` if all constraints in the environment are satisfied.
205    fn is_satisfied() -> bool {
206        CIRCUIT.with(|circuit| circuit.borrow().is_satisfied())
207    }
208
209    /// Returns `true` if all constraints in the current scope are satisfied.
210    fn is_satisfied_in_scope() -> bool {
211        CIRCUIT.with(|circuit| circuit.borrow().is_satisfied_in_scope())
212    }
213
214    /// Returns the number of constants in the entire circuit.
215    fn num_constants() -> u64 {
216        CIRCUIT.with(|circuit| circuit.borrow().num_constants())
217    }
218
219    /// Returns the number of public variables in the entire circuit.
220    fn num_public() -> u64 {
221        CIRCUIT.with(|circuit| circuit.borrow().num_public())
222    }
223
224    /// Returns the number of private variables in the entire circuit.
225    fn num_private() -> u64 {
226        CIRCUIT.with(|circuit| circuit.borrow().num_private())
227    }
228
229    /// Returns the number of constant, public, and private variables in the entire circuit.
230    fn num_variables() -> u64 {
231        CIRCUIT.with(|circuit| circuit.borrow().num_variables())
232    }
233
234    /// Returns the number of constraints in the entire circuit.
235    fn num_constraints() -> u64 {
236        CIRCUIT.with(|circuit| circuit.borrow().num_constraints())
237    }
238
239    /// Returns the number of nonzeros in the entire circuit.
240    fn num_nonzeros() -> (u64, u64, u64) {
241        CIRCUIT.with(|circuit| circuit.borrow().num_nonzeros())
242    }
243
244    /// Returns the number of constants for the current scope.
245    fn num_constants_in_scope() -> u64 {
246        CIRCUIT.with(|circuit| circuit.borrow().num_constants_in_scope())
247    }
248
249    /// Returns the number of public variables for the current scope.
250    fn num_public_in_scope() -> u64 {
251        CIRCUIT.with(|circuit| circuit.borrow().num_public_in_scope())
252    }
253
254    /// Returns the number of private variables for the current scope.
255    fn num_private_in_scope() -> u64 {
256        CIRCUIT.with(|circuit| circuit.borrow().num_private_in_scope())
257    }
258
259    /// Returns the number of constraints for the current scope.
260    fn num_constraints_in_scope() -> u64 {
261        CIRCUIT.with(|circuit| circuit.borrow().num_constraints_in_scope())
262    }
263
264    /// Returns the number of nonzeros for the current scope.
265    fn num_nonzeros_in_scope() -> (u64, u64, u64) {
266        CIRCUIT.with(|circuit| circuit.borrow().num_nonzeros_in_scope())
267    }
268
269    /// Returns the variable limit for the circuit, if one exists.
270    fn get_variable_limit() -> Option<u64> {
271        VARIABLE_LIMIT.with(|current_limit| current_limit.get())
272    }
273
274    /// Sets the variable limit for the circuit.
275    fn set_variable_limit(limit: Option<u64>) {
276        VARIABLE_LIMIT.with(|current_limit| current_limit.replace(limit));
277    }
278
279    /// Returns the constraint limit for the circuit, if one exists.
280    fn get_constraint_limit() -> Option<u64> {
281        CONSTRAINT_LIMIT.with(|current_limit| current_limit.get())
282    }
283
284    /// Sets the constraint limit for the circuit.
285    fn set_constraint_limit(limit: Option<u64>) {
286        CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit));
287    }
288
289    /// Halts the program from further synthesis, evaluation, and execution in the current environment.
290    fn halt<S: Into<String>, T>(message: S) -> T {
291        let error = message.into();
292        // eprintln!("{}", &error);
293        panic!("{}", &error)
294    }
295
296    /// Returns the R1CS circuit, resetting the circuit.
297    fn inject_r1cs(r1cs: R1CS<Self::BaseField>) {
298        CIRCUIT.with(|circuit| {
299            // Ensure the circuit is empty before injecting.
300            assert_eq!(0, circuit.borrow().num_constants());
301            assert_eq!(1, circuit.borrow().num_public());
302            assert_eq!(0, circuit.borrow().num_private());
303            assert_eq!(1, circuit.borrow().num_variables());
304            assert_eq!(0, circuit.borrow().num_constraints());
305            // Inject the R1CS instance.
306            let r1cs = circuit.replace(r1cs);
307            // Ensure the circuit that was replaced is empty.
308            assert_eq!(0, r1cs.num_constants());
309            assert_eq!(1, r1cs.num_public());
310            assert_eq!(0, r1cs.num_private());
311            assert_eq!(1, r1cs.num_variables());
312            assert_eq!(0, r1cs.num_constraints());
313        })
314    }
315
316    /// Returns the R1CS circuit, resetting the circuit.
317    fn eject_r1cs_and_reset() -> R1CS<Self::BaseField> {
318        CIRCUIT.with(|circuit| {
319            // Reset the witness mode.
320            IN_WITNESS.with(|in_witness| in_witness.replace(false));
321            // Reset the variable limit.
322            Self::set_variable_limit(None);
323            // Reset the constraint limit.
324            Self::set_constraint_limit(None);
325            // Eject the R1CS instance.
326            let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
327            // Ensure the circuit is now empty.
328            assert_eq!(0, circuit.borrow().num_constants());
329            assert_eq!(1, circuit.borrow().num_public());
330            assert_eq!(0, circuit.borrow().num_private());
331            assert_eq!(1, circuit.borrow().num_variables());
332            assert_eq!(0, circuit.borrow().num_constraints());
333            // Return the R1CS instance.
334            r1cs
335        })
336    }
337
338    /// Returns the R1CS assignment of the circuit, resetting the circuit.
339    fn eject_assignment_and_reset() -> Assignment<<Self::Network as console::Environment>::Field> {
340        CIRCUIT.with(|circuit| {
341            // Reset the witness mode.
342            IN_WITNESS.with(|in_witness| in_witness.replace(false));
343            // Reset the variable limit.
344            Self::set_variable_limit(None);
345            // Reset the constraint limit.
346            Self::set_constraint_limit(None);
347            // Eject the R1CS instance.
348            let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
349            assert_eq!(0, circuit.borrow().num_constants());
350            assert_eq!(1, circuit.borrow().num_public());
351            assert_eq!(0, circuit.borrow().num_private());
352            assert_eq!(1, circuit.borrow().num_variables());
353            assert_eq!(0, circuit.borrow().num_constraints());
354            // Convert the R1CS instance to an assignment.
355            Assignment::from(r1cs)
356        })
357    }
358
359    /// Clears the circuit and initializes an empty environment.
360    fn reset() {
361        CIRCUIT.with(|circuit| {
362            // Reset the witness mode.
363            IN_WITNESS.with(|in_witness| in_witness.replace(false));
364            // Reset the variable limit.
365            Self::set_variable_limit(None);
366            // Reset the constraint limit.
367            Self::set_constraint_limit(None);
368            // Reset the circuit.
369            *circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new();
370            assert_eq!(0, circuit.borrow().num_constants());
371            assert_eq!(1, circuit.borrow().num_public());
372            assert_eq!(0, circuit.borrow().num_private());
373            assert_eq!(1, circuit.borrow().num_variables());
374            assert_eq!(0, circuit.borrow().num_constraints());
375        });
376    }
377}
378
379impl fmt::Display for Circuit {
380    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381        CIRCUIT.with(|circuit| write!(f, "{}", circuit.borrow()))
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use snarkvm_circuit::prelude::*;
388
389    /// Compute 2^EXPONENT - 1, in a purposefully constraint-inefficient manner for testing.
390    fn create_example_circuit<E: Environment>() -> Field<E> {
391        let one = snarkvm_console_types::Field::<E::Network>::one();
392        let two = one + one;
393
394        const EXPONENT: u64 = 64;
395
396        // Compute 2^EXPONENT - 1, in a purposefully constraint-inefficient manner for testing.
397        let mut candidate = Field::<E>::new(Mode::Public, one);
398        let mut accumulator = Field::new(Mode::Private, two);
399        for _ in 0..EXPONENT {
400            candidate += &accumulator;
401            accumulator *= Field::new(Mode::Private, two);
402        }
403
404        assert_eq!((accumulator - Field::one()).eject_value(), candidate.eject_value());
405        assert_eq!(2, E::num_public());
406        assert_eq!(2 * EXPONENT + 1, E::num_private());
407        assert_eq!(EXPONENT, E::num_constraints());
408        assert!(E::is_satisfied());
409
410        candidate
411    }
412
413    #[test]
414    fn test_print_circuit() {
415        let _candidate = create_example_circuit::<Circuit>();
416        let output = format!("{Circuit}");
417        println!("{output}");
418    }
419
420    #[test]
421    fn test_circuit_scope() {
422        Circuit::scope("test_circuit_scope", || {
423            assert_eq!(0, Circuit::num_constants());
424            assert_eq!(1, Circuit::num_public());
425            assert_eq!(0, Circuit::num_private());
426            assert_eq!(0, Circuit::num_constraints());
427
428            assert_eq!(0, Circuit::num_constants_in_scope());
429            assert_eq!(0, Circuit::num_public_in_scope());
430            assert_eq!(0, Circuit::num_private_in_scope());
431            assert_eq!(0, Circuit::num_constraints_in_scope());
432        })
433    }
434}