use time::Date;
use super::{BalanceProof, Hash, leaf_hash, node_hash};
use crate::model::{
AccountAction, AccountHandle, AccountKind, AccountPath, Balance, Currency, DecodeError, Layer,
PeriodId, Record, RecordKind,
};
pub const MAX_PROOF_HASHES: usize = 63;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum ProofError {
#[error("{size} is beyond the largest supported tree")]
TooLarge {
size: u64,
},
#[error("leaf {index} is not in a tree of {size}")]
IndexOutOfRange {
index: u64,
size: u64,
},
#[error("nothing can be included in the empty tree")]
EmptyTree,
#[error("a consistency proof from the empty tree proves nothing")]
EmptyOldTree,
#[error("old size {old} exceeds new size {new}")]
OldLargerThanNew {
old: u64,
new: u64,
},
#[error("size {size} is beyond the tree's {tree_size} leaves")]
BeyondTree {
size: u64,
tree_size: u64,
},
#[error("the proof is for size {proof}, the head has size {head}")]
SizeMismatch {
proof: u64,
head: u64,
},
#[error("the path has {found} hashes, {expected} expected")]
PathLength {
expected: usize,
found: usize,
},
#[error("the proof computes root {computed}, expected {expected}")]
RootMismatch {
expected: Hash,
computed: Hash,
},
#[error("expected {expected} stored nodes, got {found}")]
MalformedNodes {
expected: usize,
found: usize,
},
#[error("stored node {index} disagrees with the leaves below it")]
NodeMismatch {
index: u64,
},
#[error("the proof is for leaf {index}, not handle {handle}")]
HandleMismatch {
index: u64,
handle: u32,
},
#[error("the balance tree has {tree_size} leaves, the seal {accounts} accounts")]
AccountCountMismatch {
tree_size: u64,
accounts: u32,
},
#[error("the seal has no {currency} {layer:?} tree")]
NoSuchTree {
currency: Currency,
layer: Layer,
},
#[error("handle {handle} was given two balances in one (currency, layer)")]
DuplicateBalance {
handle: u32,
},
#[error("the balance proof does not match the claim")]
BalanceProofMismatch,
#[error("expected a {expected:?} record, found {found:?}")]
UnexpectedRecord {
expected: RecordKind,
found: RecordKind,
},
#[error("the account record is a change, not a registration")]
NotARegistration,
#[error("the account is registered at {position}, after the seal's last record {through}")]
AccountAfterSeal {
position: u64,
through: u64,
},
#[error("the proven leaf does not decode: {0}")]
Decode(DecodeError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TreeHead {
pub size: u64,
pub root: Hash,
}
impl core::fmt::Display for TreeHead {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
crate::model::pad(f, &format!("size {} root {}", self.size, self.root))
}
}
fn bit(x: u64, i: usize) -> bool {
u32::try_from(i)
.ok()
.and_then(|i| x.checked_shr(i))
.is_some_and(|shifted| shifted & 1 == 1)
}
fn bit_len(x: u64) -> usize {
x.checked_ilog2()
.map_or(0, |log| (log as usize).saturating_add(1))
}
fn decompose(index: u64, size: u64) -> (usize, usize) {
let inner = bit_len(index ^ size.saturating_sub(1));
let above = u32::try_from(inner)
.ok()
.and_then(|inner| index.checked_shr(inner))
.unwrap_or(0);
(inner, above.count_ones() as usize)
}
fn chain_inner(seed: Hash, path: &[Hash], index: u64) -> Hash {
path.iter().enumerate().fold(seed, |acc, (i, sibling)| {
if bit(index, i) {
node_hash(sibling, &acc)
} else {
node_hash(&acc, sibling)
}
})
}
fn chain_inner_right(seed: Hash, path: &[Hash], index: u64) -> Hash {
path.iter().enumerate().fold(seed, |acc, (i, sibling)| {
if bit(index, i) {
node_hash(sibling, &acc)
} else {
acc
}
})
}
fn chain_border_right(seed: Hash, path: &[Hash]) -> Hash {
path.iter()
.fold(seed, |acc, sibling| node_hash(sibling, &acc))
}
fn expect_root(expected: Hash, computed: Hash) -> Result<(), ProofError> {
if expected == computed {
Ok(())
} else {
Err(ProofError::RootMismatch { expected, computed })
}
}
fn expect_length(expected: usize, found: usize) -> Result<(), ProofError> {
if expected == found && found <= MAX_PROOF_HASHES {
Ok(())
} else {
Err(ProofError::PathLength { expected, found })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InclusionProof {
pub index: u64,
pub size: u64,
pub path: Vec<Hash>,
}
impl InclusionProof {
pub fn root_from(&self, leaf_hash: &Hash) -> Result<Hash, ProofError> {
if self.size == 0 {
return Err(ProofError::EmptyTree);
}
if self.size > super::nodes::MAX_TREE_SIZE {
return Err(ProofError::TooLarge { size: self.size });
}
if self.index >= self.size {
return Err(ProofError::IndexOutOfRange {
index: self.index,
size: self.size,
});
}
let (inner, border) = decompose(self.index, self.size);
expect_length(inner.saturating_add(border), self.path.len())?;
let (inner_path, border_path) = self.path.split_at(inner.min(self.path.len()));
let root = chain_inner(*leaf_hash, inner_path, self.index);
Ok(chain_border_right(root, border_path))
}
pub fn verify(&self, leaf_hash: &Hash, head: &TreeHead) -> Result<(), ProofError> {
if self.size != head.size {
return Err(ProofError::SizeMismatch {
proof: self.size,
head: head.size,
});
}
expect_root(head.root, self.root_from(leaf_hash)?)
}
pub fn verify_record<const P: u8>(
&self,
head: &TreeHead,
leaf_bytes: &[u8],
) -> Result<Record<P>, ProofError> {
self.verify(&leaf_hash(leaf_bytes), head)?;
Record::decode(leaf_bytes).map_err(ProofError::Decode)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ConsistencyProof {
pub old_size: u64,
pub new_size: u64,
pub path: Vec<Hash>,
}
impl ConsistencyProof {
pub fn verify(&self, old: &TreeHead, new: &TreeHead) -> Result<(), ProofError> {
for (proof, head) in [(self.old_size, old.size), (self.new_size, new.size)] {
if proof != head {
return Err(ProofError::SizeMismatch { proof, head });
}
}
let (m, n) = (self.old_size, self.new_size);
if m == 0 {
return Err(ProofError::EmptyOldTree);
}
if m > n {
return Err(ProofError::OldLargerThanNew { old: m, new: n });
}
if n > super::nodes::MAX_TREE_SIZE {
return Err(ProofError::TooLarge { size: n });
}
if m == n {
expect_length(0, self.path.len())?;
return expect_root(new.root, old.root);
}
let (inner, border) = decompose(m.saturating_sub(1), n);
let shift = m.trailing_zeros();
let inner = inner.saturating_sub(shift as usize);
let complete = m.count_ones() == 1;
let (seed, path) = if complete {
(old.root, self.path.as_slice())
} else {
match self.path.split_first() {
Some((seed, rest)) => (*seed, rest),
None => return expect_length(inner.saturating_add(border).saturating_add(1), 0),
}
};
let seed_len = usize::from(!complete);
expect_length(
inner.saturating_add(border).saturating_add(seed_len),
self.path.len(),
)?;
let (inner_path, border_path) = path.split_at(inner.min(path.len()));
let mask = m.saturating_sub(1).checked_shr(shift).unwrap_or(0);
let old_root = chain_border_right(chain_inner_right(seed, inner_path, mask), border_path);
expect_root(old.root, old_root)?;
let new_root = chain_border_right(chain_inner(seed, inner_path, mask), border_path);
expect_root(new.root, new_root)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SealedBalanceProof<const P: u8 = 2> {
#[cfg_attr(feature = "serde", serde(with = "crate::model::serde_hex"))]
pub seal_leaf: Vec<u8>,
pub seal_proof: InclusionProof,
#[cfg_attr(feature = "serde", serde(with = "crate::model::serde_hex"))]
pub account_leaf: Vec<u8>,
pub account_proof: InclusionProof,
pub currency: Currency,
pub layer: Layer,
pub handle: AccountHandle,
pub balance: Option<BalanceProof<P>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ProvenBalance<const P: u8 = 2> {
pub head: TreeHead,
pub account: AccountPath,
pub handle: AccountHandle,
pub kind: AccountKind,
pub period: PeriodId,
pub start: Date,
pub end: Date,
pub seal_position: u64,
pub currency: Currency,
pub layer: Layer,
pub balance: Balance<P>,
}
fn unexpected(expected: RecordKind, found: RecordKind) -> ProofError {
ProofError::UnexpectedRecord { expected, found }
}
impl<const P: u8> SealedBalanceProof<P> {
pub fn verify(&self, head: &TreeHead) -> Result<ProvenBalance<P>, ProofError> {
let seal = match self.seal_proof.verify_record::<P>(head, &self.seal_leaf)? {
Record::Seal(seal) => seal,
other => return Err(unexpected(RecordKind::Seal, other.kind())),
};
let account = match self
.account_proof
.verify_record::<P>(head, &self.account_leaf)?
{
Record::Account(event) => match event.action {
AccountAction::Register(account) => account,
AccountAction::Change { .. } => return Err(ProofError::NotARegistration),
},
other => return Err(unexpected(RecordKind::Account, other.kind())),
};
let handle = self.handle.get();
if account.handle != self.handle {
return Err(ProofError::HandleMismatch {
index: u64::from(account.handle.get()),
handle,
});
}
let parts = seal.parts();
if handle >= parts.accounts {
return Err(ProofError::IndexOutOfRange {
index: u64::from(handle),
size: u64::from(parts.accounts),
});
}
if self.account_proof.index > parts.through {
return Err(ProofError::AccountAfterSeal {
position: self.account_proof.index,
through: parts.through,
});
}
let balance = match (&self.balance, seal.tree(self.currency, self.layer)) {
(None, None) => Balance::ZERO,
(Some(proof), Some(_))
if (proof.currency, proof.layer, proof.handle)
== (self.currency, self.layer, self.handle) =>
{
proof.verify(&seal)?;
proof.balance
}
_ => return Err(ProofError::BalanceProofMismatch),
};
Ok(ProvenBalance {
head: *head,
account: account.path,
handle: self.handle,
kind: account.kind,
period: parts.period.clone(),
start: parts.start,
end: parts.end,
seal_position: self.seal_proof.index,
currency: self.currency,
layer: self.layer,
balance,
})
}
}