164 lines
4.5 KiB
Rust
164 lines
4.5 KiB
Rust
//! Implementation of the Pedersen hash function used in Sapling.
|
|
|
|
use crate::jubjub::*;
|
|
use byteorder::{ByteOrder, LittleEndian};
|
|
use ff::{Endianness, Field, PrimeField};
|
|
use std::ops::{AddAssign, Neg};
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub enum Personalization {
|
|
NoteCommitment,
|
|
MerkleTree(usize),
|
|
}
|
|
|
|
impl Personalization {
|
|
pub fn get_bits(&self) -> Vec<bool> {
|
|
match *self {
|
|
Personalization::NoteCommitment => vec![true, true, true, true, true, true],
|
|
Personalization::MerkleTree(num) => {
|
|
assert!(num < 63);
|
|
|
|
(0..6).map(|i| (num >> i) & 1 == 1).collect()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn pedersen_hash<E, I>(
|
|
personalization: Personalization,
|
|
bits: I,
|
|
params: &E::Params,
|
|
) -> edwards::Point<E, PrimeOrder>
|
|
where
|
|
I: IntoIterator<Item = bool>,
|
|
E: JubjubEngine,
|
|
{
|
|
let mut bits = personalization
|
|
.get_bits()
|
|
.into_iter()
|
|
.chain(bits.into_iter());
|
|
|
|
let mut result = edwards::Point::zero();
|
|
let mut generators = params.pedersen_hash_exp_table().iter();
|
|
|
|
loop {
|
|
let mut acc = E::Fs::zero();
|
|
let mut cur = E::Fs::one();
|
|
let mut chunks_remaining = params.pedersen_hash_chunks_per_generator();
|
|
let mut encountered_bits = false;
|
|
|
|
// Grab three bits from the input
|
|
while let Some(a) = bits.next() {
|
|
encountered_bits = true;
|
|
|
|
let b = bits.next().unwrap_or(false);
|
|
let c = bits.next().unwrap_or(false);
|
|
|
|
// Start computing this portion of the scalar
|
|
let mut tmp = cur;
|
|
if a {
|
|
tmp.add_assign(&cur);
|
|
}
|
|
cur = cur.double(); // 2^1 * cur
|
|
if b {
|
|
tmp.add_assign(&cur);
|
|
}
|
|
|
|
// conditionally negate
|
|
if c {
|
|
tmp = tmp.neg();
|
|
}
|
|
|
|
acc.add_assign(&tmp);
|
|
|
|
chunks_remaining -= 1;
|
|
|
|
if chunks_remaining == 0 {
|
|
break;
|
|
} else {
|
|
cur = cur.double().double().double(); // 2^4 * cur
|
|
}
|
|
}
|
|
|
|
if !encountered_bits {
|
|
break;
|
|
}
|
|
|
|
let mut table: &[Vec<edwards::Point<E, _>>] =
|
|
&generators.next().expect("we don't have enough generators");
|
|
let window = JubjubBls12::pedersen_hash_exp_window_size() as usize;
|
|
let window_mask = (1u64 << window) - 1;
|
|
|
|
let mut acc = acc.to_repr();
|
|
<E::Fs as PrimeField>::ReprEndianness::toggle_little_endian(&mut acc);
|
|
let num_limbs: usize = acc.as_ref().len() / 8;
|
|
let mut limbs = vec![0u64; num_limbs + 1];
|
|
LittleEndian::read_u64_into(acc.as_ref(), &mut limbs[..num_limbs]);
|
|
|
|
let mut tmp = edwards::Point::zero();
|
|
|
|
let mut pos = 0;
|
|
while pos < E::Fs::NUM_BITS as usize {
|
|
let u64_idx = pos / 64;
|
|
let bit_idx = pos % 64;
|
|
let i = (if bit_idx + window < 64 {
|
|
// This window's bits are contained in a single u64.
|
|
limbs[u64_idx] >> bit_idx
|
|
} else {
|
|
// Combine the current u64's bits with the bits from the next u64.
|
|
(limbs[u64_idx] >> bit_idx) | (limbs[u64_idx + 1] << (64 - bit_idx))
|
|
} & window_mask) as usize;
|
|
|
|
tmp = tmp.add(&table[0][i], params);
|
|
|
|
pos += window;
|
|
table = &table[1..];
|
|
}
|
|
|
|
result = result.add(&tmp, params);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test {
|
|
|
|
use super::*;
|
|
use crate::test_vectors::pedersen_hash_vectors;
|
|
use pairing::bls12_381::Bls12;
|
|
|
|
pub struct TestVector<'a> {
|
|
pub personalization: Personalization,
|
|
pub input_bits: Vec<u8>,
|
|
pub hash_x: &'a str,
|
|
pub hash_y: &'a str,
|
|
}
|
|
|
|
#[test]
|
|
fn test_pedersen_hash_points() {
|
|
let test_vectors = pedersen_hash_vectors::get_vectors();
|
|
|
|
assert!(test_vectors.len() > 0);
|
|
|
|
for v in test_vectors.iter() {
|
|
let params = &JubjubBls12::new();
|
|
|
|
let input_bools: Vec<bool> = v.input_bits.iter().map(|&i| i == 1).collect();
|
|
|
|
// The 6 bits prefix is handled separately
|
|
assert_eq!(v.personalization.get_bits(), &input_bools[..6]);
|
|
|
|
let (x, y) = pedersen_hash::<Bls12, _>(
|
|
v.personalization,
|
|
input_bools.into_iter().skip(6),
|
|
params,
|
|
)
|
|
.to_xy();
|
|
|
|
assert_eq!(x.to_string(), v.hash_x);
|
|
assert_eq!(y.to_string(), v.hash_y);
|
|
}
|
|
}
|
|
}
|