sapling_crypto/
pedersen_hash.rs1#[cfg(test)]
4pub(crate) mod test_vectors;
5
6use alloc::vec::Vec;
7use core::ops::{AddAssign, Neg};
8use ff::PrimeField;
9use group::Group;
10
11use super::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_WINDOW_SIZE};
12
13#[derive(Copy, Clone)]
14pub enum Personalization {
15 NoteCommitment,
16 MerkleTree(usize),
17}
18
19impl Personalization {
20 pub fn get_bits(&self) -> Vec<bool> {
21 match *self {
22 Personalization::NoteCommitment => vec![true, true, true, true, true, true],
23 Personalization::MerkleTree(num) => {
24 assert!(num < 63);
25
26 (0..6).map(|i| (num >> i) & 1 == 1).collect()
27 }
28 }
29 }
30}
31
32pub fn pedersen_hash<I>(personalization: Personalization, bits: I) -> jubjub::SubgroupPoint
33where
34 I: IntoIterator<Item = bool>,
35{
36 let mut bits = personalization.get_bits().into_iter().chain(bits);
37
38 let mut result = jubjub::SubgroupPoint::identity();
39 let mut generators = crate::constants::PEDERSEN_HASH_EXP_TABLE.iter();
40
41 loop {
42 let mut acc = jubjub::Fr::zero();
43 let mut cur = jubjub::Fr::one();
44 let mut chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
45 let mut encountered_bits = false;
46
47 while let Some(a) = bits.next() {
49 encountered_bits = true;
50
51 let b = bits.next().unwrap_or(false);
52 let c = bits.next().unwrap_or(false);
53
54 let mut tmp = cur;
56 if a {
57 tmp.add_assign(&cur);
58 }
59 cur = cur.double(); if b {
61 tmp.add_assign(&cur);
62 }
63
64 if c {
66 tmp = tmp.neg();
67 }
68
69 acc.add_assign(&tmp);
70
71 chunks_remaining -= 1;
72
73 if chunks_remaining == 0 {
74 break;
75 } else {
76 cur = cur.double().double().double(); }
78 }
79
80 if !encountered_bits {
81 break;
82 }
83
84 let mut table: &[Vec<jubjub::SubgroupPoint>] =
85 generators.next().expect("we don't have enough generators");
86 let window = PEDERSEN_HASH_EXP_WINDOW_SIZE as usize;
87 let window_mask = (1u64 << window) - 1;
88
89 let acc = acc.to_repr();
90 let num_limbs: usize = acc.as_ref().len() / 8;
91 let mut limbs = vec![0u64; num_limbs + 1];
92 for (src, dst) in acc.chunks_exact(8).zip(limbs[..num_limbs].iter_mut()) {
93 *dst = u64::from_le_bytes(src.try_into().expect("correct length"));
94 }
95
96 let mut tmp = jubjub::SubgroupPoint::identity();
97
98 let mut pos = 0;
99 while pos < jubjub::Fr::NUM_BITS as usize {
100 let u64_idx = pos / 64;
101 let bit_idx = pos % 64;
102 let i = (if bit_idx + window < 64 {
103 limbs[u64_idx] >> bit_idx
105 } else {
106 (limbs[u64_idx] >> bit_idx) | (limbs[u64_idx + 1] << (64 - bit_idx))
108 } & window_mask) as usize;
109
110 tmp += table[0][i];
111
112 pos += window;
113 table = &table[1..];
114 }
115
116 result += tmp;
117 }
118
119 result
120}
121
122#[cfg(test)]
123pub mod test {
124 use alloc::string::ToString;
125 use group::Curve;
126
127 use super::*;
128
129 pub struct TestVector<'a> {
130 pub personalization: Personalization,
131 pub input_bits: Vec<u8>,
132 pub hash_u: &'a str,
133 pub hash_v: &'a str,
134 }
135
136 #[test]
137 fn test_pedersen_hash_points() {
138 let test_vectors = test_vectors::get_vectors();
139
140 assert!(!test_vectors.is_empty());
141
142 for v in test_vectors.iter() {
143 let input_bools: Vec<bool> = v.input_bits.iter().map(|&i| i == 1).collect();
144
145 assert_eq!(v.personalization.get_bits(), &input_bools[..6]);
147
148 let p = jubjub::ExtendedPoint::from(pedersen_hash(
149 v.personalization,
150 input_bools.into_iter().skip(6),
151 ))
152 .to_affine();
153
154 assert_eq!(p.get_u().to_string(), v.hash_u);
155 assert_eq!(p.get_v().to_string(), v.hash_v);
156 }
157 }
158}