Skip to main content

sonobe_primitives/circuits/
utils.rs

1//! This module provides utility circuits.
2
3use ark_ff::{Field, PrimeField};
4use ark_r1cs_std::{
5    GR1CSVar,
6    alloc::AllocVar,
7    fields::fp::{AllocatedFp, FpVar},
8};
9use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable};
10
11use super::Assignments;
12use crate::{arithmetizations::r1cs::R1CS, circuits::FCircuit, traits::SonobeField};
13
14/// [`CircuitForTest`] implements a simple test circuit computing
15/// `y = x^3 + x + 5` with 4 R1CS constraints.
16///
17/// It is used in unit tests to verify constraint extraction and witness
18/// generation.
19pub struct CircuitForTest<F: PrimeField> {
20    /// [`CircuitForTest::x`] is the input variable `x` of the circuit.
21    pub x: F,
22}
23
24impl<F: PrimeField> ConstraintSynthesizer<F> for CircuitForTest<F> {
25    fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
26        // Variable 0 (implicitly added by arkworks as 1)
27        // Variable 1
28        let x = AllocatedFp::new_input(cs.clone(), || Ok(self.x))?;
29        // Variable 2
30        let y = AllocatedFp::new_witness(cs.clone(), || Ok(self.x.pow([3]) + self.x + F::from(5)))?;
31
32        // Variable 3, Constraint 0
33        let x_square = x.square()?;
34        // Variable 4, Constraint 1
35        let x_cube = x_square.mul(&x);
36        // Variable 5
37        let t = AllocatedFp::new_witness(cs.clone(), || Ok(self.x.pow([3]) + self.x))?;
38        let x_cube_plus_x = x.add(&x_cube);
39        // Constraint 2
40        cs.enforce_r1cs_constraint(
41            || x_cube_plus_x.variable.into(),
42            || Variable::one().into(),
43            || t.variable.into(),
44        )?;
45        let x_cube_plus_x_plus_5 = t.add_constant(F::from(5));
46        // Constraint 3
47        cs.enforce_r1cs_constraint(
48            || x_cube_plus_x_plus_5.variable.into(),
49            || Variable::one().into(),
50            || y.variable.into(),
51        )?;
52        Ok(())
53    }
54}
55
56impl<F: SonobeField> FCircuit for CircuitForTest<F> {
57    type Field = F;
58    type State = [F; 1];
59    type StateVar = [FpVar<F>; 1];
60
61    type ExternalInputs = ();
62    type ExternalOutputs = ();
63
64    fn dummy_state(&self) -> Self::State {
65        [F::zero(); 1]
66    }
67
68    fn same_state_shape(_a: &Self::State, _b: &Self::State) -> bool {
69        // `[F; 1]` is fixed-size, so all states share the same shape.
70        true
71    }
72
73    fn dummy_external_inputs(&self) -> Self::ExternalInputs {}
74
75    fn generate_step_constraints(
76        &self,
77        _i: FpVar<Self::Field>,
78        z_i: Self::StateVar,
79        _external_inputs: Self::ExternalInputs,
80    ) -> Result<(Self::StateVar, Self::ExternalOutputs), SynthesisError> {
81        let cs = z_i.cs();
82
83        // Variable 0 (implicitly added by arkworks as 1)
84        // Variable 1
85        let x = if let FpVar::Var(x) = z_i[0].clone() {
86            x
87        } else {
88            unreachable!()
89        };
90        // Variable 2
91        let y = AllocatedFp::new_witness(cs.clone(), || {
92            Ok(x.value()?.pow([3]) + x.value()? + F::from(5))
93        })?;
94
95        // Variable 3, Constraint 0
96        let x_square = x.square()?;
97        // Variable 4, Constraint 1
98        let x_cube = x_square.mul(&x);
99        // Variable 5
100        let t = AllocatedFp::new_witness(cs.clone(), || Ok(x.value()?.pow([3]) + x.value()?))?;
101        let x_cube_plus_x = x.add(&x_cube);
102        // Constraint 2
103        cs.enforce_r1cs_constraint(
104            || x_cube_plus_x.variable.into(),
105            || Variable::one().into(),
106            || t.variable.into(),
107        )?;
108        let x_cube_plus_x_plus_5 = t.add_constant(F::from(5));
109        // Constraint 3
110        cs.enforce_r1cs_constraint(
111            || x_cube_plus_x_plus_5.variable.into(),
112            || Variable::one().into(),
113            || y.variable.into(),
114        )?;
115        Ok(([FpVar::Var(x_cube_plus_x_plus_5)], ()))
116    }
117}
118
119/// [`constraints_for_test`] returns the R1CS constraints for the test circuit.
120#[allow(non_snake_case)]
121pub fn constraints_for_test<F: Field>() -> R1CS<F> {
122    // R1CS for: x^3 + x + 5 = y (example from article
123    // https://vitalik.eth.limo/general/2016/12/10/qap.html)
124    let A = vec![
125        vec![(F::one(), 1)],
126        vec![(F::one(), 3)],
127        vec![(F::one(), 1), (F::one(), 4)],
128        vec![(F::from(5), 0), (F::one(), 5)],
129    ];
130    let B = vec![
131        vec![(F::one(), 1)],
132        vec![(F::one(), 1)],
133        vec![(F::one(), 0)],
134        vec![(F::one(), 0)],
135    ];
136    let C = vec![
137        vec![(F::one(), 3)],
138        vec![(F::one(), 4)],
139        vec![(F::one(), 5)],
140        vec![(F::one(), 2)],
141    ];
142
143    R1CS::<F>::new_without_validity_check(4, 6, 1, [A, B, C])
144}
145
146/// [`satisfying_assignments_for_test`] returns a satisfying assignment for the
147/// test circuit given an input `x`.
148pub fn satisfying_assignments_for_test<F: Field>(x: F) -> Assignments<F, Vec<F>> {
149    Assignments::from((
150        F::one(),
151        vec![x],
152        vec![
153            x * x * x + x + F::from(5), // x^3 + x + 5
154            x * x,                      // x^2
155            x * x * x,                  // x^2 * x
156            x * x * x + x,              // x^3 + x
157        ],
158    ))
159}