Skip to main content

blvm_consensus/
bip_validation.rs

1//! BIP Validation Rules
2//!
3//! Implementation of critical Bitcoin Improvement Proposals (BIPs) that enforce
4//! consensus rules for block and transaction validation.
5//!
6//! Mathematical specifications from Orange Paper Section 5.4.
7
8use crate::activation::IsForkActive;
9use crate::block::calculate_tx_id;
10use crate::error::Result;
11use crate::opcodes::{
12    OP_0, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4,
13};
14use crate::transaction::is_coinbase;
15use crate::types::*;
16#[cfg(feature = "spec-lock")]
17use blvm_spec_lock::spec_locked;
18
19/// BIP30 index: maps coinbase txid โ†’ count of unspent outputs.
20/// When count > 0, a coinbase with that txid has unspent outputs (BIP30 would reject duplicate).
21/// Uses FxHashMap for faster lookups on integer-like keys (#17).
22#[cfg(feature = "production")]
23pub type Bip30Index = rustc_hash::FxHashMap<crate::types::Hash, usize>;
24#[cfg(not(feature = "production"))]
25pub type Bip30Index = std::collections::HashMap<crate::types::Hash, usize>;
26
27/// Build Bip30Index from an existing UTXO set (for IBD resume).
28/// Scans coinbase UTXOs and counts outputs per txid. O(n) over utxo_set.
29pub fn build_bip30_index(utxo_set: &UtxoSet) -> Bip30Index {
30    let mut index = Bip30Index::default();
31    for (outpoint, utxo) in utxo_set.iter() {
32        if utxo.is_coinbase {
33            *index.entry(outpoint.hash).or_insert(0) += 1;
34        }
35    }
36    index
37}
38
39/// Mainnet block 91842. Digest order, matching `block_header_hash`.
40const BIP30_REPEAT_91842: Hash = [
41    0xec, 0xca, 0xe0, 0x00, 0xe3, 0xc8, 0xe4, 0xe0, 0x93, 0x93, 0x63, 0x60, 0x43, 0x1f, 0x3b, 0x76,
42    0x03, 0xc5, 0x63, 0xc1, 0xff, 0x61, 0x81, 0x39, 0x0a, 0x4d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
43];
44/// Mainnet block 91880. Digest order, matching `block_header_hash`.
45const BIP30_REPEAT_91880: Hash = [
46    0x21, 0xd7, 0x7c, 0xcb, 0x4c, 0x08, 0x38, 0x6a, 0x04, 0xac, 0x01, 0x96, 0xae, 0x10, 0xf6, 0xa1,
47    0xd2, 0xc2, 0xa3, 0x77, 0x55, 0x8c, 0xa1, 0x90, 0xf1, 0x43, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
48];
49/// Known mainnet block at the height-in-coinbase activation. Digest order.
50const BIP34_MAINNET_HASH: Hash = [
51    0xb8, 0x08, 0x08, 0x9c, 0x75, 0x6a, 0xdd, 0x15, 0x91, 0xb1, 0xd1, 0x7b, 0xab, 0x44, 0xbb, 0xa3,
52    0xfe, 0xd9, 0xe0, 0x2f, 0x94, 0x2a, 0xb4, 0x89, 0x4b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
53];
54/// Known testnet block at the height-in-coinbase activation. Digest order.
55const BIP34_TESTNET_HASH: Hash = [
56    0xf8, 0x8e, 0xcd, 0x99, 0x12, 0xd0, 0x0d, 0x3f, 0x5c, 0x2a, 0x8e, 0x0f, 0x50, 0x41, 0x7d, 0x3e,
57    0x41, 0x5c, 0x75, 0xb3, 0xab, 0xe5, 0x84, 0x34, 0x6d, 0xa9, 0xb3, 0x23, 0x00, 0x00, 0x00, 0x00,
58];
59
60/// From this height the duplicate-coinbase lookup is mandatory again, even on a chain
61/// whose height-in-coinbase activation block is the known one. A coinbase from before
62/// that rule can carry an indicated height this large.
63pub const BIP34_IMPLIES_BIP30_LIMIT: u64 = 1_983_702;
64
65/// True only for the two mainnet blocks whose coinbase repeats an earlier unspent one.
66pub fn is_bip30_repeat_block(height: u64, block_hash: &Hash) -> bool {
67    match height {
68        91_842 => block_hash == &BIP30_REPEAT_91842,
69        91_880 => block_hash == &BIP30_REPEAT_91880,
70        _ => false,
71    }
72}
73
74/// True when this header is the known block at the height-in-coinbase activation.
75/// The header is hashed only at that height.
76pub fn is_known_bip34_header(network: Network, height: u64, header: &BlockHeader) -> bool {
77    let expected = match network {
78        Network::Mainnet if height == crate::constants::BIP34_ACTIVATION_MAINNET => {
79            &BIP34_MAINNET_HASH
80        }
81        Network::Testnet if height == crate::constants::BIP34_ACTIVATION_TESTNET => {
82            &BIP34_TESTNET_HASH
83        }
84        _ => return false,
85    };
86    &crate::block::block_header_hash(header) == expected
87}
88
89/// True when `block_hash` is the known block at this network's height-in-coinbase activation.
90pub fn is_known_bip34_block(network: Network, height: u64, block_hash: &Hash) -> bool {
91    match network {
92        Network::Mainnet => {
93            height == crate::constants::BIP34_ACTIVATION_MAINNET
94                && block_hash == &BIP34_MAINNET_HASH
95        }
96        Network::Testnet => {
97            height == crate::constants::BIP34_ACTIVATION_TESTNET
98                && block_hash == &BIP34_TESTNET_HASH
99        }
100        Network::Regtest | Network::Signet | Network::Testnet4 => false,
101    }
102}
103
104/// The duplicate lookup may be skipped only after the known height-in-coinbase block,
105/// and only before [`BIP34_IMPLIES_BIP30_LIMIT`].
106pub fn bip30_lookup_skippable(network: Network, height: u64, bip34_hash_matches: bool) -> bool {
107    if !bip34_hash_matches || height >= BIP34_IMPLIES_BIP30_LIMIT {
108        return false;
109    }
110    let activation = match network {
111        Network::Mainnet => crate::constants::BIP34_ACTIVATION_MAINNET,
112        Network::Testnet => crate::constants::BIP34_ACTIVATION_TESTNET,
113        Network::Regtest | Network::Signet | Network::Testnet4 => return false,
114    };
115    height > activation
116}
117
118/// BIP30: Duplicate Coinbase Prevention
119///
120/// A coinbase whose transaction id already has an unspent output is invalid.
121/// Mathematical specification: Orange Paper Section 5.4.1
122///
123/// **BIP30Check**: โ„ฌ ร— ๐’ฐ๐’ฎ ร— โ„• ร— Network โ†’ {valid, invalid}
124///
125/// Two mainnet blocks are exempt, and only when the block hash matches:
126/// height 91842 and height 91880. Every other duplicate is invalid, on every
127/// network, at every height. There is no deactivation height.
128///
129/// **Optimization**: When `bip30_index` is `Some`, uses O(1) lookup instead of O(n) iteration
130/// over the UTXO set. Caller must maintain the index in sync with UTXO changes.
131/// **#2**: When `coinbase_txid` is `Some`, skips `calculate_tx_id(coinbase)` โ€” caller precomputed.
132#[cfg_attr(feature = "spec-lock", spec_locked("5.4.1", "BIP30Check"))]
133pub fn check_bip30(
134    block: &Block,
135    utxo_set: &UtxoSet,
136    bip30_index: Option<&Bip30Index>,
137    height: Natural,
138    activation: &impl IsForkActive,
139    coinbase_txid: Option<&Hash>,
140) -> Result<bool> {
141    if !activation.is_fork_active(ForkId::Bip30, height) {
142        return Ok(true);
143    }
144    // The two historical duplicates are exempt by hash. Any other block at
145    // those heights is still checked. The header is hashed only then.
146    if height == 91_842 || height == 91_880 {
147        let hash = crate::block::block_header_hash(&block.header);
148        if is_bip30_repeat_block(height, &hash) {
149            return Ok(true);
150        }
151    }
152    // Find coinbase transaction
153    let coinbase = block.transactions.first();
154
155    if let Some(tx) = coinbase {
156        if !is_coinbase(tx) {
157            // Not a coinbase transaction - BIP30 doesn't apply
158            return Ok(true);
159        }
160
161        let txid = coinbase_txid
162            .copied()
163            .unwrap_or_else(|| calculate_tx_id(tx));
164
165        // Fast path: O(1) lookup when index is provided
166        if let Some(index) = bip30_index {
167            if index.get(&txid).is_some_and(|&c| c > 0) {
168                return Ok(false);
169            }
170            return Ok(true);
171        }
172
173        // Fallback: O(n) iteration when index not available (tests, sync path)
174        // BIP30: Check if ANY UTXO exists with this txid
175        for (outpoint, _utxo) in utxo_set.iter() {
176            if outpoint.hash == txid {
177                return Ok(false);
178            }
179        }
180    }
181
182    Ok(true)
183}
184
185/// BIP34: Block Height in Coinbase
186///
187/// Starting at the mainnet height in `BIP34_ACTIVATION_MAINNET`,
188/// coinbase scriptSig must contain the block height.
189/// Mathematical specification: Orange Paper Section 5.4.2
190///
191/// **BIP34Check**: โ„ฌ ร— โ„• โ†’ {valid, invalid}
192///
193/// Activation Heights:
194/// - Mainnet: `BIP34_ACTIVATION_MAINNET` (227,931)
195/// - Testnet: Block 211,111
196/// - Regtest: Block 0 (always active)
197#[cfg_attr(feature = "spec-lock", spec_locked("5.4.2", "BIP34Check"))]
198pub fn check_bip34(block: &Block, height: Natural, activation: &impl IsForkActive) -> Result<bool> {
199    if !activation.is_fork_active(ForkId::Bip34, height) {
200        return Ok(true);
201    }
202
203    // Find coinbase transaction
204    let coinbase = block.transactions.first();
205
206    if let Some(tx) = coinbase {
207        if !is_coinbase(tx) {
208            return Ok(true);
209        }
210
211        // The script must begin with the minimal push of this height.
212        // A longer encoding of the same integer is a different script.
213        if !script_sig_starts_with_height(&tx.inputs[0].script_sig, height) {
214            return Ok(false);
215        }
216    }
217
218    Ok(true)
219}
220
221/// Little-endian magnitude of `height`, plus a trailing `0x00` when the high bit is set.
222fn bip34_magnitude(height: u64) -> ([u8; 9], usize) {
223    let mut raw = [0u8; 9];
224    let mut n = height;
225    let mut len = 0usize;
226    while n > 0 {
227        raw[len] = (n & 0xff) as u8;
228        n >>= 8;
229        len += 1;
230    }
231    if len > 0 && raw[len - 1] & 0x80 != 0 {
232        raw[len] = 0x00;
233        len += 1;
234    }
235    (raw, len)
236}
237
238/// Coinbase `scriptSig` whose prefix is the BIP34 height encoding.
239///
240/// Height 0 is `OP_0`. Heights 1..=16 are `OP_1`..=`OP_16`. Taller heights are
241/// one length byte plus the minimal little-endian magnitude. A trailing `0xff`
242/// keeps the script at least 2 bytes, the coinbase minimum.
243pub fn encode_bip34_coinbase_script(height: u64) -> Vec<u8> {
244    if height == 0 {
245        return vec![0x00, 0xff];
246    }
247    if (1..=16).contains(&height) {
248        return vec![0x50 + height as u8, 0xff];
249    }
250    let (raw, len) = bip34_magnitude(height);
251    let mut script = Vec::with_capacity(len + 2);
252    script.push(len as u8);
253    script.extend_from_slice(&raw[..len]);
254    if script.len() < 2 {
255        script.push(0xff);
256    }
257    script
258}
259
260/// True when `script_sig` begins with the BIP34 height prefix.
261///
262/// Height 0 is `OP_0`. Heights 1 through 16 are the single opcodes `OP_1`..=`OP_16`
263/// (`0x51`..=`0x60`), not a length-prefixed push of the same integer. Testnet4 and
264/// signet activate BIP34 at height 1, and testnet4 block 1's coinbase starts with
265/// `OP_1`. A longer height is one length byte plus the little-endian magnitude.
266/// When the high bit of the last magnitude byte is set, one extra `0x00` follows so
267/// the number stays non-negative. Bytes after that prefix are ignored.
268fn script_sig_starts_with_height(script_sig: &[u8], height: u64) -> bool {
269    if height == 0 {
270        return script_sig.first().copied() == Some(0x00);
271    }
272    if (1..=16).contains(&height) {
273        // OP_1 = 0x51. Heights 1..=16 are the single opcode 0x50 + height.
274        return script_sig.first().copied() == Some(0x50 + height as u8);
275    }
276    let (raw, len) = bip34_magnitude(height);
277    script_sig.len() > len && script_sig[0] == len as u8 && script_sig[1..1 + len] == raw[..len]
278}
279
280/// BIP54: Consensus Cleanup activation (with optional override).
281///
282/// Orange Paper ยง5.4.9. When `activation_override` is `Some(h)`, returns true iff `height >= h`
283/// (caller-derived activation, e.g. from BIP9 version bits). When `None`, uses per-network constants
284/// (`BIP54_ACTIVATION_*`). This allows the node to run BIP54 when miners are signalling
285/// without configuring a fixed activation height.
286#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "IsBip54ActiveAt"))]
287pub fn is_bip54_active_at(
288    height: Natural,
289    network: crate::types::Network,
290    activation_override: Option<u64>,
291) -> bool {
292    let activation = match activation_override {
293        Some(h) => h,
294        None => match network {
295            crate::types::Network::Mainnet => crate::constants::BIP54_ACTIVATION_MAINNET,
296            crate::types::Network::Testnet => crate::constants::BIP54_ACTIVATION_TESTNET,
297            crate::types::Network::Regtest
298            | crate::types::Network::Signet
299            | crate::types::Network::Testnet4 => crate::constants::BIP54_ACTIVATION_REGTEST,
300        },
301    };
302    activation != u64::MAX && height >= activation
303}
304
305/// BIP54: Consensus Cleanup activation (constant-only).
306///
307/// Orange Paper ยง5.4.9. Returns true if block at `height` on `network` is at or past the configured
308/// BIP54 activation height. For activation derived from miner signalling (version bits),
309/// use `connect_block_ibd` with `bip54_activation_override` set from
310/// `blvm_consensus::version_bits::activation_height_from_headers` (e.g. with `version_bits::bip54_deployment_mainnet()`).
311#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "IsBip54Active"))]
312pub fn is_bip54_active(height: Natural, network: crate::types::Network) -> bool {
313    is_bip54_active_at(height, network, None)
314}
315
316/// BIP54 timewarp mitigation at difficulty period boundaries (Orange Paper ยง5.4.9).
317#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "BIP54TimewarpCheck"))]
318pub fn check_bip54_timewarp(
319    header: &BlockHeader,
320    height: Natural,
321    boundary: Option<&Bip54BoundaryTimestamps>,
322    bip54_active: bool,
323) -> bool {
324    if !bip54_active {
325        return true;
326    }
327    let rem = height % 2016;
328    if rem == 2015 {
329        let Some(b) = boundary else {
330            return false;
331        };
332        header.timestamp >= b.timestamp_n_minus_2015
333    } else if rem == 0 {
334        let Some(b) = boundary else {
335            return false;
336        };
337        const TWOHOURS: u64 = 7200;
338        let min_ts = b.timestamp_n_minus_1.saturating_sub(TWOHOURS);
339        header.timestamp >= min_ts
340    } else {
341        true
342    }
343}
344
345/// BIP54: reject non-coinbase txs with witness-stripped size exactly 64 bytes.
346#[cfg_attr(
347    feature = "spec-lock",
348    spec_locked("5.4.9", "CheckBip54TxStrippedSize")
349)]
350pub fn check_bip54_tx_stripped_size(tx: &Transaction) -> bool {
351    is_coinbase(tx) || crate::transaction::calculate_transaction_size(tx) != 64
352}
353
354/// BIP54 per-transaction sigop cap (โ‰ค 2,500 for non-coinbase when active).
355#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "CheckBip54SigOpLimit"))]
356pub fn check_bip54_sigop_limit<U: crate::utxo_overlay::UtxoLookup>(
357    bip54_active: bool,
358    tx: &Transaction,
359    utxo_lookup: &U,
360    wits: Option<&[Witness]>,
361    tx_flags: u32,
362) -> Result<Option<&'static str>> {
363    if !bip54_active || is_coinbase(tx) {
364        return Ok(None);
365    }
366    let sigop_count =
367        crate::sigop::get_transaction_sigop_count_for_bip54(tx, utxo_lookup, wits, tx_flags)?;
368    if sigop_count > crate::constants::BIP54_MAX_SIGOPS_PER_TX {
369        return Ok(Some("BIP54: Transaction sigop count exceeds 2500"));
370    }
371    Ok(None)
372}
373
374/// BIP54: Coinbase nLockTime and nSequence (Consensus Cleanup).
375///
376/// BIP54: coinbase nLockTime is the block height minus 1 (the last height at which
377/// the coinbase is invalid). nSequence must not be 0xffff_ffff.
378#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "CheckBip54Coinbase"))]
379pub fn check_bip54_coinbase(coinbase: &Transaction, height: Natural) -> bool {
380    let required_lock_time = height.saturating_sub(1);
381    if coinbase.lock_time != required_lock_time {
382        return false;
383    }
384    if coinbase.inputs.is_empty() {
385        return false;
386    }
387    if coinbase.inputs[0].sequence == 0xffff_ffff {
388        return false;
389    }
390    true
391}
392
393// Network type is now in crate::types::Network
394
395/// BIP66: Strict DER Signature Validation
396///
397/// Enforces strict DER encoding for ECDSA signatures.
398/// Mathematical specification: Orange Paper Section 5.4.3
399///
400/// **BIP66Check**: ๐•Š ร— โ„• โ†’ {valid, invalid}
401///
402/// Activation Heights:
403/// - Mainnet: `BIP66_ACTIVATION_MAINNET` (363,725)
404/// - Testnet: Block 330,776
405/// - Regtest: Block 0 (always active)
406#[cfg_attr(feature = "spec-lock", spec_locked("5.4.3", "BIP66Check"))]
407pub fn check_bip66(
408    signature: &[u8],
409    height: Natural,
410    activation: &impl IsForkActive,
411) -> Result<bool> {
412    if !activation.is_fork_active(ForkId::Bip66, height) {
413        return Ok(true);
414    }
415
416    // Check if signature is strictly DER-encoded
417    // The secp256k1 library's from_der() method should enforce strict DER
418    // We verify by attempting to parse and checking for strict compliance
419    is_strict_der(signature)
420}
421
422/// Check if signature is strictly DER-encoded
423///
424/// Implements IsValidSignatureEncoding (BIP66 strict DER) exactly.
425/// BIP66 requires strict DER encoding with specific rules:
426/// - Format: `0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]`
427/// - No leading zeros in R or S (unless needed to prevent negative interpretation)
428/// - Minimal length encoding
429pub(crate) fn is_strict_der(signature: &[u8]) -> Result<bool> {
430    // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]
431    // * total-length: 1-byte length descriptor of everything that follows,
432    //   excluding the sighash byte.
433    // * R-length: 1-byte length descriptor of the R value that follows.
434    // * R: arbitrary-length big-endian encoded R value. It must use the shortest
435    //   possible encoding for a positive integer (which means no null bytes at
436    //   the start, except a single one when the next byte has its highest bit set).
437    // * S-length: 1-byte length descriptor of the S value that follows.
438    // * S: arbitrary-length big-endian encoded S value. The same rules apply.
439    // * sighash: 1-byte value indicating what data is hashed (not part of the DER
440    //   signature)
441
442    // Minimum and maximum size constraints.
443    if signature.len() < 9 {
444        return Ok(false);
445    }
446    if signature.len() > 73 {
447        return Ok(false);
448    }
449
450    // A signature is of type 0x30 (compound).
451    if signature[0] != 0x30 {
452        return Ok(false);
453    }
454
455    // Make sure the length covers the entire signature.
456    if signature[1] != (signature.len() - 3) as u8 {
457        return Ok(false);
458    }
459
460    // Extract the length of the R element.
461    let len_r = signature[3] as usize;
462
463    // Make sure the length of the S element is still inside the signature.
464    if 5 + len_r >= signature.len() {
465        return Ok(false);
466    }
467
468    // Extract the length of the S element.
469    let len_s = signature[5 + len_r] as usize;
470
471    // Verify that the length of the signature matches the sum of the length
472    // of the elements.
473    if (len_r + len_s + 7) != signature.len() {
474        return Ok(false);
475    }
476
477    // Check whether the R element is an integer.
478    if signature[2] != 0x02 {
479        return Ok(false);
480    }
481
482    // Zero-length integers are not allowed for R.
483    if len_r == 0 {
484        return Ok(false);
485    }
486
487    // Negative numbers are not allowed for R.
488    if (signature[4] & 0x80) != 0 {
489        return Ok(false);
490    }
491
492    // Null bytes at the start of R are not allowed, unless R would
493    // otherwise be interpreted as a negative number.
494    if len_r > 1 && signature[4] == 0x00 && (signature[5] & 0x80) == 0 {
495        return Ok(false);
496    }
497
498    // Check whether the S element is an integer.
499    if signature[len_r + 4] != 0x02 {
500        return Ok(false);
501    }
502
503    // Zero-length integers are not allowed for S.
504    if len_s == 0 {
505        return Ok(false);
506    }
507
508    // Negative numbers are not allowed for S.
509    if (signature[len_r + 6] & 0x80) != 0 {
510        return Ok(false);
511    }
512
513    // Null bytes at the start of S are not allowed, unless S would otherwise be
514    // interpreted as a negative number.
515    if len_s > 1 && signature[len_r + 6] == 0x00 && (signature[len_r + 7] & 0x80) == 0 {
516        return Ok(false);
517    }
518
519    Ok(true)
520}
521
522// Network type is now in crate::types::Network
523
524/// BIP90: Block Version Enforcement
525///
526/// Enforces minimum block versions based on activation heights.
527/// Mathematical specification: Orange Paper Section 5.4.4
528///
529/// **BIP90Check**: โ„‹ ร— โ„• โ†’ {valid, invalid}
530///
531/// Activation Heights:
532/// - BIP34: Mainnet 227,931 (requires version >= 2)
533/// - BIP66: Mainnet 363,725 (requires version >= 3)
534/// - BIP65: Mainnet 388,381 (requires version >= 4)
535#[cfg_attr(feature = "spec-lock", spec_locked("5.4.4", "BIP90Check"))]
536pub fn check_bip90(
537    block_version: i64,
538    height: Natural,
539    activation: &impl IsForkActive,
540) -> Result<bool> {
541    if activation.is_fork_active(ForkId::Bip34, height) && block_version < 2 {
542        return Ok(false);
543    }
544    if activation.is_fork_active(ForkId::Bip66, height) && block_version < 3 {
545        return Ok(false);
546    }
547    if activation.is_fork_active(ForkId::Bip65, height) && block_version < 4 {
548        return Ok(false);
549    }
550
551    Ok(true)
552}
553
554/// Convenience: BIP30 check using network (builds activation table).
555pub fn check_bip30_network(
556    block: &Block,
557    utxo_set: &UtxoSet,
558    bip30_index: Option<&Bip30Index>,
559    height: Natural,
560    network: crate::types::Network,
561    coinbase_txid: Option<&Hash>,
562) -> Result<bool> {
563    let table = crate::activation::ForkActivationTable::from_network(network);
564    check_bip30(block, utxo_set, bip30_index, height, &table, coinbase_txid)
565}
566
567/// Convenience: BIP34 check using network.
568pub fn check_bip34_network(
569    block: &Block,
570    height: Natural,
571    network: crate::types::Network,
572) -> Result<bool> {
573    let table = crate::activation::ForkActivationTable::from_network(network);
574    check_bip34(block, height, &table)
575}
576
577/// Convenience: BIP66 check using network (for script/signature callers).
578pub fn check_bip66_network(
579    signature: &[u8],
580    height: Natural,
581    network: crate::types::Network,
582) -> Result<bool> {
583    let table = crate::activation::ForkActivationTable::from_network(network);
584    check_bip66(signature, height, &table)
585}
586
587/// Convenience: BIP90 check using network.
588pub fn check_bip90_network(
589    block_version: i64,
590    height: Natural,
591    network: crate::types::Network,
592) -> Result<bool> {
593    let table = crate::activation::ForkActivationTable::from_network(network);
594    check_bip90(block_version, height, &table)
595}
596
597/// Convenience: BIP147 check using network (Bip147Network for backward compatibility).
598pub fn check_bip147_network(
599    script_sig: &[u8],
600    script_pubkey: &[u8],
601    height: Natural,
602    network: Bip147Network,
603) -> Result<bool> {
604    let table = match network {
605        Bip147Network::Mainnet => {
606            crate::activation::ForkActivationTable::from_network(crate::types::Network::Mainnet)
607        }
608        Bip147Network::Testnet => {
609            crate::activation::ForkActivationTable::from_network(crate::types::Network::Testnet)
610        }
611        Bip147Network::Regtest => {
612            crate::activation::ForkActivationTable::from_network(crate::types::Network::Regtest)
613        }
614    };
615    check_bip147(script_sig, script_pubkey, height, &table)
616}
617
618/// Returns true when `opcode` appears as an executable instruction (not inside push data).
619pub(crate) fn script_contains_executable_opcode(script: &[u8], opcode: u8) -> bool {
620    let mut i = 0;
621    while i < script.len() {
622        let op = script[i];
623        if op > 0 && op < OP_PUSHDATA1 {
624            i += 1 + op as usize;
625            continue;
626        }
627        match op {
628            OP_PUSHDATA1 => {
629                if i + 1 >= script.len() {
630                    break;
631                }
632                let len = script[i + 1] as usize;
633                i += 2 + len;
634            }
635            OP_PUSHDATA2 => {
636                if i + 2 >= script.len() {
637                    break;
638                }
639                let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
640                i += 3 + len;
641            }
642            OP_PUSHDATA4 => {
643                if i + 4 >= script.len() {
644                    break;
645                }
646                let len = u32::from_le_bytes([
647                    script[i + 1],
648                    script[i + 2],
649                    script[i + 3],
650                    script[i + 4],
651                ]) as usize;
652                i += 5 + len;
653            }
654            _ => {
655                if op == opcode {
656                    return true;
657                }
658                i += 1;
659            }
660        }
661    }
662    false
663}
664
665fn script_has_checkmultisig(script_pubkey: &[u8]) -> bool {
666    script_contains_executable_opcode(script_pubkey, OP_CHECKMULTISIG)
667        || script_contains_executable_opcode(script_pubkey, OP_CHECKMULTISIGVERIFY)
668}
669
670/// BIP147: NULLDUMMY Enforcement
671///
672/// Enforces that OP_CHECKMULTISIG dummy elements are empty.
673/// Mathematical specification: Orange Paper Section 5.4.5
674///
675/// **BIP147Check**: ๐•Š ร— ๐•Š ร— โ„• โ†’ {valid, invalid}
676///
677/// Activation Heights:
678/// - Mainnet: Block 481,824 (SegWit activation)
679/// - Testnet: Block 834,624
680/// - Regtest: Block 0 (always active)
681#[cfg_attr(feature = "spec-lock", spec_locked("5.4.5", "BIP147Check"))]
682pub fn check_bip147(
683    script_sig: &[u8],
684    script_pubkey: &[u8],
685    height: Natural,
686    activation: &impl IsForkActive,
687) -> Result<bool> {
688    if !activation.is_fork_active(ForkId::Bip147, height) {
689        return Ok(true);
690    }
691
692    // BIP147 applies only when scriptPubKey executes OP_CHECKMULTISIG(VERIFY).
693    if !script_has_checkmultisig(script_pubkey) {
694        return Ok(true);
695    }
696
697    // BIP147: first stack item consumed by OP_CHECKMULTISIG (first push in scriptSig) must be empty.
698    is_null_dummy(script_sig)
699}
700
701/// BIP147: The dummy element is the first element consumed by OP_CHECKMULTISIG.
702/// It must be empty (OP_0) after activation.
703fn is_null_dummy(script_sig: &[u8]) -> Result<bool> {
704    if script_sig.is_empty() {
705        return Ok(false);
706    }
707
708    let mut pc = 0usize;
709    let mut first_push_empty = false;
710    let mut saw_push = false;
711
712    while pc < script_sig.len() {
713        let opcode = script_sig[pc];
714        pc += 1;
715        match opcode {
716            OP_0 => {
717                if !saw_push {
718                    first_push_empty = true;
719                    saw_push = true;
720                }
721            }
722            0x01..=0x4b => {
723                let len = opcode as usize;
724                if pc + len > script_sig.len() {
725                    return Ok(false);
726                }
727                if !saw_push {
728                    first_push_empty = len == 0;
729                    saw_push = true;
730                }
731                pc += len;
732            }
733            OP_PUSHDATA1 => {
734                if pc >= script_sig.len() {
735                    return Ok(false);
736                }
737                let len = script_sig[pc] as usize;
738                pc += 1;
739                if pc + len > script_sig.len() {
740                    return Ok(false);
741                }
742                if !saw_push {
743                    first_push_empty = len == 0;
744                    saw_push = true;
745                }
746                pc += len;
747            }
748            OP_PUSHDATA2 => {
749                if pc + 2 > script_sig.len() {
750                    return Ok(false);
751                }
752                let len = u16::from_le_bytes([script_sig[pc], script_sig[pc + 1]]) as usize;
753                pc += 2;
754                if pc + len > script_sig.len() {
755                    return Ok(false);
756                }
757                if !saw_push {
758                    first_push_empty = len == 0;
759                    saw_push = true;
760                }
761                pc += len;
762            }
763            OP_PUSHDATA4 => {
764                if pc + 4 > script_sig.len() {
765                    return Ok(false);
766                }
767                let len = u32::from_le_bytes([
768                    script_sig[pc],
769                    script_sig[pc + 1],
770                    script_sig[pc + 2],
771                    script_sig[pc + 3],
772                ]) as usize;
773                pc += 4;
774                if pc + len > script_sig.len() {
775                    return Ok(false);
776                }
777                if !saw_push {
778                    first_push_empty = len == 0;
779                    saw_push = true;
780                }
781                pc += len;
782            }
783            _ => return Ok(false),
784        }
785    }
786
787    Ok(first_push_empty)
788}
789
790/// Network type for BIP147 activation heights
791#[derive(Debug, Clone, Copy, PartialEq, Eq)]
792pub enum Bip147Network {
793    Mainnet,
794    Testnet,
795    Regtest,
796}
797
798#[cfg(test)]
799mod tests {
800    use super::*;
801    use crate::constants::BIP147_ACTIVATION_MAINNET;
802
803    use crate::opcodes::{OP_0, OP_1, OP_2, OP_CHECKMULTISIG, OP_CHECKSIG};
804
805    #[test]
806    fn bip34_height_1_through_16_is_a_single_opcode() {
807        // Testnet4 block 1 coinbase scriptSig (mempool.space): OP_1, OP_0, then a 6-byte push.
808        let block1: &[u8] = &[0x51, 0x00, 0x06, 0x2f, 0x40, 0x77, 0x69, 0x7a, 0x2f];
809        assert!(script_sig_starts_with_height(block1, 1));
810        assert!(!script_sig_starts_with_height(&[0x01, 0x01], 1));
811        assert!(script_sig_starts_with_height(&[0x52, 0x00], 2));
812        assert!(script_sig_starts_with_height(&[0x60], 16));
813        // Height 17 is a one-byte push, not OP_17.
814        assert!(script_sig_starts_with_height(&[0x01, 0x11, 0xff], 17));
815        assert!(!script_sig_starts_with_height(&[0x51], 17));
816        assert!(script_sig_starts_with_height(&[0x00], 0));
817        assert_eq!(OP_1, 0x51);
818    }
819
820    #[test]
821    fn encode_bip34_coinbase_script_satisfies_the_prefix_check() {
822        for height in [0u64, 1, 16, 17, 127, 128, 150, 255, 256, 210_000] {
823            let script = encode_bip34_coinbase_script(height);
824            assert!(
825                script_sig_starts_with_height(&script, height),
826                "height {height} script {script:?}"
827            );
828            assert!(
829                (2..=100).contains(&script.len()),
830                "height {height} len {}",
831                script.len()
832            );
833        }
834        assert_eq!(encode_bip34_coinbase_script(0), vec![0x00, 0xff]);
835        assert_eq!(encode_bip34_coinbase_script(1), vec![0x51, 0xff]);
836        assert_eq!(encode_bip34_coinbase_script(16), vec![0x60, 0xff]);
837        assert_eq!(encode_bip34_coinbase_script(17), vec![0x01, 0x11]);
838        assert_eq!(encode_bip34_coinbase_script(127), vec![0x01, 0x7f]);
839        assert_eq!(encode_bip34_coinbase_script(128), vec![0x02, 0x80, 0x00]);
840        assert_eq!(encode_bip34_coinbase_script(150), vec![0x02, 0x96, 0x00]);
841        assert_eq!(encode_bip34_coinbase_script(255), vec![0x02, 0xff, 0x00]);
842        assert_eq!(encode_bip34_coinbase_script(256), vec![0x02, 0x00, 0x01]);
843        assert_eq!(
844            encode_bip34_coinbase_script(210_000),
845            vec![0x03, 0x50, 0x34, 0x03]
846        );
847    }
848
849    #[test]
850    fn test_bip30_repeat_hashes_are_display_ids_reversed() {
851        fn digest(display: &str) -> Hash {
852            let mut bytes = hex::decode(display).unwrap();
853            bytes.reverse();
854            bytes.try_into().unwrap()
855        }
856        assert!(is_bip30_repeat_block(
857            91_842,
858            &digest("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")
859        ));
860        assert!(is_bip30_repeat_block(
861            91_880,
862            &digest("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")
863        ));
864        assert!(!is_bip30_repeat_block(91_842, &[0; 32]));
865        assert!(!is_bip30_repeat_block(
866            91_722,
867            &digest("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")
868        ));
869        assert!(is_known_bip34_block(
870            Network::Mainnet,
871            crate::constants::BIP34_ACTIVATION_MAINNET,
872            &digest("000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8")
873        ));
874        assert!(!is_known_bip34_block(
875            Network::Mainnet,
876            crate::constants::BIP34_ACTIVATION_MAINNET,
877            &[0; 32]
878        ));
879    }
880
881    #[test]
882    fn test_bip30_basic() {
883        // Test that BIP30 check passes for new coinbase
884        let transactions: Vec<Transaction> = vec![Transaction {
885            version: 1,
886            inputs: vec![TransactionInput {
887                prevout: OutPoint {
888                    hash: [0; 32],
889                    index: 0xffffffff,
890                },
891                script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
892                sequence: 0xffffffff,
893            }]
894            .into(),
895            outputs: vec![TransactionOutput {
896                value: 50_0000_0000,
897                script_pubkey: vec![],
898            }]
899            .into(),
900            lock_time: 0,
901        }];
902        let block = Block {
903            header: BlockHeader {
904                version: 1,
905                prev_block_hash: [0; 32],
906                merkle_root: [0; 32],
907                timestamp: 1231006505,
908                bits: 0x1d00ffff,
909                nonce: 0,
910            },
911            transactions: transactions.into_boxed_slice(),
912        };
913
914        let utxo_set = UtxoSet::default();
915        let result = check_bip30_network(
916            &block,
917            &utxo_set,
918            None,
919            0,
920            crate::types::Network::Mainnet,
921            None,
922        )
923        .unwrap();
924        assert!(result, "BIP30 should pass for new coinbase");
925    }
926
927    #[test]
928    fn test_bip34_before_activation() {
929        let transactions: Vec<Transaction> = vec![Transaction {
930            version: 1,
931            inputs: vec![TransactionInput {
932                prevout: OutPoint {
933                    hash: [0; 32],
934                    index: 0xffffffff,
935                },
936                script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
937                sequence: 0xffffffff,
938            }]
939            .into(),
940            outputs: vec![TransactionOutput {
941                value: 50_0000_0000,
942                script_pubkey: vec![],
943            }]
944            .into(),
945            lock_time: 0,
946        }];
947        let block = Block {
948            header: BlockHeader {
949                version: 1,
950                prev_block_hash: [0; 32],
951                merkle_root: [0; 32],
952                timestamp: 1231006505,
953                bits: 0x1d00ffff,
954                nonce: 0,
955            },
956            transactions: transactions.into_boxed_slice(),
957        };
958
959        // Before activation, BIP34 should pass
960        let result = check_bip34_network(&block, 100_000, crate::types::Network::Mainnet).unwrap();
961        assert!(result, "BIP34 should pass before activation");
962    }
963
964    #[test]
965    fn test_bip34_after_activation() {
966        let height = crate::constants::BIP34_ACTIVATION_MAINNET;
967        let transactions: Vec<Transaction> = vec![Transaction {
968            version: 1,
969            inputs: vec![TransactionInput {
970                prevout: OutPoint {
971                    hash: [0; 32],
972                    index: 0xffffffff,
973                },
974                // Height encoded as CScriptNum: 0x03 (push 3 bytes) + height in little-endian
975                script_sig: vec![
976                    0x03,
977                    (height & 0xff) as u8,
978                    ((height >> 8) & 0xff) as u8,
979                    ((height >> 16) & 0xff) as u8,
980                ],
981                sequence: 0xffffffff,
982            }]
983            .into(),
984            outputs: vec![TransactionOutput {
985                value: 50_0000_0000,
986                script_pubkey: vec![],
987            }]
988            .into(),
989            lock_time: 0,
990        }];
991        let block = Block {
992            header: BlockHeader {
993                version: 2, // BIP34 requires version >= 2
994                prev_block_hash: [0; 32],
995                merkle_root: [0; 32],
996                timestamp: 1231006505,
997                bits: 0x1d00ffff,
998                nonce: 0,
999            },
1000            transactions: transactions.into_boxed_slice(),
1001        };
1002
1003        let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
1004        assert!(result, "BIP34 should pass with correct height encoding");
1005    }
1006
1007    #[test]
1008    fn test_bip90_version_enforcement() {
1009        // Test version 1 before BIP34 activation
1010        let result = check_bip90_network(1, 100_000, crate::types::Network::Mainnet).unwrap();
1011        assert!(result, "Version 1 should be valid before BIP34");
1012
1013        // Test version 1 after BIP34 activation (should fail)
1014        let result = check_bip90_network(
1015            1,
1016            crate::constants::BIP34_ACTIVATION_MAINNET,
1017            crate::types::Network::Mainnet,
1018        )
1019        .unwrap();
1020        assert!(
1021            !result,
1022            "Version 1 should be invalid after BIP34 activation"
1023        );
1024
1025        // Test version 2 after BIP34 activation (should pass)
1026        let result = check_bip90_network(
1027            2,
1028            crate::constants::BIP34_ACTIVATION_MAINNET,
1029            crate::types::Network::Mainnet,
1030        )
1031        .unwrap();
1032        assert!(result, "Version 2 should be valid after BIP34 activation");
1033
1034        // Test version 2 after BIP66 activation (should fail)
1035        // BIP66 activates at block 363,725, so we test at that height
1036        let result = check_bip90_network(2, 363_725, crate::types::Network::Mainnet).unwrap();
1037        assert!(
1038            !result,
1039            "Version 2 should be invalid after BIP66 activation"
1040        );
1041
1042        // Test version 3 after BIP66 activation (should pass)
1043        // BIP66 activates at block 363,725, so we test at that height
1044        let result = check_bip90_network(3, 363_725, crate::types::Network::Mainnet).unwrap();
1045        assert!(result, "Version 3 should be valid after BIP66 activation");
1046    }
1047
1048    #[test]
1049    fn test_bip30_duplicate_coinbase() {
1050        use crate::block::calculate_tx_id;
1051
1052        // Create a coinbase transaction
1053        let coinbase_tx = Transaction {
1054            version: 1,
1055            inputs: vec![TransactionInput {
1056                prevout: OutPoint {
1057                    hash: [0; 32],
1058                    index: 0xffffffff,
1059                },
1060                script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
1061                sequence: 0xffffffff,
1062            }]
1063            .into(),
1064            outputs: vec![TransactionOutput {
1065                value: 50_0000_0000,
1066                script_pubkey: vec![],
1067            }]
1068            .into(),
1069            lock_time: 0,
1070        };
1071
1072        let txid = calculate_tx_id(&coinbase_tx);
1073
1074        // Create UTXO set with a UTXO from this coinbase
1075        let mut utxo_set = UtxoSet::default();
1076        utxo_set.insert(
1077            OutPoint {
1078                hash: txid,
1079                index: 0,
1080            },
1081            std::sync::Arc::new(UTXO {
1082                value: 50_0000_0000,
1083                script_pubkey: vec![].into(),
1084                height: 0,
1085                is_coinbase: false,
1086            }),
1087        );
1088
1089        // Create block with same coinbase (duplicate)
1090        let transactions: Vec<Transaction> = vec![coinbase_tx];
1091        let block = Block {
1092            header: BlockHeader {
1093                version: 1,
1094                prev_block_hash: [0; 32],
1095                merkle_root: [0; 32],
1096                timestamp: 1231006505,
1097                bits: 0x1d00ffff,
1098                nonce: 0,
1099            },
1100            transactions: transactions.into_boxed_slice(),
1101        };
1102
1103        // BIP30 should fail for duplicate coinbase
1104        let result = check_bip30_network(
1105            &block,
1106            &utxo_set,
1107            None,
1108            0,
1109            crate::types::Network::Mainnet,
1110            None,
1111        )
1112        .unwrap();
1113        assert!(!result, "BIP30 should fail for duplicate coinbase");
1114    }
1115
1116    #[test]
1117    fn test_bip34_invalid_height() {
1118        let height = crate::constants::BIP34_ACTIVATION_MAINNET;
1119        let transactions: Vec<Transaction> = vec![Transaction {
1120            version: 1,
1121            inputs: vec![TransactionInput {
1122                prevout: OutPoint {
1123                    hash: [0; 32],
1124                    index: 0xffffffff,
1125                },
1126                // Wrong height encoding
1127                script_sig: vec![0x03, 0x00, 0x00, 0x00], // Height 0 instead of activation height
1128                sequence: 0xffffffff,
1129            }]
1130            .into(),
1131            outputs: vec![TransactionOutput {
1132                value: 50_0000_0000,
1133                script_pubkey: vec![],
1134            }]
1135            .into(),
1136            lock_time: 0,
1137        }];
1138        let block = Block {
1139            header: BlockHeader {
1140                version: 2,
1141                prev_block_hash: [0; 32],
1142                merkle_root: [0; 32],
1143                timestamp: 1231006505,
1144                bits: 0x1d00ffff,
1145                nonce: 0,
1146            },
1147            transactions: transactions.into_boxed_slice(),
1148        };
1149
1150        // BIP34 should fail with wrong height
1151        let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
1152        assert!(!result, "BIP34 should fail with incorrect height encoding");
1153    }
1154
1155    #[test]
1156    fn test_bip66_strict_der() {
1157        use crate::constants::BIP66_ACTIVATION_MAINNET;
1158        use crate::types::Network;
1159
1160        // 30 06 02 01 01 02 01 01 01 โ€” tag, length 6, R = 1, S = 1, sighash 01.
1161        let accept: Vec<u8> = vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01];
1162        assert_eq!(
1163            check_bip66_network(&accept, BIP66_ACTIVATION_MAINNET, Network::Mainnet).unwrap(),
1164            true,
1165            "9-byte strict DER must pass at activation"
1166        );
1167
1168        let mut high_bit_r = accept.clone();
1169        high_bit_r[4] = 0x81;
1170        // R length 2, leading 0x00, next byte high bit clear.
1171        let leading_zero_r: Vec<u8> =
1172            vec![0x30, 0x07, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x01, 0x01];
1173        let mut bad_length = accept.clone();
1174        bad_length[1] = 0x05;
1175        let rejects = [
1176            vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01],
1177            vec![0u8; 74],
1178            high_bit_r.clone(),
1179            leading_zero_r,
1180            bad_length,
1181        ];
1182        for sig in &rejects {
1183            assert_eq!(
1184                check_bip66_network(sig, BIP66_ACTIVATION_MAINNET, Network::Mainnet).unwrap(),
1185                false,
1186                "non-strict encoding must fail at activation: {sig:?}"
1187            );
1188        }
1189
1190        assert_eq!(
1191            check_bip66_network(&high_bit_r, BIP66_ACTIVATION_MAINNET - 1, Network::Mainnet)
1192                .unwrap(),
1193            true,
1194            "pre-activation must accept a non-strict signature"
1195        );
1196    }
1197
1198    #[test]
1199    fn test_bip147_skips_checkmultisig_byte_in_push_data() {
1200        // Push data contains 0xae; executable path is OP_CHECKSIG only.
1201        let script_pubkey = vec![0x01, OP_CHECKMULTISIG, OP_CHECKSIG];
1202        let script_sig = vec![1, OP_1];
1203        let result = check_bip147_network(
1204            &script_sig,
1205            &script_pubkey,
1206            BIP147_ACTIVATION_MAINNET,
1207            Bip147Network::Mainnet,
1208        )
1209        .unwrap();
1210        assert!(
1211            result,
1212            "BIP147 must not apply when CHECKMULTISIG is only in push data"
1213        );
1214    }
1215
1216    #[test]
1217    fn test_bip147_null_dummy() {
1218        // Executable OP_CHECKMULTISIG (matches bip_validation_suite fixture).
1219        let script_pubkey = vec![OP_CHECKMULTISIG];
1220        let script_sig_valid = vec![OP_0];
1221        let result = check_bip147_network(
1222            &script_sig_valid,
1223            &script_pubkey,
1224            BIP147_ACTIVATION_MAINNET,
1225            Bip147Network::Mainnet,
1226        )
1227        .unwrap();
1228        assert!(result, "BIP147 should pass with NULLDUMMY");
1229
1230        let script_sig_invalid = vec![1, OP_1];
1231        let result = check_bip147_network(
1232            &script_sig_invalid,
1233            &script_pubkey,
1234            BIP147_ACTIVATION_MAINNET,
1235            Bip147Network::Mainnet,
1236        )
1237        .unwrap();
1238        assert!(!result, "BIP147 should fail without NULLDUMMY");
1239
1240        // Before activation, should always pass
1241        let result = check_bip147_network(
1242            &script_sig_invalid,
1243            &script_pubkey,
1244            100_000,
1245            Bip147Network::Mainnet,
1246        )
1247        .unwrap();
1248        assert!(result, "BIP147 should pass before activation");
1249    }
1250}