chio_kernel/checkpoint/consistency.rs
1//! Anchoring rules for checkpoint-chain consistency proofs.
2//!
3//! Split from `checkpoint.rs` to keep that module inside the production file
4//! size limit enforced by `scripts/check-rust-file-hygiene.py`.
5
6use chio_core::hashing::Hash;
7use chio_core::merkle::MerkleProof;
8
9use super::{checkpoint_chain_root, CheckpointError, KernelCheckpoint};
10
11/// What ties the prefix below a consistency proof's earlier endpoint to a
12/// history the verifier accepts.
13///
14/// A pair-level proof constrains nothing before its earlier endpoint, so for a
15/// pair starting after checkpoint 1 a signing-key holder can commit an earlier
16/// tree whose leading leaves are fabricated, append the later real leaf,
17/// re-sign both bodies, and produce paths that verify.
18#[derive(Debug, Clone, Copy)]
19pub enum CheckpointConsistencyAnchor<'a> {
20 /// The earlier endpoint is checkpoint 1, so its chain tree is exactly its
21 /// own leaf and no prefix exists to fabricate.
22 Genesis,
23 /// A chain root the verifier already accepted for the earlier endpoint.
24 VerifiedChainRoot(Hash),
25 /// Every chain leaf from checkpoint 1 through the earlier endpoint.
26 ChainPrefix(&'a [Hash]),
27}
28
29/// Whether `from_chain_root` is anchored under `anchor`. Genesis anchoring
30/// constrains the verifier's inputs rather than the proof, so a mid-chain pair
31/// is an error there instead of a verification failure.
32pub(super) fn consistency_prefix_is_anchored(
33 previous: &KernelCheckpoint,
34 from_size: usize,
35 from_chain_root: &Hash,
36 anchor: CheckpointConsistencyAnchor<'_>,
37) -> Result<bool, CheckpointError> {
38 match anchor {
39 CheckpointConsistencyAnchor::Genesis => {
40 if from_size != 1 {
41 return Err(CheckpointError::Continuity(format!(
42 "consistency proof from checkpoint {} is unanchored; only a pair starting at checkpoint 1 anchors itself",
43 previous.body.checkpoint_seq
44 )));
45 }
46 Ok(true)
47 }
48 CheckpointConsistencyAnchor::VerifiedChainRoot(pinned) => Ok(pinned == *from_chain_root),
49 CheckpointConsistencyAnchor::ChainPrefix(chain_leaf_hashes) => Ok(chain_leaf_hashes.len()
50 == from_size
51 && checkpoint_chain_root(chain_leaf_hashes)? == *from_chain_root),
52 }
53}
54
55/// Whether `leaf` is committed by `root` as the final leaf of a `size`-leaf
56/// chain tree, per the supplied inclusion proof.
57pub(super) fn chain_leaf_is_committed(
58 inclusion: &MerkleProof,
59 size: usize,
60 leaf: Hash,
61 root: &Hash,
62) -> bool {
63 inclusion.tree_size == size
64 && size.checked_sub(1) == Some(inclusion.leaf_index)
65 && inclusion.verify_hash(leaf, root)
66}