use crate::PrimeField64;
pub trait Hash<F: PrimeField64> {
const WIDTH: usize;
const RATE: usize;
const CAPACITY: usize;
type State: Default + Copy + AsRef<[F]> + AsMut<[F]>;
fn hash(state: &mut Self::State);
fn linear_hash(input: &[F]) -> [F; 4]
where
Self: Sized,
{
let state = crate::merkle::linear_hash_seq::<F, Self>(input);
let s = state.as_ref();
[s[0], s[1], s[2], s[3]]
}
}
use crate::{poseidon2_hash, Poseidon2_4, Poseidon2_8, Poseidon2_12, Poseidon2_16};
impl<F: PrimeField64> Hash<F> for Poseidon2_4 {
const WIDTH: usize = 4;
const RATE: usize = 0;
const CAPACITY: usize = 4;
type State = [F; 4];
fn hash(state: &mut [F; 4]) {
*state = poseidon2_hash::<F, Self, 4>(state);
}
}
impl<F: PrimeField64> Hash<F> for Poseidon2_8 {
const WIDTH: usize = 8;
const RATE: usize = 4;
const CAPACITY: usize = 4;
type State = [F; 8];
fn hash(state: &mut [F; 8]) {
*state = poseidon2_hash::<F, Self, 8>(state);
}
}
impl<F: PrimeField64> Hash<F> for Poseidon2_12 {
const WIDTH: usize = 12;
const RATE: usize = 8;
const CAPACITY: usize = 4;
type State = [F; 12];
fn hash(state: &mut [F; 12]) {
*state = poseidon2_hash::<F, Self, 12>(state);
}
}
impl<F: PrimeField64> Hash<F> for Poseidon2_16 {
const WIDTH: usize = 16;
const RATE: usize = 12;
const CAPACITY: usize = 4;
type State = [F; 16];
fn hash(state: &mut [F; 16]) {
*state = poseidon2_hash::<F, Self, 16>(state);
}
}
use crate::{poseidon1_hash, Poseidon1_8, Poseidon1_12, Poseidon1_16};
impl<F: PrimeField64> Hash<F> for Poseidon1_8 {
const WIDTH: usize = 8;
const RATE: usize = 4;
const CAPACITY: usize = 4;
type State = [F; 8];
fn hash(state: &mut [F; 8]) {
*state = poseidon1_hash::<F, Self, 8>(state);
}
}
impl<F: PrimeField64> Hash<F> for Poseidon1_12 {
const WIDTH: usize = 12;
const RATE: usize = 8;
const CAPACITY: usize = 4;
type State = [F; 12];
fn hash(state: &mut [F; 12]) {
*state = poseidon1_hash::<F, Self, 12>(state);
}
}
impl<F: PrimeField64> Hash<F> for Poseidon1_16 {
const WIDTH: usize = 16;
const RATE: usize = 12;
const CAPACITY: usize = 4;
type State = [F; 16];
fn hash(state: &mut [F; 16]) {
*state = poseidon1_hash::<F, Self, 16>(state);
}
}
use crate::Blake3Transcript;
#[derive(Clone, Copy, Debug, Default)]
pub struct Blake3_8;
fn blake3_hash_le64<F: PrimeField64>(input: &[F]) -> [F; 4] {
let mut t = Blake3Transcript::<F>::new();
t.put(input);
let s = t.get_state();
[s[0], s[1], s[2], s[3]]
}
impl<F: PrimeField64> Hash<F> for Blake3_8 {
const WIDTH: usize = 8;
const RATE: usize = 4;
const CAPACITY: usize = 4;
type State = [F; 8];
fn hash(state: &mut [F; 8]) {
let dig = blake3_hash_le64::<F>(&state[..]);
state[..4].copy_from_slice(&dig);
state[4..].fill(F::ZERO);
}
fn linear_hash(input: &[F]) -> [F; 4] {
blake3_hash_le64::<F>(input)
}
}
#[cfg(test)]
mod blake3_tests {
use super::*;
use crate::{Field, Goldilocks};
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, Default)]
struct Blake3Core8;
fn core_hash_le64<F: PrimeField64>(input: &[F]) -> [F; 4] {
let mut h = crate::blake3_core::Hasher::new();
for x in input {
h.update(&x.as_canonical_u64().to_le_bytes());
}
let mut buf = [0u8; 32];
h.finalize_xof().fill(&mut buf);
core::array::from_fn(|i| {
F::from_u64(crate::blake3_transcript::canon(u64::from_le_bytes(buf[8 * i..8 * i + 8].try_into().unwrap())))
})
}
impl<F: PrimeField64> Hash<F> for Blake3Core8 {
const WIDTH: usize = 8;
const RATE: usize = 4;
const CAPACITY: usize = 4;
type State = [F; 8];
fn hash(state: &mut [F; 8]) {
let dig = core_hash_le64::<F>(&state[..]);
state[..4].copy_from_slice(&dig);
state[4..].fill(F::ZERO);
}
fn linear_hash(input: &[F]) -> [F; 4] {
core_hash_le64::<F>(input)
}
}
fn elems(n: usize) -> Vec<Goldilocks> {
(0..n as u64).map(|i| Goldilocks::from_u64(0x9E3779B97F4A7C15u64.wrapping_mul(i + 1))).collect()
}
#[test]
fn the_two_backends_agree_on_leaf_and_node_hashes() {
for n in [1usize, 4, 8, 9, 16, 127, 128, 129, 256, 300, 1000] {
let input = elems(n);
let core = <Blake3Core8 as Hash<Goldilocks>>::linear_hash(&input);
let krate = <Blake3_8 as Hash<Goldilocks>>::linear_hash(&input);
assert_eq!(core, krate, "leaf hash of {n} elements differs between the backends");
}
let input: [Goldilocks; 8] = core::array::from_fn(|i| elems(8)[i]);
let mut a = input;
let mut b = input;
<Blake3_8 as Hash<Goldilocks>>::hash(&mut a);
<Blake3Core8 as Hash<Goldilocks>>::hash(&mut b);
assert_eq!(a, b, "node compression differs between the backends");
}
#[test]
fn the_two_backends_agree_on_a_merkle_root_and_path() {
use crate::merkle::{calculate_root_from_proof, partial_merkle_tree};
let leaves = elems(16 * 4);
let root_krate = partial_merkle_tree::<Goldilocks, Blake3_8>(&leaves, 16, 2);
let root_core = partial_merkle_tree::<Goldilocks, Blake3Core8>(&leaves, 16, 2);
assert_eq!(root_krate, root_core, "merkle roots differ between the backends");
let mp: Vec<Vec<Goldilocks>> =
(0..4).map(|lvl| elems(4).iter().map(|x| *x + Goldilocks::from_u64(lvl)).collect()).collect();
let start: [Goldilocks; 8] = core::array::from_fn(|i| if i < 4 { leaves[i] } else { Goldilocks::ZERO });
let mut v_krate = start;
let mut i_krate = 5u64;
calculate_root_from_proof::<Goldilocks, Blake3_8>(&mut v_krate, &mp, &mut i_krate, 0, 2);
let mut v_core = start;
let mut i_core = 5u64;
calculate_root_from_proof::<Goldilocks, Blake3Core8>(&mut v_core, &mp, &mut i_core, 0, 2);
assert_eq!(v_krate, v_core, "recomputed root from a merkle path differs between the backends");
}
#[test]
fn blake3_node_and_leaf_agree_at_width_8() {
let input: [Goldilocks; 8] =
core::array::from_fn(|i| Goldilocks::from_u64(0x9E3779B97F4A7C15u64.wrapping_mul(i as u64 + 1)));
let leaf = <Blake3_8 as Hash<Goldilocks>>::linear_hash(&input);
let mut state = input;
<Blake3_8 as Hash<Goldilocks>>::hash(&mut state);
assert_eq!(&state[..4], &leaf[..], "node hash must be the leaf hash of its eight children");
assert_eq!(&state[4..], &[Goldilocks::ZERO; 4], "the tail must be cleared, not stale");
}
#[test]
fn blake3_leaf_hash_depends_on_the_whole_input() {
let long: Vec<Goldilocks> = (0..300u64).map(Goldilocks::from_u64).collect();
let mut clipped = long.clone();
clipped[299] = Goldilocks::from_u64(999);
assert_ne!(
<Blake3_8 as Hash<Goldilocks>>::linear_hash(&long),
<Blake3_8 as Hash<Goldilocks>>::linear_hash(&clipped),
"the last element of a 300-word leaf must reach the digest"
);
let short = [Goldilocks::from_u64(1), Goldilocks::from_u64(2)];
let padded = [Goldilocks::from_u64(1), Goldilocks::from_u64(2), Goldilocks::ZERO, Goldilocks::ZERO];
assert_ne!(
<Blake3_8 as Hash<Goldilocks>>::linear_hash(&short),
<Blake3_8 as Hash<Goldilocks>>::linear_hash(&padded),
"length is part of the digest; zero-padding must not collide"
);
}
}