#[cfg(test)]
#[path = "patricia_hash_test.rs"]
mod patricia_hash_test;
use bitvec::prelude::{BitArray, Msb0};
use starknet_types_core::felt::Felt;
use starknet_types_core::hash::StarkHash as CoreStarkHash;
const TREE_HEIGHT: u8 = 64;
type BitPath = BitArray<[u8; 8], Msb0>;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Entry {
key: BitPath,
value: Felt,
}
#[derive(Debug)]
struct SubTree<'a> {
leaves: &'a [Entry],
height: u8,
}
enum SubTreeSplitting {
CommonZerosPrefix(u8),
PartitionPoint(usize),
}
pub fn calculate_root<H: CoreStarkHash>(values: Vec<Felt>) -> Felt {
if values.is_empty() {
return Felt::ZERO;
}
let leaves: Vec<Entry> = values
.into_iter()
.zip(0u64..)
.map(|(felt, idx)| Entry { key: idx.to_be_bytes().into(), value: felt })
.collect();
get_hash::<H>(SubTree { leaves: &leaves[..], height: 0_u8 })
}
fn get_hash<H: CoreStarkHash>(sub_tree: SubTree<'_>) -> Felt {
if sub_tree.height == TREE_HEIGHT {
return sub_tree.leaves.first().expect("a leaf should not be empty").value;
}
match get_splitting(&sub_tree) {
SubTreeSplitting::CommonZerosPrefix(n_zeros) => get_edge_hash::<H>(sub_tree, n_zeros),
SubTreeSplitting::PartitionPoint(partition_point) => {
get_binary_hash::<H>(sub_tree, partition_point)
}
}
}
fn get_edge_hash<H: CoreStarkHash>(sub_tree: SubTree<'_>, n_zeros: u8) -> Felt {
let child_hash =
get_hash::<H>(SubTree { leaves: sub_tree.leaves, height: sub_tree.height + n_zeros });
let child_and_path_hash = H::hash(&child_hash, &Felt::ZERO);
child_and_path_hash + Felt::from(n_zeros)
}
fn get_binary_hash<H: CoreStarkHash>(sub_tree: SubTree<'_>, partition_point: usize) -> Felt {
let zero_hash = get_hash::<H>(SubTree {
leaves: &sub_tree.leaves[..partition_point],
height: sub_tree.height + 1,
});
let one_hash = get_hash::<H>(SubTree {
leaves: &sub_tree.leaves[partition_point..],
height: sub_tree.height + 1,
});
H::hash(&zero_hash, &one_hash)
}
fn get_splitting(sub_tree: &SubTree<'_>) -> SubTreeSplitting {
let mut height = sub_tree.height;
let first_one_bit_index =
sub_tree.leaves.partition_point(|entry| !entry.key[usize::from(height)]);
if first_one_bit_index < sub_tree.leaves.len() {
return SubTreeSplitting::PartitionPoint(first_one_bit_index);
}
height += 1;
let mut n_zeros = 1;
while height < TREE_HEIGHT {
if sub_tree.leaves.last().expect("sub tree should not be empty").key[usize::from(height)] {
break;
}
n_zeros += 1;
height += 1;
}
SubTreeSplitting::CommonZerosPrefix(n_zeros)
}