Skip to main content

ark_r1cs_std/boolean/
allocated.rs

1use core::borrow::Borrow;
2
3use ark_ff::{Field, PrimeField};
4use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError, Variable};
5
6use crate::{
7    alloc::{AllocVar, AllocationMode},
8    select::CondSelectGadget,
9};
10
11use super::Boolean;
12
13/// Represents a variable in the constraint system which is guaranteed
14/// to be either zero or one.
15///
16/// In general, one should prefer using `Boolean` instead of `AllocatedBool`,
17/// as `Boolean` offers better support for constant values, and implements
18/// more traits.
19#[derive(Clone, Debug, Eq, PartialEq)]
20#[must_use]
21pub struct AllocatedBool<F: Field> {
22    pub(super) variable: Variable,
23    pub(super) cs: ConstraintSystemRef<F>,
24    pub(super) value: Option<bool>,
25}
26
27impl<F: Field> AllocatedBool<F> {
28    /// Get the assigned value for `self`.
29    pub fn value(&self) -> Result<bool, SynthesisError> {
30        self.value.ok_or(SynthesisError::AssignmentMissing)
31    }
32
33    /// Get the R1CS variable for `self`.
34    pub fn variable(&self) -> Variable {
35        self.variable
36    }
37
38    /// Allocate a witness variable without a booleanity check.
39    #[doc(hidden)]
40    pub fn new_witness_without_booleanity_check<T: Borrow<bool>>(
41        cs: ConstraintSystemRef<F>,
42        f: impl FnOnce() -> Result<T, SynthesisError>,
43    ) -> Result<Self, SynthesisError> {
44        let mut value = None;
45        let f = || {
46            value = Some(*f()?.borrow());
47            value.ok_or(SynthesisError::AssignmentMissing)
48        };
49        let variable = cs.new_witness_variable(|| f().map(F::from))?;
50        Ok(Self {
51            variable,
52            cs,
53            value,
54        })
55    }
56
57    /// Performs an XOR operation over the two operands, returning
58    /// an `AllocatedBool`.
59    #[tracing::instrument(target = "gr1cs")]
60    pub fn not(&self) -> Result<Self, SynthesisError> {
61        let variable = self.cs.new_lc(|| lc_diff![Variable::One, self.variable])?;
62        Ok(Self {
63            variable,
64            cs: self.cs.clone(),
65            value: self.value.map(|v| !v),
66        })
67    }
68
69    /// Performs an XOR operation over the two operands, returning
70    /// an `AllocatedBool`.
71    #[tracing::instrument(target = "gr1cs")]
72    pub fn xor(&self, b: &Self) -> Result<Self, SynthesisError> {
73        let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || {
74            Ok(self.value()? ^ b.value()?)
75        })?;
76
77        // Constrain (a + a) * (b) = (a + b - c)
78        // Given that a and b are boolean constrained, if they
79        // are equal, the only solution for c is 0, and if they
80        // are different, the only solution for c is 1.
81        //
82        // ¬(a ∧ b) ∧ ¬(¬a ∧ ¬b) = c
83        // (1 - (a * b)) * (1 - ((1 - a) * (1 - b))) = c
84        // (1 - ab) * (1 - (1 - a - b + ab)) = c
85        // (1 - ab) * (a + b - ab) = c
86        // a + b - ab - (a^2)b - (b^2)a + (a^2)(b^2) = c
87        // a + b - ab - ab - ab + ab = c
88        // a + b - 2ab = c
89        // -2a * b = c - a - b
90        // 2a * b = a + b - c
91        // (a + a) * b = a + b - c
92        self.cs.enforce_r1cs_constraint(
93            || (F::ONE.double(), self.variable).into(),
94            || b.variable.into(),
95            || {
96                lc![
97                    (F::ONE, self.variable),
98                    (F::ONE, b.variable),
99                    (-F::ONE, result.variable),
100                ]
101            },
102        )?;
103
104        Ok(result)
105    }
106
107    /// Performs an AND operation over the two operands, returning
108    /// an `AllocatedBool`.
109    #[tracing::instrument(target = "gr1cs")]
110    pub fn and(&self, b: &Self) -> Result<Self, SynthesisError> {
111        let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || {
112            Ok(self.value()? & b.value()?)
113        })?;
114
115        // Constrain (a) * (b) = (c), ensuring c is 1 iff
116        // a AND b are both 1.
117        self.cs.enforce_r1cs_constraint(
118            || self.variable.into(),
119            || b.variable.into(),
120            || result.variable.into(),
121        )?;
122
123        Ok(result)
124    }
125
126    /// Performs an OR operation over the two operands, returning
127    /// an `AllocatedBool`.
128    #[tracing::instrument(target = "gr1cs")]
129    pub fn or(&self, b: &Self) -> Result<Self, SynthesisError> {
130        let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || {
131            Ok(self.value()? | b.value()?)
132        })?;
133
134        // Constrain (1 - a) * (1 - b) = (1 - c), ensuring c is 0 iff
135        // a and b are both false, and otherwise c is 1.
136        self.cs.enforce_r1cs_constraint(
137            || lc_diff![Variable::One, self.variable],
138            || lc_diff![Variable::One, b.variable],
139            || lc_diff![Variable::One, result.variable],
140        )?;
141
142        Ok(result)
143    }
144
145    /// Calculates `a AND (NOT b)`.
146    #[tracing::instrument(target = "gr1cs")]
147    pub fn and_not(&self, b: &Self) -> Result<Self, SynthesisError> {
148        let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || {
149            Ok(self.value()? & !b.value()?)
150        })?;
151
152        // Constrain (a) * (1 - b) = (c), ensuring c is 1 iff
153        // a is true and b is false, and otherwise c is 0.
154        self.cs.enforce_r1cs_constraint(
155            || lc!() + self.variable,
156            || lc_diff![Variable::One, b.variable],
157            || lc!() + result.variable,
158        )?;
159
160        Ok(result)
161    }
162
163    /// Calculates `(NOT a) AND (NOT b)`.
164    #[tracing::instrument(target = "gr1cs")]
165    pub fn nor(&self, b: &Self) -> Result<Self, SynthesisError> {
166        let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || {
167            Ok(!(self.value()? | b.value()?))
168        })?;
169
170        // Constrain (1 - a) * (1 - b) = (c), ensuring c is 1 iff
171        // a and b are both false, and otherwise c is 0.
172        self.cs.enforce_r1cs_constraint(
173            || lc_diff![Variable::One, self.variable],
174            || lc_diff![Variable::One, b.variable],
175            || result.variable.into(),
176        )?;
177
178        Ok(result)
179    }
180}
181
182impl<F: Field> AllocVar<bool, F> for AllocatedBool<F> {
183    /// Produces a new variable of the appropriate kind
184    /// (instance or witness), with a booleanity check.
185    ///
186    /// N.B.: we could omit the booleanity check when allocating `self`
187    /// as a new public input, but that places an additional burden on
188    /// protocol designers. Better safe than sorry!
189    fn new_variable<T: Borrow<bool>>(
190        cs: impl Into<Namespace<F>>,
191        f: impl FnOnce() -> Result<T, SynthesisError>,
192        mode: AllocationMode,
193    ) -> Result<Self, SynthesisError> {
194        let ns = cs.into();
195        let cs = ns.cs();
196        if mode == AllocationMode::Constant {
197            let value = *f()?.borrow();
198            let variable = if value { Variable::One } else { Variable::Zero };
199            Ok(Self {
200                variable,
201                cs,
202                value: Some(value),
203            })
204        } else {
205            let mut value = None;
206            let f = || {
207                value = Some(*f()?.borrow());
208                value.ok_or(SynthesisError::AssignmentMissing)
209            };
210            let variable = if mode == AllocationMode::Input {
211                cs.new_input_variable(|| f().map(F::from))?
212            } else {
213                cs.new_witness_variable(|| f().map(F::from))?
214            };
215
216            // Constrain: (1 - a) * a = 0
217            // This constrains a to be either 0 or 1.
218
219            cs.enforce_r1cs_constraint(
220                || lc_diff![Variable::One, variable],
221                || variable.into(),
222                || lc!(),
223            )?;
224
225            Ok(Self {
226                variable,
227                cs,
228                value,
229            })
230        }
231    }
232}
233
234impl<F: PrimeField> CondSelectGadget<F> for AllocatedBool<F> {
235    #[tracing::instrument(target = "gr1cs")]
236    fn conditionally_select(
237        cond: &Boolean<F>,
238        true_val: &Self,
239        false_val: &Self,
240    ) -> Result<Self, SynthesisError> {
241        let res = Boolean::conditionally_select(
242            cond,
243            &true_val.clone().into(),
244            &false_val.clone().into(),
245        )?;
246        match res {
247            Boolean::Var(a) => Ok(a),
248            _ => unreachable!("Impossible"),
249        }
250    }
251}
252
253#[cfg(test)]
254mod test {
255    use super::*;
256
257    use ark_relations::gr1cs::ConstraintSystem;
258    use ark_test_curves::bls12_381::Fr;
259    #[test]
260    fn allocated_xor() -> Result<(), SynthesisError> {
261        for a_val in [false, true].iter().copied() {
262            for b_val in [false, true].iter().copied() {
263                let cs = ConstraintSystem::<Fr>::new_ref();
264                let a = AllocatedBool::new_witness(cs.clone(), || Ok(a_val))?;
265                let b = AllocatedBool::new_witness(cs.clone(), || Ok(b_val))?;
266                let c = AllocatedBool::xor(&a, &b)?;
267                assert_eq!(c.value()?, a_val ^ b_val);
268
269                assert!(cs.is_satisfied().unwrap());
270                assert_eq!(a.value()?, (a_val));
271                assert_eq!(b.value()?, (b_val));
272                assert_eq!(c.value()?, (a_val ^ b_val));
273            }
274        }
275        Ok(())
276    }
277
278    #[test]
279    fn allocated_or() -> Result<(), SynthesisError> {
280        for a_val in [false, true].iter().copied() {
281            for b_val in [false, true].iter().copied() {
282                let cs = ConstraintSystem::<Fr>::new_ref();
283                let a = AllocatedBool::new_witness(cs.clone(), || Ok(a_val))?;
284                let b = AllocatedBool::new_witness(cs.clone(), || Ok(b_val))?;
285                let c = AllocatedBool::or(&a, &b)?;
286                assert_eq!(c.value()?, a_val | b_val);
287
288                assert!(cs.is_satisfied().unwrap());
289                assert_eq!(a.value()?, (a_val));
290                assert_eq!(b.value()?, (b_val));
291                assert_eq!(c.value()?, (a_val | b_val));
292            }
293        }
294        Ok(())
295    }
296
297    #[test]
298    fn allocated_and() -> Result<(), SynthesisError> {
299        for a_val in [false, true].iter().copied() {
300            for b_val in [false, true].iter().copied() {
301                let cs = ConstraintSystem::<Fr>::new_ref();
302                let a = AllocatedBool::new_witness(cs.clone(), || Ok(a_val))?;
303                let b = AllocatedBool::new_witness(cs.clone(), || Ok(b_val))?;
304                let c = AllocatedBool::and(&a, &b)?;
305                assert_eq!(c.value()?, a_val & b_val);
306
307                assert!(cs.is_satisfied().unwrap());
308                assert_eq!(a.value()?, (a_val));
309                assert_eq!(b.value()?, (b_val));
310                assert_eq!(c.value()?, (a_val & b_val));
311            }
312        }
313        Ok(())
314    }
315
316    #[test]
317    fn allocated_and_not() -> Result<(), SynthesisError> {
318        for a_val in [false, true].iter().copied() {
319            for b_val in [false, true].iter().copied() {
320                let cs = ConstraintSystem::<Fr>::new_ref();
321                let a = AllocatedBool::new_witness(cs.clone(), || Ok(a_val))?;
322                let b = AllocatedBool::new_witness(cs.clone(), || Ok(b_val))?;
323                let c = AllocatedBool::and_not(&a, &b)?;
324                assert_eq!(c.value()?, a_val & !b_val);
325
326                assert!(cs.is_satisfied().unwrap());
327                assert_eq!(a.value()?, (a_val));
328                assert_eq!(b.value()?, (b_val));
329                assert_eq!(c.value()?, (a_val & !b_val));
330            }
331        }
332        Ok(())
333    }
334
335    #[test]
336    fn allocated_nor() -> Result<(), SynthesisError> {
337        for a_val in [false, true].iter().copied() {
338            for b_val in [false, true].iter().copied() {
339                let cs = ConstraintSystem::<Fr>::new_ref();
340                let a = AllocatedBool::new_witness(cs.clone(), || Ok(a_val))?;
341                let b = AllocatedBool::new_witness(cs.clone(), || Ok(b_val))?;
342                let c = AllocatedBool::nor(&a, &b)?;
343                assert_eq!(c.value()?, !a_val & !b_val);
344
345                assert!(cs.is_satisfied().unwrap());
346                assert_eq!(a.value()?, (a_val));
347                assert_eq!(b.value()?, (b_val));
348                assert_eq!(c.value()?, (!a_val & !b_val));
349            }
350        }
351        Ok(())
352    }
353}