use serde::{Deserialize, Serialize};
use crate::crypto::{hash_pair, Hash};
use crate::error::{Error, Result};
use crate::event::EventId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Position {
Left,
Right,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofNode {
pub hash: Hash,
pub position: Position,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockInclusionProof {
pub event_id: EventId,
pub block_height: u64,
pub path: Vec<ProofNode>,
pub events_root: Hash,
}
impl BlockInclusionProof {
pub fn verify(&self) -> Result<bool> {
let mut current = self.event_id.0;
for node in &self.path {
current = match node.position {
Position::Left => hash_pair(node.hash, current),
Position::Right => hash_pair(current, node.hash),
};
}
Ok(current == self.events_root)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MmrProof {
pub leaf_pos: u64,
pub leaf_hash: Hash,
pub siblings: Vec<ProofNode>,
pub peak_hash: Hash,
pub peaks: Vec<Hash>,
pub root: Hash,
pub mmr_size: u64,
}
impl MmrProof {
pub fn verify(&self) -> Result<bool> {
let mut current = self.leaf_hash;
for node in &self.siblings {
current = match node.position {
Position::Left => hash_pair(node.hash, current),
Position::Right => hash_pair(current, node.hash),
};
}
if current != self.peak_hash {
return Ok(false);
}
let computed_root = bag_peaks(&self.peaks)?;
Ok(computed_root == self.root)
}
}
fn bag_peaks(peaks: &[Hash]) -> Result<Hash> {
if peaks.is_empty() {
return Ok(Hash::ZERO);
}
let mut root = *peaks.last().unwrap();
for peak in peaks.iter().rev().skip(1) {
root = hash_pair(*peak, root);
}
Ok(root)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InclusionProof {
pub block_proof: BlockInclusionProof,
pub chain_proof: MmrProof,
}
impl InclusionProof {
pub fn verify(&self) -> Result<bool> {
if !self.block_proof.verify()? {
return Ok(false);
}
if self.block_proof.events_root != self.chain_proof.leaf_hash {
return Ok(false);
}
self.chain_proof.verify()
}
pub fn event_id(&self) -> EventId {
self.block_proof.event_id
}
pub fn block_height(&self) -> u64 {
self.block_proof.block_height
}
pub fn mmr_root(&self) -> Hash {
self.chain_proof.root
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConsistencyProof {
pub old_size: u64,
pub new_size: u64,
pub old_root: Hash,
pub new_root: Hash,
pub proof: Vec<Hash>,
}
impl ConsistencyProof {
pub fn verify(&self) -> Result<bool> {
if self.old_size > self.new_size {
return Err(Error::invalid_proof("old_size > new_size"));
}
if self.old_size == self.new_size {
return Ok(self.old_root == self.new_root);
}
if self.proof.is_empty() {
return Ok(false);
}
Ok(true) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::hash;
#[test]
fn test_block_inclusion_proof_single_event() {
let event_hash = hash(b"event1");
let event_id = EventId(event_hash);
let proof = BlockInclusionProof {
event_id,
block_height: 0,
path: vec![],
events_root: event_hash,
};
assert!(proof.verify().unwrap());
}
#[test]
fn test_block_inclusion_proof_two_events() {
let e1 = hash(b"event1");
let e2 = hash(b"event2");
let root = hash_pair(e1, e2);
let proof = BlockInclusionProof {
event_id: EventId(e1),
block_height: 0,
path: vec![ProofNode {
hash: e2,
position: Position::Right,
}],
events_root: root,
};
assert!(proof.verify().unwrap());
let proof2 = BlockInclusionProof {
event_id: EventId(e2),
block_height: 0,
path: vec![ProofNode {
hash: e1,
position: Position::Left,
}],
events_root: root,
};
assert!(proof2.verify().unwrap());
}
#[test]
fn test_block_inclusion_proof_four_events() {
let e1 = hash(b"event1");
let e2 = hash(b"event2");
let e3 = hash(b"event3");
let e4 = hash(b"event4");
let h12 = hash_pair(e1, e2);
let h34 = hash_pair(e3, e4);
let root = hash_pair(h12, h34);
let proof = BlockInclusionProof {
event_id: EventId(e3),
block_height: 5,
path: vec![
ProofNode {
hash: e4,
position: Position::Right,
},
ProofNode {
hash: h12,
position: Position::Left,
},
],
events_root: root,
};
assert!(proof.verify().unwrap());
}
#[test]
fn test_invalid_proof_fails() {
let e1 = hash(b"event1");
let e2 = hash(b"event2");
let root = hash_pair(e1, e2);
let proof = BlockInclusionProof {
event_id: EventId(e1),
block_height: 0,
path: vec![ProofNode {
hash: hash(b"wrong"),
position: Position::Right,
}],
events_root: root,
};
assert!(!proof.verify().unwrap());
}
#[test]
fn test_bag_peaks() {
let p1 = hash(b"peak1");
let p2 = hash(b"peak2");
let p3 = hash(b"peak3");
assert_eq!(bag_peaks(&[p1]).unwrap(), p1);
let expected_2 = hash_pair(p1, p2);
assert_eq!(bag_peaks(&[p1, p2]).unwrap(), expected_2);
let h23 = hash_pair(p2, p3);
let expected_3 = hash_pair(p1, h23);
assert_eq!(bag_peaks(&[p1, p2, p3]).unwrap(), expected_3);
}
#[test]
fn test_mmr_proof_single_leaf() {
let leaf = hash(b"leaf");
let proof = MmrProof {
leaf_pos: 0,
leaf_hash: leaf,
siblings: vec![],
peak_hash: leaf,
peaks: vec![leaf],
root: leaf,
mmr_size: 1,
};
assert!(proof.verify().unwrap());
}
}