ark_r1cs_std/
alloc.rs

1use crate::Vec;
2use ark_ff::Field;
3use ark_relations::r1cs::{Namespace, SynthesisError};
4use core::borrow::Borrow;
5
6/// Describes the mode that a variable should be allocated in within
7/// a `ConstraintSystem`.
8#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone)]
9pub enum AllocationMode {
10    /// Indicate to the `ConstraintSystem` that the high-level variable should
11    /// be allocated as a constant. That is, no `Variable`s should be
12    /// generated.
13    Constant = 0,
14
15    /// Indicate to the `ConstraintSystem` that the high-level variable should
16    /// be allocated as a public input to the `ConstraintSystem`.
17    Input = 1,
18
19    /// Indicate to the `ConstraintSystem` that the high-level variable should
20    /// be allocated as a private witness to the `ConstraintSystem`.
21    Witness = 2,
22}
23
24impl AllocationMode {
25    /// Outputs the maximum according to the relation `Constant < Input <
26    /// Witness`.
27    pub fn max(&self, other: Self) -> Self {
28        use AllocationMode::*;
29        match (self, other) {
30            (Constant, _) => other,
31            (Input, Constant) => *self,
32            (Input, _) => other,
33            (Witness, _) => *self,
34        }
35    }
36}
37
38/// Specifies how variables of type `Self` should be allocated in a
39/// `ConstraintSystem`.
40pub trait AllocVar<V: ?Sized, F: Field>: Sized {
41    /// Allocates a new variable of type `Self` in the `ConstraintSystem` `cs`.
42    /// The mode of allocation is decided by `mode`.
43    fn new_variable<T: Borrow<V>>(
44        cs: impl Into<Namespace<F>>,
45        f: impl FnOnce() -> Result<T, SynthesisError>,
46        mode: AllocationMode,
47    ) -> Result<Self, SynthesisError>;
48
49    /// Allocates a new constant of type `Self` in the `ConstraintSystem` `cs`.
50    ///
51    /// This should *not* allocate any new variables or constraints in `cs`.
52    #[tracing::instrument(target = "r1cs", skip(cs, t))]
53    fn new_constant(
54        cs: impl Into<Namespace<F>>,
55        t: impl Borrow<V>,
56    ) -> Result<Self, SynthesisError> {
57        Self::new_variable(cs, || Ok(t), AllocationMode::Constant)
58    }
59
60    /// Allocates a new public input of type `Self` in the `ConstraintSystem`
61    /// `cs`.
62    #[tracing::instrument(target = "r1cs", skip(cs, f))]
63    fn new_input<T: Borrow<V>>(
64        cs: impl Into<Namespace<F>>,
65        f: impl FnOnce() -> Result<T, SynthesisError>,
66    ) -> Result<Self, SynthesisError> {
67        Self::new_variable(cs, f, AllocationMode::Input)
68    }
69
70    /// Allocates a new private witness of type `Self` in the `ConstraintSystem`
71    /// `cs`.
72    #[tracing::instrument(target = "r1cs", skip(cs, f))]
73    fn new_witness<T: Borrow<V>>(
74        cs: impl Into<Namespace<F>>,
75        f: impl FnOnce() -> Result<T, SynthesisError>,
76    ) -> Result<Self, SynthesisError> {
77        Self::new_variable(cs, f, AllocationMode::Witness)
78    }
79
80    /// Allocates a new constant or private witness of type `Self` in the
81    /// `ConstraintSystem` `cs` with the allocation mode inferred from `cs`.
82    /// A constant is allocated if `cs` is `None`, and a private witness is
83    /// allocated otherwise.
84    ///
85    /// A common use case is the creation of non-deterministic advice (a.k.a.
86    /// hints) in the circuit, where this method can avoid boilerplate code
87    /// while allowing optimization on circuit size.
88    ///
89    /// For example, to compute `x_var / y_var` where `y_var` is a non-zero
90    /// variable, one can write:
91    /// ```
92    /// use ark_ff::PrimeField;
93    /// use ark_r1cs_std::{alloc::AllocVar, fields::{fp::FpVar, FieldVar}, R1CSVar};
94    /// use ark_relations::r1cs::SynthesisError;
95    ///
96    /// fn div<F: PrimeField>(x_var: &FpVar<F>, y_var: &FpVar<F>) -> Result<FpVar<F>, SynthesisError> {
97    ///   let cs = x_var.cs().or(y_var.cs());
98    ///   let z_var = FpVar::new_variable_with_inferred_mode(cs, || Ok(x_var.value()? / y_var.value()?))?;
99    ///   z_var.mul_equals(y_var, x_var)?;
100    ///   Ok(z_var)
101    /// }
102    /// ```
103    /// In this example, if either `x_var` or `y_var` is a witness variable,
104    /// then `z_var` is also a witness variable. On the other hand, `z_var`
105    /// is a constant if both `x_var` and `y_var` are constants (i.e., `cs`
106    /// is `None`), and future operations on `z_var` do not generate any
107    /// constraints.
108    ///
109    /// (Note that we use division as an example for simplicity. You may
110    /// call `x_var.mul_by_inverse(y_var)?` directly, which internally works
111    /// similarly to the above code.)
112    #[tracing::instrument(target = "r1cs", skip(cs, f))]
113    fn new_variable_with_inferred_mode<T: Borrow<V>>(
114        cs: impl Into<Namespace<F>>,
115        f: impl FnOnce() -> Result<T, SynthesisError>,
116    ) -> Result<Self, SynthesisError> {
117        let ns: Namespace<F> = cs.into();
118        let cs = ns.cs();
119        let mode = if cs.is_none() {
120            AllocationMode::Constant
121        } else {
122            AllocationMode::Witness
123        };
124        Self::new_variable(cs, f, mode)
125    }
126}
127
128/// This blanket implementation just allocates variables in `Self`
129/// element by element.
130impl<I, F: Field, A: AllocVar<I, F>> AllocVar<[I], F> for Vec<A> {
131    fn new_variable<T: Borrow<[I]>>(
132        cs: impl Into<Namespace<F>>,
133        f: impl FnOnce() -> Result<T, SynthesisError>,
134        mode: AllocationMode,
135    ) -> Result<Self, SynthesisError> {
136        let ns = cs.into();
137        let cs = ns.cs();
138        f().and_then(|v| {
139            v.borrow()
140                .iter()
141                .map(|e| A::new_variable(cs.clone(), || Ok(e), mode))
142                .collect()
143        })
144    }
145}
146
147/// Dummy impl for `()`.
148impl<F: Field> AllocVar<(), F> for () {
149    fn new_variable<T: Borrow<()>>(
150        _cs: impl Into<Namespace<F>>,
151        _f: impl FnOnce() -> Result<T, SynthesisError>,
152        _mode: AllocationMode,
153    ) -> Result<Self, SynthesisError> {
154        Ok(())
155    }
156}
157
158/// This blanket implementation just allocates variables in `Self`
159/// element by element.
160impl<I, F: Field, A: AllocVar<I, F>, const N: usize> AllocVar<[I; N], F> for [A; N] {
161    fn new_variable<T: Borrow<[I; N]>>(
162        cs: impl Into<Namespace<F>>,
163        f: impl FnOnce() -> Result<T, SynthesisError>,
164        mode: AllocationMode,
165    ) -> Result<Self, SynthesisError> {
166        let ns = cs.into();
167        let cs = ns.cs();
168        f().map(|v| {
169            let v = v.borrow();
170            core::array::from_fn(|i| A::new_variable(cs.clone(), || Ok(&v[i]), mode).unwrap())
171        })
172    }
173}
174
175/// This blanket implementation just allocates variables in `Self`
176/// element by element.
177impl<I, F: Field, A: AllocVar<I, F>, const N: usize> AllocVar<[I], F> for [A; N] {
178    fn new_variable<T: Borrow<[I]>>(
179        cs: impl Into<Namespace<F>>,
180        f: impl FnOnce() -> Result<T, SynthesisError>,
181        mode: AllocationMode,
182    ) -> Result<Self, SynthesisError> {
183        let ns = cs.into();
184        let cs = ns.cs();
185        f().map(|v| {
186            let v = v.borrow();
187            core::array::from_fn(|i| A::new_variable(cs.clone(), || Ok(&v[i]), mode).unwrap())
188        })
189    }
190}