Skip to main content

blvm_protocol/utxo_commitments/
verification.rs

1//! UTXO Commitment Verification
2//!
3//! Provides utilities for verifying UTXO commitments against:
4//! - Bitcoin supply calculations
5//! - Block header chain (Proof of Work)
6//! - Peer consensus consistency
7
8#[cfg(feature = "utxo-commitments")]
9use crate::utxo_commitments::data_structures::{
10    UtxoCommitment, UtxoCommitmentError, UtxoCommitmentResult,
11};
12#[cfg(feature = "utxo-commitments")]
13use blvm_consensus::economic::total_supply;
14#[cfg(feature = "utxo-commitments")]
15use blvm_consensus::pow::check_proof_of_work;
16#[cfg(feature = "utxo-commitments")]
17use blvm_consensus::types::{BlockHeader, Hash, Natural};
18#[cfg(feature = "utxo-commitments")]
19/// Verify that a UTXO commitment's supply matches expected Bitcoin supply
20///
21/// Checks that the total supply in the commitment equals the sum of all
22/// block subsidies up to the commitment's block height.
23pub fn verify_supply(commitment: &UtxoCommitment) -> UtxoCommitmentResult<bool> {
24    let expected_supply = total_supply(commitment.block_height) as u64;
25
26    if commitment.total_supply != expected_supply {
27        return Err(UtxoCommitmentError::VerificationFailed(format!(
28            "Supply mismatch at height {}: commitment has {} satoshis, expected {} satoshis",
29            commitment.block_height, commitment.total_supply, expected_supply
30        )));
31    }
32
33    Ok(true)
34}
35
36/// Verify that a block header chain is valid (Proof of Work)
37///
38/// Verifies the chain of block headers from genesis to the commitment height,
39/// checking that each header satisfies proof of work requirements.
40pub fn verify_header_chain(headers: &[BlockHeader]) -> UtxoCommitmentResult<bool> {
41    if headers.is_empty() {
42        return Err(UtxoCommitmentError::VerificationFailed(
43            "Empty header chain".to_string(),
44        ));
45    }
46
47    // Verify each header's proof of work
48    for (i, header) in headers.iter().enumerate() {
49        match check_proof_of_work(header) {
50            Ok(is_valid) => {
51                if !is_valid {
52                    return Err(UtxoCommitmentError::VerificationFailed(format!(
53                        "Invalid proof of work at height {i}"
54                    )));
55                }
56            }
57            Err(e) => {
58                return Err(UtxoCommitmentError::VerificationFailed(format!(
59                    "PoW check failed at height {i}: {e}"
60                )));
61            }
62        }
63
64        // Verify chain linkage (except for genesis)
65        if i > 0 {
66            let prev_header = &headers[i - 1];
67            // Compute block hash using double SHA256
68            let expected_prev_hash = compute_block_hash(prev_header);
69
70            if header.prev_block_hash != expected_prev_hash {
71                return Err(UtxoCommitmentError::VerificationFailed(format!(
72                    "Chain linkage broken at height {}: expected prev_hash {:?}, got {:?}",
73                    i, expected_prev_hash, header.prev_block_hash
74                )));
75            }
76        }
77    }
78
79    Ok(true)
80}
81
82/// Verify commitment against block header
83///
84/// Verifies that the commitment's block_hash matches the actual block hash
85/// at the given height.
86pub fn verify_commitment_block_hash(
87    commitment: &UtxoCommitment,
88    header: &BlockHeader,
89) -> UtxoCommitmentResult<bool> {
90    let computed_hash = compute_block_hash(header);
91
92    if commitment.block_hash != computed_hash {
93        return Err(UtxoCommitmentError::VerificationFailed(format!(
94            "Block hash mismatch: commitment has {:?}, header has {:?}",
95            commitment.block_hash, computed_hash
96        )));
97    }
98
99    Ok(true)
100}
101
102/// Compute block header hash (double SHA256) — 80-byte Bitcoin wire format.
103///
104/// Casts each field to its on-wire width (version/timestamp/bits/nonce = u32 LE)
105/// matching `blvm_consensus::pow::serialize_header`.
106fn compute_block_hash(header: &BlockHeader) -> Hash {
107    use sha2::{Digest, Sha256};
108
109    let mut bytes = [0u8; 80];
110    bytes[0..4].copy_from_slice(&(header.version as u32).to_le_bytes());
111    bytes[4..36].copy_from_slice(&header.prev_block_hash);
112    bytes[36..68].copy_from_slice(&header.merkle_root);
113    bytes[68..72].copy_from_slice(&(header.timestamp as u32).to_le_bytes());
114    bytes[72..76].copy_from_slice(&(header.bits as u32).to_le_bytes());
115    bytes[76..80].copy_from_slice(&(header.nonce as u32).to_le_bytes());
116
117    let first_hash = Sha256::digest(bytes);
118    let second_hash = Sha256::digest(first_hash);
119
120    let mut hash = [0u8; 32];
121    hash.copy_from_slice(&second_hash);
122    hash
123}
124
125/// Verify forward consistency
126///
127/// Verifies that applying a sequence of blocks to a commitment results in
128/// a consistent new commitment. Used to ensure commitments remain valid
129/// as the chain progresses.
130pub fn verify_forward_consistency(
131    initial_commitment: &UtxoCommitment,
132    new_commitment: &UtxoCommitment,
133    expected_height_increase: Natural,
134) -> UtxoCommitmentResult<bool> {
135    // Verify height progression
136    if new_commitment.block_height != initial_commitment.block_height + expected_height_increase {
137        return Err(UtxoCommitmentError::VerificationFailed(format!(
138            "Height mismatch: initial {}, new {}, expected increase {}",
139            initial_commitment.block_height, new_commitment.block_height, expected_height_increase
140        )));
141    }
142
143    // Verify supply progression (should only increase or stay same, never decrease)
144    if new_commitment.total_supply < initial_commitment.total_supply {
145        return Err(UtxoCommitmentError::VerificationFailed(format!(
146            "Supply decreased: initial {}, new {}",
147            initial_commitment.total_supply, new_commitment.total_supply
148        )));
149    }
150
151    // Note: We can't verify UTXO count changes without knowing transactions,
152    // but we can verify supply changes match expected block subsidies
153    let expected_new_supply = total_supply(new_commitment.block_height) as u64;
154    if new_commitment.total_supply != expected_new_supply {
155        return Err(UtxoCommitmentError::VerificationFailed(format!(
156            "New supply mismatch: commitment has {}, expected {}",
157            new_commitment.total_supply, expected_new_supply
158        )));
159    }
160
161    Ok(true)
162}
163
164// ============================================================================
165// FORMAL VERIFICATION
166// ============================================================================
167//
168// Mathematical Specification for UTXO Commitment Verification:
169// ∀ commitment ∈ UtxoCommitment, height ∈ ℕ:
170// - verify_supply(commitment) = true ⟺ commitment.total_supply = total_supply(height)
171// - verify_forward_consistency(c1, c2) = true ⟺ c2.height = c1.height + Δ ∧ c2.supply ≥ c1.supply
172//
173// Invariants:
174// - Supply verification prevents inflation
175// - Forward consistency ensures commitments progress correctly
176// - Block hash verification ensures commitment matches actual block