Implementation of fundamental circuitry and primitive Jubjub curve arithmetic.
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
use pairing::{
|
||||
Engine,
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
SynthesisError,
|
||||
ConstraintSystem
|
||||
};
|
||||
|
||||
use super::boolean::{
|
||||
Boolean
|
||||
};
|
||||
|
||||
use super::uint32::{
|
||||
UInt32
|
||||
};
|
||||
|
||||
/*
|
||||
2.1. Parameters
|
||||
The following table summarizes various parameters and their ranges:
|
||||
| BLAKE2b | BLAKE2s |
|
||||
--------------+------------------+------------------+
|
||||
Bits in word | w = 64 | w = 32 |
|
||||
Rounds in F | r = 12 | r = 10 |
|
||||
Block bytes | bb = 128 | bb = 64 |
|
||||
Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 |
|
||||
Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 |
|
||||
Input bytes | 0 <= ll < 2**128 | 0 <= ll < 2**64 |
|
||||
--------------+------------------+------------------+
|
||||
G Rotation | (R1, R2, R3, R4) | (R1, R2, R3, R4) |
|
||||
constants = | (32, 24, 16, 63) | (16, 12, 8, 7) |
|
||||
--------------+------------------+------------------+
|
||||
*/
|
||||
|
||||
const R1: usize = 16;
|
||||
const R2: usize = 12;
|
||||
const R3: usize = 8;
|
||||
const R4: usize = 7;
|
||||
|
||||
/*
|
||||
Round | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
||||
----------+-------------------------------------------------+
|
||||
SIGMA[0] | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
||||
SIGMA[1] | 14 10 4 8 9 15 13 6 1 12 0 2 11 7 5 3 |
|
||||
SIGMA[2] | 11 8 12 0 5 2 15 13 10 14 3 6 7 1 9 4 |
|
||||
SIGMA[3] | 7 9 3 1 13 12 11 14 2 6 5 10 4 0 15 8 |
|
||||
SIGMA[4] | 9 0 5 7 2 4 10 15 14 1 11 12 6 8 3 13 |
|
||||
SIGMA[5] | 2 12 6 10 0 11 8 3 4 13 7 5 15 14 1 9 |
|
||||
SIGMA[6] | 12 5 1 15 14 13 4 10 0 7 6 3 9 2 8 11 |
|
||||
SIGMA[7] | 13 11 7 14 12 1 3 9 5 0 15 4 8 6 2 10 |
|
||||
SIGMA[8] | 6 15 14 9 11 3 0 8 12 2 13 7 1 4 10 5 |
|
||||
SIGMA[9] | 10 2 8 4 7 6 1 5 15 11 9 14 3 12 13 0 |
|
||||
----------+-------------------------------------------------+
|
||||
*/
|
||||
|
||||
const SIGMA: [[usize; 16]; 10] = [
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
||||
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
||||
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
||||
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
||||
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
||||
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
||||
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
||||
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
||||
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]
|
||||
];
|
||||
|
||||
/*
|
||||
3.1. Mixing Function G
|
||||
The G primitive function mixes two input words, "x" and "y", into
|
||||
four words indexed by "a", "b", "c", and "d" in the working vector
|
||||
v[0..15]. The full modified vector is returned. The rotation
|
||||
constants (R1, R2, R3, R4) are given in Section 2.1.
|
||||
FUNCTION G( v[0..15], a, b, c, d, x, y )
|
||||
|
|
||||
| v[a] := (v[a] + v[b] + x) mod 2**w
|
||||
| v[d] := (v[d] ^ v[a]) >>> R1
|
||||
| v[c] := (v[c] + v[d]) mod 2**w
|
||||
| v[b] := (v[b] ^ v[c]) >>> R2
|
||||
| v[a] := (v[a] + v[b] + y) mod 2**w
|
||||
| v[d] := (v[d] ^ v[a]) >>> R3
|
||||
| v[c] := (v[c] + v[d]) mod 2**w
|
||||
| v[b] := (v[b] ^ v[c]) >>> R4
|
||||
|
|
||||
| RETURN v[0..15]
|
||||
|
|
||||
END FUNCTION.
|
||||
*/
|
||||
|
||||
fn mixing_g<E: Engine, CS: ConstraintSystem<E>>(
|
||||
mut cs: CS,
|
||||
v: &mut [UInt32<CS::Variable>],
|
||||
a: usize,
|
||||
b: usize,
|
||||
c: usize,
|
||||
d: usize,
|
||||
x: &UInt32<CS::Variable>,
|
||||
y: &UInt32<CS::Variable>
|
||||
) -> Result<(), SynthesisError>
|
||||
{
|
||||
v[a] = UInt32::addmany(cs.namespace(|| "mixing step 1"), &[v[a].clone(), v[b].clone(), x.clone()])?;
|
||||
v[d] = v[d].xor(cs.namespace(|| "mixing step 2"), &v[a])?.rotr(R1);
|
||||
v[c] = UInt32::addmany(cs.namespace(|| "mixing step 3"), &[v[c].clone(), v[d].clone()])?;
|
||||
v[b] = v[b].xor(cs.namespace(|| "mixing step 4"), &v[c])?.rotr(R2);
|
||||
v[a] = UInt32::addmany(cs.namespace(|| "mixing step 5"), &[v[a].clone(), v[b].clone(), y.clone()])?;
|
||||
v[d] = v[d].xor(cs.namespace(|| "mixing step 6"), &v[a])?.rotr(R3);
|
||||
v[c] = UInt32::addmany(cs.namespace(|| "mixing step 7"), &[v[c].clone(), v[d].clone()])?;
|
||||
v[b] = v[b].xor(cs.namespace(|| "mixing step 8"), &v[c])?.rotr(R4);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
3.2. Compression Function F
|
||||
Compression function F takes as an argument the state vector "h",
|
||||
message block vector "m" (last block is padded with zeros to full
|
||||
block size, if required), 2w-bit offset counter "t", and final block
|
||||
indicator flag "f". Local vector v[0..15] is used in processing. F
|
||||
returns a new state vector. The number of rounds, "r", is 12 for
|
||||
BLAKE2b and 10 for BLAKE2s. Rounds are numbered from 0 to r - 1.
|
||||
FUNCTION F( h[0..7], m[0..15], t, f )
|
||||
|
|
||||
| // Initialize local work vector v[0..15]
|
||||
| v[0..7] := h[0..7] // First half from state.
|
||||
| v[8..15] := IV[0..7] // Second half from IV.
|
||||
|
|
||||
| v[12] := v[12] ^ (t mod 2**w) // Low word of the offset.
|
||||
| v[13] := v[13] ^ (t >> w) // High word.
|
||||
|
|
||||
| IF f = TRUE THEN // last block flag?
|
||||
| | v[14] := v[14] ^ 0xFF..FF // Invert all bits.
|
||||
| END IF.
|
||||
|
|
||||
| // Cryptographic mixing
|
||||
| FOR i = 0 TO r - 1 DO // Ten or twelve rounds.
|
||||
| |
|
||||
| | // Message word selection permutation for this round.
|
||||
| | s[0..15] := SIGMA[i mod 10][0..15]
|
||||
| |
|
||||
| | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] )
|
||||
| | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] )
|
||||
| | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] )
|
||||
| | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] )
|
||||
| |
|
||||
| | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] )
|
||||
| | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] )
|
||||
| | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] )
|
||||
| | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] )
|
||||
| |
|
||||
| END FOR
|
||||
|
|
||||
| FOR i = 0 TO 7 DO // XOR the two halves.
|
||||
| | h[i] := h[i] ^ v[i] ^ v[i + 8]
|
||||
| END FOR.
|
||||
|
|
||||
| RETURN h[0..7] // New state.
|
||||
|
|
||||
END FUNCTION.
|
||||
*/
|
||||
|
||||
|
||||
fn blake2s_compression<E: Engine, CS: ConstraintSystem<E>>(
|
||||
mut cs: CS,
|
||||
h: &mut [UInt32<CS::Variable>],
|
||||
m: &[UInt32<CS::Variable>],
|
||||
t: u64,
|
||||
f: bool
|
||||
) -> Result<(), SynthesisError>
|
||||
{
|
||||
assert_eq!(h.len(), 8);
|
||||
assert_eq!(m.len(), 16);
|
||||
|
||||
/*
|
||||
static const uint32_t blake2s_iv[8] =
|
||||
{
|
||||
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
|
||||
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
|
||||
};
|
||||
*/
|
||||
|
||||
let mut v = Vec::with_capacity(16);
|
||||
v.extend_from_slice(h);
|
||||
v.push(UInt32::constant(0x6A09E667));
|
||||
v.push(UInt32::constant(0xBB67AE85));
|
||||
v.push(UInt32::constant(0x3C6EF372));
|
||||
v.push(UInt32::constant(0xA54FF53A));
|
||||
v.push(UInt32::constant(0x510E527F));
|
||||
v.push(UInt32::constant(0x9B05688C));
|
||||
v.push(UInt32::constant(0x1F83D9AB));
|
||||
v.push(UInt32::constant(0x5BE0CD19));
|
||||
|
||||
assert_eq!(v.len(), 16);
|
||||
|
||||
v[12] = v[12].xor(cs.namespace(|| "first xor"), &UInt32::constant(t as u32))?;
|
||||
v[13] = v[13].xor(cs.namespace(|| "second xor"), &UInt32::constant((t >> 32) as u32))?;
|
||||
|
||||
if f {
|
||||
v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?;
|
||||
}
|
||||
|
||||
for i in 0..10 {
|
||||
let mut cs = cs.namespace(|| format!("round {}", i));
|
||||
|
||||
let s = SIGMA[i % 10];
|
||||
|
||||
mixing_g(cs.namespace(|| "mixing invocation 1"), &mut v, 0, 4, 8, 12, &m[s[ 0]], &m[s[ 1]])?;
|
||||
mixing_g(cs.namespace(|| "mixing invocation 2"), &mut v, 1, 5, 9, 13, &m[s[ 2]], &m[s[ 3]])?;
|
||||
mixing_g(cs.namespace(|| "mixing invocation 3"), &mut v, 2, 6, 10, 14, &m[s[ 4]], &m[s[ 5]])?;
|
||||
mixing_g(cs.namespace(|| "mixing invocation 4"), &mut v, 3, 7, 11, 15, &m[s[ 6]], &m[s[ 7]])?;
|
||||
|
||||
mixing_g(cs.namespace(|| "mixing invocation 5"), &mut v, 0, 5, 10, 15, &m[s[ 8]], &m[s[ 9]])?;
|
||||
mixing_g(cs.namespace(|| "mixing invocation 6"), &mut v, 1, 6, 11, 12, &m[s[10]], &m[s[11]])?;
|
||||
mixing_g(cs.namespace(|| "mixing invocation 7"), &mut v, 2, 7, 8, 13, &m[s[12]], &m[s[13]])?;
|
||||
mixing_g(cs.namespace(|| "mixing invocation 8"), &mut v, 3, 4, 9, 14, &m[s[14]], &m[s[15]])?;
|
||||
}
|
||||
|
||||
for i in 0..8 {
|
||||
let mut cs = cs.namespace(|| format!("h[{i}] ^ v[{i}] ^ v[{i} + 8]", i=i));
|
||||
|
||||
h[i] = h[i].xor(cs.namespace(|| "first xor"), &v[i])?;
|
||||
h[i] = h[i].xor(cs.namespace(|| "second xor"), &v[i + 8])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
FUNCTION BLAKE2( d[0..dd-1], ll, kk, nn )
|
||||
|
|
||||
| h[0..7] := IV[0..7] // Initialization Vector.
|
||||
|
|
||||
| // Parameter block p[0]
|
||||
| h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn
|
||||
|
|
||||
| // Process padded key and data blocks
|
||||
| IF dd > 1 THEN
|
||||
| | FOR i = 0 TO dd - 2 DO
|
||||
| | | h := F( h, d[i], (i + 1) * bb, FALSE )
|
||||
| | END FOR.
|
||||
| END IF.
|
||||
|
|
||||
| // Final block.
|
||||
| IF kk = 0 THEN
|
||||
| | h := F( h, d[dd - 1], ll, TRUE )
|
||||
| ELSE
|
||||
| | h := F( h, d[dd - 1], ll + bb, TRUE )
|
||||
| END IF.
|
||||
|
|
||||
| RETURN first "nn" bytes from little-endian word array h[].
|
||||
|
|
||||
END FUNCTION.
|
||||
*/
|
||||
|
||||
pub fn blake2s<E: Engine, CS: ConstraintSystem<E>>(
|
||||
mut cs: CS,
|
||||
input: &[Boolean<CS::Variable>]
|
||||
) -> Result<Vec<Boolean<CS::Variable>>, SynthesisError>
|
||||
{
|
||||
assert!(input.len() % 8 == 0);
|
||||
|
||||
let mut h = Vec::with_capacity(8);
|
||||
h.push(UInt32::constant(0x6A09E667 ^ 0x01010000 ^ 32));
|
||||
h.push(UInt32::constant(0xBB67AE85));
|
||||
h.push(UInt32::constant(0x3C6EF372));
|
||||
h.push(UInt32::constant(0xA54FF53A));
|
||||
h.push(UInt32::constant(0x510E527F));
|
||||
h.push(UInt32::constant(0x9B05688C));
|
||||
h.push(UInt32::constant(0x1F83D9AB));
|
||||
h.push(UInt32::constant(0x5BE0CD19));
|
||||
|
||||
let mut blocks: Vec<Vec<UInt32<CS::Variable>>> = vec![];
|
||||
|
||||
for block in input.chunks(512) {
|
||||
let mut this_block = Vec::with_capacity(16);
|
||||
for word in block.chunks(32) {
|
||||
let mut tmp = word.to_vec();
|
||||
while tmp.len() < 32 {
|
||||
tmp.push(Boolean::constant(false));
|
||||
}
|
||||
this_block.push(UInt32::from_bits(&tmp));
|
||||
}
|
||||
while this_block.len() < 16 {
|
||||
this_block.push(UInt32::constant(0));
|
||||
}
|
||||
blocks.push(this_block);
|
||||
}
|
||||
|
||||
if blocks.len() == 0 {
|
||||
blocks.push((0..16).map(|_| UInt32::constant(0)).collect());
|
||||
}
|
||||
|
||||
for (i, block) in blocks[0..blocks.len() - 1].iter().enumerate() {
|
||||
let cs = cs.namespace(|| format!("block {}", i));
|
||||
|
||||
blake2s_compression(cs, &mut h, block, ((i as u64) + 1) * 64, false)?;
|
||||
}
|
||||
|
||||
{
|
||||
let cs = cs.namespace(|| "final block");
|
||||
|
||||
blake2s_compression(cs, &mut h, &blocks[blocks.len() - 1], (input.len() / 8) as u64, true)?;
|
||||
}
|
||||
|
||||
Ok(h.iter().flat_map(|b| b.into_bits()).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{XorShiftRng, SeedableRng, Rng};
|
||||
use pairing::bls12_381::{Bls12};
|
||||
use ::circuit::boolean::{Boolean, AllocatedBit};
|
||||
use ::circuit::test::TestConstraintSystem;
|
||||
use super::blake2s;
|
||||
use bellman::{ConstraintSystem};
|
||||
use blake2::{Blake2s};
|
||||
use digest::{FixedOutput, Input};
|
||||
|
||||
#[test]
|
||||
fn test_blake2s_constraints() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let input_bits: Vec<_> = (0..512).map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()).collect();
|
||||
blake2s(&mut cs, &input_bits).unwrap();
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(cs.num_constraints(), 21792);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blake2s() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0))
|
||||
{
|
||||
let mut h = Blake2s::new_keyed(&[], 32);
|
||||
|
||||
let data: Vec<u8> = (0..input_len).map(|_| rng.gen()).collect();
|
||||
|
||||
h.process(&data);
|
||||
|
||||
let hash_result = h.fixed_result();
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut input_bits = vec![];
|
||||
|
||||
for (byte_i, input_byte) in data.into_iter().enumerate() {
|
||||
for bit_i in (0..8).rev() {
|
||||
let cs = cs.namespace(|| format!("input bit {} {}", byte_i, bit_i));
|
||||
|
||||
input_bits.push(AllocatedBit::alloc(cs, Some((input_byte >> bit_i) & 1u8 == 1u8)).unwrap().into());
|
||||
}
|
||||
}
|
||||
|
||||
let r = blake2s(&mut cs, &input_bits).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
let mut s = hash_result.as_ref().iter()
|
||||
.flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8));
|
||||
|
||||
for b in r {
|
||||
match b {
|
||||
Boolean::Is(b) => {
|
||||
assert!(s.next().unwrap() == b.get_value().unwrap());
|
||||
},
|
||||
Boolean::Not(b) => {
|
||||
assert!(s.next().unwrap() != b.get_value().unwrap());
|
||||
},
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
use pairing::{
|
||||
Engine,
|
||||
Field
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
ConstraintSystem,
|
||||
SynthesisError,
|
||||
LinearCombination
|
||||
};
|
||||
|
||||
use super::{
|
||||
Assignment
|
||||
};
|
||||
|
||||
/// Represents a variable in the constraint system which is guaranteed
|
||||
/// to be either zero or one.
|
||||
#[derive(Clone)]
|
||||
pub struct AllocatedBit<Var> {
|
||||
variable: Var,
|
||||
value: Option<bool>
|
||||
}
|
||||
|
||||
impl<Var: Copy> AllocatedBit<Var> {
|
||||
pub fn get_value(&self) -> Option<bool> {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn get_variable(&self) -> Var {
|
||||
self.variable
|
||||
}
|
||||
|
||||
/// Allocate a variable in the constraint system which can only be a
|
||||
/// boolean value.
|
||||
pub fn alloc<E, CS>(
|
||||
mut cs: CS,
|
||||
value: Option<bool>,
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let var = cs.alloc(|| "boolean", || {
|
||||
if *value.get()? {
|
||||
Ok(E::Fr::one())
|
||||
} else {
|
||||
Ok(E::Fr::zero())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain: (1 - a) * a = 0
|
||||
// This constrains a to be either 0 or 1.
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "boolean constraint",
|
||||
LinearCombination::zero() + one - var,
|
||||
LinearCombination::zero() + var,
|
||||
LinearCombination::zero()
|
||||
);
|
||||
|
||||
Ok(AllocatedBit {
|
||||
variable: var,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs an XOR operation over the two operands, returning
|
||||
/// an `AllocatedBit`.
|
||||
pub fn xor<E, CS>(
|
||||
mut cs: CS,
|
||||
a: &Self,
|
||||
b: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let mut result_value = None;
|
||||
|
||||
let result_var = cs.alloc(|| "xor result", || {
|
||||
if *a.value.get()? ^ *b.value.get()? {
|
||||
result_value = Some(true);
|
||||
|
||||
Ok(E::Fr::one())
|
||||
} else {
|
||||
result_value = Some(false);
|
||||
|
||||
Ok(E::Fr::zero())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain (a + a) * (b) = (a + b - c)
|
||||
// Given that a and b are boolean constrained, if they
|
||||
// are equal, the only solution for c is 0, and if they
|
||||
// are different, the only solution for c is 1.
|
||||
//
|
||||
// ¬(a ∧ b) ∧ ¬(¬a ∧ ¬b) = c
|
||||
// (1 - (a * b)) * (1 - ((1 - a) * (1 - b))) = c
|
||||
// (1 - ab) * (1 - (1 - a - b + ab)) = c
|
||||
// (1 - ab) * (a + b - ab) = c
|
||||
// a + b - ab - (a^2)b - (b^2)a + (a^2)(b^2) = c
|
||||
// a + b - ab - ab - ab + ab = c
|
||||
// a + b - 2ab = c
|
||||
// -2a * b = c - a - b
|
||||
// 2a * b = a + b - c
|
||||
// (a + a) * b = a + b - c
|
||||
cs.enforce(
|
||||
|| "xor constraint",
|
||||
LinearCombination::zero() + a.variable + a.variable,
|
||||
LinearCombination::zero() + b.variable,
|
||||
LinearCombination::zero() + a.variable + b.variable - result_var
|
||||
);
|
||||
|
||||
Ok(AllocatedBit {
|
||||
variable: result_var,
|
||||
value: result_value
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs an AND operation over the two operands, returning
|
||||
/// an `AllocatedBit`.
|
||||
pub fn and<E, CS>(
|
||||
mut cs: CS,
|
||||
a: &Self,
|
||||
b: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let mut result_value = None;
|
||||
|
||||
let result_var = cs.alloc(|| "and result", || {
|
||||
if *a.value.get()? & *b.value.get()? {
|
||||
result_value = Some(true);
|
||||
|
||||
Ok(E::Fr::one())
|
||||
} else {
|
||||
result_value = Some(false);
|
||||
|
||||
Ok(E::Fr::zero())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain (a) * (b) = (c), ensuring c is 1 iff
|
||||
// a AND b are both 1.
|
||||
cs.enforce(
|
||||
|| "and constraint",
|
||||
LinearCombination::zero() + a.variable,
|
||||
LinearCombination::zero() + b.variable,
|
||||
LinearCombination::zero() + result_var
|
||||
);
|
||||
|
||||
Ok(AllocatedBit {
|
||||
variable: result_var,
|
||||
value: result_value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a boolean value which may be either a constant or
|
||||
/// an interpretation of an `AllocatedBit`.
|
||||
#[derive(Clone)]
|
||||
pub enum Boolean<Var> {
|
||||
/// Existential view of the boolean variable
|
||||
Is(AllocatedBit<Var>),
|
||||
/// Negated view of the boolean variable
|
||||
Not(AllocatedBit<Var>),
|
||||
/// Constant (not an allocated variable)
|
||||
Constant(bool)
|
||||
}
|
||||
|
||||
impl<Var: Copy> Boolean<Var> {
|
||||
/// Construct a boolean from a known constant
|
||||
pub fn constant(b: bool) -> Self {
|
||||
Boolean::Constant(b)
|
||||
}
|
||||
|
||||
/// Return a negated interpretation of this boolean.
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform XOR over two boolean operands
|
||||
pub fn xor<'a, E, CS>(
|
||||
cs: CS,
|
||||
a: &'a Self,
|
||||
b: &'a Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
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()),
|
||||
// a XOR (NOT b) = NOT(a XOR b)
|
||||
(is @ &Boolean::Is(_), not @ &Boolean::Not(_)) | (not @ &Boolean::Not(_), is @ &Boolean::Is(_)) => {
|
||||
Ok(Boolean::xor(
|
||||
cs,
|
||||
is,
|
||||
¬.not()
|
||||
)?.not())
|
||||
},
|
||||
// a XOR b = (NOT a) XOR (NOT b)
|
||||
(&Boolean::Is(ref a), &Boolean::Is(ref b)) | (&Boolean::Not(ref a), &Boolean::Not(ref b)) => {
|
||||
Ok(Boolean::Is(AllocatedBit::xor(cs, a, b)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Var> From<AllocatedBit<Var>> for Boolean<Var> {
|
||||
fn from(b: AllocatedBit<Var>) -> Boolean<Var> {
|
||||
Boolean::Is(b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bellman::{ConstraintSystem};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::{Field, PrimeField};
|
||||
use ::circuit::test::*;
|
||||
use super::{AllocatedBit, Boolean};
|
||||
|
||||
#[test]
|
||||
fn test_allocated_bit() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
AllocatedBit::alloc(&mut cs, Some(true)).unwrap();
|
||||
assert!(cs.get("boolean") == Fr::one());
|
||||
assert!(cs.is_satisfied());
|
||||
cs.set("boolean", Fr::zero());
|
||||
assert!(cs.is_satisfied());
|
||||
cs.set("boolean", Fr::from_str("2").unwrap());
|
||||
assert!(!cs.is_satisfied());
|
||||
assert!(cs.which_is_unsatisfied() == Some("boolean constraint"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xor() {
|
||||
for a_val in [false, true].iter() {
|
||||
for b_val in [false, true].iter() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||
AllocatedBit::xor(&mut cs, &a, &b).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("xor result") == if *a_val ^ *b_val { Field::one() } else { Field::zero() });
|
||||
|
||||
// Invert the result and check if the constraint system is still satisfied
|
||||
cs.set("xor result", if *a_val ^ *b_val { Field::zero() } else { Field::one() });
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_and() {
|
||||
for a_val in [false, true].iter() {
|
||||
for b_val in [false, true].iter() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||
AllocatedBit::and(&mut cs, &a, &b).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("and result") == if *a_val & *b_val { Field::one() } else { Field::zero() });
|
||||
|
||||
// Invert the result and check if the constraint system is still satisfied
|
||||
cs.set("and result", if *a_val & *b_val { Field::zero() } else { Field::one() });
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_negation() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut b = Boolean::from(AllocatedBit::alloc(&mut cs, Some(true)).unwrap());
|
||||
|
||||
match b {
|
||||
Boolean::Is(_) => {},
|
||||
_ => panic!("unexpected value")
|
||||
}
|
||||
|
||||
b = b.not();
|
||||
|
||||
match b {
|
||||
Boolean::Not(_) => {},
|
||||
_ => panic!("unexpected value")
|
||||
}
|
||||
|
||||
b = b.not();
|
||||
|
||||
match b {
|
||||
Boolean::Is(_) => {},
|
||||
_ => panic!("unexpected value")
|
||||
}
|
||||
|
||||
b = Boolean::constant(true);
|
||||
|
||||
match b {
|
||||
Boolean::Constant(true) => {},
|
||||
_ => panic!("unexpected value")
|
||||
}
|
||||
|
||||
b = b.not();
|
||||
|
||||
match b {
|
||||
Boolean::Constant(false) => {},
|
||||
_ => panic!("unexpected value")
|
||||
}
|
||||
|
||||
b = b.not();
|
||||
|
||||
match b {
|
||||
Boolean::Constant(true) => {},
|
||||
_ => panic!("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_xor() {
|
||||
#[derive(Copy, Clone)]
|
||||
enum OperandType {
|
||||
True,
|
||||
False,
|
||||
AllocatedTrue,
|
||||
AllocatedFalse,
|
||||
NegatedAllocatedTrue,
|
||||
NegatedAllocatedFalse
|
||||
}
|
||||
|
||||
let variants = [
|
||||
OperandType::True,
|
||||
OperandType::False,
|
||||
OperandType::AllocatedTrue,
|
||||
OperandType::AllocatedFalse,
|
||||
OperandType::NegatedAllocatedTrue,
|
||||
OperandType::NegatedAllocatedFalse
|
||||
];
|
||||
|
||||
for first_operand in variants.iter().cloned() {
|
||||
for second_operand in variants.iter().cloned() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let a;
|
||||
let b;
|
||||
|
||||
{
|
||||
let mut dyn_construct = |operand, name| {
|
||||
let cs = cs.namespace(|| name);
|
||||
|
||||
match operand {
|
||||
OperandType::True => Boolean::constant(true),
|
||||
OperandType::False => Boolean::constant(false),
|
||||
OperandType::AllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()),
|
||||
OperandType::AllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()),
|
||||
OperandType::NegatedAllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()).not(),
|
||||
OperandType::NegatedAllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()).not(),
|
||||
}
|
||||
};
|
||||
|
||||
a = dyn_construct(first_operand, "a");
|
||||
b = dyn_construct(second_operand, "b");
|
||||
}
|
||||
|
||||
let c = Boolean::xor(&mut cs, &a, &b).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
match (first_operand, second_operand, c) {
|
||||
(OperandType::True, OperandType::True, Boolean::Constant(false)) => {},
|
||||
(OperandType::True, OperandType::False, Boolean::Constant(true)) => {},
|
||||
(OperandType::True, OperandType::AllocatedTrue, Boolean::Not(_)) => {},
|
||||
(OperandType::True, OperandType::AllocatedFalse, Boolean::Not(_)) => {},
|
||||
(OperandType::True, OperandType::NegatedAllocatedTrue, Boolean::Is(_)) => {},
|
||||
(OperandType::True, OperandType::NegatedAllocatedFalse, Boolean::Is(_)) => {},
|
||||
|
||||
(OperandType::False, OperandType::True, Boolean::Constant(true)) => {},
|
||||
(OperandType::False, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::False, OperandType::AllocatedTrue, Boolean::Is(_)) => {},
|
||||
(OperandType::False, OperandType::AllocatedFalse, Boolean::Is(_)) => {},
|
||||
(OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {},
|
||||
(OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {},
|
||||
|
||||
(OperandType::AllocatedTrue, OperandType::True, Boolean::Not(_)) => {},
|
||||
(OperandType::AllocatedTrue, OperandType::False, Boolean::Is(_)) => {},
|
||||
(OperandType::AllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::AllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
|
||||
(OperandType::AllocatedFalse, OperandType::True, Boolean::Not(_)) => {},
|
||||
(OperandType::AllocatedFalse, OperandType::False, Boolean::Is(_)) => {},
|
||||
(OperandType::AllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::AllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::AllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::True, Boolean::Is(_)) => {},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Not(_)) => {},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::AllocatedTrue, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::AllocatedFalse, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::True, Boolean::Is(_)) => {},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Not(_)) => {},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::AllocatedTrue, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::AllocatedFalse, Boolean::Not(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("xor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
|
||||
_ => panic!("this should never be encountered")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
||||
pub mod boolean;
|
||||
pub mod uint32;
|
||||
pub mod blake2s;
|
||||
|
||||
use bellman::SynthesisError;
|
||||
|
||||
trait Assignment<T> {
|
||||
fn get(&self) -> Result<&T, SynthesisError>;
|
||||
}
|
||||
|
||||
impl<T> Assignment<T> for Option<T> {
|
||||
fn get(&self) -> Result<&T, SynthesisError> {
|
||||
match *self {
|
||||
Some(ref v) => Ok(v),
|
||||
None => Err(SynthesisError::AssignmentMissing)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
use pairing::{
|
||||
Engine,
|
||||
Field
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
LinearCombination,
|
||||
SynthesisError,
|
||||
ConstraintSystem
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Variable {
|
||||
Input(usize),
|
||||
Aux(usize)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NamedObject {
|
||||
Constraint(usize),
|
||||
Var(Variable),
|
||||
Namespace
|
||||
}
|
||||
|
||||
/// Constraint system for testing purposes.
|
||||
pub struct TestConstraintSystem<E: Engine> {
|
||||
named_objects: HashMap<String, NamedObject>,
|
||||
current_namespace: Vec<String>,
|
||||
constraints: Vec<(LinearCombination<Variable, E>, LinearCombination<Variable, E>, LinearCombination<Variable, E>, String)>,
|
||||
inputs: Vec<(E::Fr, String)>,
|
||||
aux: Vec<(E::Fr, String)>
|
||||
}
|
||||
|
||||
fn eval_lc<E: Engine>(
|
||||
terms: &[(Variable, E::Fr)],
|
||||
inputs: &[(E::Fr, String)],
|
||||
aux: &[(E::Fr, String)]
|
||||
) -> E::Fr
|
||||
{
|
||||
let mut acc = E::Fr::zero();
|
||||
|
||||
for &(var, ref coeff) in terms {
|
||||
let mut tmp = match var {
|
||||
Variable::Input(index) => inputs[index].0,
|
||||
Variable::Aux(index) => aux[index].0
|
||||
};
|
||||
|
||||
tmp.mul_assign(&coeff);
|
||||
acc.add_assign(&tmp);
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
impl<E: Engine> TestConstraintSystem<E> {
|
||||
pub fn new() -> TestConstraintSystem<E> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("ONE".into(), NamedObject::Var(Variable::Input(0)));
|
||||
|
||||
TestConstraintSystem {
|
||||
named_objects: map,
|
||||
current_namespace: vec![],
|
||||
constraints: vec![],
|
||||
inputs: vec![(E::Fr::one(), "ONE".into())],
|
||||
aux: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn which_is_unsatisfied(&self) -> Option<&str> {
|
||||
for &(ref a, ref b, ref c, ref path) in &self.constraints {
|
||||
let mut a = eval_lc::<E>(a.as_ref(), &self.inputs, &self.aux);
|
||||
let b = eval_lc::<E>(b.as_ref(), &self.inputs, &self.aux);
|
||||
let c = eval_lc::<E>(c.as_ref(), &self.inputs, &self.aux);
|
||||
|
||||
a.mul_assign(&b);
|
||||
|
||||
if a != c {
|
||||
return Some(&*path)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_satisfied(&self) -> bool
|
||||
{
|
||||
self.which_is_unsatisfied().is_none()
|
||||
}
|
||||
|
||||
pub fn num_constraints(&self) -> usize
|
||||
{
|
||||
self.constraints.len()
|
||||
}
|
||||
|
||||
pub fn set(&mut self, path: &str, to: E::Fr)
|
||||
{
|
||||
match self.named_objects.get(path) {
|
||||
Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0 = to,
|
||||
Some(&NamedObject::Var(Variable::Aux(index))) => self.aux[index].0 = to,
|
||||
Some(e) => panic!("tried to set path `{}` to value, but `{:?}` already exists there.", path, e),
|
||||
_ => panic!("no variable exists at path: {}", path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&mut self, path: &str) -> E::Fr
|
||||
{
|
||||
match self.named_objects.get(path) {
|
||||
Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0,
|
||||
Some(&NamedObject::Var(Variable::Aux(index))) => self.aux[index].0,
|
||||
Some(e) => panic!("tried to get value of path `{}`, but `{:?}` exists there (not a variable)", path, e),
|
||||
_ => panic!("no variable exists at path: {}", path)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_named_obj(&mut self, path: String, to: NamedObject) {
|
||||
if self.named_objects.contains_key(&path) {
|
||||
panic!("tried to create object at existing path: {}", path);
|
||||
}
|
||||
|
||||
self.named_objects.insert(path, to);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_path(ns: &[String], this: String) -> String {
|
||||
if this.chars().any(|a| a == '/') {
|
||||
panic!("'/' is not allowed in names");
|
||||
}
|
||||
|
||||
let mut name = String::new();
|
||||
|
||||
let mut needs_separation = false;
|
||||
for ns in ns.iter().chain(Some(&this).into_iter())
|
||||
{
|
||||
if needs_separation {
|
||||
name += "/";
|
||||
}
|
||||
|
||||
name += ns;
|
||||
needs_separation = true;
|
||||
}
|
||||
|
||||
name
|
||||
}
|
||||
|
||||
impl<E: Engine> ConstraintSystem<E> for TestConstraintSystem<E> {
|
||||
type Variable = Variable;
|
||||
type Root = Self;
|
||||
|
||||
fn one(&self) -> Self::Variable {
|
||||
Variable::Input(0)
|
||||
}
|
||||
|
||||
fn alloc<F, A, AR>(
|
||||
&mut self,
|
||||
annotation: A,
|
||||
f: F
|
||||
) -> Result<Self::Variable, SynthesisError>
|
||||
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
|
||||
{
|
||||
let index = self.aux.len();
|
||||
let path = compute_path(&self.current_namespace, annotation().into());
|
||||
self.aux.push((f()?, path.clone()));
|
||||
let var = Variable::Aux(index);
|
||||
self.set_named_obj(path, NamedObject::Var(var));
|
||||
|
||||
Ok(var)
|
||||
}
|
||||
|
||||
fn enforce<A, AR>(
|
||||
&mut self,
|
||||
annotation: A,
|
||||
a: LinearCombination<Self::Variable, E>,
|
||||
b: LinearCombination<Self::Variable, E>,
|
||||
c: LinearCombination<Self::Variable, E>
|
||||
)
|
||||
where A: FnOnce() -> AR, AR: Into<String>
|
||||
{
|
||||
let path = compute_path(&self.current_namespace, annotation().into());
|
||||
let index = self.constraints.len();
|
||||
self.set_named_obj(path.clone(), NamedObject::Constraint(index));
|
||||
|
||||
self.constraints.push((a, b, c, path));
|
||||
}
|
||||
|
||||
fn push_namespace<NR, N>(&mut self, name_fn: N)
|
||||
where NR: Into<String>, N: FnOnce() -> NR
|
||||
{
|
||||
let name = name_fn().into();
|
||||
let path = compute_path(&self.current_namespace, name.clone());
|
||||
self.set_named_obj(path.clone(), NamedObject::Namespace);
|
||||
self.current_namespace.push(name);
|
||||
}
|
||||
|
||||
fn pop_namespace(&mut self)
|
||||
{
|
||||
assert!(self.current_namespace.pop().is_some());
|
||||
}
|
||||
|
||||
fn get_root(&mut self) -> &mut Self::Root
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cs() {
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::PrimeField;
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(cs.num_constraints(), 0);
|
||||
let a = cs.namespace(|| "a").alloc(|| "var", || Ok(Fr::from_str("10").unwrap())).unwrap();
|
||||
let b = cs.namespace(|| "b").alloc(|| "var", || Ok(Fr::from_str("4").unwrap())).unwrap();
|
||||
let c = cs.alloc(|| "product", || Ok(Fr::from_str("40").unwrap())).unwrap();
|
||||
|
||||
cs.enforce(
|
||||
|| "mult",
|
||||
LinearCombination::zero() + a,
|
||||
LinearCombination::zero() + b,
|
||||
LinearCombination::zero() + c
|
||||
);
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(cs.num_constraints(), 1);
|
||||
|
||||
cs.set("a/var", Fr::from_str("4").unwrap());
|
||||
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "eq",
|
||||
LinearCombination::zero() + a,
|
||||
LinearCombination::zero() + one,
|
||||
LinearCombination::zero() + b
|
||||
);
|
||||
|
||||
assert!(!cs.is_satisfied());
|
||||
assert!(cs.which_is_unsatisfied() == Some("mult"));
|
||||
|
||||
assert!(cs.get("product") == Fr::from_str("40").unwrap());
|
||||
|
||||
cs.set("product", Fr::from_str("16").unwrap());
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
{
|
||||
let mut cs = cs.namespace(|| "test1");
|
||||
let mut cs = cs.namespace(|| "test2");
|
||||
cs.alloc(|| "hehe", || Ok(Fr::one())).unwrap();
|
||||
}
|
||||
|
||||
assert!(cs.get("test1/test2/hehe") == Fr::one());
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
use pairing::{
|
||||
Engine,
|
||||
Field,
|
||||
PrimeField
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
SynthesisError,
|
||||
ConstraintSystem,
|
||||
LinearCombination
|
||||
};
|
||||
|
||||
use super::boolean::{
|
||||
Boolean,
|
||||
AllocatedBit
|
||||
};
|
||||
|
||||
/// Represents an interpretation of 32 `Boolean` objects as an
|
||||
/// unsigned integer.
|
||||
#[derive(Clone)]
|
||||
pub struct UInt32<Var> {
|
||||
// Least significant bit first
|
||||
bits: Vec<Boolean<Var>>,
|
||||
value: Option<u32>
|
||||
}
|
||||
|
||||
impl<Var: Copy> UInt32<Var> {
|
||||
/// Construct a constant `UInt32` from a `u32`
|
||||
pub fn constant(value: u32) -> Self
|
||||
{
|
||||
let mut bits = Vec::with_capacity(32);
|
||||
|
||||
let mut tmp = value;
|
||||
for _ in 0..32 {
|
||||
if tmp & 1 == 1 {
|
||||
bits.push(Boolean::constant(true))
|
||||
} else {
|
||||
bits.push(Boolean::constant(false))
|
||||
}
|
||||
|
||||
tmp >>= 1;
|
||||
}
|
||||
|
||||
UInt32 {
|
||||
bits: bits,
|
||||
value: Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a `UInt32` in the constraint system
|
||||
pub fn alloc<E, CS>(
|
||||
mut cs: CS,
|
||||
value: Option<u32>
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let values = match value {
|
||||
Some(mut val) => {
|
||||
let mut v = Vec::with_capacity(32);
|
||||
|
||||
for _ in 0..32 {
|
||||
v.push(Some(val & 1 == 1));
|
||||
val >>= 1;
|
||||
}
|
||||
|
||||
v
|
||||
},
|
||||
None => vec![None; 32]
|
||||
};
|
||||
|
||||
let bits = values.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
Ok(Boolean::from(AllocatedBit::alloc(cs.namespace(|| format!("allocated bit {}", i)), v)?))
|
||||
})
|
||||
.collect::<Result<Vec<_>, SynthesisError>>()?;
|
||||
|
||||
Ok(UInt32 {
|
||||
bits: bits,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
|
||||
/// Turns this `UInt32` into its little-endian byte order representation.
|
||||
pub fn into_bits(&self) -> Vec<Boolean<Var>> {
|
||||
self.bits.chunks(8)
|
||||
.flat_map(|v| v.iter().rev())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Converts a little-endian byte order representation of bits into a
|
||||
/// `UInt32`.
|
||||
pub fn from_bits(bits: &[Boolean<Var>]) -> Self
|
||||
{
|
||||
assert_eq!(bits.len(), 32);
|
||||
|
||||
let new_bits = bits.chunks(8)
|
||||
.flat_map(|v| v.iter().rev())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut value = Some(0u32);
|
||||
for b in new_bits.iter().rev() {
|
||||
value.as_mut().map(|v| *v <<= 1);
|
||||
|
||||
match b {
|
||||
&Boolean::Constant(b) => {
|
||||
if b {
|
||||
value.as_mut().map(|v| *v |= 1);
|
||||
}
|
||||
},
|
||||
&Boolean::Is(ref b) => {
|
||||
match b.get_value() {
|
||||
Some(true) => { value.as_mut().map(|v| *v |= 1); },
|
||||
Some(false) => {},
|
||||
None => { value = None }
|
||||
}
|
||||
},
|
||||
&Boolean::Not(ref b) => {
|
||||
match b.get_value() {
|
||||
Some(false) => { value.as_mut().map(|v| *v |= 1); },
|
||||
Some(true) => {},
|
||||
None => { value = None }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UInt32 {
|
||||
value: value,
|
||||
bits: new_bits
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotr(&self, by: usize) -> Self {
|
||||
let by = by % 32;
|
||||
|
||||
let new_bits = self.bits.iter()
|
||||
.skip(by)
|
||||
.chain(self.bits.iter())
|
||||
.take(32)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
UInt32 {
|
||||
bits: new_bits,
|
||||
value: self.value.map(|v| v.rotate_right(by as u32))
|
||||
}
|
||||
}
|
||||
|
||||
/// XOR this `UInt32` with another `UInt32`
|
||||
pub fn xor<E, CS>(
|
||||
&self,
|
||||
mut cs: CS,
|
||||
other: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let new_value = match (self.value, other.value) {
|
||||
(Some(a), Some(b)) => {
|
||||
Some(a ^ b)
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
let bits = self.bits.iter()
|
||||
.zip(other.bits.iter())
|
||||
.enumerate()
|
||||
.map(|(i, (a, b))| {
|
||||
Boolean::xor(cs.namespace(|| format!("xor of bit {}", i)), a, b)
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(UInt32 {
|
||||
bits: bits,
|
||||
value: new_value
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: could optimize
|
||||
/// Perform modular addition of several `UInt32` objects.
|
||||
pub fn addmany<E, CS>(
|
||||
mut cs: CS,
|
||||
operands: &[Self]
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
// Make some arbitrary bounds for ourselves to avoid overflows
|
||||
// in the scalar field
|
||||
assert!(E::Fr::NUM_BITS >= 64);
|
||||
assert!(operands.len() >= 2); // Weird trivial cases that should never happen
|
||||
assert!(operands.len() <= 10);
|
||||
|
||||
// Compute the maximum value of the sum so we allocate enough bits for
|
||||
// the result
|
||||
let mut max_value = (operands.len() as u64) * (u32::max_value() as u64);
|
||||
|
||||
// Keep track of the resulting value
|
||||
let mut result_value = Some(0u64);
|
||||
|
||||
// This is a linear combination that we will enforce to be "zero"
|
||||
let mut lc = LinearCombination::zero();
|
||||
|
||||
// Iterate over the operands
|
||||
for op in operands {
|
||||
// Accumulate the value
|
||||
match op.value {
|
||||
Some(val) => {
|
||||
result_value.as_mut().map(|v| *v += val as u64);
|
||||
},
|
||||
None => {
|
||||
// If any of our operands have unknown value, we won't
|
||||
// know the value of the result
|
||||
result_value = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over each bit of the operand and add the operand to
|
||||
// the linear combination
|
||||
let mut coeff = E::Fr::one();
|
||||
for bit in &op.bits {
|
||||
match bit {
|
||||
&Boolean::Is(ref bit) => {
|
||||
// Add coeff * bit
|
||||
lc = lc + (coeff, bit.get_variable());
|
||||
},
|
||||
&Boolean::Not(ref bit) => {
|
||||
// Add coeff * (1 - bit) = coeff * ONE - coeff * bit
|
||||
lc = lc + (coeff, cs.one()) - (coeff, bit.get_variable());
|
||||
},
|
||||
&Boolean::Constant(bit) => {
|
||||
if bit {
|
||||
lc = lc + (coeff, cs.one());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coeff.double();
|
||||
}
|
||||
}
|
||||
|
||||
// The value of the actual result is modulo 2^32
|
||||
let modular_value = result_value.map(|v| v as u32);
|
||||
|
||||
// Storage area for the resulting bits
|
||||
let mut result_bits = vec![];
|
||||
|
||||
// Allocate each bit of the result
|
||||
let mut coeff = E::Fr::one();
|
||||
let mut i = 0;
|
||||
while max_value != 0 {
|
||||
// Allocate the bit
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| format!("result bit {}", i)), result_value.map(|v| (v >> i) & 1 == 1))?;
|
||||
|
||||
// Subtract this bit from the linear combination to ensure the sums balance out
|
||||
lc = lc - (coeff, b.get_variable());
|
||||
|
||||
result_bits.push(b.into());
|
||||
|
||||
max_value >>= 1;
|
||||
i += 1;
|
||||
coeff.double();
|
||||
}
|
||||
|
||||
// Enforce that the linear combination equals zero
|
||||
cs.enforce(
|
||||
|| "modular addition",
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero(),
|
||||
lc
|
||||
);
|
||||
|
||||
// Discard carry bits that we don't care about
|
||||
result_bits.truncate(32);
|
||||
|
||||
Ok(UInt32 {
|
||||
bits: result_bits,
|
||||
value: modular_value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{XorShiftRng, SeedableRng, Rng};
|
||||
use ::circuit::boolean::{Boolean};
|
||||
use super::{UInt32};
|
||||
use pairing::bls12_381::{Bls12};
|
||||
use pairing::{Field};
|
||||
use ::circuit::test::*;
|
||||
use bellman::{ConstraintSystem};
|
||||
|
||||
#[test]
|
||||
fn test_uint32_from_bits() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let mut v = (0..32).map(|_| Boolean::<()>::constant(rng.gen())).collect::<Vec<_>>();
|
||||
|
||||
let b = UInt32::from_bits(&v);
|
||||
|
||||
for (i, bit) in b.bits.iter().enumerate() {
|
||||
match bit {
|
||||
&Boolean::Constant(bit) => {
|
||||
assert!(bit == ((b.value.unwrap() >> i) & 1 == 1));
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
let expected_to_be_same = b.into_bits();
|
||||
|
||||
for x in v.iter().zip(expected_to_be_same.iter())
|
||||
{
|
||||
match x {
|
||||
(&Boolean::Constant(true), &Boolean::Constant(true)) => {},
|
||||
(&Boolean::Constant(false), &Boolean::Constant(false)) => {},
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uint32_xor() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let a: u32 = rng.gen();
|
||||
let b: u32 = rng.gen();
|
||||
let c: u32 = rng.gen();
|
||||
|
||||
let mut expected = a ^ b ^ c;
|
||||
|
||||
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
|
||||
let b_bit = UInt32::constant(b);
|
||||
let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap();
|
||||
|
||||
let r = a_bit.xor(cs.namespace(|| "first xor"), &b_bit).unwrap();
|
||||
let r = r.xor(cs.namespace(|| "second xor"), &c_bit).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
assert!(r.value == Some(expected));
|
||||
|
||||
for b in r.bits.iter() {
|
||||
match b {
|
||||
&Boolean::Is(ref b) => {
|
||||
assert!(b.get_value().unwrap() == (expected & 1 == 1));
|
||||
},
|
||||
&Boolean::Not(ref b) => {
|
||||
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
|
||||
},
|
||||
&Boolean::Constant(b) => {
|
||||
assert!(b == (expected & 1 == 1));
|
||||
}
|
||||
}
|
||||
|
||||
expected >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uint32_addmany() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let a: u32 = rng.gen();
|
||||
let b: u32 = rng.gen();
|
||||
let c: u32 = rng.gen();
|
||||
let d: u32 = rng.gen();
|
||||
|
||||
let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d);
|
||||
|
||||
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
|
||||
let b_bit = UInt32::constant(b);
|
||||
let c_bit = UInt32::constant(c);
|
||||
let d_bit = UInt32::alloc(cs.namespace(|| "d_bit"), Some(d)).unwrap();
|
||||
|
||||
let r = a_bit.xor(cs.namespace(|| "xor"), &b_bit).unwrap();
|
||||
let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
assert!(r.value == Some(expected));
|
||||
|
||||
for b in r.bits.iter() {
|
||||
match b {
|
||||
&Boolean::Is(ref b) => {
|
||||
assert!(b.get_value().unwrap() == (expected & 1 == 1));
|
||||
},
|
||||
&Boolean::Not(ref b) => {
|
||||
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
|
||||
},
|
||||
&Boolean::Constant(b) => {
|
||||
assert!(b == (expected & 1 == 1));
|
||||
}
|
||||
}
|
||||
|
||||
expected >>= 1;
|
||||
}
|
||||
|
||||
// Flip a bit and see if the addition constraint still works
|
||||
if cs.get("addition/result bit 0/boolean").is_zero() {
|
||||
cs.set("addition/result bit 0/boolean", Field::one());
|
||||
} else {
|
||||
cs.set("addition/result bit 0/boolean", Field::zero());
|
||||
}
|
||||
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uint32_rotr() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
let mut num = rng.gen();
|
||||
|
||||
let a = UInt32::<()>::constant(num);
|
||||
|
||||
for i in 0..32 {
|
||||
let b = a.rotr(i);
|
||||
|
||||
assert!(b.value.unwrap() == num);
|
||||
|
||||
let mut tmp = num;
|
||||
for b in &b.bits {
|
||||
match b {
|
||||
&Boolean::Constant(b) => {
|
||||
assert_eq!(b, tmp & 1 == 1);
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
|
||||
tmp >>= 1;
|
||||
}
|
||||
|
||||
num = num.rotate_right(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user