use dig_capsule::format::Bytes32 as CapsuleBytes32;
use dig_capsule::merkle::{MerkleProof, ProofStep};
use dig_chainsource_interface::ChainSource;
use crate::error::{EvidenceError, EvidenceResult};
use crate::evidence::Evidence;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RangeInclusionClaim {
pub leaf: CapsuleBytes32,
pub path: Vec<ProofStep>,
pub generation_root: CapsuleBytes32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RangeInclusionEvidence {
proof: MerkleProof,
}
impl RangeInclusionEvidence {
pub fn generation_root(&self) -> CapsuleBytes32 {
self.proof.root
}
pub fn leaf(&self) -> CapsuleBytes32 {
self.proof.leaf
}
pub fn path(&self) -> &[ProofStep] {
&self.proof.path
}
}
impl Evidence for RangeInclusionEvidence {
type Claim = RangeInclusionClaim;
fn gather<S: ChainSource>(claim: &Self::Claim, _chain: &S) -> EvidenceResult<Self> {
let proof = MerkleProof {
leaf: claim.leaf,
path: claim.path.clone(),
root: claim.generation_root,
};
if !proof.verify() {
return Err(EvidenceError::ProofDoesNotFold);
}
Ok(Self { proof })
}
fn verify(&self) -> EvidenceResult<()> {
if self.proof.verify() {
Ok(())
} else {
Err(EvidenceError::ProofDoesNotFold)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dig_capsule::merkle::MerkleTree;
use dig_chainsource_interface::MockChainSource;
fn tree_proof(n: usize, index: usize) -> (CapsuleBytes32, CapsuleBytes32, Vec<ProofStep>) {
let chunks: Vec<Vec<u8>> = (0..n).map(|i| vec![i as u8; 8]).collect();
let tree = MerkleTree::build(&chunks);
let proof = tree.prove(index).expect("index in range");
(tree.root(), proof.leaf, proof.path)
}
#[test]
fn a_valid_range_proof_gathers_and_verifies() {
let (root, leaf, path) = tree_proof(5, 2);
let claim = RangeInclusionClaim {
leaf,
path,
generation_root: root,
};
let evidence =
RangeInclusionEvidence::gather(&claim, &MockChainSource::new()).expect("valid proof");
assert_eq!(evidence.generation_root(), root);
assert_eq!(evidence.leaf(), leaf);
assert!(evidence.verify().is_ok());
}
#[test]
fn a_tampered_leaf_is_rejected() {
let (root, _leaf, path) = tree_proof(5, 2);
let forged = RangeInclusionClaim {
leaf: CapsuleBytes32([0xff; 32]),
path,
generation_root: root,
};
assert_eq!(
RangeInclusionEvidence::gather(&forged, &MockChainSource::new()).unwrap_err(),
EvidenceError::ProofDoesNotFold
);
}
#[test]
fn a_wrong_claimed_root_is_rejected() {
let (_root, leaf, path) = tree_proof(8, 5);
let forged = RangeInclusionClaim {
leaf,
path,
generation_root: CapsuleBytes32([0x00; 32]),
};
assert_eq!(
RangeInclusionEvidence::gather(&forged, &MockChainSource::new()).unwrap_err(),
EvidenceError::ProofDoesNotFold
);
}
#[test]
fn a_tampered_path_step_is_rejected() {
let (root, leaf, mut path) = tree_proof(6, 1);
if let Some(step) = path.first_mut() {
step.hash = CapsuleBytes32([0x77; 32]);
}
let forged = RangeInclusionClaim {
leaf,
path,
generation_root: root,
};
assert_eq!(
RangeInclusionEvidence::gather(&forged, &MockChainSource::new()).unwrap_err(),
EvidenceError::ProofDoesNotFold
);
}
}