runx-receipts 0.6.19

Pure Rust receipt model, canonicalization, and verification parity for runx.
Documentation
mod findings;
mod proof;
mod resolver;
mod traversal;

#[cfg(test)]
mod proof_tests;
#[cfg(test)]
mod structural_tests;
#[cfg(test)]
mod test_support;

use std::collections::BTreeSet;

use runx_contracts::{Receipt, Reference};

use crate::{
    ReceiptFinding, ReceiptProofContext, ReceiptVerification, verify_receipt, verify_receipt_proof,
};
use findings::{child_receipt_findings, duplicate_child_findings, orphan_child_findings};
use proof::{StrictChildProofPolicy, StructuralChildProofPolicy, child_receipt_proof_findings};
use resolver::SliceReceiptResolver;
use traversal::TreeTraversal;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReceiptTreeConfig {
    pub max_depth: usize,
    pub max_breadth: usize,
    pub require_parent_links: bool,
}

impl Default for ReceiptTreeConfig {
    fn default() -> Self {
        Self {
            max_depth: 64,
            max_breadth: 1024,
            require_parent_links: false,
        }
    }
}

pub trait ReceiptResolver {
    fn resolve_child<'a>(&'a self, reference: &Reference) -> ReceiptResolveResult<'a>;
    fn supplied_receipts<'a>(&'a self) -> Vec<ResolvedReceipt<'a>>;
}

pub trait ReceiptProofContextProvider {
    fn proof_context<'a>(&'a self, receipt: &Receipt) -> ReceiptProofContext<'a>;
}

#[derive(Clone, Debug)]
pub struct ResolvedReceipt<'a> {
    pub path: String,
    pub receipt: &'a Receipt,
}

#[derive(Clone, Debug)]
pub enum ReceiptResolveResult<'a> {
    Found(ResolvedReceipt<'a>),
    Missing,
    Malformed,
    Ambiguous,
    ResolverError,
}

pub fn validate_receipt_tree(
    root: &Receipt,
    children: &[Receipt],
) -> Result<(), ReceiptVerification> {
    let resolver = SliceReceiptResolver { children };
    validate_receipt_tree_with_resolver(root, &resolver, ReceiptTreeConfig::default())
}

#[must_use]
pub fn verify_receipt_tree(root: &Receipt, children: &[Receipt]) -> ReceiptVerification {
    let resolver = SliceReceiptResolver { children };
    verify_receipt_tree_with_resolver(root, &resolver, ReceiptTreeConfig::default())
}

pub fn validate_receipt_tree_proof(
    root: &Receipt,
    children: &[Receipt],
    proof_contexts: &impl ReceiptProofContextProvider,
) -> Result<(), ReceiptVerification> {
    let resolver = SliceReceiptResolver { children };
    validate_receipt_tree_proof_with_resolver(
        root,
        &resolver,
        ReceiptTreeConfig::default(),
        proof_contexts,
    )
}

#[must_use]
pub fn verify_receipt_tree_proof(
    root: &Receipt,
    children: &[Receipt],
    proof_contexts: &impl ReceiptProofContextProvider,
) -> ReceiptVerification {
    let resolver = SliceReceiptResolver { children };
    verify_receipt_tree_proof_with_resolver(
        root,
        &resolver,
        ReceiptTreeConfig::default(),
        proof_contexts,
    )
}

pub fn validate_receipt_tree_with_resolver(
    root: &Receipt,
    resolver: &impl ReceiptResolver,
    config: ReceiptTreeConfig,
) -> Result<(), ReceiptVerification> {
    let verification = verify_receipt_tree_with_resolver(root, resolver, config);
    if verification.valid {
        Ok(())
    } else {
        Err(verification)
    }
}

#[must_use]
pub fn verify_receipt_tree_with_resolver(
    root: &Receipt,
    resolver: &impl ReceiptResolver,
    config: ReceiptTreeConfig,
) -> ReceiptVerification {
    let mut findings = verify_receipt(root).findings;
    let supplied = resolver.supplied_receipts();
    findings.extend(duplicate_child_findings(&supplied));
    findings.extend(child_receipt_findings(&supplied));
    verify_tree_relationships(root, resolver, config, &supplied, findings)
}

pub fn validate_receipt_tree_proof_with_resolver(
    root: &Receipt,
    resolver: &impl ReceiptResolver,
    config: ReceiptTreeConfig,
    proof_contexts: &impl ReceiptProofContextProvider,
) -> Result<(), ReceiptVerification> {
    let verification =
        verify_receipt_tree_proof_with_resolver(root, resolver, config, proof_contexts);
    if verification.valid {
        Ok(())
    } else {
        Err(verification)
    }
}

#[must_use]
pub fn verify_receipt_tree_proof_with_resolver(
    root: &Receipt,
    resolver: &impl ReceiptResolver,
    config: ReceiptTreeConfig,
    proof_contexts: &impl ReceiptProofContextProvider,
) -> ReceiptVerification {
    let root_context = proof_contexts.proof_context(root);
    let mut findings = verify_receipt_proof(root, &root_context).findings;
    let supplied = resolver.supplied_receipts();
    findings.extend(duplicate_child_findings(&supplied));
    findings.extend(child_receipt_proof_findings(&supplied, proof_contexts));
    verify_tree_relationships_with_proof(
        root,
        resolver,
        config,
        &supplied,
        findings,
        proof_contexts,
    )
}

fn verify_tree_relationships<R: ReceiptResolver>(
    root: &Receipt,
    resolver: &R,
    config: ReceiptTreeConfig,
    supplied: &[ResolvedReceipt<'_>],
    mut findings: Vec<ReceiptFinding>,
) -> ReceiptVerification {
    let mut traversal = TreeTraversal {
        resolver,
        config,
        proof_policy: StructuralChildProofPolicy,
        visiting: BTreeSet::new(),
        reached: BTreeSet::new(),
    };
    findings.extend(traversal.subtree_findings("", root, 0));
    findings.extend(orphan_child_findings(supplied, &traversal.reached));
    ReceiptVerification::from_findings(findings)
}

fn verify_tree_relationships_with_proof<R: ReceiptResolver>(
    root: &Receipt,
    resolver: &R,
    config: ReceiptTreeConfig,
    supplied: &[ResolvedReceipt<'_>],
    mut findings: Vec<ReceiptFinding>,
    proof_contexts: &impl ReceiptProofContextProvider,
) -> ReceiptVerification {
    let mut traversal = TreeTraversal {
        resolver,
        config,
        proof_policy: StrictChildProofPolicy::new(supplied, proof_contexts),
        visiting: BTreeSet::new(),
        reached: BTreeSet::new(),
    };
    findings.extend(traversal.subtree_findings("", root, 0));
    findings.extend(orphan_child_findings(supplied, &traversal.reached));
    ReceiptVerification::from_findings(findings)
}