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