Move generic circuit gadgets into bellman
This commit is contained in:
parent
9b3d76694b
commit
dfb86fcf11
|
@ -10,6 +10,7 @@ version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bit-vec = "0.4.4"
|
bit-vec = "0.4.4"
|
||||||
|
blake2s_simd = "0.5"
|
||||||
ff = { path = "../ff" }
|
ff = { path = "../ff" }
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
futures-cpupool = { version = "0.1", optional = true }
|
futures-cpupool = { version = "0.1", optional = true }
|
||||||
|
@ -21,7 +22,10 @@ rand_core = "0.5"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
hex-literal = "0.1"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
|
rand_xorshift = "0.2"
|
||||||
|
sha2 = "0.8"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
groth16 = ["pairing"]
|
groth16 = ["pairing"]
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
pub mod test;
|
||||||
|
|
||||||
|
pub mod boolean;
|
||||||
|
pub mod multieq;
|
||||||
|
pub mod uint32;
|
||||||
|
pub mod blake2s;
|
||||||
|
pub mod num;
|
||||||
|
pub mod lookup;
|
||||||
|
pub mod multipack;
|
||||||
|
pub mod sha256;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
SynthesisError
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This should probably be removed and we
|
||||||
|
// should use existing helper methods on `Option`
|
||||||
|
// for mapping with an error.
|
||||||
|
/// This basically is just an extension to `Option`
|
||||||
|
/// which allows for a convenient mapping to an
|
||||||
|
/// error on `None`.
|
||||||
|
pub 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,449 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::boolean::{
|
||||||
|
Boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::uint32::{
|
||||||
|
UInt32
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::multieq::MultiEq;
|
||||||
|
|
||||||
|
/*
|
||||||
|
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>, M>(
|
||||||
|
mut cs: M,
|
||||||
|
v: &mut [UInt32],
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
c: usize,
|
||||||
|
d: usize,
|
||||||
|
x: &UInt32,
|
||||||
|
y: &UInt32
|
||||||
|
) -> Result<(), SynthesisError>
|
||||||
|
where M: ConstraintSystem<E, Root=MultiEq<E, CS>>
|
||||||
|
{
|
||||||
|
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],
|
||||||
|
m: &[UInt32],
|
||||||
|
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()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cs = MultiEq::new(&mut cs);
|
||||||
|
|
||||||
|
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],
|
||||||
|
personalization: &[u8]
|
||||||
|
) -> Result<Vec<Boolean>, SynthesisError>
|
||||||
|
{
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
|
||||||
|
assert_eq!(personalization.len(), 8);
|
||||||
|
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));
|
||||||
|
|
||||||
|
// Personalization is stored here
|
||||||
|
h.push(UInt32::constant(0x1F83D9AB ^ LittleEndian::read_u32(&personalization[0..4])));
|
||||||
|
h.push(UInt32::constant(0x5BE0CD19 ^ LittleEndian::read_u32(&personalization[4..8])));
|
||||||
|
|
||||||
|
let mut blocks: Vec<Vec<UInt32>> = 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 blake2s_simd::Params as Blake2sParams;
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use rand_core::{RngCore, SeedableRng};
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
use crate::gadgets::boolean::{Boolean, AllocatedBit};
|
||||||
|
use crate::gadgets::test::TestConstraintSystem;
|
||||||
|
use super::blake2s;
|
||||||
|
use crate::{ConstraintSystem};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blank_hash() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let input_bits = vec![];
|
||||||
|
let out = blake2s(&mut cs, &input_bits, b"12345678").unwrap();
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 0);
|
||||||
|
|
||||||
|
// >>> import blake2s from hashlib
|
||||||
|
// >>> h = blake2s(digest_size=32, person=b'12345678')
|
||||||
|
// >>> h.hexdigest()
|
||||||
|
let expected = hex!("c59f682376d137f3f255e671e207d1f2374ebe504e9314208a52d9f88d69e8c8");
|
||||||
|
|
||||||
|
let mut out = out.into_iter();
|
||||||
|
for b in expected.into_iter() {
|
||||||
|
for i in 0..8 {
|
||||||
|
let c = out.next().unwrap().get_value().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(c, (b >> i) & 1u8 == 1u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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, b"12345678").unwrap();
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 21518);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blake2s_precomp_constraints() {
|
||||||
|
// Test that 512 fixed leading bits (constants)
|
||||||
|
// doesn't result in more constraints.
|
||||||
|
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
let input_bits: Vec<_> = (0..512)
|
||||||
|
.map(|_| Boolean::constant(rng.next_u32() % 2 != 0))
|
||||||
|
.chain((0..512)
|
||||||
|
.map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()))
|
||||||
|
.collect();
|
||||||
|
blake2s(&mut cs, &input_bits, b"12345678").unwrap();
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 21518);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blake2s_constant_constraints() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
let input_bits: Vec<_> = (0..512).map(|_| Boolean::constant(rng.next_u32() % 2 != 0)).collect();
|
||||||
|
blake2s(&mut cs, &input_bits, b"12345678").unwrap();
|
||||||
|
assert_eq!(cs.num_constraints(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blake2s() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0))
|
||||||
|
{
|
||||||
|
let mut h = Blake2sParams::new().hash_length(32).personal(b"12345678").to_state();
|
||||||
|
|
||||||
|
let data: Vec<u8> = (0..input_len).map(|_| rng.next_u32() as u8).collect();
|
||||||
|
|
||||||
|
h.update(&data);
|
||||||
|
|
||||||
|
let hash_result = h.finalize();
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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, b"12345678").unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let mut s = hash_result.as_ref().iter()
|
||||||
|
.flat_map(|&byte| (0..8).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());
|
||||||
|
},
|
||||||
|
Boolean::Constant(b) => {
|
||||||
|
assert!(input_len == 0);
|
||||||
|
assert!(s.next().unwrap() == b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,319 @@
|
||||||
|
use ff::Field;
|
||||||
|
use pairing::Engine;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use super::num::{
|
||||||
|
AllocatedNum,
|
||||||
|
Num
|
||||||
|
};
|
||||||
|
use super::boolean::Boolean;
|
||||||
|
use crate::{
|
||||||
|
ConstraintSystem
|
||||||
|
};
|
||||||
|
|
||||||
|
// Synthesize the constants for each base pattern.
|
||||||
|
fn synth<'a, E: Engine, I>(
|
||||||
|
window_size: usize,
|
||||||
|
constants: I,
|
||||||
|
assignment: &mut [E::Fr]
|
||||||
|
)
|
||||||
|
where I: IntoIterator<Item=&'a E::Fr>
|
||||||
|
{
|
||||||
|
assert_eq!(assignment.len(), 1 << window_size);
|
||||||
|
|
||||||
|
for (i, constant) in constants.into_iter().enumerate() {
|
||||||
|
let mut cur = assignment[i];
|
||||||
|
cur.negate();
|
||||||
|
cur.add_assign(constant);
|
||||||
|
assignment[i] = cur;
|
||||||
|
for (j, eval) in assignment.iter_mut().enumerate().skip(i + 1) {
|
||||||
|
if j & i == i {
|
||||||
|
eval.add_assign(&cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a 3-bit window table lookup. `bits` is in
|
||||||
|
/// little-endian order.
|
||||||
|
pub fn lookup3_xy<E: Engine, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
bits: &[Boolean],
|
||||||
|
coords: &[(E::Fr, E::Fr)]
|
||||||
|
) -> Result<(AllocatedNum<E>, AllocatedNum<E>), SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
assert_eq!(bits.len(), 3);
|
||||||
|
assert_eq!(coords.len(), 8);
|
||||||
|
|
||||||
|
// Calculate the index into `coords`
|
||||||
|
let i =
|
||||||
|
match (bits[0].get_value(), bits[1].get_value(), bits[2].get_value()) {
|
||||||
|
(Some(a_value), Some(b_value), Some(c_value)) => {
|
||||||
|
let mut tmp = 0;
|
||||||
|
if a_value {
|
||||||
|
tmp += 1;
|
||||||
|
}
|
||||||
|
if b_value {
|
||||||
|
tmp += 2;
|
||||||
|
}
|
||||||
|
if c_value {
|
||||||
|
tmp += 4;
|
||||||
|
}
|
||||||
|
Some(tmp)
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate the x-coordinate resulting from the lookup
|
||||||
|
let res_x = AllocatedNum::alloc(
|
||||||
|
cs.namespace(|| "x"),
|
||||||
|
|| {
|
||||||
|
Ok(coords[*i.get()?].0)
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Allocate the y-coordinate resulting from the lookup
|
||||||
|
let res_y = AllocatedNum::alloc(
|
||||||
|
cs.namespace(|| "y"),
|
||||||
|
|| {
|
||||||
|
Ok(coords[*i.get()?].1)
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Compute the coefficients for the lookup constraints
|
||||||
|
let mut x_coeffs = [E::Fr::zero(); 8];
|
||||||
|
let mut y_coeffs = [E::Fr::zero(); 8];
|
||||||
|
synth::<E, _>(3, coords.iter().map(|c| &c.0), &mut x_coeffs);
|
||||||
|
synth::<E, _>(3, coords.iter().map(|c| &c.1), &mut y_coeffs);
|
||||||
|
|
||||||
|
let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[1], &bits[2])?;
|
||||||
|
|
||||||
|
let one = CS::one();
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "x-coordinate lookup",
|
||||||
|
|lc| lc + (x_coeffs[0b001], one)
|
||||||
|
+ &bits[1].lc::<E>(one, x_coeffs[0b011])
|
||||||
|
+ &bits[2].lc::<E>(one, x_coeffs[0b101])
|
||||||
|
+ &precomp.lc::<E>(one, x_coeffs[0b111]),
|
||||||
|
|lc| lc + &bits[0].lc::<E>(one, E::Fr::one()),
|
||||||
|
|lc| lc + res_x.get_variable()
|
||||||
|
- (x_coeffs[0b000], one)
|
||||||
|
- &bits[1].lc::<E>(one, x_coeffs[0b010])
|
||||||
|
- &bits[2].lc::<E>(one, x_coeffs[0b100])
|
||||||
|
- &precomp.lc::<E>(one, x_coeffs[0b110]),
|
||||||
|
);
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "y-coordinate lookup",
|
||||||
|
|lc| lc + (y_coeffs[0b001], one)
|
||||||
|
+ &bits[1].lc::<E>(one, y_coeffs[0b011])
|
||||||
|
+ &bits[2].lc::<E>(one, y_coeffs[0b101])
|
||||||
|
+ &precomp.lc::<E>(one, y_coeffs[0b111]),
|
||||||
|
|lc| lc + &bits[0].lc::<E>(one, E::Fr::one()),
|
||||||
|
|lc| lc + res_y.get_variable()
|
||||||
|
- (y_coeffs[0b000], one)
|
||||||
|
- &bits[1].lc::<E>(one, y_coeffs[0b010])
|
||||||
|
- &bits[2].lc::<E>(one, y_coeffs[0b100])
|
||||||
|
- &precomp.lc::<E>(one, y_coeffs[0b110]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((res_x, res_y))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a 3-bit window table lookup, where
|
||||||
|
/// one of the bits is a sign bit.
|
||||||
|
pub fn lookup3_xy_with_conditional_negation<E: Engine, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
bits: &[Boolean],
|
||||||
|
coords: &[(E::Fr, E::Fr)]
|
||||||
|
) -> Result<(Num<E>, Num<E>), SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
assert_eq!(bits.len(), 3);
|
||||||
|
assert_eq!(coords.len(), 4);
|
||||||
|
|
||||||
|
// Calculate the index into `coords`
|
||||||
|
let i =
|
||||||
|
match (bits[0].get_value(), bits[1].get_value()) {
|
||||||
|
(Some(a_value), Some(b_value)) => {
|
||||||
|
let mut tmp = 0;
|
||||||
|
if a_value {
|
||||||
|
tmp += 1;
|
||||||
|
}
|
||||||
|
if b_value {
|
||||||
|
tmp += 2;
|
||||||
|
}
|
||||||
|
Some(tmp)
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate the y-coordinate resulting from the lookup
|
||||||
|
// and conditional negation
|
||||||
|
let y = AllocatedNum::alloc(
|
||||||
|
cs.namespace(|| "y"),
|
||||||
|
|| {
|
||||||
|
let mut tmp = coords[*i.get()?].1;
|
||||||
|
if *bits[2].get_value().get()? {
|
||||||
|
tmp.negate();
|
||||||
|
}
|
||||||
|
Ok(tmp)
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let one = CS::one();
|
||||||
|
|
||||||
|
// Compute the coefficients for the lookup constraints
|
||||||
|
let mut x_coeffs = [E::Fr::zero(); 4];
|
||||||
|
let mut y_coeffs = [E::Fr::zero(); 4];
|
||||||
|
synth::<E, _>(2, coords.iter().map(|c| &c.0), &mut x_coeffs);
|
||||||
|
synth::<E, _>(2, coords.iter().map(|c| &c.1), &mut y_coeffs);
|
||||||
|
|
||||||
|
let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[0], &bits[1])?;
|
||||||
|
|
||||||
|
let x = Num::zero()
|
||||||
|
.add_bool_with_coeff(one, &Boolean::constant(true), x_coeffs[0b00])
|
||||||
|
.add_bool_with_coeff(one, &bits[0], x_coeffs[0b01])
|
||||||
|
.add_bool_with_coeff(one, &bits[1], x_coeffs[0b10])
|
||||||
|
.add_bool_with_coeff(one, &precomp, x_coeffs[0b11]);
|
||||||
|
|
||||||
|
let y_lc = precomp.lc::<E>(one, y_coeffs[0b11]) +
|
||||||
|
&bits[1].lc::<E>(one, y_coeffs[0b10]) +
|
||||||
|
&bits[0].lc::<E>(one, y_coeffs[0b01]) +
|
||||||
|
(y_coeffs[0b00], one);
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "y-coordinate lookup",
|
||||||
|
|lc| lc + &y_lc + &y_lc,
|
||||||
|
|lc| lc + &bits[2].lc::<E>(one, E::Fr::one()),
|
||||||
|
|lc| lc + &y_lc - y.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((x, y.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::gadgets::test::*;
|
||||||
|
use crate::gadgets::boolean::{Boolean, AllocatedBit};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use rand_core::{RngCore, SeedableRng};
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lookup3_xy() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a_val = rng.next_u32() % 2 != 0;
|
||||||
|
let a = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let b_val = rng.next_u32() % 2 != 0;
|
||||||
|
let b = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let c_val = rng.next_u32() % 2 != 0;
|
||||||
|
let c = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let bits = vec![a, b, c];
|
||||||
|
|
||||||
|
let points: Vec<(Fr, Fr)> = (0..8).map(|_| (Fr::random(&mut rng), Fr::random(&mut rng))).collect();
|
||||||
|
|
||||||
|
let res = lookup3_xy(&mut cs, &bits, &points).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let mut index = 0;
|
||||||
|
if a_val { index += 1 }
|
||||||
|
if b_val { index += 2 }
|
||||||
|
if c_val { index += 4 }
|
||||||
|
|
||||||
|
assert_eq!(res.0.get_value().unwrap(), points[index].0);
|
||||||
|
assert_eq!(res.1.get_value().unwrap(), points[index].1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lookup3_xy_with_conditional_negation() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a_val = rng.next_u32() % 2 != 0;
|
||||||
|
let a = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let b_val = rng.next_u32() % 2 != 0;
|
||||||
|
let b = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let c_val = rng.next_u32() % 2 != 0;
|
||||||
|
let c = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let bits = vec![a, b, c];
|
||||||
|
|
||||||
|
let points: Vec<(Fr, Fr)> = (0..4).map(|_| (Fr::random(&mut rng), Fr::random(&mut rng))).collect();
|
||||||
|
|
||||||
|
let res = lookup3_xy_with_conditional_negation(&mut cs, &bits, &points).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let mut index = 0;
|
||||||
|
if a_val { index += 1 }
|
||||||
|
if b_val { index += 2 }
|
||||||
|
|
||||||
|
assert_eq!(res.0.get_value().unwrap(), points[index].0);
|
||||||
|
let mut tmp = points[index].1;
|
||||||
|
if c_val { tmp.negate() }
|
||||||
|
assert_eq!(res.1.get_value().unwrap(), tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_synth() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let window_size = 4;
|
||||||
|
|
||||||
|
let mut assignment = vec![Fr::zero(); 1 << window_size];
|
||||||
|
let constants: Vec<_> = (0..(1 << window_size)).map(|_| Fr::random(&mut rng)).collect();
|
||||||
|
|
||||||
|
synth::<Bls12, _>(window_size, &constants, &mut assignment);
|
||||||
|
|
||||||
|
for b in 0..(1 << window_size) {
|
||||||
|
let mut acc = Fr::zero();
|
||||||
|
|
||||||
|
for j in 0..(1 << window_size) {
|
||||||
|
if j & b == j {
|
||||||
|
acc.add_assign(&assignment[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(acc, constants[b]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
use ff::{Field, PrimeField};
|
||||||
|
use pairing::Engine;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem,
|
||||||
|
LinearCombination,
|
||||||
|
Variable
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct MultiEq<E: Engine, CS: ConstraintSystem<E>>{
|
||||||
|
cs: CS,
|
||||||
|
ops: usize,
|
||||||
|
bits_used: usize,
|
||||||
|
lhs: LinearCombination<E>,
|
||||||
|
rhs: LinearCombination<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, CS: ConstraintSystem<E>> MultiEq<E, CS> {
|
||||||
|
pub fn new(cs: CS) -> Self {
|
||||||
|
MultiEq {
|
||||||
|
cs: cs,
|
||||||
|
ops: 0,
|
||||||
|
bits_used: 0,
|
||||||
|
lhs: LinearCombination::zero(),
|
||||||
|
rhs: LinearCombination::zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accumulate(&mut self)
|
||||||
|
{
|
||||||
|
let ops = self.ops;
|
||||||
|
let lhs = self.lhs.clone();
|
||||||
|
let rhs = self.rhs.clone();
|
||||||
|
self.cs.enforce(
|
||||||
|
|| format!("multieq {}", ops),
|
||||||
|
|_| lhs,
|
||||||
|
|lc| lc + CS::one(),
|
||||||
|
|_| rhs
|
||||||
|
);
|
||||||
|
self.lhs = LinearCombination::zero();
|
||||||
|
self.rhs = LinearCombination::zero();
|
||||||
|
self.bits_used = 0;
|
||||||
|
self.ops += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enforce_equal(
|
||||||
|
&mut self,
|
||||||
|
num_bits: usize,
|
||||||
|
lhs: &LinearCombination<E>,
|
||||||
|
rhs: &LinearCombination<E>
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Check if we will exceed the capacity
|
||||||
|
if (E::Fr::CAPACITY as usize) <= (self.bits_used + num_bits) {
|
||||||
|
self.accumulate();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!((E::Fr::CAPACITY as usize) > (self.bits_used + num_bits));
|
||||||
|
|
||||||
|
let coeff = E::Fr::from_str("2").unwrap().pow(&[self.bits_used as u64]);
|
||||||
|
self.lhs = self.lhs.clone() + (coeff, lhs);
|
||||||
|
self.rhs = self.rhs.clone() + (coeff, rhs);
|
||||||
|
self.bits_used += num_bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, CS: ConstraintSystem<E>> Drop for MultiEq<E, CS> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.bits_used > 0 {
|
||||||
|
self.accumulate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, CS: ConstraintSystem<E>> ConstraintSystem<E> for MultiEq<E, CS>
|
||||||
|
{
|
||||||
|
type Root = Self;
|
||||||
|
|
||||||
|
fn one() -> Variable {
|
||||||
|
CS::one()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc<F, A, AR>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
f: F
|
||||||
|
) -> Result<Variable, SynthesisError>
|
||||||
|
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
|
||||||
|
{
|
||||||
|
self.cs.alloc(annotation, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_input<F, A, AR>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
f: F
|
||||||
|
) -> Result<Variable, SynthesisError>
|
||||||
|
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
|
||||||
|
{
|
||||||
|
self.cs.alloc_input(annotation, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enforce<A, AR, LA, LB, LC>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
a: LA,
|
||||||
|
b: LB,
|
||||||
|
c: LC
|
||||||
|
)
|
||||||
|
where A: FnOnce() -> AR, AR: Into<String>,
|
||||||
|
LA: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
|
||||||
|
LB: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
|
||||||
|
LC: FnOnce(LinearCombination<E>) -> LinearCombination<E>
|
||||||
|
{
|
||||||
|
self.cs.enforce(annotation, a, b, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_namespace<NR, N>(&mut self, name_fn: N)
|
||||||
|
where NR: Into<String>, N: FnOnce() -> NR
|
||||||
|
{
|
||||||
|
self.cs.get_root().push_namespace(name_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_namespace(&mut self)
|
||||||
|
{
|
||||||
|
self.cs.get_root().pop_namespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_root(&mut self) -> &mut Self::Root
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
use ff::{Field, PrimeField};
|
||||||
|
use pairing::Engine;
|
||||||
|
use crate::{ConstraintSystem, SynthesisError};
|
||||||
|
use super::boolean::{Boolean};
|
||||||
|
use super::num::Num;
|
||||||
|
use super::Assignment;
|
||||||
|
|
||||||
|
/// Takes a sequence of booleans and exposes them as compact
|
||||||
|
/// public inputs
|
||||||
|
pub fn pack_into_inputs<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
bits: &[Boolean]
|
||||||
|
) -> Result<(), SynthesisError>
|
||||||
|
where E: Engine, CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
for (i, bits) in bits.chunks(E::Fr::CAPACITY as usize).enumerate()
|
||||||
|
{
|
||||||
|
let mut num = Num::<E>::zero();
|
||||||
|
let mut coeff = E::Fr::one();
|
||||||
|
for bit in bits {
|
||||||
|
num = num.add_bool_with_coeff(CS::one(), bit, coeff);
|
||||||
|
|
||||||
|
coeff.double();
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = cs.alloc_input(|| format!("input {}", i), || {
|
||||||
|
Ok(*num.get_value().get()?)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// num * 1 = input
|
||||||
|
cs.enforce(
|
||||||
|
|| format!("packing constraint {}", i),
|
||||||
|
|_| num.lc(E::Fr::one()),
|
||||||
|
|lc| lc + CS::one(),
|
||||||
|
|lc| lc + input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes_to_bits(bytes: &[u8]) -> Vec<bool>
|
||||||
|
{
|
||||||
|
bytes.iter()
|
||||||
|
.flat_map(|&v| (0..8).rev().map(move |i| (v >> i) & 1 == 1))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes_to_bits_le(bytes: &[u8]) -> Vec<bool>
|
||||||
|
{
|
||||||
|
bytes.iter()
|
||||||
|
.flat_map(|&v| (0..8).map(move |i| (v >> i) & 1 == 1))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_multipacking<E: Engine>(
|
||||||
|
bits: &[bool]
|
||||||
|
) -> Vec<E::Fr>
|
||||||
|
{
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
|
for bits in bits.chunks(E::Fr::CAPACITY as usize)
|
||||||
|
{
|
||||||
|
let mut cur = E::Fr::zero();
|
||||||
|
let mut coeff = E::Fr::one();
|
||||||
|
|
||||||
|
for bit in bits {
|
||||||
|
if *bit {
|
||||||
|
cur.add_assign(&coeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
coeff.double();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multipacking() {
|
||||||
|
use crate::{ConstraintSystem};
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use rand_core::{RngCore, SeedableRng};
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
use crate::gadgets::test::*;
|
||||||
|
use super::boolean::{AllocatedBit, Boolean};
|
||||||
|
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for num_bits in 0..1500 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let bits: Vec<bool> = (0..num_bits).map(|_| rng.next_u32() % 2 != 0).collect();
|
||||||
|
|
||||||
|
let circuit_bits = bits.iter().enumerate()
|
||||||
|
.map(|(i, &b)| {
|
||||||
|
Boolean::from(
|
||||||
|
AllocatedBit::alloc(
|
||||||
|
cs.namespace(|| format!("bit {}", i)),
|
||||||
|
Some(b)
|
||||||
|
).unwrap()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected_inputs = compute_multipacking::<Bls12>(&bits);
|
||||||
|
|
||||||
|
pack_into_inputs(cs.namespace(|| "pack"), &circuit_bits).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.verify(&expected_inputs));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,422 @@
|
||||||
|
use super::uint32::UInt32;
|
||||||
|
use super::multieq::MultiEq;
|
||||||
|
use super::boolean::Boolean;
|
||||||
|
use crate::{ConstraintSystem, SynthesisError};
|
||||||
|
use pairing::Engine;
|
||||||
|
|
||||||
|
const ROUND_CONSTANTS: [u32; 64] = [
|
||||||
|
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||||
|
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||||
|
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||||
|
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||||
|
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||||
|
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||||
|
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||||
|
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||||
|
];
|
||||||
|
|
||||||
|
const IV: [u32; 8] = [
|
||||||
|
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||||
|
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn sha256_block_no_padding<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
input: &[Boolean]
|
||||||
|
) -> Result<Vec<Boolean>, SynthesisError>
|
||||||
|
where E: Engine, CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
assert_eq!(input.len(), 512);
|
||||||
|
|
||||||
|
Ok(sha256_compression_function(
|
||||||
|
&mut cs,
|
||||||
|
&input,
|
||||||
|
&get_sha256_iv()
|
||||||
|
)?
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|e| e.into_bits_be())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sha256<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
input: &[Boolean]
|
||||||
|
) -> Result<Vec<Boolean>, SynthesisError>
|
||||||
|
where E: Engine, CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
assert!(input.len() % 8 == 0);
|
||||||
|
|
||||||
|
let mut padded = input.to_vec();
|
||||||
|
let plen = padded.len() as u64;
|
||||||
|
// append a single '1' bit
|
||||||
|
padded.push(Boolean::constant(true));
|
||||||
|
// append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512
|
||||||
|
while (padded.len() + 64) % 512 != 0 {
|
||||||
|
padded.push(Boolean::constant(false));
|
||||||
|
}
|
||||||
|
// append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits
|
||||||
|
for b in (0..64).rev().map(|i| (plen >> i) & 1 == 1) {
|
||||||
|
padded.push(Boolean::constant(b));
|
||||||
|
}
|
||||||
|
assert!(padded.len() % 512 == 0);
|
||||||
|
|
||||||
|
let mut cur = get_sha256_iv();
|
||||||
|
for (i, block) in padded.chunks(512).enumerate() {
|
||||||
|
cur = sha256_compression_function(
|
||||||
|
cs.namespace(|| format!("block {}", i)),
|
||||||
|
block,
|
||||||
|
&cur
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cur.into_iter()
|
||||||
|
.flat_map(|e| e.into_bits_be())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sha256_iv() -> Vec<UInt32> {
|
||||||
|
IV.iter().map(|&v| UInt32::constant(v)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sha256_compression_function<E, CS>(
|
||||||
|
cs: CS,
|
||||||
|
input: &[Boolean],
|
||||||
|
current_hash_value: &[UInt32]
|
||||||
|
) -> Result<Vec<UInt32>, SynthesisError>
|
||||||
|
where E: Engine, CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
assert_eq!(input.len(), 512);
|
||||||
|
assert_eq!(current_hash_value.len(), 8);
|
||||||
|
|
||||||
|
let mut w = input.chunks(32)
|
||||||
|
.map(|e| UInt32::from_bits_be(e))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// We can save some constraints by combining some of
|
||||||
|
// the constraints in different u32 additions
|
||||||
|
let mut cs = MultiEq::new(cs);
|
||||||
|
|
||||||
|
for i in 16..64 {
|
||||||
|
let cs = &mut cs.namespace(|| format!("w extension {}", i));
|
||||||
|
|
||||||
|
// s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
|
||||||
|
let mut s0 = w[i-15].rotr(7);
|
||||||
|
s0 = s0.xor(
|
||||||
|
cs.namespace(|| "first xor for s0"),
|
||||||
|
&w[i-15].rotr(18)
|
||||||
|
)?;
|
||||||
|
s0 = s0.xor(
|
||||||
|
cs.namespace(|| "second xor for s0"),
|
||||||
|
&w[i-15].shr(3)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10)
|
||||||
|
let mut s1 = w[i-2].rotr(17);
|
||||||
|
s1 = s1.xor(
|
||||||
|
cs.namespace(|| "first xor for s1"),
|
||||||
|
&w[i-2].rotr(19)
|
||||||
|
)?;
|
||||||
|
s1 = s1.xor(
|
||||||
|
cs.namespace(|| "second xor for s1"),
|
||||||
|
&w[i-2].shr(10)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let tmp = UInt32::addmany(
|
||||||
|
cs.namespace(|| "computation of w[i]"),
|
||||||
|
&[w[i-16].clone(), s0, w[i-7].clone(), s1]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// w[i] := w[i-16] + s0 + w[i-7] + s1
|
||||||
|
w.push(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(w.len(), 64);
|
||||||
|
|
||||||
|
enum Maybe {
|
||||||
|
Deferred(Vec<UInt32>),
|
||||||
|
Concrete(UInt32)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Maybe {
|
||||||
|
fn compute<E, CS, M>(
|
||||||
|
self,
|
||||||
|
cs: M,
|
||||||
|
others: &[UInt32]
|
||||||
|
) -> Result<UInt32, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E>,
|
||||||
|
M: ConstraintSystem<E, Root=MultiEq<E, CS>>
|
||||||
|
{
|
||||||
|
Ok(match self {
|
||||||
|
Maybe::Concrete(ref v) => {
|
||||||
|
return Ok(v.clone())
|
||||||
|
},
|
||||||
|
Maybe::Deferred(mut v) => {
|
||||||
|
v.extend(others.into_iter().cloned());
|
||||||
|
UInt32::addmany(
|
||||||
|
cs,
|
||||||
|
&v
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut a = Maybe::Concrete(current_hash_value[0].clone());
|
||||||
|
let mut b = current_hash_value[1].clone();
|
||||||
|
let mut c = current_hash_value[2].clone();
|
||||||
|
let mut d = current_hash_value[3].clone();
|
||||||
|
let mut e = Maybe::Concrete(current_hash_value[4].clone());
|
||||||
|
let mut f = current_hash_value[5].clone();
|
||||||
|
let mut g = current_hash_value[6].clone();
|
||||||
|
let mut h = current_hash_value[7].clone();
|
||||||
|
|
||||||
|
for i in 0..64 {
|
||||||
|
let cs = &mut cs.namespace(|| format!("compression round {}", i));
|
||||||
|
|
||||||
|
// S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
|
||||||
|
let new_e = e.compute(cs.namespace(|| "deferred e computation"), &[])?;
|
||||||
|
let mut s1 = new_e.rotr(6);
|
||||||
|
s1 = s1.xor(
|
||||||
|
cs.namespace(|| "first xor for s1"),
|
||||||
|
&new_e.rotr(11)
|
||||||
|
)?;
|
||||||
|
s1 = s1.xor(
|
||||||
|
cs.namespace(|| "second xor for s1"),
|
||||||
|
&new_e.rotr(25)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// ch := (e and f) xor ((not e) and g)
|
||||||
|
let ch = UInt32::sha256_ch(
|
||||||
|
cs.namespace(|| "ch"),
|
||||||
|
&new_e,
|
||||||
|
&f,
|
||||||
|
&g
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// temp1 := h + S1 + ch + k[i] + w[i]
|
||||||
|
let temp1 = vec![
|
||||||
|
h.clone(),
|
||||||
|
s1,
|
||||||
|
ch,
|
||||||
|
UInt32::constant(ROUND_CONSTANTS[i]),
|
||||||
|
w[i].clone()
|
||||||
|
];
|
||||||
|
|
||||||
|
// S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
|
||||||
|
let new_a = a.compute(cs.namespace(|| "deferred a computation"), &[])?;
|
||||||
|
let mut s0 = new_a.rotr(2);
|
||||||
|
s0 = s0.xor(
|
||||||
|
cs.namespace(|| "first xor for s0"),
|
||||||
|
&new_a.rotr(13)
|
||||||
|
)?;
|
||||||
|
s0 = s0.xor(
|
||||||
|
cs.namespace(|| "second xor for s0"),
|
||||||
|
&new_a.rotr(22)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// maj := (a and b) xor (a and c) xor (b and c)
|
||||||
|
let maj = UInt32::sha256_maj(
|
||||||
|
cs.namespace(|| "maj"),
|
||||||
|
&new_a,
|
||||||
|
&b,
|
||||||
|
&c
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// temp2 := S0 + maj
|
||||||
|
let temp2 = vec![s0, maj];
|
||||||
|
|
||||||
|
/*
|
||||||
|
h := g
|
||||||
|
g := f
|
||||||
|
f := e
|
||||||
|
e := d + temp1
|
||||||
|
d := c
|
||||||
|
c := b
|
||||||
|
b := a
|
||||||
|
a := temp1 + temp2
|
||||||
|
*/
|
||||||
|
|
||||||
|
h = g;
|
||||||
|
g = f;
|
||||||
|
f = new_e;
|
||||||
|
e = Maybe::Deferred(temp1.iter().cloned().chain(Some(d)).collect::<Vec<_>>());
|
||||||
|
d = c;
|
||||||
|
c = b;
|
||||||
|
b = new_a;
|
||||||
|
a = Maybe::Deferred(temp1.iter().cloned().chain(temp2.iter().cloned()).collect::<Vec<_>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the compressed chunk to the current hash value:
|
||||||
|
h0 := h0 + a
|
||||||
|
h1 := h1 + b
|
||||||
|
h2 := h2 + c
|
||||||
|
h3 := h3 + d
|
||||||
|
h4 := h4 + e
|
||||||
|
h5 := h5 + f
|
||||||
|
h6 := h6 + g
|
||||||
|
h7 := h7 + h
|
||||||
|
*/
|
||||||
|
|
||||||
|
let h0 = a.compute(
|
||||||
|
cs.namespace(|| "deferred h0 computation"),
|
||||||
|
&[current_hash_value[0].clone()]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h1 = UInt32::addmany(
|
||||||
|
cs.namespace(|| "new h1"),
|
||||||
|
&[current_hash_value[1].clone(), b]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h2 = UInt32::addmany(
|
||||||
|
cs.namespace(|| "new h2"),
|
||||||
|
&[current_hash_value[2].clone(), c]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h3 = UInt32::addmany(
|
||||||
|
cs.namespace(|| "new h3"),
|
||||||
|
&[current_hash_value[3].clone(), d]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h4 = e.compute(
|
||||||
|
cs.namespace(|| "deferred h4 computation"),
|
||||||
|
&[current_hash_value[4].clone()]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h5 = UInt32::addmany(
|
||||||
|
cs.namespace(|| "new h5"),
|
||||||
|
&[current_hash_value[5].clone(), f]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h6 = UInt32::addmany(
|
||||||
|
cs.namespace(|| "new h6"),
|
||||||
|
&[current_hash_value[6].clone(), g]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let h7 = UInt32::addmany(
|
||||||
|
cs.namespace(|| "new h7"),
|
||||||
|
&[current_hash_value[7].clone(), h]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(vec![h0, h1, h2, h3, h4, h5, h6, h7])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::gadgets::boolean::AllocatedBit;
|
||||||
|
use pairing::bls12_381::Bls12;
|
||||||
|
use crate::gadgets::test::TestConstraintSystem;
|
||||||
|
use rand_core::{RngCore, SeedableRng};
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blank_hash() {
|
||||||
|
let iv = get_sha256_iv();
|
||||||
|
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let mut input_bits: Vec<_> = (0..512).map(|_| Boolean::Constant(false)).collect();
|
||||||
|
input_bits[0] = Boolean::Constant(true);
|
||||||
|
let out = sha256_compression_function(
|
||||||
|
&mut cs,
|
||||||
|
&input_bits,
|
||||||
|
&iv
|
||||||
|
).unwrap();
|
||||||
|
let out_bits: Vec<_> = out.into_iter().flat_map(|e| e.into_bits_be()).collect();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 0);
|
||||||
|
|
||||||
|
let expected = hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
|
||||||
|
|
||||||
|
let mut out = out_bits.into_iter();
|
||||||
|
for b in expected.into_iter() {
|
||||||
|
for i in (0..8).rev() {
|
||||||
|
let c = out.next().unwrap().get_value().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(c, (b >> i) & 1u8 == 1u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_full_block() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let iv = get_sha256_iv();
|
||||||
|
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let input_bits: Vec<_> = (0..512).map(|i| {
|
||||||
|
Boolean::from(
|
||||||
|
AllocatedBit::alloc(
|
||||||
|
cs.namespace(|| format!("input bit {}", i)),
|
||||||
|
Some(rng.next_u32() % 2 != 0)
|
||||||
|
).unwrap()
|
||||||
|
)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
sha256_compression_function(
|
||||||
|
cs.namespace(|| "sha256"),
|
||||||
|
&input_bits,
|
||||||
|
&iv
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints() - 512, 25840);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_against_vectors() {
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0))
|
||||||
|
{
|
||||||
|
let mut h = Sha256::new();
|
||||||
|
let data: Vec<u8> = (0..input_len).map(|_| rng.next_u32() as u8).collect();
|
||||||
|
h.input(&data);
|
||||||
|
let hash_result = h.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 = sha256(&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());
|
||||||
|
},
|
||||||
|
Boolean::Constant(b) => {
|
||||||
|
assert!(input_len == 0);
|
||||||
|
assert!(s.next().unwrap() == b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,488 @@
|
||||||
|
use ff::{Field, PrimeField, PrimeFieldRepr};
|
||||||
|
use pairing::Engine;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
LinearCombination,
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem,
|
||||||
|
Variable,
|
||||||
|
Index
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use blake2s_simd::{Params as Blake2sParams, State as Blake2sState};
|
||||||
|
|
||||||
|
#[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<E>,
|
||||||
|
LinearCombination<E>,
|
||||||
|
LinearCombination<E>,
|
||||||
|
String
|
||||||
|
)>,
|
||||||
|
inputs: Vec<(E::Fr, String)>,
|
||||||
|
aux: Vec<(E::Fr, String)>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct OrderedVariable(Variable);
|
||||||
|
|
||||||
|
impl Eq for OrderedVariable {}
|
||||||
|
impl PartialEq for OrderedVariable {
|
||||||
|
fn eq(&self, other: &OrderedVariable) -> bool {
|
||||||
|
match (self.0.get_unchecked(), other.0.get_unchecked()) {
|
||||||
|
(Index::Input(ref a), Index::Input(ref b)) => a == b,
|
||||||
|
(Index::Aux(ref a), Index::Aux(ref b)) => a == b,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialOrd for OrderedVariable {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Ord for OrderedVariable {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
match (self.0.get_unchecked(), other.0.get_unchecked()) {
|
||||||
|
(Index::Input(ref a), Index::Input(ref b)) => a.cmp(b),
|
||||||
|
(Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b),
|
||||||
|
(Index::Input(_), Index::Aux(_)) => Ordering::Less,
|
||||||
|
(Index::Aux(_), Index::Input(_)) => Ordering::Greater
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proc_lc<E: Engine>(
|
||||||
|
terms: &[(Variable, E::Fr)],
|
||||||
|
) -> BTreeMap<OrderedVariable, E::Fr>
|
||||||
|
{
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
for &(var, coeff) in terms {
|
||||||
|
map.entry(OrderedVariable(var))
|
||||||
|
.or_insert(E::Fr::zero())
|
||||||
|
.add_assign(&coeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove terms that have a zero coefficient to normalize
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
for (var, coeff) in map.iter() {
|
||||||
|
if coeff.is_zero() {
|
||||||
|
to_remove.push(var.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for var in to_remove {
|
||||||
|
map.remove(&var);
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_lc<E: Engine>(
|
||||||
|
terms: &[(Variable, E::Fr)],
|
||||||
|
h: &mut Blake2sState
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let map = proc_lc::<E>(terms);
|
||||||
|
|
||||||
|
let mut buf = [0u8; 9 + 32];
|
||||||
|
BigEndian::write_u64(&mut buf[0..8], map.len() as u64);
|
||||||
|
h.update(&buf[0..8]);
|
||||||
|
|
||||||
|
for (var, coeff) in map {
|
||||||
|
match var.0.get_unchecked() {
|
||||||
|
Index::Input(i) => {
|
||||||
|
buf[0] = b'I';
|
||||||
|
BigEndian::write_u64(&mut buf[1..9], i as u64);
|
||||||
|
},
|
||||||
|
Index::Aux(i) => {
|
||||||
|
buf[0] = b'A';
|
||||||
|
BigEndian::write_u64(&mut buf[1..9], i as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coeff.into_repr().write_be(&mut buf[9..]).unwrap();
|
||||||
|
|
||||||
|
h.update(&buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.get_unchecked() {
|
||||||
|
Index::Input(index) => inputs[index].0,
|
||||||
|
Index::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(TestConstraintSystem::<E>::one()));
|
||||||
|
|
||||||
|
TestConstraintSystem {
|
||||||
|
named_objects: map,
|
||||||
|
current_namespace: vec![],
|
||||||
|
constraints: vec![],
|
||||||
|
inputs: vec![(E::Fr::one(), "ONE".into())],
|
||||||
|
aux: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pretty_print(&self) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
|
||||||
|
let negone = {
|
||||||
|
let mut tmp = E::Fr::one();
|
||||||
|
tmp.negate();
|
||||||
|
tmp
|
||||||
|
};
|
||||||
|
|
||||||
|
let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| {
|
||||||
|
E::Fr::from_str("2").unwrap().pow(&[i as u64])
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let pp = |s: &mut String, lc: &LinearCombination<E>| {
|
||||||
|
write!(s, "(").unwrap();
|
||||||
|
let mut is_first = true;
|
||||||
|
for (var, coeff) in proc_lc::<E>(lc.as_ref()) {
|
||||||
|
if coeff == negone {
|
||||||
|
write!(s, " - ").unwrap();
|
||||||
|
} else if !is_first {
|
||||||
|
write!(s, " + ").unwrap();
|
||||||
|
}
|
||||||
|
is_first = false;
|
||||||
|
|
||||||
|
if coeff != E::Fr::one() && coeff != negone {
|
||||||
|
for (i, x) in powers_of_two.iter().enumerate() {
|
||||||
|
if x == &coeff {
|
||||||
|
write!(s, "2^{} . ", i).unwrap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(s, "{} . ", coeff).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
match var.0.get_unchecked() {
|
||||||
|
Index::Input(i) => {
|
||||||
|
write!(s, "`{}`", &self.inputs[i].1).unwrap();
|
||||||
|
},
|
||||||
|
Index::Aux(i) => {
|
||||||
|
write!(s, "`{}`", &self.aux[i].1).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_first {
|
||||||
|
// Nothing was visited, print 0.
|
||||||
|
write!(s, "0").unwrap();
|
||||||
|
}
|
||||||
|
write!(s, ")").unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
for &(ref a, ref b, ref c, ref name) in &self.constraints {
|
||||||
|
write!(&mut s, "\n").unwrap();
|
||||||
|
|
||||||
|
write!(&mut s, "{}: ", name).unwrap();
|
||||||
|
pp(&mut s, a);
|
||||||
|
write!(&mut s, " * ").unwrap();
|
||||||
|
pp(&mut s, b);
|
||||||
|
write!(&mut s, " = ").unwrap();
|
||||||
|
pp(&mut s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(&mut s, "\n").unwrap();
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(&self) -> String {
|
||||||
|
let mut h = Blake2sParams::new().hash_length(32).to_state();
|
||||||
|
{
|
||||||
|
let mut buf = [0u8; 24];
|
||||||
|
|
||||||
|
BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64);
|
||||||
|
BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64);
|
||||||
|
BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64);
|
||||||
|
h.update(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for constraint in &self.constraints {
|
||||||
|
hash_lc::<E>(constraint.0.as_ref(), &mut h);
|
||||||
|
hash_lc::<E>(constraint.1.as_ref(), &mut h);
|
||||||
|
hash_lc::<E>(constraint.2.as_ref(), &mut h);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = String::new();
|
||||||
|
for b in h.finalize().as_ref() {
|
||||||
|
s += &format!("{:02x}", b);
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
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(ref v)) => {
|
||||||
|
match v.get_unchecked() {
|
||||||
|
Index::Input(index) => self.inputs[index].0 = to,
|
||||||
|
Index::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 verify(&self, expected: &[E::Fr]) -> bool
|
||||||
|
{
|
||||||
|
assert_eq!(expected.len() + 1, self.inputs.len());
|
||||||
|
|
||||||
|
for (a, b) in self.inputs.iter().skip(1).zip(expected.iter())
|
||||||
|
{
|
||||||
|
if &a.0 != b {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_inputs(&self) -> usize {
|
||||||
|
self.inputs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_input(&mut self, index: usize, path: &str) -> E::Fr
|
||||||
|
{
|
||||||
|
let (assignment, name) = self.inputs[index].clone();
|
||||||
|
|
||||||
|
assert_eq!(path, name);
|
||||||
|
|
||||||
|
assignment
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, path: &str) -> E::Fr
|
||||||
|
{
|
||||||
|
match self.named_objects.get(path) {
|
||||||
|
Some(&NamedObject::Var(ref v)) => {
|
||||||
|
match v.get_unchecked() {
|
||||||
|
Index::Input(index) => self.inputs[index].0,
|
||||||
|
Index::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 Root = Self;
|
||||||
|
|
||||||
|
fn alloc<F, A, AR>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
f: F
|
||||||
|
) -> Result<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::new_unchecked(Index::Aux(index));
|
||||||
|
self.set_named_obj(path, NamedObject::Var(var));
|
||||||
|
|
||||||
|
Ok(var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_input<F, A, AR>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
f: F
|
||||||
|
) -> Result<Variable, SynthesisError>
|
||||||
|
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
|
||||||
|
{
|
||||||
|
let index = self.inputs.len();
|
||||||
|
let path = compute_path(&self.current_namespace, annotation().into());
|
||||||
|
self.inputs.push((f()?, path.clone()));
|
||||||
|
let var = Variable::new_unchecked(Index::Input(index));
|
||||||
|
self.set_named_obj(path, NamedObject::Var(var));
|
||||||
|
|
||||||
|
Ok(var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enforce<A, AR, LA, LB, LC>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
a: LA,
|
||||||
|
b: LB,
|
||||||
|
c: LC
|
||||||
|
)
|
||||||
|
where A: FnOnce() -> AR, AR: Into<String>,
|
||||||
|
LA: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
|
||||||
|
LB: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
|
||||||
|
LC: FnOnce(LinearCombination<E>) -> LinearCombination<E>
|
||||||
|
{
|
||||||
|
let path = compute_path(&self.current_namespace, annotation().into());
|
||||||
|
let index = self.constraints.len();
|
||||||
|
self.set_named_obj(path.clone(), NamedObject::Constraint(index));
|
||||||
|
|
||||||
|
let a = a(LinearCombination::zero());
|
||||||
|
let b = b(LinearCombination::zero());
|
||||||
|
let c = c(LinearCombination::zero());
|
||||||
|
|
||||||
|
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 ff::PrimeField;
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
|
||||||
|
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",
|
||||||
|
|lc| lc + a,
|
||||||
|
|lc| lc + b,
|
||||||
|
|lc| lc + c
|
||||||
|
);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 1);
|
||||||
|
|
||||||
|
cs.set("a/var", Fr::from_str("4").unwrap());
|
||||||
|
|
||||||
|
let one = TestConstraintSystem::<Bls12>::one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "eq",
|
||||||
|
|lc| lc + a,
|
||||||
|
|lc| lc + one,
|
||||||
|
|lc| lc + 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,780 @@
|
||||||
|
use ff::{Field, PrimeField};
|
||||||
|
use pairing::Engine;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem,
|
||||||
|
LinearCombination
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::boolean::{
|
||||||
|
Boolean,
|
||||||
|
AllocatedBit
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::multieq::MultiEq;
|
||||||
|
|
||||||
|
/// Represents an interpretation of 32 `Boolean` objects as an
|
||||||
|
/// unsigned integer.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UInt32 {
|
||||||
|
// Least significant bit first
|
||||||
|
bits: Vec<Boolean>,
|
||||||
|
value: Option<u32>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UInt32 {
|
||||||
|
/// 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>
|
||||||
|
{
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bits_be(&self) -> Vec<Boolean> {
|
||||||
|
self.bits.iter().rev().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bits_be(bits: &[Boolean]) -> Self {
|
||||||
|
assert_eq!(bits.len(), 32);
|
||||||
|
|
||||||
|
let mut value = Some(0u32);
|
||||||
|
for b in bits {
|
||||||
|
value.as_mut().map(|v| *v <<= 1);
|
||||||
|
|
||||||
|
match b.get_value() {
|
||||||
|
Some(true) => { value.as_mut().map(|v| *v |= 1); },
|
||||||
|
Some(false) => {},
|
||||||
|
None => { value = None; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt32 {
|
||||||
|
value: value,
|
||||||
|
bits: bits.iter().rev().cloned().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Turns this `UInt32` into its little-endian byte order representation.
|
||||||
|
pub fn into_bits(&self) -> Vec<Boolean> {
|
||||||
|
self.bits.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a little-endian byte order representation of bits into a
|
||||||
|
/// `UInt32`.
|
||||||
|
pub fn from_bits(bits: &[Boolean]) -> Self
|
||||||
|
{
|
||||||
|
assert_eq!(bits.len(), 32);
|
||||||
|
|
||||||
|
let new_bits = bits.to_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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shr(&self, by: usize) -> Self {
|
||||||
|
let by = by % 32;
|
||||||
|
|
||||||
|
let fill = Boolean::constant(false);
|
||||||
|
|
||||||
|
let new_bits = self.bits
|
||||||
|
.iter() // The bits are least significant first
|
||||||
|
.skip(by) // Skip the bits that will be lost during the shift
|
||||||
|
.chain(Some(&fill).into_iter().cycle()) // Rest will be zeros
|
||||||
|
.take(32) // Only 32 bits needed!
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
UInt32 {
|
||||||
|
bits: new_bits,
|
||||||
|
value: self.value.map(|v| v >> by as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn triop<E, CS, F, U>(
|
||||||
|
mut cs: CS,
|
||||||
|
a: &Self,
|
||||||
|
b: &Self,
|
||||||
|
c: &Self,
|
||||||
|
tri_fn: F,
|
||||||
|
circuit_fn: U
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E>,
|
||||||
|
F: Fn(u32, u32, u32) -> u32,
|
||||||
|
U: Fn(&mut CS, usize, &Boolean, &Boolean, &Boolean) -> Result<Boolean, SynthesisError>
|
||||||
|
{
|
||||||
|
let new_value = match (a.value, b.value, c.value) {
|
||||||
|
(Some(a), Some(b), Some(c)) => {
|
||||||
|
Some(tri_fn(a, b, c))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let bits = a.bits.iter()
|
||||||
|
.zip(b.bits.iter())
|
||||||
|
.zip(c.bits.iter())
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, ((a, b), c))| circuit_fn(&mut cs, i, a, b, c))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
Ok(UInt32 {
|
||||||
|
bits: bits,
|
||||||
|
value: new_value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the `maj` value (a and b) xor (a and c) xor (b and c)
|
||||||
|
/// during SHA256.
|
||||||
|
pub fn sha256_maj<E, CS>(
|
||||||
|
cs: CS,
|
||||||
|
a: &Self,
|
||||||
|
b: &Self,
|
||||||
|
c: &Self
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ (a & c) ^ (b & c),
|
||||||
|
|cs, i, a, b, c| {
|
||||||
|
Boolean::sha256_maj(
|
||||||
|
cs.namespace(|| format!("maj {}", i)),
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the `ch` value `(a and b) xor ((not a) and c)`
|
||||||
|
/// during SHA256.
|
||||||
|
pub fn sha256_ch<E, CS>(
|
||||||
|
cs: CS,
|
||||||
|
a: &Self,
|
||||||
|
b: &Self,
|
||||||
|
c: &Self
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E>
|
||||||
|
{
|
||||||
|
Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ ((!a) & c),
|
||||||
|
|cs, i, a, b, c| {
|
||||||
|
Boolean::sha256_ch(
|
||||||
|
cs.namespace(|| format!("ch {}", i)),
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>
|
||||||
|
{
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform modular addition of several `UInt32` objects.
|
||||||
|
pub fn addmany<E, CS, M>(
|
||||||
|
mut cs: M,
|
||||||
|
operands: &[Self]
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E>,
|
||||||
|
M: ConstraintSystem<E, Root=MultiEq<E, CS>>
|
||||||
|
{
|
||||||
|
// 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 equal the
|
||||||
|
// output
|
||||||
|
let mut lc = LinearCombination::zero();
|
||||||
|
|
||||||
|
let mut all_constants = true;
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
lc = lc + &bit.lc(CS::one(), coeff);
|
||||||
|
|
||||||
|
all_constants &= bit.is_constant();
|
||||||
|
|
||||||
|
coeff.double();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The value of the actual result is modulo 2^32
|
||||||
|
let modular_value = result_value.map(|v| v as u32);
|
||||||
|
|
||||||
|
if all_constants && modular_value.is_some() {
|
||||||
|
// We can just return a constant, rather than
|
||||||
|
// unpacking the result into allocated bits.
|
||||||
|
|
||||||
|
return Ok(UInt32::constant(modular_value.unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage area for the resulting bits
|
||||||
|
let mut result_bits = vec![];
|
||||||
|
|
||||||
|
// Linear combination representing the output,
|
||||||
|
// for comparison with the sum of the operands
|
||||||
|
let mut result_lc = LinearCombination::zero();
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Add this bit to the result combination
|
||||||
|
result_lc = result_lc + (coeff, b.get_variable());
|
||||||
|
|
||||||
|
result_bits.push(b.into());
|
||||||
|
|
||||||
|
max_value >>= 1;
|
||||||
|
i += 1;
|
||||||
|
coeff.double();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce equality between the sum and result
|
||||||
|
cs.get_root().enforce_equal(i, &lc, &result_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 crate::gadgets::boolean::{Boolean};
|
||||||
|
use super::{UInt32};
|
||||||
|
use ff::Field;
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use crate::gadgets::test::*;
|
||||||
|
use crate::{ConstraintSystem};
|
||||||
|
use crate::gadgets::multieq::MultiEq;
|
||||||
|
use rand_core::{RngCore, SeedableRng};
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_from_bits_be() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut v = (0..32).map(|_| Boolean::constant(rng.next_u32() % 2 != 0)).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let b = UInt32::from_bits_be(&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_be();
|
||||||
|
|
||||||
|
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_from_bits() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut v = (0..32).map(|_| Boolean::constant(rng.next_u32() % 2 != 0)).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([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a = rng.next_u32();
|
||||||
|
let b = rng.next_u32();
|
||||||
|
let c = rng.next_u32();
|
||||||
|
|
||||||
|
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_constants() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a = rng.next_u32();
|
||||||
|
let b = rng.next_u32();
|
||||||
|
let c = rng.next_u32();
|
||||||
|
|
||||||
|
let a_bit = UInt32::constant(a);
|
||||||
|
let b_bit = UInt32::constant(b);
|
||||||
|
let c_bit = UInt32::constant(c);
|
||||||
|
|
||||||
|
let mut expected = a.wrapping_add(b).wrapping_add(c);
|
||||||
|
|
||||||
|
let r = {
|
||||||
|
let mut cs = MultiEq::new(&mut cs);
|
||||||
|
let r = UInt32::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap();
|
||||||
|
r
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(r.value == Some(expected));
|
||||||
|
|
||||||
|
for b in r.bits.iter() {
|
||||||
|
match b {
|
||||||
|
&Boolean::Is(_) => panic!(),
|
||||||
|
&Boolean::Not(_) => panic!(),
|
||||||
|
&Boolean::Constant(b) => {
|
||||||
|
assert!(b == (expected & 1 == 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_addmany() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a = rng.next_u32();
|
||||||
|
let b = rng.next_u32();
|
||||||
|
let c = rng.next_u32();
|
||||||
|
let d = rng.next_u32();
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
let mut cs = MultiEq::new(&mut cs);
|
||||||
|
let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap();
|
||||||
|
r
|
||||||
|
};
|
||||||
|
|
||||||
|
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(_) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mut num = rng.next_u32();
|
||||||
|
|
||||||
|
let a = UInt32::constant(num);
|
||||||
|
|
||||||
|
for i in 0..32 {
|
||||||
|
let b = a.rotr(i);
|
||||||
|
assert_eq!(a.bits.len(), b.bits.len());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_shr() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..50 {
|
||||||
|
for i in 0..60 {
|
||||||
|
let num = rng.next_u32();
|
||||||
|
let a = UInt32::constant(num).shr(i);
|
||||||
|
let b = UInt32::constant(num.wrapping_shr(i as u32));
|
||||||
|
|
||||||
|
assert_eq!(a.value.unwrap(), num.wrapping_shr(i as u32));
|
||||||
|
|
||||||
|
assert_eq!(a.bits.len(), b.bits.len());
|
||||||
|
for (a, b) in a.bits.iter().zip(b.bits.iter()) {
|
||||||
|
assert_eq!(a.get_value().unwrap(), b.get_value().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_sha256_maj() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a = rng.next_u32();
|
||||||
|
let b = rng.next_u32();
|
||||||
|
let c = rng.next_u32();
|
||||||
|
|
||||||
|
let mut expected = (a & b) ^ (a & c) ^ (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 = UInt32::sha256_maj(&mut cs, &a_bit, &b_bit, &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_sha256_ch() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([
|
||||||
|
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
|
||||||
|
0xe5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a = rng.next_u32();
|
||||||
|
let b = rng.next_u32();
|
||||||
|
let c = rng.next_u32();
|
||||||
|
|
||||||
|
let mut expected = (a & b) ^ ((!a) & 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 = UInt32::sha256_ch(&mut cs, &a_bit, &b_bit, &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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/lib.rs
12
src/lib.rs
|
@ -6,6 +6,7 @@ extern crate rand_core;
|
||||||
|
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate bit_vec;
|
extern crate bit_vec;
|
||||||
|
extern crate blake2s_simd;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
|
||||||
#[cfg(feature = "multicore")]
|
#[cfg(feature = "multicore")]
|
||||||
|
@ -15,9 +16,20 @@ extern crate futures_cpupool;
|
||||||
#[cfg(feature = "multicore")]
|
#[cfg(feature = "multicore")]
|
||||||
extern crate num_cpus;
|
extern crate num_cpus;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate hex_literal;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate rand_xorshift;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate sha2;
|
||||||
|
|
||||||
|
pub mod gadgets;
|
||||||
pub mod multicore;
|
pub mod multicore;
|
||||||
mod multiexp;
|
mod multiexp;
|
||||||
pub mod domain;
|
pub mod domain;
|
||||||
|
|
Loading…
Reference in New Issue