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    fn constant(self, x: F) -> CircuitIdx {
325        self.allocate(|_| None, |circuit| circuit.next_constant(x))
326    }
327}
328
329#[derive(Clone)]
330enum VarInner<'ctx, F> {
331    Native(F),
332    Circuit {
333        ctx: Context<'ctx, F>,
334        idx: CircuitIdx,
335    },
336}
337
338/// A value in a circuit being built.
339///
340/// Vars are created with [`Self::witness`] and [`Self::constant`], combined
341/// with the usual arithmetic operators, and constrained with
342/// [`Self::assert_eq`]. Vars implement the algebra traits from
343/// [`commonware_math`], so code written against [`Ring`] or [`Field`] runs
344/// unchanged over circuit values.
345///
346/// Values produced by [`Additive::zero`] and [`Ring::one`] are "native":
347/// they live outside the circuit until combined with a circuit value. This
348/// is visible in two places: equality compares what vars refer to, not what
349/// they evaluate to (a native var is never equal to a circuit-backed var,
350/// even when their values agree), and [`Self::assert_eq`] panics on two
351/// unequal native vars. Generic code that branches on equality may
352/// therefore behave differently over vars than over plain values.
353#[derive(Clone)]
354pub struct Var<'ctx, F> {
355    inner: VarInner<'ctx, F>,
356}
357
358impl<F: fmt::Debug> fmt::Debug for Var<'_, F> {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        match &self.inner {
361            VarInner::Native(value) => f.debug_tuple("Native").field(value).finish(),
362            VarInner::Circuit { ctx, idx } => {
363                let ctx_ptr = ctx.inner as *const ContextInner<F>;
364                f.debug_struct("Circuit")
365                    .field("ctx", &ctx_ptr)
366                    .field("idx", idx)
367                    .finish()
368            }
369        }
370    }
371}
372
373impl<F: PartialEq> PartialEq for Var<'_, F> {
374    fn eq(&self, other: &Self) -> bool {
375        match (&self.inner, &other.inner) {
376            (VarInner::Native(a), VarInner::Native(b)) => a == b,
377            (VarInner::Circuit { idx: a_idx, .. }, VarInner::Circuit { idx: b_idx, .. }) => {
378                a_idx == b_idx
379            }
380            _ => false,
381        }
382    }
383}
384
385impl<F: Eq> Eq for Var<'_, F> {}
386
387impl<'ctx, F> Var<'ctx, F> {
388    /// Allocate a fresh witness.
389    ///
390    /// In prover mode, `init` receives the values assigned so far, and must
391    /// return the value of this witness. In verifier mode, `init` does not
392    /// run.
393    ///
394    /// `init` must not use the [`Context`]: doing so deadlocks. See
395    /// [`Values`].
396    pub fn witness(ctx: Context<'ctx, F>, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> Self {
397        Self {
398            inner: VarInner::Circuit {
399                ctx,
400                idx: ctx.witness(init),
401            },
402        }
403    }
404
405    /// Create a "native" var holding a value outside any circuit.
406    ///
407    /// Like the vars produced by [`Additive::zero`] and [`Ring::one`], a
408    /// native var lives outside the circuit until it is combined with a
409    /// circuit-backed var, at which point it is folded in as a constant. This
410    /// is convenient for fixed constants (such as curve parameters) that are
411    /// the same in every circuit and so do not need a [`Context`] to create.
412    pub const fn native(value: F) -> Self {
413        Self {
414            inner: VarInner::Native(value),
415        }
416    }
417
418    /// Create a var with a fixed, public value.
419    pub fn constant(ctx: Context<'ctx, F>, value: F) -> Self {
420        Self {
421            inner: VarInner::Circuit {
422                ctx,
423                idx: ctx.constant(value),
424            },
425        }
426    }
427
428    /// Assert that this var equals `other`.
429    ///
430    /// The constraint must hold for the circuit to be satisfied.
431    ///
432    /// # Panics
433    ///
434    /// Panics if both vars are native and their values differ, since there
435    /// is no circuit to record the failure in.
436    pub fn assert_eq(&self, other: &Self)
437    where
438        F: Clone + PartialEq,
439    {
440        match (&self.inner, &other.inner) {
441            (VarInner::Native(a), VarInner::Native(b)) => {
442                assert!(a == b, "asserted equality between distinct native vars");
443            }
444            (VarInner::Native(a), VarInner::Circuit { ctx, idx })
445            | (VarInner::Circuit { ctx, idx }, VarInner::Native(a)) => {
446                ctx.assert_eq(Self::constant(*ctx, a.clone()).circuit_idx(), *idx);
447            }
448            (VarInner::Circuit { ctx, idx: a }, VarInner::Circuit { idx: b, .. }) => {
449                ctx.assert_eq(*a, *b);
450            }
451        }
452    }
453
454    fn circuit_idx(&self) -> CircuitIdx {
455        match self.inner {
456            VarInner::Circuit { idx, .. } => idx,
457            VarInner::Native(_) => panic!("expected circuit-backed var"),
458        }
459    }
460}
461
462impl<'ctx, F: Clone> Var<'ctx, F> {
463    /// The value of this var, under a prover-mode assignment.
464    pub fn value(&self, values: Values<'_, F>) -> F {
465        match &self.inner {
466            VarInner::Native(value) => value.clone(),
467            VarInner::Circuit { idx, .. } => values[*idx].clone(),
468        }
469    }
470
471    /// Combine `self` and `other` with a commutative binary operation.
472    ///
473    /// `combine` is the value-level operation used both for the all-native case
474    /// and for prover-mode node evaluation. `node` is the circuit node
475    /// constructor (for example `CircuitNode::Add` or `CircuitNode::Mul`).
476    fn merge(
477        self,
478        other: &Self,
479        combine: impl Fn(&F, &F) -> F,
480        node: fn(CircuitIdx, CircuitIdx) -> CircuitNode,
481    ) -> Self {
482        let (ctx, a_idx, b_idx) = match (self.inner, &other.inner) {
483            (VarInner::Native(a), VarInner::Native(b)) => {
484                return Self {
485                    inner: VarInner::Native(combine(&a, b)),
486                };
487            }
488            (VarInner::Native(ref a), &VarInner::Circuit { ctx, idx: b_idx })
489            | (VarInner::Circuit { ctx, idx: b_idx }, &VarInner::Native(ref a)) => {
490                (ctx, Self::constant(ctx, a.clone()).circuit_idx(), b_idx)
491            }
492            (VarInner::Circuit { ctx, idx: a }, &VarInner::Circuit { idx: b, .. }) => (ctx, a, b),
493        };
494        if let (CircuitIdx::Constant(a_idx), CircuitIdx::Constant(b_idx)) = (a_idx, b_idx) {
495            return Self {
496                inner: VarInner::Circuit {
497                    ctx,
498                    idx: ctx.allocate_constant(|constants| {
499                        combine(&constants[a_idx as usize], &constants[b_idx as usize])
500                    }),
501                },
502            };
503        }
504        let new_idx = ctx.node(node(a_idx, b_idx), move |v| combine(&v[a_idx], &v[b_idx]));
505        Self {
506            inner: VarInner::Circuit { ctx, idx: new_idx },
507        }
508    }
509}
510
511impl<'ctx, F: Object> Object for Var<'ctx, F> {}
512
513impl<'ctx, F: Additive> Add<&Self> for Var<'ctx, F> {
514    type Output = Self;
515    fn add(self, rhs: &Self) -> Self {
516        self.merge(rhs, |a, b| a.clone() + b, CircuitNode::Add)
517    }
518}
519
520impl<'ctx, F: Additive> AddAssign<&Self> for Var<'ctx, F> {
521    fn add_assign(&mut self, rhs: &Self) {
522        *self = self.clone() + rhs;
523    }
524}
525
526impl<'ctx, F: Ring> Neg for Var<'ctx, F> {
527    type Output = Self;
528    fn neg(self) -> Self {
529        match self.inner {
530            VarInner::Native(a) => Self {
531                inner: VarInner::Native(-a),
532            },
533            VarInner::Circuit {
534                ctx,
535                idx: CircuitIdx::Constant(idx),
536            } => Self {
537                inner: VarInner::Circuit {
538                    ctx,
539                    idx: ctx.allocate_constant(|constants| -constants[idx as usize].clone()),
540                },
541            },
542            VarInner::Circuit { ctx, idx } => {
543                let minus_one = Var::constant(ctx, -F::one()).circuit_idx();
544                let new_idx = ctx.node(CircuitNode::Mul(minus_one, idx), move |v| -v[idx].clone());
545                Self {
546                    inner: VarInner::Circuit { ctx, idx: new_idx },
547                }
548            }
549        }
550    }
551}
552
553impl<'ctx, F: Ring> Sub<&Self> for Var<'ctx, F> {
554    type Output = Self;
555    fn sub(self, rhs: &Self) -> Self {
556        self + &(-rhs.clone())
557    }
558}
559
560impl<'ctx, F: Ring> SubAssign<&Self> for Var<'ctx, F> {
561    fn sub_assign(&mut self, rhs: &Self) {
562        *self = self.clone() - rhs;
563    }
564}
565
566impl<'ctx, F: Multiplicative> Mul<&Self> for Var<'ctx, F> {
567    type Output = Self;
568    fn mul(self, rhs: &Self) -> Self {
569        self.merge(rhs, |a, b| a.clone() * b, CircuitNode::Mul)
570    }
571}
572
573impl<'ctx, F: Multiplicative> MulAssign<&Self> for Var<'ctx, F> {
574    fn mul_assign(&mut self, rhs: &Self) {
575        *self = self.clone() * rhs;
576    }
577}
578
579/// Division by `rhs`, computed as a single multiplication constraint.
580///
581/// Rather than inverting `rhs` and multiplying (which costs two
582/// multiplications), the prover supplies the quotient `q = self / rhs` as a
583/// witness and the circuit constrains `q * rhs == self`.
584///
585/// # Caveats
586///
587/// Like [`Field::inv`] on a circuit-backed var, this deviates from the
588/// `inv(0) = 0` field contract: dividing by a circuit-backed `rhs` of zero
589/// adds an unsatisfiable constraint when `self != 0`. Worse, `0 / 0`
590/// constrains `q * 0 == 0`, which holds for *any* `q`, leaving the quotient
591/// unconstrained. Only use `/` where `rhs` is known to be nonzero.
592impl<'ctx, F: Field> Div<&Self> for Var<'ctx, F> {
593    type Output = Self;
594
595    fn div(self, rhs: &Self) -> Self {
596        let &ctx = match (&self.inner, &rhs.inner) {
597            (VarInner::Native(a), VarInner::Native(b)) => {
598                return Self {
599                    inner: VarInner::Native(a.clone() * &b.inv()),
600                };
601            }
602            (VarInner::Circuit { ctx, .. }, _) | (_, VarInner::Circuit { ctx, .. }) => ctx,
603        };
604        let q = { Self::witness(ctx, |v| self.value(v) * &rhs.value(v).inv()) };
605        (q.clone() * rhs).assert_eq(&self);
606        q
607    }
608}
609
610impl<'ctx, F: Field> DivAssign<&Self> for Var<'ctx, F> {
611    fn div_assign(&mut self, rhs: &Self) {
612        *self = self.clone() / rhs;
613    }
614}
615
616impl<'ctx, F: Ring> Additive for Var<'ctx, F> {
617    fn zero() -> Self {
618        Self {
619            inner: VarInner::Native(F::zero()),
620        }
621    }
622}
623
624impl<'ctx, F: Multiplicative> Multiplicative for Var<'ctx, F> {}
625
626impl<'ctx, F: Ring> Ring for Var<'ctx, F> {
627    fn one() -> Self {
628        Self {
629            inner: VarInner::Native(F::one()),
630        }
631    }
632}
633
634/// Unlike the [`Field::inv`] contract, inverting a circuit-backed zero does
635/// not produce zero: it adds an unsatisfiable constraint to the circuit.
636impl<'ctx, F: Field> Field for Var<'ctx, F> {
637    fn inv(&self) -> Self {
638        match &self.inner {
639            VarInner::Native(c) => Self {
640                inner: VarInner::Native(c.inv()),
641            },
642            &VarInner::Circuit { ctx, .. } => {
643                // Prover supplies the inverse via the oracle; verifier just
644                // allocates the slot.
645                let inv = Self::witness(ctx, |v| self.value(v).inv());
646                (inv.clone() * self).assert_eq(&Self::one());
647                inv
648            }
649        }
650    }
651}
652
653/// A circuit value constrained to be `0` or `1`.
654///
655/// A `BoolVar` wraps a [`Var`] together with a guarantee that it holds a
656/// boolean: every constructor either produces a value that is boolean by
657/// construction, or adds the constraint `b * (1 - b) == 0` enforcing it.
658/// Holding that guarantee in the type lets later operations skip
659/// re-checking: [`Self::select`] and the boolean combinators below are sound
660/// precisely because their inputs are already known to be boolean.
661///
662/// The motivating use is scalar multiplication in a circuit, where a scalar
663/// is decomposed into bits (each a `BoolVar`) and a point is accumulated by
664/// conditionally adding with [`Self::select`].
665///
666/// # Native Vars
667///
668/// Like [`Var`], a `BoolVar` built from a native value (see
669/// [`Self::constant`]) lives outside the circuit until combined with a
670/// circuit-backed value. [`Self::assert`] on a native, non-boolean var
671/// therefore panics rather than recording an unsatisfiable constraint, in
672/// keeping with the native-var semantics described on [`Var`].
673///
674/// # Example
675///
676/// ```
677/// use commonware_cryptography::zk::circuit::{build_with_values, BoolVar, Var};
678/// use commonware_math::test::F;
679///
680/// // Use a bit to choose between two values, then check the choice.
681/// let (valued, _) = build_with_values(|ctx| {
682///     let bit = BoolVar::witness(ctx, |_| true);
683///     let a = Var::constant(ctx, F::from(7u64));
684///     let b = Var::constant(ctx, F::from(9u64));
685///     bit.select(&a, &b).assert_eq(&Var::constant(ctx, F::from(7u64)));
686///     Vec::new()
687/// });
688/// assert!(valued.is_satisfied());
689/// ```
690#[derive(Clone)]
691pub struct BoolVar<'ctx, F> {
692    var: Var<'ctx, F>,
693}
694
695impl<F: fmt::Debug> fmt::Debug for BoolVar<'_, F> {
696    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697        f.debug_tuple("BoolVar").field(&self.var).finish()
698    }
699}
700
701impl<'ctx, F> BoolVar<'ctx, F> {
702    /// The underlying [`Var`], whose value is `0` or `1`.
703    pub const fn var(&self) -> &Var<'ctx, F> {
704        &self.var
705    }
706
707    /// Consume this `BoolVar`, returning the underlying [`Var`].
708    pub fn into_var(self) -> Var<'ctx, F> {
709        self.var
710    }
711}
712
713impl<'ctx, F: Ring> BoolVar<'ctx, F> {
714    /// Create a boolean constant with a fixed, public value.
715    ///
716    /// Like [`Var::native`], the value lives outside the circuit until it is
717    /// combined with a circuit-backed var. No constraint is added: the value
718    /// is boolean by construction.
719    pub fn constant(value: bool) -> Self {
720        Self {
721            var: Var::native(if value { F::one() } else { F::zero() }),
722        }
723    }
724
725    /// Assert that this var equals `other`.
726    pub fn assert_eq(&self, other: &Self) {
727        self.var.assert_eq(other.var());
728    }
729
730    /// Select between two vars based on this bit.
731    ///
732    /// Returns `on_true` when the bit is `1` and `on_false` when it is `0`,
733    /// computed as `on_false + b * (on_true - on_false)` with a single
734    /// multiplication.
735    pub fn select(&self, on_true: &Var<'ctx, F>, on_false: &Var<'ctx, F>) -> Var<'ctx, F> {
736        on_false.clone() + &(self.var.clone() * &(on_true.clone() - on_false))
737    }
738
739    /// Allocate a fresh boolean witness, constrained to be `0` or `1`.
740    ///
741    /// In prover mode, `init` receives the values assigned so far and must
742    /// return the bit's value. In verifier mode, `init` does not run. The
743    /// constraint `b * (1 - b) == 0` is added in both modes.
744    ///
745    /// `init` must not use the [`Context`]: doing so deadlocks. See
746    /// [`Values`].
747    pub fn witness(
748        ctx: Context<'ctx, F>,
749        init: impl for<'a> FnOnce(Values<'a, F>) -> bool,
750    ) -> Self {
751        let var = Var::witness(ctx, |v| if init(v) { F::one() } else { F::zero() });
752        Self::enforce(&var);
753        Self { var }
754    }
755
756    /// Constrain an arbitrary var to be boolean and wrap it.
757    ///
758    /// Adds the constraint `var * (1 - var) == 0`, which holds exactly when
759    /// `var` is `0` or `1`.
760    ///
761    /// # Panics
762    ///
763    /// Panics if `var` is a native var whose value is not `0` or `1`, since
764    /// there is no circuit to record the failed constraint in. See
765    /// [`Var::assert_eq`].
766    pub fn assert(var: Var<'ctx, F>) -> Self {
767        Self::enforce(&var);
768        Self { var }
769    }
770
771    /// Add the booleanity constraint `var * var == var` (equivalently
772    /// `var * (1 - var) == 0`).
773    fn enforce(var: &Var<'ctx, F>) {
774        (var.clone() * var).assert_eq(var);
775    }
776}
777
778impl<'ctx, F: Ring> Not for BoolVar<'ctx, F> {
779    type Output = Self;
780
781    fn not(self) -> Self::Output {
782        Self {
783            var: Var::one() - &self.var,
784        }
785    }
786}
787
788impl<'ctx, F: Ring> BitAnd for BoolVar<'ctx, F> {
789    type Output = Self;
790
791    // Over 0/1, boolean `and` IS multiplication, so `self.var * &rhs.var` is
792    // correct. But clippy's `suspicious_arithmetic_impl` lint assumes any
793    // operator impl that uses a *different* operator internally (here, `*` inside
794    // `BitAnd`) is a copy-paste typo. That heuristic is wrong: it has no notion
795    // of the algebra, so it flags correct code and forces this `#[allow]`. The
796    // lint gives confident, authoritative advice that is simply false, which is
797    // why its designer now resides in the Eighth Circle of Hell,
798    // among the fraudulent counselors (Inferno XXVI) who misused their cleverness
799    // to advise others into error, each concealed within a tongue of flame.
800    #[allow(clippy::suspicious_arithmetic_impl)]
801    fn bitand(self, rhs: Self) -> Self::Output {
802        Self {
803            var: self.var * &rhs.var,
804        }
805    }
806}
807
808impl<'ctx, F: Ring> BitOr for BoolVar<'ctx, F> {
809    type Output = Self;
810
811    // Boolean `or` over 0/1 is `a + b - a * b`, which is correct.
812    fn bitor(self, rhs: Self) -> Self::Output {
813        Self {
814            var: self.var.clone() + &rhs.var - &(self.var * &rhs.var),
815        }
816    }
817}
818
819/// A tool for selecting among multiple items.
820///
821/// This is a generalization of using a [`BoolVar`] to select among two items,
822/// letting you use `k` bits to select among `2^k` items.
823pub struct Selector<'ctx, F> {
824    monomials: Vec<Var<'ctx, F>>,
825}
826
827impl<'ctx, F: Ring> Selector<'ctx, F> {
828    /// Create a new selector, using a given number of in-circuit bits.
829    ///
830    /// This selector will then be able to select among exactly `2^k` items,
831    /// where `k` is the number of bits passed in here.
832    ///
833    /// It is more efficient to create one selector and reuse it.
834    ///
835    /// The selection is made in ascending order, i.e. 0..00, 0..01, 0..10, ...
836    /// In other words, if you pass in, e.g. 010 to this function, you then
837    /// will always get the 3rd (index 2) of 8 items.
838    pub fn new(bits: &[BoolVar<'ctx, F>]) -> Self {
839        let mut monomials = vec![Var::one(); 1usize << bits.len()];
840        for mask in 1..monomials.len() {
841            let bit = mask.trailing_zeros() as usize;
842            let prev = mask ^ (1usize << bit);
843            monomials[mask] = if prev == 0 {
844                bits[bit].var().clone()
845            } else {
846                monomials[prev].clone() * bits[bit].var()
847            };
848        }
849        Self { monomials }
850    }
851
852    /// Select a constant value among `2^k` possibilities.
853    pub fn select_constant(&self, constants: &[F]) -> Var<'ctx, F> {
854        assert_eq!(
855            self.monomials.len(),
856            constants.len(),
857            "constants len must match selectors len"
858        );
859
860        let values = {
861            let mut values = constants.to_vec();
862            let len = values.len().trailing_zeros() as usize;
863            for bit in 0..len {
864                for mask in 0..values.len() {
865                    if mask & (1usize << bit) != 0 {
866                        let prev = values[mask ^ (1usize << bit)].clone();
867                        values[mask] -= &prev;
868                    }
869                }
870            }
871            values
872        };
873
874        values
875            .into_iter()
876            .zip(&self.monomials)
877            .map(|(v_i, m_i)| Var::native(v_i) * m_i)
878            .reduce(|acc, x| acc + &x)
879            .expect("values is non empty")
880    }
881}
882
883/// Build a circuit without computing an assignment (verifier mode).
884///
885/// Witness `init` closures do not run in this mode.
886///
887/// The closure returns the vars whose circuit indices the caller wants back
888/// (e.g. the committed outputs of a circuit); the returned indices are in the
889/// same order. Return an empty vec to ignore this.
890///
891/// # Panics
892///
893/// Panics if any returned var is native (not backed by the circuit).
894pub fn build<F: Ring>(
895    f: impl for<'ctx> FnOnce(Context<'ctx, F>) -> Vec<Var<'ctx, F>>,
896) -> (Circuit<F>, Vec<CircuitIdx>) {
897    let inner = ContextInner {
898        values: None,
899        circuit: Mutex::new(Circuit::default()),
900    };
901    let indices = f(Context {
902        inner: &inner,
903        _brand: PhantomData,
904    })
905    .iter()
906    .map(Var::circuit_idx)
907    .collect();
908    (inner.circuit.into_inner(), indices)
909}
910
911/// Build a circuit while simultaneously computing the assignment (prover mode).
912///
913/// Each witness's value comes from the `init` closure passed to
914/// [`Var::witness`].
915///
916/// The closure returns the vars whose circuit indices the caller wants back
917/// (e.g. the committed outputs of a circuit); the returned indices are in the
918/// same order. Return an empty vec to ignore this.
919///
920/// # Panics
921///
922/// Panics if any returned var is native (not backed by the circuit).
923pub fn build_with_values<F: Ring>(
924    f: impl for<'ctx> FnOnce(Context<'ctx, F>) -> Vec<Var<'ctx, F>>,
925) -> (ValuedCircuit<F>, Vec<CircuitIdx>) {
926    let inner = ContextInner {
927        values: Some(Mutex::new(ValuesBuilder {
928            witnesses: Vec::new(),
929            nodes: Vec::new(),
930        })),
931        circuit: Mutex::new(Circuit::default()),
932    };
933    let indices = f(Context {
934        inner: &inner,
935        _brand: PhantomData,
936    })
937    .iter()
938    .map(Var::circuit_idx)
939    .collect();
940    let circuit = inner.circuit.into_inner();
941    let values = inner.values.unwrap().into_inner();
942    (
943        ValuedCircuit {
944            circuit,
945            witnesses: values.witnesses,
946            nodes: values.nodes,
947        },
948        indices,
949    )
950}
951
952/// Fuzzing utilities, comparing circuit satisfaction against native
953/// evaluation of the same operations.
954#[commonware_macros::stability(ALPHA)]
955#[cfg(any(test, feature = "fuzz"))]
956pub mod fuzz {
957    use super::*;
958    use arbitrary::{Arbitrary, Unstructured};
959    use commonware_math::test::F;
960
961    #[derive(Debug)]
962    enum Op {
963        Witness(F),
964        Constant(F),
965        Zero,
966        One,
967        Add(usize, usize),
968        Sub(usize, usize),
969        Mul(usize, usize),
970        Neg(usize),
971        Inv(usize),
972        AssertEq(usize, usize),
973    }
974
975    /// A random sequence of circuit operations, together with whether the
976    /// circuit they build should be satisfied.
977    ///
978    /// Values are drawn from a small range so that assertions have a decent
979    /// chance of holding, exercising both outcomes.
980    #[derive(Debug)]
981    pub struct Plan {
982        ops: Vec<Op>,
983        satisfied: bool,
984    }
985
986    impl Arbitrary<'_> for Plan {
987        fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
988            let mut ops = Vec::new();
989            let mut values: Vec<F> = Vec::new();
990            let mut is_native: Vec<bool> = Vec::new();
991            let mut satisfied = true;
992            for _ in 0..u.int_in_range(1..=32)? {
993                let kind = if values.is_empty() {
994                    u.int_in_range(0..=3)?
995                } else {
996                    u.int_in_range(0..=9)?
997                };
998                if kind <= 3 {
999                    let v = F::from(u.int_in_range::<u8>(0..=4)?);
1000                    let (op, value, native) = match kind {
1001                        0 => (Op::Witness(v), v, false),
1002                        1 => (Op::Constant(v), v, false),
1003                        2 => (Op::Zero, F::zero(), true),
1004                        _ => (Op::One, F::one(), true),
1005                    };
1006                    ops.push(op);
1007                    values.push(value);
1008                    is_native.push(native);
1009                    continue;
1010                }
1011                let a = u.int_in_range(0..=values.len() - 1)?;
1012                let b = u.int_in_range(0..=values.len() - 1)?;
1013                let merged = is_native[a] && is_native[b];
1014                let (op, value, native) = match kind {
1015                    4 => (Op::Add(a, b), values[a] + &values[b], merged),
1016                    5 => (Op::Sub(a, b), values[a] - &values[b], merged),
1017                    6 => (Op::Mul(a, b), values[a] * &values[b], merged),
1018                    7 => (Op::Neg(a), -values[a], is_native[a]),
1019                    8 => {
1020                        // Inverting a circuit-backed zero constrains z * 0 = 1,
1021                        // which is unsatisfiable. Native vars add no constraint.
1022                        if !is_native[a] && values[a] == F::zero() {
1023                            satisfied = false;
1024                        }
1025                        (Op::Inv(a), values[a].inv(), is_native[a])
1026                    }
1027                    _ => {
1028                        // Asserting equality between two unequal native vars is
1029                        // a panic by design, so the plan must avoid it.
1030                        if merged && values[a] != values[b] {
1031                            continue;
1032                        }
1033                        ops.push(Op::AssertEq(a, b));
1034                        satisfied = satisfied && values[a] == values[b];
1035                        continue;
1036                    }
1037                };
1038                ops.push(op);
1039                values.push(value);
1040                is_native.push(native);
1041            }
1042            Ok(Self { ops, satisfied })
1043        }
1044    }
1045
1046    impl Plan {
1047        /// Check that satisfaction matches the natively computed expectation.
1048        pub fn run(self, _u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
1049            assert_eq!(
1050                self.build().is_satisfied(),
1051                self.satisfied(),
1052                "plan: {self:?}"
1053            );
1054            Ok(())
1055        }
1056
1057        /// Whether the circuit built by [`Self::build`] should be satisfied.
1058        pub const fn satisfied(&self) -> bool {
1059            self.satisfied
1060        }
1061
1062        /// Build the circuit, along with its prover assignment.
1063        pub fn build(&self) -> ValuedCircuit<F> {
1064            build_with_values(|ctx| {
1065                let mut vars: Vec<Var<'_, F>> = Vec::new();
1066                for op in &self.ops {
1067                    let var = match *op {
1068                        Op::Witness(v) => Var::witness(ctx, move |_| v),
1069                        Op::Constant(v) => Var::constant(ctx, v),
1070                        Op::Zero => Var::zero(),
1071                        Op::One => Var::one(),
1072                        Op::Add(a, b) => vars[a].clone() + &vars[b],
1073                        Op::Sub(a, b) => vars[a].clone() - &vars[b],
1074                        Op::Mul(a, b) => vars[a].clone() * &vars[b],
1075                        Op::Neg(a) => -vars[a].clone(),
1076                        Op::Inv(a) => vars[a].inv(),
1077                        Op::AssertEq(a, b) => {
1078                            vars[a].assert_eq(&vars[b]);
1079                            continue;
1080                        }
1081                    };
1082                    vars.push(var);
1083                }
1084                Vec::new()
1085            })
1086            .0
1087        }
1088    }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093    use super::*;
1094    use commonware_invariants::minifuzz;
1095    use commonware_math::test::F;
1096
1097    #[test]
1098    fn test_is_satisfied_matches_native_evaluation_minifuzz() {
1099        minifuzz::test(|u| u.arbitrary::<fuzz::Plan>()?.run(u));
1100    }
1101
1102    #[test]
1103    fn test_is_satisfied_cubic() {
1104        // Constrain a witness `x` to satisfy `x^3 + x + 5 = 35`.
1105        let cubic = |x_value: u64| {
1106            build_with_values(move |ctx| {
1107                let x = Var::witness(ctx, move |_| F::from(x_value));
1108                let out = x.clone() * &x * &x + &x + &Var::constant(ctx, F::from(5u64));
1109                out.assert_eq(&Var::constant(ctx, F::from(35u64)));
1110                Vec::new()
1111            })
1112            .0
1113        };
1114        assert!(cubic(3).is_satisfied());
1115        assert!(!cubic(4).is_satisfied());
1116    }
1117
1118    #[test]
1119    fn test_bool_witness_enforces_booleanity() {
1120        // A boolean witness from a `bool` is always satisfiable.
1121        for b in [false, true] {
1122            let (valued, _) = build_with_values(move |ctx| {
1123                BoolVar::<F>::witness(ctx, move |_| b);
1124                Vec::new()
1125            });
1126            assert!(valued.is_satisfied());
1127        }
1128
1129        // A var that is not 0 or 1 fails the booleanity constraint.
1130        let (bad, _) = build_with_values(|ctx| {
1131            let two = Var::witness(ctx, |_| F::from(2u64));
1132            BoolVar::assert(two);
1133            Vec::new()
1134        });
1135        assert!(!bad.is_satisfied());
1136
1137        // 0 and 1 pass `from_var`.
1138        for v in [0u64, 1u64] {
1139            let (valued, _) = build_with_values(move |ctx| {
1140                let x = Var::witness(ctx, move |_| F::from(v));
1141                BoolVar::assert(x);
1142                Vec::new()
1143            });
1144            assert!(valued.is_satisfied());
1145        }
1146    }
1147
1148    #[test]
1149    fn test_bool_select() {
1150        // `select` returns `on_true` when the bit is set, else `on_false`.
1151        for b in [false, true] {
1152            let (valued, _) = build_with_values(move |ctx| {
1153                let bit = BoolVar::witness(ctx, move |_| b);
1154                let on_true = Var::witness(ctx, |_| F::from(7u64));
1155                let on_false = Var::witness(ctx, |_| F::from(9u64));
1156                let selected = bit.select(&on_true, &on_false);
1157                let expected = if b { F::from(7u64) } else { F::from(9u64) };
1158                selected.assert_eq(&Var::constant(ctx, expected));
1159                Vec::new()
1160            });
1161            assert!(valued.is_satisfied());
1162        }
1163    }
1164
1165    #[test]
1166    fn test_bool_combinators_truth_tables() {
1167        for a in [false, true] {
1168            for b in [false, true] {
1169                let (valued, _) = build_with_values::<F>(move |ctx| {
1170                    let a_var = BoolVar::witness(ctx, move |_| a);
1171                    let b_var = BoolVar::witness(ctx, move |_| b);
1172                    (!a_var.clone()).assert_eq(&BoolVar::constant(!a));
1173                    (a_var.clone() & b_var.clone()).assert_eq(&BoolVar::constant(a & b));
1174                    (a_var | b_var).assert_eq(&BoolVar::constant(a | b));
1175                    Vec::new()
1176                });
1177                assert!(valued.is_satisfied());
1178            }
1179        }
1180    }
1181
1182    #[test]
1183    fn test_bool_constant() {
1184        let (valued, _) = build_with_values(|ctx| {
1185            // A native boolean constant folds in correctly when combined.
1186            let t = BoolVar::<F>::constant(true);
1187            let f = BoolVar::<F>::constant(false);
1188            let x = Var::witness(ctx, |_| F::from(5u64));
1189            t.select(&x, &Var::zero())
1190                .assert_eq(&Var::constant(ctx, F::from(5u64)));
1191            f.select(&x, &Var::zero()).assert_eq(&Var::zero());
1192            Vec::new()
1193        });
1194        assert!(valued.is_satisfied());
1195    }
1196}