mirror of
https://github.com/Qortal/pirate-librustzcash.git
synced 2025-08-01 12:51:30 +00:00
Move generic circuit gadgets into bellman
This commit is contained in:
625
bellman/src/gadgets/num.rs
Normal file
625
bellman/src/gadgets/num.rs
Normal file
@@ -0,0 +1,625 @@
|
||||
use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr};
|
||||
use pairing::Engine;
|
||||
|
||||
use crate::{
|
||||
SynthesisError,
|
||||
ConstraintSystem,
|
||||
LinearCombination,
|
||||
Variable
|
||||
};
|
||||
|
||||
use super::{
|
||||
Assignment
|
||||
};
|
||||
|
||||
use super::boolean::{
|
||||
self,
|
||||
Boolean,
|
||||
AllocatedBit
|
||||
};
|
||||
|
||||
pub struct AllocatedNum<E: Engine> {
|
||||
value: Option<E::Fr>,
|
||||
variable: Variable
|
||||
}
|
||||
|
||||
impl<E: Engine> Clone for AllocatedNum<E> {
|
||||
fn clone(&self) -> Self {
|
||||
AllocatedNum {
|
||||
value: self.value,
|
||||
variable: self.variable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> AllocatedNum<E> {
|
||||
pub fn alloc<CS, F>(
|
||||
mut cs: CS,
|
||||
value: F,
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E>,
|
||||
F: FnOnce() -> Result<E::Fr, SynthesisError>
|
||||
{
|
||||
let mut new_value = None;
|
||||
let var = cs.alloc(|| "num", || {
|
||||
let tmp = value()?;
|
||||
|
||||
new_value = Some(tmp);
|
||||
|
||||
Ok(tmp)
|
||||
})?;
|
||||
|
||||
Ok(AllocatedNum {
|
||||
value: new_value,
|
||||
variable: var
|
||||
})
|
||||
}
|
||||
|
||||
pub fn inputize<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<(), SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let input = cs.alloc_input(
|
||||
|| "input variable",
|
||||
|| {
|
||||
Ok(*self.value.get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "enforce input is correct",
|
||||
|lc| lc + input,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + self.variable
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deconstructs this allocated number into its
|
||||
/// boolean representation in little-endian bit
|
||||
/// order, requiring that the representation
|
||||
/// strictly exists "in the field" (i.e., a
|
||||
/// congruency is not allowed.)
|
||||
pub fn into_bits_le_strict<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Vec<Boolean>, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
pub fn kary_and<E, CS>(
|
||||
mut cs: CS,
|
||||
v: &[AllocatedBit]
|
||||
) -> Result<AllocatedBit, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E>
|
||||
{
|
||||
assert!(v.len() > 0);
|
||||
|
||||
// Let's keep this simple for now and just AND them all
|
||||
// manually
|
||||
let mut cur = None;
|
||||
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if cur.is_none() {
|
||||
cur = Some(v.clone());
|
||||
} else {
|
||||
cur = Some(AllocatedBit::and(
|
||||
cs.namespace(|| format!("and {}", i)),
|
||||
cur.as_ref().unwrap(),
|
||||
v
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cur.expect("v.len() > 0"))
|
||||
}
|
||||
|
||||
// We want to ensure that the bit representation of a is
|
||||
// less than or equal to r - 1.
|
||||
let mut a = self.value.map(|e| BitIterator::new(e.into_repr()));
|
||||
let mut b = E::Fr::char();
|
||||
b.sub_noborrow(&1.into());
|
||||
|
||||
let mut result = vec![];
|
||||
|
||||
// Runs of ones in r
|
||||
let mut last_run = None;
|
||||
let mut current_run = vec![];
|
||||
|
||||
let mut found_one = false;
|
||||
let mut i = 0;
|
||||
for b in BitIterator::new(b) {
|
||||
let a_bit = a.as_mut().map(|e| e.next().unwrap());
|
||||
|
||||
// Skip over unset bits at the beginning
|
||||
found_one |= b;
|
||||
if !found_one {
|
||||
// a_bit should also be false
|
||||
a_bit.map(|e| assert!(!e));
|
||||
continue;
|
||||
}
|
||||
|
||||
if b {
|
||||
// This is part of a run of ones. Let's just
|
||||
// allocate the boolean with the expected value.
|
||||
let a_bit = AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
a_bit
|
||||
)?;
|
||||
// ... and add it to the current run of ones.
|
||||
current_run.push(a_bit.clone());
|
||||
result.push(a_bit);
|
||||
} else {
|
||||
if current_run.len() > 0 {
|
||||
// This is the start of a run of zeros, but we need
|
||||
// to k-ary AND against `last_run` first.
|
||||
|
||||
if last_run.is_some() {
|
||||
current_run.push(last_run.clone().unwrap());
|
||||
}
|
||||
last_run = Some(kary_and(
|
||||
cs.namespace(|| format!("run ending at {}", i)),
|
||||
¤t_run
|
||||
)?);
|
||||
current_run.truncate(0);
|
||||
}
|
||||
|
||||
// If `last_run` is true, `a` must be false, or it would
|
||||
// not be in the field.
|
||||
//
|
||||
// If `last_run` is false, `a` can be true or false.
|
||||
|
||||
let a_bit = AllocatedBit::alloc_conditionally(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
a_bit,
|
||||
&last_run.as_ref().expect("char always starts with a one")
|
||||
)?;
|
||||
result.push(a_bit);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// char is prime, so we'll always end on
|
||||
// a run of zeros.
|
||||
assert_eq!(current_run.len(), 0);
|
||||
|
||||
// Now, we have `result` in big-endian order.
|
||||
// However, now we have to unpack self!
|
||||
|
||||
let mut lc = LinearCombination::zero();
|
||||
let mut coeff = E::Fr::one();
|
||||
|
||||
for bit in result.iter().rev() {
|
||||
lc = lc + (coeff, bit.get_variable());
|
||||
|
||||
coeff.double();
|
||||
}
|
||||
|
||||
lc = lc - self.variable;
|
||||
|
||||
cs.enforce(
|
||||
|| "unpacking constraint",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|_| lc
|
||||
);
|
||||
|
||||
// Convert into booleans, and reverse for little-endian bit order
|
||||
Ok(result.into_iter().map(|b| Boolean::from(b)).rev().collect())
|
||||
}
|
||||
|
||||
/// Convert the allocated number into its little-endian representation.
|
||||
/// Note that this does not strongly enforce that the commitment is
|
||||
/// "in the field."
|
||||
pub fn into_bits_le<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Vec<Boolean>, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let bits = boolean::field_into_allocated_bits_le(
|
||||
&mut cs,
|
||||
self.value
|
||||
)?;
|
||||
|
||||
let mut lc = LinearCombination::zero();
|
||||
let mut coeff = E::Fr::one();
|
||||
|
||||
for bit in bits.iter() {
|
||||
lc = lc + (coeff, bit.get_variable());
|
||||
|
||||
coeff.double();
|
||||
}
|
||||
|
||||
lc = lc - self.variable;
|
||||
|
||||
cs.enforce(
|
||||
|| "unpacking constraint",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|_| lc
|
||||
);
|
||||
|
||||
Ok(bits.into_iter().map(|b| Boolean::from(b)).collect())
|
||||
}
|
||||
|
||||
pub fn mul<CS>(
|
||||
&self,
|
||||
mut cs: CS,
|
||||
other: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let mut value = None;
|
||||
|
||||
let var = cs.alloc(|| "product num", || {
|
||||
let mut tmp = *self.value.get()?;
|
||||
tmp.mul_assign(other.value.get()?);
|
||||
|
||||
value = Some(tmp);
|
||||
|
||||
Ok(tmp)
|
||||
})?;
|
||||
|
||||
// Constrain: a * b = ab
|
||||
cs.enforce(
|
||||
|| "multiplication constraint",
|
||||
|lc| lc + self.variable,
|
||||
|lc| lc + other.variable,
|
||||
|lc| lc + var
|
||||
);
|
||||
|
||||
Ok(AllocatedNum {
|
||||
value: value,
|
||||
variable: var
|
||||
})
|
||||
}
|
||||
|
||||
pub fn square<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let mut value = None;
|
||||
|
||||
let var = cs.alloc(|| "squared num", || {
|
||||
let mut tmp = *self.value.get()?;
|
||||
tmp.square();
|
||||
|
||||
value = Some(tmp);
|
||||
|
||||
Ok(tmp)
|
||||
})?;
|
||||
|
||||
// Constrain: a * a = aa
|
||||
cs.enforce(
|
||||
|| "squaring constraint",
|
||||
|lc| lc + self.variable,
|
||||
|lc| lc + self.variable,
|
||||
|lc| lc + var
|
||||
);
|
||||
|
||||
Ok(AllocatedNum {
|
||||
value: value,
|
||||
variable: var
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assert_nonzero<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<(), SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let inv = cs.alloc(|| "ephemeral inverse", || {
|
||||
let tmp = *self.value.get()?;
|
||||
|
||||
if tmp.is_zero() {
|
||||
Err(SynthesisError::DivisionByZero)
|
||||
} else {
|
||||
Ok(tmp.inverse().unwrap())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain a * inv = 1, which is only valid
|
||||
// iff a has a multiplicative inverse, untrue
|
||||
// for zero.
|
||||
cs.enforce(
|
||||
|| "nonzero assertion constraint",
|
||||
|lc| lc + self.variable,
|
||||
|lc| lc + inv,
|
||||
|lc| lc + CS::one()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes two allocated numbers (a, b) and returns
|
||||
/// (b, a) if the condition is true, and (a, b)
|
||||
/// otherwise.
|
||||
pub fn conditionally_reverse<CS>(
|
||||
mut cs: CS,
|
||||
a: &Self,
|
||||
b: &Self,
|
||||
condition: &Boolean
|
||||
) -> Result<(Self, Self), SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let c = Self::alloc(
|
||||
cs.namespace(|| "conditional reversal result 1"),
|
||||
|| {
|
||||
if *condition.get_value().get()? {
|
||||
Ok(*b.value.get()?)
|
||||
} else {
|
||||
Ok(*a.value.get()?)
|
||||
}
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "first conditional reversal",
|
||||
|lc| lc + a.variable - b.variable,
|
||||
|_| condition.lc(CS::one(), E::Fr::one()),
|
||||
|lc| lc + a.variable - c.variable
|
||||
);
|
||||
|
||||
let d = Self::alloc(
|
||||
cs.namespace(|| "conditional reversal result 2"),
|
||||
|| {
|
||||
if *condition.get_value().get()? {
|
||||
Ok(*a.value.get()?)
|
||||
} else {
|
||||
Ok(*b.value.get()?)
|
||||
}
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "second conditional reversal",
|
||||
|lc| lc + b.variable - a.variable,
|
||||
|_| condition.lc(CS::one(), E::Fr::one()),
|
||||
|lc| lc + b.variable - d.variable
|
||||
);
|
||||
|
||||
Ok((c, d))
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Option<E::Fr> {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn get_variable(&self) -> Variable {
|
||||
self.variable
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Num<E: Engine> {
|
||||
value: Option<E::Fr>,
|
||||
lc: LinearCombination<E>
|
||||
}
|
||||
|
||||
impl<E: Engine> From<AllocatedNum<E>> for Num<E> {
|
||||
fn from(num: AllocatedNum<E>) -> Num<E> {
|
||||
Num {
|
||||
value: num.value,
|
||||
lc: LinearCombination::<E>::zero() + num.variable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Num<E> {
|
||||
pub fn zero() -> Self {
|
||||
Num {
|
||||
value: Some(E::Fr::zero()),
|
||||
lc: LinearCombination::zero()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Option<E::Fr> {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn lc(&self, coeff: E::Fr) -> LinearCombination<E> {
|
||||
LinearCombination::zero() + (coeff, &self.lc)
|
||||
}
|
||||
|
||||
pub fn add_bool_with_coeff(
|
||||
self,
|
||||
one: Variable,
|
||||
bit: &Boolean,
|
||||
coeff: E::Fr
|
||||
) -> Self
|
||||
{
|
||||
let newval = match (self.value, bit.get_value()) {
|
||||
(Some(mut curval), Some(bval)) => {
|
||||
if bval {
|
||||
curval.add_assign(&coeff);
|
||||
}
|
||||
|
||||
Some(curval)
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
Num {
|
||||
value: newval,
|
||||
lc: self.lc + &bit.lc(one, coeff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{ConstraintSystem};
|
||||
use ff::{BitIterator, Field, PrimeField};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use rand_core::SeedableRng;
|
||||
use rand_xorshift::XorShiftRng;
|
||||
|
||||
use crate::gadgets::test::*;
|
||||
use super::{AllocatedNum, Boolean};
|
||||
|
||||
#[test]
|
||||
fn test_allocated_num() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
AllocatedNum::alloc(&mut cs, || Ok(Fr::one())).unwrap();
|
||||
|
||||
assert!(cs.get("num") == Fr::one());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_squaring() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap();
|
||||
let n2 = n.square(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("squared num") == Fr::from_str("9").unwrap());
|
||||
assert!(n2.value.unwrap() == Fr::from_str("9").unwrap());
|
||||
cs.set("squared num", Fr::from_str("10").unwrap());
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_multiplication() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::from_str("12").unwrap())).unwrap();
|
||||
let n2 = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::from_str("10").unwrap())).unwrap();
|
||||
let n3 = n.mul(&mut cs, &n2).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("product num") == Fr::from_str("120").unwrap());
|
||||
assert!(n3.value.unwrap() == Fr::from_str("120").unwrap());
|
||||
cs.set("product num", Fr::from_str("121").unwrap());
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_conditional_reversal() {
|
||||
let mut rng = XorShiftRng::from_seed([
|
||||
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||
0xe5,
|
||||
]);
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::random(&mut rng))).unwrap();
|
||||
let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::random(&mut rng))).unwrap();
|
||||
let condition = Boolean::constant(false);
|
||||
let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
assert_eq!(a.value.unwrap(), c.value.unwrap());
|
||||
assert_eq!(b.value.unwrap(), d.value.unwrap());
|
||||
}
|
||||
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::random(&mut rng))).unwrap();
|
||||
let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::random(&mut rng))).unwrap();
|
||||
let condition = Boolean::constant(true);
|
||||
let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
assert_eq!(a.value.unwrap(), d.value.unwrap());
|
||||
assert_eq!(b.value.unwrap(), c.value.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_nonzero() {
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap();
|
||||
n.assert_nonzero(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
cs.set("ephemeral inverse", Fr::from_str("3").unwrap());
|
||||
assert!(cs.which_is_unsatisfied() == Some("nonzero assertion constraint"));
|
||||
}
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::zero())).unwrap();
|
||||
assert!(n.assert_nonzero(&mut cs).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_bits_strict() {
|
||||
let mut negone = Fr::one();
|
||||
negone.negate();
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap();
|
||||
n.into_bits_le_strict(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
// make the bit representation the characteristic
|
||||
cs.set("bit 254/boolean", Fr::one());
|
||||
|
||||
// this makes the conditional boolean constraint fail
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_bits() {
|
||||
let mut rng = XorShiftRng::from_seed([
|
||||
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||
0xe5,
|
||||
]);
|
||||
|
||||
for i in 0..200 {
|
||||
let r = Fr::random(&mut rng);
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap();
|
||||
|
||||
let bits = if i % 2 == 0 {
|
||||
n.into_bits_le(&mut cs).unwrap()
|
||||
} else {
|
||||
n.into_bits_le_strict(&mut cs).unwrap()
|
||||
};
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter().rev()) {
|
||||
if let &Boolean::Is(ref a) = a {
|
||||
assert_eq!(b, a.get_value().unwrap());
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
cs.set("num", Fr::random(&mut rng));
|
||||
assert!(!cs.is_satisfied());
|
||||
cs.set("num", r);
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
for i in 0..Fr::NUM_BITS {
|
||||
let name = format!("bit {}/boolean", i);
|
||||
let cur = cs.get(&name);
|
||||
let mut tmp = Fr::one();
|
||||
tmp.sub_assign(&cur);
|
||||
cs.set(&name, tmp);
|
||||
assert!(!cs.is_satisfied());
|
||||
cs.set(&name, cur);
|
||||
assert!(cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user