diff --git a/Cargo.toml b/Cargo.toml index a60e138..0135682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,11 @@ blake2 = "0.7" digest = "0.7" bellman = "0.0.8" +byteorder = "1" + +[dev-dependencies] +hex-literal = "0.1" + [features] default = ["u128-support"] u128-support = ["pairing/u128-support"] diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 23ec447..18bb4d0 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -2,7 +2,6 @@ use pairing::{ Engine, Field, PrimeField, - PrimeFieldRepr, BitIterator }; @@ -34,6 +33,44 @@ impl AllocatedBit { self.variable } + /// Allocate a variable in the constraint system which can only be a + /// boolean value. Further, constrain that the boolean is false + /// unless the condition is false. + pub fn alloc_conditionally( + mut cs: CS, + value: Option, + must_be_false: &AllocatedBit + ) -> 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 - must_be_false - a) * a = 0 + // if must_be_false is true, the equation + // reduces to -a * a = 0, which implies a = 0. + // if must_be_false is false, the equation + // reduces to (1 - a) * a = 0, which is a + // traditional boolean constraint. + cs.enforce( + || "boolean constraint", + |lc| lc + CS::one() - must_be_false.variable - var, + |lc| lc + var, + |lc| lc + ); + + Ok(AllocatedBit { + variable: var, + value: value + }) + } + /// Allocate a variable in the constraint system which can only be a /// boolean value. pub fn alloc( @@ -328,7 +365,34 @@ impl Boolean { { let c = Self::xor(&mut cs, a, b)?; - Self::enforce_nand(&mut cs, &[c]) + match c { + Boolean::Constant(false) => { + Ok(()) + }, + Boolean::Constant(true) => { + Err(SynthesisError::Unsatisfiable) + }, + Boolean::Is(ref res) => { + cs.enforce( + || "enforce equals zero", + |lc| lc, + |lc| lc, + |lc| lc + res.get_variable() + ); + + Ok(()) + }, + Boolean::Not(ref res) => { + cs.enforce( + || "enforce equals one", + |lc| lc, + |lc| lc, + |lc| lc + CS::one() - res.get_variable() + ); + + Ok(()) + }, + } } pub fn get_value(&self) -> Option { @@ -431,140 +495,6 @@ impl Boolean { } } } - - pub fn kary_and( - mut cs: CS, - bits: &[Self] - ) -> Result - where E: Engine, - CS: ConstraintSystem - { - assert!(bits.len() > 0); - let mut bits = bits.iter(); - - let mut cur: Self = bits.next().unwrap().clone(); - - let mut i = 0; - while let Some(next) = bits.next() { - cur = Boolean::and(cs.namespace(|| format!("AND {}", i)), &cur, next)?; - - i += 1; - } - - Ok(cur) - } - - /// Asserts that at least one operand is false. - pub fn enforce_nand( - mut cs: CS, - bits: &[Self] - ) -> Result<(), SynthesisError> - where E: Engine, - CS: ConstraintSystem - { - let res = Self::kary_and(&mut cs, bits)?; - - match res { - Boolean::Constant(false) => { - Ok(()) - }, - Boolean::Constant(true) => { - Err(SynthesisError::Unsatisfiable) - }, - Boolean::Is(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + res.get_variable() - ); - - Ok(()) - }, - Boolean::Not(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + CS::one() - res.get_variable() - ); - - Ok(()) - }, - } - } - - /// Asserts that this bit representation is "in - /// the field" when interpreted in big endian. - pub fn enforce_in_field( - mut cs: CS, - bits: &[Self] - ) -> Result<(), SynthesisError> - where E: Engine, - CS: ConstraintSystem - { - assert_eq!(bits.len(), F::NUM_BITS as usize); - - let mut a = bits.iter(); - - // b = char() - 1 - let mut b = F::char(); - b.sub_noborrow(&1.into()); - - // Runs of ones in r - let mut last_run = Boolean::constant(true); - let mut current_run = vec![]; - - let mut found_one = false; - let mut run_i = 0; - let mut nand_i = 0; - for b in BitIterator::new(b) { - // Skip over unset bits at the beginning - found_one |= b; - if !found_one { - continue; - } - - let a = a.next().unwrap(); - - if b { - // This is part of a run of ones. - current_run.push(a.clone()); - } else { - if current_run.len() > 0 { - // This is the start of a run of zeros, but we need - // to k-ary AND against `last_run` first. - - current_run.push(last_run.clone()); - last_run = Self::kary_and( - cs.namespace(|| format!("run {}", run_i)), - ¤t_run - )?; - run_i += 1; - current_run.truncate(0); - } - - // If `last_run` is true, `a` must be false, or it would - // not be in the field. - // - // If `last_run` is false, `a` can be true or false. - // - // Ergo, at least one of `last_run` and `a` must be false. - Self::enforce_nand( - cs.namespace(|| format!("nand {}", nand_i)), - &[last_run.clone(), a.clone()] - )?; - nand_i += 1; - } - } - - // We should always end in a "run" of zeros, because - // the characteristic is an odd prime. So, this should - // be empty. - assert_eq!(current_run.len(), 0); - - Ok(()) - } } impl From for Boolean { @@ -575,10 +505,9 @@ impl From for Boolean { #[cfg(test)] mod test { - use rand::{SeedableRng, Rand, XorShiftRng}; use bellman::{ConstraintSystem}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator}; + use pairing::{Field, PrimeField}; use ::circuit::test::*; use super::{ AllocatedBit, @@ -1052,160 +981,6 @@ mod test { } } - #[test] - fn test_enforce_in_field() { - { - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(!cs.is_satisfied()); - } - - let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..1000 { - let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(cs.is_satisfied()); - } - - for _ in 0..1000 { - // Sample a random element not in the field - let r = loop { - let mut a = Fr::rand(&mut rng).into_repr(); - let b = Fr::rand(&mut rng).into_repr(); - - a.add_nocarry(&b); - // we're shaving off the high bit later - a.as_mut()[3] &= 0x7fffffffffffffff; - if Fr::from_repr(a).is_err() { - break a; - } - }; - - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(!cs.is_satisfied()); - } - } - - #[test] - fn test_enforce_nand() { - { - let mut cs = TestConstraintSystem::::new(); - - Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok(); - Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err(); - } - - for i in 1..5 { - // with every possible assignment for them - for mut b in 0..(1 << i) { - // with every possible negation - for mut n in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); - - let mut expected = true; - - let mut bits = vec![]; - for j in 0..i { - expected &= b & 1 == 1; - - if n & 1 == 1 { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 1) - ).unwrap())); - } else { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 0) - ).unwrap()).not()); - } - - b >>= 1; - n >>= 1; - } - - let expected = !expected; - - Boolean::enforce_nand(&mut cs, &bits).unwrap(); - - if expected { - assert!(cs.is_satisfied()); - } else { - assert!(!cs.is_satisfied()); - } - } - } - } - } - - #[test] - fn test_kary_and() { - // test different numbers of operands - for i in 1..15 { - // with every possible assignment for them - for mut b in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); - - let mut expected = true; - - let mut bits = vec![]; - for j in 0..i { - expected &= b & 1 == 1; - - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 1) - ).unwrap())); - b >>= 1; - } - - let r = Boolean::kary_and(&mut cs, &bits).unwrap(); - - assert!(cs.is_satisfied()); - - match r { - Boolean::Is(ref r) => { - assert_eq!(r.value.unwrap(), expected); - }, - _ => unreachable!() - } - } - } - } - #[test] fn test_u64_into_allocated_bits_be() { let mut cs = TestConstraintSystem::::new(); diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index fbb9b72..0828812 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -30,20 +30,12 @@ use super::lookup::{ use super::boolean::Boolean; +#[derive(Clone)] pub struct EdwardsPoint { pub x: AllocatedNum, pub y: AllocatedNum } -impl Clone for EdwardsPoint { - fn clone(&self) -> Self { - EdwardsPoint { - x: self.x.clone(), - y: self.y.clone() - } - } -} - /// Perform a fixed-base scalar multiplication with /// `by` being in little-endian bit order. pub fn fixed_base_multiplication( @@ -92,6 +84,33 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + /// This converts the point into a representation. + pub fn repr( + &self, + mut cs: CS + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + let mut tmp = vec![]; + + let mut x = self.x.into_bits_strict( + cs.namespace(|| "unpack x") + )?; + + let mut y = self.y.into_bits_strict( + cs.namespace(|| "unpack y") + )?; + + // We want the representation in little endian bit order + x.reverse(); + y.reverse(); + + tmp.extend(y); + tmp.push(x[0].clone()); + + Ok(tmp) + } + /// This 'witnesses' a point inside the constraint system. /// It guarantees the point is on the curve. pub fn witness( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index cf27c34..e1256ef 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -9,7 +9,23 @@ pub mod lookup; pub mod ecc; pub mod pedersen_hash; -use bellman::SynthesisError; +use pairing::{ + PrimeField, + PrimeFieldRepr, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + Circuit +}; + +use jubjub::{ + JubjubEngine, + Unknown, + FixedGenerators, + edwards +}; trait Assignment { fn get(&self) -> Result<&T, SynthesisError>; @@ -23,3 +39,701 @@ impl Assignment for Option { } } } + +pub struct Spend<'a, E: JubjubEngine> { + pub params: &'a E::Params, + /// Value of the note being spent + pub value: Option, + /// Randomness that will hide the value + pub value_randomness: Option, + /// Key which allows the proof to be constructed + /// as defense-in-depth against a flaw in the + /// protocol that would otherwise be exploitable + /// by a holder of a viewing key. + pub rsk: Option, + /// The public key that will be re-randomized for + /// use as a nullifier and signing key for the + /// transaction. + pub ak: Option>, + /// The diversified base used to compute pk_d. + pub g_d: Option>, + /// The randomness used to hide the note commitment data + pub commitment_randomness: Option, + /// The authentication path of the commitment in the tree + pub auth_path: Vec> +} + +impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_allocated_bits_be( + cs.namespace(|| "value"), + self.value + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + { + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + self.params + )?; + + // Booleanize the randomness + let hr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "hr"), + self.value_randomness + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + self.params + )?; + + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + self.params + )?; + + // Expose the value commitment publicly + let value_commitment_x = cs.alloc_input( + || "value commitment x", + || { + Ok(*gvhr.x.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment x equals input", + |lc| lc + value_commitment_x, + |lc| lc + CS::one(), + |lc| lc + gvhr.x.get_variable() + ); + + let value_commitment_y = cs.alloc_input( + || "value commitment y", + || { + Ok(*gvhr.y.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment y equals input", + |lc| lc + value_commitment_y, + |lc| lc + CS::one(), + |lc| lc + gvhr.y.get_variable() + ); + } + + // Compute rk = [rsk] ProvingPublicKey + let rk; + { + // Witness rsk as bits + let rsk = boolean::field_into_allocated_bits_be( + cs.namespace(|| "rsk"), + self.rsk + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)).collect::>(); + + // NB: We don't ensure that the bit representation of rsk + // is "in the field" (Fs) because it's not used except to + // demonstrate the prover knows it. If they know a + // congruency then that's equivalent. + + rk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rk"), + FixedGenerators::ProvingPublicKey, + &rsk, + self.params + )?; + } + + // Prover witnesses ak (ensures that it's on the curve) + let ak = ecc::EdwardsPoint::witness( + cs.namespace(|| "ak"), + self.ak, + self.params + )?; + + // Unpack ak and rk for input to BLAKE2s + let mut vk = vec![]; + let mut rho_preimage = vec![]; + vk.extend( + ak.repr(cs.namespace(|| "representation of ak"))? + ); + { + let repr_rk = rk.repr( + cs.namespace(|| "representation of rk") + )?; + + vk.extend(repr_rk.iter().cloned()); + rho_preimage.extend(repr_rk); + } + + assert_eq!(vk.len(), 512); + + // Compute the incoming viewing key + let mut ivk = blake2s::blake2s( + cs.namespace(|| "computation of ivk"), + &vk + )?; + + // Little endian bit order + ivk.reverse(); + ivk.truncate(E::Fs::CAPACITY as usize); // drop_5 + + // Witness g_d + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.g_d, + self.params + )?; + + // Compute pk_d + let pk_d = g_d.mul( + cs.namespace(|| "compute pk_d"), + &ivk, + self.params + )?; + + // Compute note contents + let mut note_contents = vec![]; + note_contents.extend(value_bits); + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + note_contents.extend( + pk_d.repr(cs.namespace(|| "representation of pk_d"))? + ); + + assert_eq!( + note_contents.len(), + 64 + // value + 256 + // g_d + 256 // p_d + ); + + // Compute the hash of the note contents + let mut cm = pedersen_hash::pedersen_hash( + cs.namespace(|| "note content hash"), + pedersen_hash::Personalization::NoteCommitment, + ¬e_contents, + self.params + )?; + + { + // Booleanize the randomness + let cmr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "cmr"), + self.commitment_randomness + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + let tree_depth = self.auth_path.len(); + + let mut position_bits = vec![]; + + // Injective encoding. + let mut cur = cm.x.clone(); + + for (i, e) in self.auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); + + let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "position bit"), + e.map(|e| e.1) + )?); + + position_bits.push(cur_is_right.clone()); + + let path_element = num::AllocatedNum::alloc( + cs.namespace(|| "path element"), + || { + Ok(e.get()?.0) + } + )?; + + let (xl, xr) = num::AllocatedNum::conditionally_reverse( + cs.namespace(|| "conditional reversal of preimage"), + &cur, + &path_element, + &cur_is_right + )?; + + // We don't need to be strict, because the function is + // collision-resistant. + let mut preimage = vec![]; + preimage.extend(xl.into_bits(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?); + + cur = pedersen_hash::pedersen_hash( + cs.namespace(|| "computation of pedersen hash"), + pedersen_hash::Personalization::MerkleTree(tree_depth - i), + &preimage, + self.params + )?.x; // Injective encoding + } + + assert_eq!(position_bits.len(), tree_depth); + + { + // Expose the anchor + let anchor = cs.alloc_input( + || "anchor x", + || { + Ok(*cur.get_value().get()?) + } + )?; + + cs.enforce( + || "anchor x equals anchor", + |lc| lc + anchor, + |lc| lc + CS::one(), + |lc| lc + cur.get_variable() + ); + } + + { + let position = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &position, + self.params + )?; + } + + // Let's compute rho = BLAKE2s(rk || cm + position) + rho_preimage.extend( + cm.repr(cs.namespace(|| "representation of cm"))? + ); + + assert_eq!(rho_preimage.len(), 512); + + let mut rho = blake2s::blake2s( + cs.namespace(|| "rho computation"), + &rho_preimage + )?; + + // Little endian bit order + rho.reverse(); + rho.truncate(251); // drop_5 + + // Compute nullifier + let nf = ak.mul( + cs.namespace(|| "computation of nf"), + &rho, + self.params + )?; + + { + // Expose the nullifier publicly + let nf_x = cs.alloc_input( + || "nf_x", + || { + Ok(*nf.x.get_value().get()?) + } + )?; + + cs.enforce( + || "nf_x equals input", + |lc| lc + nf_x, + |lc| lc + CS::one(), + |lc| lc + nf.x.get_variable() + ); + + let nf_y = cs.alloc_input( + || "nf_y", + || { + Ok(*nf.y.get_value().get()?) + } + )?; + + cs.enforce( + || "nf_y equals input", + |lc| lc + nf_y, + |lc| lc + CS::one(), + |lc| lc + nf.y.get_variable() + ); + } + + Ok(()) + } +} + +/// This is an output circuit instance. +pub struct Output<'a, E: JubjubEngine> { + pub params: &'a E::Params, + /// Value of the note being created + pub value: Option, + /// Randomness that will hide the value + pub value_randomness: Option, + /// The diversified base, computed by GH(d) + pub g_d: Option>, + /// The diversified address point, computed by GH(d)^ivk + pub p_d: Option>, + /// The randomness used to hide the note commitment data + pub commitment_randomness: Option, + /// The ephemeral secret key for DH with recipient + pub esk: Option +} + +impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_allocated_bits_be( + cs.namespace(|| "value"), + self.value + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + { + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + self.params + )?; + + // Booleanize the randomness + let hr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "hr"), + self.value_randomness + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + self.params + )?; + + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + self.params + )?; + + // Expose the value commitment publicly + let value_commitment_x = cs.alloc_input( + || "value commitment x", + || { + Ok(*gvhr.x.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment x equals input", + |lc| lc + value_commitment_x, + |lc| lc + CS::one(), + |lc| lc + gvhr.x.get_variable() + ); + + let value_commitment_y = cs.alloc_input( + || "value commitment y", + || { + Ok(*gvhr.y.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment y equals input", + |lc| lc + value_commitment_y, + |lc| lc + CS::one(), + |lc| lc + gvhr.y.get_variable() + ); + } + + // Let's start to construct our note + let mut note_contents = vec![]; + note_contents.extend(value_bits); + + // Let's deal with g_d + { + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.g_d, + self.params + )?; + + // Check that g_d is not of small order + { + let g_d = g_d.double( + cs.namespace(|| "first doubling of g_d"), + self.params + )?; + let g_d = g_d.double( + cs.namespace(|| "second doubling of g_d"), + self.params + )?; + let g_d = g_d.double( + cs.namespace(|| "third doubling of g_d"), + self.params + )?; + + // (0, -1) is a small order point, but won't ever appear here + // because cofactor is 2^3, and we performed three doublings. + // (0, 1) is the neutral element, so checking if x is nonzero + // is sufficient to prevent small order points here. + g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; + } + + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + + // Compute epk from esk + let esk = boolean::field_into_allocated_bits_be( + cs.namespace(|| "esk"), + self.esk + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let epk = g_d.mul( + cs.namespace(|| "epk computation"), + &esk, + self.params + )?; + + // Expose epk publicly + let epk_x = cs.alloc_input( + || "epk x", + || { + Ok(*epk.x.get_value().get()?) + } + )?; + + cs.enforce( + || "epk x equals input", + |lc| lc + epk_x, + |lc| lc + CS::one(), + |lc| lc + epk.x.get_variable() + ); + + let epk_y = cs.alloc_input( + || "epk y", + || { + Ok(*epk.y.get_value().get()?) + } + )?; + + cs.enforce( + || "epk y equals input", + |lc| lc + epk_y, + |lc| lc + CS::one(), + |lc| lc + epk.y.get_variable() + ); + } + + // Now let's deal with p_d. We don't do any checks and + // essentially allow the prover to witness any 256 bits + // they would like. + { + let p_d = self.p_d.map(|e| e.into_xy()); + + let y_contents = boolean::field_into_allocated_bits_be( + cs.namespace(|| "p_d bits of y"), + p_d.map(|e| e.1) + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "p_d bit of x"), + p_d.map(|e| e.0.into_repr().is_odd()) + )?); + + note_contents.extend(y_contents); + note_contents.push(sign_bit); + } + + assert_eq!( + note_contents.len(), + 64 + // value + 256 + // g_d + 256 // p_d + ); + + // Compute the hash of the note contents + let mut cm = pedersen_hash::pedersen_hash( + cs.namespace(|| "note content hash"), + pedersen_hash::Personalization::NoteCommitment, + ¬e_contents, + self.params + )?; + + { + // Booleanize the randomness + let cmr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "cmr"), + self.commitment_randomness + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + // Only the x-coordinate of the output is revealed, + // since we know it is prime order, and we know that + // the x-coordinate is an injective encoding for + // prime-order elements. + let commitment_input = cs.alloc_input( + || "commitment input", + || { + Ok(*cm.x.get_value().get()?) + } + )?; + + cs.enforce( + || "commitment input correct", + |lc| lc + commitment_input, + |lc| lc + CS::one(), + |lc| lc + cm.x.get_variable() + ); + + Ok(()) + } +} + +#[test] +fn test_input_circuit_with_bls12_381() { + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let tree_depth = 29; + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let ak: edwards::Point = edwards::Point::rand(rng, params); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Spend { + params: params, + value: Some(value), + value_randomness: Some(value_randomness), + rsk: Some(rsk), + ak: Some(ak), + g_d: Some(g_d), + commitment_randomness: Some(commitment_randomness), + auth_path: auth_path + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 97379); + assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654"); + } +} + +#[test] +fn test_output_circuit_with_bls12_381() { + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let p_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Output { + params: params, + value: Some(value), + value_randomness: Some(value_randomness), + g_d: Some(g_d.clone()), + p_d: Some(p_d.clone()), + commitment_randomness: Some(commitment_randomness), + esk: Some(esk.clone()) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 7827); + assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793"); + } +} diff --git a/src/circuit/num.rs b/src/circuit/num.rs index fe20050..92e25c7 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -2,6 +2,8 @@ use pairing::{ Engine, Field, PrimeField, + PrimeFieldRepr, + BitIterator }; use bellman::{ @@ -18,6 +20,7 @@ use super::{ use super::boolean::{ self, Boolean, + AllocatedBit }; pub struct AllocatedNum { @@ -63,27 +66,111 @@ impl AllocatedNum { ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bits = self.into_bits(&mut cs)?; - Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?; + pub fn kary_and( + mut cs: CS, + v: &[AllocatedBit] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + assert!(v.len() > 0); - Ok(bits) - } + // Let's keep this simple for now and just AND them all + // manually + let mut cur = None; - pub fn into_bits( - &self, - mut cs: CS - ) -> Result, SynthesisError> - where CS: ConstraintSystem - { - let bits = boolean::field_into_allocated_bits_be( - &mut cs, - self.value - )?; + for (i, v) in v.iter().enumerate() { + if cur.is_none() { + cur = Some(v.clone()); + } else { + cur = Some(AllocatedBit::and( + cs.namespace(|| format!("and {}", i)), + cur.as_ref().unwrap(), + v + )?); + } + } + + Ok(cur.expect("v.len() > 0")) + } + + // We want to ensure that the bit representation of a is + // less than or equal to r - 1. + let mut a = self.value.map(|e| BitIterator::new(e.into_repr())); + let mut b = E::Fr::char(); + b.sub_noborrow(&1.into()); + + let mut result = vec![]; + + // Runs of ones in r + let mut last_run = None; + let mut current_run = vec![]; + + let mut found_one = false; + let mut i = 0; + for b in BitIterator::new(b) { + let a_bit = a.as_mut().map(|e| e.next().unwrap()); + + // Skip over unset bits at the beginning + found_one |= b; + if !found_one { + // a_bit should also be false + a_bit.map(|e| assert!(!e)); + continue; + } + + if b { + // This is part of a run of ones. Let's just + // allocate the boolean with the expected value. + let a_bit = AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + a_bit + )?; + // ... and add it to the current run of ones. + current_run.push(a_bit.clone()); + result.push(a_bit); + } else { + if current_run.len() > 0 { + // This is the start of a run of zeros, but we need + // to k-ary AND against `last_run` first. + + if last_run.is_some() { + current_run.push(last_run.clone().unwrap()); + } + last_run = Some(kary_and( + cs.namespace(|| format!("run ending at {}", i)), + ¤t_run + )?); + current_run.truncate(0); + } + + // If `last_run` is true, `a` must be false, or it would + // not be in the field. + // + // If `last_run` is false, `a` can be true or false. + + let a_bit = AllocatedBit::alloc_conditionally( + cs.namespace(|| format!("bit {}", i)), + a_bit, + &last_run.as_ref().expect("char always starts with a one") + )?; + result.push(a_bit); + } + + i += 1; + } + + // char is prime, so we'll always end on + // a run of zeros. + assert_eq!(current_run.len(), 0); + + // Now, we have `result` in big-endian order. + // However, now we have to unpack self! let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); - for bit in bits.iter().rev() { + for bit in result.iter().rev() { lc = lc + (coeff, bit.get_variable()); coeff.double(); @@ -98,76 +185,39 @@ impl AllocatedNum { |_| lc ); - Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) + Ok(result.into_iter().map(|b| Boolean::from(b)).collect()) } - pub fn from_bits_strict( - mut cs: CS, - bits: &[Boolean] - ) -> Result + pub fn into_bits( + &self, + mut cs: CS + ) -> Result, SynthesisError> where CS: ConstraintSystem { - assert_eq!(bits.len(), E::Fr::NUM_BITS as usize); - - Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?; + let bits = boolean::field_into_allocated_bits_be( + &mut cs, + self.value + )?; - let one = CS::one(); - let mut lc = LinearCombination::::zero(); + let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); - let mut value = Some(E::Fr::zero()); - for bit in bits.iter().rev() { - match bit { - &Boolean::Constant(false) => {}, - &Boolean::Constant(true) => { - value.as_mut().map(|value| value.add_assign(&coeff)); - - lc = lc + (coeff, one); - }, - &Boolean::Is(ref bit) => { - match bit.get_value() { - Some(bit) => { - if bit { - value.as_mut().map(|value| value.add_assign(&coeff)); - } - }, - None => { - value = None; - } - } - - lc = lc + (coeff, bit.get_variable()); - }, - &Boolean::Not(ref bit) => { - match bit.get_value() { - Some(bit) => { - if !bit { - value.as_mut().map(|value| value.add_assign(&coeff)); - } - }, - None => { - value = None; - } - } - lc = lc + (coeff, one) - (coeff, bit.get_variable()); - } - } + for bit in bits.iter().rev() { + lc = lc + (coeff, bit.get_variable()); coeff.double(); } - let num = Self::alloc(&mut cs, || value.get().map(|v| *v))?; - - lc = lc - num.get_variable(); + lc = lc - self.variable; cs.enforce( - || "packing constraint", + || "unpacking constraint", |lc| lc, |lc| lc, |_| lc ); - Ok(num) + Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) } pub fn mul( @@ -384,7 +434,6 @@ mod test { use pairing::{Field, PrimeField, BitIterator}; use ::circuit::test::*; use super::{AllocatedNum, Boolean}; - use super::super::boolean::AllocatedBit; #[test] fn test_allocated_num() { @@ -491,31 +540,25 @@ mod test { // make the bit representation the characteristic cs.set("bit 254/boolean", Fr::one()); - // this makes the unpacking constraint fail - assert_eq!(cs.which_is_unsatisfied().unwrap(), "unpacking constraint"); - - // fix it by making the number zero (congruent to the characteristic) - cs.set("num", Fr::zero()); - - // and constraint is disturbed during enforce in field check - assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/AND 0/and constraint"); - cs.set("nand 121/AND 0/and result", Fr::one()); - - // now the nand should fail (enforce in field is working) - assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/enforce nand"); + // this makes the conditional boolean constraint fail + assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint"); } #[test] fn test_into_bits() { let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - for _ in 0..100 { + for i in 0..200 { let r = Fr::rand(&mut rng); let mut cs = TestConstraintSystem::::new(); let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); - let bits = n.into_bits(&mut cs).unwrap(); + let bits = if i % 2 == 0 { + n.into_bits(&mut cs).unwrap() + } else { + n.into_bits_strict(&mut cs).unwrap() + }; assert!(cs.is_satisfied()); @@ -544,55 +587,4 @@ mod test { } } } - - #[test] - fn test_from_bits_strict() { - { - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); - assert!(num.value.unwrap().is_zero()); - assert!(!cs.is_satisfied()); - } - - let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..1000 { - let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - let parity: bool = rng.gen(); - - if parity { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } else { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(!b) - ).unwrap()).not()); - } - } - - let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); - assert!(cs.is_satisfied()); - assert_eq!(num.value.unwrap(), r); - assert_eq!(cs.get("num"), r); - - cs.set("num", Fr::rand(&mut rng)); - assert_eq!(cs.which_is_unsatisfied().unwrap(), "packing constraint"); - } - } } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 9dfb460..7eec3bb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -13,7 +13,8 @@ use super::lookup::*; // TODO: ensure these match the spec pub enum Personalization { NoteCommitment, - AnotherPersonalization + AnotherPersonalization, + MerkleTree(usize) } impl Personalization { @@ -30,6 +31,8 @@ impl Personalization { vec![false, false, false, false, false, false], Personalization::AnotherPersonalization => vec![false, false, false, false, false, true], + Personalization::MerkleTree(_) => + vec![false, false, false, false, true, false], } } } diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index ef042fe..e7f7515 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -1,6 +1,8 @@ use pairing::{ Engine, - Field + Field, + PrimeField, + PrimeFieldRepr }; use bellman::{ @@ -12,6 +14,13 @@ use bellman::{ }; use std::collections::HashMap; +use std::fmt::Write; + +use blake2::{Blake2s}; +use digest::{FixedOutput, Input}; +use byteorder::{BigEndian, ByteOrder}; +use std::cmp::Ordering; +use std::collections::BTreeMap; #[derive(Debug)] enum NamedObject { @@ -34,6 +43,90 @@ pub struct TestConstraintSystem { aux: Vec<(E::Fr, String)> } +#[derive(Clone, Copy)] +struct OrderedVariable(Variable); + +impl Eq for OrderedVariable {} +impl PartialEq for OrderedVariable { + fn eq(&self, other: &OrderedVariable) -> bool { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) => a == b, + (Index::Aux(ref a), Index::Aux(ref b)) => a == b, + _ => false + } + } +} +impl PartialOrd for OrderedVariable { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for OrderedVariable { + fn cmp(&self, other: &Self) -> Ordering { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) => a.cmp(b), + (Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b), + (Index::Input(_), Index::Aux(_)) => Ordering::Less, + (Index::Aux(_), Index::Input(_)) => Ordering::Greater + } + } +} + +fn proc_lc( + terms: &[(Variable, E::Fr)], +) -> BTreeMap +{ + let mut map = BTreeMap::new(); + for &(var, coeff) in terms { + map.entry(OrderedVariable(var)) + .or_insert(E::Fr::zero()) + .add_assign(&coeff); + } + + // Remove terms that have a zero coefficient to normalize + let mut to_remove = vec![]; + for (var, coeff) in map.iter() { + if coeff.is_zero() { + to_remove.push(var.clone()) + } + } + + for var in to_remove { + map.remove(&var); + } + + map +} + +fn hash_lc( + terms: &[(Variable, E::Fr)], + h: &mut Blake2s +) +{ + let map = proc_lc::(terms); + + let mut buf = [0u8; 9 + 32]; + BigEndian::write_u64(&mut buf[0..8], map.len() as u64); + h.process(&buf[0..8]); + + for (var, coeff) in map { + match var.0.get_unchecked() { + Index::Input(i) => { + buf[0] = b'I'; + BigEndian::write_u64(&mut buf[1..9], i as u64); + }, + Index::Aux(i) => { + buf[0] = b'A'; + BigEndian::write_u64(&mut buf[1..9], i as u64); + } + } + + coeff.into_repr().write_be(&mut buf[9..]).unwrap(); + + h.process(&buf); + } +} + fn eval_lc( terms: &[(Variable, E::Fr)], inputs: &[(E::Fr, String)], @@ -69,6 +162,98 @@ impl TestConstraintSystem { } } + pub fn pretty_print(&self) -> String { + let mut s = String::new(); + + let negone = { + let mut tmp = E::Fr::one(); + tmp.negate(); + tmp + }; + + let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| { + E::Fr::from_str("2").unwrap().pow(&[i as u64]) + }).collect::>(); + + let pp = |s: &mut String, lc: &LinearCombination| { + write!(s, "(").unwrap(); + let mut is_first = true; + for (var, coeff) in proc_lc::(lc.as_ref()) { + if coeff == negone { + write!(s, " - ").unwrap(); + } else if !is_first { + write!(s, " + ").unwrap(); + } + is_first = false; + + if coeff != E::Fr::one() && coeff != negone { + for (i, x) in powers_of_two.iter().enumerate() { + if x == &coeff { + write!(s, "2^{} . ", i).unwrap(); + break; + } + } + + write!(s, "{} . ", coeff).unwrap(); + } + + match var.0.get_unchecked() { + Index::Input(i) => { + write!(s, "`{}`", &self.inputs[i].1).unwrap(); + }, + Index::Aux(i) => { + write!(s, "`{}`", &self.aux[i].1).unwrap(); + } + } + } + if is_first { + // Nothing was visited, print 0. + write!(s, "0").unwrap(); + } + write!(s, ")").unwrap(); + }; + + for &(ref a, ref b, ref c, ref name) in &self.constraints { + write!(&mut s, "\n").unwrap(); + + write!(&mut s, "{}: ", name).unwrap(); + pp(&mut s, a); + write!(&mut s, " * ").unwrap(); + pp(&mut s, b); + write!(&mut s, " = ").unwrap(); + pp(&mut s, c); + } + + write!(&mut s, "\n").unwrap(); + + s + } + + pub fn hash(&self) -> String { + let mut h = Blake2s::new_keyed(&[], 32); + { + let mut buf = [0u8; 24]; + + BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64); + BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64); + BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64); + h.process(&buf); + } + + for constraint in &self.constraints { + hash_lc::(constraint.0.as_ref(), &mut h); + hash_lc::(constraint.1.as_ref(), &mut h); + hash_lc::(constraint.2.as_ref(), &mut h); + } + + let mut s = String::new(); + for b in h.fixed_result().as_ref() { + s += &format!("{:02x}", b); + } + + s + } + pub fn which_is_unsatisfied(&self) -> Option<&str> { for &(ref a, ref b, ref c, ref path) in &self.constraints { let mut a = eval_lc::(a.as_ref(), &self.inputs, &self.aux); diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index 130fd59..eeabe9d 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -20,6 +20,12 @@ use rand::{ use std::marker::PhantomData; +use std::io::{ + self, + Write, + Read +}; + // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. pub struct Point { @@ -80,7 +86,67 @@ impl PartialEq for Point { } } +fn swap_bits_u64(x: u64) -> u64 +{ + let mut tmp = 0; + for i in 0..64 { + tmp |= ((x >> i) & 1) << (63 - i); + } + tmp +} + +#[test] +fn test_swap_bits_u64() { + assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); + assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011); + assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010); + assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000); + assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101); + + let mut a = 15863238721320035327u64; + for _ in 0..1000 { + a = a.wrapping_mul(a); + + let swapped = swap_bits_u64(a); + let unswapped = swap_bits_u64(swapped); + + assert_eq!(a, unswapped); + } +} + impl Point { + pub fn read( + reader: R, + params: &E::Params + ) -> io::Result + { + let mut y_repr = ::Repr::default(); + y_repr.read_be(reader)?; + + y_repr.as_mut().reverse(); + + for b in y_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + let x_sign = (y_repr.as_ref()[3] >> 63) == 1; + y_repr.as_mut()[3] &= 0x7fffffffffffffff; + + match E::Fr::from_repr(y_repr) { + Ok(y) => { + match Self::get_for_y(y, x_sign, params) { + Some(p) => Ok(p), + None => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve")) + } + } + }, + Err(_) => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "y is not in field")) + } + } + } + pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option { // Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1) @@ -151,6 +217,30 @@ impl Point { } impl Point { + pub fn write( + &self, + writer: W + ) -> io::Result<()> + { + let (x, y) = self.into_xy(); + + assert_eq!(E::Fr::NUM_BITS, 255); + + let x_repr = x.into_repr(); + let mut y_repr = y.into_repr(); + if x_repr.is_odd() { + y_repr.as_mut()[3] |= 0x8000000000000000u64; + } + + y_repr.as_mut().reverse(); + + for b in y_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + y_repr.write_be(writer) + } + /// Convert from a Montgomery point pub fn from_montgomery( m: &montgomery::Point, diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 13495bc..189d434 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -71,7 +71,9 @@ pub enum FixedGenerators { ProvingPublicKey = 1, ValueCommitmentValue = 2, ValueCommitmentRandomness = 3, - Max = 4 + NullifierPosition = 4, + SpendingKeyGenerator = 5, + Max = 6 } pub struct JubjubBls12 { @@ -235,4 +237,14 @@ fn test_jubjub_bls12() { let params = JubjubBls12::new(); tests::test_suite::(¶ms); + + let test_repr = hex!("b9481dd1103b7d1f8578078eb429d3c476472f53e88c0eaefdf51334c7c8b98c"); + let p = edwards::Point::::read(&test_repr[..], ¶ms).unwrap(); + let q = edwards::Point::::get_for_y( + Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(), + false, + ¶ms + ).unwrap(); + + assert!(p == q); } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index e034854..dfd44d0 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -26,6 +26,7 @@ pub fn test_suite(params: &E::Params) { test_order::(params); test_mul_associativity::(params); test_loworder::(params); + test_read_write::(params); } fn is_on_mont_curve>( @@ -245,6 +246,21 @@ fn test_get_for(params: &E::Params) { } } +fn test_read_write(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let e = edwards::Point::::rand(rng, params); + + let mut v = vec![]; + e.write(&mut v).unwrap(); + + let e2 = edwards::Point::read(&v[..], params).unwrap(); + + assert!(e == e2); + } +} + fn test_rand(params: &E::Params) { let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/lib.rs b/src/lib.rs index 6e45309..48af45c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,12 @@ extern crate blake2; extern crate digest; extern crate rand; +extern crate byteorder; + +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + pub mod jubjub; pub mod circuit; pub mod group_hash;