use ff::{PrimeField, PrimeFieldBits};
use crate::frontend::{ConstraintSystem, LinearCombination, SynthesisError, Variable};
#[derive(Debug, Clone)]
pub struct AllocatedBit {
variable: Variable,
value: Option<bool>,
}
impl AllocatedBit {
pub fn get_value(&self) -> Option<bool> {
self.value
}
pub fn get_variable(&self) -> Variable {
self.variable
}
pub fn from_parts_unchecked(variable: Variable, value: Option<bool>) -> Self {
AllocatedBit { variable, value }
}
pub fn alloc_conditionally<Scalar, CS>(
mut cs: CS,
value: Option<bool>,
must_be_false: &AllocatedBit,
) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let var = cs.alloc(
|| "boolean",
|| {
if value.ok_or(SynthesisError::AssignmentMissing)? {
Ok(Scalar::ONE)
} else {
Ok(Scalar::ZERO)
}
},
)?;
cs.enforce(
|| "boolean constraint",
|lc| lc + CS::one() - must_be_false.variable - var,
|lc| lc + var,
|lc| lc,
);
Ok(AllocatedBit {
variable: var,
value,
})
}
pub fn alloc<Scalar, CS>(mut cs: CS, value: Option<bool>) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let var = cs.alloc(
|| "boolean",
|| {
if value.ok_or(SynthesisError::AssignmentMissing)? {
Ok(Scalar::ONE)
} else {
Ok(Scalar::ZERO)
}
},
)?;
cs.enforce(
|| "boolean constraint",
|lc| lc + CS::one() - var,
|lc| lc + var,
|lc| lc,
);
Ok(AllocatedBit {
variable: var,
value,
})
}
pub fn xor<Scalar, CS>(mut cs: CS, a: &Self, b: &Self) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let mut result_value = None;
let result_var = cs.alloc(
|| "xor result",
|| {
if a.value.ok_or(SynthesisError::AssignmentMissing)?
^ b.value.ok_or(SynthesisError::AssignmentMissing)?
{
result_value = Some(true);
Ok(Scalar::ONE)
} else {
result_value = Some(false);
Ok(Scalar::ZERO)
}
},
)?;
cs.enforce(
|| "xor constraint",
|lc| lc + a.variable + a.variable,
|lc| lc + b.variable,
|lc| lc + a.variable + b.variable - result_var,
);
Ok(AllocatedBit {
variable: result_var,
value: result_value,
})
}
pub fn and<Scalar, CS>(mut cs: CS, a: &Self, b: &Self) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let mut result_value = None;
let result_var = cs.alloc(
|| "and result",
|| {
if a.value.ok_or(SynthesisError::AssignmentMissing)?
& b.value.ok_or(SynthesisError::AssignmentMissing)?
{
result_value = Some(true);
Ok(Scalar::ONE)
} else {
result_value = Some(false);
Ok(Scalar::ZERO)
}
},
)?;
cs.enforce(
|| "and constraint",
|lc| lc + a.variable,
|lc| lc + b.variable,
|lc| lc + result_var,
);
Ok(AllocatedBit {
variable: result_var,
value: result_value,
})
}
pub fn and_not<Scalar, CS>(mut cs: CS, a: &Self, b: &Self) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let mut result_value = None;
let result_var = cs.alloc(
|| "and not result",
|| {
if a.value.ok_or(SynthesisError::AssignmentMissing)?
& !b.value.ok_or(SynthesisError::AssignmentMissing)?
{
result_value = Some(true);
Ok(Scalar::ONE)
} else {
result_value = Some(false);
Ok(Scalar::ZERO)
}
},
)?;
cs.enforce(
|| "and not constraint",
|lc| lc + a.variable,
|lc| lc + CS::one() - b.variable,
|lc| lc + result_var,
);
Ok(AllocatedBit {
variable: result_var,
value: result_value,
})
}
pub fn nor<Scalar, CS>(mut cs: CS, a: &Self, b: &Self) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let mut result_value = None;
let result_var = cs.alloc(
|| "nor result",
|| {
if !a.value.ok_or(SynthesisError::AssignmentMissing)?
& !b.value.ok_or(SynthesisError::AssignmentMissing)?
{
result_value = Some(true);
Ok(Scalar::ONE)
} else {
result_value = Some(false);
Ok(Scalar::ZERO)
}
},
)?;
cs.enforce(
|| "nor constraint",
|lc| lc + CS::one() - a.variable,
|lc| lc + CS::one() - b.variable,
|lc| lc + result_var,
);
Ok(AllocatedBit {
variable: result_var,
value: result_value,
})
}
}
pub fn field_into_allocated_bits_le<Scalar, CS>(
mut cs: CS,
value: Option<Scalar>,
) -> Result<Vec<AllocatedBit>, SynthesisError>
where
Scalar: PrimeField,
Scalar: PrimeFieldBits,
CS: ConstraintSystem<Scalar>,
{
let values = match value {
Some(ref value) => {
let field_char = Scalar::char_le_bits();
let mut field_char = field_char.into_iter().rev();
let mut tmp = Vec::with_capacity(Scalar::NUM_BITS as usize);
let mut found_one = false;
for b in value.to_le_bits().into_iter().rev() {
found_one |= field_char.next().unwrap();
if !found_one {
continue;
}
tmp.push(Some(b));
}
assert_eq!(tmp.len(), Scalar::NUM_BITS as usize);
tmp
}
None => vec![None; Scalar::NUM_BITS as usize],
};
let bits = values
.into_iter()
.rev()
.enumerate()
.map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("bit {i}")), b))
.collect::<Result<Vec<_>, SynthesisError>>()?;
Ok(bits)
}
#[derive(Clone, Debug)]
pub enum Boolean {
Is(AllocatedBit),
Not(AllocatedBit),
Constant(bool),
}
impl Boolean {
pub fn is_constant(&self) -> bool {
matches!(*self, Boolean::Constant(_))
}
pub fn enforce_equal<Scalar, CS>(mut cs: CS, a: &Self, b: &Self) -> Result<(), SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
match (a, b) {
(&Boolean::Constant(a), &Boolean::Constant(b)) => {
if a == b {
Ok(())
} else {
Err(SynthesisError::Unsatisfiable(
"Booleans are not equal".to_string(),
))
}
}
(&Boolean::Constant(true), a) | (a, &Boolean::Constant(true)) => {
cs.enforce(
|| "enforce equal to one",
|lc| lc,
|lc| lc,
|lc| lc + CS::one() - &a.lc(CS::one(), Scalar::ONE),
);
Ok(())
}
(&Boolean::Constant(false), a) | (a, &Boolean::Constant(false)) => {
cs.enforce(
|| "enforce equal to zero",
|lc| lc,
|lc| lc,
|_| a.lc(CS::one(), Scalar::ONE),
);
Ok(())
}
(a, b) => {
cs.enforce(
|| "enforce equal",
|lc| lc,
|lc| lc,
|_| a.lc(CS::one(), Scalar::ONE) - &b.lc(CS::one(), Scalar::ONE),
);
Ok(())
}
}
}
pub fn get_value(&self) -> Option<bool> {
match *self {
Boolean::Constant(c) => Some(c),
Boolean::Is(ref v) => v.get_value(),
Boolean::Not(ref v) => v.get_value().map(|b| !b),
}
}
pub fn lc<Scalar: PrimeField>(&self, one: Variable, coeff: Scalar) -> LinearCombination<Scalar> {
match *self {
Boolean::Constant(c) => {
if c {
LinearCombination::<Scalar>::zero() + (coeff, one)
} else {
LinearCombination::<Scalar>::zero()
}
}
Boolean::Is(ref v) => LinearCombination::<Scalar>::zero() + (coeff, v.get_variable()),
Boolean::Not(ref v) => {
LinearCombination::<Scalar>::zero() + (coeff, one) - (coeff, v.get_variable())
}
}
}
pub fn constant(b: bool) -> Self {
Boolean::Constant(b)
}
pub fn not(&self) -> Self {
match *self {
Boolean::Constant(c) => Boolean::Constant(!c),
Boolean::Is(ref v) => Boolean::Not(v.clone()),
Boolean::Not(ref v) => Boolean::Is(v.clone()),
}
}
pub fn xor<'a, Scalar, CS>(cs: CS, a: &'a Self, b: &'a Self) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
match (a, b) {
(&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(x.clone()),
(&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.not()),
(is @ &Boolean::Is(_), not @ &Boolean::Not(_))
| (not @ &Boolean::Not(_), is @ &Boolean::Is(_)) => {
Ok(Boolean::xor(cs, is, ¬.not())?.not())
}
(&Boolean::Is(ref a), &Boolean::Is(ref b)) | (&Boolean::Not(ref a), &Boolean::Not(ref b)) => {
Ok(Boolean::Is(AllocatedBit::xor(cs, a, b)?))
}
}
}
pub fn and<'a, Scalar, CS>(cs: CS, a: &'a Self, b: &'a Self) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
match (a, b) {
(&Boolean::Constant(false), _) | (_, &Boolean::Constant(false)) => {
Ok(Boolean::Constant(false))
}
(&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.clone()),
(&Boolean::Is(ref is), &Boolean::Not(ref not))
| (&Boolean::Not(ref not), &Boolean::Is(ref is)) => {
Ok(Boolean::Is(AllocatedBit::and_not(cs, is, not)?))
}
(Boolean::Not(a), Boolean::Not(b)) => Ok(Boolean::Is(AllocatedBit::nor(cs, a, b)?)),
(Boolean::Is(a), Boolean::Is(b)) => Ok(Boolean::Is(AllocatedBit::and(cs, a, b)?)),
}
}
pub fn or<'a, Scalar, CS>(
mut cs: CS,
a: &'a Boolean,
b: &'a Boolean,
) -> Result<Boolean, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
Ok(Boolean::not(&Boolean::and(
cs.namespace(|| "not and (not a) (not b)"),
&Boolean::not(a),
&Boolean::not(b),
)?))
}
pub fn sha256_ch<'a, Scalar, CS>(
mut cs: CS,
a: &'a Self,
b: &'a Self,
c: &'a Self,
) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let ch_value = match (a.get_value(), b.get_value(), c.get_value()) {
(Some(a), Some(b), Some(c)) => {
Some((a & b) ^ ((!a) & c))
}
_ => None,
};
match (a, b, c) {
(&Boolean::Constant(_), &Boolean::Constant(_), &Boolean::Constant(_)) => {
return Ok(Boolean::Constant(ch_value.expect("they're all constants")));
}
(&Boolean::Constant(false), _, c) => {
return Ok(c.clone());
}
(a, &Boolean::Constant(false), c) => {
return Boolean::and(cs, &a.not(), c);
}
(a, b, &Boolean::Constant(false)) => {
return Boolean::and(cs, a, b);
}
(a, b, &Boolean::Constant(true)) => {
return Ok(Boolean::and(cs, a, &b.not())?.not());
}
(a, &Boolean::Constant(true), c) => {
return Ok(Boolean::and(cs, &a.not(), &c.not())?.not());
}
(&Boolean::Constant(true), _, _) => {
}
(
&Boolean::Is(_) | &Boolean::Not(_),
&Boolean::Is(_) | &Boolean::Not(_),
&Boolean::Is(_) | &Boolean::Not(_),
) => {}
}
let ch = cs.alloc(
|| "ch",
|| {
ch_value.ok_or(SynthesisError::AssignmentMissing).map(|v| {
if v {
Scalar::ONE
} else {
Scalar::ZERO
}
})
},
)?;
cs.enforce(
|| "ch computation",
|_| b.lc(CS::one(), Scalar::ONE) - &c.lc(CS::one(), Scalar::ONE),
|_| a.lc(CS::one(), Scalar::ONE),
|lc| lc + ch - &c.lc(CS::one(), Scalar::ONE),
);
Ok(
AllocatedBit {
value: ch_value,
variable: ch,
}
.into(),
)
}
pub fn sha256_maj<'a, Scalar, CS>(
mut cs: CS,
a: &'a Self,
b: &'a Self,
c: &'a Self,
) -> Result<Self, SynthesisError>
where
Scalar: PrimeField,
CS: ConstraintSystem<Scalar>,
{
let maj_value = match (a.get_value(), b.get_value(), c.get_value()) {
(Some(a), Some(b), Some(c)) => {
Some((a & b) ^ (a & c) ^ (b & c))
}
_ => None,
};
match (a, b, c) {
(&Boolean::Constant(_), &Boolean::Constant(_), &Boolean::Constant(_)) => {
return Ok(Boolean::Constant(maj_value.expect("they're all constants")));
}
(&Boolean::Constant(false), b, c) => {
return Boolean::and(cs, b, c);
}
(a, &Boolean::Constant(false), c) => {
return Boolean::and(cs, a, c);
}
(a, b, &Boolean::Constant(false)) => {
return Boolean::and(cs, a, b);
}
(a, b, &Boolean::Constant(true)) => {
return Ok(Boolean::and(cs, &a.not(), &b.not())?.not());
}
(a, &Boolean::Constant(true), c) => {
return Ok(Boolean::and(cs, &a.not(), &c.not())?.not());
}
(&Boolean::Constant(true), b, c) => {
return Ok(Boolean::and(cs, &b.not(), &c.not())?.not());
}
(
&Boolean::Is(_) | &Boolean::Not(_),
&Boolean::Is(_) | &Boolean::Not(_),
&Boolean::Is(_) | &Boolean::Not(_),
) => {}
}
let maj = cs.alloc(
|| "maj",
|| {
maj_value.ok_or(SynthesisError::AssignmentMissing).map(|v| {
if v {
Scalar::ONE
} else {
Scalar::ZERO
}
})
},
)?;
let bc = Self::and(cs.namespace(|| "b and c"), b, c)?;
cs.enforce(
|| "maj computation",
|_| {
bc.lc(CS::one(), Scalar::ONE) + &bc.lc(CS::one(), Scalar::ONE)
- &b.lc(CS::one(), Scalar::ONE)
- &c.lc(CS::one(), Scalar::ONE)
},
|_| a.lc(CS::one(), Scalar::ONE),
|_| bc.lc(CS::one(), Scalar::ONE) - maj,
);
Ok(
AllocatedBit {
value: maj_value,
variable: maj,
}
.into(),
)
}
}
impl From<AllocatedBit> for Boolean {
fn from(b: AllocatedBit) -> Boolean {
Boolean::Is(b)
}
}