Skip to main content

fil_sapling_crypto/
pedersen_hash.rs

1use fff::{Field, PrimeField, PrimeFieldRepr};
2use lazy_static::lazy_static;
3#[cfg(target_arch = "x86_64")]
4use paired::bls12_381::Bls12;
5
6use crate::jubjub::*;
7
8lazy_static! {
9    pub static ref CPU_SUPPORTS_ADX_INSTRUCTION: bool = {
10        #[cfg(target_arch = "x86_64")]
11        {
12            is_x86_feature_detected!("adx")
13        }
14        #[cfg(not(target_arch = "x86_64"))]
15        {
16            false
17        }
18    };
19}
20
21#[derive(Copy, Clone)]
22pub enum Personalization {
23    NoteCommitment,
24    MerkleTree(usize),
25    None,
26}
27
28impl Personalization {
29    pub fn get_bits(&self) -> Vec<bool> {
30        match *self {
31            Personalization::None => Vec::new(),
32            Personalization::NoteCommitment => vec![true, true, true, true, true, true],
33            Personalization::MerkleTree(num) => {
34                assert!(num < 63);
35
36                (0..6).map(|i| (num >> i) & 1 == 1).collect()
37            }
38        }
39    }
40}
41
42pub fn pedersen_hash<E, I>(
43    personalization: Personalization,
44    bits: I,
45    params: &E::Params,
46) -> edwards::Point<E, PrimeOrder>
47where
48    I: IntoIterator<Item = bool>,
49    E: JubjubEngine,
50{
51    let mut bits = personalization
52        .get_bits()
53        .into_iter()
54        .chain(bits.into_iter());
55
56    let mut result = edwards::Point::zero();
57    let mut generators = params.pedersen_hash_exp_table().iter();
58
59    loop {
60        let mut acc = E::Fs::zero();
61        let mut cur = E::Fs::one();
62        let mut chunks_remaining = params.pedersen_hash_chunks_per_generator();
63        let mut encountered_bits = false;
64
65        // Grab three bits from the input
66        while let Some(a) = bits.next() {
67            encountered_bits = true;
68
69            let b = bits.next().unwrap_or(false);
70            let c = bits.next().unwrap_or(false);
71
72            // Start computing this portion of the scalar
73            let mut tmp = cur;
74            if a {
75                tmp.add_assign(&cur);
76            }
77            cur.double(); // 2^1 * cur
78            if b {
79                tmp.add_assign(&cur);
80            }
81
82            // conditionally negate
83            if c {
84                tmp.negate();
85            }
86
87            acc.add_assign(&tmp);
88
89            chunks_remaining -= 1;
90
91            if chunks_remaining == 0 {
92                break;
93            } else {
94                cur.double(); // 2^2 * cur
95                cur.double(); // 2^3 * cur
96                cur.double(); // 2^4 * cur
97            }
98        }
99
100        if !encountered_bits {
101            break;
102        }
103
104        let mut table: &[Vec<edwards::Point<E, _>>] =
105            &generators.next().expect("we don't have enough generators");
106        let window = params.pedersen_hash_exp_window_size();
107        let window_mask = (1 << window) - 1;
108
109        let mut acc = acc.into_repr();
110
111        let mut tmp = edwards::Point::zero();
112
113        while !acc.is_zero() {
114            let i = (acc.as_ref()[0] & window_mask) as usize;
115
116            tmp = tmp.add(&table[0][i], params);
117
118            acc.shr(window);
119            table = &table[1..];
120        }
121
122        result = result.add(&tmp, params);
123    }
124
125    result
126}
127
128// If we are compiling for x86_64, export an optimized version of `pedersen_hash` that uses
129// precomputed values.
130#[cfg(target_arch = "x86_64")]
131pub fn pedersen_hash_bls12_381_with_precomp<I>(
132    personalization: Personalization,
133    bits: I,
134    params: &<Bls12 as JubjubEngine>::Params,
135) -> edwards::Point<Bls12, PrimeOrder>
136where
137    I: IntoIterator<Item = bool>,
138{
139    use std::convert::TryInto;
140    use std::slice;
141
142    // If we compiled for an x86_64 CPU, but the CPU does not support the ADX instruction, fallback
143    // to using the non-optimized Pedersen hash.
144    if !*CPU_SUPPORTS_ADX_INSTRUCTION {
145        return pedersen_hash::<Bls12, _>(personalization, bits, params);
146    }
147
148    let mut bits = personalization
149        .get_bits()
150        .into_iter()
151        .chain(bits.into_iter());
152
153    let mut result = edwards::Point::zero();
154    let mut generators = params.pedersen_hash_exp_table_precomp().iter();
155
156    loop {
157        let mut acc = <Bls12 as JubjubEngine>::Fs::zero();
158        let mut cur = <Bls12 as JubjubEngine>::Fs::one();
159        let mut chunks_remaining = params.pedersen_hash_chunks_per_generator();
160        let mut encountered_bits = false;
161
162        // Grab three bits from the input
163        while let Some(a) = bits.next() {
164            encountered_bits = true;
165
166            let b = bits.next().unwrap_or(false);
167            let c = bits.next().unwrap_or(false);
168
169            // Start computing this portion of the scalar
170            let mut tmp = cur;
171            if a {
172                tmp.add_assign(&cur);
173            }
174            cur.double(); // 2^1 * cur
175            if b {
176                tmp.add_assign(&cur);
177            }
178
179            // conditionally negate
180            if c {
181                tmp.negate();
182            }
183
184            acc.add_assign(&tmp);
185
186            chunks_remaining -= 1;
187
188            if chunks_remaining == 0 {
189                break;
190            } else {
191                cur.double(); // 2^2 * cur
192                cur.double(); // 2^3 * cur
193                cur.double(); // 2^4 * cur
194            }
195        }
196
197        if !encountered_bits {
198            break;
199        }
200
201        let mut table: &[Vec<edwards::Point<Bls12, PrimeOrder>>] =
202            &generators.next().expect("we don't have enough generators");
203        let window = params.pedersen_hash_exp_window_size();
204        let window_mask = (1 << window) - 1;
205
206        let mut acc = acc.into_repr();
207
208        let mut tmp = edwards::Point::zero();
209        let tmp_bytes_mut =
210            unsafe { slice::from_raw_parts_mut((&mut tmp as *mut _) as *mut u64, 16) };
211        let tmp_bytes = unsafe { slice::from_raw_parts((&tmp as *const _) as *const u64, 16) };
212
213        while !acc.is_zero() {
214            let i = (acc.as_ref()[0] & window_mask) as usize;
215            let p2 = unsafe { slice::from_raw_parts((&table[0][i] as *const _) as *const u64, 16) };
216
217            twisted_edwards_add::ext_twisted_ed_add_256(
218                tmp_bytes.try_into().expect("slice needs len of 16"),
219                p2.try_into().expect("slice needs len of 16"),
220                tmp_bytes_mut.try_into().expect("slice needs len of 16"),
221            );
222
223            acc.shr(window);
224            table = &table[1..];
225        }
226
227        result = result.add(&tmp, params);
228    }
229
230    result
231}
232
233#[cfg(test)]
234mod test {
235    use super::*;
236    use paired::bls12_381::Bls12;
237    use rand::{Rng, SeedableRng};
238    use rand_xorshift::XorShiftRng;
239
240    #[cfg(target_arch = "x86_64")]
241    #[test]
242    fn test_pedersen_hash_vs_precomp() {
243        let params = JubjubBls12::new_with_window_size(16);
244        let rng = &mut XorShiftRng::from_seed([
245            0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
246            0xbc, 0xe5,
247        ]);
248        let personalization = Personalization::MerkleTree(31);
249
250        // The number of bits in 5 segments worth of preimage (945 bits) minus the 6 personalization
251        // bits.
252        let max_preimage_bits = 945 - 6;
253
254        for _ in 0..500000 {
255            let preimage_len_bits = rng.gen_range(0, max_preimage_bits + 1);
256            let bits: Vec<bool> = (0..preimage_len_bits).map(|_| rng.gen()).collect();
257            let hash_orig =
258                pedersen_hash::<Bls12, _>(personalization, bits.clone(), &params).into_xy();
259            let hash_new =
260                pedersen_hash_bls12_381_with_precomp::<_>(personalization, bits.clone(), &params)
261                    .into_xy();
262            assert_eq!(hash_orig, hash_new);
263        }
264    }
265}