use spine::{Hasher, constant_time_eq, frontier_for_size, nary_mr};
use crate::Sealed;
use crate::root::combined_root;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FillKind {
Eml,
Emt,
}
impl FillKind {
#[must_use]
pub fn bag(self) -> spine::BagFn {
match self {
Self::Eml => cml::mountain::bag_peaks,
Self::Emt => cmt::rebalanced_bag,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FillError {
LeafCountMismatch {
committed: u64,
supplied: u64,
},
ExtentsDoNotCoverSize {
tree_size: u64,
covered: u64,
},
UnknownAlgorithm(u64),
MissingHasher {
alg_id: u64,
},
BindingRootMismatch {
alg_id: u64,
},
}
impl std::fmt::Display for FillError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LeafCountMismatch {
committed,
supplied,
} => write!(
f,
"leaf data does not match the sealed commitment: committed {committed} leaves, \
caller supplied {supplied}"
),
Self::ExtentsDoNotCoverSize { tree_size, covered } => write!(
f,
"committed run-extents cover {covered} leaves but the commitment sealed \
{tree_size}"
),
Self::UnknownAlgorithm(id) => {
write!(
f,
"algorithm {id} has no committed frontier in the sealed commitment"
)
},
Self::MissingHasher { alg_id } => {
write!(
f,
"no hasher supplied for algorithm {alg_id}, which is active in the sealed \
commitment; the binding-root check needs every active algorithm's hasher"
)
},
Self::BindingRootMismatch { alg_id } => write!(
f,
"rebuilt binding root for algorithm {alg_id} does not match the committed binding \
root: the data could not reproduce the committed layout"
),
}
}
}
impl std::error::Error for FillError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilledTree {
alg_id: u64,
tree_size: u64,
kind: FillKind,
root: Vec<u8>,
}
impl FilledTree {
#[must_use]
pub fn alg_id(&self) -> u64 {
self.alg_id
}
#[must_use]
pub fn tree_size(&self) -> u64 {
self.tree_size
}
#[must_use]
pub fn kind(&self) -> FillKind {
self.kind
}
#[must_use]
pub fn root(&self) -> &[u8] {
&self.root
}
}
pub fn fill<D: AsRef<[u8]>>(
sealed: &Sealed,
alg_id: u64,
hasher: &dyn Hasher,
leaf_data: &[D],
kind: FillKind,
all_hashers: &[(u64, &dyn Hasher)],
) -> Result<FilledTree, FillError> {
let tree_size = sealed.tree_size();
let k = sealed.arity();
if sealed.peaks(alg_id).is_none() {
return Err(FillError::UnknownAlgorithm(alg_id));
}
let supplied = leaf_data.len() as u64;
if supplied != tree_size {
return Err(FillError::LeafCountMismatch {
committed: tree_size,
supplied,
});
}
let partition = committed_partition(sealed, tree_size, k)?;
let mut component_roots = Vec::with_capacity(partition.len());
for &(left, height) in &partition {
let span = k.pow(height) as usize;
let lo = left as usize;
let component = subtree_root(hasher, &leaf_data[lo..lo + span], k);
component_roots.push(component);
}
let member_root = kind.bag()(hasher, &component_roots, k);
let committed = sealed
.binding_root(alg_id, hasher, all_hashers, kind.bag())
.map_err(fill_error_from_missing_hasher)?
.ok_or(FillError::UnknownAlgorithm(alg_id))?;
let rebuilt_binding =
rebuilt_binding_root(sealed, alg_id, hasher, &member_root, all_hashers, kind)?;
if !constant_time_eq(&rebuilt_binding, &committed) {
return Err(FillError::BindingRootMismatch { alg_id });
}
Ok(FilledTree {
alg_id,
tree_size,
kind,
root: member_root,
})
}
fn rebuilt_binding_root(
sealed: &Sealed,
alg_id: u64,
hasher: &dyn Hasher,
member_root: &[u8],
all_hashers: &[(u64, &dyn Hasher)],
kind: FillKind,
) -> Result<Vec<u8>, FillError> {
let mut members = sealed
.all_member_roots(all_hashers, kind.bag())
.map_err(fill_error_from_missing_hasher)?;
for (id, mr) in &mut members {
if *id == alg_id {
*mr = member_root.to_vec();
}
}
Ok(combined_root(
hasher,
&members,
sealed.alg_epochs(),
sealed.tree_size(),
sealed.arity(),
))
}
fn fill_error_from_missing_hasher(e: crate::Error) -> FillError {
match e {
crate::Error::Spine(spine::Error::MissingHasher { alg_id }) => {
FillError::MissingHasher { alg_id }
},
crate::Error::Spine(spine::Error::BadArity | spine::Error::MalformedFrontier)
| crate::Error::MalformedEpochs => {
unreachable!("binding-root fold over a validated Sealed yields only MissingHasher")
},
}
}
fn committed_partition(
sealed: &Sealed,
tree_size: u64,
k: u64,
) -> Result<Vec<(u64, u32)>, FillError> {
let canonical = frontier_for_size(tree_size, k);
let mut partition: Vec<(u64, u32)> = sealed
.run_extents()
.iter()
.map(|e| (e.left(), e.height()))
.chain(canonical.iter().copied().filter(|&(_, height)| height == 0))
.collect();
partition.sort_unstable_by_key(|&(left, _)| left);
let covered: u64 = partition.iter().map(|&(_, height)| k.pow(height)).sum();
if covered != tree_size {
return Err(FillError::ExtentsDoNotCoverSize { tree_size, covered });
}
Ok(partition)
}
fn subtree_root<D: AsRef<[u8]>>(hasher: &dyn Hasher, leaves: &[D], k: u64) -> Vec<u8> {
let mut level: Vec<Vec<u8>> = leaves.iter().map(|l| hasher.leaf(l.as_ref())).collect();
let k_usize = k as usize;
while level.len() > 1 {
let mut next = Vec::with_capacity(level.len() / k_usize);
for chunk in level.chunks(k_usize) {
let refs: Vec<&[u8]> = chunk.iter().map(|v| v.as_slice()).collect();
next.push(nary_mr(hasher, &refs));
}
level = next;
}
level.into_iter().next().unwrap_or_else(|| hasher.empty())
}
#[cfg(test)]
mod tests {
use sha2::{Digest, Sha256};
use spine::Hasher;
use super::*;
use crate::storage::MemoryStorage;
use crate::tree::{NaryMerkleLog, TreeConfig};
#[derive(Debug, Clone)]
struct Sha256Hasher;
impl Hasher for Sha256Hasher {
fn leaf(&self, data: &[u8]) -> Vec<u8> {
Sha256::digest(data).to_vec()
}
fn node(&self, children: &[&[u8]]) -> Vec<u8> {
let mut h = Sha256::new();
for child in children {
h.update(child);
}
h.finalize().to_vec()
}
fn empty(&self) -> Vec<u8> {
Sha256::digest(b"").to_vec()
}
fn hash(&self, data: &[u8]) -> Vec<u8> {
Sha256::digest(data).to_vec()
}
fn clone_box(&self) -> Box<dyn Hasher> {
Box::new(self.clone())
}
}
fn leaves(n: u64) -> Vec<Vec<u8>> {
(0..n).map(|i| format!("leaf-{i}").into_bytes()).collect()
}
async fn gapless_sealed(data: &[Vec<u8>], k: usize) -> Sealed {
let config = TreeConfig { arity: k as u64 };
let mut log = NaryMerkleLog::new(MemoryStorage::new(), Box::new(Sha256Hasher), config)
.await
.unwrap();
for leaf in data {
log.append_leaf(leaf).await.unwrap();
}
log.seal().await.unwrap()
}
async fn from_scratch_root(data: &[Vec<u8>], k: usize) -> Vec<u8> {
let config = TreeConfig { arity: k as u64 };
let mut log = NaryMerkleLog::new(MemoryStorage::new(), Box::new(Sha256Hasher), config)
.await
.unwrap();
for leaf in data {
log.append_leaf(leaf).await.unwrap();
}
log.root_for_at(0, log.count()).await.unwrap()
}
fn emt_sealed(data: &[Vec<u8>], k: usize) -> Sealed {
let mut t = crate::EpochTree::new(crate::CmtConfig { arity: k as u64 }).unwrap();
t.register_algorithm(0, Box::new(Sha256Hasher)).unwrap();
for (i, leaf) in data.iter().enumerate() {
t.set(i as u64, leaf.clone(), Vec::new()).unwrap();
}
t.seal().unwrap()
}
fn emt_from_scratch_root(data: &[Vec<u8>], k: usize) -> Vec<u8> {
let mut t = crate::EpochTree::new(crate::CmtConfig { arity: k as u64 }).unwrap();
t.register_algorithm(0, Box::new(Sha256Hasher)).unwrap();
for (i, leaf) in data.iter().enumerate() {
t.set(i as u64, leaf.clone(), Vec::new()).unwrap();
}
t.root(0).unwrap()
}
#[test]
fn filled_root_equals_from_scratch_and_verifies_per_kind() {
smol::block_on(async {
let h = Sha256Hasher;
let hashers: [(u64, &dyn Hasher); 1] = [(0, &h)];
for k in [2usize, 3, 4] {
for n in 1u64..40 {
let data = leaves(n);
let eml_sealed = gapless_sealed(&data, k).await;
let eml_oracle = from_scratch_root(&data, k).await;
let eml_filled = fill(&eml_sealed, 0, &h, &data, FillKind::Eml, &hashers)
.expect("gapless data verifies against the EML binding root");
assert_eq!(eml_filled.root(), eml_oracle.as_slice(), "EML n={n} k={k}");
assert_eq!(eml_filled.tree_size(), n);
assert_eq!(eml_filled.kind(), FillKind::Eml);
let emt_sealed = emt_sealed(&data, k);
let emt_oracle = emt_from_scratch_root(&data, k);
let emt_filled = fill(&emt_sealed, 0, &h, &data, FillKind::Emt, &hashers)
.expect("gapless data verifies against the EMT binding root");
assert_eq!(emt_filled.root(), emt_oracle.as_slice(), "EMT n={n} k={k}");
assert_eq!(emt_filled.tree_size(), n);
assert_eq!(emt_filled.kind(), FillKind::Emt);
}
}
});
}
#[test]
fn forged_leaf_data_is_rejected() {
smol::block_on(async {
let h = Sha256Hasher;
let hashers: [(u64, &dyn Hasher); 1] = [(0, &h)];
let data = leaves(7);
let sealed = gapless_sealed(&data, 2).await;
let mut forged = data.clone();
forged[3] = b"forged".to_vec();
assert_eq!(
fill(&sealed, 0, &h, &forged, FillKind::Eml, &hashers),
Err(FillError::BindingRootMismatch { alg_id: 0 })
);
});
}
#[test]
fn fill_requires_leaf_data_matching_the_committed_size() {
smol::block_on(async {
let h = Sha256Hasher;
let hashers: [(u64, &dyn Hasher); 1] = [(0, &h)];
let data = leaves(6);
let sealed = gapless_sealed(&data, 2).await;
assert_eq!(
fill(&sealed, 0, &h, &data[..4], FillKind::Eml, &hashers),
Err(FillError::LeafCountMismatch {
committed: 6,
supplied: 4
})
);
let mut long = data.clone();
long.push(b"extra".to_vec());
assert_eq!(
fill(&sealed, 0, &h, &long, FillKind::Eml, &hashers),
Err(FillError::LeafCountMismatch {
committed: 6,
supplied: 7
})
);
});
}
#[test]
fn unknown_algorithm_is_rejected() {
smol::block_on(async {
let h = Sha256Hasher;
let hashers: [(u64, &dyn Hasher); 1] = [(0, &h)];
let data = leaves(4);
let sealed = gapless_sealed(&data, 2).await;
assert_eq!(
fill(&sealed, 9, &h, &data, FillKind::Eml, &hashers),
Err(FillError::UnknownAlgorithm(9))
);
});
}
#[test]
fn empty_commitment_fills_to_the_empty_root() {
smol::block_on(async {
let h = Sha256Hasher;
let hashers: [(u64, &dyn Hasher); 1] = [(0, &h)];
let data: Vec<Vec<u8>> = Vec::new();
let sealed = gapless_sealed(&data, 2).await;
assert_eq!(
fill(&sealed, 0, &h, &data, FillKind::Eml, &hashers),
Err(FillError::UnknownAlgorithm(0))
);
});
}
#[test]
fn multi_algorithm_gapless_fill_verifies() {
smol::block_on(async {
let config = TreeConfig { arity: 2 };
let mut log = NaryMerkleLog::new(MemoryStorage::new(), Box::new(Sha256Hasher), config)
.await
.unwrap();
log.add_algorithm(1, Box::new(Sha256Hasher)).await.unwrap();
let data = leaves(8);
for leaf in &data {
log.append_leaf(leaf).await.unwrap();
}
let sealed = log.seal().await.unwrap();
let h = Sha256Hasher;
let hashers: [(u64, &dyn Hasher); 2] = [(0, &h), (1, &h)];
let f0 = fill(&sealed, 0, &h, &data, FillKind::Eml, &hashers).unwrap();
let f1 = fill(&sealed, 1, &h, &data, FillKind::Emt, &hashers).unwrap();
assert_eq!(f0.root(), f1.root());
});
}
}