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