Implementation of fundamental circuitry and primitive Jubjub curve arithmetic.
This commit is contained in:
parent
35314c8771
commit
86619c7334
17
Cargo.toml
17
Cargo.toml
|
@ -8,9 +8,16 @@ name = "sapling"
|
||||||
repository = "https://github.com/zcash/sapling"
|
repository = "https://github.com/zcash/sapling"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bellman = "0.0.5"
|
|
||||||
|
|
||||||
[dependencies.pairing]
|
[dependencies.pairing]
|
||||||
version = "0.13"
|
version = "~0.13.2"
|
||||||
features = ["u128-support"]
|
features = ["expose-arith"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.3"
|
||||||
|
blake2 = "0.7"
|
||||||
|
digest = "0.7"
|
||||||
|
bellman = "0.0.6"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["u128-support"]
|
||||||
|
u128-support = ["pairing/u128-support"]
|
||||||
|
|
|
@ -0,0 +1,374 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bellman::{
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::boolean::{
|
||||||
|
Boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::uint32::{
|
||||||
|
UInt32
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
2.1. Parameters
|
||||||
|
The following table summarizes various parameters and their ranges:
|
||||||
|
| BLAKE2b | BLAKE2s |
|
||||||
|
--------------+------------------+------------------+
|
||||||
|
Bits in word | w = 64 | w = 32 |
|
||||||
|
Rounds in F | r = 12 | r = 10 |
|
||||||
|
Block bytes | bb = 128 | bb = 64 |
|
||||||
|
Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 |
|
||||||
|
Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 |
|
||||||
|
Input bytes | 0 <= ll < 2**128 | 0 <= ll < 2**64 |
|
||||||
|
--------------+------------------+------------------+
|
||||||
|
G Rotation | (R1, R2, R3, R4) | (R1, R2, R3, R4) |
|
||||||
|
constants = | (32, 24, 16, 63) | (16, 12, 8, 7) |
|
||||||
|
--------------+------------------+------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
const R1: usize = 16;
|
||||||
|
const R2: usize = 12;
|
||||||
|
const R3: usize = 8;
|
||||||
|
const R4: usize = 7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Round | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
||||||
|
----------+-------------------------------------------------+
|
||||||
|
SIGMA[0] | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
||||||
|
SIGMA[1] | 14 10 4 8 9 15 13 6 1 12 0 2 11 7 5 3 |
|
||||||
|
SIGMA[2] | 11 8 12 0 5 2 15 13 10 14 3 6 7 1 9 4 |
|
||||||
|
SIGMA[3] | 7 9 3 1 13 12 11 14 2 6 5 10 4 0 15 8 |
|
||||||
|
SIGMA[4] | 9 0 5 7 2 4 10 15 14 1 11 12 6 8 3 13 |
|
||||||
|
SIGMA[5] | 2 12 6 10 0 11 8 3 4 13 7 5 15 14 1 9 |
|
||||||
|
SIGMA[6] | 12 5 1 15 14 13 4 10 0 7 6 3 9 2 8 11 |
|
||||||
|
SIGMA[7] | 13 11 7 14 12 1 3 9 5 0 15 4 8 6 2 10 |
|
||||||
|
SIGMA[8] | 6 15 14 9 11 3 0 8 12 2 13 7 1 4 10 5 |
|
||||||
|
SIGMA[9] | 10 2 8 4 7 6 1 5 15 11 9 14 3 12 13 0 |
|
||||||
|
----------+-------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SIGMA: [[usize; 16]; 10] = [
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
||||||
|
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
||||||
|
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
||||||
|
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
||||||
|
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
||||||
|
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
||||||
|
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
||||||
|
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
||||||
|
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
3.1. Mixing Function G
|
||||||
|
The G primitive function mixes two input words, "x" and "y", into
|
||||||
|
four words indexed by "a", "b", "c", and "d" in the working vector
|
||||||
|
v[0..15]. The full modified vector is returned. The rotation
|
||||||
|
constants (R1, R2, R3, R4) are given in Section 2.1.
|
||||||
|
FUNCTION G( v[0..15], a, b, c, d, x, y )
|
||||||
|
|
|
||||||
|
| v[a] := (v[a] + v[b] + x) mod 2**w
|
||||||
|
| v[d] := (v[d] ^ v[a]) >>> R1
|
||||||
|
| v[c] := (v[c] + v[d]) mod 2**w
|
||||||
|
| v[b] := (v[b] ^ v[c]) >>> R2
|
||||||
|
| v[a] := (v[a] + v[b] + y) mod 2**w
|
||||||
|
| v[d] := (v[d] ^ v[a]) >>> R3
|
||||||
|
| v[c] := (v[c] + v[d]) mod 2**w
|
||||||
|
| v[b] := (v[b] ^ v[c]) >>> R4
|
||||||
|
|
|
||||||
|
| RETURN v[0..15]
|
||||||
|
|
|
||||||
|
END FUNCTION.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn mixing_g<E: Engine, CS: ConstraintSystem<E>>(
|
||||||
|
mut cs: CS,
|
||||||
|
v: &mut [UInt32<CS::Variable>],
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
c: usize,
|
||||||
|
d: usize,
|
||||||
|
x: &UInt32<CS::Variable>,
|
||||||
|
y: &UInt32<CS::Variable>
|
||||||
|
) -> Result<(), SynthesisError>
|
||||||
|
{
|
||||||
|
v[a] = UInt32::addmany(cs.namespace(|| "mixing step 1"), &[v[a].clone(), v[b].clone(), x.clone()])?;
|
||||||
|
v[d] = v[d].xor(cs.namespace(|| "mixing step 2"), &v[a])?.rotr(R1);
|
||||||
|
v[c] = UInt32::addmany(cs.namespace(|| "mixing step 3"), &[v[c].clone(), v[d].clone()])?;
|
||||||
|
v[b] = v[b].xor(cs.namespace(|| "mixing step 4"), &v[c])?.rotr(R2);
|
||||||
|
v[a] = UInt32::addmany(cs.namespace(|| "mixing step 5"), &[v[a].clone(), v[b].clone(), y.clone()])?;
|
||||||
|
v[d] = v[d].xor(cs.namespace(|| "mixing step 6"), &v[a])?.rotr(R3);
|
||||||
|
v[c] = UInt32::addmany(cs.namespace(|| "mixing step 7"), &[v[c].clone(), v[d].clone()])?;
|
||||||
|
v[b] = v[b].xor(cs.namespace(|| "mixing step 8"), &v[c])?.rotr(R4);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
3.2. Compression Function F
|
||||||
|
Compression function F takes as an argument the state vector "h",
|
||||||
|
message block vector "m" (last block is padded with zeros to full
|
||||||
|
block size, if required), 2w-bit offset counter "t", and final block
|
||||||
|
indicator flag "f". Local vector v[0..15] is used in processing. F
|
||||||
|
returns a new state vector. The number of rounds, "r", is 12 for
|
||||||
|
BLAKE2b and 10 for BLAKE2s. Rounds are numbered from 0 to r - 1.
|
||||||
|
FUNCTION F( h[0..7], m[0..15], t, f )
|
||||||
|
|
|
||||||
|
| // Initialize local work vector v[0..15]
|
||||||
|
| v[0..7] := h[0..7] // First half from state.
|
||||||
|
| v[8..15] := IV[0..7] // Second half from IV.
|
||||||
|
|
|
||||||
|
| v[12] := v[12] ^ (t mod 2**w) // Low word of the offset.
|
||||||
|
| v[13] := v[13] ^ (t >> w) // High word.
|
||||||
|
|
|
||||||
|
| IF f = TRUE THEN // last block flag?
|
||||||
|
| | v[14] := v[14] ^ 0xFF..FF // Invert all bits.
|
||||||
|
| END IF.
|
||||||
|
|
|
||||||
|
| // Cryptographic mixing
|
||||||
|
| FOR i = 0 TO r - 1 DO // Ten or twelve rounds.
|
||||||
|
| |
|
||||||
|
| | // Message word selection permutation for this round.
|
||||||
|
| | s[0..15] := SIGMA[i mod 10][0..15]
|
||||||
|
| |
|
||||||
|
| | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] )
|
||||||
|
| | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] )
|
||||||
|
| | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] )
|
||||||
|
| | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] )
|
||||||
|
| |
|
||||||
|
| | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] )
|
||||||
|
| | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] )
|
||||||
|
| | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] )
|
||||||
|
| | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] )
|
||||||
|
| |
|
||||||
|
| END FOR
|
||||||
|
|
|
||||||
|
| FOR i = 0 TO 7 DO // XOR the two halves.
|
||||||
|
| | h[i] := h[i] ^ v[i] ^ v[i + 8]
|
||||||
|
| END FOR.
|
||||||
|
|
|
||||||
|
| RETURN h[0..7] // New state.
|
||||||
|
|
|
||||||
|
END FUNCTION.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
fn blake2s_compression<E: Engine, CS: ConstraintSystem<E>>(
|
||||||
|
mut cs: CS,
|
||||||
|
h: &mut [UInt32<CS::Variable>],
|
||||||
|
m: &[UInt32<CS::Variable>],
|
||||||
|
t: u64,
|
||||||
|
f: bool
|
||||||
|
) -> Result<(), SynthesisError>
|
||||||
|
{
|
||||||
|
assert_eq!(h.len(), 8);
|
||||||
|
assert_eq!(m.len(), 16);
|
||||||
|
|
||||||
|
/*
|
||||||
|
static const uint32_t blake2s_iv[8] =
|
||||||
|
{
|
||||||
|
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
|
||||||
|
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
let mut v = Vec::with_capacity(16);
|
||||||
|
v.extend_from_slice(h);
|
||||||
|
v.push(UInt32::constant(0x6A09E667));
|
||||||
|
v.push(UInt32::constant(0xBB67AE85));
|
||||||
|
v.push(UInt32::constant(0x3C6EF372));
|
||||||
|
v.push(UInt32::constant(0xA54FF53A));
|
||||||
|
v.push(UInt32::constant(0x510E527F));
|
||||||
|
v.push(UInt32::constant(0x9B05688C));
|
||||||
|
v.push(UInt32::constant(0x1F83D9AB));
|
||||||
|
v.push(UInt32::constant(0x5BE0CD19));
|
||||||
|
|
||||||
|
assert_eq!(v.len(), 16);
|
||||||
|
|
||||||
|
v[12] = v[12].xor(cs.namespace(|| "first xor"), &UInt32::constant(t as u32))?;
|
||||||
|
v[13] = v[13].xor(cs.namespace(|| "second xor"), &UInt32::constant((t >> 32) as u32))?;
|
||||||
|
|
||||||
|
if f {
|
||||||
|
v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..10 {
|
||||||
|
let mut cs = cs.namespace(|| format!("round {}", i));
|
||||||
|
|
||||||
|
let s = SIGMA[i % 10];
|
||||||
|
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 1"), &mut v, 0, 4, 8, 12, &m[s[ 0]], &m[s[ 1]])?;
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 2"), &mut v, 1, 5, 9, 13, &m[s[ 2]], &m[s[ 3]])?;
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 3"), &mut v, 2, 6, 10, 14, &m[s[ 4]], &m[s[ 5]])?;
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 4"), &mut v, 3, 7, 11, 15, &m[s[ 6]], &m[s[ 7]])?;
|
||||||
|
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 5"), &mut v, 0, 5, 10, 15, &m[s[ 8]], &m[s[ 9]])?;
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 6"), &mut v, 1, 6, 11, 12, &m[s[10]], &m[s[11]])?;
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 7"), &mut v, 2, 7, 8, 13, &m[s[12]], &m[s[13]])?;
|
||||||
|
mixing_g(cs.namespace(|| "mixing invocation 8"), &mut v, 3, 4, 9, 14, &m[s[14]], &m[s[15]])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
let mut cs = cs.namespace(|| format!("h[{i}] ^ v[{i}] ^ v[{i} + 8]", i=i));
|
||||||
|
|
||||||
|
h[i] = h[i].xor(cs.namespace(|| "first xor"), &v[i])?;
|
||||||
|
h[i] = h[i].xor(cs.namespace(|| "second xor"), &v[i + 8])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FUNCTION BLAKE2( d[0..dd-1], ll, kk, nn )
|
||||||
|
|
|
||||||
|
| h[0..7] := IV[0..7] // Initialization Vector.
|
||||||
|
|
|
||||||
|
| // Parameter block p[0]
|
||||||
|
| h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn
|
||||||
|
|
|
||||||
|
| // Process padded key and data blocks
|
||||||
|
| IF dd > 1 THEN
|
||||||
|
| | FOR i = 0 TO dd - 2 DO
|
||||||
|
| | | h := F( h, d[i], (i + 1) * bb, FALSE )
|
||||||
|
| | END FOR.
|
||||||
|
| END IF.
|
||||||
|
|
|
||||||
|
| // Final block.
|
||||||
|
| IF kk = 0 THEN
|
||||||
|
| | h := F( h, d[dd - 1], ll, TRUE )
|
||||||
|
| ELSE
|
||||||
|
| | h := F( h, d[dd - 1], ll + bb, TRUE )
|
||||||
|
| END IF.
|
||||||
|
|
|
||||||
|
| RETURN first "nn" bytes from little-endian word array h[].
|
||||||
|
|
|
||||||
|
END FUNCTION.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub fn blake2s<E: Engine, CS: ConstraintSystem<E>>(
|
||||||
|
mut cs: CS,
|
||||||
|
input: &[Boolean<CS::Variable>]
|
||||||
|
) -> Result<Vec<Boolean<CS::Variable>>, SynthesisError>
|
||||||
|
{
|
||||||
|
assert!(input.len() % 8 == 0);
|
||||||
|
|
||||||
|
let mut h = Vec::with_capacity(8);
|
||||||
|
h.push(UInt32::constant(0x6A09E667 ^ 0x01010000 ^ 32));
|
||||||
|
h.push(UInt32::constant(0xBB67AE85));
|
||||||
|
h.push(UInt32::constant(0x3C6EF372));
|
||||||
|
h.push(UInt32::constant(0xA54FF53A));
|
||||||
|
h.push(UInt32::constant(0x510E527F));
|
||||||
|
h.push(UInt32::constant(0x9B05688C));
|
||||||
|
h.push(UInt32::constant(0x1F83D9AB));
|
||||||
|
h.push(UInt32::constant(0x5BE0CD19));
|
||||||
|
|
||||||
|
let mut blocks: Vec<Vec<UInt32<CS::Variable>>> = vec![];
|
||||||
|
|
||||||
|
for block in input.chunks(512) {
|
||||||
|
let mut this_block = Vec::with_capacity(16);
|
||||||
|
for word in block.chunks(32) {
|
||||||
|
let mut tmp = word.to_vec();
|
||||||
|
while tmp.len() < 32 {
|
||||||
|
tmp.push(Boolean::constant(false));
|
||||||
|
}
|
||||||
|
this_block.push(UInt32::from_bits(&tmp));
|
||||||
|
}
|
||||||
|
while this_block.len() < 16 {
|
||||||
|
this_block.push(UInt32::constant(0));
|
||||||
|
}
|
||||||
|
blocks.push(this_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if blocks.len() == 0 {
|
||||||
|
blocks.push((0..16).map(|_| UInt32::constant(0)).collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, block) in blocks[0..blocks.len() - 1].iter().enumerate() {
|
||||||
|
let cs = cs.namespace(|| format!("block {}", i));
|
||||||
|
|
||||||
|
blake2s_compression(cs, &mut h, block, ((i as u64) + 1) * 64, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let cs = cs.namespace(|| "final block");
|
||||||
|
|
||||||
|
blake2s_compression(cs, &mut h, &blocks[blocks.len() - 1], (input.len() / 8) as u64, true)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(h.iter().flat_map(|b| b.into_bits()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use rand::{XorShiftRng, SeedableRng, Rng};
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use ::circuit::boolean::{Boolean, AllocatedBit};
|
||||||
|
use ::circuit::test::TestConstraintSystem;
|
||||||
|
use super::blake2s;
|
||||||
|
use bellman::{ConstraintSystem};
|
||||||
|
use blake2::{Blake2s};
|
||||||
|
use digest::{FixedOutput, Input};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blake2s_constraints() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let input_bits: Vec<_> = (0..512).map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()).collect();
|
||||||
|
blake2s(&mut cs, &input_bits).unwrap();
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 21792);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blake2s() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
|
||||||
|
for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0))
|
||||||
|
{
|
||||||
|
let mut h = Blake2s::new_keyed(&[], 32);
|
||||||
|
|
||||||
|
let data: Vec<u8> = (0..input_len).map(|_| rng.gen()).collect();
|
||||||
|
|
||||||
|
h.process(&data);
|
||||||
|
|
||||||
|
let hash_result = h.fixed_result();
|
||||||
|
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let mut input_bits = vec![];
|
||||||
|
|
||||||
|
for (byte_i, input_byte) in data.into_iter().enumerate() {
|
||||||
|
for bit_i in (0..8).rev() {
|
||||||
|
let cs = cs.namespace(|| format!("input bit {} {}", byte_i, bit_i));
|
||||||
|
|
||||||
|
input_bits.push(AllocatedBit::alloc(cs, Some((input_byte >> bit_i) & 1u8 == 1u8)).unwrap().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = blake2s(&mut cs, &input_bits).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let mut s = hash_result.as_ref().iter()
|
||||||
|
.flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8));
|
||||||
|
|
||||||
|
for b in r {
|
||||||
|
match b {
|
||||||
|
Boolean::Is(b) => {
|
||||||
|
assert!(s.next().unwrap() == b.get_value().unwrap());
|
||||||
|
},
|
||||||
|
Boolean::Not(b) => {
|
||||||
|
assert!(s.next().unwrap() != b.get_value().unwrap());
|
||||||
|
},
|
||||||
|
_ => panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,476 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
Field
|
||||||
|
};
|
||||||
|
|
||||||
|
use bellman::{
|
||||||
|
ConstraintSystem,
|
||||||
|
SynthesisError,
|
||||||
|
LinearCombination
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Assignment
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a variable in the constraint system which is guaranteed
|
||||||
|
/// to be either zero or one.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AllocatedBit<Var> {
|
||||||
|
variable: Var,
|
||||||
|
value: Option<bool>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Var: Copy> AllocatedBit<Var> {
|
||||||
|
pub fn get_value(&self) -> Option<bool> {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_variable(&self) -> Var {
|
||||||
|
self.variable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a variable in the constraint system which can only be a
|
||||||
|
/// boolean value.
|
||||||
|
pub fn alloc<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
value: Option<bool>,
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
let var = cs.alloc(|| "boolean", || {
|
||||||
|
if *value.get()? {
|
||||||
|
Ok(E::Fr::one())
|
||||||
|
} else {
|
||||||
|
Ok(E::Fr::zero())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Constrain: (1 - a) * a = 0
|
||||||
|
// This constrains a to be either 0 or 1.
|
||||||
|
let one = cs.one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "boolean constraint",
|
||||||
|
LinearCombination::zero() + one - var,
|
||||||
|
LinearCombination::zero() + var,
|
||||||
|
LinearCombination::zero()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(AllocatedBit {
|
||||||
|
variable: var,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an XOR operation over the two operands, returning
|
||||||
|
/// an `AllocatedBit`.
|
||||||
|
pub fn xor<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
a: &Self,
|
||||||
|
b: &Self
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
let mut result_value = None;
|
||||||
|
|
||||||
|
let result_var = cs.alloc(|| "xor result", || {
|
||||||
|
if *a.value.get()? ^ *b.value.get()? {
|
||||||
|
result_value = Some(true);
|
||||||
|
|
||||||
|
Ok(E::Fr::one())
|
||||||
|
} else {
|
||||||
|
result_value = Some(false);
|
||||||
|
|
||||||
|
Ok(E::Fr::zero())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Constrain (a + a) * (b) = (a + b - c)
|
||||||
|
// Given that a and b are boolean constrained, if they
|
||||||
|
// are equal, the only solution for c is 0, and if they
|
||||||
|
// are different, the only solution for c is 1.
|
||||||
|
//
|
||||||
|
// ¬(a ∧ b) ∧ ¬(¬a ∧ ¬b) = c
|
||||||
|
// (1 - (a * b)) * (1 - ((1 - a) * (1 - b))) = c
|
||||||
|
// (1 - ab) * (1 - (1 - a - b + ab)) = c
|
||||||
|
// (1 - ab) * (a + b - ab) = c
|
||||||
|
// a + b - ab - (a^2)b - (b^2)a + (a^2)(b^2) = c
|
||||||
|
// a + b - ab - ab - ab + ab = c
|
||||||
|
// a + b - 2ab = c
|
||||||
|
// -2a * b = c - a - b
|
||||||
|
// 2a * b = a + b - c
|
||||||
|
// (a + a) * b = a + b - c
|
||||||
|
cs.enforce(
|
||||||
|
|| "xor constraint",
|
||||||
|
LinearCombination::zero() + a.variable + a.variable,
|
||||||
|
LinearCombination::zero() + b.variable,
|
||||||
|
LinearCombination::zero() + a.variable + b.variable - result_var
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(AllocatedBit {
|
||||||
|
variable: result_var,
|
||||||
|
value: result_value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an AND operation over the two operands, returning
|
||||||
|
/// an `AllocatedBit`.
|
||||||
|
pub fn and<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
a: &Self,
|
||||||
|
b: &Self
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
let mut result_value = None;
|
||||||
|
|
||||||
|
let result_var = cs.alloc(|| "and result", || {
|
||||||
|
if *a.value.get()? & *b.value.get()? {
|
||||||
|
result_value = Some(true);
|
||||||
|
|
||||||
|
Ok(E::Fr::one())
|
||||||
|
} else {
|
||||||
|
result_value = Some(false);
|
||||||
|
|
||||||
|
Ok(E::Fr::zero())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Constrain (a) * (b) = (c), ensuring c is 1 iff
|
||||||
|
// a AND b are both 1.
|
||||||
|
cs.enforce(
|
||||||
|
|| "and constraint",
|
||||||
|
LinearCombination::zero() + a.variable,
|
||||||
|
LinearCombination::zero() + b.variable,
|
||||||
|
LinearCombination::zero() + result_var
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(AllocatedBit {
|
||||||
|
variable: result_var,
|
||||||
|
value: result_value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a boolean value which may be either a constant or
|
||||||
|
/// an interpretation of an `AllocatedBit`.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Boolean<Var> {
|
||||||
|
/// Existential view of the boolean variable
|
||||||
|
Is(AllocatedBit<Var>),
|
||||||
|
/// Negated view of the boolean variable
|
||||||
|
Not(AllocatedBit<Var>),
|
||||||
|
/// Constant (not an allocated variable)
|
||||||
|
Constant(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Var: Copy> Boolean<Var> {
|
||||||
|
/// Construct a boolean from a known constant
|
||||||
|
pub fn constant(b: bool) -> Self {
|
||||||
|
Boolean::Constant(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a negated interpretation of this boolean.
|
||||||
|
pub fn not(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
&Boolean::Constant(c) => Boolean::Constant(!c),
|
||||||
|
&Boolean::Is(ref v) => Boolean::Not(v.clone()),
|
||||||
|
&Boolean::Not(ref v) => Boolean::Is(v.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform XOR over two boolean operands
|
||||||
|
pub fn xor<'a, E, CS>(
|
||||||
|
cs: CS,
|
||||||
|
a: &'a Self,
|
||||||
|
b: &'a Self
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
match (a, b) {
|
||||||
|
(&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(x.clone()),
|
||||||
|
(&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.not()),
|
||||||
|
// a XOR (NOT b) = NOT(a XOR b)
|
||||||
|
(is @ &Boolean::Is(_), not @ &Boolean::Not(_)) | (not @ &Boolean::Not(_), is @ &Boolean::Is(_)) => {
|
||||||
|
Ok(Boolean::xor(
|
||||||
|
cs,
|
||||||
|
is,
|
||||||
|
¬.not()
|
||||||
|
)?.not())
|
||||||
|
},
|
||||||
|
// a XOR b = (NOT a) XOR (NOT b)
|
||||||
|
(&Boolean::Is(ref a), &Boolean::Is(ref b)) | (&Boolean::Not(ref a), &Boolean::Not(ref b)) => {
|
||||||
|
Ok(Boolean::Is(AllocatedBit::xor(cs, a, b)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Var> From<AllocatedBit<Var>> for Boolean<Var> {
|
||||||
|
fn from(b: AllocatedBit<Var>) -> Boolean<Var> {
|
||||||
|
Boolean::Is(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use bellman::{ConstraintSystem};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use pairing::{Field, PrimeField};
|
||||||
|
use ::circuit::test::*;
|
||||||
|
use super::{AllocatedBit, Boolean};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_allocated_bit() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
AllocatedBit::alloc(&mut cs, Some(true)).unwrap();
|
||||||
|
assert!(cs.get("boolean") == Fr::one());
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
cs.set("boolean", Fr::zero());
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
cs.set("boolean", Fr::from_str("2").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
assert!(cs.which_is_unsatisfied() == Some("boolean constraint"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_xor() {
|
||||||
|
for a_val in [false, true].iter() {
|
||||||
|
for b_val in [false, true].iter() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||||
|
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||||
|
AllocatedBit::xor(&mut cs, &a, &b).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||||
|
assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() });
|
||||||
|
assert!(cs.get("xor result") == if *a_val ^ *b_val { Field::one() } else { Field::zero() });
|
||||||
|
|
||||||
|
// Invert the result and check if the constraint system is still satisfied
|
||||||
|
cs.set("xor result", if *a_val ^ *b_val { Field::zero() } else { Field::one() });
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_and() {
|
||||||
|
for a_val in [false, true].iter() {
|
||||||
|
for b_val in [false, true].iter() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||||
|
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||||
|
AllocatedBit::and(&mut cs, &a, &b).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||||
|
assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() });
|
||||||
|
assert!(cs.get("and result") == if *a_val & *b_val { Field::one() } else { Field::zero() });
|
||||||
|
|
||||||
|
// Invert the result and check if the constraint system is still satisfied
|
||||||
|
cs.set("and result", if *a_val & *b_val { Field::zero() } else { Field::one() });
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_boolean_negation() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let mut b = Boolean::from(AllocatedBit::alloc(&mut cs, Some(true)).unwrap());
|
||||||
|
|
||||||
|
match b {
|
||||||
|
Boolean::Is(_) => {},
|
||||||
|
_ => panic!("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = b.not();
|
||||||
|
|
||||||
|
match b {
|
||||||
|
Boolean::Not(_) => {},
|
||||||
|
_ => panic!("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = b.not();
|
||||||
|
|
||||||
|
match b {
|
||||||
|
Boolean::Is(_) => {},
|
||||||
|
_ => panic!("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = Boolean::constant(true);
|
||||||
|
|
||||||
|
match b {
|
||||||
|
Boolean::Constant(true) => {},
|
||||||
|
_ => panic!("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = b.not();
|
||||||
|
|
||||||
|
match b {
|
||||||
|
Boolean::Constant(false) => {},
|
||||||
|
_ => panic!("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = b.not();
|
||||||
|
|
||||||
|
match b {
|
||||||
|
Boolean::Constant(true) => {},
|
||||||
|
_ => panic!("unexpected value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_boolean_xor() {
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum OperandType {
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
AllocatedTrue,
|
||||||
|
AllocatedFalse,
|
||||||
|
NegatedAllocatedTrue,
|
||||||
|
NegatedAllocatedFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
let variants = [
|
||||||
|
OperandType::True,
|
||||||
|
OperandType::False,
|
||||||
|
OperandType::AllocatedTrue,
|
||||||
|
OperandType::AllocatedFalse,
|
||||||
|
OperandType::NegatedAllocatedTrue,
|
||||||
|
OperandType::NegatedAllocatedFalse
|
||||||
|
];
|
||||||
|
|
||||||
|
for first_operand in variants.iter().cloned() {
|
||||||
|
for second_operand in variants.iter().cloned() {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a;
|
||||||
|
let b;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut dyn_construct = |operand, name| {
|
||||||
|
let cs = cs.namespace(|| name);
|
||||||
|
|
||||||
|
match operand {
|
||||||
|
OperandType::True => Boolean::constant(true),
|
||||||
|
OperandType::False => Boolean::constant(false),
|
||||||
|
OperandType::AllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()),
|
||||||
|
OperandType::AllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()),
|
||||||
|
OperandType::NegatedAllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()).not(),
|
||||||
|
OperandType::NegatedAllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()).not(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
a = dyn_construct(first_operand, "a");
|
||||||
|
b = dyn_construct(second_operand, "b");
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = Boolean::xor(&mut cs, &a, &b).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
match (first_operand, second_operand, c) {
|
||||||
|
(OperandType::True, OperandType::True, Boolean::Constant(false)) => {},
|
||||||
|
(OperandType::True, OperandType::False, Boolean::Constant(true)) => {},
|
||||||
|
(OperandType::True, OperandType::AllocatedTrue, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::True, OperandType::AllocatedFalse, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::True, OperandType::NegatedAllocatedTrue, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::True, OperandType::NegatedAllocatedFalse, Boolean::Is(_)) => {},
|
||||||
|
|
||||||
|
(OperandType::False, OperandType::True, Boolean::Constant(true)) => {},
|
||||||
|
(OperandType::False, OperandType::False, Boolean::Constant(false)) => {},
|
||||||
|
(OperandType::False, OperandType::AllocatedTrue, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::False, OperandType::AllocatedFalse, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {},
|
||||||
|
|
||||||
|
(OperandType::AllocatedTrue, OperandType::True, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::AllocatedTrue, OperandType::False, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::AllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
(OperandType::AllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
(OperandType::AllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
(OperandType::AllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
|
||||||
|
(OperandType::AllocatedFalse, OperandType::True, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::AllocatedFalse, OperandType::False, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::AllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
(OperandType::AllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
(OperandType::AllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
(OperandType::AllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
|
||||||
|
(OperandType::NegatedAllocatedTrue, OperandType::True, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::NegatedAllocatedTrue, OperandType::AllocatedTrue, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
(OperandType::NegatedAllocatedTrue, OperandType::AllocatedFalse, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
(OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
(OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
|
||||||
|
(OperandType::NegatedAllocatedFalse, OperandType::True, Boolean::Is(_)) => {},
|
||||||
|
(OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Not(_)) => {},
|
||||||
|
(OperandType::NegatedAllocatedFalse, OperandType::AllocatedTrue, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
(OperandType::NegatedAllocatedFalse, OperandType::AllocatedFalse, Boolean::Not(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
(OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::one());
|
||||||
|
assert_eq!(v.value, Some(true));
|
||||||
|
},
|
||||||
|
(OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||||
|
assert!(cs.get("xor result") == Field::zero());
|
||||||
|
assert_eq!(v.value, Some(false));
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => panic!("this should never be encountered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test;
|
||||||
|
|
||||||
|
pub mod boolean;
|
||||||
|
pub mod uint32;
|
||||||
|
pub mod blake2s;
|
||||||
|
|
||||||
|
use bellman::SynthesisError;
|
||||||
|
|
||||||
|
trait Assignment<T> {
|
||||||
|
fn get(&self) -> Result<&T, SynthesisError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Assignment<T> for Option<T> {
|
||||||
|
fn get(&self) -> Result<&T, SynthesisError> {
|
||||||
|
match *self {
|
||||||
|
Some(ref v) => Ok(v),
|
||||||
|
None => Err(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
Field
|
||||||
|
};
|
||||||
|
|
||||||
|
use bellman::{
|
||||||
|
LinearCombination,
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Variable {
|
||||||
|
Input(usize),
|
||||||
|
Aux(usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum NamedObject {
|
||||||
|
Constraint(usize),
|
||||||
|
Var(Variable),
|
||||||
|
Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constraint system for testing purposes.
|
||||||
|
pub struct TestConstraintSystem<E: Engine> {
|
||||||
|
named_objects: HashMap<String, NamedObject>,
|
||||||
|
current_namespace: Vec<String>,
|
||||||
|
constraints: Vec<(LinearCombination<Variable, E>, LinearCombination<Variable, E>, LinearCombination<Variable, E>, String)>,
|
||||||
|
inputs: Vec<(E::Fr, String)>,
|
||||||
|
aux: Vec<(E::Fr, String)>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_lc<E: Engine>(
|
||||||
|
terms: &[(Variable, E::Fr)],
|
||||||
|
inputs: &[(E::Fr, String)],
|
||||||
|
aux: &[(E::Fr, String)]
|
||||||
|
) -> E::Fr
|
||||||
|
{
|
||||||
|
let mut acc = E::Fr::zero();
|
||||||
|
|
||||||
|
for &(var, ref coeff) in terms {
|
||||||
|
let mut tmp = match var {
|
||||||
|
Variable::Input(index) => inputs[index].0,
|
||||||
|
Variable::Aux(index) => aux[index].0
|
||||||
|
};
|
||||||
|
|
||||||
|
tmp.mul_assign(&coeff);
|
||||||
|
acc.add_assign(&tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> TestConstraintSystem<E> {
|
||||||
|
pub fn new() -> TestConstraintSystem<E> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("ONE".into(), NamedObject::Var(Variable::Input(0)));
|
||||||
|
|
||||||
|
TestConstraintSystem {
|
||||||
|
named_objects: map,
|
||||||
|
current_namespace: vec![],
|
||||||
|
constraints: vec![],
|
||||||
|
inputs: vec![(E::Fr::one(), "ONE".into())],
|
||||||
|
aux: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn which_is_unsatisfied(&self) -> Option<&str> {
|
||||||
|
for &(ref a, ref b, ref c, ref path) in &self.constraints {
|
||||||
|
let mut a = eval_lc::<E>(a.as_ref(), &self.inputs, &self.aux);
|
||||||
|
let b = eval_lc::<E>(b.as_ref(), &self.inputs, &self.aux);
|
||||||
|
let c = eval_lc::<E>(c.as_ref(), &self.inputs, &self.aux);
|
||||||
|
|
||||||
|
a.mul_assign(&b);
|
||||||
|
|
||||||
|
if a != c {
|
||||||
|
return Some(&*path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_satisfied(&self) -> bool
|
||||||
|
{
|
||||||
|
self.which_is_unsatisfied().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_constraints(&self) -> usize
|
||||||
|
{
|
||||||
|
self.constraints.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, path: &str, to: E::Fr)
|
||||||
|
{
|
||||||
|
match self.named_objects.get(path) {
|
||||||
|
Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0 = to,
|
||||||
|
Some(&NamedObject::Var(Variable::Aux(index))) => self.aux[index].0 = to,
|
||||||
|
Some(e) => panic!("tried to set path `{}` to value, but `{:?}` already exists there.", path, e),
|
||||||
|
_ => panic!("no variable exists at path: {}", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, path: &str) -> E::Fr
|
||||||
|
{
|
||||||
|
match self.named_objects.get(path) {
|
||||||
|
Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0,
|
||||||
|
Some(&NamedObject::Var(Variable::Aux(index))) => self.aux[index].0,
|
||||||
|
Some(e) => panic!("tried to get value of path `{}`, but `{:?}` exists there (not a variable)", path, e),
|
||||||
|
_ => panic!("no variable exists at path: {}", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_named_obj(&mut self, path: String, to: NamedObject) {
|
||||||
|
if self.named_objects.contains_key(&path) {
|
||||||
|
panic!("tried to create object at existing path: {}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.named_objects.insert(path, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_path(ns: &[String], this: String) -> String {
|
||||||
|
if this.chars().any(|a| a == '/') {
|
||||||
|
panic!("'/' is not allowed in names");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
let mut needs_separation = false;
|
||||||
|
for ns in ns.iter().chain(Some(&this).into_iter())
|
||||||
|
{
|
||||||
|
if needs_separation {
|
||||||
|
name += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
name += ns;
|
||||||
|
needs_separation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> ConstraintSystem<E> for TestConstraintSystem<E> {
|
||||||
|
type Variable = Variable;
|
||||||
|
type Root = Self;
|
||||||
|
|
||||||
|
fn one(&self) -> Self::Variable {
|
||||||
|
Variable::Input(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc<F, A, AR>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
f: F
|
||||||
|
) -> Result<Self::Variable, SynthesisError>
|
||||||
|
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
|
||||||
|
{
|
||||||
|
let index = self.aux.len();
|
||||||
|
let path = compute_path(&self.current_namespace, annotation().into());
|
||||||
|
self.aux.push((f()?, path.clone()));
|
||||||
|
let var = Variable::Aux(index);
|
||||||
|
self.set_named_obj(path, NamedObject::Var(var));
|
||||||
|
|
||||||
|
Ok(var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enforce<A, AR>(
|
||||||
|
&mut self,
|
||||||
|
annotation: A,
|
||||||
|
a: LinearCombination<Self::Variable, E>,
|
||||||
|
b: LinearCombination<Self::Variable, E>,
|
||||||
|
c: LinearCombination<Self::Variable, E>
|
||||||
|
)
|
||||||
|
where A: FnOnce() -> AR, AR: Into<String>
|
||||||
|
{
|
||||||
|
let path = compute_path(&self.current_namespace, annotation().into());
|
||||||
|
let index = self.constraints.len();
|
||||||
|
self.set_named_obj(path.clone(), NamedObject::Constraint(index));
|
||||||
|
|
||||||
|
self.constraints.push((a, b, c, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_namespace<NR, N>(&mut self, name_fn: N)
|
||||||
|
where NR: Into<String>, N: FnOnce() -> NR
|
||||||
|
{
|
||||||
|
let name = name_fn().into();
|
||||||
|
let path = compute_path(&self.current_namespace, name.clone());
|
||||||
|
self.set_named_obj(path.clone(), NamedObject::Namespace);
|
||||||
|
self.current_namespace.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_namespace(&mut self)
|
||||||
|
{
|
||||||
|
assert!(self.current_namespace.pop().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_root(&mut self) -> &mut Self::Root
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cs() {
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use pairing::PrimeField;
|
||||||
|
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 0);
|
||||||
|
let a = cs.namespace(|| "a").alloc(|| "var", || Ok(Fr::from_str("10").unwrap())).unwrap();
|
||||||
|
let b = cs.namespace(|| "b").alloc(|| "var", || Ok(Fr::from_str("4").unwrap())).unwrap();
|
||||||
|
let c = cs.alloc(|| "product", || Ok(Fr::from_str("40").unwrap())).unwrap();
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "mult",
|
||||||
|
LinearCombination::zero() + a,
|
||||||
|
LinearCombination::zero() + b,
|
||||||
|
LinearCombination::zero() + c
|
||||||
|
);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 1);
|
||||||
|
|
||||||
|
cs.set("a/var", Fr::from_str("4").unwrap());
|
||||||
|
|
||||||
|
let one = cs.one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "eq",
|
||||||
|
LinearCombination::zero() + a,
|
||||||
|
LinearCombination::zero() + one,
|
||||||
|
LinearCombination::zero() + b
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
assert!(cs.which_is_unsatisfied() == Some("mult"));
|
||||||
|
|
||||||
|
assert!(cs.get("product") == Fr::from_str("40").unwrap());
|
||||||
|
|
||||||
|
cs.set("product", Fr::from_str("16").unwrap());
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cs = cs.namespace(|| "test1");
|
||||||
|
let mut cs = cs.namespace(|| "test2");
|
||||||
|
cs.alloc(|| "hehe", || Ok(Fr::one())).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(cs.get("test1/test2/hehe") == Fr::one());
|
||||||
|
}
|
|
@ -0,0 +1,451 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
Field,
|
||||||
|
PrimeField
|
||||||
|
};
|
||||||
|
|
||||||
|
use bellman::{
|
||||||
|
SynthesisError,
|
||||||
|
ConstraintSystem,
|
||||||
|
LinearCombination
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::boolean::{
|
||||||
|
Boolean,
|
||||||
|
AllocatedBit
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents an interpretation of 32 `Boolean` objects as an
|
||||||
|
/// unsigned integer.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UInt32<Var> {
|
||||||
|
// Least significant bit first
|
||||||
|
bits: Vec<Boolean<Var>>,
|
||||||
|
value: Option<u32>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Var: Copy> UInt32<Var> {
|
||||||
|
/// Construct a constant `UInt32` from a `u32`
|
||||||
|
pub fn constant(value: u32) -> Self
|
||||||
|
{
|
||||||
|
let mut bits = Vec::with_capacity(32);
|
||||||
|
|
||||||
|
let mut tmp = value;
|
||||||
|
for _ in 0..32 {
|
||||||
|
if tmp & 1 == 1 {
|
||||||
|
bits.push(Boolean::constant(true))
|
||||||
|
} else {
|
||||||
|
bits.push(Boolean::constant(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt32 {
|
||||||
|
bits: bits,
|
||||||
|
value: Some(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a `UInt32` in the constraint system
|
||||||
|
pub fn alloc<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
value: Option<u32>
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
let values = match value {
|
||||||
|
Some(mut val) => {
|
||||||
|
let mut v = Vec::with_capacity(32);
|
||||||
|
|
||||||
|
for _ in 0..32 {
|
||||||
|
v.push(Some(val & 1 == 1));
|
||||||
|
val >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
},
|
||||||
|
None => vec![None; 32]
|
||||||
|
};
|
||||||
|
|
||||||
|
let bits = values.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, v)| {
|
||||||
|
Ok(Boolean::from(AllocatedBit::alloc(cs.namespace(|| format!("allocated bit {}", i)), v)?))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, SynthesisError>>()?;
|
||||||
|
|
||||||
|
Ok(UInt32 {
|
||||||
|
bits: bits,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns this `UInt32` into its little-endian byte order representation.
|
||||||
|
pub fn into_bits(&self) -> Vec<Boolean<Var>> {
|
||||||
|
self.bits.chunks(8)
|
||||||
|
.flat_map(|v| v.iter().rev())
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a little-endian byte order representation of bits into a
|
||||||
|
/// `UInt32`.
|
||||||
|
pub fn from_bits(bits: &[Boolean<Var>]) -> Self
|
||||||
|
{
|
||||||
|
assert_eq!(bits.len(), 32);
|
||||||
|
|
||||||
|
let new_bits = bits.chunks(8)
|
||||||
|
.flat_map(|v| v.iter().rev())
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut value = Some(0u32);
|
||||||
|
for b in new_bits.iter().rev() {
|
||||||
|
value.as_mut().map(|v| *v <<= 1);
|
||||||
|
|
||||||
|
match b {
|
||||||
|
&Boolean::Constant(b) => {
|
||||||
|
if b {
|
||||||
|
value.as_mut().map(|v| *v |= 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&Boolean::Is(ref b) => {
|
||||||
|
match b.get_value() {
|
||||||
|
Some(true) => { value.as_mut().map(|v| *v |= 1); },
|
||||||
|
Some(false) => {},
|
||||||
|
None => { value = None }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&Boolean::Not(ref b) => {
|
||||||
|
match b.get_value() {
|
||||||
|
Some(false) => { value.as_mut().map(|v| *v |= 1); },
|
||||||
|
Some(true) => {},
|
||||||
|
None => { value = None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt32 {
|
||||||
|
value: value,
|
||||||
|
bits: new_bits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotr(&self, by: usize) -> Self {
|
||||||
|
let by = by % 32;
|
||||||
|
|
||||||
|
let new_bits = self.bits.iter()
|
||||||
|
.skip(by)
|
||||||
|
.chain(self.bits.iter())
|
||||||
|
.take(32)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
UInt32 {
|
||||||
|
bits: new_bits,
|
||||||
|
value: self.value.map(|v| v.rotate_right(by as u32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XOR this `UInt32` with another `UInt32`
|
||||||
|
pub fn xor<E, CS>(
|
||||||
|
&self,
|
||||||
|
mut cs: CS,
|
||||||
|
other: &Self
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
let new_value = match (self.value, other.value) {
|
||||||
|
(Some(a), Some(b)) => {
|
||||||
|
Some(a ^ b)
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let bits = self.bits.iter()
|
||||||
|
.zip(other.bits.iter())
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, (a, b))| {
|
||||||
|
Boolean::xor(cs.namespace(|| format!("xor of bit {}", i)), a, b)
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
Ok(UInt32 {
|
||||||
|
bits: bits,
|
||||||
|
value: new_value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: could optimize
|
||||||
|
/// Perform modular addition of several `UInt32` objects.
|
||||||
|
pub fn addmany<E, CS>(
|
||||||
|
mut cs: CS,
|
||||||
|
operands: &[Self]
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where E: Engine,
|
||||||
|
CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
// Make some arbitrary bounds for ourselves to avoid overflows
|
||||||
|
// in the scalar field
|
||||||
|
assert!(E::Fr::NUM_BITS >= 64);
|
||||||
|
assert!(operands.len() >= 2); // Weird trivial cases that should never happen
|
||||||
|
assert!(operands.len() <= 10);
|
||||||
|
|
||||||
|
// Compute the maximum value of the sum so we allocate enough bits for
|
||||||
|
// the result
|
||||||
|
let mut max_value = (operands.len() as u64) * (u32::max_value() as u64);
|
||||||
|
|
||||||
|
// Keep track of the resulting value
|
||||||
|
let mut result_value = Some(0u64);
|
||||||
|
|
||||||
|
// This is a linear combination that we will enforce to be "zero"
|
||||||
|
let mut lc = LinearCombination::zero();
|
||||||
|
|
||||||
|
// Iterate over the operands
|
||||||
|
for op in operands {
|
||||||
|
// Accumulate the value
|
||||||
|
match op.value {
|
||||||
|
Some(val) => {
|
||||||
|
result_value.as_mut().map(|v| *v += val as u64);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// If any of our operands have unknown value, we won't
|
||||||
|
// know the value of the result
|
||||||
|
result_value = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over each bit of the operand and add the operand to
|
||||||
|
// the linear combination
|
||||||
|
let mut coeff = E::Fr::one();
|
||||||
|
for bit in &op.bits {
|
||||||
|
match bit {
|
||||||
|
&Boolean::Is(ref bit) => {
|
||||||
|
// Add coeff * bit
|
||||||
|
lc = lc + (coeff, bit.get_variable());
|
||||||
|
},
|
||||||
|
&Boolean::Not(ref bit) => {
|
||||||
|
// Add coeff * (1 - bit) = coeff * ONE - coeff * bit
|
||||||
|
lc = lc + (coeff, cs.one()) - (coeff, bit.get_variable());
|
||||||
|
},
|
||||||
|
&Boolean::Constant(bit) => {
|
||||||
|
if bit {
|
||||||
|
lc = lc + (coeff, cs.one());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coeff.double();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The value of the actual result is modulo 2^32
|
||||||
|
let modular_value = result_value.map(|v| v as u32);
|
||||||
|
|
||||||
|
// Storage area for the resulting bits
|
||||||
|
let mut result_bits = vec![];
|
||||||
|
|
||||||
|
// Allocate each bit of the result
|
||||||
|
let mut coeff = E::Fr::one();
|
||||||
|
let mut i = 0;
|
||||||
|
while max_value != 0 {
|
||||||
|
// Allocate the bit
|
||||||
|
let b = AllocatedBit::alloc(cs.namespace(|| format!("result bit {}", i)), result_value.map(|v| (v >> i) & 1 == 1))?;
|
||||||
|
|
||||||
|
// Subtract this bit from the linear combination to ensure the sums balance out
|
||||||
|
lc = lc - (coeff, b.get_variable());
|
||||||
|
|
||||||
|
result_bits.push(b.into());
|
||||||
|
|
||||||
|
max_value >>= 1;
|
||||||
|
i += 1;
|
||||||
|
coeff.double();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce that the linear combination equals zero
|
||||||
|
cs.enforce(
|
||||||
|
|| "modular addition",
|
||||||
|
LinearCombination::zero(),
|
||||||
|
LinearCombination::zero(),
|
||||||
|
lc
|
||||||
|
);
|
||||||
|
|
||||||
|
// Discard carry bits that we don't care about
|
||||||
|
result_bits.truncate(32);
|
||||||
|
|
||||||
|
Ok(UInt32 {
|
||||||
|
bits: result_bits,
|
||||||
|
value: modular_value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use rand::{XorShiftRng, SeedableRng, Rng};
|
||||||
|
use ::circuit::boolean::{Boolean};
|
||||||
|
use super::{UInt32};
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use pairing::{Field};
|
||||||
|
use ::circuit::test::*;
|
||||||
|
use bellman::{ConstraintSystem};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_from_bits() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut v = (0..32).map(|_| Boolean::<()>::constant(rng.gen())).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let b = UInt32::from_bits(&v);
|
||||||
|
|
||||||
|
for (i, bit) in b.bits.iter().enumerate() {
|
||||||
|
match bit {
|
||||||
|
&Boolean::Constant(bit) => {
|
||||||
|
assert!(bit == ((b.value.unwrap() >> i) & 1 == 1));
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_to_be_same = b.into_bits();
|
||||||
|
|
||||||
|
for x in v.iter().zip(expected_to_be_same.iter())
|
||||||
|
{
|
||||||
|
match x {
|
||||||
|
(&Boolean::Constant(true), &Boolean::Constant(true)) => {},
|
||||||
|
(&Boolean::Constant(false), &Boolean::Constant(false)) => {},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_xor() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a: u32 = rng.gen();
|
||||||
|
let b: u32 = rng.gen();
|
||||||
|
let c: u32 = rng.gen();
|
||||||
|
|
||||||
|
let mut expected = a ^ b ^ c;
|
||||||
|
|
||||||
|
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
|
||||||
|
let b_bit = UInt32::constant(b);
|
||||||
|
let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap();
|
||||||
|
|
||||||
|
let r = a_bit.xor(cs.namespace(|| "first xor"), &b_bit).unwrap();
|
||||||
|
let r = r.xor(cs.namespace(|| "second xor"), &c_bit).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
assert!(r.value == Some(expected));
|
||||||
|
|
||||||
|
for b in r.bits.iter() {
|
||||||
|
match b {
|
||||||
|
&Boolean::Is(ref b) => {
|
||||||
|
assert!(b.get_value().unwrap() == (expected & 1 == 1));
|
||||||
|
},
|
||||||
|
&Boolean::Not(ref b) => {
|
||||||
|
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
|
||||||
|
},
|
||||||
|
&Boolean::Constant(b) => {
|
||||||
|
assert!(b == (expected & 1 == 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_addmany() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let a: u32 = rng.gen();
|
||||||
|
let b: u32 = rng.gen();
|
||||||
|
let c: u32 = rng.gen();
|
||||||
|
let d: u32 = rng.gen();
|
||||||
|
|
||||||
|
let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d);
|
||||||
|
|
||||||
|
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
|
||||||
|
let b_bit = UInt32::constant(b);
|
||||||
|
let c_bit = UInt32::constant(c);
|
||||||
|
let d_bit = UInt32::alloc(cs.namespace(|| "d_bit"), Some(d)).unwrap();
|
||||||
|
|
||||||
|
let r = a_bit.xor(cs.namespace(|| "xor"), &b_bit).unwrap();
|
||||||
|
let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
assert!(r.value == Some(expected));
|
||||||
|
|
||||||
|
for b in r.bits.iter() {
|
||||||
|
match b {
|
||||||
|
&Boolean::Is(ref b) => {
|
||||||
|
assert!(b.get_value().unwrap() == (expected & 1 == 1));
|
||||||
|
},
|
||||||
|
&Boolean::Not(ref b) => {
|
||||||
|
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
|
||||||
|
},
|
||||||
|
&Boolean::Constant(b) => {
|
||||||
|
assert!(b == (expected & 1 == 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flip a bit and see if the addition constraint still works
|
||||||
|
if cs.get("addition/result bit 0/boolean").is_zero() {
|
||||||
|
cs.set("addition/result bit 0/boolean", Field::one());
|
||||||
|
} else {
|
||||||
|
cs.set("addition/result bit 0/boolean", Field::zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uint32_rotr() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
|
||||||
|
let mut num = rng.gen();
|
||||||
|
|
||||||
|
let a = UInt32::<()>::constant(num);
|
||||||
|
|
||||||
|
for i in 0..32 {
|
||||||
|
let b = a.rotr(i);
|
||||||
|
|
||||||
|
assert!(b.value.unwrap() == num);
|
||||||
|
|
||||||
|
let mut tmp = num;
|
||||||
|
for b in &b.bits {
|
||||||
|
match b {
|
||||||
|
&Boolean::Constant(b) => {
|
||||||
|
assert_eq!(b, tmp & 1 == 1);
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
num = num.rotate_right(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,547 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
Field,
|
||||||
|
SqrtField,
|
||||||
|
PrimeField,
|
||||||
|
PrimeFieldRepr,
|
||||||
|
BitIterator
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
JubjubParams,
|
||||||
|
Unknown,
|
||||||
|
PrimeOrder,
|
||||||
|
Fs,
|
||||||
|
FsRepr,
|
||||||
|
montgomery
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::{
|
||||||
|
Rng
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
// Represents the affine point (X/Z, Y/Z) via the extended
|
||||||
|
// twisted Edwards coordinates.
|
||||||
|
pub struct Point<E: Engine, Subgroup> {
|
||||||
|
x: E::Fr,
|
||||||
|
y: E::Fr,
|
||||||
|
t: E::Fr,
|
||||||
|
z: E::Fr,
|
||||||
|
_marker: PhantomData<Subgroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_subgroup<E: Engine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2>
|
||||||
|
{
|
||||||
|
Point {
|
||||||
|
x: from.x,
|
||||||
|
y: from.y,
|
||||||
|
t: from.t,
|
||||||
|
z: from.z,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
|
||||||
|
{
|
||||||
|
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
|
||||||
|
{
|
||||||
|
convert_subgroup(&p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, Subgroup> Clone for Point<E, Subgroup>
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
convert_subgroup(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, Subgroup> PartialEq for Point<E, Subgroup> {
|
||||||
|
fn eq(&self, other: &Point<E, Subgroup>) -> bool {
|
||||||
|
// p1 = (x1/z1, y1/z1)
|
||||||
|
// p2 = (x2/z2, y2/z2)
|
||||||
|
// Deciding that these two points are equal is a matter of
|
||||||
|
// determining that x1/z1 = x2/z2, or equivalently that
|
||||||
|
// x1*z2 = x2*z1, and similarly for y.
|
||||||
|
|
||||||
|
let mut x1 = self.x;
|
||||||
|
x1.mul_assign(&other.z);
|
||||||
|
|
||||||
|
let mut y1 = self.y;
|
||||||
|
y1.mul_assign(&other.z);
|
||||||
|
|
||||||
|
let mut x2 = other.x;
|
||||||
|
x2.mul_assign(&self.z);
|
||||||
|
|
||||||
|
let mut y2 = other.y;
|
||||||
|
y2.mul_assign(&self.z);
|
||||||
|
|
||||||
|
x1 == x2 && y1 == y2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Point<E, Unknown> {
|
||||||
|
/// This guarantees the point is in the prime order subgroup
|
||||||
|
pub fn mul_by_cofactor(&self, params: &JubjubParams<E>) -> Point<E, PrimeOrder>
|
||||||
|
{
|
||||||
|
let tmp = self.double(params)
|
||||||
|
.double(params)
|
||||||
|
.double(params);
|
||||||
|
|
||||||
|
convert_subgroup(&tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rand<R: Rng>(rng: &mut R, params: &JubjubParams<E>) -> Self
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
// given an x on the curve, y^2 = (1 + x^2) / (1 - dx^2)
|
||||||
|
let x: E::Fr = rng.gen();
|
||||||
|
let mut x2 = x;
|
||||||
|
x2.square();
|
||||||
|
|
||||||
|
let mut num = E::Fr::one();
|
||||||
|
num.add_assign(&x2);
|
||||||
|
|
||||||
|
x2.mul_assign(¶ms.edwards_d);
|
||||||
|
|
||||||
|
let mut den = E::Fr::one();
|
||||||
|
den.sub_assign(&x2);
|
||||||
|
|
||||||
|
match den.inverse() {
|
||||||
|
Some(invden) => {
|
||||||
|
num.mul_assign(&invden);
|
||||||
|
|
||||||
|
match num.sqrt() {
|
||||||
|
Some(mut y) => {
|
||||||
|
if y.into_repr().is_odd() != rng.gen() {
|
||||||
|
y.negate();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut t = x;
|
||||||
|
t.mul_assign(&y);
|
||||||
|
|
||||||
|
return Point {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
t: t,
|
||||||
|
z: E::Fr::one(),
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, Subgroup> Point<E, Subgroup> {
|
||||||
|
/// Convert from a Montgomery point
|
||||||
|
pub fn from_montgomery(
|
||||||
|
m: &montgomery::Point<E, Subgroup>,
|
||||||
|
params: &JubjubParams<E>
|
||||||
|
) -> Self
|
||||||
|
{
|
||||||
|
match m.into_xy() {
|
||||||
|
None => {
|
||||||
|
// Map the point at infinity to the neutral element.
|
||||||
|
Point::zero()
|
||||||
|
},
|
||||||
|
Some((x, y)) => {
|
||||||
|
// The map from a Montgomery curve is defined as:
|
||||||
|
// (x, y) -> (u, v) where
|
||||||
|
// u = x / y
|
||||||
|
// v = (x - 1) / (x + 1)
|
||||||
|
//
|
||||||
|
// This map is not defined for y = 0 and x = -1.
|
||||||
|
//
|
||||||
|
// y = 0 is a valid point only for x = 0:
|
||||||
|
// y^2 = x^3 + A.x^2 + x
|
||||||
|
// 0 = x^3 + A.x^2 + x
|
||||||
|
// 0 = x(x^2 + A.x + 1)
|
||||||
|
// We have: x = 0 OR x^2 + A.x + 1 = 0
|
||||||
|
// x^2 + A.x + 1 = 0
|
||||||
|
// (2.x + A)^2 = A^2 - 4 (Complete the square.)
|
||||||
|
// The left hand side is a square, and so if A^2 - 4
|
||||||
|
// is nonsquare, there is no solution. Indeed, A^2 - 4
|
||||||
|
// is nonsquare.
|
||||||
|
//
|
||||||
|
// (0, 0) is a point of order 2, and so we map it to
|
||||||
|
// (0, -1) in the twisted Edwards curve, which is the
|
||||||
|
// only point of order 2 that is not the neutral element.
|
||||||
|
if y.is_zero() {
|
||||||
|
// This must be the point (0, 0) as above.
|
||||||
|
let mut neg1 = E::Fr::one();
|
||||||
|
neg1.negate();
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: E::Fr::zero(),
|
||||||
|
y: neg1,
|
||||||
|
t: E::Fr::zero(),
|
||||||
|
z: E::Fr::one(),
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, as stated above, the mapping is still
|
||||||
|
// not defined at x = -1. However, x = -1 is not
|
||||||
|
// on the curve when A - 2 is nonsquare:
|
||||||
|
// y^2 = x^3 + A.x^2 + x
|
||||||
|
// y^2 = (-1) + A + (-1)
|
||||||
|
// y^2 = A - 2
|
||||||
|
// Indeed, A - 2 is nonsquare.
|
||||||
|
|
||||||
|
let mut u = x;
|
||||||
|
u.mul_assign(&y.inverse().expect("y is nonzero"));
|
||||||
|
|
||||||
|
let mut v = x;
|
||||||
|
v.sub_assign(&E::Fr::one());
|
||||||
|
{
|
||||||
|
let mut tmp = x;
|
||||||
|
tmp.add_assign(&E::Fr::one());
|
||||||
|
v.mul_assign(&tmp.inverse().expect("A - 2 is nonsquare"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resulting x-coordinate needs to be scaled.
|
||||||
|
u.mul_assign(¶ms.scale);
|
||||||
|
|
||||||
|
let mut t = u;
|
||||||
|
t.mul_assign(&v);
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: u,
|
||||||
|
y: v,
|
||||||
|
t: t,
|
||||||
|
z: E::Fr::one(),
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to cast this as a prime order element, failing if it's
|
||||||
|
/// not in the prime order subgroup.
|
||||||
|
pub fn as_prime_order(&self, params: &JubjubParams<E>) -> Option<Point<E, PrimeOrder>> {
|
||||||
|
if self.mul(Fs::char(), params) == Point::zero() {
|
||||||
|
Some(convert_subgroup(self))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Point {
|
||||||
|
x: E::Fr::zero(),
|
||||||
|
y: E::Fr::one(),
|
||||||
|
t: E::Fr::zero(),
|
||||||
|
z: E::Fr::one(),
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_xy(&self) -> (E::Fr, E::Fr)
|
||||||
|
{
|
||||||
|
let zinv = self.z.inverse().unwrap();
|
||||||
|
|
||||||
|
let mut x = self.x;
|
||||||
|
x.mul_assign(&zinv);
|
||||||
|
|
||||||
|
let mut y = self.y;
|
||||||
|
y.mul_assign(&zinv);
|
||||||
|
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn negate(&self) -> Self {
|
||||||
|
let mut p = self.clone();
|
||||||
|
|
||||||
|
p.x.negate();
|
||||||
|
p.t.negate();
|
||||||
|
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn double(&self, params: &JubjubParams<E>) -> Self {
|
||||||
|
self.add(self, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&self, other: &Self, params: &JubjubParams<E>) -> Self
|
||||||
|
{
|
||||||
|
// A = x1 * x2
|
||||||
|
let mut a = self.x;
|
||||||
|
a.mul_assign(&other.x);
|
||||||
|
|
||||||
|
// B = y1 * y2
|
||||||
|
let mut b = self.y;
|
||||||
|
b.mul_assign(&other.y);
|
||||||
|
|
||||||
|
// C = d * t1 * t2
|
||||||
|
let mut c = params.edwards_d;
|
||||||
|
c.mul_assign(&self.t);
|
||||||
|
c.mul_assign(&other.t);
|
||||||
|
|
||||||
|
// D = z1 * z2
|
||||||
|
let mut d = self.z;
|
||||||
|
d.mul_assign(&other.z);
|
||||||
|
|
||||||
|
// H = B - aA
|
||||||
|
// = B + A
|
||||||
|
let mut h = b;
|
||||||
|
h.add_assign(&a);
|
||||||
|
|
||||||
|
// E = (x1 + y1) * (x2 + y2) - A - B
|
||||||
|
// = (x1 + y1) * (x2 + y2) - H
|
||||||
|
let mut e = self.x;
|
||||||
|
e.add_assign(&self.y);
|
||||||
|
{
|
||||||
|
let mut tmp = other.x;
|
||||||
|
tmp.add_assign(&other.y);
|
||||||
|
e.mul_assign(&tmp);
|
||||||
|
}
|
||||||
|
e.sub_assign(&h);
|
||||||
|
|
||||||
|
// F = D - C
|
||||||
|
let mut f = d;
|
||||||
|
f.sub_assign(&c);
|
||||||
|
|
||||||
|
// G = D + C
|
||||||
|
let mut g = d;
|
||||||
|
g.add_assign(&c);
|
||||||
|
|
||||||
|
// x3 = E * F
|
||||||
|
let mut x3 = e;
|
||||||
|
x3.mul_assign(&f);
|
||||||
|
|
||||||
|
// y3 = G * H
|
||||||
|
let mut y3 = g;
|
||||||
|
y3.mul_assign(&h);
|
||||||
|
|
||||||
|
// t3 = E * H
|
||||||
|
let mut t3 = e;
|
||||||
|
t3.mul_assign(&h);
|
||||||
|
|
||||||
|
// z3 = F * G
|
||||||
|
let mut z3 = f;
|
||||||
|
z3.mul_assign(&g);
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: x3,
|
||||||
|
y: y3,
|
||||||
|
t: t3,
|
||||||
|
z: z3,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul<S: Into<FsRepr>>(&self, scalar: S, params: &JubjubParams<E>) -> Self
|
||||||
|
{
|
||||||
|
let mut res = Self::zero();
|
||||||
|
|
||||||
|
for b in BitIterator::new(scalar.into()) {
|
||||||
|
res = res.double(params);
|
||||||
|
|
||||||
|
if b {
|
||||||
|
res = res.add(self, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use rand::{XorShiftRng, SeedableRng, Rand};
|
||||||
|
use super::{JubjubParams, Point, PrimeOrder, Fs};
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use pairing::{Engine, Field};
|
||||||
|
|
||||||
|
fn is_on_curve<E: Engine>(
|
||||||
|
x: E::Fr,
|
||||||
|
y: E::Fr,
|
||||||
|
params: &JubjubParams<E>
|
||||||
|
) -> bool
|
||||||
|
{
|
||||||
|
let mut x2 = x;
|
||||||
|
x2.square();
|
||||||
|
|
||||||
|
let mut y2 = y;
|
||||||
|
y2.square();
|
||||||
|
|
||||||
|
// -x^2 + y^2
|
||||||
|
let mut lhs = y2;
|
||||||
|
lhs.sub_assign(&x2);
|
||||||
|
|
||||||
|
// 1 + d x^2 y^2
|
||||||
|
let mut rhs = y2;
|
||||||
|
rhs.mul_assign(&x2);
|
||||||
|
rhs.mul_assign(¶ms.edwards_d);
|
||||||
|
rhs.add_assign(&E::Fr::one());
|
||||||
|
|
||||||
|
lhs == rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rand() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let (x, y) = Point::rand(&mut rng, ¶ms).into_xy();
|
||||||
|
|
||||||
|
assert!(is_on_curve(x, y, ¶ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_identities() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
let z = Point::<Bls12, PrimeOrder>::zero();
|
||||||
|
assert!(z.double(¶ms) == z);
|
||||||
|
assert!(z.negate() == z);
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let r = Point::rand(&mut rng, ¶ms);
|
||||||
|
|
||||||
|
assert!(r.add(&Point::zero(), ¶ms) == r);
|
||||||
|
assert!(r.add(&r.negate(), ¶ms) == Point::zero());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_associativity() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let a = Point::rand(&mut rng, ¶ms);
|
||||||
|
let b = Point::rand(&mut rng, ¶ms);
|
||||||
|
let c = Point::rand(&mut rng, ¶ms);
|
||||||
|
|
||||||
|
assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_order() {
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
// The neutral element is in the prime order subgroup.
|
||||||
|
assert!(Point::<Bls12, PrimeOrder>::zero().as_prime_order(params).is_some());
|
||||||
|
|
||||||
|
for _ in 0..50 {
|
||||||
|
// Pick a random point and multiply it by the cofactor
|
||||||
|
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||||
|
|
||||||
|
// Any point multiplied by the cofactor will be in the prime
|
||||||
|
// order subgroup
|
||||||
|
assert!(base.as_prime_order(params).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's very likely that at least one out of 50 random points on the curve
|
||||||
|
// is not in the prime order subgroup.
|
||||||
|
let mut at_least_one_not_in_prime_order_subgroup = false;
|
||||||
|
for _ in 0..50 {
|
||||||
|
// Pick a random point.
|
||||||
|
let base = Point::rand(rng, params);
|
||||||
|
|
||||||
|
at_least_one_not_in_prime_order_subgroup |= base.as_prime_order(params).is_none();
|
||||||
|
}
|
||||||
|
assert!(at_least_one_not_in_prime_order_subgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul_associativity() {
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
// Pick a random point and multiply it by the cofactor
|
||||||
|
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||||
|
|
||||||
|
let mut a = Fs::rand(rng);
|
||||||
|
let b = Fs::rand(rng);
|
||||||
|
let c = Fs::rand(rng);
|
||||||
|
|
||||||
|
let res1 = base.mul(a, params).mul(b, params).mul(c, params);
|
||||||
|
let res2 = base.mul(b, params).mul(c, params).mul(a, params);
|
||||||
|
let res3 = base.mul(c, params).mul(a, params).mul(b, params);
|
||||||
|
a.mul_assign(&b);
|
||||||
|
a.mul_assign(&c);
|
||||||
|
let res4 = base.mul(a, params);
|
||||||
|
|
||||||
|
assert!(res1 == res2);
|
||||||
|
assert!(res2 == res3);
|
||||||
|
assert!(res3 == res4);
|
||||||
|
|
||||||
|
let (x, y) = res1.into_xy();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
|
||||||
|
let (x, y) = res2.into_xy();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
|
||||||
|
let (x, y) = res3.into_xy();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_montgomery_conversion() {
|
||||||
|
use super::montgomery;
|
||||||
|
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..200 {
|
||||||
|
// compute base in montgomery
|
||||||
|
let base = montgomery::Point::rand(rng, params);
|
||||||
|
|
||||||
|
// sample random exponent
|
||||||
|
let exp = Fs::rand(rng);
|
||||||
|
|
||||||
|
// exponentiate in montgomery, convert to edwards
|
||||||
|
let ed_expected = Point::from_montgomery(&base.mul(exp, params), params);
|
||||||
|
|
||||||
|
// convert to edwards and exponentiate
|
||||||
|
let ed_exponentiated = Point::from_montgomery(&base, params).mul(exp, params);
|
||||||
|
|
||||||
|
let (x, y) = ed_expected.into_xy();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
|
||||||
|
assert!(ed_exponentiated == ed_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_back_and_forth() {
|
||||||
|
use super::montgomery;
|
||||||
|
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..200 {
|
||||||
|
// compute base in montgomery
|
||||||
|
let base = montgomery::Point::rand(rng, params);
|
||||||
|
|
||||||
|
// convert to edwards
|
||||||
|
let base_ed = Point::from_montgomery(&base, params);
|
||||||
|
|
||||||
|
{
|
||||||
|
let (x, y) = base_ed.into_xy();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert back to montgomery
|
||||||
|
let base_mont = montgomery::Point::from_edwards(&base_ed, params);
|
||||||
|
|
||||||
|
assert!(base == base_mont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,100 @@
|
||||||
|
//! Jubjub is an elliptic curve defined over the BLS12-381 scalar field, Fr.
|
||||||
|
//! It is a Montgomery curve that takes the form `y^2 = x^3 + Ax^2 + x` where
|
||||||
|
//! `A = 40962`. This is the smallest integer choice of A such that:
|
||||||
|
//!
|
||||||
|
//! * `(A - 2) / 4` is a small integer (`10240`).
|
||||||
|
//! * `A^2 - 4` is quadratic residue.
|
||||||
|
//! * The group order of the curve and its quadratic twist has a large prime factor.
|
||||||
|
//!
|
||||||
|
//! Jubjub has `s = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7`
|
||||||
|
//! as the prime subgroup order, with cofactor 8. (The twist has cofactor 4.)
|
||||||
|
//!
|
||||||
|
//! This curve is birationally equivalent to a twisted Edwards curve of the
|
||||||
|
//! form `-x^2 + y^2 = 1 + dx^2y^2` with `d = -(10240/10241)`. In fact, this equivalence
|
||||||
|
//! forms a group isomorphism, so points can be freely converted between the Montgomery
|
||||||
|
//! and twisted Edwards forms.
|
||||||
|
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
PrimeField
|
||||||
|
};
|
||||||
|
|
||||||
|
use pairing::bls12_381::{
|
||||||
|
Bls12,
|
||||||
|
Fr
|
||||||
|
};
|
||||||
|
|
||||||
|
mod fs;
|
||||||
|
|
||||||
|
pub use self::fs::{Fs, FsRepr};
|
||||||
|
|
||||||
|
pub mod edwards;
|
||||||
|
pub mod montgomery;
|
||||||
|
|
||||||
|
/// These are the pre-computed parameters of the Jubjub
|
||||||
|
/// curve.
|
||||||
|
pub struct JubjubParams<E: Engine> {
|
||||||
|
edwards_d: E::Fr,
|
||||||
|
montgomery_a: E::Fr,
|
||||||
|
|
||||||
|
scale: E::Fr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Unknown { }
|
||||||
|
pub enum PrimeOrder { }
|
||||||
|
|
||||||
|
impl JubjubParams<Bls12> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
JubjubParams {
|
||||||
|
// d = -(10240/10241)
|
||||||
|
edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(),
|
||||||
|
// A = 40962
|
||||||
|
montgomery_a: Fr::from_str("40962").unwrap(),
|
||||||
|
// scaling factor = sqrt(4 / (a - d))
|
||||||
|
scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use pairing::{Field, SqrtField, LegendreSymbol, PrimeField};
|
||||||
|
use pairing::bls12_381::{Fr};
|
||||||
|
use super::JubjubParams;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_params() {
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
// a = -1
|
||||||
|
let mut a = Fr::one();
|
||||||
|
a.negate();
|
||||||
|
|
||||||
|
{
|
||||||
|
// The twisted Edwards addition law is complete when d is nonsquare
|
||||||
|
// and a is square.
|
||||||
|
|
||||||
|
assert!(params.edwards_d.legendre() == LegendreSymbol::QuadraticNonResidue);
|
||||||
|
assert!(a.legendre() == LegendreSymbol::QuadraticResidue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that A^2 - 4 is nonsquare:
|
||||||
|
let mut tmp = params.montgomery_a;
|
||||||
|
tmp.square();
|
||||||
|
tmp.sub_assign(&Fr::from_str("4").unwrap());
|
||||||
|
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
|
||||||
|
|
||||||
|
// Check that A - 2 is nonsquare:
|
||||||
|
let mut tmp = params.montgomery_a;
|
||||||
|
tmp.sub_assign(&Fr::from_str("2").unwrap());
|
||||||
|
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
|
||||||
|
|
||||||
|
// Check the validity of the scaling factor
|
||||||
|
let mut tmp = a;
|
||||||
|
tmp.sub_assign(¶ms.edwards_d);
|
||||||
|
tmp = tmp.inverse().unwrap();
|
||||||
|
tmp.mul_assign(&Fr::from_str("4").unwrap());
|
||||||
|
tmp = tmp.sqrt().unwrap();
|
||||||
|
assert_eq!(tmp, params.scale);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,608 @@
|
||||||
|
use pairing::{
|
||||||
|
Engine,
|
||||||
|
Field,
|
||||||
|
SqrtField,
|
||||||
|
PrimeField,
|
||||||
|
PrimeFieldRepr,
|
||||||
|
BitIterator
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
JubjubParams,
|
||||||
|
Unknown,
|
||||||
|
PrimeOrder,
|
||||||
|
Fs,
|
||||||
|
FsRepr,
|
||||||
|
edwards
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::{
|
||||||
|
Rng
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
// Represents the affine point (X/Z, Y/Z) via the extended
|
||||||
|
// twisted Edwards coordinates.
|
||||||
|
pub struct Point<E: Engine, Subgroup> {
|
||||||
|
x: E::Fr,
|
||||||
|
y: E::Fr,
|
||||||
|
infinity: bool,
|
||||||
|
_marker: PhantomData<Subgroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_subgroup<E: Engine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2>
|
||||||
|
{
|
||||||
|
Point {
|
||||||
|
x: from.x,
|
||||||
|
y: from.y,
|
||||||
|
infinity: from.infinity,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
|
||||||
|
{
|
||||||
|
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
|
||||||
|
{
|
||||||
|
convert_subgroup(&p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, Subgroup> Clone for Point<E, Subgroup>
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
convert_subgroup(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, Subgroup> PartialEq for Point<E, Subgroup> {
|
||||||
|
fn eq(&self, other: &Point<E, Subgroup>) -> bool {
|
||||||
|
match (self.infinity, other.infinity) {
|
||||||
|
(true, true) => true,
|
||||||
|
(true, false) | (false, true) => false,
|
||||||
|
(false, false) => {
|
||||||
|
self.x == other.x && self.y == other.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Point<E, Unknown> {
|
||||||
|
/// This guarantees the point is in the prime order subgroup
|
||||||
|
pub fn mul_by_cofactor(&self, params: &JubjubParams<E>) -> Point<E, PrimeOrder>
|
||||||
|
{
|
||||||
|
let tmp = self.double(params)
|
||||||
|
.double(params)
|
||||||
|
.double(params);
|
||||||
|
|
||||||
|
convert_subgroup(&tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rand<R: Rng>(rng: &mut R, params: &JubjubParams<E>) -> Self
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
// given an x on the curve, y^2 = x^3 + A*x^2 + x
|
||||||
|
let x: E::Fr = rng.gen();
|
||||||
|
|
||||||
|
let mut x2 = x;
|
||||||
|
x2.square();
|
||||||
|
|
||||||
|
let mut rhs = x2;
|
||||||
|
rhs.mul_assign(¶ms.montgomery_a);
|
||||||
|
rhs.add_assign(&x);
|
||||||
|
x2.mul_assign(&x);
|
||||||
|
rhs.add_assign(&x2);
|
||||||
|
|
||||||
|
match rhs.sqrt() {
|
||||||
|
Some(mut y) => {
|
||||||
|
if y.into_repr().is_odd() != rng.gen() {
|
||||||
|
y.negate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Point {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
infinity: false,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine, Subgroup> Point<E, Subgroup> {
|
||||||
|
/// Convert from an Edwards point
|
||||||
|
pub fn from_edwards(
|
||||||
|
e: &edwards::Point<E, Subgroup>,
|
||||||
|
params: &JubjubParams<E>
|
||||||
|
) -> Self
|
||||||
|
{
|
||||||
|
let (x, y) = e.into_xy();
|
||||||
|
|
||||||
|
if y == E::Fr::one() {
|
||||||
|
// The only solution for y = 1 is x = 0. (0, 1) is
|
||||||
|
// the neutral element, so we map this to the point
|
||||||
|
// at infinity.
|
||||||
|
|
||||||
|
Point::zero()
|
||||||
|
} else {
|
||||||
|
// The map from a twisted Edwards curve is defined as
|
||||||
|
// (x, y) -> (u, v) where
|
||||||
|
// u = (1 + y) / (1 - y)
|
||||||
|
// v = u / x
|
||||||
|
//
|
||||||
|
// This mapping is not defined for y = 1 and for x = 0.
|
||||||
|
//
|
||||||
|
// We have that y != 1 above. If x = 0, the only
|
||||||
|
// solutions for y are 1 (contradiction) or -1.
|
||||||
|
if x.is_zero() {
|
||||||
|
// (0, -1) is the point of order two which is not
|
||||||
|
// the neutral element, so we map it to (0, 0) which is
|
||||||
|
// the only affine point of order 2.
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: E::Fr::zero(),
|
||||||
|
y: E::Fr::zero(),
|
||||||
|
infinity: false,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The mapping is defined as above.
|
||||||
|
//
|
||||||
|
// (x, y) -> (u, v) where
|
||||||
|
// u = (1 + y) / (1 - y)
|
||||||
|
// v = u / x
|
||||||
|
|
||||||
|
let mut u = E::Fr::one();
|
||||||
|
u.add_assign(&y);
|
||||||
|
{
|
||||||
|
let mut tmp = E::Fr::one();
|
||||||
|
tmp.sub_assign(&y);
|
||||||
|
u.mul_assign(&tmp.inverse().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut v = u;
|
||||||
|
v.mul_assign(&x.inverse().unwrap());
|
||||||
|
|
||||||
|
// Scale it into the correct curve constants
|
||||||
|
v.mul_assign(¶ms.scale);
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: u,
|
||||||
|
y: v,
|
||||||
|
infinity: false,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to cast this as a prime order element, failing if it's
|
||||||
|
/// not in the prime order subgroup.
|
||||||
|
pub fn as_prime_order(&self, params: &JubjubParams<E>) -> Option<Point<E, PrimeOrder>> {
|
||||||
|
if self.mul(Fs::char(), params) == Point::zero() {
|
||||||
|
Some(convert_subgroup(self))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Point {
|
||||||
|
x: E::Fr::zero(),
|
||||||
|
y: E::Fr::zero(),
|
||||||
|
infinity: true,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_xy(&self) -> Option<(E::Fr, E::Fr)>
|
||||||
|
{
|
||||||
|
if self.infinity {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((self.x, self.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn negate(&self) -> Self {
|
||||||
|
let mut p = self.clone();
|
||||||
|
|
||||||
|
p.y.negate();
|
||||||
|
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn double(&self, params: &JubjubParams<E>) -> Self {
|
||||||
|
if self.infinity {
|
||||||
|
return Point::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.y == E::Fr::zero() {
|
||||||
|
return Point::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut delta = E::Fr::one();
|
||||||
|
{
|
||||||
|
let mut tmp = params.montgomery_a;
|
||||||
|
tmp.mul_assign(&self.x);
|
||||||
|
tmp.double();
|
||||||
|
delta.add_assign(&tmp);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut tmp = self.x;
|
||||||
|
tmp.square();
|
||||||
|
delta.add_assign(&tmp);
|
||||||
|
tmp.double();
|
||||||
|
delta.add_assign(&tmp);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut tmp = self.y;
|
||||||
|
tmp.double();
|
||||||
|
delta.mul_assign(&tmp.inverse().expect("y is nonzero so this must be nonzero"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut x3 = delta;
|
||||||
|
x3.square();
|
||||||
|
x3.sub_assign(¶ms.montgomery_a);
|
||||||
|
x3.sub_assign(&self.x);
|
||||||
|
x3.sub_assign(&self.x);
|
||||||
|
|
||||||
|
let mut y3 = x3;
|
||||||
|
y3.sub_assign(&self.x);
|
||||||
|
y3.mul_assign(&delta);
|
||||||
|
y3.add_assign(&self.y);
|
||||||
|
y3.negate();
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: x3,
|
||||||
|
y: y3,
|
||||||
|
infinity: false,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&self, other: &Self, params: &JubjubParams<E>) -> Self
|
||||||
|
{
|
||||||
|
match (self.infinity, other.infinity) {
|
||||||
|
(true, true) => Point::zero(),
|
||||||
|
(true, false) => other.clone(),
|
||||||
|
(false, true) => self.clone(),
|
||||||
|
(false, false) => {
|
||||||
|
if self.x == other.x {
|
||||||
|
if self.y == other.y {
|
||||||
|
self.double(params)
|
||||||
|
} else {
|
||||||
|
Point::zero()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut delta = other.y;
|
||||||
|
delta.sub_assign(&self.y);
|
||||||
|
{
|
||||||
|
let mut tmp = other.x;
|
||||||
|
tmp.sub_assign(&self.x);
|
||||||
|
delta.mul_assign(&tmp.inverse().expect("self.x != other.x, so this must be nonzero"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut x3 = delta;
|
||||||
|
x3.square();
|
||||||
|
x3.sub_assign(¶ms.montgomery_a);
|
||||||
|
x3.sub_assign(&self.x);
|
||||||
|
x3.sub_assign(&other.x);
|
||||||
|
|
||||||
|
let mut y3 = x3;
|
||||||
|
y3.sub_assign(&self.x);
|
||||||
|
y3.mul_assign(&delta);
|
||||||
|
y3.add_assign(&self.y);
|
||||||
|
y3.negate();
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: x3,
|
||||||
|
y: y3,
|
||||||
|
infinity: false,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul<S: Into<FsRepr>>(&self, scalar: S, params: &JubjubParams<E>) -> Self
|
||||||
|
{
|
||||||
|
let mut res = Self::zero();
|
||||||
|
|
||||||
|
for b in BitIterator::new(scalar.into()) {
|
||||||
|
res = res.double(params);
|
||||||
|
|
||||||
|
if b {
|
||||||
|
res = res.add(self, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use rand::{XorShiftRng, SeedableRng, Rand};
|
||||||
|
use super::{JubjubParams, Point, PrimeOrder, Unknown, Fs};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use pairing::{Engine, Field, PrimeField};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
fn is_on_curve<E: Engine>(
|
||||||
|
x: E::Fr,
|
||||||
|
y: E::Fr,
|
||||||
|
params: &JubjubParams<E>
|
||||||
|
) -> bool
|
||||||
|
{
|
||||||
|
let mut lhs = y;
|
||||||
|
lhs.square();
|
||||||
|
|
||||||
|
let mut x2 = x;
|
||||||
|
x2.square();
|
||||||
|
|
||||||
|
let mut x3 = x2;
|
||||||
|
x3.mul_assign(&x);
|
||||||
|
|
||||||
|
let mut rhs = x2;
|
||||||
|
rhs.mul_assign(¶ms.montgomery_a);
|
||||||
|
rhs.add_assign(&x);
|
||||||
|
rhs.add_assign(&x3);
|
||||||
|
|
||||||
|
lhs == rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rand() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let (x, y) = Point::rand(&mut rng, ¶ms).into_xy().unwrap();
|
||||||
|
|
||||||
|
assert!(is_on_curve(x, y, ¶ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_identities() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
let z = Point::<Bls12, PrimeOrder>::zero();
|
||||||
|
assert!(z.double(¶ms) == z);
|
||||||
|
assert!(z.negate() == z);
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let r = Point::rand(&mut rng, ¶ms);
|
||||||
|
|
||||||
|
assert!(r.add(&Point::zero(), ¶ms) == r);
|
||||||
|
assert!(r.add(&r.negate(), ¶ms) == Point::zero());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_associativity() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let a = Point::rand(&mut rng, ¶ms);
|
||||||
|
let b = Point::rand(&mut rng, ¶ms);
|
||||||
|
let c = Point::rand(&mut rng, ¶ms);
|
||||||
|
|
||||||
|
assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_order() {
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
// The neutral element is in the prime order subgroup.
|
||||||
|
assert!(Point::<Bls12, PrimeOrder>::zero().as_prime_order(params).is_some());
|
||||||
|
|
||||||
|
for _ in 0..50 {
|
||||||
|
// Pick a random point and multiply it by the cofactor
|
||||||
|
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||||
|
|
||||||
|
// Any point multiplied by the cofactor will be in the prime
|
||||||
|
// order subgroup
|
||||||
|
assert!(base.as_prime_order(params).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's very likely that at least one out of 50 random points on the curve
|
||||||
|
// is not in the prime order subgroup.
|
||||||
|
let mut at_least_one_not_in_prime_order_subgroup = false;
|
||||||
|
for _ in 0..50 {
|
||||||
|
// Pick a random point.
|
||||||
|
let base = Point::rand(rng, params);
|
||||||
|
|
||||||
|
at_least_one_not_in_prime_order_subgroup |= base.as_prime_order(params).is_none();
|
||||||
|
}
|
||||||
|
assert!(at_least_one_not_in_prime_order_subgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul_associativity() {
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
// Pick a random point and multiply it by the cofactor
|
||||||
|
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||||
|
|
||||||
|
let mut a = Fs::rand(rng);
|
||||||
|
let b = Fs::rand(rng);
|
||||||
|
let c = Fs::rand(rng);
|
||||||
|
|
||||||
|
let res1 = base.mul(a, params).mul(b, params).mul(c, params);
|
||||||
|
let res2 = base.mul(b, params).mul(c, params).mul(a, params);
|
||||||
|
let res3 = base.mul(c, params).mul(a, params).mul(b, params);
|
||||||
|
a.mul_assign(&b);
|
||||||
|
a.mul_assign(&c);
|
||||||
|
let res4 = base.mul(a, params);
|
||||||
|
|
||||||
|
assert!(res1 == res2);
|
||||||
|
assert!(res2 == res3);
|
||||||
|
assert!(res3 == res4);
|
||||||
|
|
||||||
|
let (x, y) = res1.into_xy().unwrap();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
|
||||||
|
let (x, y) = res2.into_xy().unwrap();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
|
||||||
|
let (x, y) = res3.into_xy().unwrap();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edwards_conversion() {
|
||||||
|
use super::edwards;
|
||||||
|
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
// compute base in edwards
|
||||||
|
let base = edwards::Point::rand(rng, params);
|
||||||
|
|
||||||
|
// sample random exponent
|
||||||
|
let exp = Fs::rand(rng);
|
||||||
|
|
||||||
|
// exponentiate in edwards
|
||||||
|
let mont_expected = Point::from_edwards(&base.mul(exp, params), params);
|
||||||
|
|
||||||
|
// convert to montgomery and exponentiate
|
||||||
|
let mont_exp = Point::from_edwards(&base, params).mul(exp, params);
|
||||||
|
|
||||||
|
assert!(mont_exp == mont_expected);
|
||||||
|
|
||||||
|
let (x, y) = mont_expected.into_xy().unwrap();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_back_and_forth() {
|
||||||
|
use super::edwards;
|
||||||
|
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
// compute base in edwards
|
||||||
|
let base = edwards::Point::rand(rng, params);
|
||||||
|
|
||||||
|
// convert to montgomery
|
||||||
|
let base_mont = Point::from_edwards(&base, params);
|
||||||
|
|
||||||
|
{
|
||||||
|
let (x, y) = base_mont.into_xy().unwrap();
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert back to edwards
|
||||||
|
let base_ed = edwards::Point::from_montgomery(&base_mont, params);
|
||||||
|
|
||||||
|
assert!(base == base_ed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_awkward_points() {
|
||||||
|
use super::edwards;
|
||||||
|
|
||||||
|
//let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubParams::new();
|
||||||
|
|
||||||
|
let mut awkward_points: Vec<Point<Bls12, Unknown>> = vec![];
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut push_point = |x, y| {
|
||||||
|
let x = Fr::from_str(x).unwrap();
|
||||||
|
let y = Fr::from_str(y).unwrap();
|
||||||
|
|
||||||
|
assert!(is_on_curve(x, y, params));
|
||||||
|
|
||||||
|
awkward_points.push(Point {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
infinity: false,
|
||||||
|
_marker: PhantomData
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// p is a point of order 8
|
||||||
|
|
||||||
|
// push p
|
||||||
|
push_point(
|
||||||
|
"26700795483254565448379661158233243896148151268643422869645920428793919977699",
|
||||||
|
"38240351061652197568958466618399906060451208175623222883988435386266133962140"
|
||||||
|
);
|
||||||
|
|
||||||
|
// push 2p
|
||||||
|
push_point(
|
||||||
|
"1",
|
||||||
|
"40876724960280933289965479552128619538703197557433544801868355907127087029496"
|
||||||
|
);
|
||||||
|
|
||||||
|
// push 3p
|
||||||
|
push_point(
|
||||||
|
"48853380121562139410032601262067414539517111118072400994428343856767649516850",
|
||||||
|
"32041076745907035847439769934443325418710075447471957144325987857573529479623"
|
||||||
|
);
|
||||||
|
|
||||||
|
// push 4p
|
||||||
|
push_point(
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
);
|
||||||
|
|
||||||
|
// push 5p
|
||||||
|
push_point(
|
||||||
|
"48853380121562139410032601262067414539517111118072400994428343856767649516850",
|
||||||
|
"20394798429219154632007970573742640418980477053055680678277670842365051704890"
|
||||||
|
);
|
||||||
|
|
||||||
|
// push 6p
|
||||||
|
push_point(
|
||||||
|
"1",
|
||||||
|
"11559150214845257189482260956057346298987354943094093020735302792811494155017"
|
||||||
|
);
|
||||||
|
|
||||||
|
// push 7p
|
||||||
|
push_point(
|
||||||
|
"26700795483254565448379661158233243896148151268643422869645920428793919977699",
|
||||||
|
"14195524113473992910489273889786059777239344324904414938615223313672447222373"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// push 8p (point at infinity)
|
||||||
|
awkward_points.push(Point::zero());
|
||||||
|
|
||||||
|
for point in &awkward_points {
|
||||||
|
let ed = edwards::Point::from_montgomery(point, params);
|
||||||
|
let mut ed_tmp = ed.clone();
|
||||||
|
let mut mont_tmp = point.clone();
|
||||||
|
for _ in 0..8 {
|
||||||
|
let mont_again = Point::from_edwards(&ed_tmp, params);
|
||||||
|
assert!(mont_again == mont_tmp);
|
||||||
|
|
||||||
|
let ed_again = edwards::Point::from_montgomery(&mont_tmp, params);
|
||||||
|
assert!(ed_again == ed_tmp);
|
||||||
|
|
||||||
|
ed_tmp = ed_tmp.add(&ed, params);
|
||||||
|
mont_tmp = mont_tmp.add(point, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,9 @@
|
||||||
extern crate pairing;
|
extern crate pairing;
|
||||||
extern crate bellman;
|
extern crate bellman;
|
||||||
|
extern crate blake2;
|
||||||
|
extern crate digest;
|
||||||
|
extern crate rand;
|
||||||
|
|
||||||
|
pub mod jubjub;
|
||||||
|
pub mod circuit;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue