Files
pirate-librustzcash/src/bit.rs

561 lines
16 KiB
Rust

use tinysnark::FieldT;
use std::rc::Rc;
use std::cell::RefCell;
use super::variable::*;
use self::Bit::*;
use self::Op::*;
use super::circuit::*;
macro_rules! mirror {
($a:pat, $b:pat) => (($a, $b) | ($b, $a))
}
macro_rules! mirror_match {
(@as_expr $e:expr) => {$e};
(@parse
$e:expr, ($($arms:tt)*);
$(,)*
) => {
mirror_match!(@as_expr match $e { $($arms)* })
};
(@parse
$e:expr, $arms:tt;
, $($tail:tt)*
) => {
mirror_match!(@parse $e, $arms; $($tail)*)
};
(@parse
$e:expr, ($($arms:tt)*);
mirror!($a:pat, $b:pat) => $body:expr,
$($tail:tt)*
) => {
mirror_match!(
@parse
$e,
(
$($arms)*
($a, $b) | ($b, $a) => $body,
);
$($tail)*
)
};
(@parse
$e:expr, ($($arms:tt)*);
$pat:pat => $body:expr,
$($tail:tt)*
) => {
mirror_match!(
@parse
$e,
(
$($arms)*
$pat => $body,
);
$($tail)*
)
};
(@parse
$e:expr, ($($arms:tt)*);
$pat:pat => $body:expr,
$($tail:tt)*
) => {
mirror_match!(
@parse
$e,
(
$($arms)*
$pat => $body,
);
$($tail)*
)
};
(($e:expr) { $($arms:tt)* }) => {
mirror_match!(@parse $e, (); $($arms)*)
};
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum Op {
And,
Nand,
Xor,
Xnor,
MaterialNonimplication,
MaterialImplication,
Nor,
Or
}
impl Op {
fn not(&self) -> Op {
match *self {
And => Nand,
Nand => And,
Xor => Xnor,
Xnor => Xor,
Nor => Or,
Or => Nor,
MaterialNonimplication => MaterialImplication,
MaterialImplication => MaterialNonimplication
}
}
fn val(&self, a: FieldT, b: FieldT) -> FieldT {
let a = a == FieldT::one();
let b = b == FieldT::one();
let res = match *self {
And => a && b,
Nand => !(a && b),
Xor => a != b,
Xnor => a == b,
Or => a || b,
Nor => !(a || b),
MaterialNonimplication => a && (!b),
MaterialImplication => !(a && (!b))
};
if res {
FieldT::one()
} else {
FieldT::zero()
}
}
}
#[derive(Clone)]
pub struct BinaryOp {
a: Var,
b: Var,
op: Op,
resolved: Rc<RefCell<Option<Var>>>
}
impl BinaryOp {
fn new(a: Var, b: Var, op: Op) -> BinaryOp {
BinaryOp {
a: a,
b: b,
op: op,
resolved: Rc::new(RefCell::new(None))
}
}
fn walk(&self, counter: &mut usize, constraints: &mut Vec<Constraint>, witness_map: &mut WitnessMap)
{
self.a.walk(counter, constraints, witness_map);
self.b.walk(counter, constraints, witness_map);
}
fn val(&self, map: &[FieldT], inverted: bool) -> FieldT {
let v = self.op.val(self.a.val(map), self.b.val(map));
if inverted {
if v == FieldT::one() {
FieldT::zero()
} else {
FieldT::one()
}
} else {
v
}
}
fn resolve(&self, inverted: bool) -> Bit {
let res = { self.resolved.borrow_mut().clone() };
match res {
Some(v) => {
if inverted {
Not(v)
} else {
Is(v)
}
},
None => {
let v = resolve(&self.a, &self.b, self.op);
*self.resolved.borrow_mut() = Some(v.clone());
if inverted {
Not(v)
} else {
Is(v)
}
}
}
}
}
#[derive(Clone)]
pub enum Bit {
Constant(bool),
Is(Var),
Not(Var),
Bin(BinaryOp, bool)
}
struct BitEquality {
a: Bit,
b: Var
}
impl Constrainable for BitEquality {
type Result = Var;
fn synthesize(&self, enforce: &Bit) -> Var {
// TODO: currently only support unconditional enforcement
match enforce {
&Bit::Constant(true) => {},
_ => unimplemented!()
}
match self.a {
Bin(ref binop, inverted) => {
// TODO: figure this out later
assert!(binop.resolved.borrow().is_none());
let mut op = binop.op;
if inverted {
op = op.not();
}
gadget(&[&binop.a, &binop.b, &self.b], 0, move |vals| {
let a = vals.get_input(0);
let b = vals.get_input(1);
unsafe { vals.set_input(2, op.val(a, b)) };
}, |i, o, cs| {
cs.push(binaryop_constraint(i[0], i[1], i[2], op));
vec![i[2]]
}).remove(0)
},
_ => unimplemented!()
}
}
}
impl Equals<Var> for Bit {
type Result = BitEquality;
fn must_equal(&self, other: &Var) -> BitEquality {
BitEquality {
a: self.clone(),
b: other.clone()
}
}
}
fn binaryop_constraint(a: &Var, b: &Var, c: &Var, op: Op) -> Constraint {
match op {
// a * b = c
And => Constraint(vec![(FieldT::one(), a.clone())],
vec![(FieldT::one(), b.clone())],
vec![(FieldT::one(), c.clone())]
),
// a * b = 1 - c
Nand => Constraint(vec![(FieldT::one(), a.clone())],
vec![(FieldT::one(), b.clone())],
vec![(FieldT::one(), Var::one()),
(-FieldT::one(), c.clone())
]
),
// 2a * b = a + b - c
Xor => Constraint(vec![(FieldT::from(2), a.clone())],
vec![(FieldT::one(), b.clone())],
vec![(FieldT::one(), a.clone()),
(FieldT::one(), b.clone()),
(-FieldT::one(), c.clone())
]
),
// 2a * b = a + b + c - 1
Xnor => Constraint(vec![(FieldT::from(2), a.clone())],
vec![(FieldT::one(), b.clone())],
vec![
(FieldT::one(), a.clone()),
(FieldT::one(), b.clone()),
(FieldT::one(), c.clone()),
(-FieldT::one(), Var::one())
]
),
// a * (1 - b) = c
MaterialNonimplication => Constraint(vec![(FieldT::one(), a.clone())],
vec![(FieldT::one(), Var::one()),
(-FieldT::one(), b.clone())
],
vec![(FieldT::one(), c.clone())]
),
// a * b = a + c - 1
MaterialImplication => Constraint(vec![(FieldT::one(), a.clone())],
vec![(FieldT::one(), b.clone())],
vec![(FieldT::one(), a.clone()),
(FieldT::one(), c.clone()),
(-FieldT::one(), Var::one())
]
),
// (1 - a) * (1 - b) = c
Nor => Constraint(vec![(FieldT::one(), Var::one()),
(-FieldT::one(), a.clone())
],
vec![(FieldT::one(), Var::one()),
(-FieldT::one(), b.clone())
],
vec![(FieldT::one(), c.clone())]
),
// a * b = a + b - c
Or => Constraint(vec![(FieldT::one(), a.clone())],
vec![(FieldT::one(), b.clone())],
vec![(FieldT::one(), a.clone()),
(FieldT::one(), b.clone()),
(-FieldT::one(), c.clone())
]
)
}
}
fn resolve(a: &Var, b: &Var, op: Op) -> Var {
gadget(&[a, b], 1, move |vals| {
let a = vals.get_input(0);
let b = vals.get_input(1);
vals.set_output(0, op.val(a, b));
}, |i, o, cs| {
cs.push(binaryop_constraint(i[0], i[1], o[0], op));
vec![o[0]]
}).remove(0)
}
impl ConstraintWalker for Bit {
fn walk(&self, counter: &mut usize, constraints: &mut Vec<Constraint>, witness_map: &mut WitnessMap)
{
match *self {
Constant(_) => {},
Not(ref v) => {
v.walk(counter, constraints, witness_map);
},
Is(ref v) => {
v.walk(counter, constraints, witness_map);
},
Bin(ref bin, _) => {
bin.walk(counter, constraints, witness_map);
}
}
}
}
impl Bit {
pub fn val(&self, map: &[FieldT]) -> bool {
match *self {
Constant(c) => c,
Not(ref v) => v.val(map) == FieldT::zero(),
Is(ref v) => v.val(map) == FieldT::one(),
Bin(ref bin, inverted) => bin.val(map, inverted) == FieldT::one()
}
}
// probably could remove this
pub fn resolve(&self) -> Bit {
match *self {
Bin(ref bin, inverted) => bin.resolve(inverted),
_ => self.clone()
}
}
pub fn new(v: &Var) -> Bit {
Is(gadget(&[v], 0, |_| {}, |i, o, cs| {
// boolean constraint:
// (1 - a) * a = 0
cs.push(Constraint(vec![(FieldT::one(), Var::one()),
(-FieldT::one(), i[0].clone())],
vec![(FieldT::one(), i[0].clone())],
vec![(FieldT::zero(), Var::one())]
));
vec![i[0]]
}).remove(0))
}
pub fn constant(num: bool) -> Bit {
Constant(num)
}
// self xor other
pub fn xor(&self, other: &Bit) -> Bit {
mirror_match!(((self, other)) {
(&Constant(a), &Constant(b)) => {
Constant(a != b)
},
mirror!(&Is(ref v), &Constant(a)) => {
if a {
// Anything XOR 1 is the NOT of that thing.
Not(v.clone())
} else {
// Anything XOR 0 equals that thing.
Is(v.clone())
}
},
mirror!(&Not(ref v), &Constant(a)) => {
if a {
// Anything XOR 1 is the NOT of that thing.
Is(v.clone())
} else {
Not(v.clone())
}
},
mirror!(&Bin(ref bin, inverted), &Constant(c)) => {
if c {
// Anything XOR 1 is the NOT of that thing.
Bin(bin.clone(), !inverted)
} else {
Bin(bin.clone(), inverted)
}
},
mirror!(&Bin(ref bin, inverted), &Is(ref i)) => {
bin.resolve(inverted).xor(&Is(i.clone()))
},
(&Bin(ref bin1, inverted1), &Bin(ref bin2, inverted2)) => {
bin1.resolve(inverted1).xor(&bin2.resolve(inverted2))
},
mirror!(&Bin(ref bin, inverted), &Not(ref n)) => {
bin.resolve(inverted).xor(&Not(n.clone()))
},
(&Not(ref a), &Not(ref b)) => {
Bin(BinaryOp::new(a.clone(), b.clone(), Xor), false)
},
mirror!(&Is(ref i), &Not(ref n)) => {
Bin(BinaryOp::new(i.clone(), n.clone(), Xnor), false)
},
(&Is(ref a), &Is(ref b)) => {
Bin(BinaryOp::new(a.clone(), b.clone(), Xor), false)
},
})
}
pub fn and(&self, other: &Bit) -> Bit {
mirror_match!(((self, other)) {
(&Constant(a), &Constant(b)) => {
Constant(a && b)
},
mirror!(&Is(ref v), &Constant(a)) => {
if a {
// Anything AND 1 is the identity of that thing
Is(v.clone())
} else {
// Anything AND 0 is false
Constant(false)
}
},
mirror!(&Not(ref v), &Constant(a)) => {
if a {
// Anything AND 1 is the identity of that thing
Not(v.clone())
} else {
// Anything AND 0 is false
Constant(false)
}
},
mirror!(&Bin(ref bin, inverted), &Constant(c)) => {
if c {
// Anything AND 1 is the identity of that thing
Bin(bin.clone(), inverted)
} else {
// Anything AND 0 is false
Constant(false)
}
},
mirror!(&Bin(ref bin, inverted), &Is(ref i)) => {
bin.resolve(inverted).and(&Is(i.clone()))
},
(&Bin(ref bin1, inverted1), &Bin(ref bin2, inverted2)) => {
bin1.resolve(inverted1).and(&bin2.resolve(inverted2))
},
mirror!(&Bin(ref bin, inverted), &Not(ref n)) => {
bin.resolve(inverted).and(&Not(n.clone()))
},
(&Not(ref a), &Not(ref b)) => {
Bin(BinaryOp::new(a.clone(), b.clone(), Nor), false)
},
mirror!(&Is(ref i), &Not(ref n)) => {
Bin(BinaryOp::new(i.clone(), n.clone(), MaterialNonimplication), false)
},
(&Is(ref a), &Is(ref b)) => {
Bin(BinaryOp::new(a.clone(), b.clone(), And), false)
},
})
}
// (not self) and other
pub fn notand(&self, other: &Bit) -> Bit {
self.xor(&Constant(true)).and(other)
}
}
#[cfg(test)]
fn test_binary_op<F: Fn(&Bit, &Bit) -> Bit>(op: F, a_in: i64, b_in: i64, c_out: i64)
{
let a = Var::new(1);
let b = Var::new(2);
let a = Bit::new(&a);
let b = Bit::new(&b);
let mut counter = 3;
let mut witness_map = WitnessMap::new();
let mut constraints = vec![];
let c = op(&a, &b);
let c = c.resolve();
c.walk(&mut counter, &mut constraints, &mut witness_map);
assert_eq!(counter, 4);
assert_eq!(constraints.len(), 3);
assert_eq!(witness_map.len(), 2);
assert_eq!(witness_map[&1].len(), 2);
assert_eq!(witness_map[&2].len(), 1);
let mut f: Vec<FieldT> = (0..counter).map(|_| FieldT::zero()).collect();
f[0] = FieldT::one();
f[1] = FieldT::from(a_in);
f[2] = FieldT::from(b_in);
witness_field_elements(&mut f, &witness_map);
assert_eq!(f[3], FieldT::from(c_out));
}
#[test]
fn test_xor() {
use tinysnark;
tinysnark::init();
test_binary_op(Bit::xor, 0, 0, 0);
test_binary_op(Bit::xor, 0, 1, 1);
test_binary_op(Bit::xor, 1, 0, 1);
test_binary_op(Bit::xor, 1, 1, 0);
}
#[test]
fn test_and() {
use tinysnark;
tinysnark::init();
test_binary_op(Bit::and, 0, 0, 0);
test_binary_op(Bit::and, 0, 1, 0);
test_binary_op(Bit::and, 1, 0, 0);
test_binary_op(Bit::and, 1, 1, 1);
}