diff --git a/examples/bench.rs b/examples/bench.rs.disabled similarity index 100% rename from examples/bench.rs rename to examples/bench.rs.disabled diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index def6d9ed5..828fa3196 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -22,11 +22,23 @@ use bellman::{ use jubjub::{ JubjubEngine, - Unknown, - FixedGenerators, - edwards + FixedGenerators }; +use constants; + +use primitives::{ + ValueCommitment, + ProofGenerationKey, + PaymentAddress +}; + +// TODO: This should probably be removed and we +// should use existing helper methods on `Option` +// for mapping with an error. +/// This basically is just an extension to `Option` +/// which allows for a convenient mapping to an +/// error on `None`. trait Assignment { fn get(&self) -> Result<&T, SynthesisError>; } @@ -40,75 +52,113 @@ impl Assignment for Option { } } +/// This is an instance of the `Spend` circuit. 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 + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + + /// Key required to construct proofs for spending notes + /// for a particular spending key + pub proof_generation_key: Option>, + + /// The payment address associated with the note + pub payment_address: Option>, + + /// The randomness of the note commitment pub commitment_randomness: Option, + /// The authentication path of the commitment in the tree pub auth_path: Vec> } +/// This is an output circuit instance. +pub struct Output<'a, E: JubjubEngine> { + pub params: &'a E::Params, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + + /// The payment address of the recipient + pub payment_address: 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 +} + +/// Exposes a Pedersen commitment to the value as an +/// input to the circuit +fn expose_value_commitment( + mut cs: CS, + value_commitment: Option>, + params: &E::Params +) -> Result, SynthesisError> + where E: JubjubEngine, + CS: ConstraintSystem +{ + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_boolean_vec_le( + cs.namespace(|| "value"), + value_commitment.as_ref().map(|c| c.value) + )?; + + // Compute the note value in the exponent + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + params + )?; + + // Booleanize the randomness. This does not ensure + // the bit representation is "in the field" because + // it doesn't matter for security. + let hr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "hr"), + value_commitment.as_ref().map(|c| c.randomness) + )?; + + // Compute the randomness in the exponent + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + params + )?; + + // Compute the Pedersen commitment to the value + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + params + )?; + + // Expose the commitment as an input to the circuit + gvhr.inputize(cs.namespace(|| "commitment point"))?; + + Ok(value_bits) +} + 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_boolean_vec_le( - cs.namespace(|| "value"), - self.value + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params )?; - { - 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_boolean_vec_le( - cs.namespace(|| "hr"), - self.value_randomness - )?; - - 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 - )?; - - gvhr.inputize(cs.namespace(|| "value commitment"))?; - } - // Compute rk = [rsk] ProvingPublicKey let rk; { // Witness rsk as bits let rsk = boolean::field_into_boolean_vec_le( cs.namespace(|| "rsk"), - self.rsk + self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) )?; // NB: We don't ensure that the bit representation of rsk @@ -116,6 +166,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // demonstrate the prover knows it. If they know a // congruency then that's equivalent. + // Compute rk = [rsk] ProvingPublicKey rk = ecc::fixed_base_multiplication( cs.namespace(|| "computation of rk"), FixedGenerators::ProofGenerationKey, @@ -127,51 +178,79 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Prover witnesses ak (ensures that it's on the curve) let ak = ecc::EdwardsPoint::witness( cs.namespace(|| "ak"), - self.ak, + self.proof_generation_key.as_ref().map(|k| k.ak.clone()), self.params )?; + // There are no sensible attacks on small order points + // of ak (that we're aware of!) but it's a cheap check, + // so we do it. ak.assert_not_small_order( cs.namespace(|| "ak not small order"), self.params )?; // Unpack ak and rk for input to BLAKE2s + + // This is the "viewing key" preimage for CRH^ivk let mut vk = vec![]; - let mut rho_preimage = vec![]; vk.extend( ak.repr(cs.namespace(|| "representation of ak"))? ); + + // This is the nullifier randomness preimage for PRF^nr + let mut nr_preimage = vec![]; + + // Extend vk and nr preimages with the representation of + // rk. { let repr_rk = rk.repr( cs.namespace(|| "representation of rk") )?; vk.extend(repr_rk.iter().cloned()); - rho_preimage.extend(repr_rk); + nr_preimage.extend(repr_rk); } assert_eq!(vk.len(), 512); + assert_eq!(nr_preimage.len(), 256); - // Compute the incoming viewing key + // Compute the incoming viewing key ivk let mut ivk = blake2s::blake2s( cs.namespace(|| "computation of ivk"), &vk, - ::CRH_IVK_PERSONALIZATION + constants::CRH_IVK_PERSONALIZATION )?; // 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 - )?; + // drop_5 to ensure it's in the field + ivk.truncate(E::Fs::CAPACITY as usize); - // Compute pk_d + // Witness g_d. Ensures the point is on the + // curve, but not its order. If the prover + // manages to witness a commitment in the + // tree, then the Output circuit would have + // already guaranteed this. + // TODO: We might as well just perform the + // check again here, since it's not expensive. + let g_d = { + // This binding is to avoid a weird edge case in Rust's + // ownership/borrowing rules. self is partially moved + // above, but the closure for and_then will have to + // move self (or a reference to self) to reference + // self.params, so we have to copy self.params here. + let params = self.params; + + ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.payment_address.as_ref().and_then(|a| a.g_d(params)), + self.params + )? + }; + + // Compute pk_d = g_d^ivk let pk_d = g_d.mul( cs.namespace(|| "compute pk_d"), &ivk, @@ -179,6 +258,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Compute note contents + // value (in big endian) followed by g_d and pk_d let mut note_contents = vec![]; note_contents.extend(value_bits.into_iter().rev()); note_contents.extend( @@ -204,12 +284,13 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; { - // Booleanize the randomness + // Booleanize the randomness for the note commitment let cmr = boolean::field_into_boolean_vec_le( cs.namespace(|| "cmr"), self.commitment_randomness )?; + // Compute the note commitment randomness in the exponent let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), FixedGenerators::NoteCommitmentRandomness, @@ -217,6 +298,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + // Randomize the note commitment. Pedersen hashes are not + // themselves hiding commitments. cm = cm.add( cs.namespace(|| "randomization of note commitment"), &cmr, @@ -226,21 +309,30 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let tree_depth = self.auth_path.len(); + // This will store (least significant bit first) + // the position of the note in the tree, for use + // in nullifier computation. let mut position_bits = vec![]; - // Injective encoding. + // This is an injective encoding, as cur is a + // point in the prime order subgroup. let mut cur = cm.get_x().clone(); for (i, e) in self.auth_path.into_iter().enumerate() { let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); + // Determines if the current subtree is the "right" leaf at this + // depth of the tree. let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( cs.namespace(|| "position bit"), e.map(|e| e.1) )?); + // Push this boolean for nullifier computation later position_bits.push(cur_is_right.clone()); + // Witness the authentication path element adjacent + // at this depth. let path_element = num::AllocatedNum::alloc( cs.namespace(|| "path element"), || { @@ -248,6 +340,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { } )?; + // Swap the two if the current subtree is on the right let (xl, xr) = num::AllocatedNum::conditionally_reverse( cs.namespace(|| "conditional reversal of preimage"), &cur, @@ -263,6 +356,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?); preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?); + // Compute the new subtree value cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), pedersen_hash::Personalization::MerkleTree(i), @@ -276,7 +370,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Expose the anchor cur.inputize(cs.namespace(|| "anchor"))?; + // Compute the cm + g^position for preventing + // faerie gold attacks { + // Compute the position in the exponent let position = ecc::fixed_base_multiplication( cs.namespace(|| "g^position"), FixedGenerators::NullifierPosition, @@ -284,6 +381,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + // Add the position to the commitment cm = cm.add( cs.namespace(|| "faerie gold prevention"), &position, @@ -291,144 +389,126 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - // Let's compute rho = BLAKE2s(rk || cm + position) - rho_preimage.extend( + // Let's compute nr = BLAKE2s(rk || cm + position) + nr_preimage.extend( cm.repr(cs.namespace(|| "representation of cm"))? ); - assert_eq!(rho_preimage.len(), 512); + assert_eq!(nr_preimage.len(), 512); - let mut rho = blake2s::blake2s( - cs.namespace(|| "rho computation"), - &rho_preimage, - ::PRF_NR_PERSONALIZATION + // Compute nr + let mut nr = blake2s::blake2s( + cs.namespace(|| "nr computation"), + &nr_preimage, + constants::PRF_NR_PERSONALIZATION )?; // Little endian bit order - rho.reverse(); - rho.truncate(E::Fs::CAPACITY as usize); // drop_5 + nr.reverse(); + + // We want the randomization in the field to + // simplify outside code. + // TODO: This isn't uniformly random. + nr.truncate(E::Fs::CAPACITY as usize); // Compute nullifier let nf = ak.mul( cs.namespace(|| "computation of nf"), - &rho, + &nr, self.params )?; + // Expose the nullifier publicly nf.inputize(cs.namespace(|| "nullifier"))?; 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_boolean_vec_le( - cs.namespace(|| "value"), - self.value + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params )?; - { - 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_boolean_vec_le( - cs.namespace(|| "hr"), - self.value_randomness - )?; - - 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 - )?; - - gvhr.inputize(cs.namespace(|| "value commitment"))?; - } - - // Let's start to construct our note + // Let's start to construct our note, which contains + // value (big endian) let mut note_contents = vec![]; note_contents.extend(value_bits.into_iter().rev()); // Let's deal with g_d { + let params = self.params; + + // Prover witnesses g_d, ensuring it's on the + // curve. let g_d = ecc::EdwardsPoint::witness( cs.namespace(|| "witness g_d"), - self.g_d, + self.payment_address.as_ref().and_then(|a| a.g_d(params)), self.params )?; + // g_d is ensured to be large order. The relationship + // between g_d and pk_d ultimately binds ivk to the + // note. If this were a small order point, it would + // not do this correctly, and the prover could + // double-spend by finding random ivk's that satisfy + // the relationship. + // + // Further, if it were small order, epk would be + // small order too! g_d.assert_not_small_order( cs.namespace(|| "g_d not small order"), self.params )?; + // Extend our note contents with the representation of + // g_d. note_contents.extend( g_d.repr(cs.namespace(|| "representation of g_d"))? ); - // Compute epk from esk + // Booleanize our ephemeral secret key let esk = boolean::field_into_boolean_vec_le( cs.namespace(|| "esk"), self.esk )?; + // Create the ephemeral public key from g_d. let epk = g_d.mul( cs.namespace(|| "epk computation"), &esk, self.params )?; + // Expose epk publicly. epk.inputize(cs.namespace(|| "epk"))?; } - // Now let's deal with p_d. We don't do any checks and + // Now let's deal with pk_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()); + // Just grab pk_d from the witness + let pk_d = self.payment_address.as_ref().map(|e| e.pk_d.into_xy()); + // Witness the y-coordinate, encoded as little + // endian bits (to match the representation) let y_contents = boolean::field_into_boolean_vec_le( - cs.namespace(|| "p_d bits of y"), - p_d.map(|e| e.1) + cs.namespace(|| "pk_d bits of y"), + pk_d.map(|e| e.1) )?; + // Witness the sign bit 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()) + cs.namespace(|| "pk_d bit of x"), + pk_d.map(|e| e.0.into_repr().is_odd()) )?); + // Extend the note with pk_d representation note_contents.extend(y_contents); note_contents.push(sign_bit); } @@ -455,6 +535,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.commitment_randomness )?; + // Compute the note commitment randomness in the exponent let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), FixedGenerators::NoteCommitmentRandomness, @@ -462,6 +543,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; + // Randomize our note commitment cm = cm.add( cs.namespace(|| "randomization of note commitment"), &cmr, @@ -481,22 +563,49 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { #[test] fn test_input_circuit_with_bls12_381() { + use pairing::{Field, BitIterator}; use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; + use jubjub::{JubjubBls12, fs, edwards}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let tree_depth = 32; - 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 value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + let rsk: fs::Fs = rng.gen(); + let ak = 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]; { @@ -504,13 +613,11 @@ fn test_input_circuit_with_bls12_381() { let instance = Spend { params: params, - value: Some(value), - value_randomness: Some(value_randomness), - rsk: Some(rsk), - ak: Some(ak), - g_d: Some(g_d), + value_commitment: Some(value_commitment.clone()), + proof_generation_key: Some(proof_generation_key.clone()), + payment_address: Some(payment_address.clone()), commitment_randomness: Some(commitment_randomness), - auth_path: auth_path + auth_path: auth_path.clone() }; instance.synthesize(&mut cs).unwrap(); @@ -518,23 +625,106 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 101550); assert_eq!(cs.hash(), "3cc6d9383ca882ae3666267618e826e9d51a3177fc89ef6d42d9f63b84179f77"); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + + assert_eq!(cs.num_inputs(), 6); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + + let note = ::primitives::Note { + value: value_commitment.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); } } #[test] fn test_output_circuit_with_bls12_381() { + use pairing::{Field}; use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; + use jubjub::{JubjubBls12, fs, edwards}; 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 value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let rsk: fs::Fs = rng.gen(); + let ak = 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 commitment_randomness: fs::Fs = rng.gen(); let esk: fs::Fs = rng.gen(); @@ -543,10 +733,8 @@ fn test_output_circuit_with_bls12_381() { 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()), + value_commitment: Some(value_commitment.clone()), + payment_address: Some(payment_address.clone()), commitment_randomness: Some(commitment_randomness), esk: Some(esk.clone()) }; @@ -556,5 +744,24 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); + + let expected_cm = payment_address.create_note( + value_commitment.value, + commitment_randomness, + params + ).expect("should be valid").cm(params); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + + let expected_epk = payment_address.g_d(params).expect("should be valid").mul(esk, params); + let expected_epk_xy = expected_epk.into_xy(); + + assert_eq!(cs.num_inputs(), 6); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + assert_eq!(cs.get_input(3, "epk/x/input variable"), expected_epk_xy.0); + assert_eq!(cs.get_input(4, "epk/y/input variable"), expected_epk_xy.1); + assert_eq!(cs.get_input(5, "commitment/input variable"), expected_cm); } } diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index 01fda4a1c..9728e04a6 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -294,6 +294,19 @@ impl TestConstraintSystem { } } + pub fn num_inputs(&self) -> usize { + self.inputs.len() + } + + pub fn get_input(&mut self, index: usize, path: &str) -> E::Fr + { + let (assignment, name) = self.inputs[index].clone(); + + assert_eq!(path, name); + + assignment + } + pub fn get(&mut self, path: &str) -> E::Fr { match self.named_objects.get(path) { diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 000000000..71b96e1a8 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,29 @@ +/// First 64 bytes of the BLAKE2s input during group hash. +/// This is chosen to be some random string that we couldn't have anticipated when we designed +/// the algorithm, for rigidity purposes. +/// We deliberately use an ASCII hex string of 32 bytes here. +pub const GH_FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; + +// BLAKE2s invocation personalizations +/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) +pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; +/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) +pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; + +// Group hash personalizations +/// BLAKE2s Personalization for Pedersen hash generators. +pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +/// BLAKE2s Personalization for the group hash for key diversification +pub const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8] = b"Zcash_gh"; +/// BLAKE2s Personalization for the proof generation key base point +pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; +/// BLAKE2s Personalization for the note commitment randomness generator +pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; +/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) +pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; +/// BLAKE2s Personalization for the value commitment generator for the value +pub const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; +/// BLAKE2s Personalization for the value commitment randomness generator +pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; +/// BLAKE2s Personalization for the spending key base point +pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"sksksksk"; diff --git a/src/group_hash.rs b/src/group_hash.rs index 58ece78f2..9e6b2f878 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -1,15 +1,20 @@ -use jubjub::*; -use pairing::*; +use jubjub::{ + JubjubEngine, + PrimeOrder, + edwards +}; + +use pairing::{ + PrimeField, + PrimeFieldRepr +}; + use blake2_rfc::blake2s::Blake2s; +use constants; -/// This is chosen to be some random string that we couldn't have anticipated when we designed -/// the algorithm, for rigidity purposes. -pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; - -/// Produces an (x, y) pair (Montgomery) for a -/// random point in the Jubjub curve. The point -/// is guaranteed to be prime order and not the -/// identity. +/// Produces a random point in the Jubjub curve. +/// The point is guaranteed to be prime order +/// and not the identity. pub fn group_hash( tag: &[u8], personalization: &[u8], @@ -22,7 +27,7 @@ pub fn group_hash( assert!(E::Fr::NUM_BITS == 255); let mut h = Blake2s::with_params(32, &[], &[], personalization); - h.update(FIRST_BLOCK); + h.update(constants::GH_FIRST_BLOCK); h.update(tag); let mut h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index eeabe9d49..e73c0efc7 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -28,6 +28,9 @@ use std::io::{ // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. +// +// See "Twisted Edwards Curves Revisited" +// Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson pub struct Point { x: E::Fr, y: E::Fr, @@ -120,7 +123,14 @@ impl Point { params: &E::Params ) -> io::Result { + // Jubjub points are encoded least significant bit first. + // The most significant bit (bit 254) encodes the parity + // of the x-coordinate. + let mut y_repr = ::Repr::default(); + + // This reads in big-endian, so we perform a swap of the + // limbs in the representation and swap the bit order. y_repr.read_be(reader)?; y_repr.as_mut().reverse(); @@ -393,11 +403,19 @@ impl Point { } pub fn double(&self, params: &E::Params) -> Self { + // Point addition is unified and complete. + // There are dedicated formulae, but we do + // not implement these now. + self.add(self, params) } pub fn add(&self, other: &Self, params: &E::Params) -> Self { + // See "Twisted Edwards Curves Revisited" + // Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson + // 3.1 Unified Addition in E^e + // A = x1 * x2 let mut a = self.x; a.mul_assign(&other.x); @@ -470,6 +488,8 @@ impl Point { params: &E::Params ) -> Self { + // Standard double-and-add scalar multiplication + let mut res = Self::zero(); for b in BitIterator::new(scalar.into()) { diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index b9bdfafc1..ca874c6c8 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -1,18 +1,21 @@ -//! 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: +//! Jubjub is a twisted Edwards curve defined over the BLS12-381 scalar +//! field, Fr. It takes the form `-x^2 + y^2 = 1 + dx^2y^2` with +//! `d = -(10240/10241)`. It is birationally equivalent to a Montgomery +//! curve of the form `y^2 = x^3 + Ax^2 + x` with `A = 40962`. This +//! value `A` is the smallest integer choice 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. +//! * `A^2 - 4` is quadratic nonresidue. +//! * 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.) +//! 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. +//! It is a complete twisted Edwards curve, so the equivalence with +//! the Montgomery curve forms a group isomorphism, allowing points +//! to be freely converted between the two forms. use pairing::{ Engine, @@ -21,19 +24,34 @@ use pairing::{ SqrtField }; -use super::group_hash::group_hash; +use group_hash::group_hash; + +use constants; use pairing::bls12_381::{ Bls12, Fr }; +/// This is an implementation of the twisted Edwards Jubjub curve. pub mod edwards; + +/// This is an implementation of the birationally equivalent +/// Montgomery curve. pub mod montgomery; +/// This is an implementation of the scalar field for Jubjub. +pub mod fs; + #[cfg(test)] pub mod tests; +/// Point of unknown order. +pub enum Unknown { } + +/// Point of prime order. +pub enum PrimeOrder { } + /// Fixed generators of the Jubjub curve of unknown /// exponent. #[derive(Copy, Clone)] @@ -71,7 +89,9 @@ pub enum FixedGenerators { /// offers a scalar field for the embedded curve (Jubjub) /// and some pre-computed parameters. pub trait JubjubEngine: Engine { + /// The scalar field of the Jubjub curve type Fs: PrimeField + SqrtField; + /// The parameters of Jubjub and the Sapling protocol type Params: JubjubParams; } @@ -104,14 +124,6 @@ pub trait JubjubParams: Sized { fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } -/// Point of unknown order. -pub enum Unknown { } - -/// Point of prime order. -pub enum PrimeOrder { } - -pub mod fs; - impl JubjubEngine for Bls12 { type Fs = self::fs::Fs; type Params = JubjubBls12; @@ -163,7 +175,7 @@ impl JubjubBls12 { let mut montgomery_2a = montgomery_a; montgomery_2a.double(); - let mut tmp = JubjubBls12 { + let mut tmp_params = JubjubBls12 { // d = -(10240/10241) edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), // A = 40962 @@ -173,20 +185,24 @@ impl JubjubBls12 { // scaling factor = sqrt(4 / (a - d)) scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), + // We'll initialize these below pedersen_hash_generators: vec![], pedersen_circuit_generators: vec![], - fixed_base_generators: vec![], fixed_base_circuit_generators: vec![], }; // Create the bases for the Pedersen hashes { + // TODO: This currently does not match the specification let mut cur = 0; let mut pedersen_hash_generators = vec![]; + // TODO: This generates more bases for the Pedersen hashes + // than necessary, which is just a performance issue in + // practice. while pedersen_hash_generators.len() < 5 { - let gh = group_hash(&[cur], ::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); + let gh = group_hash(&[cur], constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp_params); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; @@ -196,7 +212,20 @@ impl JubjubBls12 { } } - tmp.pedersen_hash_generators = pedersen_hash_generators; + // Check for duplicates, far worse than spec inconsistencies! + for (i, p1) in pedersen_hash_generators.iter().enumerate() { + if p1 == &edwards::Point::zero() { + panic!("Neutral element!"); + } + + for p2 in pedersen_hash_generators.iter().skip(i+1) { + if p1 == p2 { + panic!("Duplicate generator!"); + } + } + } + + tmp_params.pedersen_hash_generators = pedersen_hash_generators; } // Create the bases for other parts of the protocol @@ -207,10 +236,10 @@ impl JubjubBls12 { // Each generator is found by invoking the group hash // on tag 0x00, 0x01, ... until we find a valid result. let find_first_gh = |personalization| { - let mut cur = 0; + let mut cur = 0u8; loop { - let gh = group_hash::(&[cur], personalization, &tmp); + let gh = group_hash::(&[cur], personalization, &tmp_params); // We don't want to overflow. assert!(cur != u8::max_value()); cur += 1; @@ -226,22 +255,22 @@ impl JubjubBls12 { for c in 0..(FixedGenerators::Max as usize) { let p = match c { c if c == (FixedGenerators::ProofGenerationKey as usize) => { - ::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION + constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::NoteCommitmentRandomness as usize) => { - ::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::NullifierPosition as usize) => { - ::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION + constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::ValueCommitmentValue as usize) => { - ::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION + constants::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::ValueCommitmentRandomness as usize) => { - ::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::SpendingKeyGenerator as usize) => { - ::SPENDING_KEY_GENERATOR_PERSONALIZATION + constants::SPENDING_KEY_GENERATOR_PERSONALIZATION }, _ => unreachable!() }; @@ -263,7 +292,7 @@ impl JubjubBls12 { } } - tmp.fixed_base_generators = fixed_base_generators; + tmp_params.fixed_base_generators = fixed_base_generators; } // Create the 2-bit window table lookups for each 4-bit @@ -272,10 +301,10 @@ impl JubjubBls12 { let mut pedersen_circuit_generators = vec![]; // Process each segment - for mut gen in tmp.pedersen_hash_generators.iter().cloned() { - let mut gen = montgomery::Point::from_edwards(&gen, &tmp); + for mut gen in tmp_params.pedersen_hash_generators.iter().cloned() { + let mut gen = montgomery::Point::from_edwards(&gen, &tmp_params); let mut windows = vec![]; - for _ in 0..tmp.pedersen_hash_chunks_per_generator() { + for _ in 0..tmp_params.pedersen_hash_chunks_per_generator() { // Create (x, y) coeffs for this chunk let mut coeffs = vec![]; let mut g = gen.clone(); @@ -283,19 +312,19 @@ impl JubjubBls12 { // coeffs = g, g*2, g*3, g*4 for _ in 0..4 { coeffs.push(g.into_xy().expect("cannot produce O")); - g = g.add(&gen, &tmp); + g = g.add(&gen, &tmp_params); } windows.push(coeffs); // Our chunks are separated by 2 bits to prevent overlap. for _ in 0..4 { - gen = gen.double(&tmp); + gen = gen.double(&tmp_params); } } pedersen_circuit_generators.push(windows); } - tmp.pedersen_circuit_generators = pedersen_circuit_generators; + tmp_params.pedersen_circuit_generators = pedersen_circuit_generators; } // Create the 3-bit window table lookups for fixed-base @@ -303,14 +332,14 @@ impl JubjubBls12 { { let mut fixed_base_circuit_generators = vec![]; - for mut gen in tmp.fixed_base_generators.iter().cloned() { + for mut gen in tmp_params.fixed_base_generators.iter().cloned() { let mut windows = vec![]; - for _ in 0..tmp.fixed_base_chunks_per_generator() { + for _ in 0..tmp_params.fixed_base_chunks_per_generator() { let mut coeffs = vec![(Fr::zero(), Fr::one())]; let mut g = gen.clone(); for _ in 0..7 { coeffs.push(g.into_xy()); - g = g.add(&gen, &tmp); + g = g.add(&gen, &tmp_params); } windows.push(coeffs); @@ -320,10 +349,10 @@ impl JubjubBls12 { fixed_base_circuit_generators.push(windows); } - tmp.fixed_base_circuit_generators = fixed_base_circuit_generators; + tmp_params.fixed_base_circuit_generators = fixed_base_circuit_generators; } - tmp + tmp_params } } diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs index e82711122..224387717 100644 --- a/src/jubjub/montgomery.rs +++ b/src/jubjub/montgomery.rs @@ -20,8 +20,7 @@ use rand::{ use std::marker::PhantomData; -// Represents the affine point (X/Z, Y/Z) via the extended -// twisted Edwards coordinates. +// Represents the affine point (X, Y) pub struct Point { x: E::Fr, y: E::Fr, @@ -69,7 +68,7 @@ impl PartialEq for Point { impl Point { pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> Option { - // given an x on the curve, y^2 = x^3 + A*x^2 + x + // Given an x on the curve, y = sqrt(x^3 + A*x^2 + x) let mut x2 = x; x2.square(); @@ -230,10 +229,17 @@ impl Point { return Point::zero(); } + // (0, 0) is the point of order 2. Doubling + // produces the point at infinity. if self.y == E::Fr::zero() { return Point::zero(); } + // This is a standard affine point doubling formula + // See 4.3.2 The group law for Weierstrass curves + // Montgomery curves and the Montgomery Ladder + // Daniel J. Bernstein and Tanja Lange + let mut delta = E::Fr::one(); { let mut tmp = params.montgomery_a().clone(); @@ -276,6 +282,11 @@ impl Point { pub fn add(&self, other: &Self, params: &E::Params) -> Self { + // This is a standard affine point addition formula + // See 4.3.2 The group law for Weierstrass curves + // Montgomery curves and the Montgomery Ladder + // Daniel J. Bernstein and Tanja Lange + match (self.infinity, other.infinity) { (true, true) => Point::zero(), (true, false) => other.clone(), @@ -325,6 +336,8 @@ impl Point { params: &E::Params ) -> Self { + // Standard double-and-add scalar multiplication + let mut res = Self::zero(); for b in BitIterator::new(scalar.into()) { diff --git a/src/lib.rs b/src/lib.rs index 686f93a37..0f5fded65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ extern crate bellman; extern crate blake2_rfc; extern crate digest; extern crate rand; - extern crate byteorder; #[cfg(test)] @@ -11,29 +10,8 @@ extern crate byteorder; extern crate hex_literal; pub mod jubjub; -pub mod circuit; pub mod group_hash; +pub mod circuit; pub mod pedersen_hash; pub mod primitives; - -// BLAKE2s invocation personalizations -/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) -const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; -/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) -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 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 -const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; -/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) -const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; -/// BLAKE2s Personalization for the value commitment generator for the value -const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; -/// BLAKE2s Personalization for the value commitment randomness generator -const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; -/// BLAKE2s Personalization for the spending key base point -const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"sksksksk"; +pub mod constants; diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 7a990920b..6ac10402d 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,3 +1,12 @@ +use pairing::{ + PrimeField, + PrimeFieldRepr +}; + +use constants; + +use group_hash::group_hash; + use pedersen_hash::{ pedersen_hash, Personalization @@ -5,7 +14,7 @@ use pedersen_hash::{ use byteorder::{ BigEndian, - ByteOrder + WriteBytesExt }; use jubjub::{ @@ -16,6 +25,134 @@ use jubjub::{ FixedGenerators }; +use blake2_rfc::blake2s::Blake2s; + +#[derive(Clone)] +pub struct ValueCommitment { + pub value: u64, + pub randomness: E::Fs +} + +impl ValueCommitment { + pub fn cm( + &self, + params: &E::Params + ) -> edwards::Point + { + params.generator(FixedGenerators::ValueCommitmentValue) + .mul(self.value, params) + .add( + ¶ms.generator(FixedGenerators::ValueCommitmentRandomness) + .mul(self.randomness, params), + params + ) + } +} + +#[derive(Clone)] +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, &[], &[], constants::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, constants::KEY_DIVERSIFICATION_PERSONALIZATION, params) + } +} + +#[derive(Clone)] +pub struct PaymentAddress { + pub pk_d: edwards::Point, + pub diversifier: Diversifier +} + +impl PaymentAddress { + pub fn g_d( + &self, + params: &E::Params + ) -> Option> + { + self.diversifier.g_d(params) + } + + pub fn create_note( + &self, + value: u64, + randomness: E::Fs, + params: &E::Params + ) -> Option> + { + self.g_d(params).map(|g_d| { + Note { + value: value, + r: randomness, + g_d: g_d, + pk_d: self.pk_d.clone() + } + }) + } +} + pub struct Note { /// The value of the note pub value: u64, @@ -28,14 +165,14 @@ 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![]; // Write the value in big endian - BigEndian::write_u64(&mut note_contents, self.value); + (&mut note_contents).write_u64::(self.value).unwrap(); // Write g_d self.g_d.write(&mut note_contents).unwrap(); @@ -43,6 +180,8 @@ impl Note { // Write pk_d self.pk_d.write(&mut note_contents).unwrap(); + assert_eq!(note_contents.len(), 32 + 32 + 8); + // Compute the Pedersen hash of the note contents let hash_of_contents = pedersen_hash( Personalization::NoteCommitment, @@ -54,12 +193,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, &[], &[], constants::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 } }