use std::collections::BTreeMap;
use spine::{ARITY_RANGE, Hasher, ProofStep, Seal, nary_mr};
use crate::error::{Error, Result};
use crate::shape::{self, ShapeNode, covers, leftmost, rightmost};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub arity: u64,
}
impl Default for Config {
fn default() -> Self {
Self { arity: 2 }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct Cell {
payload: Vec<u8>,
metadata: Vec<u8>,
}
struct AlgState {
root: Option<Vec<u8>>,
cache: BTreeMap<(u64, u64), Vec<u8>>,
}
impl std::fmt::Debug for AlgState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AlgState")
.field("root", &self.root)
.field("materialized_nodes", &self.cache.len())
.finish()
}
}
#[derive(Debug)]
pub struct Cmt {
config: Config,
cells: Vec<Cell>,
hashers: BTreeMap<u64, Box<dyn Hasher>>,
states: BTreeMap<u64, AlgState>,
}
impl Cmt {
pub fn new(config: Config) -> Result<Self> {
if !ARITY_RANGE.contains(&config.arity) {
return Err(Error::InvalidArity(config.arity));
}
Ok(Self {
config,
cells: Vec::new(),
hashers: BTreeMap::new(),
states: BTreeMap::new(),
})
}
pub fn register_algorithm(&mut self, alg_id: u64, hasher: Box<dyn Hasher>) -> Result<()> {
if self.hashers.contains_key(&alg_id) {
return Err(Error::DuplicateAlgorithm(alg_id));
}
let mut state = AlgState {
root: None,
cache: BTreeMap::new(),
};
recompute_full(&self.cells, self.config.arity, hasher.as_ref(), &mut state);
self.hashers.insert(alg_id, hasher);
self.states.insert(alg_id, state);
Ok(())
}
pub fn set(&mut self, index: u64, payload: Vec<u8>, metadata: Vec<u8>) -> Result<()> {
let len = self.cells.len() as u64;
if index > len {
return Err(Error::IndexGap { index, len });
}
let cell = Cell { payload, metadata };
let appended = index == len;
if appended {
self.cells.push(cell);
} else {
self.cells[index as usize] = cell;
}
for (id, state) in &mut self.states {
let h = self
.hashers
.get(id)
.expect("states and hashers are in sync")
.as_ref();
if appended {
recompute_full(&self.cells, self.config.arity, h, state);
} else {
let _ = recompute_path(&self.cells, self.config.arity, h, state, index);
}
}
Ok(())
}
#[must_use]
pub fn get(&self, index: u64) -> Option<&[u8]> {
self.cells.get(index as usize).map(|c| c.payload.as_slice())
}
#[must_use]
pub fn metadata(&self, index: u64) -> Option<&[u8]> {
self.cells
.get(index as usize)
.map(|c| c.metadata.as_slice())
}
#[must_use]
pub fn len(&self) -> u64 {
self.cells.len() as u64
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cells.is_empty()
}
#[must_use]
pub fn arity(&self) -> u64 {
self.config.arity
}
#[must_use]
pub fn root(&self, alg_id: u64) -> Option<Vec<u8>> {
self.states.get(&alg_id).and_then(|s| s.root.clone())
}
#[must_use]
pub fn member_roots(&self) -> Vec<(u64, Vec<u8>)> {
self.states
.iter()
.filter_map(|(&id, s)| s.root.clone().map(|r| (id, r)))
.collect()
}
#[must_use]
pub fn hasher(&self, alg_id: u64) -> Option<&dyn Hasher> {
self.hashers.get(&alg_id).map(AsRef::as_ref)
}
#[must_use]
pub fn inclusion_proof(&self, alg_id: u64, index: u64) -> Option<(Vec<u8>, Vec<ProofStep>)> {
if index >= self.len() {
return None;
}
let h = self.hashers.get(&alg_id)?.as_ref();
let state = self.states.get(&alg_id)?;
let shape = shape::build(self.len(), self.config.arity)?;
let leaf_hash = leaf_digest_raw(&self.cells, h, index);
let path = crate::proof::inclusion_path(
&shape,
index,
&state.cache,
&mut |pos| leaf_digest_raw(&self.cells, h, pos),
&mut |children| nary_mr(h, children),
);
Some((leaf_hash, path))
}
#[must_use]
pub fn leaf_proof(&self, alg_id: u64, index: u64) -> Option<spine::LeafProof> {
let (leaf_hash, path) = self.inclusion_proof(alg_id, index)?;
Some(spine::LeafProof::new(
leaf_hash,
index,
self.len(),
self.config.arity,
path,
))
}
#[must_use]
pub fn non_membership_proof(
&self,
alg_id: u64,
index: u64,
) -> Option<(Vec<u8>, Vec<ProofStep>)> {
let h = self.hashers.get(&alg_id)?.as_ref();
let (leaf_hash, path) = self.inclusion_proof(alg_id, index)?;
if leaf_hash == h.null() {
Some((leaf_hash, path))
} else {
None
}
}
pub fn add_algorithm_at(
&mut self,
alg_id: u64,
index: u64,
hasher: Box<dyn Hasher>,
) -> Result<usize> {
if self.hashers.contains_key(&alg_id) {
return Err(Error::DuplicateAlgorithm(alg_id));
}
if index >= self.len() {
return Err(Error::IndexGap {
index,
len: self.len(),
});
}
let mut state = AlgState {
root: None,
cache: BTreeMap::new(),
};
let recomputed = add_alg_seeded(
&self.cells,
self.config.arity,
hasher.as_ref(),
&mut state,
index,
);
self.hashers.insert(alg_id, hasher);
self.states.insert(alg_id, state);
Ok(recomputed)
}
pub fn seal(self) -> Result<Seal> {
if self.cells.is_empty() {
return Err(Error::EmptySeal);
}
let size = self.cells.len() as u64;
let k = self.config.arity;
let coords = spine::frontier_for_size(size, k);
let mut frontiers: Vec<(u64, Vec<Vec<u8>>)> = Vec::with_capacity(self.states.len());
let mut states = self.states;
for (id, state) in &mut states {
if state.root.is_none() {
continue;
}
let h = self
.hashers
.get(id)
.expect("states and hashers are in sync")
.as_ref();
let peaks: Vec<Vec<u8>> = coords
.iter()
.map(|&(left, height)| peak_digest(&self.cells, h, state, left, height, k))
.collect();
frontiers.push((*id, peaks));
}
Seal::new(size, k, frontiers).map_err(|_| Error::MalformedSeal)
}
#[cfg(test)]
pub(crate) fn inclusion_proof_miss_count(
&self,
alg_id: u64,
index: u64,
) -> Option<(Vec<u8>, Vec<ProofStep>, usize)> {
if index >= self.len() {
return None;
}
let h = self.hashers.get(&alg_id)?.as_ref();
let state = self.states.get(&alg_id)?;
let shape = shape::build(self.len(), self.config.arity)?;
let leaf_hash = leaf_digest_raw(&self.cells, h, index);
let (path, misses) = crate::proof::inclusion_path_with_miss_count(
&shape,
index,
&state.cache,
&mut |pos| leaf_digest_raw(&self.cells, h, pos),
&mut |children| nary_mr(h, children),
);
Some((leaf_hash, path, misses))
}
}
fn recompute_full(cells: &[Cell], arity: u64, hasher: &dyn Hasher, state: &mut AlgState) {
state.cache.clear();
state.root = shape::build(cells.len() as u64, arity).map(|shape| {
eval_subtree(&mut state.cache, hasher, &shape, &mut |pos| {
leaf_digest_raw(cells, hasher, pos)
})
});
}
fn recompute_path(
cells: &[Cell],
arity: u64,
hasher: &dyn Hasher,
state: &mut AlgState,
index: u64,
) -> usize {
let Some(shape) = shape::build(cells.len() as u64, arity) else {
state.root = None;
return 0;
};
let mut recomputed = 0usize;
state.root = Some(eval_on_path(
cells,
hasher,
state,
&shape,
index,
&mut recomputed,
));
recomputed
}
fn eval_on_path(
cells: &[Cell],
hasher: &dyn Hasher,
state: &mut AlgState,
node: &ShapeNode,
index: u64,
recomputed: &mut usize,
) -> Vec<u8> {
match node {
ShapeNode::Leaf(_) => {
let digest = leaf_digest_raw(cells, hasher, shape::leftmost(node));
state.cache.insert(node_key(node), digest.clone());
digest
},
ShapeNode::Inner(children) => {
let mut refs_owned: Vec<Vec<u8>> = Vec::with_capacity(children.len());
for child in children {
if covers(child, index) {
refs_owned.push(eval_on_path(cells, hasher, state, child, index, recomputed));
} else {
let cached = state
.cache
.get(&node_key(child))
.cloned()
.unwrap_or_else(|| {
eval_subtree(&mut state.cache, hasher, child, &mut |pos| {
leaf_digest_raw(cells, hasher, pos)
})
});
refs_owned.push(cached);
}
}
let refs: Vec<&[u8]> = refs_owned.iter().map(Vec::as_slice).collect();
let digest = nary_mr(hasher, &refs);
state.cache.insert(node_key(node), digest.clone());
*recomputed += 1;
digest
},
}
}
fn seed_null(cells: &[Cell], arity: u64, hasher: &dyn Hasher, state: &mut AlgState) {
state.cache.clear();
let null = hasher.null();
state.root = shape::build(cells.len() as u64, arity).map(|shape| {
eval_subtree(&mut state.cache, hasher, &shape, &mut |_| null.clone())
});
}
fn add_alg_seeded(
cells: &[Cell],
arity: u64,
hasher: &dyn Hasher,
state: &mut AlgState,
index: u64,
) -> usize {
seed_null(cells, arity, hasher, state);
recompute_path(cells, arity, hasher, state, index)
}
fn peak_digest(
cells: &[Cell],
hasher: &dyn Hasher,
state: &mut AlgState,
left: u64,
height: u32,
k: u64,
) -> Vec<u8> {
let right = left + k.pow(height) - 1;
if let Some(d) = state.cache.get(&(left, right)) {
return d.clone();
}
let shape = shape::perfect(left, height, k);
eval_subtree(&mut state.cache, hasher, &shape, &mut |pos| {
leaf_digest_raw(cells, hasher, pos)
})
}
fn eval_subtree(
cache: &mut BTreeMap<(u64, u64), Vec<u8>>,
hasher: &dyn Hasher,
node: &ShapeNode,
leaf_fn: &mut dyn FnMut(u64) -> Vec<u8>,
) -> Vec<u8> {
let key = node_key(node);
match node {
ShapeNode::Leaf(pos) => {
let digest = leaf_fn(*pos);
cache.insert(key, digest.clone());
digest
},
ShapeNode::Inner(children) => {
let child_digests: Vec<Vec<u8>> = children
.iter()
.map(|c| eval_subtree(cache, hasher, c, leaf_fn))
.collect();
let refs: Vec<&[u8]> = child_digests.iter().map(Vec::as_slice).collect();
let digest = nary_mr(hasher, &refs);
cache.insert(key, digest.clone());
digest
},
}
}
fn leaf_digest_raw(cells: &[Cell], hasher: &dyn Hasher, pos: u64) -> Vec<u8> {
match cells.get(pos as usize) {
Some(cell) => hasher.leaf(&cell.payload),
None => hasher.null(),
}
}
fn node_key(node: &ShapeNode) -> (u64, u64) {
(leftmost(node), rightmost(node))
}