use alloc::string::String;
use ff::PrimeField;
use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength};
use pasta_curves::pallas;
pub const IMT_DEPTH: usize = 29;
pub(crate) fn gov_auth_domain_tag() -> pallas::Base {
let mut bytes = [0u8; 32];
bytes[..24].copy_from_slice(b"governance authorization");
pallas::Base::from_repr(bytes).unwrap()
}
pub(crate) fn poseidon_hash_2(a: pallas::Base, b: pallas::Base) -> pallas::Base {
poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash([a, b])
}
pub(crate) fn poseidon_hash_3(a: pallas::Base, b: pallas::Base, c: pallas::Base) -> pallas::Base {
poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<3>, 3, 2>::init().hash([a, b, c])
}
pub fn derive_nullifier_domain(vote_round_id: pallas::Base) -> pallas::Base {
poseidon_hash_2(gov_auth_domain_tag(), vote_round_id)
}
pub(crate) fn gov_null_hash(
nk: pallas::Base,
dom: pallas::Base,
real_nf: pallas::Base,
) -> pallas::Base {
poseidon_hash_3(nk, dom, real_nf)
}
#[derive(Clone, Debug)]
pub struct ImtProofData {
pub root: pallas::Base,
pub nf_bounds: [pallas::Base; 3],
pub leaf_pos: u32,
pub path: [pallas::Base; IMT_DEPTH],
}
#[derive(Clone, Debug)]
pub struct ImtError(pub String);
impl core::fmt::Display for ImtError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "IMT error: {}", self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ImtError {}
pub trait ImtProvider {
fn root(&self) -> pallas::Base;
fn non_membership_proof(&self, nf: pallas::Base) -> Result<ImtProofData, ImtError>;
}
use alloc::vec::Vec;
use ff::Field;
pub fn empty_imt_hashes() -> Vec<pallas::Base> {
let empty_leaf = poseidon_hash_3(pallas::Base::zero(), pallas::Base::zero(), pallas::Base::zero());
let mut hashes = vec![empty_leaf];
for _ in 1..=IMT_DEPTH {
let prev = *hashes.last().unwrap();
hashes.push(poseidon_hash_2(prev, prev));
}
hashes
}
const SENTINEL_EXPONENT: u64 = 249;
const SENTINEL_COUNT: u64 = 32;
fn build_sentinel_list() -> Vec<pallas::Base> {
let step = pallas::Base::from(2u64).pow([SENTINEL_EXPONENT, 0, 0, 0]);
let mut nfs: Vec<pallas::Base> = (0u64..=SENTINEL_COUNT)
.map(|k| step * pallas::Base::from(k))
.collect();
nfs.push(-pallas::Base::one()); nfs.sort();
nfs.dedup();
if nfs.len() % 2 == 0 {
debug_assert_eq!(nfs[0], pallas::Base::zero(), "sentinel 0 must be first");
nfs.insert(1, pallas::Base::from(2u64));
}
nfs
}
fn build_punctured_ranges_local(sorted_nfs: &[pallas::Base]) -> Vec<[pallas::Base; 3]> {
let n = sorted_nfs.len();
assert!(n >= 3, "need at least 3 sorted nullifiers, got {n}");
assert!(n % 2 == 1, "sorted nullifier count must be odd (got {n})");
let num_leaves = (n - 1) / 2;
(0..num_leaves)
.map(|i| {
let base = i * 2;
let (lo, mid, hi) = (sorted_nfs[base], sorted_nfs[base + 1], sorted_nfs[base + 2]);
assert!(
lo < mid && mid < hi,
"punctured range {i} violates strict ordering: \
nf_lo={lo:?}, nf_mid={mid:?}, nf_hi={hi:?}"
);
[lo, mid, hi]
})
.collect()
}
fn find_range_for_value(ranges: &[[pallas::Base; 3]], value: pallas::Base) -> Option<usize> {
let i = ranges.partition_point(|[nf_lo, _, _]| *nf_lo < value);
if i == 0 {
return None;
}
let idx = i - 1;
let [nf_lo, nf_mid, nf_hi] = ranges[idx];
let offset = value - nf_lo;
let span = nf_hi - nf_lo;
if offset == pallas::Base::zero() || offset >= span {
return None;
}
if value == nf_mid {
return None;
}
Some(idx)
}
#[derive(Debug)]
pub struct SpacedLeafImtProvider {
root: pallas::Base,
leaves: Vec<[pallas::Base; 3]>,
subtree_levels: Vec<Vec<pallas::Base>>,
}
impl SpacedLeafImtProvider {
pub fn new() -> Self {
let sorted_nfs = build_sentinel_list();
let leaves = build_punctured_ranges_local(&sorted_nfs);
let empty = empty_imt_hashes();
let empty_leaf_hash = poseidon_hash_3(
pallas::Base::zero(),
pallas::Base::zero(),
pallas::Base::zero(),
);
let mut level0 = vec![empty_leaf_hash; 32];
for (k, bounds) in leaves.iter().enumerate() {
level0[k] = poseidon_hash_3(bounds[0], bounds[1], bounds[2]);
}
let mut subtree_levels = vec![level0];
for _l in 1..=5 {
let prev = subtree_levels.last().unwrap();
let mut current = Vec::with_capacity(prev.len() / 2);
for j in 0..(prev.len() / 2) {
current.push(poseidon_hash_2(prev[2 * j], prev[2 * j + 1]));
}
subtree_levels.push(current);
}
let mut root = subtree_levels[5][0];
for l in 5..IMT_DEPTH {
root = poseidon_hash_2(root, empty[l]);
}
SpacedLeafImtProvider {
root,
leaves,
subtree_levels,
}
}
}
impl ImtProvider for SpacedLeafImtProvider {
fn root(&self) -> pallas::Base {
self.root
}
fn non_membership_proof(&self, nf: pallas::Base) -> Result<ImtProofData, ImtError> {
let k = find_range_for_value(&self.leaves, nf).ok_or_else(|| {
ImtError(alloc::format!(
"nullifier {nf:?} not in any punctured range"
))
})?;
let nf_bounds = self.leaves[k];
let leaf_pos = k as u32;
let empty = empty_imt_hashes();
let mut path = [pallas::Base::zero(); IMT_DEPTH];
let mut idx = k;
for l in 0..5 {
let sibling_idx = idx ^ 1;
path[l] = self.subtree_levels[l][sibling_idx];
idx >>= 1;
}
for l in 5..IMT_DEPTH {
path[l] = empty[l];
}
Ok(ImtProofData {
root: self.root,
nf_bounds,
leaf_pos,
path,
})
}
}