diff --git a/Cargo.toml b/Cargo.toml index 91643baa3..6bc00b274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs new file mode 100644 index 000000000..d49a233f4 --- /dev/null +++ b/src/circuit/blake2s.rs @@ -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>( + mut cs: CS, + v: &mut [UInt32], + a: usize, + b: usize, + c: usize, + d: usize, + x: &UInt32, + y: &UInt32 +) -> 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>( + mut cs: CS, + h: &mut [UInt32], + m: &[UInt32], + t: u64, + f: bool +) -> Result<(), SynthesisError> +{ + assert_eq!(h.len(), 8); + assert_eq!(m.len(), 16); + + /* + static const uint32_t blake2s_iv[8] = + { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 + }; + */ + + let mut v = Vec::with_capacity(16); + v.extend_from_slice(h); + v.push(UInt32::constant(0x6A09E667)); + v.push(UInt32::constant(0xBB67AE85)); + v.push(UInt32::constant(0x3C6EF372)); + v.push(UInt32::constant(0xA54FF53A)); + v.push(UInt32::constant(0x510E527F)); + v.push(UInt32::constant(0x9B05688C)); + v.push(UInt32::constant(0x1F83D9AB)); + v.push(UInt32::constant(0x5BE0CD19)); + + assert_eq!(v.len(), 16); + + v[12] = v[12].xor(cs.namespace(|| "first xor"), &UInt32::constant(t as u32))?; + v[13] = v[13].xor(cs.namespace(|| "second xor"), &UInt32::constant((t >> 32) as u32))?; + + if f { + v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?; + } + + 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>( + mut cs: CS, + input: &[Boolean] +) -> Result>, 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![]; + + 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::::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 = (0..input_len).map(|_| rng.gen()).collect(); + + h.process(&data); + + let hash_result = h.fixed_result(); + + let mut cs = TestConstraintSystem::::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!() + } + } + } + } +} diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs new file mode 100644 index 000000000..4d3bc3a4e --- /dev/null +++ b/src/circuit/boolean.rs @@ -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 { + variable: Var, + value: Option +} + +impl AllocatedBit { + pub fn get_value(&self) -> Option { + 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( + mut cs: CS, + value: Option, + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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 { + /// Existential view of the boolean variable + Is(AllocatedBit), + /// Negated view of the boolean variable + Not(AllocatedBit), + /// Constant (not an allocated variable) + Constant(bool) +} + +impl Boolean { + /// 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 + where E: Engine, + CS: ConstraintSystem + { + 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 From> for Boolean { + fn from(b: AllocatedBit) -> Boolean { + 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::::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::::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::::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::::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::::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") + } + } + } + } +} diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs new file mode 100644 index 000000000..1f0cf9496 --- /dev/null +++ b/src/circuit/mod.rs @@ -0,0 +1,21 @@ +#[cfg(test)] +pub mod test; + +pub mod boolean; +pub mod uint32; +pub mod blake2s; + +use bellman::SynthesisError; + +trait Assignment { + fn get(&self) -> Result<&T, SynthesisError>; +} + +impl Assignment for Option { + fn get(&self) -> Result<&T, SynthesisError> { + match *self { + Some(ref v) => Ok(v), + None => Err(SynthesisError::AssignmentMissing) + } + } +} diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs new file mode 100644 index 000000000..22575e93e --- /dev/null +++ b/src/circuit/test/mod.rs @@ -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 { + named_objects: HashMap, + current_namespace: Vec, + constraints: Vec<(LinearCombination, LinearCombination, LinearCombination, String)>, + inputs: Vec<(E::Fr, String)>, + aux: Vec<(E::Fr, String)> +} + +fn eval_lc( + 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 TestConstraintSystem { + pub fn new() -> TestConstraintSystem { + 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::(a.as_ref(), &self.inputs, &self.aux); + let b = eval_lc::(b.as_ref(), &self.inputs, &self.aux); + let c = eval_lc::(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 ConstraintSystem for TestConstraintSystem { + type Variable = Variable; + type Root = Self; + + fn one(&self) -> Self::Variable { + Variable::Input(0) + } + + fn alloc( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + 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( + &mut self, + annotation: A, + a: LinearCombination, + b: LinearCombination, + c: LinearCombination + ) + where A: FnOnce() -> AR, AR: Into + { + 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(&mut self, name_fn: N) + where NR: Into, 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::::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()); +} diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs new file mode 100644 index 000000000..c8bb62997 --- /dev/null +++ b/src/circuit/uint32.rs @@ -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 { + // Least significant bit first + bits: Vec>, + value: Option +} + +impl UInt32 { + /// Construct a constant `UInt32` from a `u32` + pub fn constant(value: u32) -> Self + { + let mut bits = Vec::with_capacity(32); + + let mut tmp = value; + for _ in 0..32 { + if tmp & 1 == 1 { + bits.push(Boolean::constant(true)) + } else { + bits.push(Boolean::constant(false)) + } + + tmp >>= 1; + } + + UInt32 { + bits: bits, + value: Some(value) + } + } + + /// Allocate a `UInt32` in the constraint system + pub fn alloc( + mut cs: CS, + value: Option + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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::, SynthesisError>>()?; + + Ok(UInt32 { + bits: bits, + value: value + }) + } + + /// Turns this `UInt32` into its little-endian byte order representation. + pub fn into_bits(&self) -> Vec> { + 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]) -> Self + { + assert_eq!(bits.len(), 32); + + let new_bits = bits.chunks(8) + .flat_map(|v| v.iter().rev()) + .cloned() + .collect::>(); + + 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( + &self, + mut cs: CS, + other: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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::>()?; + + Ok(UInt32 { + bits: bits, + value: new_value + }) + } + + // TODO: could optimize + /// Perform modular addition of several `UInt32` objects. + pub fn addmany( + mut cs: CS, + operands: &[Self] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + // 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::>(); + + 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::::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::::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); + } + } +} diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs new file mode 100644 index 000000000..e803a9cf9 --- /dev/null +++ b/src/jubjub/edwards.rs @@ -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 { + x: E::Fr, + y: E::Fr, + t: E::Fr, + z: E::Fr, + _marker: PhantomData +} + +fn convert_subgroup(from: &Point) -> Point +{ + Point { + x: from.x, + y: from.y, + t: from.t, + z: from.z, + _marker: PhantomData + } +} + +impl From> for Point +{ + fn from(p: Point) -> Point + { + convert_subgroup(&p) + } +} + +impl Clone for Point +{ + fn clone(&self) -> Self { + convert_subgroup(self) + } +} + +impl PartialEq for Point { + fn eq(&self, other: &Point) -> 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 Point { + /// This guarantees the point is in the prime order subgroup + pub fn mul_by_cofactor(&self, params: &JubjubParams) -> Point + { + let tmp = self.double(params) + .double(params) + .double(params); + + convert_subgroup(&tmp) + } + + pub fn rand(rng: &mut R, params: &JubjubParams) -> 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 Point { + /// Convert from a Montgomery point + pub fn from_montgomery( + m: &montgomery::Point, + params: &JubjubParams + ) -> 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) -> Option> { + 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) -> Self { + self.add(self, params) + } + + pub fn add(&self, other: &Self, params: &JubjubParams) -> 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>(&self, scalar: S, params: &JubjubParams) -> 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( + x: E::Fr, + y: E::Fr, + params: &JubjubParams + ) -> 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::::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::::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); + } + } +} diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs new file mode 100644 index 000000000..888660e9b --- /dev/null +++ b/src/jubjub/fs.rs @@ -0,0 +1,1240 @@ +use pairing::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError, LegendreSymbol}; +use pairing::LegendreSymbol::*; +use pairing::{adc, sbb, mac_with_carry}; + +// s = 6554484396890773809930967563523245729705921265872317281365359162392183254199 +const MODULUS: FsRepr = FsRepr([0xd0970e5ed6f72cb7, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9]); + +// The number of bits needed to represent the modulus. +const MODULUS_BITS: u32 = 252; + +// The number of bits that must be shaved from the beginning of +// the representation when randomly sampling. +const REPR_SHAVE_BITS: u32 = 4; + +// R = 2**256 % s +const R: FsRepr = FsRepr([0x25f80bb3b99607d9, 0xf315d62f66b6e750, 0x932514eeeb8814f4, 0x9a6fc6f479155c6]); + +// R2 = R^2 % s +const R2: FsRepr = FsRepr([0x67719aa495e57731, 0x51b0cef09ce3fc26, 0x69dab7fac026e9a5, 0x4f6547b8d127688]); + +// INV = -(s^{-1} mod 2^64) mod s +const INV: u64 = 0x1ba3a358ef788ef9; + +// GENERATOR = 6 (multiplicative generator of r-1 order, that is also quadratic nonresidue) +const GENERATOR: FsRepr = FsRepr([0x720b1b19d49ea8f1, 0xbf4aa36101f13a58, 0x5fa8cc968193ccbb, 0xe70cbdc7dccf3ac]); + +// 2^S * t = MODULUS - 1 with t odd +const S: u32 = 1; + +// 2^S root of unity computed by GENERATOR^t +const ROOT_OF_UNITY: FsRepr = FsRepr([0xaa9f02ab1d6124de, 0xb3524a6466112932, 0x7342261215ac260b, 0x4d6b87b1da259e2]); + +// -((2**256) mod s) mod s +const NEGATIVE_ONE: Fs = Fs(FsRepr([0xaa9f02ab1d6124de, 0xb3524a6466112932, 0x7342261215ac260b, 0x4d6b87b1da259e2])); + +/// This is the underlying representation of an element of `Fs`. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub struct FsRepr(pub [u64; 4]); + +impl ::rand::Rand for FsRepr { + #[inline(always)] + fn rand(rng: &mut R) -> Self { + FsRepr(rng.gen()) + } +} + +impl ::std::fmt::Display for FsRepr +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + try!(write!(f, "0x")); + for i in self.0.iter().rev() { + try!(write!(f, "{:016x}", *i)); + } + + Ok(()) + } +} + +impl AsRef<[u64]> for FsRepr { + #[inline(always)] + fn as_ref(&self) -> &[u64] { + &self.0 + } +} + +impl AsMut<[u64]> for FsRepr { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u64] { + &mut self.0 + } +} + +impl From for FsRepr { + #[inline(always)] + fn from(val: u64) -> FsRepr { + let mut repr = Self::default(); + repr.0[0] = val; + repr + } +} + +impl Ord for FsRepr { + #[inline(always)] + fn cmp(&self, other: &FsRepr) -> ::std::cmp::Ordering { + for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { + if a < b { + return ::std::cmp::Ordering::Less + } else if a > b { + return ::std::cmp::Ordering::Greater + } + } + + ::std::cmp::Ordering::Equal + } +} + +impl PartialOrd for FsRepr { + #[inline(always)] + fn partial_cmp(&self, other: &FsRepr) -> Option<::std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl PrimeFieldRepr for FsRepr { + #[inline(always)] + fn is_odd(&self) -> bool { + self.0[0] & 1 == 1 + } + + #[inline(always)] + fn is_even(&self) -> bool { + !self.is_odd() + } + + #[inline(always)] + fn is_zero(&self) -> bool { + self.0.iter().all(|&e| e == 0) + } + + #[inline(always)] + fn divn(&mut self, mut n: u32) { + if n >= 64 * 4 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << (64 - n); + *i >>= n; + *i |= t; + t = t2; + } + } + } + + #[inline(always)] + fn div2(&mut self) { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << 63; + *i >>= 1; + *i |= t; + t = t2; + } + } + + #[inline(always)] + fn mul2(&mut self) { + let mut last = 0; + for i in &mut self.0 { + let tmp = *i >> 63; + *i <<= 1; + *i |= last; + last = tmp; + } + } + + #[inline(always)] + fn muln(&mut self, mut n: u32) { + if n >= 64 * 4 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in &mut self.0 { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in &mut self.0 { + let t2 = *i >> (64 - n); + *i <<= n; + *i |= t; + t = t2; + } + } + } + + #[inline(always)] + fn num_bits(&self) -> u32 { + let mut ret = (4 as u32) * 64; + for i in self.0.iter().rev() { + let leading = i.leading_zeros(); + ret -= leading; + if leading != 64 { + break; + } + } + + ret + } + + #[inline(always)] + fn add_nocarry(&mut self, other: &FsRepr) -> bool { + let mut carry = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = adc(*a, *b, &mut carry); + } + + carry != 0 + } + + #[inline(always)] + fn sub_noborrow(&mut self, other: &FsRepr) -> bool { + let mut borrow = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = sbb(*a, *b, &mut borrow); + } + + borrow != 0 + } +} + +/// This is an element of the scalar field of the Jubjub curve. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Fs(FsRepr); + +impl ::std::fmt::Display for Fs +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fs({})", self.into_repr()) + } +} + +impl ::rand::Rand for Fs { + fn rand(rng: &mut R) -> Self { + loop { + let mut tmp = Fs(FsRepr::rand(rng)); + + // Mask away the unused bits at the beginning. + tmp.0.as_mut()[3] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; + + if tmp.is_valid() { + return tmp + } + } + } +} + +impl From for FsRepr { + fn from(e: Fs) -> FsRepr { + e.into_repr() + } +} + +impl PrimeField for Fs { + type Repr = FsRepr; + + fn from_repr(r: FsRepr) -> Result { + let mut r = Fs(r); + if r.is_valid() { + r.mul_assign(&Fs(R2)); + + Ok(r) + } else { + Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) + } + } + + fn into_repr(&self) -> FsRepr { + let mut r = *self; + r.mont_reduce((self.0).0[0], (self.0).0[1], + (self.0).0[2], (self.0).0[3], + 0, 0, 0, 0); + r.0 + } + + fn char() -> FsRepr { + MODULUS + } + + const NUM_BITS: u32 = MODULUS_BITS; + + const CAPACITY: u32 = Self::NUM_BITS - 1; + + fn multiplicative_generator() -> Self { + Fs(GENERATOR) + } + + const S: u32 = S; + + fn root_of_unity() -> Self { + Fs(ROOT_OF_UNITY) + } +} + +impl Field for Fs { + #[inline] + fn zero() -> Self { + Fs(FsRepr::from(0)) + } + + #[inline] + fn one() -> Self { + Fs(R) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + #[inline] + fn add_assign(&mut self, other: &Fs) { + // This cannot exceed the backing capacity. + self.0.add_nocarry(&other.0); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn double(&mut self) { + // This cannot exceed the backing capacity. + self.0.mul2(); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn sub_assign(&mut self, other: &Fs) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.0 > self.0 { + self.0.add_nocarry(&MODULUS); + } + + self.0.sub_noborrow(&other.0); + } + + #[inline] + fn negate(&mut self) { + if !self.is_zero() { + let mut tmp = MODULUS; + tmp.sub_noborrow(&self.0); + self.0 = tmp; + } + } + + fn inverse(&self) -> Option { + if self.is_zero() { + None + } else { + // Guajardo Kumar Paar Pelzl + // Efficient Software-Implementation of Finite Fields with Applications to Cryptography + // Algorithm 16 (BEA for Inversion in Fp) + + let one = FsRepr::from(1); + + let mut u = self.0; + let mut v = MODULUS; + let mut b = Fs(R2); // Avoids unnecessary reduction step. + let mut c = Self::zero(); + + while u != one && v != one { + while u.is_even() { + u.div2(); + + if b.0.is_even() { + b.0.div2(); + } else { + b.0.add_nocarry(&MODULUS); + b.0.div2(); + } + } + + while v.is_even() { + v.div2(); + + if c.0.is_even() { + c.0.div2(); + } else { + c.0.add_nocarry(&MODULUS); + c.0.div2(); + } + } + + if v < u { + u.sub_noborrow(&v); + b.sub_assign(&c); + } else { + v.sub_noborrow(&u); + c.sub_assign(&b); + } + } + + if u == one { + Some(b) + } else { + Some(c) + } + } + } + + #[inline(always)] + fn frobenius_map(&mut self, _: usize) { + // This has no effect in a prime field. + } + + #[inline] + fn mul_assign(&mut self, other: &Fs) + { + let mut carry = 0; + let r0 = mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r1 = mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r2 = mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r6 = carry; + let mut carry = 0; + let r3 = mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r7 = carry; + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + #[inline] + fn square(&mut self) + { + let mut carry = 0; + let r1 = mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); + let r2 = mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); + let r3 = mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r3 = mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r5 = mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); + let r6 = carry; + + let r7 = r6 >> 63; + let r6 = (r6 << 1) | (r5 >> 63); + let r5 = (r5 << 1) | (r4 >> 63); + let r4 = (r4 << 1) | (r3 >> 63); + let r3 = (r3 << 1) | (r2 >> 63); + let r2 = (r2 << 1) | (r1 >> 63); + let r1 = r1 << 1; + + let mut carry = 0; + let r0 = mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); + let r1 = adc(r1, 0, &mut carry); + let r2 = mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); + let r3 = adc(r3, 0, &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); + let r5 = adc(r5, 0, &mut carry); + let r6 = mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); + let r7 = adc(r7, 0, &mut carry); + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } +} + +impl Fs { + /// Determines if the element is really in the field. This is only used + /// internally. + #[inline(always)] + fn is_valid(&self) -> bool { + self.0 < MODULUS + } + + /// Subtracts the modulus from this element if this element is not in the + /// field. Only used internally. + #[inline(always)] + fn reduce(&mut self) { + if !self.is_valid() { + self.0.sub_noborrow(&MODULUS); + } + } + + #[inline(always)] + fn mont_reduce( + &mut self, + r0: u64, + mut r1: u64, + mut r2: u64, + mut r3: u64, + mut r4: u64, + mut r5: u64, + mut r6: u64, + mut r7: u64 + ) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r0, k, MODULUS.0[0], &mut carry); + r1 = mac_with_carry(r1, k, MODULUS.0[1], &mut carry); + r2 = mac_with_carry(r2, k, MODULUS.0[2], &mut carry); + r3 = mac_with_carry(r3, k, MODULUS.0[3], &mut carry); + r4 = adc(r4, 0, &mut carry); + let carry2 = carry; + let k = r1.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r1, k, MODULUS.0[0], &mut carry); + r2 = mac_with_carry(r2, k, MODULUS.0[1], &mut carry); + r3 = mac_with_carry(r3, k, MODULUS.0[2], &mut carry); + r4 = mac_with_carry(r4, k, MODULUS.0[3], &mut carry); + r5 = adc(r5, carry2, &mut carry); + let carry2 = carry; + let k = r2.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r2, k, MODULUS.0[0], &mut carry); + r3 = mac_with_carry(r3, k, MODULUS.0[1], &mut carry); + r4 = mac_with_carry(r4, k, MODULUS.0[2], &mut carry); + r5 = mac_with_carry(r5, k, MODULUS.0[3], &mut carry); + r6 = adc(r6, carry2, &mut carry); + let carry2 = carry; + let k = r3.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r3, k, MODULUS.0[0], &mut carry); + r4 = mac_with_carry(r4, k, MODULUS.0[1], &mut carry); + r5 = mac_with_carry(r5, k, MODULUS.0[2], &mut carry); + r6 = mac_with_carry(r6, k, MODULUS.0[3], &mut carry); + r7 = adc(r7, carry2, &mut carry); + (self.0).0[0] = r4; + (self.0).0[1] = r5; + (self.0).0[2] = r6; + (self.0).0[3] = r7; + self.reduce(); + } +} + +impl SqrtField for Fs { + + fn legendre(&self) -> LegendreSymbol { + // s = self^((s - 1) // 2) + let s = self.pow([0x684b872f6b7b965b, 0x53341049e6640841, 0x83339d80809a1d80, 0x73eda753299d7d4]); + if s == Self::zero() { Zero } + else if s == Self::one() { QuadraticResidue } + else { QuadraticNonResidue } + } + + fn sqrt(&self) -> Option { + // Shank's algorithm for s mod 4 = 3 + // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) + + // a1 = self^((s - 3) // 4) + let mut a1 = self.pow([0xb425c397b5bdcb2d, 0x299a0824f3320420, 0x4199cec0404d0ec0, 0x39f6d3a994cebea]); + let mut a0 = a1; + a0.square(); + a0.mul_assign(self); + + if a0 == NEGATIVE_ONE + { + None + } + else + { + a1.mul_assign(self); + Some(a1) + } + } +} + + +#[test] +fn test_neg_one() { + let mut o = Fs::one(); + o.negate(); + + assert_eq!(NEGATIVE_ONE, o); +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng, Rand}; + +#[test] +fn test_fs_repr_ordering() { + fn assert_equality(a: FsRepr, b: FsRepr) { + assert_eq!(a, b); + assert!(a.cmp(&b) == ::std::cmp::Ordering::Equal); + } + + fn assert_lt(a: FsRepr, b: FsRepr) { + assert!(a < b); + assert!(b > a); + } + + assert_equality(FsRepr([9999, 9999, 9999, 9999]), FsRepr([9999, 9999, 9999, 9999])); + assert_equality(FsRepr([9999, 9998, 9999, 9999]), FsRepr([9999, 9998, 9999, 9999])); + assert_equality(FsRepr([9999, 9999, 9999, 9997]), FsRepr([9999, 9999, 9999, 9997])); + assert_lt(FsRepr([9999, 9997, 9999, 9998]), FsRepr([9999, 9997, 9999, 9999])); + assert_lt(FsRepr([9999, 9997, 9998, 9999]), FsRepr([9999, 9997, 9999, 9999])); + assert_lt(FsRepr([9, 9999, 9999, 9997]), FsRepr([9999, 9999, 9999, 9997])); +} + +#[test] +fn test_fs_repr_from() { + assert_eq!(FsRepr::from(100), FsRepr([100, 0, 0, 0])); +} + +#[test] +fn test_fs_repr_is_odd() { + assert!(!FsRepr::from(0).is_odd()); + assert!(FsRepr::from(0).is_even()); + assert!(FsRepr::from(1).is_odd()); + assert!(!FsRepr::from(1).is_even()); + assert!(!FsRepr::from(324834872).is_odd()); + assert!(FsRepr::from(324834872).is_even()); + assert!(FsRepr::from(324834873).is_odd()); + assert!(!FsRepr::from(324834873).is_even()); +} + +#[test] +fn test_fs_repr_is_zero() { + assert!(FsRepr::from(0).is_zero()); + assert!(!FsRepr::from(1).is_zero()); + assert!(!FsRepr([0, 0, 1, 0]).is_zero()); +} + +#[test] +fn test_fs_repr_div2() { + let mut a = FsRepr([0xbd2920b19c972321, 0x174ed0466a3be37e, 0xd468d5e3b551f0b5, 0xcb67c072733beefc]); + a.div2(); + assert_eq!(a, FsRepr([0x5e949058ce4b9190, 0x8ba76823351df1bf, 0x6a346af1daa8f85a, 0x65b3e039399df77e])); + for _ in 0..10 { + a.div2(); + } + assert_eq!(a, FsRepr([0x6fd7a524163392e4, 0x16a2e9da08cd477c, 0xdf9a8d1abc76aa3e, 0x196cf80e4e677d])); + for _ in 0..200 { + a.div2(); + } + assert_eq!(a, FsRepr([0x196cf80e4e67, 0x0, 0x0, 0x0])); + for _ in 0..40 { + a.div2(); + } + assert_eq!(a, FsRepr([0x19, 0x0, 0x0, 0x0])); + for _ in 0..4 { + a.div2(); + } + assert_eq!(a, FsRepr([0x1, 0x0, 0x0, 0x0])); + a.div2(); + assert!(a.is_zero()); +} + +#[test] +fn test_fs_repr_divn() { + let mut a = FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); + a.divn(0); + assert_eq!( + a, + FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) + ); + a.divn(1); + assert_eq!( + a, + FsRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) + ); + a.divn(50); + assert_eq!( + a, + FsRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) + ); + a.divn(130); + assert_eq!( + a, + FsRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) + ); + a.divn(64); + assert_eq!( + a, + FsRepr([0x1b0, 0x0, 0x0, 0x0]) + ); +} + +#[test] +fn test_fs_repr_mul2() { + let mut a = FsRepr::from(23712937547); + a.mul2(); + assert_eq!(a, FsRepr([0xb0acd6c96, 0x0, 0x0, 0x0])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FsRepr([0x6000000000000000, 0xb0acd6c9, 0x0, 0x0])); + for _ in 0..128 { + a.mul2(); + } + assert_eq!(a, FsRepr([0x0, 0x0, 0x6000000000000000, 0xb0acd6c9])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FsRepr([0x0, 0x0, 0x0, 0x9600000000000000])); + for _ in 0..7 { + a.mul2(); + } + assert!(a.is_zero()); +} + +#[test] +fn test_fs_repr_num_bits() { + let mut a = FsRepr::from(0); + assert_eq!(0, a.num_bits()); + a = FsRepr::from(1); + for i in 1..257 { + assert_eq!(i, a.num_bits()); + a.mul2(); + } + assert_eq!(0, a.num_bits()); +} + +#[test] +fn test_fs_repr_sub_noborrow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FsRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495]); + t.sub_noborrow(&FsRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf])); + assert!(t == FsRepr([0xb813415048991c1f, 0x10ad07ae88725d92, 0x5a7b851271759961, 0x36850eedd30c39c5])); + + for _ in 0..1000 { + let mut a = FsRepr::rand(&mut rng); + a.0[3] >>= 30; + let mut b = a; + for _ in 0..10 { + b.mul2(); + } + let mut c = b; + for _ in 0..10 { + c.mul2(); + } + + assert!(a < b); + assert!(b < c); + + let mut csub_ba = c; + csub_ba.sub_noborrow(&b); + csub_ba.sub_noborrow(&a); + + let mut csub_ab = c; + csub_ab.sub_noborrow(&a); + csub_ab.sub_noborrow(&b); + + assert_eq!(csub_ab, csub_ba); + } + + // Subtracting r+1 from r should produce a borrow + let mut qplusone = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(qplusone.sub_noborrow(&FsRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); + + // Subtracting x from x should produce no borrow + let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(!x.sub_noborrow(&FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) +} + +#[test] +fn test_fs_legendre() { + assert_eq!(QuadraticResidue, Fs::one().legendre()); + assert_eq!(Zero, Fs::zero().legendre()); + + let e = FsRepr([0x8385eec23df1f88e, 0x9a01fb412b2dba16, 0x4c928edcdd6c22f, 0x9f2df7ef69ecef9]); + assert_eq!(QuadraticResidue, Fs::from_repr(e).unwrap().legendre()); + let e = FsRepr([0xe8ed9f299da78568, 0x35efdebc88b2209, 0xc82125cb1f916dbe, 0x6813d2b38c39bd0]); + assert_eq!(QuadraticNonResidue, Fs::from_repr(e).unwrap().legendre()); +} + +#[test] +fn test_fr_repr_add_nocarry() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FsRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf]); + t.add_nocarry(&FsRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495])); + assert_eq!(t, FsRepr([0x64b20e805c30a967, 0x59a9ee9aa114a02, 0x5871a104789020c9, 0x8999707c5c726f65])); + + // Test for the associativity of addition. + for _ in 0..1000 { + let mut a = FsRepr::rand(&mut rng); + let mut b = FsRepr::rand(&mut rng); + let mut c = FsRepr::rand(&mut rng); + + // Unset the first few bits, so that overflow won't occur. + a.0[3] >>= 3; + b.0[3] >>= 3; + c.0[3] >>= 3; + + let mut abc = a; + abc.add_nocarry(&b); + abc.add_nocarry(&c); + + let mut acb = a; + acb.add_nocarry(&c); + acb.add_nocarry(&b); + + let mut bac = b; + bac.add_nocarry(&a); + bac.add_nocarry(&c); + + let mut bca = b; + bca.add_nocarry(&c); + bca.add_nocarry(&a); + + let mut cab = c; + cab.add_nocarry(&a); + cab.add_nocarry(&b); + + let mut cba = c; + cba.add_nocarry(&b); + cba.add_nocarry(&a); + + assert_eq!(abc, acb); + assert_eq!(abc, bac); + assert_eq!(abc, bca); + assert_eq!(abc, cab); + assert_eq!(abc, cba); + } + + // Adding 1 to (2^256 - 1) should produce a carry + let mut x = FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + assert!(x.add_nocarry(&FsRepr::from(1))); + + // Adding 1 to r should not produce a carry + let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(!x.add_nocarry(&FsRepr::from(1))); +} + +#[test] +fn test_fs_is_valid() { + let mut a = Fs(MODULUS); + assert!(!a.is_valid()); + a.0.sub_noborrow(&FsRepr::from(1)); + assert!(a.is_valid()); + assert!(Fs(FsRepr::from(0)).is_valid()); + assert!(Fs(FsRepr([0xd0970e5ed6f72cb6, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9])).is_valid()); + assert!(!Fs(FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])).is_valid()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = Fs::rand(&mut rng); + assert!(a.is_valid()); + } +} + +#[test] +fn test_fs_add_assign() { + { + // Random number + let mut tmp = Fs::from_str("4577408157467272683998459759522778614363623736323078995109579213719612604198").unwrap(); + assert!(tmp.is_valid()); + // Test that adding zero has no effect. + tmp.add_assign(&Fs(FsRepr::from(0))); + assert_eq!(tmp, Fs(FsRepr([0x8e6bfff4722d6e67, 0x5643da5c892044f9, 0x9465f4b281921a69, 0x25f752d3edd7162]))); + // Add one and test for the result. + tmp.add_assign(&Fs(FsRepr::from(1))); + assert_eq!(tmp, Fs(FsRepr([0x8e6bfff4722d6e68, 0x5643da5c892044f9, 0x9465f4b281921a69, 0x25f752d3edd7162]))); + // Add another random number that exercises the reduction. + tmp.add_assign(&Fs(FsRepr([0xb634d07bc42d4a70, 0xf724f0c008411f5f, 0x456d4053d865af34, 0x24ce814e8c63027]))); + assert_eq!(tmp, Fs(FsRepr([0x44a0d070365ab8d8, 0x4d68cb1c91616459, 0xd9d3350659f7c99e, 0x4ac5d4227a3a189]))); + // Add one to (s - 1) and test for the result. + tmp = Fs(FsRepr([0xd0970e5ed6f72cb6, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9])); + tmp.add_assign(&Fs(FsRepr::from(1))); + assert!(tmp.0.is_zero()); + // Add a random number to another one such that the result is s - 1 + tmp = Fs(FsRepr([0xa11fda5950ce3636, 0x922e0dbccfe0ca0e, 0xacebb6e215b82d4a, 0x97ffb8cdc3aee93])); + tmp.add_assign(&Fs(FsRepr([0x2f7734058628f680, 0x143a12d6fce74674, 0x597b841eeb7c0db6, 0x4fdb95d88f8c115]))); + assert_eq!(tmp, Fs(FsRepr([0xd0970e5ed6f72cb6, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9]))); + // Add one to the result and test for it. + tmp.add_assign(&Fs(FsRepr::from(1))); + assert!(tmp.0.is_zero()); + } + + // Test associativity + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Generate a, b, c and ensure (a + b) + c == a + (b + c). + let a = Fs::rand(&mut rng); + let b = Fs::rand(&mut rng); + let c = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + + let mut tmp2 = b; + tmp2.add_assign(&c); + tmp2.add_assign(&a); + + assert!(tmp1.is_valid()); + assert!(tmp2.is_valid()); + assert_eq!(tmp1, tmp2); + } +} + +#[test] +fn test_fs_sub_assign() { + { + // Test arbitrary subtraction that tests reduction. + let mut tmp = Fs(FsRepr([0xb384d9f6877afd99, 0x4442513958e1a1c1, 0x352c4b8a95eccc3f, 0x2db62dee4b0f2])); + tmp.sub_assign(&Fs(FsRepr([0xec5bd2d13ed6b05a, 0x2adc0ab3a39b5fa, 0x82d3360a493e637e, 0x53ccff4a64d6679]))); + assert_eq!(tmp, Fs(FsRepr([0x97c015841f9b79f6, 0xe7fcb121eb6ffc49, 0xb8c050814de2a3c1, 0x943c0589dcafa21]))); + + // Test the opposite subtraction which doesn't test reduction. + tmp = Fs(FsRepr([0xec5bd2d13ed6b05a, 0x2adc0ab3a39b5fa, 0x82d3360a493e637e, 0x53ccff4a64d6679])); + tmp.sub_assign(&Fs(FsRepr([0xb384d9f6877afd99, 0x4442513958e1a1c1, 0x352c4b8a95eccc3f, 0x2db62dee4b0f2]))); + assert_eq!(tmp, Fs(FsRepr([0x38d6f8dab75bb2c1, 0xbe6b6f71e1581439, 0x4da6ea7fb351973e, 0x539f491c768b587]))); + + // Test for sensible results with zero + tmp = Fs(FsRepr::from(0)); + tmp.sub_assign(&Fs(FsRepr::from(0))); + assert!(tmp.is_zero()); + + tmp = Fs(FsRepr([0x361e16aef5cce835, 0x55bbde2536e274c1, 0x4dc77a63fd15ee75, 0x1e14bb37c14f230])); + tmp.sub_assign(&Fs(FsRepr::from(0))); + assert_eq!(tmp, Fs(FsRepr([0x361e16aef5cce835, 0x55bbde2536e274c1, 0x4dc77a63fd15ee75, 0x1e14bb37c14f230]))); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure that (a - b) + (b - a) = 0. + let a = Fs::rand(&mut rng); + let b = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.sub_assign(&b); + + let mut tmp2 = b; + tmp2.sub_assign(&a); + + tmp1.add_assign(&tmp2); + assert!(tmp1.is_zero()); + } +} + +#[test] +fn test_fs_mul_assign() { + let mut tmp = Fs(FsRepr([0xb433b01287f71744, 0x4eafb86728c4d108, 0xfdd52c14b9dfbe65, 0x2ff1f3434821118])); + tmp.mul_assign(&Fs(FsRepr([0xdae00fc63c9fa90f, 0x5a5ed89b96ce21ce, 0x913cd26101bd6f58, 0x3f0822831697fe9]))); + assert!(tmp == Fs(FsRepr([0xb68ecb61d54d2992, 0x5ff95874defce6a6, 0x3590eb053894657d, 0x53823a118515933]))); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * b) * c = a * (b * c) + let a = Fs::rand(&mut rng); + let b = Fs::rand(&mut rng); + let c = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.mul_assign(&b); + tmp1.mul_assign(&c); + + let mut tmp2 = b; + tmp2.mul_assign(&c); + tmp2.mul_assign(&a); + + assert_eq!(tmp1, tmp2); + } + + for _ in 0..1000000 { + // Ensure that r * (a + b + c) = r*a + r*b + r*c + + let r = Fs::rand(&mut rng); + let mut a = Fs::rand(&mut rng); + let mut b = Fs::rand(&mut rng); + let mut c = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + tmp1.mul_assign(&r); + + a.mul_assign(&r); + b.mul_assign(&r); + c.mul_assign(&r); + + a.add_assign(&b); + a.add_assign(&c); + + assert_eq!(tmp1, a); + } +} + +#[test] +fn test_fr_squaring() { + let mut a = Fs(FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xe7db4ea6533afa8])); + assert!(a.is_valid()); + a.square(); + assert_eq!(a, Fs::from_repr(FsRepr([0x12c7f55cbc52fbaa, 0xdedc98a0b5e6ce9e, 0xad2892726a5396a, 0x9fe82af8fee77b3])).unwrap()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * a) = a^2 + let a = Fs::rand(&mut rng); + + let mut tmp = a; + tmp.square(); + + let mut tmp2 = a; + tmp2.mul_assign(&a); + + assert_eq!(tmp, tmp2); + } +} + +#[test] +fn test_fs_inverse() { + assert!(Fs::zero().inverse().is_none()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let one = Fs::one(); + + for _ in 0..1000 { + // Ensure that a * a^-1 = 1 + let mut a = Fs::rand(&mut rng); + let ainv = a.inverse().unwrap(); + a.mul_assign(&ainv); + assert_eq!(a, one); + } +} + +#[test] +fn test_fs_double() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure doubling a is equivalent to adding a to itself. + let mut a = Fs::rand(&mut rng); + let mut b = a; + b.add_assign(&a); + a.double(); + assert_eq!(a, b); + } +} + +#[test] +fn test_fs_negate() { + { + let mut a = Fs::zero(); + a.negate(); + + assert!(a.is_zero()); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure (a - (-a)) = 0. + let mut a = Fs::rand(&mut rng); + let mut b = a; + b.negate(); + a.add_assign(&b); + + assert!(a.is_zero()); + } +} + +#[test] +fn test_fs_pow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for i in 0..1000 { + // Exponentiate by various small numbers and ensure it consists with repeated + // multiplication. + let a = Fs::rand(&mut rng); + let target = a.pow(&[i]); + let mut c = Fs::one(); + for _ in 0..i { + c.mul_assign(&a); + } + assert_eq!(c, target); + } + + for _ in 0..1000 { + // Exponentiating by the modulus should have no effect in a prime field. + let a = Fs::rand(&mut rng); + + assert_eq!(a, a.pow(Fs::char())); + } +} + +#[test] +fn test_fs_sqrt() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + assert_eq!(Fs::zero().sqrt().unwrap(), Fs::zero()); + + for _ in 0..1000 { + // Ensure sqrt(a^2) = a or -a + let a = Fs::rand(&mut rng); + let mut nega = a; + nega.negate(); + let mut b = a; + b.square(); + + let b = b.sqrt().unwrap(); + + assert!(a == b || nega == b); + } + + for _ in 0..1000 { + // Ensure sqrt(a)^2 = a for random a + let a = Fs::rand(&mut rng); + + if let Some(mut tmp) = a.sqrt() { + tmp.square(); + + assert_eq!(a, tmp); + } + } +} + +#[test] +fn test_fs_from_into_repr() { + // r + 1 should not be in the field + assert!(Fs::from_repr(FsRepr([0xd0970e5ed6f72cb8, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9])).is_err()); + + // r should not be in the field + assert!(Fs::from_repr(Fs::char()).is_err()); + + // Multiply some arbitrary representations to see if the result is as expected. + let a = FsRepr([0x5f2d0c05d0337b71, 0xa1df2b0f8a20479, 0xad73785e71bb863, 0x504a00480c9acec]); + let mut a_fs = Fs::from_repr(a).unwrap(); + let b = FsRepr([0x66356ff51e477562, 0x60a92ab55cf7603, 0x8e4273c7364dd192, 0x36df8844a344dc5]); + let b_fs = Fs::from_repr(b).unwrap(); + let c = FsRepr([0x7eef61708f4f2868, 0x747a7e6cf52946fb, 0x83dd75d7c9120017, 0x762f5177f0f3df7]); + a_fs.mul_assign(&b_fs); + assert_eq!(a_fs.into_repr(), c); + + // Zero should be in the field. + assert!(Fs::from_repr(FsRepr::from(0)).unwrap().is_zero()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Try to turn Fs elements into representations and back again, and compare. + let a = Fs::rand(&mut rng); + let a_repr = a.into_repr(); + let b_repr = FsRepr::from(a); + assert_eq!(a_repr, b_repr); + let a_again = Fs::from_repr(a_repr).unwrap(); + + assert_eq!(a, a_again); + } +} + +#[test] +fn test_fs_repr_display() { + assert_eq!( + format!("{}", FsRepr([0xa296db59787359df, 0x8d3e33077430d318, 0xd1abf5c606102eb7, 0xcbc33ee28108f0])), + "0x00cbc33ee28108f0d1abf5c606102eb78d3e33077430d318a296db59787359df".to_string() + ); + assert_eq!( + format!("{}", FsRepr([0x14cb03535054a620, 0x312aa2bf2d1dff52, 0x970fe98746ab9361, 0xc1e18acf82711e6])), + "0x0c1e18acf82711e6970fe98746ab9361312aa2bf2d1dff5214cb03535054a620".to_string() + ); + assert_eq!( + format!("{}", FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() + ); + assert_eq!( + format!("{}", FsRepr([0, 0, 0, 0])), + "0x0000000000000000000000000000000000000000000000000000000000000000".to_string() + ); +} + +#[test] +fn test_fs_display() { + assert_eq!( + format!("{}", Fs::from_repr(FsRepr([0x5528efb9998a01a3, 0x5bd2add5cb357089, 0xc061fa6adb491f98, 0x70db9d143db03d9])).unwrap()), + "Fs(0x070db9d143db03d9c061fa6adb491f985bd2add5cb3570895528efb9998a01a3)".to_string() + ); + assert_eq!( + format!("{}", Fs::from_repr(FsRepr([0xd674745e2717999e, 0xbeb1f52d3e96f338, 0x9c7ae147549482b9, 0x999706024530d22])).unwrap()), + "Fs(0x0999706024530d229c7ae147549482b9beb1f52d3e96f338d674745e2717999e)".to_string() + ); +} + +#[test] +fn test_fs_num_bits() { + assert_eq!(Fs::NUM_BITS, 252); + assert_eq!(Fs::CAPACITY, 251); +} + +#[test] +fn test_fs_root_of_unity() { + assert_eq!(Fs::S, 1); + assert_eq!(Fs::multiplicative_generator(), Fs::from_repr(FsRepr::from(6)).unwrap()); + assert_eq!( + Fs::multiplicative_generator().pow([0x684b872f6b7b965b, 0x53341049e6640841, 0x83339d80809a1d80, 0x73eda753299d7d4]), + Fs::root_of_unity() + ); + assert_eq!( + Fs::root_of_unity().pow([1 << Fs::S]), + Fs::one() + ); + assert!(Fs::multiplicative_generator().sqrt().is_none()); +} + +// TODO +/* +#[test] +fn fr_field_tests() { + ::tests::field::random_field_tests::(); + ::tests::field::random_sqrt_tests::(); + ::tests::field::random_frobenius_tests::(Fr::char(), 13); + ::tests::field::from_str_tests::(); +} + +#[test] +fn fr_repr_tests() { + ::tests::repr::random_repr_tests::(); +} +*/ diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs new file mode 100644 index 000000000..2bffda863 --- /dev/null +++ b/src/jubjub/mod.rs @@ -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 { + edwards_d: E::Fr, + montgomery_a: E::Fr, + + scale: E::Fr +} + +pub enum Unknown { } +pub enum PrimeOrder { } + +impl JubjubParams { + 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); + } +} diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs new file mode 100644 index 000000000..559d69abd --- /dev/null +++ b/src/jubjub/montgomery.rs @@ -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 { + x: E::Fr, + y: E::Fr, + infinity: bool, + _marker: PhantomData +} + +fn convert_subgroup(from: &Point) -> Point +{ + Point { + x: from.x, + y: from.y, + infinity: from.infinity, + _marker: PhantomData + } +} + +impl From> for Point +{ + fn from(p: Point) -> Point + { + convert_subgroup(&p) + } +} + +impl Clone for Point +{ + fn clone(&self) -> Self { + convert_subgroup(self) + } +} + +impl PartialEq for Point { + fn eq(&self, other: &Point) -> 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 Point { + /// This guarantees the point is in the prime order subgroup + pub fn mul_by_cofactor(&self, params: &JubjubParams) -> Point + { + let tmp = self.double(params) + .double(params) + .double(params); + + convert_subgroup(&tmp) + } + + pub fn rand(rng: &mut R, params: &JubjubParams) -> 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 Point { + /// Convert from an Edwards point + pub fn from_edwards( + e: &edwards::Point, + params: &JubjubParams + ) -> 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) -> Option> { + 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) -> 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) -> 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>(&self, scalar: S, params: &JubjubParams) -> 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( + x: E::Fr, + y: E::Fr, + params: &JubjubParams + ) -> 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::::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::::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> = 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); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index e4c902b58..552fe0154 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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;