mod hash_uncompressed;
#[cfg(test)]
mod tests;
use crate::Blake2Xs;
use snarkvm_console_types::prelude::*;
use snarkvm_utilities::BigInteger;
#[cfg(not(feature = "serial"))]
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
pub(super) const BHP_CHUNK_SIZE: usize = 3;
pub(super) const BHP_LOOKUP_SIZE: usize = 1 << BHP_CHUNK_SIZE;
pub(super) const BHP_NUM_COMBINED_CHUNKS: usize = 4;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(bound = "E: Serialize + DeserializeOwned")]
pub struct BHPHasher<E: Environment, const NUM_WINDOWS: u8, const WINDOW_SIZE: u8> {
bases: Arc<Vec<Vec<Group<E>>>>,
bases_lookup: Arc<Vec<Vec<[Group<E>; BHP_LOOKUP_SIZE]>>>,
combined_bases_lookup: Arc<[Vec<Vec<Group<E>>>]>,
random_base: Arc<Vec<Group<E>>>,
}
impl<E: Environment, const NUM_WINDOWS: u8, const WINDOW_SIZE: u8> BHPHasher<E, NUM_WINDOWS, WINDOW_SIZE> {
const MAX_BITS: usize = NUM_WINDOWS as usize * WINDOW_SIZE as usize * BHP_CHUNK_SIZE;
const MIN_BITS: usize = WINDOW_SIZE as usize * BHP_CHUNK_SIZE;
pub fn setup(domain: &str) -> Result<Self> {
#[cfg(feature = "dev-print")]
let timer = std::time::Instant::now();
let mut maximum_window_size = 0;
let mut range = E::BigInteger::from(2_u64);
while range < E::Scalar::modulus_minus_one_div_two() {
range.muln(4); maximum_window_size += 1;
}
ensure!(WINDOW_SIZE <= maximum_window_size, "The maximum BHP window size is {maximum_window_size}");
let bases = (0..NUM_WINDOWS)
.map(|index| {
let (generator, _, _) = Blake2Xs::hash_to_curve::<E::Affine>(&format!(
"Aleo.BHP.{NUM_WINDOWS}.{WINDOW_SIZE}.{domain}.{index}"
));
let mut base = Group::<E>::new(generator);
let mut powers = Vec::with_capacity(WINDOW_SIZE as usize);
for _ in 0..WINDOW_SIZE {
powers.push(base);
for _ in 0..4 {
base = base.double();
}
}
powers
})
.collect::<Vec<Vec<Group<E>>>>();
ensure!(bases.len() == NUM_WINDOWS as usize, "Incorrect number of BHP windows ({})", bases.len());
for window in &bases {
ensure!(window.len() == WINDOW_SIZE as usize, "Incorrect BHP window size ({})", window.len());
}
let bases_lookup = cfg_iter!(bases)
.map(|window| {
window
.iter()
.map(|g| {
let mut lookup = [Group::<E>::zero(); BHP_LOOKUP_SIZE];
for (i, element) in lookup.iter_mut().enumerate().take(BHP_LOOKUP_SIZE) {
*element = *g;
if (i & 0x01) != 0 {
*element += g;
}
if (i & 0x02) != 0 {
*element += g.double();
}
if (i & 0x04) != 0 {
*element = element.neg();
}
}
lookup
})
.collect()
})
.collect::<Vec<Vec<[Group<E>; BHP_LOOKUP_SIZE]>>>();
ensure!(bases_lookup.len() == NUM_WINDOWS as usize, "Incorrect number of BHP lookups ({})", bases_lookup.len());
for window in &bases_lookup {
ensure!(window.len() == WINDOW_SIZE as usize, "Incorrect BHP lookup window size ({})", window.len());
}
let combined_bases_lookup: Arc<[Vec<Vec<Group<E>>>]> = cfg_iter!(bases_lookup)
.map(|window| {
window
.chunks_exact(BHP_NUM_COMBINED_CHUNKS)
.map(|chunks| {
chunks.iter().fold(vec![Group::<E>::zero()], |prev_sums, new_terms| {
prev_sums
.iter()
.flat_map(|prev_sum| new_terms.iter().map(|new_term| *prev_sum + *new_term))
.collect::<Vec<Group<E>>>()
})
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
.into();
let (generator, _, _) =
Blake2Xs::hash_to_curve::<E::Affine>(&format!("Aleo.BHP.{NUM_WINDOWS}.{WINDOW_SIZE}.{domain}.Randomizer"));
let mut base_power = Group::<E>::new(generator);
let mut random_base = Vec::with_capacity(Scalar::<E>::size_in_bits());
for _ in 0..Scalar::<E>::size_in_bits() {
random_base.push(base_power);
base_power = base_power.double();
}
ensure!(
random_base.len() == Scalar::<E>::size_in_bits(),
"Incorrect number of BHP random base powers ({})",
random_base.len()
);
#[cfg(feature = "dev-print")]
{
let elapsed = timer.elapsed().as_micros() as f64 / 1000.0;
println!(
" • BHP hasher setup (NUM_WINDOWS = {NUM_WINDOWS}, WINDOW_SIZE = {WINDOW_SIZE}, NUM_COMBINED_CHUNKS = {BHP_NUM_COMBINED_CHUNKS}, DOMAIN = '{domain}'): {elapsed:.2} ms"
);
let num_els_bases: usize = bases.iter().map(|v| v.len()).sum();
let num_els_bases_lookup: usize =
bases_lookup.iter().map(|vs| vs.iter().map(|v| v.len()).sum::<usize>()).sum();
let num_els_combined_bases_lookup: usize =
combined_bases_lookup.iter().map(|vs| vs.iter().map(|v| v.len()).sum::<usize>()).sum();
let num_els_random_base = random_base.len();
let bytes = (num_els_bases + num_els_bases_lookup + num_els_combined_bases_lookup + num_els_random_base)
* std::mem::size_of::<Group<E>>();
println!(" Approximate BHP hasher size: {bytes} B");
}
Ok(Self {
bases: Arc::new(bases),
bases_lookup: Arc::new(bases_lookup),
combined_bases_lookup,
random_base: Arc::new(random_base),
})
}
pub fn bases(&self) -> &Arc<Vec<Vec<Group<E>>>> {
&self.bases
}
pub fn random_base(&self) -> &Arc<Vec<Group<E>>> {
&self.random_base
}
}