Skip to main content

commonware_cryptography/zk/
circuit.rs

1//! Utilities for creating arithmetic circuits.
2//!
3//! A [`Circuit`] holds the additions and multiplications making up a
4//! computation over a ring `F`, along with assertions that two values in
5//! the computation are equal. The inputs to the computation are constants,
6//! or witnesses, whose values are chosen by the prover. Proof systems
7//! consume circuits to prove that the assertions hold, without revealing
8//! the witnesses.
9//!
10//! Circuits are built by writing plain Rust over [`Var`], which implements
11//! the algebra traits from [`commonware_math`]. This allows the same code to
12//! be generic over `F` and `Var<F>`. The building code runs in one of two
13//! modes:
14//!
15//! - [`build`] records only the circuit itself (verifier mode),
16//! - [`build_with_values`] also computes every value in the computation as
17//!   the circuit is constructed (prover mode).
18//!
19//! Because both modes run the same code, the prover and the verifier
20//! construct the same circuit.
21//!
22//! # Example
23//!
24//! ```
25//! use commonware_cryptography::zk::circuit::{build_with_values, Var};
26//! use commonware_math::test::F;
27//!
28//! // Constrain a witness `x` to satisfy `x^3 + x + 5 = 35`.
29//! let (valued, _) = build_with_values(|ctx| {
30//!     let x = Var::witness(ctx, |_| F::from(3u64));
31//!     let out = x.clone() * &x * &x + &x + &Var::constant(ctx, F::from(5u64));
32//!     out.assert_eq(&Var::constant(ctx, F::from(35u64)));
33//!     Vec::new()
34//! });
35//! assert!(valued.is_satisfied());
36//! ```
37//!
38//! # Caveats
39//!
40//! ## Witness Closures
41//!
42//! The `init` closure passed to [`Var::witness`] must not use the
43//! [`Context`], for example by creating new vars: the build deadlocks,
44//! hanging without an error. Compute the witness value using only the
45//! [`Values`] view the closure receives.
46//!
47//! ## Inversion
48//!
49//! [`Field::inv`] requires that inverting zero produce zero. Circuit-backed
50//! vars deviate: inverting zero adds an unsatisfiable constraint instead,
51//! with no error when building. Generic code relying on `inv(0) = 0` will
52//! produce circuits that can never be satisfied.
53
54use commonware_math::algebra::{Additive, Field, Multiplicative, Object, Ring};
55use commonware_utils::sync::Mutex;
56use std::{
57    fmt,
58    marker::PhantomData,
59    ops::{
60        Add, AddAssign, BitAnd, BitOr, Div, DivAssign, Index, Mul, MulAssign, Neg, Not, Sub,
61        SubAssign,
62    },
63};
64
65/// Identifies a value in a [`Circuit`]: a constant, a witness, or the
66/// output of an operation.
67///
68/// Witnesses are numbered in allocation order, letting callers name
69/// specific witnesses after building, for example to choose which values a
70/// proof system should commit to.
71#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
72pub enum CircuitIdx {
73    Constant(u32),
74    Witness(u32),
75    Node(u32),
76}
77
78/// An addition or multiplication of two earlier values.
79pub(crate) enum CircuitNode {
80    Add(CircuitIdx, CircuitIdx),
81    Mul(CircuitIdx, CircuitIdx),
82}
83
84/// An arithmetic circuit over `F`.
85///
86/// Create one with [`build`] or [`build_with_values`]. On its own, a
87/// circuit only describes constraints; proving and verifying that they hold
88/// is the job of a proof system consuming it.
89//
90// Exposing the structure of the circuit directly (here and in CircuitNode
91// and ValuedCircuit) is not ideal: proof systems would be better served by
92// an abstraction over it. We should wait until we have a few different
93// backends before designing one, so that we don't freeze an abstraction
94// that won't work for all of our use cases. In the meantime, exposing the
95// structure at the crate pub level is not harmful.
96pub struct Circuit<F> {
97    pub(crate) witnesses: u32,
98    pub(crate) constants: Vec<F>,
99    pub(crate) nodes: Vec<CircuitNode>,
100    pub(crate) assertions: Vec<(CircuitIdx, CircuitIdx)>,
101}
102
103impl<F> Default for Circuit<F> {
104    fn default() -> Self {
105        Self {
106            witnesses: 0,
107            constants: Vec::new(),
108            nodes: Vec::new(),
109            assertions: Vec::new(),
110        }
111    }
112}
113
114impl<F> Circuit<F> {
115    const fn next_witness(&mut self) -> CircuitIdx {
116        let next = CircuitIdx::Witness(self.witnesses);
117        self.witnesses += 1;
118        next
119    }
120
121    fn next_constant(&mut self, x: F) -> CircuitIdx {
122        let next = CircuitIdx::Constant(self.constants.len() as u32);
123        self.constants.push(x);
124        next
125    }
126
127    fn next_node(&mut self, n: CircuitNode) -> CircuitIdx {
128        let next = CircuitIdx::Node(self.nodes.len() as u32);
129        self.nodes.push(n);
130        next
131    }
132}
133
134/// A circuit together with concrete values for its whole computation.
135///
136/// Produced by [`build_with_values`]. Use [`Self::is_satisfied`] to check
137/// whether the values satisfy the circuit's assertions.
138pub struct ValuedCircuit<F> {
139    pub(crate) circuit: Circuit<F>,
140    pub(crate) witnesses: Vec<F>,
141    pub(crate) nodes: Vec<F>,
142}
143
144#[doc(hidden)]
145impl<F> Index<CircuitIdx> for ValuedCircuit<F> {
146    type Output = F;
147
148    fn index(&self, index: CircuitIdx) -> &Self::Output {
149        match index {
150            CircuitIdx::Constant(i) => &self.circuit.constants[i as usize],
151            CircuitIdx::Witness(i) => &self.witnesses[i as usize],
152            CircuitIdx::Node(i) => &self.nodes[i as usize],
153        }
154    }
155}
156
157impl<F: PartialEq> ValuedCircuit<F> {
158    /// Checks whether the values assigned to this circuit satisfy its assertions.
159    #[must_use]
160    pub fn is_satisfied(&self) -> bool {
161        self.circuit
162            .assertions
163            .iter()
164            .all(|&(a, b)| self[a] == self[b])
165    }
166}
167
168struct ValuesBuilder<F> {
169    witnesses: Vec<F>,
170    nodes: Vec<F>,
171}
172
173/// A view of the values assigned so far during prover-mode construction.
174///
175/// A view is passed to witness `init` closures, which read the values of
176/// earlier vars with [`Var::value`]. This is how a prover supplies values
177/// that are cheaper to verify than to compute with circuit operations, such
178/// as inverses: compute the value natively, then constrain it with
179/// assertions.
180///
181/// Closures receiving a view must not call back into the [`Context`], for
182/// example by creating new vars: doing so deadlocks.
183///
184/// # Example
185///
186/// ```
187/// use commonware_cryptography::zk::circuit::{build_with_values, Var};
188/// use commonware_math::{
189///     algebra::{Field, Ring},
190///     test::F,
191/// };
192///
193/// let (valued, _) = build_with_values(|ctx| {
194///     let x = Var::witness(ctx, |_| F::from(3u64));
195///     // The prover computes the inverse natively...
196///     let inv = Var::witness(ctx, {
197///         let x = x.clone();
198///         move |v| x.value(v).inv()
199///     });
200///     // ...and the circuit checks it with a single multiplication.
201///     (x * &inv).assert_eq(&Var::one());
202///     Vec::new()
203/// });
204/// assert!(valued.is_satisfied());
205/// ```
206pub struct Values<'a, F> {
207    constants: &'a [F],
208    witnesses: &'a [F],
209    nodes: &'a [F],
210}
211
212// Manual `Copy`/`Clone` so they hold for any `F`: the derived versions would
213// add a spurious `F: Copy`/`F: Clone` bound, but `Values` only holds slices.
214impl<F> Clone for Values<'_, F> {
215    fn clone(&self) -> Self {
216        *self
217    }
218}
219impl<F> Copy for Values<'_, F> {}
220
221#[doc(hidden)]
222impl<'a, F> Index<CircuitIdx> for Values<'a, F> {
223    type Output = F;
224
225    fn index(&self, index: CircuitIdx) -> &Self::Output {
226        match index {
227            CircuitIdx::Witness(id) => &self.witnesses[id as usize],
228            CircuitIdx::Constant(id) => &self.constants[id as usize],
229            CircuitIdx::Node(id) => &self.nodes[id as usize],
230        }
231    }
232}
233
234struct ContextInner<F> {
235    values: Option<Mutex<ValuesBuilder<F>>>,
236    circuit: Mutex<Circuit<F>>,
237}
238
239/// A handle for recording operations into a circuit being built.
240///
241/// A context is passed to the closure given to [`build`] or
242/// [`build_with_values`], and is captured by the [`Var`]s created from it.
243/// Contexts are `Copy`, so they can be passed around freely; vars from two
244/// different builds cannot be mixed.
245pub struct Context<'ctx, F> {
246    inner: &'ctx ContextInner<F>,
247    /// Make this struct invariant in 'ctx, so two Contexts from different
248    /// `build` calls have incompatible types.
249    _brand: PhantomData<fn(&'ctx ()) -> &'ctx ()>,
250}
251
252impl<F> Clone for Context<'_, F> {
253    fn clone(&self) -> Self {
254        *self
255    }
256}
257impl<F> Copy for Context<'_, F> {}
258
259impl<'ctx, F> Context<'ctx, F> {
260    fn allocate_constant(self, combine: impl Fn(&[F]) -> F) -> CircuitIdx {
261        let mut circuit = self.inner.circuit.lock();
262        let combined = combine(&circuit.constants);
263        circuit.next_constant(combined)
264    }
265
266    fn allocate(
267        self,
268        init: impl for<'a> FnOnce(Values<'a, F>) -> Option<F>,
269        reserve: impl FnOnce(&mut Circuit<F>) -> CircuitIdx,
270    ) -> CircuitIdx {
271        // Both locks are held while `init` runs, so an `init` closure that
272        // calls back into the Context deadlocks. This is why Values forbids
273        // doing so.
274        let mut circuit = self.inner.circuit.lock();
275        if let Some(values) = &self.inner.values {
276            let mut values = values.lock();
277            let value = init(Values {
278                constants: &circuit.constants,
279                witnesses: &values.witnesses,
280                nodes: &values.nodes,
281            });
282            let idx = reserve(&mut circuit);
283            match idx {
284                CircuitIdx::Witness(_) => {
285                    values
286                        .witnesses
287                        .push(value.expect("witness allocations populate prover assignments"));
288                }
289                CircuitIdx::Node(_) => {
290                    values
291                        .nodes
292                        .push(value.expect("node allocations populate prover assignments"));
293                }
294                CircuitIdx::Constant(_) => {
295                    assert!(
296                        value.is_none(),
297                        "constants do not populate prover assignments"
298                    );
299                }
300            }
301            return idx;
302        }
303
304        reserve(&mut circuit)
305    }
306
307    /// Push a node into the circuit. In prover mode, `init` runs with read
308    /// access to the current circuit values so it can compute the node's
309    /// value, which is appended in lockstep with the node.
310    fn node(self, n: CircuitNode, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> CircuitIdx {
311        self.allocate(|values| Some(init(values)), |circuit| circuit.next_node(n))
312    }
313
314    fn assert_eq(self, a: CircuitIdx, b: CircuitIdx) {
315        self.inner.circuit.lock().assertions.push((a, b));
316    }
317
318    /// Allocate a fresh witness slot. In prover mode, `init` runs with read
319    /// access to the current circuit values to compute the witness value.
320    fn witness(self, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> CircuitIdx {
321        self.allocate(|values| Some(init(values)), Circuit::next_witness)
322    }
323}
324
325impl<'ctx, F> Context<'ctx, F> {
326    fn constant(self, x: F) -> CircuitIdx {
327        self.allocate(|_| None, |circuit| circuit.next_constant(x))
328    }
329}
330
331#[derive(Clone)]
332enum VarInner<'ctx, F> {
333    Native(F),
334    Circuit {
335        ctx: Context<'ctx, F>,
336        idx: CircuitIdx,
337    },
338}
339
340/// A value in a circuit being built.
341///
342/// Vars are created with [`Self::witness`] and [`Self::constant`], combined
343/// with the usual arithmetic operators, and constrained with
344/// [`Self::assert_eq`]. Vars implement the algebra traits from
345/// [`commonware_math`], so code written against [`Ring`] or [`Field`] runs
346/// unchanged over circuit values.
347///
348/// Values produced by [`Additive::zero`] and [`Ring::one`] are "native":
349/// they live outside the circuit until combined with a circuit value. This
350/// is visible in two places: equality compares what vars refer to, not what
351/// they evaluate to (a native var is never equal to a circuit-backed var,
352/// even when their values agree), and [`Self::assert_eq`] panics on two
353/// unequal native vars. Generic code that branches on equality may
354/// therefore behave differently over vars than over plain values.
355#[derive(Clone)]
356pub struct Var<'ctx, F> {
357    inner: VarInner<'ctx, F>,
358}
359
360impl<F: fmt::Debug> fmt::Debug for Var<'_, F> {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        match &self.inner {
363            VarInner::Native(value) => f.debug_tuple("Native").field(value).finish(),
364            VarInner::Circuit { ctx, idx } => {
365                let ctx_ptr = ctx.inner as *const ContextInner<F>;
366                f.debug_struct("Circuit")
367                    .field("ctx", &ctx_ptr)
368                    .field("idx", idx)
369                    .finish()
370            }
371        }
372    }
373}
374
375impl<F: PartialEq> PartialEq for Var<'_, F> {
376    fn eq(&self, other: &Self) -> bool {
377        match (&self.inner, &other.inner) {
378            (VarInner::Native(a), VarInner::Native(b)) => a == b,
379            (VarInner::Circuit { idx: a_idx, .. }, VarInner::Circuit { idx: b_idx, .. }) => {
380                a_idx == b_idx
381            }
382            _ => false,
383        }
384    }
385}
386
387impl<F: Eq> Eq for Var<'_, F> {}
388
389impl<'ctx, F> Var<'ctx, F> {
390    /// Allocate a fresh witness.
391    ///
392    /// In prover mode, `init` receives the values assigned so far, and must
393    /// return the value of this witness. In verifier mode, `init` does not
394    /// run.
395    ///
396    /// `init` must not use the [`Context`]: doing so deadlocks. See
397    /// [`Values`].
398    pub fn witness(ctx: Context<'ctx, F>, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> Self {
399        Self {
400            inner: VarInner::Circuit {
401                ctx,
402                idx: ctx.witness(init),
403            },
404        }
405    }
406
407    /// Create a "native" var holding a value outside any circuit.
408    ///
409    /// Like the vars produced by [`Additive::zero`] and [`Ring::one`], a
410    /// native var lives outside the circuit until it is combined with a
411    /// circuit-backed var, at which point it is folded in as a constant. This
412    /// is convenient for fixed constants (such as curve parameters) that are
413    /// the same in every circuit and so do not need a [`Context`] to create.
414    pub const fn native(value: F) -> Self {
415        Self {
416            inner: VarInner::Native(value),
417        }
418    }
419
420    /// Create a var with a fixed, public value.
421    pub fn constant(ctx: Context<'ctx, F>, value: F) -> Self {
422        Self {
423            inner: VarInner::Circuit {
424                ctx,
425                idx: ctx.constant(value),
426            },
427        }
428    }
429
430    /// Assert that this var equals `other`.
431    ///
432    /// The constraint must hold for the circuit to be satisfied.
433    ///
434    /// # Panics
435    ///
436    /// Panics if both vars are native and their values differ, since there
437    /// is no circuit to record the failure in.
438    pub fn assert_eq(&self, other: &Self)
439    where
440        F: Clone + PartialEq,
441    {
442        match (&self.inner, &other.inner) {
443            (VarInner::Native(a), VarInner::Native(b)) => {
444                assert!(a == b, "asserted equality between distinct native vars");
445            }
446            (VarInner::Native(a), VarInner::Circuit { ctx, idx })
447            | (VarInner::Circuit { ctx, idx }, VarInner::Native(a)) => {
448                ctx.assert_eq(Self::constant(*ctx, a.clone()).circuit_idx(), *idx);
449            }
450            (VarInner::Circuit { ctx, idx: a }, VarInner::Circuit { idx: b, .. }) => {
451                ctx.assert_eq(*a, *b);
452            }
453        }
454    }
455
456    fn circuit_idx(&self) -> CircuitIdx {
457        match self.inner {
458            VarInner::Circuit { idx, .. } => idx,
459            VarInner::Native(_) => panic!("expected circuit-backed var"),
460        }
461    }
462}
463
464impl<'ctx, F: Clone> Var<'ctx, F> {
465    /// The value of this var, under a prover-mode assignment.
466    pub fn value(&self, values: Values<'_, F>) -> F {
467        match &self.inner {
468            VarInner::Native(value) => value.clone(),
469            VarInner::Circuit { idx, .. } => values[*idx].clone(),
470        }
471    }
472
473    /// Combine `self` and `other` with a commutative binary operation.
474    ///
475    /// `combine` is the value-level operation used both for the all-native case
476    /// and for prover-mode node evaluation. `node` is the circuit node
477    /// constructor (for example `CircuitNode::Add` or `CircuitNode::Mul`).
478    fn merge(
479        self,
480        other: &Self,
481        combine: impl Fn(&F, &F) -> F,
482        node: fn(CircuitIdx, CircuitIdx) -> CircuitNode,
483    ) -> Self {
484        let (ctx, a_idx, b_idx) = match (self.inner, &other.inner) {
485            (VarInner::Native(a), VarInner::Native(b)) => {
486                return Self {
487                    inner: VarInner::Native(combine(&a, b)),
488                }
489            }
490            (VarInner::Native(ref a), &VarInner::Circuit { ctx, idx: b_idx })
491            | (VarInner::Circuit { ctx, idx: b_idx }, &VarInner::Native(ref a)) => {
492                (ctx, Self::constant(ctx, a.clone()).circuit_idx(), b_idx)
493            }
494            (VarInner::Circuit { ctx, idx: a }, &VarInner::Circuit { idx: b, .. }) => (ctx, a, b),
495        };
496        if let (CircuitIdx::Constant(a_idx), CircuitIdx::Constant(b_idx)) = (a_idx, b_idx) {
497            return Self {
498                inner: VarInner::Circuit {
499                    ctx,
500                    idx: ctx.allocate_constant(|constants| {
501                        combine(&constants[a_idx as usize], &constants[b_idx as usize])
502                    }),
503                },
504            };
505        }
506        let new_idx = ctx.node(node(a_idx, b_idx), move |v| combine(&v[a_idx], &v[b_idx]));
507        Self {
508            inner: VarInner::Circuit { ctx, idx: new_idx },
509        }
510    }
511}
512
513impl<'ctx, F: Object> Object for Var<'ctx, F> {}
514
515impl<'ctx, F: Additive> Add<&Self> for Var<'ctx, F> {
516    type Output = Self;
517    fn add(self, rhs: &Self) -> Self {
518        self.merge(rhs, |a, b| a.clone() + b, CircuitNode::Add)
519    }
520}
521
522impl<'ctx, F: Additive> AddAssign<&Self> for Var<'ctx, F> {
523    fn add_assign(&mut self, rhs: &Self) {
524        *self = self.clone() + rhs;
525    }
526}
527
528impl<'ctx, F: Additive + Ring> Neg for Var<'ctx, F> {
529    type Output = Self;
530    fn neg(self) -> Self {
531        match self.inner {
532            VarInner::Native(a) => Self {
533                inner: VarInner::Native(-a),
534            },
535            VarInner::Circuit {
536                ctx,
537                idx: CircuitIdx::Constant(idx),
538            } => Self {
539                inner: VarInner::Circuit {
540                    ctx,
541                    idx: ctx.allocate_constant(|constants| -constants[idx as usize].clone()),
542                },
543            },
544            VarInner::Circuit { ctx, idx } => {
545                let minus_one = Var::constant(ctx, -F::one()).circuit_idx();
546                let new_idx = ctx.node(CircuitNode::Mul(minus_one, idx), move |v| -v[idx].clone());
547                Self {
548                    inner: VarInner::Circuit { ctx, idx: new_idx },
549                }
550            }
551        }
552    }
553}
554
555impl<'ctx, F: Additive + Ring> Sub<&Self> for Var<'ctx, F> {
556    type Output = Self;
557    fn sub(self, rhs: &Self) -> Self {
558        self + &(-rhs.clone())
559    }
560}
561
562impl<'ctx, F: Additive + Ring> SubAssign<&Self> for Var<'ctx, F> {
563    fn sub_assign(&mut self, rhs: &Self) {
564        *self = self.clone() - rhs;
565    }
566}
567
568impl<'ctx, F: Multiplicative> Mul<&Self> for Var<'ctx, F> {
569    type Output = Self;
570    fn mul(self, rhs: &Self) -> Self {
571        self.merge(rhs, |a, b| a.clone() * b, CircuitNode::Mul)
572    }
573}
574
575impl<'ctx, F: Multiplicative> MulAssign<&Self> for Var<'ctx, F> {
576    fn mul_assign(&mut self, rhs: &Self) {
577        *self = self.clone() * rhs;
578    }
579}
580
581/// Division by `rhs`, computed as a single multiplication constraint.
582///
583/// Rather than inverting `rhs` and multiplying (which costs two
584/// multiplications), the prover supplies the quotient `q = self / rhs` as a
585/// witness and the circuit constrains `q * rhs == self`.
586///
587/// # Caveats
588///
589/// Like [`Field::inv`] on a circuit-backed var, this deviates from the
590/// `inv(0) = 0` field contract: dividing by a circuit-backed `rhs` of zero
591/// adds an unsatisfiable constraint when `self != 0`. Worse, `0 / 0`
592/// constrains `q * 0 == 0`, which holds for *any* `q`, leaving the quotient
593/// unconstrained. Only use `/` where `rhs` is known to be nonzero.
594impl<'ctx, F: Field> Div<&Self> for Var<'ctx, F> {
595    type Output = Self;
596
597    fn div(self, rhs: &Self) -> Self {
598        let &ctx = match (&self.inner, &rhs.inner) {
599            (VarInner::Native(a), VarInner::Native(b)) => {
600                return Self {
601                    inner: VarInner::Native(a.clone() * &b.inv()),
602                }
603            }
604            (VarInner::Circuit { ctx, .. }, _) | (_, VarInner::Circuit { ctx, .. }) => ctx,
605        };
606        let q = { Self::witness(ctx, |v| self.value(v) * &rhs.value(v).inv()) };
607        (q.clone() * rhs).assert_eq(&self);
608        q
609    }
610}
611
612impl<'ctx, F: Field> DivAssign<&Self> for Var<'ctx, F> {
613    fn div_assign(&mut self, rhs: &Self) {
614        *self = self.clone() / rhs;
615    }
616}
617
618impl<'ctx, F: Additive + Ring> Additive for Var<'ctx, F> {
619    fn zero() -> Self {
620        Self {
621            inner: VarInner::Native(F::zero()),
622        }
623    }
624}
625
626impl<'ctx, F: Multiplicative> Multiplicative for Var<'ctx, F> {}
627
628impl<'ctx, F: Ring> Ring for Var<'ctx, F> {
629    fn one() -> Self {
630        Self {
631            inner: VarInner::Native(F::one()),
632        }
633    }
634}
635
636/// Unlike the [`Field::inv`] contract, inverting a circuit-backed zero does
637/// not produce zero: it adds an unsatisfiable constraint to the circuit.
638impl<'ctx, F: Field> Field for Var<'ctx, F> {
639    fn inv(&self) -> Self {
640        match &self.inner {
641            VarInner::Native(c) => Self {
642                inner: VarInner::Native(c.inv()),
643            },
644            &VarInner::Circuit { ctx, .. } => {
645                // Prover supplies the inverse via the oracle; verifier just
646                // allocates the slot.
647                let inv = Self::witness(ctx, |v| self.value(v).inv());
648                (inv.clone() * self).assert_eq(&Self::one());
649                inv
650            }
651        }
652    }
653}
654
655/// A circuit value constrained to be `0` or `1`.
656///
657/// A `BoolVar` wraps a [`Var`] together with a guarantee that it holds a
658/// boolean: every constructor either produces a value that is boolean by
659/// construction, or adds the constraint `b * (1 - b) == 0` enforcing it.
660/// Holding that guarantee in the type lets later operations skip
661/// re-checking: [`Self::select`] and the boolean combinators below are sound
662/// precisely because their inputs are already known to be boolean.
663///
664/// The motivating use is scalar multiplication in a circuit, where a scalar
665/// is decomposed into bits (each a `BoolVar`) and a point is accumulated by
666/// conditionally adding with [`Self::select`].
667///
668/// # Native Vars
669///
670/// Like [`Var`], a `BoolVar` built from a native value (see
671/// [`Self::constant`]) lives outside the circuit until combined with a
672/// circuit-backed value. [`Self::assert`] on a native, non-boolean var
673/// therefore panics rather than recording an unsatisfiable constraint, in
674/// keeping with the native-var semantics described on [`Var`].
675///
676/// # Example
677///
678/// ```
679/// use commonware_cryptography::zk::circuit::{build_with_values, BoolVar, Var};
680/// use commonware_math::test::F;
681///
682/// // Use a bit to choose between two values, then check the choice.
683/// let (valued, _) = build_with_values(|ctx| {
684///     let bit = BoolVar::witness(ctx, |_| true);
685///     let a = Var::constant(ctx, F::from(7u64));
686///     let b = Var::constant(ctx, F::from(9u64));
687///     bit.select(&a, &b).assert_eq(&Var::constant(ctx, F::from(7u64)));
688///     Vec::new()
689/// });
690/// assert!(valued.is_satisfied());
691/// ```
692#[derive(Clone)]
693pub struct BoolVar<'ctx, F> {
694    var: Var<'ctx, F>,
695}
696
697impl<F: fmt::Debug> fmt::Debug for BoolVar<'_, F> {
698    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699        f.debug_tuple("BoolVar").field(&self.var).finish()
700    }
701}
702
703impl<'ctx, F> BoolVar<'ctx, F> {
704    /// The underlying [`Var`], whose value is `0` or `1`.
705    pub const fn var(&self) -> &Var<'ctx, F> {
706        &self.var
707    }
708
709    /// Consume this `BoolVar`, returning the underlying [`Var`].
710    #[allow(clippy::missing_const_for_fn)]
711    pub fn into_var(self) -> Var<'ctx, F> {
712        self.var
713    }
714}
715
716impl<'ctx, F: Ring> BoolVar<'ctx, F> {
717    /// Create a boolean constant with a fixed, public value.
718    ///
719    /// Like [`Var::native`], the value lives outside the circuit until it is
720    /// combined with a circuit-backed var. No constraint is added: the value
721    /// is boolean by construction.
722    pub fn constant(value: bool) -> Self {
723        Self {
724            var: Var::native(if value { F::one() } else { F::zero() }),
725        }
726    }
727
728    /// Assert that this var equals `other`.
729    pub fn assert_eq(&self, other: &Self) {
730        self.var.assert_eq(other.var());
731    }
732}
733
734impl<'ctx, F: Ring + PartialEq> BoolVar<'ctx, F> {
735    /// Allocate a fresh boolean witness, constrained to be `0` or `1`.
736    ///
737    /// In prover mode, `init` receives the values assigned so far and must
738    /// return the bit's value. In verifier mode, `init` does not run. The
739    /// constraint `b * (1 - b) == 0` is added in both modes.
740    ///
741    /// `init` must not use the [`Context`]: doing so deadlocks. See
742    /// [`Values`].
743    pub fn witness(
744        ctx: Context<'ctx, F>,
745        init: impl for<'a> FnOnce(Values<'a, F>) -> bool,
746    ) -> Self {
747        let var = Var::witness(ctx, |v| if init(v) { F::one() } else { F::zero() });
748        Self::enforce(&var);
749        Self { var }
750    }
751
752    /// Constrain an arbitrary var to be boolean and wrap it.
753    ///
754    /// Adds the constraint `var * (1 - var) == 0`, which holds exactly when
755    /// `var` is `0` or `1`.
756    ///
757    /// # Panics
758    ///
759    /// Panics if `var` is a native var whose value is not `0` or `1`, since
760    /// there is no circuit to record the failed constraint in. See
761    /// [`Var::assert_eq`].
762    pub fn assert(var: Var<'ctx, F>) -> Self {
763        Self::enforce(&var);
764        Self { var }
765    }
766
767    /// Add the booleanity constraint `var * var == var` (equivalently
768    /// `var * (1 - var) == 0`).
769    fn enforce(var: &Var<'ctx, F>) {
770        (var.clone() * var).assert_eq(var);
771    }
772}
773
774impl<'ctx, F: Ring> BoolVar<'ctx, F> {
775    /// Select between two vars based on this bit.
776    ///
777    /// Returns `on_true` when the bit is `1` and `on_false` when it is `0`,
778    /// computed as `on_false + b * (on_true - on_false)` with a single
779    /// multiplication.
780    pub fn select(&self, on_true: &Var<'ctx, F>, on_false: &Var<'ctx, F>) -> Var<'ctx, F> {
781        on_false.clone() + &(self.var.clone() * &(on_true.clone() - on_false))
782    }
783}
784
785impl<'ctx, F: Ring> Not for BoolVar<'ctx, F> {
786    type Output = Self;
787
788    fn not(self) -> Self::Output {
789        Self {
790            var: Var::one() - &self.var,
791        }
792    }
793}
794
795impl<'ctx, F: Ring> BitAnd for BoolVar<'ctx, F> {
796    type Output = Self;
797
798    // Over 0/1, boolean `and` IS multiplication, so `self.var * &rhs.var` is
799    // correct. But clippy's `suspicious_arithmetic_impl` lint assumes any
800    // operator impl that uses a *different* operator internally (here, `*` inside
801    // `BitAnd`) is a copy-paste typo. That heuristic is wrong: it has no notion
802    // of the algebra, so it flags correct code and forces this `#[allow]`. The
803    // lint gives confident, authoritative advice that is simply false, which is
804    // why its designer now resides in the Eighth Circle of Hell,
805    // among the fraudulent counselors (Inferno XXVI) who misused their cleverness
806    // to advise others into error, each concealed within a tongue of flame.
807    #[allow(clippy::suspicious_arithmetic_impl)]
808    fn bitand(self, rhs: Self) -> Self::Output {
809        Self {
810            var: self.var * &rhs.var,
811        }
812    }
813}
814
815impl<'ctx, F: Ring> BitOr for BoolVar<'ctx, F> {
816    type Output = Self;
817
818    // Boolean `or` over 0/1 is `a + b - a * b`, which is correct. Same lint, same
819    // false alarm over the `+`/`-`/`*`, same `#[allow]`. See `bitand` above for
820    // why the lint's designer is doing time in the Eighth Circle of Hell.
821    #[allow(clippy::suspicious_arithmetic_impl)]
822    fn bitor(self, rhs: Self) -> Self::Output {
823        Self {
824            var: self.var.clone() + &rhs.var - &(self.var * &rhs.var),
825        }
826    }
827}
828
829/// A tool for selecting among multiple items.
830///
831/// This is a generalization of using a [`BoolVar`] to select among two items,
832/// letting you use `k` bits to select among `2^k` items.
833pub struct Selector<'ctx, F> {
834    monomials: Vec<Var<'ctx, F>>,
835}
836
837impl<'ctx, F: Ring> Selector<'ctx, F> {
838    /// Create a new selector, using a given number of in-circuit bits.
839    ///
840    /// This selector will then be able to select among exactly `2^k` items,
841    /// where `k` is the number of bits passed in here.
842    ///
843    /// It is more efficient to create one selector and reuse it.
844    ///
845    /// The selection is made in ascending order, i.e. 0..00, 0..01, 0..10, ...
846    /// In other words, if you pass in, e.g. 010 to this function, you then
847    /// will always get the 3rd (index 2) of 8 items.
848    pub fn new(bits: &[BoolVar<'ctx, F>]) -> Self {
849        let mut monomials = vec![Var::one(); 1usize << bits.len()];
850        for mask in 1..monomials.len() {
851            let bit = mask.trailing_zeros() as usize;
852            let prev = mask ^ (1usize << bit);
853            monomials[mask] = if prev == 0 {
854                bits[bit].var().clone()
855            } else {
856                monomials[prev].clone() * bits[bit].var()
857            };
858        }
859        Self { monomials }
860    }
861
862    /// Select a constant value among `2^k` possibilities.
863    pub fn select_constant(&self, constants: &[F]) -> Var<'ctx, F> {
864        assert_eq!(
865            self.monomials.len(),
866            constants.len(),
867            "constants len must match selectors len"
868        );
869
870        let values = {
871            let mut values = constants.to_vec();
872            let len = values.len().trailing_zeros() as usize;
873            for bit in 0..len {
874                for mask in 0..values.len() {
875                    if mask & (1usize << bit) != 0 {
876                        let prev = values[mask ^ (1usize << bit)].clone();
877                        values[mask] -= &prev;
878                    }
879                }
880            }
881            values
882        };
883
884        values
885            .into_iter()
886            .zip(&self.monomials)
887            .map(|(v_i, m_i)| Var::native(v_i) * m_i)
888            .reduce(|acc, x| acc + &x)
889            .expect("values is non empty")
890    }
891}
892
893/// Build a circuit without computing an assignment (verifier mode).
894///
895/// Witness `init` closures do not run in this mode.
896///
897/// The closure returns the vars whose circuit indices the caller wants back
898/// (e.g. the committed outputs of a circuit); the returned indices are in the
899/// same order. Return an empty vec to ignore this.
900///
901/// # Panics
902///
903/// Panics if any returned var is native (not backed by the circuit).
904pub fn build<F: Ring + PartialEq>(
905    f: impl for<'ctx> FnOnce(Context<'ctx, F>) -> Vec<Var<'ctx, F>>,
906) -> (Circuit<F>, Vec<CircuitIdx>) {
907    let inner = ContextInner {
908        values: None,
909        circuit: Mutex::new(Circuit::default()),
910    };
911    let indices = f(Context {
912        inner: &inner,
913        _brand: PhantomData,
914    })
915    .iter()
916    .map(Var::circuit_idx)
917    .collect();
918    (inner.circuit.into_inner(), indices)
919}
920
921/// Build a circuit while simultaneously computing the assignment (prover mode).
922///
923/// Each witness's value comes from the `init` closure passed to
924/// [`Var::witness`].
925///
926/// The closure returns the vars whose circuit indices the caller wants back
927/// (e.g. the committed outputs of a circuit); the returned indices are in the
928/// same order. Return an empty vec to ignore this.
929///
930/// # Panics
931///
932/// Panics if any returned var is native (not backed by the circuit).
933pub fn build_with_values<F: Ring + PartialEq>(
934    f: impl for<'ctx> FnOnce(Context<'ctx, F>) -> Vec<Var<'ctx, F>>,
935) -> (ValuedCircuit<F>, Vec<CircuitIdx>) {
936    let inner = ContextInner {
937        values: Some(Mutex::new(ValuesBuilder {
938            witnesses: Vec::new(),
939            nodes: Vec::new(),
940        })),
941        circuit: Mutex::new(Circuit::default()),
942    };
943    let indices = f(Context {
944        inner: &inner,
945        _brand: PhantomData,
946    })
947    .iter()
948    .map(Var::circuit_idx)
949    .collect();
950    let circuit = inner.circuit.into_inner();
951    let values = inner.values.unwrap().into_inner();
952    (
953        ValuedCircuit {
954            circuit,
955            witnesses: values.witnesses,
956            nodes: values.nodes,
957        },
958        indices,
959    )
960}
961
962/// Fuzzing utilities, comparing circuit satisfaction against native
963/// evaluation of the same operations.
964#[commonware_macros::stability(ALPHA)]
965#[cfg(any(test, feature = "fuzz"))]
966pub mod fuzz {
967    use super::*;
968    use arbitrary::{Arbitrary, Unstructured};
969    use commonware_math::test::F;
970
971    #[derive(Debug)]
972    enum Op {
973        Witness(F),
974        Constant(F),
975        Zero,
976        One,
977        Add(usize, usize),
978        Sub(usize, usize),
979        Mul(usize, usize),
980        Neg(usize),
981        Inv(usize),
982        AssertEq(usize, usize),
983    }
984
985    /// A random sequence of circuit operations, together with whether the
986    /// circuit they build should be satisfied.
987    ///
988    /// Values are drawn from a small range so that assertions have a decent
989    /// chance of holding, exercising both outcomes.
990    #[derive(Debug)]
991    pub struct Plan {
992        ops: Vec<Op>,
993        satisfied: bool,
994    }
995
996    impl Arbitrary<'_> for Plan {
997        fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
998            let mut ops = Vec::new();
999            let mut values: Vec<F> = Vec::new();
1000            let mut is_native: Vec<bool> = Vec::new();
1001            let mut satisfied = true;
1002            for _ in 0..u.int_in_range(1..=32)? {
1003                let kind = if values.is_empty() {
1004                    u.int_in_range(0..=3)?
1005                } else {
1006                    u.int_in_range(0..=9)?
1007                };
1008                if kind <= 3 {
1009                    let v = F::from(u.int_in_range::<u8>(0..=4)?);
1010                    let (op, value, native) = match kind {
1011                        0 => (Op::Witness(v), v, false),
1012                        1 => (Op::Constant(v), v, false),
1013                        2 => (Op::Zero, F::zero(), true),
1014                        _ => (Op::One, F::one(), true),
1015                    };
1016                    ops.push(op);
1017                    values.push(value);
1018                    is_native.push(native);
1019                    continue;
1020                }
1021                let a = u.int_in_range(0..=values.len() - 1)?;
1022                let b = u.int_in_range(0..=values.len() - 1)?;
1023                let merged = is_native[a] && is_native[b];
1024                let (op, value, native) = match kind {
1025                    4 => (Op::Add(a, b), values[a] + &values[b], merged),
1026                    5 => (Op::Sub(a, b), values[a] - &values[b], merged),
1027                    6 => (Op::Mul(a, b), values[a] * &values[b], merged),
1028                    7 => (Op::Neg(a), -values[a], is_native[a]),
1029                    8 => {
1030                        // Inverting a circuit-backed zero constrains z * 0 = 1,
1031                        // which is unsatisfiable. Native vars add no constraint.
1032                        if !is_native[a] && values[a] == F::zero() {
1033                            satisfied = false;
1034                        }
1035                        (Op::Inv(a), values[a].inv(), is_native[a])
1036                    }
1037                    _ => {
1038                        // Asserting equality between two unequal native vars is
1039                        // a panic by design, so the plan must avoid it.
1040                        if merged && values[a] != values[b] {
1041                            continue;
1042                        }
1043                        ops.push(Op::AssertEq(a, b));
1044                        satisfied = satisfied && values[a] == values[b];
1045                        continue;
1046                    }
1047                };
1048                ops.push(op);
1049                values.push(value);
1050                is_native.push(native);
1051            }
1052            Ok(Self { ops, satisfied })
1053        }
1054    }
1055
1056    impl Plan {
1057        /// Check that satisfaction matches the natively computed expectation.
1058        pub fn run(self, _u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
1059            assert_eq!(
1060                self.build().is_satisfied(),
1061                self.satisfied(),
1062                "plan: {self:?}"
1063            );
1064            Ok(())
1065        }
1066
1067        /// Whether the circuit built by [`Self::build`] should be satisfied.
1068        pub const fn satisfied(&self) -> bool {
1069            self.satisfied
1070        }
1071
1072        /// Build the circuit, along with its prover assignment.
1073        pub fn build(&self) -> ValuedCircuit<F> {
1074            build_with_values(|ctx| {
1075                let mut vars: Vec<Var<'_, F>> = Vec::new();
1076                for op in &self.ops {
1077                    let var = match *op {
1078                        Op::Witness(v) => Var::witness(ctx, move |_| v),
1079                        Op::Constant(v) => Var::constant(ctx, v),
1080                        Op::Zero => Var::zero(),
1081                        Op::One => Var::one(),
1082                        Op::Add(a, b) => vars[a].clone() + &vars[b],
1083                        Op::Sub(a, b) => vars[a].clone() - &vars[b],
1084                        Op::Mul(a, b) => vars[a].clone() * &vars[b],
1085                        Op::Neg(a) => -vars[a].clone(),
1086                        Op::Inv(a) => vars[a].inv(),
1087                        Op::AssertEq(a, b) => {
1088                            vars[a].assert_eq(&vars[b]);
1089                            continue;
1090                        }
1091                    };
1092                    vars.push(var);
1093                }
1094                Vec::new()
1095            })
1096            .0
1097        }
1098    }
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103    use super::*;
1104    use commonware_invariants::minifuzz;
1105    use commonware_math::test::F;
1106
1107    #[test]
1108    fn test_is_satisfied_matches_native_evaluation_minifuzz() {
1109        minifuzz::test(|u| u.arbitrary::<fuzz::Plan>()?.run(u));
1110    }
1111
1112    #[test]
1113    fn test_is_satisfied_cubic() {
1114        // Constrain a witness `x` to satisfy `x^3 + x + 5 = 35`.
1115        let cubic = |x_value: u64| {
1116            build_with_values(move |ctx| {
1117                let x = Var::witness(ctx, move |_| F::from(x_value));
1118                let out = x.clone() * &x * &x + &x + &Var::constant(ctx, F::from(5u64));
1119                out.assert_eq(&Var::constant(ctx, F::from(35u64)));
1120                Vec::new()
1121            })
1122            .0
1123        };
1124        assert!(cubic(3).is_satisfied());
1125        assert!(!cubic(4).is_satisfied());
1126    }
1127
1128    #[test]
1129    fn test_bool_witness_enforces_booleanity() {
1130        // A boolean witness from a `bool` is always satisfiable.
1131        for b in [false, true] {
1132            let (valued, _) = build_with_values(move |ctx| {
1133                BoolVar::<F>::witness(ctx, move |_| b);
1134                Vec::new()
1135            });
1136            assert!(valued.is_satisfied());
1137        }
1138
1139        // A var that is not 0 or 1 fails the booleanity constraint.
1140        let (bad, _) = build_with_values(|ctx| {
1141            let two = Var::witness(ctx, |_| F::from(2u64));
1142            BoolVar::assert(two);
1143            Vec::new()
1144        });
1145        assert!(!bad.is_satisfied());
1146
1147        // 0 and 1 pass `from_var`.
1148        for v in [0u64, 1u64] {
1149            let (valued, _) = build_with_values(move |ctx| {
1150                let x = Var::witness(ctx, move |_| F::from(v));
1151                BoolVar::assert(x);
1152                Vec::new()
1153            });
1154            assert!(valued.is_satisfied());
1155        }
1156    }
1157
1158    #[test]
1159    fn test_bool_select() {
1160        // `select` returns `on_true` when the bit is set, else `on_false`.
1161        for b in [false, true] {
1162            let (valued, _) = build_with_values(move |ctx| {
1163                let bit = BoolVar::witness(ctx, move |_| b);
1164                let on_true = Var::witness(ctx, |_| F::from(7u64));
1165                let on_false = Var::witness(ctx, |_| F::from(9u64));
1166                let selected = bit.select(&on_true, &on_false);
1167                let expected = if b { F::from(7u64) } else { F::from(9u64) };
1168                selected.assert_eq(&Var::constant(ctx, expected));
1169                Vec::new()
1170            });
1171            assert!(valued.is_satisfied());
1172        }
1173    }
1174
1175    #[test]
1176    fn test_bool_combinators_truth_tables() {
1177        for a in [false, true] {
1178            for b in [false, true] {
1179                let (valued, _) = build_with_values::<F>(move |ctx| {
1180                    let a_var = BoolVar::witness(ctx, move |_| a);
1181                    let b_var = BoolVar::witness(ctx, move |_| b);
1182                    (!a_var.clone()).assert_eq(&BoolVar::constant(!a));
1183                    (a_var.clone() & b_var.clone()).assert_eq(&BoolVar::constant(a & b));
1184                    (a_var | b_var).assert_eq(&BoolVar::constant(a | b));
1185                    Vec::new()
1186                });
1187                assert!(valued.is_satisfied());
1188            }
1189        }
1190    }
1191
1192    #[test]
1193    fn test_bool_constant() {
1194        let (valued, _) = build_with_values(|ctx| {
1195            // A native boolean constant folds in correctly when combined.
1196            let t = BoolVar::<F>::constant(true);
1197            let f = BoolVar::<F>::constant(false);
1198            let x = Var::witness(ctx, |_| F::from(5u64));
1199            t.select(&x, &Var::zero())
1200                .assert_eq(&Var::constant(ctx, F::from(5u64)));
1201            f.select(&x, &Var::zero()).assert_eq(&Var::zero());
1202            Vec::new()
1203        });
1204        assert!(valued.is_satisfied());
1205    }
1206}