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