#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#[cfg(not(feature = "std"))]
extern crate alloc;
mod maybestd {
#[cfg(not(feature = "std"))]
pub use alloc::{boxed, vec};
#[cfg(all(not(feature = "std"), feature = "serde"))]
pub use alloc::{format, string};
#[cfg(not(feature = "std"))]
pub use core::{cmp, fmt, hash, marker, mem, ops};
#[cfg(feature = "std")]
pub use std::{boxed, cmp, fmt, hash, marker, mem, ops, vec};
#[cfg(all(feature = "std", feature = "serde"))]
pub use std::{format, string};
pub mod hash_or_btree_map {
#[cfg(not(feature = "std"))]
pub use alloc::collections::btree_map::{BTreeMap as Map, Entry};
#[cfg(feature = "std")]
pub use std::collections::hash_map::{Entry, HashMap as Map};
}
}
use crate::maybestd::{hash_or_btree_map, ops::Range, vec::Vec};
pub use nmt_proof::NamespaceProof;
use simple_merkle::{
db::{LeafWithHash, MemDb, PreimageDb},
error::RangeProofError,
proof::Proof,
tree::{MerkleHash, MerkleTree},
utils::compute_num_left_siblings,
};
mod namespaced_hash;
pub use namespaced_hash::*;
mod tendermint_hash;
pub use tendermint_hash::*;
pub mod nmt_proof;
pub mod simple_merkle;
const CELESTIA_NS_ID_SIZE: usize = 29;
pub type CelestiaNmt = NamespaceMerkleTree<
MemDb<NamespacedHash<CELESTIA_NS_ID_SIZE>>,
NamespacedSha2Hasher<CELESTIA_NS_ID_SIZE>,
CELESTIA_NS_ID_SIZE,
>;
fn check_proof_completeness<const NS_ID_SIZE: usize>(
leaves: &[NamespacedHash<NS_ID_SIZE>],
proof: &[NamespacedHash<NS_ID_SIZE>],
num_left_siblings: usize,
) -> RangeProofType {
let mut proof_type = RangeProofType::Complete;
if num_left_siblings != 0 {
let rightmost_left_sibling = &proof[num_left_siblings - 1];
if rightmost_left_sibling.max_namespace() >= leaves[0].min_namespace() {
proof_type = RangeProofType::Partial
}
}
let num_right_siblings = proof.len() - num_left_siblings;
if num_right_siblings != 0 {
let leftmost_right_sibling = &proof[num_left_siblings];
if leftmost_right_sibling.min_namespace()
<= leaves
.last()
.expect("leaves has already been checked to be non-empty")
.max_namespace()
{
proof_type = RangeProofType::Partial
}
}
proof_type
}
pub struct NamespaceMerkleTree<Db, M: MerkleHash, const NS_ID_SIZE: usize> {
namespace_ranges: hash_or_btree_map::Map<NamespaceId<NS_ID_SIZE>, Range<usize>>,
highest_ns: NamespaceId<NS_ID_SIZE>,
ignore_max_ns: bool,
inner: MerkleTree<Db, M>,
}
impl<Db, M, const NS_ID_SIZE: usize> NamespaceMerkleTree<Db, M, NS_ID_SIZE>
where
Db: PreimageDb<M::Output>,
M: NamespaceMerkleHasher<NS_ID_SIZE, Output = NamespacedHash<NS_ID_SIZE>> + Default,
{
pub fn new() -> Self {
Default::default()
}
}
impl<Db, M, const NS_ID_SIZE: usize> NamespaceMerkleTree<Db, M, NS_ID_SIZE>
where
Db: PreimageDb<M::Output>,
M: NamespaceMerkleHasher<NS_ID_SIZE, Output = NamespacedHash<NS_ID_SIZE>>,
{
pub fn with_hasher(hasher: M) -> Self {
Self {
namespace_ranges: Default::default(),
highest_ns: NamespaceId([0u8; NS_ID_SIZE]),
ignore_max_ns: hasher.ignores_max_ns(),
inner: MerkleTree::<Db, M>::with_hasher(hasher),
}
}
pub fn push_leaf(
&mut self,
raw_data: &[u8],
namespace: NamespaceId<NS_ID_SIZE>,
) -> Result<(), &'static str> {
if namespace < self.highest_ns {
return Err("Leaves' namespaces should be inserted in ascending order");
}
let leaf =
LeafWithHash::new_with_namespace(raw_data.to_vec(), namespace, self.ignore_max_ns);
self.highest_ns = namespace;
self.inner.push_leaf_with_hash(leaf);
let leaves_len = self.leaves().len();
match self.namespace_ranges.entry(namespace) {
hash_or_btree_map::Entry::Occupied(entry) => {
entry.into_mut().end = leaves_len;
}
hash_or_btree_map::Entry::Vacant(entry) => {
entry.insert(leaves_len - 1..leaves_len);
}
}
Ok(())
}
pub fn root(&mut self) -> NamespacedHash<NS_ID_SIZE> {
self.inner.root()
}
fn check_range_proof(
&self,
root: &NamespacedHash<NS_ID_SIZE>,
leaves: &[NamespacedHash<NS_ID_SIZE>],
proof: &[NamespacedHash<NS_ID_SIZE>],
leaves_start_idx: usize,
) -> Result<RangeProofType, RangeProofError> {
match leaves.len() {
0 => {
if root == &M::EMPTY_ROOT && proof.is_empty() {
return Ok(RangeProofType::Complete);
}
return Err(RangeProofError::NoLeavesProvided);
}
1 => {
if proof.is_empty() {
if &leaves[0] == root && leaves_start_idx == 0 {
return Ok(RangeProofType::Complete);
}
return Err(RangeProofError::TreeDoesNotContainLeaf);
}
}
_ => {}
};
for hash in proof.iter() {
if hash.min_namespace() > hash.max_namespace() {
return Err(RangeProofError::MalformedTree);
}
}
let num_left_siblings = compute_num_left_siblings(leaves_start_idx);
let proof_completeness = check_proof_completeness(leaves, proof, num_left_siblings);
self.inner
.check_range_proof(root, leaves, proof, leaves_start_idx)?;
Ok(proof_completeness)
}
pub fn build_range_proof(&mut self, leaf_range: Range<usize>) -> Proof<M> {
self.inner.build_range_proof(leaf_range)
}
pub fn get_range_with_proof(
&mut self,
leaf_range: Range<usize>,
) -> (Vec<Vec<u8>>, NamespaceProof<M, NS_ID_SIZE>) {
let (leaves, proof) = self.inner.get_range_with_proof(leaf_range);
(
leaves,
NamespaceProof::PresenceProof {
proof,
ignore_max_ns: self.ignore_max_ns,
},
)
}
pub fn get_index_with_proof(&mut self, idx: usize) -> (Vec<u8>, Proof<M>) {
self.inner.get_index_with_proof(idx)
}
pub fn get_namespace_with_proof(
&mut self,
namespace: NamespaceId<NS_ID_SIZE>,
) -> (Vec<Vec<u8>>, NamespaceProof<M, NS_ID_SIZE>) {
let leaf_range = if let Some(range) = self.namespace_ranges.get(&namespace) {
range.clone()
} else {
0..0
};
let leaves = self.inner.get_leaves(leaf_range);
(leaves, self.get_namespace_proof(namespace))
}
pub fn leaves(&self) -> &[LeafWithHash<M>] {
self.inner.leaves()
}
pub fn get_namespace_proof(
&mut self,
namespace: NamespaceId<NS_ID_SIZE>,
) -> NamespaceProof<M, NS_ID_SIZE> {
if !self.root().contains::<M>(namespace) {
return NamespaceProof::AbsenceProof {
proof: Default::default(),
ignore_max_ns: self.ignore_max_ns,
leaf: None,
};
}
if let Some(leaf_range) = self.namespace_ranges.get(&namespace) {
return NamespaceProof::PresenceProof {
proof: self.inner.build_range_proof(leaf_range.clone()),
ignore_max_ns: self.ignore_max_ns,
};
}
let namespace = self
.inner
.leaves()
.binary_search_by(|l| l.hash().min_namespace().cmp(&namespace));
let idx =
namespace.expect_err("tree cannot contain leaf with namespace that does not exist");
let proof = self.build_range_proof(idx..idx + 1);
let mut proof = NamespaceProof::PresenceProof {
proof,
ignore_max_ns: self.ignore_max_ns,
};
proof.convert_to_absence_proof(self.inner.leaves()[idx].hash().clone());
proof
}
fn verify_namespace(
&self,
root: &NamespacedHash<NS_ID_SIZE>,
raw_leaves: &[impl AsRef<[u8]>],
namespace: NamespaceId<NS_ID_SIZE>,
proof: &NamespaceProof<M, NS_ID_SIZE>,
) -> Result<(), RangeProofError> {
if root.is_empty_root::<M>() && raw_leaves.is_empty() {
return Ok(());
}
match proof {
NamespaceProof::AbsenceProof { leaf, .. } => {
if !root.contains::<M>(namespace) {
return Ok(());
}
let leaf = leaf.clone().ok_or(RangeProofError::MalformedProof(
"Absence proof was inside tree range but did not contain a leaf",
))?;
if !raw_leaves.is_empty() {
return Err(RangeProofError::MalformedProof(
"provided an absence proof for a non-empty namespace",
));
}
if namespace >= leaf.min_namespace() {
return Err(RangeProofError::MalformedProof(
"provided leaf must have namespace greater than the namespace which is being proven absent",
));
}
let num_left_siblings = compute_num_left_siblings(proof.start_idx() as usize);
let siblings = proof.siblings();
if num_left_siblings > 0 {
let rightmost_left_sibling = &siblings[num_left_siblings - 1];
if rightmost_left_sibling.max_namespace() >= namespace {
return Err(RangeProofError::MalformedProof("proven namespace must be greater than the namespace of the rightmost left sibling"));
}
}
self.inner.check_range_proof(
root,
&[leaf],
proof.siblings(),
proof.start_idx() as usize,
)?;
}
NamespaceProof::PresenceProof { ignore_max_ns, .. } => {
if !root.contains::<M>(namespace) {
return Err(RangeProofError::TreeDoesNotContainLeaf);
}
let leaf_hashes: Vec<NamespacedHash<NS_ID_SIZE>> = raw_leaves
.iter()
.map(|data| {
M::with_ignore_max_ns(*ignore_max_ns)
.hash_leaf_with_namespace(data.as_ref(), namespace)
})
.collect();
let proof_type = self.check_range_proof(
root,
&leaf_hashes,
proof.siblings(),
proof.start_idx() as usize,
)?;
if proof_type == RangeProofType::Partial {
return Err(RangeProofError::MissingLeaf);
}
}
}
Ok(())
}
}
impl<Db, M, const NS_ID_SIZE: usize> Default for NamespaceMerkleTree<Db, M, NS_ID_SIZE>
where
Db: PreimageDb<M::Output>,
M: MerkleHash + Default,
{
fn default() -> Self {
Self {
namespace_ranges: Default::default(),
highest_ns: NamespaceId::default(),
ignore_max_ns: true,
inner: Default::default(),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum RangeProofType {
Complete,
Partial,
}
#[cfg(test)]
mod tests {
use crate::maybestd::vec::Vec;
use crate::NamespaceMerkleHasher;
use crate::{
namespaced_hash::{NamespaceId, NamespacedSha2Hasher},
nmt_proof::NamespaceProof,
simple_merkle::db::MemDb,
NamespaceMerkleTree, NamespacedHash, RangeProofType, CELESTIA_NS_ID_SIZE,
};
type DefaultNmt<const NS_ID_SIZE: usize> = NamespaceMerkleTree<
MemDb<NamespacedHash<NS_ID_SIZE>>,
NamespacedSha2Hasher<NS_ID_SIZE>,
NS_ID_SIZE,
>;
fn ns_id_from_u64<const NS_ID_SIZE: usize>(val: u64) -> NamespaceId<NS_ID_SIZE> {
assert!(NS_ID_SIZE >= 8);
let mut namespace = NamespaceId::default();
namespace.0[NS_ID_SIZE - 8..].copy_from_slice(&val.to_be_bytes());
namespace
}
fn tree_from_namespace_ids<const NS_ID_SIZE: usize>(
ns_ids: impl AsRef<[u64]>,
) -> DefaultNmt<NS_ID_SIZE> {
let mut tree = DefaultNmt::new();
for (i, &ns_id) in ns_ids.as_ref().iter().enumerate() {
let data = format!("leaf_{i}");
let namespace = ns_id_from_u64(ns_id);
tree.push_leaf(data.as_bytes(), namespace)
.expect("Failed to push the leaf");
}
tree
}
fn tree_with_n_leaves<const NS_ID_SIZE: usize>(n: usize) -> DefaultNmt<NS_ID_SIZE> {
tree_from_namespace_ids((0..n as u64).collect::<Vec<_>>())
}
#[test]
fn test_absence_proof_leaf_advances_the_namespace() {
let mut tree = tree_from_namespace_ids::<8>(&[1, 2, 3, 4, 6, 7, 8, 9]);
let namespace = ns_id_from_u64(5);
let proof = tree.get_namespace_proof(namespace);
let no_leaves: &[&[u8]] = &[];
proof
.verify_complete_namespace(&tree.root(), no_leaves, namespace)
.unwrap();
let NamespaceProof::AbsenceProof {
leaf: Some(leaf), ..
} = proof
else {
unreachable!();
};
assert!(leaf.min_namespace() > namespace);
}
#[test]
fn test_absence_proof_return_err_if_leaf_doesnt_follow_rightmost_left_sibling() {
let mut tree = tree_from_namespace_ids::<8>(&[1, 2, 3, 4, 6, 7, 8, 9]);
let namespace = ns_id_from_u64(5);
let proof = tree.get_namespace_proof(namespace);
let no_leaves: &[&[u8]] = &[];
for i in [3, 4, 6, 7] {
let mut proof = proof.clone();
let NamespaceProof::AbsenceProof { leaf, .. } = &mut proof else {
unreachable!();
};
let data = format!("leaf_{i}").as_bytes().to_vec();
*leaf = Some(
NamespacedSha2Hasher::default().hash_leaf_with_namespace(&data, ns_id_from_u64(i)),
);
proof
.verify_complete_namespace(&tree.root(), no_leaves, ns_id_from_u64(2))
.unwrap_err();
}
}
#[test]
fn test_absence_proof_doesnt_include_leaf_if_namespace_is_out_of_root_ns_range() {
let mut tree = tree_from_namespace_ids::<8>(&[2, 3, 4, 5]);
for namespace in [1, 6] {
let namespace = ns_id_from_u64(namespace);
let proof = tree.get_namespace_proof(namespace);
proof
.clone()
.verify_complete_namespace(&tree.root(), &Vec::<Vec<u8>>::new(), namespace)
.unwrap();
assert!(matches!(
proof,
NamespaceProof::AbsenceProof { leaf: None, .. }
));
}
}
#[test]
fn test_wrong_amount_of_leaves() {
let mut tree = tree_from_namespace_ids::<8>(&[1, 2, 2, 2, 3, 4, 5, 6]);
let namespace = ns_id_from_u64(2);
let proof = tree.get_namespace_proof(namespace);
let leaves = [b"leaf_1", b"leaf_2", b"leaf_3", b"leaf_4"];
for leaves in [&leaves[..], &leaves[..2]] {
proof
.verify_complete_namespace(&tree.root(), leaves, namespace)
.unwrap_err();
proof
.verify_range(&tree.root(), leaves, namespace)
.unwrap_err();
}
proof
.verify_complete_namespace(&tree.root(), &leaves[..3], namespace)
.unwrap();
proof
.verify_range(&tree.root(), &leaves[..3], namespace)
.unwrap();
}
fn test_range_proof_roundtrip_with_n_leaves<const NS_ID_SIZE: usize>(n: usize) {
let mut tree = tree_with_n_leaves::<NS_ID_SIZE>(n);
let root = tree.root();
for i in 1..=n {
for j in 0..=i {
let proof = tree.build_range_proof(j..i);
let leaf_hashes: Vec<_> = tree.leaves()[j..i]
.iter()
.map(|l| l.hash().clone())
.collect();
let res = tree.check_range_proof(&root, &leaf_hashes, proof.siblings(), j);
if i != j {
assert!(res.is_ok());
assert_eq!(res.unwrap(), RangeProofType::Complete)
} else {
assert!(res.is_err())
}
}
}
test_min_and_max_ns_against(&mut tree)
}
#[test]
fn test_range_proof_roundtrip() {
for x in 0..20 {
test_range_proof_roundtrip_with_n_leaves::<8>(x);
test_range_proof_roundtrip_with_n_leaves::<17>(x);
test_range_proof_roundtrip_with_n_leaves::<24>(x);
test_range_proof_roundtrip_with_n_leaves::<CELESTIA_NS_ID_SIZE>(x);
test_range_proof_roundtrip_with_n_leaves::<32>(x);
}
}
fn test_completeness_check_impl<const NS_ID_SIZE: usize>() {
let mut tree = DefaultNmt::<NS_ID_SIZE>::new();
for x in 0..32 {
let namespace = ns_id_from_u64(x / 4);
let _ = tree.push_leaf(x.to_be_bytes().as_ref(), namespace);
}
let root = tree.root();
let leaf_hashes: Vec<_> = tree.leaves().iter().map(|x| x.hash().clone()).collect();
for i in 0..=28 {
let leaf_range = i..i + 4;
let proof = tree.build_range_proof(leaf_range.clone());
let result =
tree.check_range_proof(&root, &leaf_hashes[leaf_range], proof.siblings(), i);
assert!(result.is_ok());
let should_be_complete = (i % 4) == 0;
if should_be_complete {
assert_eq!(result, Ok(RangeProofType::Complete))
} else {
assert_eq!(result, Ok(RangeProofType::Partial))
}
}
for nid in 0..100u64 {
let namespace = ns_id_from_u64(nid);
let (leaves, proof) = tree.get_namespace_with_proof(namespace);
let pf = proof.verify_complete_namespace(&root, &leaves, namespace);
assert!(pf.is_ok());
}
}
#[test]
fn test_completeness_check() {
test_completeness_check_impl::<8>();
test_completeness_check_impl::<17>();
test_completeness_check_impl::<24>();
test_completeness_check_impl::<CELESTIA_NS_ID_SIZE>();
test_completeness_check_impl::<32>();
}
fn test_min_and_max_ns_against<const NS_ID_SIZE: usize>(tree: &mut DefaultNmt<NS_ID_SIZE>) {
let root = tree.root();
let min_namespace = NamespaceId([0; NS_ID_SIZE]);
let max_namespace = NamespaceId([0xff; NS_ID_SIZE]);
let (leaves, proof) = tree.get_namespace_with_proof(min_namespace);
assert!(proof
.verify_complete_namespace(&root, &leaves, min_namespace)
.is_ok());
let (leaves, proof) = tree.get_namespace_with_proof(max_namespace);
assert!(proof
.verify_complete_namespace(&root, &leaves, max_namespace)
.is_ok());
tree.push_leaf(b"some_leaf", max_namespace)
.expect("can always push max namespace");
let root = tree.root();
let (leaves, proof) = tree.get_namespace_with_proof(max_namespace);
assert!(proof
.verify_complete_namespace(&root, &leaves, max_namespace)
.is_ok());
}
fn test_namespace_verification_impl<const NS_ID_SIZE: usize>() {
let mut tree = DefaultNmt::<NS_ID_SIZE>::new();
for x in 0..33 {
let namespace = ns_id_from_u64(((x / 5) * 3) + 1);
let _ = tree.push_leaf(x.to_be_bytes().as_ref(), namespace);
}
let root = tree.root();
let raw_leaves: Vec<Vec<u8>> = tree.leaves().iter().map(|x| x.data().to_vec()).collect();
for (namespace, range) in tree.namespace_ranges.clone().iter() {
let proof = tree.build_range_proof(range.clone());
assert!(!range.is_empty());
let proof = NamespaceProof::PresenceProof {
proof,
ignore_max_ns: true,
};
assert!(tree
.verify_namespace(&root, &raw_leaves[range.clone()], *namespace, &proof)
.is_ok());
}
for nid in 0..100u64 {
let namespace = ns_id_from_u64(nid);
let (leaves, proof) = tree.get_namespace_with_proof(namespace);
let pf = proof.verify_complete_namespace(&root, &leaves, namespace);
assert!(pf.is_ok());
}
test_min_and_max_ns_against(&mut tree)
}
#[test]
fn test_namespace_verification() {
test_namespace_verification_impl::<8>();
test_namespace_verification_impl::<17>();
test_namespace_verification_impl::<24>();
test_namespace_verification_impl::<CELESTIA_NS_ID_SIZE>();
test_namespace_verification_impl::<32>();
}
}