Skip to main content

blvm_consensus/block/
header.rs

1//! Block header validation (Orange Paper Section 5.3, §5.3.1).
2//!
3//! Single place for structural and time header rules. Part of a larger validation pipeline.
4//!
5//! ## What this module checks (H01, H03–H06 of §5.3.1)
6//!
7//! - **H01** — version ≥ 1 (floor; version 0 is rejected unconditionally)
8//! - **H03** — timestamp ≠ 0
9//! - **H04** — timestamp ≤ network_time + MAX_FUTURE_BLOCK_TIME (requires [`TimeContext`])
10//! - **H05** — timestamp ≥ median_time_past / BIP113 MTP (requires [`TimeContext`])
11//! - **H06** — bits ≠ 0
12//! - merkle_root ≠ all-zeros (structural sanity only; full merkle verification is in ConnectBlock)
13//!
14//! ## What this module does NOT check
15//!
16//! - **H02** — height-dependent version minimums (version ≥ 2/3/4 after BIP34/66/65): see
17//!   [`crate::bip_validation::check_bip90`], called by `connect_block_inner`.
18//! - **H07** — proof of work (hash vs compact target): see [`crate::pow::check_proof_of_work`].
19//! - **H08** — parent hash linkage: pure predicate [`validate_prev_block_hash`]; the node chain
20//!   layer supplies the parent header/hash and rejects blocks that fail H08 before `connect_block`.
21//!
22//! Callers connecting a block must invoke H02, H07, and H08 (via the predicate) in addition to
23//! [`validate_block_header`] to satisfy `ValidBlockHeader` in full.
24//!
25//! ## Refactor / audit notes (coordinate with `blvm-spec-lock` before changing shape)
26//!
27//! - **Early returns** encode consensus rejects (`Ok(false)`). Do not duplicate the same condition
28//!   with `assert!` below — that only adds panic risk if someone reorders code.
29//! - The tautological `assert!(result || !result)` (below) is **on purpose**: formal verification /
30//!   spec-lock tooling hooks here. Do not delete without verifier sign-off.
31//! - **Version `0`** is rejected by `version < 1` (H01). Version 1 is valid before BIP34 and
32//!   invalid after it — that boundary is enforced by `check_bip90` (H02), not here.
33//! - **Merkle root** field is checked for all-zeros only (structural guard). Cryptographic
34//!   verification of the merkle root against block transactions happens in `connect_block_inner`.
35
36use crate::error::Result;
37use crate::types::{BlockHeader, TimeContext};
38use blvm_spec_lock::spec_locked;
39
40/// Validate block header structural and time rules (H01, H03–H06 of §5.3.1).
41///
42/// Returns `Ok(true)` if all checks pass, `Ok(false)` if any check fails.
43///
44/// This is one component of `ValidBlockHeader`. Callers connecting a block must also invoke:
45/// - [`crate::bip_validation::check_bip90`] — H02: height-dependent version minimums
46/// - [`crate::pow::check_proof_of_work`] — H07: hash vs compact target
47///
48/// Parent hash linkage (H08): [`validate_prev_block_hash`]; orchestration is in the node layer.
49///
50/// # Arguments
51///
52/// * `header` - Block header to validate
53/// * `time_context` - Optional time context for timestamp validation (BIP113).
54///   If `None`, only H01/H03/H06 (version, non-zero timestamp, bits) are enforced.
55///   If `Some`, also enforces H04 (timestamp ≤ network_time + MAX_FUTURE_BLOCK_TIME)
56///   and H05 (timestamp ≥ median_time_past).
57#[allow(clippy::overly_complex_bool_expr, clippy::redundant_comparisons)] // Intentional tautological assertions for formal verification
58#[spec_locked("5.3.1", "ValidBlockHeader")]
59#[inline]
60pub(crate) fn validate_block_header(
61    header: &BlockHeader,
62    time_context: Option<&TimeContext>,
63) -> Result<bool> {
64    if header.version < 1 {
65        return Ok(false);
66    }
67    if header.timestamp == 0 {
68        return Ok(false);
69    }
70    if let Some(ctx) = time_context {
71        let max_ts = ctx
72            .network_time
73            .saturating_add(crate::constants::MAX_FUTURE_BLOCK_TIME);
74        if header.timestamp > max_ts {
75            return Ok(false);
76        }
77        if header.timestamp < ctx.median_time_past {
78            return Ok(false);
79        }
80    }
81    if header.bits == 0 {
82        return Ok(false);
83    }
84    if header.merkle_root == [0u8; 32] {
85        return Ok(false);
86    }
87
88    // Formal-verification anchor (spec-lock): keep `result` and the tautology; omit a second
89    // `assert!(result)` — success is `Ok(true)` below.
90    let result = true;
91    #[allow(clippy::eq_op)]
92    {
93        assert!(result || !result, "Validation result must be boolean");
94    }
95    Ok(result)
96}
97
98/// Double-SHA256 hash of an 80-byte serialized block header (Bitcoin block id).
99#[spec_locked("5.3.1", "BlockHeaderHash")]
100#[inline]
101pub fn block_header_hash(header: &BlockHeader) -> crate::types::Hash {
102    use blvm_primitives::crypto::hash256;
103    use blvm_primitives::serialization::serialize_block_header;
104    hash256(&serialize_block_header(header))
105}
106
107/// H08 (§5.3.1): `child.prev_block_hash` MUST equal `block_header_hash(parent)`.
108///
109/// Pure predicate for spec-lock binding. The node layer calls this before `connect_block` when
110/// the parent header is known; IBD/header sync enforces the same invariant while walking the chain.
111#[spec_locked("5.3.1", "ValidatePrevBlockHash")]
112#[blvm_spec_lock::ensures(result == true || result == false)]
113#[inline]
114pub fn validate_prev_block_hash(child: &BlockHeader, parent: &BlockHeader) -> bool {
115    child.prev_block_hash == block_header_hash(parent)
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::types::BlockHeader;
122
123    #[test]
124    fn validate_prev_block_hash_accepts_matching_parent() {
125        let parent = BlockHeader {
126            version: 1,
127            prev_block_hash: [0u8; 32],
128            merkle_root: [1u8; 32],
129            timestamp: 1,
130            bits: 0x1d00ffff,
131            nonce: 0,
132        };
133        let parent_hash = block_header_hash(&parent);
134        let child = BlockHeader {
135            prev_block_hash: parent_hash,
136            merkle_root: [2u8; 32],
137            ..parent
138        };
139        assert!(validate_prev_block_hash(&child, &parent));
140    }
141
142    #[test]
143    fn validate_prev_block_hash_rejects_mismatch() {
144        let parent = BlockHeader {
145            version: 1,
146            prev_block_hash: [0u8; 32],
147            merkle_root: [1u8; 32],
148            timestamp: 1,
149            bits: 0x1d00ffff,
150            nonce: 0,
151        };
152        let child = BlockHeader {
153            prev_block_hash: [9u8; 32],
154            merkle_root: [2u8; 32],
155            ..parent
156        };
157        assert!(!validate_prev_block_hash(&child, &parent));
158    }
159}