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