use spine::{Hasher, Seal};
use crate::error::{Error, Result};
use crate::root::{combined_root, validate_committed_epochs};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoundSnapshot {
seal: Seal,
alg_epochs: Vec<(u64, Vec<(u64, u64)>)>,
}
impl BoundSnapshot {
pub fn new(seal: Seal, alg_epochs: Vec<(u64, Vec<(u64, u64)>)>) -> Result<Self> {
if !validate_committed_epochs(&alg_epochs, seal.tree_size()) {
return Err(Error::MalformedEpochs);
}
Ok(Self { seal, alg_epochs })
}
#[must_use]
pub fn seal(&self) -> &Seal {
&self.seal
}
#[must_use]
pub fn alg_epochs(&self) -> &[(u64, Vec<(u64, u64)>)] {
&self.alg_epochs
}
pub fn binding_roots(
&self,
hashers: &[(u64, &dyn Hasher)],
bag: spine::BagFn,
) -> Result<Vec<(u64, Vec<u8>)>> {
let members = self.seal.all_member_roots(hashers, bag)?;
members
.iter()
.map(|(id, _)| {
let hasher = hashers
.iter()
.find(|(hid, _)| hid == id)
.map(|(_, h)| *h)
.ok_or(spine::Error::MissingHasher { alg_id: *id })?;
Ok((
*id,
combined_root(
hasher,
&members,
&self.alg_epochs,
self.seal.tree_size(),
self.seal.arity(),
),
))
})
.collect()
}
pub fn binding_root(
&self,
alg_id: u64,
hasher: &dyn Hasher,
all_hashers: &[(u64, &dyn Hasher)],
bag: spine::BagFn,
) -> Result<Option<Vec<u8>>> {
if self.seal.peaks(alg_id).is_none() {
return Ok(None);
}
let members = self.seal.all_member_roots(all_hashers, bag)?;
Ok(Some(combined_root(
hasher,
&members,
&self.alg_epochs,
self.seal.tree_size(),
self.seal.arity(),
)))
}
}
#[cfg(test)]
mod tests {
use sha2::{Digest, Sha256};
use super::*;
#[derive(Debug, Clone)]
struct H;
impl Hasher for H {
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 c in children {
h.update(c);
}
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())
}
}
#[test]
fn new_rejects_malformed_timeline() {
let seal = Seal::new(3, 2, vec![(0, vec![vec![0xAA; 32], vec![0xBB; 32]])])
.expect("well-formed seal");
assert_eq!(
BoundSnapshot::new(seal, vec![(0, vec![(5, u64::MAX)])]),
Err(Error::MalformedEpochs)
);
}
fn bag(hasher: &dyn Hasher, peaks: &[Vec<u8>], k: u64) -> Vec<u8> {
cml::mountain::bag_peaks(hasher, peaks, k)
}
#[test]
fn promoted_registry_binding_root_equals_member_root() {
let p0 = vec![0x11; 32];
let p1 = vec![0x22; 32];
let seal = Seal::new(3, 2, vec![(0, vec![p0.clone(), p1.clone()])]).expect("well-formed");
let member = seal.member_root(0, &H, bag).unwrap();
let snap =
BoundSnapshot::new(seal, vec![(0, vec![(0, u64::MAX)])]).expect("well-formed timeline");
let hashers: [(u64, &dyn Hasher); 1] = [(0, &H)];
assert_eq!(
snap.binding_root(0, &H, &hashers, bag),
Ok(Some(member.clone()))
);
assert_eq!(snap.binding_roots(&hashers, bag), Ok(vec![(0, member)]));
}
#[test]
fn binding_root_errors_on_a_missing_active_hasher() {
let p0 = vec![0x11; 32];
let p1 = vec![0x22; 32];
let seal = Seal::new(
3,
2,
vec![(0, vec![p0.clone(), p0.clone()]), (1, vec![p1.clone(), p1])],
)
.expect("well-formed");
let snap = BoundSnapshot::new(
seal,
vec![(0, vec![(0, u64::MAX)]), (1, vec![(0, u64::MAX)])],
)
.expect("well-formed timeline");
let partial: [(u64, &dyn Hasher); 1] = [(0, &H)];
assert_eq!(
snap.binding_root(0, &H, &partial, bag),
Err(Error::Spine(spine::Error::MissingHasher { alg_id: 1 }))
);
assert_eq!(
snap.binding_roots(&partial, bag),
Err(Error::Spine(spine::Error::MissingHasher { alg_id: 1 }))
);
let full: [(u64, &dyn Hasher); 2] = [(0, &H), (1, &H)];
assert!(snap.binding_root(0, &H, &full, bag).unwrap().is_some());
assert_eq!(snap.binding_roots(&full, bag).unwrap().len(), 2);
assert_eq!(snap.binding_root(9, &H, &full, bag), Ok(None));
}
}