use crate::trie::error::{Result, TrieError};
use super::{
EMPTY_HASH, SmtHandle, SmtNode, TREE_DEPTH,
bitpath::BitPath,
codec::{hash_internal, hash_leaf, wrap_hash},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SmtProof {
pub key_hash: [u8; 32],
pub leaf: Option<([u8; 32], Vec<u8>)>,
pub siblings: Vec<[u8; 32]>,
}
impl SmtProof {
pub fn value(&self) -> Option<&[u8]> {
match &self.leaf {
Some((kh, v)) if *kh == self.key_hash => Some(v),
_ => None,
}
}
}
pub fn prove(root: &SmtHandle, key_hash: &[u8; 32]) -> Result<SmtProof> {
let key_path = BitPath::from_hash(key_hash);
let mut siblings: Vec<[u8; 32]> = Vec::new();
let mut handle = root;
let mut depth = 0usize;
loop {
match handle.node() {
SmtNode::Empty => {
return Ok(SmtProof {
key_hash: *key_hash,
leaf: None,
siblings,
});
}
SmtNode::Leaf {
key_hash: leaf_kh,
value,
path: _,
} => {
return Ok(SmtProof {
key_hash: *key_hash,
leaf: Some((*leaf_kh, value.clone())),
siblings,
});
}
SmtNode::Internal { path, left, right } => {
if !key_path.starts_with_from(depth, path) {
let common = key_path.common_prefix_from(depth, path);
siblings.resize(depth + common, EMPTY_HASH);
let left_h = to_hash32(left.expect_hash()?)?;
let right_h = to_hash32(right.expect_hash()?)?;
let internal_h = hash_internal(&left_h, &right_h);
let suffix = path.slice(common + 1, path.len());
siblings.push(wrap_hash(internal_h, &suffix));
return Ok(SmtProof {
key_hash: *key_hash,
leaf: None,
siblings,
});
}
let split_depth = depth + path.len();
if split_depth >= TREE_DEPTH {
return Err(TrieError::InvalidState(
"SMT depth exceeded 256 bits".into(),
));
}
siblings.resize(split_depth, EMPTY_HASH);
let bit = key_path.bit_at(split_depth);
let (next, other) = if bit == 0 {
(left, right)
} else {
(right, left)
};
siblings.push(to_hash32(other.expect_hash()?)?);
handle = next;
depth = split_depth + 1;
}
}
}
}
pub fn verify_proof(
root_hash: &[u8; 32],
expected_key_hash: &[u8; 32],
proof: &SmtProof,
) -> Result<bool> {
if &proof.key_hash != expected_key_hash {
return Ok(false);
}
if proof.siblings.len() > TREE_DEPTH {
return Err(TrieError::InvalidState(format!(
"proof must have at most {} siblings, got {}",
TREE_DEPTH,
proof.siblings.len()
)));
}
let key_path = BitPath::from_hash(&proof.key_hash);
let mut current = match &proof.leaf {
None => EMPTY_HASH,
Some((leaf_kh, leaf_val)) => {
if leaf_kh != &proof.key_hash {
let leaf_path = BitPath::from_hash(leaf_kh);
if key_path.common_prefix(&leaf_path) < proof.siblings.len() {
return Ok(false);
}
}
hash_leaf(leaf_kh, leaf_val)
}
};
for depth in (0..proof.siblings.len()).rev() {
let bit = key_path.bit_at(depth);
if bit == 0 {
current = hash_internal(¤t, &proof.siblings[depth]);
} else {
current = hash_internal(&proof.siblings[depth], ¤t);
}
}
Ok(current == *root_hash)
}
fn to_hash32(slice: &[u8]) -> Result<[u8; 32]> {
slice
.try_into()
.map_err(|_| TrieError::InvalidState("expected 32-byte hash".into()))
}