Implementation of fundamental circuitry and primitive Jubjub curve arithmetic.

This commit is contained in:
Sean Bowe 2017-11-22 21:57:00 -07:00
parent 35314c8771
commit 86619c7334
No known key found for this signature in database
GPG Key ID: 95684257D8F8B031
11 changed files with 4088 additions and 5 deletions

View File

@ -8,9 +8,16 @@ name = "sapling"
repository = "https://github.com/zcash/sapling"
version = "0.0.1"
[dependencies]
bellman = "0.0.5"
[dependencies.pairing]
version = "0.13"
features = ["u128-support"]
version = "~0.13.2"
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"]

374
src/circuit/blake2s.rs Normal file
View File

@ -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!()
}
}
}
}
}

476
src/circuit/boolean.rs Normal file
View File

@ -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()
)?.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")
}
}
}
}
}

21
src/circuit/mod.rs Normal file
View File

@ -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)
}
}
}

253
src/circuit/test/mod.rs Normal file
View File

@ -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());
}

451
src/circuit/uint32.rs Normal file
View File

@ -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);
}
}
}

547
src/jubjub/edwards.rs Normal file
View File

@ -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(&params.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(&params.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(&params.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, &params).into_xy();
assert!(is_on_curve(x, y, &params));
}
}
#[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(&params) == z);
assert!(z.negate() == z);
for _ in 0..100 {
let r = Point::rand(&mut rng, &params);
assert!(r.add(&Point::zero(), &params) == r);
assert!(r.add(&r.negate(), &params) == 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, &params);
let b = Point::rand(&mut rng, &params);
let c = Point::rand(&mut rng, &params);
assert!(a.add(&b, &params).add(&c, &params) == c.add(&a, &params).add(&b, &params));
}
}
#[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);
}
}
}

1240
src/jubjub/fs.rs Normal file

File diff suppressed because it is too large Load Diff

100
src/jubjub/mod.rs Normal file
View File

@ -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(&params.edwards_d);
tmp = tmp.inverse().unwrap();
tmp.mul_assign(&Fr::from_str("4").unwrap());
tmp = tmp.sqrt().unwrap();
assert_eq!(tmp, params.scale);
}
}

608
src/jubjub/montgomery.rs Normal file
View File

@ -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(&params.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(&params.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(&params.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(&params.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(&params.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, &params).into_xy().unwrap();
assert!(is_on_curve(x, y, &params));
}
}
#[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(&params) == z);
assert!(z.negate() == z);
for _ in 0..100 {
let r = Point::rand(&mut rng, &params);
assert!(r.add(&Point::zero(), &params) == r);
assert!(r.add(&r.negate(), &params) == 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, &params);
let b = Point::rand(&mut rng, &params);
let c = Point::rand(&mut rng, &params);
assert!(a.add(&b, &params).add(&c, &params) == c.add(&a, &params).add(&b, &params));
}
}
#[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);
}
}
}
}

View File

@ -1,3 +1,9 @@
extern crate pairing;
extern crate bellman;
extern crate blake2;
extern crate digest;
extern crate rand;
pub mod jubjub;
pub mod circuit;