mod poseidon_primitives;
pub mod frozen;
pub mod poseidon;
use ff::PrimeField;
use halo2curves::bn256::Fr;
use poseidon::Bn254IncrementalMerkleTree;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OrchardMerklePath {
pub position: u32,
pub siblings: Vec<String>,
}
pub struct OrchardCommitmentTree {
inner: Bn254IncrementalMerkleTree,
size: u64,
latest_checkpoint: Option<u64>,
}
impl OrchardCommitmentTree {
pub fn new() -> Self {
Self { inner: Bn254IncrementalMerkleTree::new(), size: 0, latest_checkpoint: None }
}
pub fn append(&mut self, cmx_be: [u8; 32]) -> Option<u64> {
self.inner.append(be_bytes_to_fr(cmx_be));
let pos = self.size;
self.size += 1;
Some(pos)
}
pub fn checkpoint(&mut self, checkpoint_id: u64) -> bool {
self.latest_checkpoint = Some(checkpoint_id);
true
}
pub fn latest_root(&self) -> Option<[u8; 32]> {
if self.size == 0 {
return None;
}
Some(fr_to_le_bytes(self.inner.root()))
}
pub fn merkle_path(&self, position: u64, _checkpoint_id: u64) -> Option<OrchardMerklePath> {
if position >= self.size {
return None;
}
let siblings = self
.inner
.witness(position as u32)
.iter()
.map(|fr| format!("0x{}", hex::encode(fr_to_le_bytes(*fr))))
.collect();
Some(OrchardMerklePath { position: position as u32, siblings })
}
pub fn size(&self) -> u64 {
self.size
}
pub fn latest_checkpoint_id(&self) -> Option<u64> {
self.latest_checkpoint
}
}
impl Default for OrchardCommitmentTree {
fn default() -> Self {
Self::new()
}
}
fn be_bytes_to_fr(be: [u8; 32]) -> Fr {
let mut le = be;
le.reverse();
let mut limbs = [0u64; 4];
for (i, chunk) in le.chunks(8).enumerate() {
limbs[i] = u64::from_le_bytes(chunk.try_into().unwrap());
}
Fr::from_raw(limbs)
}
fn fr_to_le_bytes(fr: Fr) -> [u8; 32] {
fr.to_repr().into()
}
#[cfg(test)]
mod tests {
use super::*;
fn be(h: &str) -> [u8; 32] {
hex::decode(h).unwrap().try_into().unwrap()
}
#[test]
fn root_matches_onchain() {
let leaves = [
"16c1a78d9bf2a808a1f71e93b9caff5c86a28c79ea5cc5f1bebc52cbd5a936ff",
"2c421b91ff2f9ef6a4f024e56c29491eb2d26a6ae65ec34a420d6a70432a1fc0",
"1369c5ef5db9200ba955725e65855b1a4f77321a01a336a37e5807c6190e0fa0",
"11fe8d42f8ae822ccf7261f40e87c5695dff7853d942d308a8e4b7e5155cd781",
];
let mut tree = OrchardCommitmentTree::new();
for l in leaves {
tree.append(be(l));
}
let expected_le = "70c14793de62ea1c6b3f134efc7900bdd5d81c71ee041e5b6481c17d3dffe422";
assert_eq!(hex::encode(tree.latest_root().unwrap()), expected_le);
}
}