diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index a81d6c7..b407b69 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -22,7 +22,6 @@ use bellman::{ use jubjub::{ JubjubEngine, - Unknown, PrimeOrder, FixedGenerators, edwards @@ -55,9 +54,9 @@ pub struct Spend<'a, E: JubjubEngine> { /// The public key that will be re-randomized for /// use as a nullifier and signing key for the /// transaction. - pub ak: Option>, + pub ak: Option>, /// The diversified base used to compute pk_d. - pub g_d: Option>, + 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 @@ -482,7 +481,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { #[test] fn test_input_circuit_with_bls12_381() { - use pairing::{Field}; + use pairing::{Field, BitIterator}; use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; @@ -495,10 +494,34 @@ fn test_input_circuit_with_bls12_381() { 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 ak: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.clone() + }; + + let viewing_key = proof_generation_key.into_viewing_key(params); + + let payment_address; + + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); + + if let Some(p) = viewing_key.into_payment_address( + diversifier, + params + ) + { + payment_address = p; + break; + } + } + + let g_d = payment_address.diversifier.g_d(params).unwrap(); + let commitment_randomness: fs::Fs = rng.gen(); let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; { @@ -510,7 +533,7 @@ fn test_input_circuit_with_bls12_381() { value_randomness: Some(value_randomness), rsk: Some(rsk), ak: Some(ak), - g_d: Some(g_d), + g_d: Some(g_d.clone()), commitment_randomness: Some(commitment_randomness), auth_path: auth_path.clone() }; @@ -535,9 +558,54 @@ fn test_input_circuit_with_bls12_381() { assert_eq!(cs.get_input(1, "value commitment/x/input variable"), expected_value_cm_xy.0); assert_eq!(cs.get_input(2, "value commitment/y/input variable"), expected_value_cm_xy.1); - cs.get_input(3, "anchor/input variable"); - cs.get_input(4, "nullifier/x/input variable"); - cs.get_input(5, "nullifier/y/input variable"); + let note = ::primitives::Note { + value: value, + g_d: g_d.clone(), + pk_d: payment_address.pk_d.clone(), + r: commitment_randomness.clone() + }; + + let mut position = 0u64; + let mut cur = note.cm(params); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); + + for (i, val) in auth_path.into_iter().enumerate() + { + let (uncle, b) = val.unwrap(); + + let mut lhs = cur; + let mut rhs = uncle; + + if b { + ::std::mem::swap(&mut lhs, &mut rhs); + } + + let mut lhs: Vec = BitIterator::new(lhs.into_repr()).collect(); + let mut rhs: Vec = BitIterator::new(rhs.into_repr()).collect(); + + lhs.reverse(); + rhs.reverse(); + + cur = ::pedersen_hash::pedersen_hash::( + ::pedersen_hash::Personalization::MerkleTree(i), + lhs.into_iter() + .take(Fr::NUM_BITS as usize) + .chain(rhs.into_iter().take(Fr::NUM_BITS as usize)), + params + ).into_xy().0; + + if b { + position |= 1 << i; + } + } + + let expected_nf = note.nf(&viewing_key, position, params); + let expected_nf_xy = expected_nf.into_xy(); + + assert_eq!(cs.get_input(3, "anchor/input variable"), cur); + assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); + assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.1); } } diff --git a/src/lib.rs b/src/lib.rs index 686f93a..100bbb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,8 @@ const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; // Group hash personalizations /// BLAKE2s Personalization for Pedersen hash generators. const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +/// BLAKE2s Personalization for the group hash for key diversification +const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8] = b"Zcash_gh"; /// BLAKE2s Personalization for the proof generation key base point const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; /// BLAKE2s Personalization for the note commitment randomness generator diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 237b633..d5bb550 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,3 +1,10 @@ +use pairing::{ + PrimeField, + PrimeFieldRepr +}; + +use group_hash::group_hash; + use pedersen_hash::{ pedersen_hash, Personalization @@ -16,6 +23,83 @@ use jubjub::{ FixedGenerators }; +use blake2_rfc::blake2s::Blake2s; + +pub struct ProofGenerationKey { + pub ak: edwards::Point, + pub rsk: E::Fs +} + +impl ProofGenerationKey { + pub fn into_viewing_key(&self, params: &E::Params) -> ViewingKey { + ViewingKey { + ak: self.ak.clone(), + rk: params.generator(FixedGenerators::ProofGenerationKey) + .mul(self.rsk, params) + } + } +} + +pub struct ViewingKey { + pub ak: edwards::Point, + pub rk: edwards::Point +} + +impl ViewingKey { + fn ivk(&self) -> E::Fs { + let mut preimage = [0; 64]; + + self.ak.write(&mut preimage[0..32]).unwrap(); + self.rk.write(&mut preimage[32..64]).unwrap(); + + let mut h = Blake2s::with_params(32, &[], &[], ::CRH_IVK_PERSONALIZATION); + h.update(&preimage); + let mut h = h.finalize().as_ref().to_vec(); + + // Drop the first five bits, so it can be interpreted as a scalar. + h[0] &= 0b0000_0111; + + let mut e = ::Repr::default(); + e.read_be(&h[..]).unwrap(); + + E::Fs::from_repr(e).expect("should be a valid scalar") + } + + pub fn into_payment_address( + &self, + diversifier: Diversifier, + params: &E::Params + ) -> Option> + { + diversifier.g_d(params).map(|g_d| { + let pk_d = g_d.mul(self.ivk(), params); + + PaymentAddress { + pk_d: pk_d, + diversifier: diversifier + } + }) + } +} + +#[derive(Copy, Clone)] +pub struct Diversifier(pub [u8; 11]); + +impl Diversifier { + pub fn g_d( + &self, + params: &E::Params + ) -> Option> + { + group_hash::(&self.0, ::KEY_DIVERSIFICATION_PERSONALIZATION, params) + } +} + +pub struct PaymentAddress { + pub pk_d: edwards::Point, + pub diversifier: Diversifier +} + pub struct Note { /// The value of the note pub value: u64, @@ -28,8 +112,8 @@ pub struct Note { } impl Note { - /// Computes the note commitment - pub fn cm(&self, params: &E::Params) -> E::Fr + /// Computes the note commitment, returning the full point. + fn cm_full_point(&self, params: &E::Params) -> edwards::Point { // Calculate the note contents, as bytes let mut note_contents = vec![]; @@ -56,12 +140,53 @@ impl Note { ); // Compute final commitment - let cm = params.generator(FixedGenerators::NoteCommitmentRandomness) - .mul(self.r, params) - .add(&hash_of_contents, params); + params.generator(FixedGenerators::NoteCommitmentRandomness) + .mul(self.r, params) + .add(&hash_of_contents, params) + } + /// Computes the nullifier given the viewing key and + /// note position + pub fn nf( + &self, + viewing_key: &ViewingKey, + position: u64, + params: &E::Params + ) -> edwards::Point + { + // Compute cm + position + let cm_plus_position = self + .cm_full_point(params) + .add( + ¶ms.generator(FixedGenerators::NullifierPosition) + .mul(position, params), + params + ); + + // Compute nr = drop_5(BLAKE2s(rk | cm_plus_position)) + let mut nr_preimage = [0u8; 64]; + viewing_key.rk.write(&mut nr_preimage[0..32]).unwrap(); + cm_plus_position.write(&mut nr_preimage[32..64]).unwrap(); + let mut h = Blake2s::with_params(32, &[], &[], ::PRF_NR_PERSONALIZATION); + h.update(&nr_preimage); + let mut h = h.finalize().as_ref().to_vec(); + + // Drop the first five bits, so it can be interpreted as a scalar. + h[0] &= 0b0000_0111; + + let mut e = ::Repr::default(); + e.read_be(&h[..]).unwrap(); + + let nr = E::Fs::from_repr(e).expect("should be a valid scalar"); + + viewing_key.ak.mul(nr, params) + } + + /// Computes the note commitment + pub fn cm(&self, params: &E::Params) -> E::Fr + { // The commitment is in the prime order subgroup, so mapping the // commitment to the x-coordinate is an injective encoding. - cm.into_xy().0 + self.cm_full_point(params).into_xy().0 } }