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