From 014afd8e4a784fea46ac5d761312af1262e70f07 Mon Sep 17 00:00:00 2001 From: Deirdre Connolly Date: Fri, 7 Aug 2020 05:16:55 -0400 Subject: [PATCH] Move Pedersen hash functions to their own submodule --- zebra-chain/src/commitments/sapling.rs | 135 +--------------- .../commitments/sapling/pedersen_hashes.rs | 145 ++++++++++++++++++ zebra-chain/src/notes/sapling/nullifiers.rs | 2 +- .../src/treestate/note_commitment_tree.rs | 2 +- 4 files changed, 150 insertions(+), 134 deletions(-) create mode 100644 zebra-chain/src/commitments/sapling/pedersen_hashes.rs diff --git a/zebra-chain/src/commitments/sapling.rs b/zebra-chain/src/commitments/sapling.rs index 11cc6fa2a..e4d42ccd9 100644 --- a/zebra-chain/src/commitments/sapling.rs +++ b/zebra-chain/src/commitments/sapling.rs @@ -5,6 +5,8 @@ mod arbitrary; #[cfg(test)] mod test_vectors; +pub mod pedersen_hashes; + use std::{fmt, io}; use bitvec::prelude::*; @@ -17,138 +19,7 @@ use crate::{ types::amount::{Amount, NonNegative}, }; -/// Generates a random scalar from the scalar field 𝔽_{r_𝕁}. -/// -/// The prime order subgroup 𝕁^(r) is the order-r_𝕁 subgroup of 𝕁 that consists -/// of the points whose order divides r. This function is useful when generating -/// the uniform distribution on 𝔽_{r_𝕁} needed for Sapling commitment schemes' -/// trapdoor generators. -/// -/// https://zips.z.cash/protocol/protocol.pdf#jubjub -pub fn generate_trapdoor(csprng: &mut T) -> jubjub::Fr -where - T: RngCore + CryptoRng, -{ - let mut bytes = [0u8; 64]; - csprng.fill_bytes(&mut bytes); - // Fr::from_bytes_wide() reduces the input modulo r via Fr::from_u512() - jubjub::Fr::from_bytes_wide(&bytes) -} - -/// "...an algebraic hash function with collision resistance (for fixed input -/// length) derived from assumed hardness of the Discrete Logarithm Problem on -/// the Jubjub curve." -/// -/// PedersenHash is used in the definitions of Pedersen commitments (§ -/// 5.4.7.2 ‘Windowed Pedersen commitments’), and of the Pedersen hash for the -/// Sapling incremental Merkle tree (§ 5.4.1.3 ‘MerkleCRH^Sapling Hash -/// Function’). -/// -/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash -#[allow(non_snake_case)] -pub fn pedersen_hash_to_point(domain: [u8; 8], M: &BitVec) -> jubjub::ExtendedPoint { - // Expects i to be 1-indexed from the loop it's called in. - fn I_i(domain: [u8; 8], i: u32) -> jubjub::ExtendedPoint { - find_group_hash(domain, &(i - 1).to_le_bytes()) - } - - /// ⟨Mᵢ⟩ - /// - /// Σ j={0,k-1}: (1 - 2x₂)⋅(1 + x₀ + 2x₁)⋅2^(4⋅j) - // This is less efficient than it could be so that it can match the math - // closely. - fn M_i(segment: &BitSlice) -> jubjub::Fr { - let mut m_i = jubjub::Fr::zero(); - - for (j, chunk) in segment.chunks(3).enumerate() { - // Pad each chunk with zeros. - let mut store = 0u8; - let bits = store.bits_mut::(); - chunk - .iter() - .enumerate() - .for_each(|(i, bit)| bits.set(i, *bit)); - - let mut tmp = jubjub::Fr::one(); - - if bits[0] { - tmp += &jubjub::Fr::one(); - } - - if bits[1] { - tmp += &jubjub::Fr::one().double(); - } - - if bits[2] { - tmp -= tmp.double(); - } - - if j > 0 { - // Inclusive range! - tmp *= (1..=(4 * j)).fold(jubjub::Fr::one(), |acc, _| acc.double()); - } - - m_i += tmp; - } - - m_i - } - - let mut result = jubjub::ExtendedPoint::identity(); - - // Split M into n segments of 3 * c bits, where c = 63, padding the last - // segment with zeros. - // - // This loop is 1-indexed per the math definitions in the spec. - // - // https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash - for (i, segment) in M - .chunks(189) - .enumerate() - .map(|(i, segment)| (i + 1, segment)) - { - result += I_i(domain, i as u32) * M_i(&segment); - } - - result -} - -/// Pedersen Hash Function -/// -/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash -#[allow(non_snake_case)] -pub fn pedersen_hash(domain: [u8; 8], M: &BitVec) -> jubjub::Fq { - jubjub::AffinePoint::from(pedersen_hash_to_point(domain, M)).get_u() -} - -/// Mixing Pedersen Hash Function -/// -/// Used to compute ρ from a note commitment and its position in the note -/// commitment tree. It takes as input a Pedersen commitment P, and hashes it -/// with another input x. -/// -/// MixingPedersenHash(P, x) := P + [x]FindGroupHash^J^(r)(“Zcash_J_”, “”) -/// -/// https://zips.z.cash/protocol/protocol.pdf#concretemixinghash -#[allow(non_snake_case)] -pub fn mixing_pedersen_hash(P: jubjub::ExtendedPoint, x: jubjub::Fr) -> jubjub::ExtendedPoint { - const J: [u8; 8] = *b"Zcash_J_"; - - P + find_group_hash(J, b"") * x -} - -/// Construct a 'windowed' Pedersen commitment by reusing a Pederson hash -/// construction, and adding a randomized point on the Jubjub curve. -/// -/// WindowedPedersenCommit_r (s) := \ -/// PedersenHashToPoint(“Zcash_PH”, s) + [r]FindGroupHash^J^(r)(“Zcash_PH”, “r”) -/// -/// https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit -pub fn windowed_pedersen_commitment(r: jubjub::Fr, s: &BitVec) -> jubjub::ExtendedPoint { - const D: [u8; 8] = *b"Zcash_PH"; - - pedersen_hash_to_point(D, &s) + find_group_hash(D, b"r") * r -} +use pedersen_hashes::*; /// The randomness used in the Pedersen Hash for note commitment. #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/zebra-chain/src/commitments/sapling/pedersen_hashes.rs b/zebra-chain/src/commitments/sapling/pedersen_hashes.rs new file mode 100644 index 000000000..503255c37 --- /dev/null +++ b/zebra-chain/src/commitments/sapling/pedersen_hashes.rs @@ -0,0 +1,145 @@ +//! Pedersen hash functions and helpers. + +use bitvec::prelude::*; +use rand_core::{CryptoRng, RngCore}; + +use crate::keys::sapling::find_group_hash; + +/// I_i +/// +/// Expects i to be 1-indexed from the loop it's called in. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +fn I_i(domain: [u8; 8], i: u32) -> jubjub::ExtendedPoint { + find_group_hash(domain, &(i - 1).to_le_bytes()) +} + +/// The encoding function ⟨Mᵢ⟩ +/// +/// Σ j={0,k-1}: (1 - 2x₂)⋅(1 + x₀ + 2x₁)⋅2^(4⋅j) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +fn M_i(segment: &BitSlice) -> jubjub::Fr { + let mut m_i = jubjub::Fr::zero(); + + for (j, chunk) in segment.chunks(3).enumerate() { + // Pad each chunk with zeros. + let mut store = 0u8; + let bits = store.bits_mut::(); + chunk + .iter() + .enumerate() + .for_each(|(i, bit)| bits.set(i, *bit)); + + let mut tmp = jubjub::Fr::one(); + + if bits[0] { + tmp += &jubjub::Fr::one(); + } + + if bits[1] { + tmp += &jubjub::Fr::one().double(); + } + + if bits[2] { + tmp -= tmp.double(); + } + + if j > 0 { + // Inclusive range! + tmp *= (1..=(4 * j)).fold(jubjub::Fr::one(), |acc, _| acc.double()); + } + + m_i += tmp; + } + + m_i +} + +/// "...an algebraic hash function with collision resistance (for fixed input +/// length) derived from assumed hardness of the Discrete Logarithm Problem on +/// the Jubjub curve." +/// +/// PedersenHash is used in the definitions of Pedersen commitments (§ +/// 5.4.7.2 ‘Windowed Pedersen commitments’), and of the Pedersen hash for the +/// Sapling incremental Merkle tree (§ 5.4.1.3 ‘MerkleCRH^Sapling Hash +/// Function’). +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +pub fn pedersen_hash_to_point(domain: [u8; 8], M: &BitVec) -> jubjub::ExtendedPoint { + let mut result = jubjub::ExtendedPoint::identity(); + + // Split M into n segments of 3 * c bits, where c = 63, padding the last + // segment with zeros. + // + // This loop is 1-indexed per the math definitions in the spec. + // + // https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash + for (i, segment) in M + .chunks(189) + .enumerate() + .map(|(i, segment)| (i + 1, segment)) + { + result += I_i(domain, i as u32) * M_i(&segment); + } + + result +} + +/// Pedersen Hash Function +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +pub fn pedersen_hash(domain: [u8; 8], M: &BitVec) -> jubjub::Fq { + jubjub::AffinePoint::from(pedersen_hash_to_point(domain, M)).get_u() +} + +/// Mixing Pedersen Hash Function +/// +/// Used to compute ρ from a note commitment and its position in the note +/// commitment tree. It takes as input a Pedersen commitment P, and hashes it +/// with another input x. +/// +/// MixingPedersenHash(P, x) := P + [x]FindGroupHash^J^(r)(“Zcash_J_”, “”) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretemixinghash +#[allow(non_snake_case)] +pub fn mixing_pedersen_hash(P: jubjub::ExtendedPoint, x: jubjub::Fr) -> jubjub::ExtendedPoint { + const J: [u8; 8] = *b"Zcash_J_"; + + P + find_group_hash(J, b"") * x +} + +/// Construct a 'windowed' Pedersen commitment by reusing a Pederson hash +/// construction, and adding a randomized point on the Jubjub curve. +/// +/// WindowedPedersenCommit_r (s) := \ +/// PedersenHashToPoint(“Zcash_PH”, s) + [r]FindGroupHash^J^(r)(“Zcash_PH”, “r”) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit +pub fn windowed_pedersen_commitment(r: jubjub::Fr, s: &BitVec) -> jubjub::ExtendedPoint { + const D: [u8; 8] = *b"Zcash_PH"; + + pedersen_hash_to_point(D, &s) + find_group_hash(D, b"r") * r +} + +/// Generates a random scalar from the scalar field 𝔽_{r_𝕁}. +/// +/// The prime order subgroup 𝕁^(r) is the order-r_𝕁 subgroup of 𝕁 that consists +/// of the points whose order divides r. This function is useful when generating +/// the uniform distribution on 𝔽_{r_𝕁} needed for Sapling commitment schemes' +/// trapdoor generators. +/// +/// https://zips.z.cash/protocol/protocol.pdf#jubjub +pub fn generate_trapdoor(csprng: &mut T) -> jubjub::Fr +where + T: RngCore + CryptoRng, +{ + let mut bytes = [0u8; 64]; + csprng.fill_bytes(&mut bytes); + // Fr::from_bytes_wide() reduces the input modulo r via Fr::from_u512() + jubjub::Fr::from_bytes_wide(&bytes) +} diff --git a/zebra-chain/src/notes/sapling/nullifiers.rs b/zebra-chain/src/notes/sapling/nullifiers.rs index 37f2590a3..8f9bdc872 100644 --- a/zebra-chain/src/notes/sapling/nullifiers.rs +++ b/zebra-chain/src/notes/sapling/nullifiers.rs @@ -4,7 +4,7 @@ use std::io; use crate::{ - commitments::sapling::{mixing_pedersen_hash, NoteCommitment}, + commitments::sapling::{pedersen_hashes::mixing_pedersen_hash, NoteCommitment}, keys::sapling::NullifierDerivingKey, serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, treestate::note_commitment_tree::Position, diff --git a/zebra-chain/src/treestate/note_commitment_tree.rs b/zebra-chain/src/treestate/note_commitment_tree.rs index 5d503e908..f9ae48a8b 100644 --- a/zebra-chain/src/treestate/note_commitment_tree.rs +++ b/zebra-chain/src/treestate/note_commitment_tree.rs @@ -20,7 +20,7 @@ use bitvec::prelude::*; use proptest_derive::Arbitrary; use crate::{ - commitments::sapling::pedersen_hash, + commitments::sapling::pedersen_hashes::pedersen_hash, serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}, };