zebra_chain/sapling/commitment/
pedersen_hashes.rs

1//! Pedersen hash functions and helpers.
2
3use bitvec::prelude::*;
4
5use super::super::keys::find_group_hash;
6
7/// I_i
8///
9/// Expects i to be 1-indexed from the loop it's called in.
10///
11/// <https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash>
12#[allow(non_snake_case)]
13fn I_i(domain: [u8; 8], i: u32) -> jubjub::ExtendedPoint {
14    find_group_hash(domain, &(i - 1).to_le_bytes())
15}
16
17/// The encoding function ⟨Mᵢ⟩
18///
19/// Σ j={0,k-1}: (1 - 2x₂)⋅(1 + x₀ + 2x₁)⋅2^(4⋅j)
20///
21/// <https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash>
22#[allow(non_snake_case)]
23fn M_i(segment: &BitSlice<u8, Lsb0>) -> jubjub::Fr {
24    let mut m_i = jubjub::Fr::zero();
25
26    for (j, chunk) in segment.chunks(3).enumerate() {
27        // Pad each chunk with zeros.
28        let mut store = 0u8;
29        let bits = BitSlice::<_, Lsb0>::from_element_mut(&mut store);
30        chunk
31            .iter()
32            .enumerate()
33            .for_each(|(i, bit)| bits.set(i, *bit));
34
35        let mut tmp = jubjub::Fr::one();
36
37        if bits[0] {
38            tmp += &jubjub::Fr::one();
39        }
40
41        if bits[1] {
42            tmp += &jubjub::Fr::one().double();
43        }
44
45        if bits[2] {
46            tmp -= tmp.double();
47        }
48
49        if j > 0 {
50            // Inclusive range!
51            tmp *= (1..=(4 * j)).fold(jubjub::Fr::one(), |acc, _| acc.double());
52        }
53
54        m_i += tmp;
55    }
56
57    m_i
58}
59
60/// "...an algebraic hash function with collision resistance (for fixed input
61/// length) derived from assumed hardness of the Discrete Logarithm Problem on
62/// the Jubjub curve."
63///
64/// PedersenHash is used in the definitions of Pedersen commitments (§
65/// 5.4.7.2 'Windowed Pedersen commitments'), and of the Pedersen hash for the
66/// Sapling incremental Merkle tree (§ 5.4.1.3 'MerkleCRH^Sapling Hash
67/// Function').
68///
69/// <https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash>
70#[allow(non_snake_case)]
71pub fn pedersen_hash_to_point(domain: [u8; 8], M: &BitVec<u8, Lsb0>) -> jubjub::ExtendedPoint {
72    let mut result = jubjub::ExtendedPoint::identity();
73
74    // Split M into n segments of 3 * c bits, where c = 63, padding the last
75    // segment with zeros.
76    //
77    // This loop is 1-indexed per the math definitions in the spec.
78    //
79    // https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash
80    for (i, segment) in M
81        .chunks(189)
82        .enumerate()
83        .map(|(i, segment)| (i + 1, segment))
84    {
85        result += I_i(domain, i as u32) * M_i(segment);
86    }
87
88    result
89}
90
91/// Pedersen Hash Function
92///
93/// This is technically returning 255 (l_MerkleSapling) bits, not 256.
94///
95/// <https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash>
96#[allow(non_snake_case)]
97pub fn pedersen_hash(domain: [u8; 8], M: &BitVec<u8, Lsb0>) -> jubjub::Fq {
98    jubjub::AffinePoint::from(pedersen_hash_to_point(domain, M)).get_u()
99}
100
101/// Construct a 'windowed' Pedersen commitment by reusing a Pederson hash
102/// construction, and adding a randomized point on the Jubjub curve.
103///
104/// WindowedPedersenCommit_r (s) := \
105///   PedersenHashToPoint("Zcash_PH", s) + \[r\]FindGroupHash^J^(r)("Zcash_PH", "r")
106///
107/// <https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit>
108pub fn windowed_pedersen_commitment(r: jubjub::Fr, s: &BitVec<u8, Lsb0>) -> jubjub::ExtendedPoint {
109    const D: [u8; 8] = *b"Zcash_PH";
110
111    pedersen_hash_to_point(D, s) + find_group_hash(D, b"r") * r
112}