use commonware_math::algebra::{Additive, Field, Multiplicative, Object, Ring};
use commonware_utils::sync::Mutex;
use std::{
fmt,
marker::PhantomData,
ops::{
Add, AddAssign, BitAnd, BitOr, Div, DivAssign, Index, Mul, MulAssign, Neg, Not, Sub,
SubAssign,
},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum CircuitIdx {
Constant(u32),
Witness(u32),
Node(u32),
}
pub(crate) enum CircuitNode {
Add(CircuitIdx, CircuitIdx),
Mul(CircuitIdx, CircuitIdx),
}
pub struct Circuit<F> {
pub(crate) witnesses: u32,
pub(crate) constants: Vec<F>,
pub(crate) nodes: Vec<CircuitNode>,
pub(crate) assertions: Vec<(CircuitIdx, CircuitIdx)>,
}
impl<F> Default for Circuit<F> {
fn default() -> Self {
Self {
witnesses: 0,
constants: Vec::new(),
nodes: Vec::new(),
assertions: Vec::new(),
}
}
}
impl<F> Circuit<F> {
const fn next_witness(&mut self) -> CircuitIdx {
let next = CircuitIdx::Witness(self.witnesses);
self.witnesses += 1;
next
}
fn next_constant(&mut self, x: F) -> CircuitIdx {
let next = CircuitIdx::Constant(self.constants.len() as u32);
self.constants.push(x);
next
}
fn next_node(&mut self, n: CircuitNode) -> CircuitIdx {
let next = CircuitIdx::Node(self.nodes.len() as u32);
self.nodes.push(n);
next
}
}
pub struct ValuedCircuit<F> {
pub(crate) circuit: Circuit<F>,
pub(crate) witnesses: Vec<F>,
pub(crate) nodes: Vec<F>,
}
#[doc(hidden)]
impl<F> Index<CircuitIdx> for ValuedCircuit<F> {
type Output = F;
fn index(&self, index: CircuitIdx) -> &Self::Output {
match index {
CircuitIdx::Constant(i) => &self.circuit.constants[i as usize],
CircuitIdx::Witness(i) => &self.witnesses[i as usize],
CircuitIdx::Node(i) => &self.nodes[i as usize],
}
}
}
impl<F: PartialEq> ValuedCircuit<F> {
#[must_use]
pub fn is_satisfied(&self) -> bool {
self.circuit
.assertions
.iter()
.all(|&(a, b)| self[a] == self[b])
}
}
struct ValuesBuilder<F> {
witnesses: Vec<F>,
nodes: Vec<F>,
}
pub struct Values<'a, F> {
constants: &'a [F],
witnesses: &'a [F],
nodes: &'a [F],
}
impl<F> Clone for Values<'_, F> {
fn clone(&self) -> Self {
*self
}
}
impl<F> Copy for Values<'_, F> {}
#[doc(hidden)]
impl<'a, F> Index<CircuitIdx> for Values<'a, F> {
type Output = F;
fn index(&self, index: CircuitIdx) -> &Self::Output {
match index {
CircuitIdx::Witness(id) => &self.witnesses[id as usize],
CircuitIdx::Constant(id) => &self.constants[id as usize],
CircuitIdx::Node(id) => &self.nodes[id as usize],
}
}
}
struct ContextInner<F> {
values: Option<Mutex<ValuesBuilder<F>>>,
circuit: Mutex<Circuit<F>>,
}
pub struct Context<'ctx, F> {
inner: &'ctx ContextInner<F>,
_brand: PhantomData<fn(&'ctx ()) -> &'ctx ()>,
}
impl<F> Clone for Context<'_, F> {
fn clone(&self) -> Self {
*self
}
}
impl<F> Copy for Context<'_, F> {}
impl<'ctx, F> Context<'ctx, F> {
fn allocate_constant(self, combine: impl Fn(&[F]) -> F) -> CircuitIdx {
let mut circuit = self.inner.circuit.lock();
let combined = combine(&circuit.constants);
circuit.next_constant(combined)
}
fn allocate(
self,
init: impl for<'a> FnOnce(Values<'a, F>) -> Option<F>,
reserve: impl FnOnce(&mut Circuit<F>) -> CircuitIdx,
) -> CircuitIdx {
let mut circuit = self.inner.circuit.lock();
if let Some(values) = &self.inner.values {
let mut values = values.lock();
let value = init(Values {
constants: &circuit.constants,
witnesses: &values.witnesses,
nodes: &values.nodes,
});
let idx = reserve(&mut circuit);
match idx {
CircuitIdx::Witness(_) => {
values
.witnesses
.push(value.expect("witness allocations populate prover assignments"));
}
CircuitIdx::Node(_) => {
values
.nodes
.push(value.expect("node allocations populate prover assignments"));
}
CircuitIdx::Constant(_) => {
assert!(
value.is_none(),
"constants do not populate prover assignments"
);
}
}
return idx;
}
reserve(&mut circuit)
}
fn node(self, n: CircuitNode, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> CircuitIdx {
self.allocate(|values| Some(init(values)), |circuit| circuit.next_node(n))
}
fn assert_eq(self, a: CircuitIdx, b: CircuitIdx) {
self.inner.circuit.lock().assertions.push((a, b));
}
fn witness(self, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> CircuitIdx {
self.allocate(|values| Some(init(values)), Circuit::next_witness)
}
}
impl<'ctx, F> Context<'ctx, F> {
fn constant(self, x: F) -> CircuitIdx {
self.allocate(|_| None, |circuit| circuit.next_constant(x))
}
}
#[derive(Clone)]
enum VarInner<'ctx, F> {
Native(F),
Circuit {
ctx: Context<'ctx, F>,
idx: CircuitIdx,
},
}
#[derive(Clone)]
pub struct Var<'ctx, F> {
inner: VarInner<'ctx, F>,
}
impl<F: fmt::Debug> fmt::Debug for Var<'_, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
VarInner::Native(value) => f.debug_tuple("Native").field(value).finish(),
VarInner::Circuit { ctx, idx } => {
let ctx_ptr = ctx.inner as *const ContextInner<F>;
f.debug_struct("Circuit")
.field("ctx", &ctx_ptr)
.field("idx", idx)
.finish()
}
}
}
}
impl<F: PartialEq> PartialEq for Var<'_, F> {
fn eq(&self, other: &Self) -> bool {
match (&self.inner, &other.inner) {
(VarInner::Native(a), VarInner::Native(b)) => a == b,
(VarInner::Circuit { idx: a_idx, .. }, VarInner::Circuit { idx: b_idx, .. }) => {
a_idx == b_idx
}
_ => false,
}
}
}
impl<F: Eq> Eq for Var<'_, F> {}
impl<'ctx, F> Var<'ctx, F> {
pub fn witness(ctx: Context<'ctx, F>, init: impl for<'a> FnOnce(Values<'a, F>) -> F) -> Self {
Self {
inner: VarInner::Circuit {
ctx,
idx: ctx.witness(init),
},
}
}
pub const fn native(value: F) -> Self {
Self {
inner: VarInner::Native(value),
}
}
pub fn constant(ctx: Context<'ctx, F>, value: F) -> Self {
Self {
inner: VarInner::Circuit {
ctx,
idx: ctx.constant(value),
},
}
}
pub fn assert_eq(&self, other: &Self)
where
F: Clone + PartialEq,
{
match (&self.inner, &other.inner) {
(VarInner::Native(a), VarInner::Native(b)) => {
assert!(a == b, "asserted equality between distinct native vars");
}
(VarInner::Native(a), VarInner::Circuit { ctx, idx })
| (VarInner::Circuit { ctx, idx }, VarInner::Native(a)) => {
ctx.assert_eq(Self::constant(*ctx, a.clone()).circuit_idx(), *idx);
}
(VarInner::Circuit { ctx, idx: a }, VarInner::Circuit { idx: b, .. }) => {
ctx.assert_eq(*a, *b);
}
}
}
fn circuit_idx(&self) -> CircuitIdx {
match self.inner {
VarInner::Circuit { idx, .. } => idx,
VarInner::Native(_) => panic!("expected circuit-backed var"),
}
}
}
impl<'ctx, F: Clone> Var<'ctx, F> {
pub fn value(&self, values: Values<'_, F>) -> F {
match &self.inner {
VarInner::Native(value) => value.clone(),
VarInner::Circuit { idx, .. } => values[*idx].clone(),
}
}
fn merge(
self,
other: &Self,
combine: impl Fn(&F, &F) -> F,
node: fn(CircuitIdx, CircuitIdx) -> CircuitNode,
) -> Self {
let (ctx, a_idx, b_idx) = match (self.inner, &other.inner) {
(VarInner::Native(a), VarInner::Native(b)) => {
return Self {
inner: VarInner::Native(combine(&a, b)),
}
}
(VarInner::Native(ref a), &VarInner::Circuit { ctx, idx: b_idx })
| (VarInner::Circuit { ctx, idx: b_idx }, &VarInner::Native(ref a)) => {
(ctx, Self::constant(ctx, a.clone()).circuit_idx(), b_idx)
}
(VarInner::Circuit { ctx, idx: a }, &VarInner::Circuit { idx: b, .. }) => (ctx, a, b),
};
if let (CircuitIdx::Constant(a_idx), CircuitIdx::Constant(b_idx)) = (a_idx, b_idx) {
return Self {
inner: VarInner::Circuit {
ctx,
idx: ctx.allocate_constant(|constants| {
combine(&constants[a_idx as usize], &constants[b_idx as usize])
}),
},
};
}
let new_idx = ctx.node(node(a_idx, b_idx), move |v| combine(&v[a_idx], &v[b_idx]));
Self {
inner: VarInner::Circuit { ctx, idx: new_idx },
}
}
}
impl<'ctx, F: Object> Object for Var<'ctx, F> {}
impl<'ctx, F: Additive> Add<&Self> for Var<'ctx, F> {
type Output = Self;
fn add(self, rhs: &Self) -> Self {
self.merge(rhs, |a, b| a.clone() + b, CircuitNode::Add)
}
}
impl<'ctx, F: Additive> AddAssign<&Self> for Var<'ctx, F> {
fn add_assign(&mut self, rhs: &Self) {
*self = self.clone() + rhs;
}
}
impl<'ctx, F: Additive + Ring> Neg for Var<'ctx, F> {
type Output = Self;
fn neg(self) -> Self {
match self.inner {
VarInner::Native(a) => Self {
inner: VarInner::Native(-a),
},
VarInner::Circuit {
ctx,
idx: CircuitIdx::Constant(idx),
} => Self {
inner: VarInner::Circuit {
ctx,
idx: ctx.allocate_constant(|constants| -constants[idx as usize].clone()),
},
},
VarInner::Circuit { ctx, idx } => {
let minus_one = Var::constant(ctx, -F::one()).circuit_idx();
let new_idx = ctx.node(CircuitNode::Mul(minus_one, idx), move |v| -v[idx].clone());
Self {
inner: VarInner::Circuit { ctx, idx: new_idx },
}
}
}
}
}
impl<'ctx, F: Additive + Ring> Sub<&Self> for Var<'ctx, F> {
type Output = Self;
fn sub(self, rhs: &Self) -> Self {
self + &(-rhs.clone())
}
}
impl<'ctx, F: Additive + Ring> SubAssign<&Self> for Var<'ctx, F> {
fn sub_assign(&mut self, rhs: &Self) {
*self = self.clone() - rhs;
}
}
impl<'ctx, F: Multiplicative> Mul<&Self> for Var<'ctx, F> {
type Output = Self;
fn mul(self, rhs: &Self) -> Self {
self.merge(rhs, |a, b| a.clone() * b, CircuitNode::Mul)
}
}
impl<'ctx, F: Multiplicative> MulAssign<&Self> for Var<'ctx, F> {
fn mul_assign(&mut self, rhs: &Self) {
*self = self.clone() * rhs;
}
}
impl<'ctx, F: Field> Div<&Self> for Var<'ctx, F> {
type Output = Self;
fn div(self, rhs: &Self) -> Self {
let &ctx = match (&self.inner, &rhs.inner) {
(VarInner::Native(a), VarInner::Native(b)) => {
return Self {
inner: VarInner::Native(a.clone() * &b.inv()),
}
}
(VarInner::Circuit { ctx, .. }, _) | (_, VarInner::Circuit { ctx, .. }) => ctx,
};
let q = { Self::witness(ctx, |v| self.value(v) * &rhs.value(v).inv()) };
(q.clone() * rhs).assert_eq(&self);
q
}
}
impl<'ctx, F: Field> DivAssign<&Self> for Var<'ctx, F> {
fn div_assign(&mut self, rhs: &Self) {
*self = self.clone() / rhs;
}
}
impl<'ctx, F: Additive + Ring> Additive for Var<'ctx, F> {
fn zero() -> Self {
Self {
inner: VarInner::Native(F::zero()),
}
}
}
impl<'ctx, F: Multiplicative> Multiplicative for Var<'ctx, F> {}
impl<'ctx, F: Ring> Ring for Var<'ctx, F> {
fn one() -> Self {
Self {
inner: VarInner::Native(F::one()),
}
}
}
impl<'ctx, F: Field> Field for Var<'ctx, F> {
fn inv(&self) -> Self {
match &self.inner {
VarInner::Native(c) => Self {
inner: VarInner::Native(c.inv()),
},
&VarInner::Circuit { ctx, .. } => {
let inv = Self::witness(ctx, |v| self.value(v).inv());
(inv.clone() * self).assert_eq(&Self::one());
inv
}
}
}
}
#[derive(Clone)]
pub struct BoolVar<'ctx, F> {
var: Var<'ctx, F>,
}
impl<F: fmt::Debug> fmt::Debug for BoolVar<'_, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("BoolVar").field(&self.var).finish()
}
}
impl<'ctx, F> BoolVar<'ctx, F> {
pub const fn var(&self) -> &Var<'ctx, F> {
&self.var
}
#[allow(clippy::missing_const_for_fn)]
pub fn into_var(self) -> Var<'ctx, F> {
self.var
}
}
impl<'ctx, F: Ring> BoolVar<'ctx, F> {
pub fn constant(value: bool) -> Self {
Self {
var: Var::native(if value { F::one() } else { F::zero() }),
}
}
pub fn assert_eq(&self, other: &Self) {
self.var.assert_eq(other.var());
}
}
impl<'ctx, F: Ring + PartialEq> BoolVar<'ctx, F> {
pub fn witness(
ctx: Context<'ctx, F>,
init: impl for<'a> FnOnce(Values<'a, F>) -> bool,
) -> Self {
let var = Var::witness(ctx, |v| if init(v) { F::one() } else { F::zero() });
Self::enforce(&var);
Self { var }
}
pub fn assert(var: Var<'ctx, F>) -> Self {
Self::enforce(&var);
Self { var }
}
fn enforce(var: &Var<'ctx, F>) {
(var.clone() * var).assert_eq(var);
}
}
impl<'ctx, F: Ring> BoolVar<'ctx, F> {
pub fn select(&self, on_true: &Var<'ctx, F>, on_false: &Var<'ctx, F>) -> Var<'ctx, F> {
on_false.clone() + &(self.var.clone() * &(on_true.clone() - on_false))
}
}
impl<'ctx, F: Ring> Not for BoolVar<'ctx, F> {
type Output = Self;
fn not(self) -> Self::Output {
Self {
var: Var::one() - &self.var,
}
}
}
impl<'ctx, F: Ring> BitAnd for BoolVar<'ctx, F> {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn bitand(self, rhs: Self) -> Self::Output {
Self {
var: self.var * &rhs.var,
}
}
}
impl<'ctx, F: Ring> BitOr for BoolVar<'ctx, F> {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn bitor(self, rhs: Self) -> Self::Output {
Self {
var: self.var.clone() + &rhs.var - &(self.var * &rhs.var),
}
}
}
pub struct Selector<'ctx, F> {
monomials: Vec<Var<'ctx, F>>,
}
impl<'ctx, F: Ring> Selector<'ctx, F> {
pub fn new(bits: &[BoolVar<'ctx, F>]) -> Self {
let mut monomials = vec![Var::one(); 1usize << bits.len()];
for mask in 1..monomials.len() {
let bit = mask.trailing_zeros() as usize;
let prev = mask ^ (1usize << bit);
monomials[mask] = if prev == 0 {
bits[bit].var().clone()
} else {
monomials[prev].clone() * bits[bit].var()
};
}
Self { monomials }
}
pub fn select_constant(&self, constants: &[F]) -> Var<'ctx, F> {
assert_eq!(
self.monomials.len(),
constants.len(),
"constants len must match selectors len"
);
let values = {
let mut values = constants.to_vec();
let len = values.len().trailing_zeros() as usize;
for bit in 0..len {
for mask in 0..values.len() {
if mask & (1usize << bit) != 0 {
let prev = values[mask ^ (1usize << bit)].clone();
values[mask] -= &prev;
}
}
}
values
};
values
.into_iter()
.zip(&self.monomials)
.map(|(v_i, m_i)| Var::native(v_i) * m_i)
.reduce(|acc, x| acc + &x)
.expect("values is non empty")
}
}
pub fn build<F: Ring + PartialEq>(
f: impl for<'ctx> FnOnce(Context<'ctx, F>) -> Vec<Var<'ctx, F>>,
) -> (Circuit<F>, Vec<CircuitIdx>) {
let inner = ContextInner {
values: None,
circuit: Mutex::new(Circuit::default()),
};
let indices = f(Context {
inner: &inner,
_brand: PhantomData,
})
.iter()
.map(Var::circuit_idx)
.collect();
(inner.circuit.into_inner(), indices)
}
pub fn build_with_values<F: Ring + PartialEq>(
f: impl for<'ctx> FnOnce(Context<'ctx, F>) -> Vec<Var<'ctx, F>>,
) -> (ValuedCircuit<F>, Vec<CircuitIdx>) {
let inner = ContextInner {
values: Some(Mutex::new(ValuesBuilder {
witnesses: Vec::new(),
nodes: Vec::new(),
})),
circuit: Mutex::new(Circuit::default()),
};
let indices = f(Context {
inner: &inner,
_brand: PhantomData,
})
.iter()
.map(Var::circuit_idx)
.collect();
let circuit = inner.circuit.into_inner();
let values = inner.values.unwrap().into_inner();
(
ValuedCircuit {
circuit,
witnesses: values.witnesses,
nodes: values.nodes,
},
indices,
)
}
#[commonware_macros::stability(ALPHA)]
#[cfg(any(test, feature = "fuzz"))]
pub mod fuzz {
use super::*;
use arbitrary::{Arbitrary, Unstructured};
use commonware_math::test::F;
#[derive(Debug)]
enum Op {
Witness(F),
Constant(F),
Zero,
One,
Add(usize, usize),
Sub(usize, usize),
Mul(usize, usize),
Neg(usize),
Inv(usize),
AssertEq(usize, usize),
}
#[derive(Debug)]
pub struct Plan {
ops: Vec<Op>,
satisfied: bool,
}
impl Arbitrary<'_> for Plan {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
let mut ops = Vec::new();
let mut values: Vec<F> = Vec::new();
let mut is_native: Vec<bool> = Vec::new();
let mut satisfied = true;
for _ in 0..u.int_in_range(1..=32)? {
let kind = if values.is_empty() {
u.int_in_range(0..=3)?
} else {
u.int_in_range(0..=9)?
};
if kind <= 3 {
let v = F::from(u.int_in_range::<u8>(0..=4)?);
let (op, value, native) = match kind {
0 => (Op::Witness(v), v, false),
1 => (Op::Constant(v), v, false),
2 => (Op::Zero, F::zero(), true),
_ => (Op::One, F::one(), true),
};
ops.push(op);
values.push(value);
is_native.push(native);
continue;
}
let a = u.int_in_range(0..=values.len() - 1)?;
let b = u.int_in_range(0..=values.len() - 1)?;
let merged = is_native[a] && is_native[b];
let (op, value, native) = match kind {
4 => (Op::Add(a, b), values[a] + &values[b], merged),
5 => (Op::Sub(a, b), values[a] - &values[b], merged),
6 => (Op::Mul(a, b), values[a] * &values[b], merged),
7 => (Op::Neg(a), -values[a], is_native[a]),
8 => {
if !is_native[a] && values[a] == F::zero() {
satisfied = false;
}
(Op::Inv(a), values[a].inv(), is_native[a])
}
_ => {
if merged && values[a] != values[b] {
continue;
}
ops.push(Op::AssertEq(a, b));
satisfied = satisfied && values[a] == values[b];
continue;
}
};
ops.push(op);
values.push(value);
is_native.push(native);
}
Ok(Self { ops, satisfied })
}
}
impl Plan {
pub fn run(self, _u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
assert_eq!(
self.build().is_satisfied(),
self.satisfied(),
"plan: {self:?}"
);
Ok(())
}
pub const fn satisfied(&self) -> bool {
self.satisfied
}
pub fn build(&self) -> ValuedCircuit<F> {
build_with_values(|ctx| {
let mut vars: Vec<Var<'_, F>> = Vec::new();
for op in &self.ops {
let var = match *op {
Op::Witness(v) => Var::witness(ctx, move |_| v),
Op::Constant(v) => Var::constant(ctx, v),
Op::Zero => Var::zero(),
Op::One => Var::one(),
Op::Add(a, b) => vars[a].clone() + &vars[b],
Op::Sub(a, b) => vars[a].clone() - &vars[b],
Op::Mul(a, b) => vars[a].clone() * &vars[b],
Op::Neg(a) => -vars[a].clone(),
Op::Inv(a) => vars[a].inv(),
Op::AssertEq(a, b) => {
vars[a].assert_eq(&vars[b]);
continue;
}
};
vars.push(var);
}
Vec::new()
})
.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_invariants::minifuzz;
use commonware_math::test::F;
#[test]
fn test_is_satisfied_matches_native_evaluation_minifuzz() {
minifuzz::test(|u| u.arbitrary::<fuzz::Plan>()?.run(u));
}
#[test]
fn test_is_satisfied_cubic() {
let cubic = |x_value: u64| {
build_with_values(move |ctx| {
let x = Var::witness(ctx, move |_| F::from(x_value));
let out = x.clone() * &x * &x + &x + &Var::constant(ctx, F::from(5u64));
out.assert_eq(&Var::constant(ctx, F::from(35u64)));
Vec::new()
})
.0
};
assert!(cubic(3).is_satisfied());
assert!(!cubic(4).is_satisfied());
}
#[test]
fn test_bool_witness_enforces_booleanity() {
for b in [false, true] {
let (valued, _) = build_with_values(move |ctx| {
BoolVar::<F>::witness(ctx, move |_| b);
Vec::new()
});
assert!(valued.is_satisfied());
}
let (bad, _) = build_with_values(|ctx| {
let two = Var::witness(ctx, |_| F::from(2u64));
BoolVar::assert(two);
Vec::new()
});
assert!(!bad.is_satisfied());
for v in [0u64, 1u64] {
let (valued, _) = build_with_values(move |ctx| {
let x = Var::witness(ctx, move |_| F::from(v));
BoolVar::assert(x);
Vec::new()
});
assert!(valued.is_satisfied());
}
}
#[test]
fn test_bool_select() {
for b in [false, true] {
let (valued, _) = build_with_values(move |ctx| {
let bit = BoolVar::witness(ctx, move |_| b);
let on_true = Var::witness(ctx, |_| F::from(7u64));
let on_false = Var::witness(ctx, |_| F::from(9u64));
let selected = bit.select(&on_true, &on_false);
let expected = if b { F::from(7u64) } else { F::from(9u64) };
selected.assert_eq(&Var::constant(ctx, expected));
Vec::new()
});
assert!(valued.is_satisfied());
}
}
#[test]
fn test_bool_combinators_truth_tables() {
for a in [false, true] {
for b in [false, true] {
let (valued, _) = build_with_values::<F>(move |ctx| {
let a_var = BoolVar::witness(ctx, move |_| a);
let b_var = BoolVar::witness(ctx, move |_| b);
(!a_var.clone()).assert_eq(&BoolVar::constant(!a));
(a_var.clone() & b_var.clone()).assert_eq(&BoolVar::constant(a & b));
(a_var | b_var).assert_eq(&BoolVar::constant(a | b));
Vec::new()
});
assert!(valued.is_satisfied());
}
}
}
#[test]
fn test_bool_constant() {
let (valued, _) = build_with_values(|ctx| {
let t = BoolVar::<F>::constant(true);
let f = BoolVar::<F>::constant(false);
let x = Var::witness(ctx, |_| F::from(5u64));
t.select(&x, &Var::zero())
.assert_eq(&Var::constant(ctx, F::from(5u64)));
f.select(&x, &Var::zero()).assert_eq(&Var::zero());
Vec::new()
});
assert!(valued.is_satisfied());
}
}