1use crate::activation::IsForkActive;
9use crate::block::calculate_tx_id;
10use crate::error::{ConsensusError, Result};
11use crate::opcodes::{
12 OP_0, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4,
13};
14use crate::transaction::is_coinbase;
15use crate::types::*;
16use blvm_spec_lock::spec_locked;
17
18#[cfg(feature = "production")]
22pub type Bip30Index = rustc_hash::FxHashMap<crate::types::Hash, usize>;
23#[cfg(not(feature = "production"))]
24pub type Bip30Index = std::collections::HashMap<crate::types::Hash, usize>;
25
26pub fn build_bip30_index(utxo_set: &UtxoSet) -> Bip30Index {
29 let mut index = Bip30Index::default();
30 for (outpoint, utxo) in utxo_set.iter() {
31 if utxo.is_coinbase {
32 *index.entry(outpoint.hash).or_insert(0) += 1;
33 }
34 }
35 index
36}
37
38#[spec_locked("5.4.1", "BIP30Check")]
58pub fn check_bip30(
59 block: &Block,
60 utxo_set: &UtxoSet,
61 bip30_index: Option<&Bip30Index>,
62 height: Natural,
63 activation: &impl IsForkActive,
64 coinbase_txid: Option<&Hash>,
65) -> Result<bool> {
66 if !activation.is_fork_active(ForkId::Bip30, height) {
67 return Ok(true);
68 }
69 let coinbase = block.transactions.first();
71
72 if let Some(tx) = coinbase {
73 if !is_coinbase(tx) {
74 return Ok(true);
76 }
77
78 let txid = coinbase_txid
79 .copied()
80 .unwrap_or_else(|| calculate_tx_id(tx));
81
82 if let Some(index) = bip30_index {
84 if index.get(&txid).is_some_and(|&c| c > 0) {
85 return Ok(false);
86 }
87 return Ok(true);
88 }
89
90 for (outpoint, _utxo) in utxo_set.iter() {
93 if outpoint.hash == txid {
94 return Ok(false);
95 }
96 }
97 }
98
99 Ok(true)
100}
101
102#[spec_locked("5.4.2", "BIP34Check")]
115pub fn check_bip34(block: &Block, height: Natural, activation: &impl IsForkActive) -> Result<bool> {
116 if !activation.is_fork_active(ForkId::Bip34, height) {
117 return Ok(true);
118 }
119
120 let coinbase = block.transactions.first();
122
123 if let Some(tx) = coinbase {
124 if !is_coinbase(tx) {
125 return Ok(true);
126 }
127
128 let script_sig = &tx.inputs[0].script_sig;
131
132 if script_sig.is_empty() {
133 return Ok(false);
134 }
135
136 let extracted_height = extract_height_from_script_sig(script_sig)?;
148
149 if extracted_height != height {
150 return Ok(false);
151 }
152 }
153
154 Ok(true)
155}
156
157#[spec_locked("5.4.9", "IsBip54ActiveAt")]
164pub fn is_bip54_active_at(
165 height: Natural,
166 network: crate::types::Network,
167 activation_override: Option<u64>,
168) -> bool {
169 let activation = match activation_override {
170 Some(h) => h,
171 None => match network {
172 crate::types::Network::Mainnet => crate::constants::BIP54_ACTIVATION_MAINNET,
173 crate::types::Network::Testnet => crate::constants::BIP54_ACTIVATION_TESTNET,
174 crate::types::Network::Regtest | crate::types::Network::Signet => {
175 crate::constants::BIP54_ACTIVATION_REGTEST
176 }
177 },
178 };
179 height >= activation
180}
181
182#[spec_locked("5.4.9", "IsBip54Active")]
189pub fn is_bip54_active(height: Natural, network: crate::types::Network) -> bool {
190 is_bip54_active_at(height, network, None)
191}
192
193#[spec_locked("5.4.9", "BIP54TimewarpCheck")]
195pub fn check_bip54_timewarp(
196 header: &BlockHeader,
197 height: Natural,
198 boundary: Option<&Bip54BoundaryTimestamps>,
199 bip54_active: bool,
200) -> bool {
201 if !bip54_active {
202 return true;
203 }
204 let rem = height % 2016;
205 if rem == 2015 {
206 let Some(b) = boundary else {
207 return false;
208 };
209 header.timestamp >= b.timestamp_n_minus_2015
210 } else if rem == 0 {
211 let Some(b) = boundary else {
212 return false;
213 };
214 const TWOHOURS: u64 = 7200;
215 let min_ts = b.timestamp_n_minus_1.saturating_sub(TWOHOURS);
216 header.timestamp >= min_ts
217 } else {
218 true
219 }
220}
221
222#[spec_locked("5.4.9", "CheckBip54TxStrippedSize")]
224pub fn check_bip54_tx_stripped_size(tx: &Transaction) -> bool {
225 is_coinbase(tx) || crate::transaction::calculate_transaction_size(tx) != 64
226}
227
228#[spec_locked("5.4.9", "CheckBip54SigOpLimit")]
230pub fn check_bip54_sigop_limit<U: crate::utxo_overlay::UtxoLookup>(
231 bip54_active: bool,
232 tx: &Transaction,
233 utxo_lookup: &U,
234 wits: Option<&[Witness]>,
235 tx_flags: u32,
236) -> Result<Option<&'static str>> {
237 if !bip54_active || is_coinbase(tx) {
238 return Ok(None);
239 }
240 let sigop_count =
241 crate::sigop::get_transaction_sigop_count_for_bip54(tx, utxo_lookup, wits, tx_flags)?;
242 if sigop_count > crate::constants::BIP54_MAX_SIGOPS_PER_TX {
243 return Ok(Some("BIP54: Transaction sigop count exceeds 2500"));
244 }
245 Ok(None)
246}
247
248#[spec_locked("5.4.9", "CheckBip54Coinbase")]
252pub fn check_bip54_coinbase(coinbase: &Transaction, height: Natural) -> bool {
253 let required_lock_time = height.saturating_sub(13);
254 if coinbase.lock_time != required_lock_time {
255 return false;
256 }
257 if coinbase.inputs.is_empty() {
258 return false;
259 }
260 if coinbase.inputs[0].sequence == 0xffff_ffff {
261 return false;
262 }
263 true
264}
265
266fn extract_height_from_script_sig(script_sig: &[u8]) -> Result<Natural> {
268 if script_sig.is_empty() {
269 return Err(ConsensusError::BlockValidation(
270 "Empty coinbase scriptSig".into(),
271 ));
272 }
273
274 let first_byte = script_sig[0];
275
276 if first_byte == 0x00 {
280 return Ok(0);
281 }
282
283 if (1..=0x4b).contains(&first_byte) {
285 let len = first_byte as usize;
286 if script_sig.len() < 1 + len {
287 return Err(ConsensusError::BlockValidation(
288 "Invalid scriptSig length".into(),
289 ));
290 }
291
292 let height_bytes = &script_sig[1..1 + len];
293
294 let mut height = 0u64;
296 for (i, &byte) in height_bytes.iter().enumerate() {
297 if i >= 8 {
298 return Err(ConsensusError::BlockValidation(
299 "Height value too large".into(),
300 ));
301 }
302 height |= (byte as u64) << (i * 8);
303 }
304
305 return Ok(height);
306 }
307
308 if first_byte == 0x4c {
310 if script_sig.len() < 2 {
311 return Err(ConsensusError::BlockValidation(
312 "Invalid OP_PUSHDATA1".into(),
313 ));
314 }
315 let len = script_sig[1] as usize;
316 if script_sig.len() < 2 + len {
317 return Err(ConsensusError::BlockValidation(
318 "Invalid scriptSig length".into(),
319 ));
320 }
321
322 let height_bytes = &script_sig[2..2 + len];
323
324 let mut height = 0u64;
325 for (i, &byte) in height_bytes.iter().enumerate() {
326 if i >= 8 {
327 return Err(ConsensusError::BlockValidation(
328 "Height value too large".into(),
329 ));
330 }
331 height |= (byte as u64) << (i * 8);
332 }
333
334 return Ok(height);
335 }
336
337 if first_byte == 0x4d {
339 if script_sig.len() < 3 {
340 return Err(ConsensusError::BlockValidation(
341 "Invalid OP_PUSHDATA2".into(),
342 ));
343 }
344 let len = u16::from_le_bytes([script_sig[1], script_sig[2]]) as usize;
345 if script_sig.len() < 3 + len {
346 return Err(ConsensusError::BlockValidation(
347 "Invalid scriptSig length".into(),
348 ));
349 }
350
351 let height_bytes = &script_sig[3..3 + len];
352
353 let mut height = 0u64;
354 for (i, &byte) in height_bytes.iter().enumerate() {
355 if i >= 8 {
356 return Err(ConsensusError::BlockValidation(
357 "Height value too large".into(),
358 ));
359 }
360 height |= (byte as u64) << (i * 8);
361 }
362
363 return Ok(height);
364 }
365
366 Err(ConsensusError::BlockValidation(
367 "Invalid height encoding in scriptSig".into(),
368 ))
369}
370
371#[spec_locked("5.4.3", "BIP66Check")]
385pub fn check_bip66(
386 signature: &[u8],
387 height: Natural,
388 activation: &impl IsForkActive,
389) -> Result<bool> {
390 if !activation.is_fork_active(ForkId::Bip66, height) {
391 return Ok(true);
392 }
393
394 is_strict_der(signature)
398}
399
400fn is_strict_der(signature: &[u8]) -> Result<bool> {
408 if signature.len() < 9 {
422 return Ok(false);
423 }
424 if signature.len() > 73 {
425 return Ok(false);
426 }
427
428 if signature[0] != 0x30 {
430 return Ok(false);
431 }
432
433 if signature[1] != (signature.len() - 3) as u8 {
435 return Ok(false);
436 }
437
438 let len_r = signature[3] as usize;
440
441 if 5 + len_r >= signature.len() {
443 return Ok(false);
444 }
445
446 let len_s = signature[5 + len_r] as usize;
448
449 if (len_r + len_s + 7) != signature.len() {
452 return Ok(false);
453 }
454
455 if signature[2] != 0x02 {
457 return Ok(false);
458 }
459
460 if len_r == 0 {
462 return Ok(false);
463 }
464
465 if (signature[4] & 0x80) != 0 {
467 return Ok(false);
468 }
469
470 if len_r > 1 && signature[4] == 0x00 && (signature[5] & 0x80) == 0 {
473 return Ok(false);
474 }
475
476 if signature[len_r + 4] != 0x02 {
478 return Ok(false);
479 }
480
481 if len_s == 0 {
483 return Ok(false);
484 }
485
486 if (signature[len_r + 6] & 0x80) != 0 {
488 return Ok(false);
489 }
490
491 if len_s > 1 && signature[len_r + 6] == 0x00 && (signature[len_r + 7] & 0x80) == 0 {
494 return Ok(false);
495 }
496
497 Ok(true)
498}
499
500#[spec_locked("5.4.4", "BIP90Check")]
514pub fn check_bip90(
515 block_version: i64,
516 height: Natural,
517 activation: &impl IsForkActive,
518) -> Result<bool> {
519 if activation.is_fork_active(ForkId::Bip34, height) && block_version < 2 {
520 return Ok(false);
521 }
522 if activation.is_fork_active(ForkId::Bip66, height) && block_version < 3 {
523 return Ok(false);
524 }
525 if activation.is_fork_active(ForkId::Bip65, height) && block_version < 4 {
526 return Ok(false);
527 }
528
529 Ok(true)
530}
531
532pub fn check_bip30_network(
534 block: &Block,
535 utxo_set: &UtxoSet,
536 bip30_index: Option<&Bip30Index>,
537 height: Natural,
538 network: crate::types::Network,
539 coinbase_txid: Option<&Hash>,
540) -> Result<bool> {
541 let table = crate::activation::ForkActivationTable::from_network(network);
542 check_bip30(block, utxo_set, bip30_index, height, &table, coinbase_txid)
543}
544
545pub fn check_bip34_network(
547 block: &Block,
548 height: Natural,
549 network: crate::types::Network,
550) -> Result<bool> {
551 let table = crate::activation::ForkActivationTable::from_network(network);
552 check_bip34(block, height, &table)
553}
554
555pub fn check_bip66_network(
557 signature: &[u8],
558 height: Natural,
559 network: crate::types::Network,
560) -> Result<bool> {
561 let table = crate::activation::ForkActivationTable::from_network(network);
562 check_bip66(signature, height, &table)
563}
564
565pub fn check_bip90_network(
567 block_version: i64,
568 height: Natural,
569 network: crate::types::Network,
570) -> Result<bool> {
571 let table = crate::activation::ForkActivationTable::from_network(network);
572 check_bip90(block_version, height, &table)
573}
574
575pub fn check_bip147_network(
577 script_sig: &[u8],
578 script_pubkey: &[u8],
579 height: Natural,
580 network: Bip147Network,
581) -> Result<bool> {
582 let table = match network {
583 Bip147Network::Mainnet => {
584 crate::activation::ForkActivationTable::from_network(crate::types::Network::Mainnet)
585 }
586 Bip147Network::Testnet => {
587 crate::activation::ForkActivationTable::from_network(crate::types::Network::Testnet)
588 }
589 Bip147Network::Regtest => {
590 crate::activation::ForkActivationTable::from_network(crate::types::Network::Regtest)
591 }
592 };
593 check_bip147(script_sig, script_pubkey, height, &table)
594}
595
596pub(crate) fn script_contains_executable_opcode(script: &[u8], opcode: u8) -> bool {
598 let mut i = 0;
599 while i < script.len() {
600 let op = script[i];
601 if op > 0 && op < OP_PUSHDATA1 {
602 i += 1 + op as usize;
603 continue;
604 }
605 match op {
606 OP_PUSHDATA1 => {
607 if i + 1 >= script.len() {
608 break;
609 }
610 let len = script[i + 1] as usize;
611 i += 2 + len;
612 }
613 OP_PUSHDATA2 => {
614 if i + 2 >= script.len() {
615 break;
616 }
617 let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
618 i += 3 + len;
619 }
620 OP_PUSHDATA4 => {
621 if i + 4 >= script.len() {
622 break;
623 }
624 let len = u32::from_le_bytes([
625 script[i + 1],
626 script[i + 2],
627 script[i + 3],
628 script[i + 4],
629 ]) as usize;
630 i += 5 + len;
631 }
632 _ => {
633 if op == opcode {
634 return true;
635 }
636 i += 1;
637 }
638 }
639 }
640 false
641}
642
643fn script_has_checkmultisig(script_pubkey: &[u8]) -> bool {
644 script_contains_executable_opcode(script_pubkey, OP_CHECKMULTISIG)
645 || script_contains_executable_opcode(script_pubkey, OP_CHECKMULTISIGVERIFY)
646}
647
648#[spec_locked("5.4.5", "BIP147Check")]
660pub fn check_bip147(
661 script_sig: &[u8],
662 script_pubkey: &[u8],
663 height: Natural,
664 activation: &impl IsForkActive,
665) -> Result<bool> {
666 if !activation.is_fork_active(ForkId::Bip147, height) {
667 return Ok(true);
668 }
669
670 if !script_has_checkmultisig(script_pubkey) {
672 return Ok(true);
673 }
674
675 is_null_dummy(script_sig)
677}
678
679fn is_null_dummy(script_sig: &[u8]) -> Result<bool> {
682 if script_sig.is_empty() {
683 return Ok(false);
684 }
685
686 let mut pc = 0usize;
687 let mut first_push_empty = false;
688 let mut saw_push = false;
689
690 while pc < script_sig.len() {
691 let opcode = script_sig[pc];
692 pc += 1;
693 match opcode {
694 OP_0 => {
695 if !saw_push {
696 first_push_empty = true;
697 saw_push = true;
698 }
699 }
700 0x01..=0x4b => {
701 let len = opcode as usize;
702 if pc + len > script_sig.len() {
703 return Ok(false);
704 }
705 if !saw_push {
706 first_push_empty = len == 0;
707 saw_push = true;
708 }
709 pc += len;
710 }
711 OP_PUSHDATA1 => {
712 if pc >= script_sig.len() {
713 return Ok(false);
714 }
715 let len = script_sig[pc] as usize;
716 pc += 1;
717 if pc + len > script_sig.len() {
718 return Ok(false);
719 }
720 if !saw_push {
721 first_push_empty = len == 0;
722 saw_push = true;
723 }
724 pc += len;
725 }
726 OP_PUSHDATA2 => {
727 if pc + 2 > script_sig.len() {
728 return Ok(false);
729 }
730 let len = u16::from_le_bytes([script_sig[pc], script_sig[pc + 1]]) as usize;
731 pc += 2;
732 if pc + len > script_sig.len() {
733 return Ok(false);
734 }
735 if !saw_push {
736 first_push_empty = len == 0;
737 saw_push = true;
738 }
739 pc += len;
740 }
741 OP_PUSHDATA4 => {
742 if pc + 4 > script_sig.len() {
743 return Ok(false);
744 }
745 let len = u32::from_le_bytes([
746 script_sig[pc],
747 script_sig[pc + 1],
748 script_sig[pc + 2],
749 script_sig[pc + 3],
750 ]) as usize;
751 pc += 4;
752 if pc + len > script_sig.len() {
753 return Ok(false);
754 }
755 if !saw_push {
756 first_push_empty = len == 0;
757 saw_push = true;
758 }
759 pc += len;
760 }
761 _ => return Ok(false),
762 }
763 }
764
765 Ok(first_push_empty)
766}
767
768#[derive(Debug, Clone, Copy, PartialEq, Eq)]
770pub enum Bip147Network {
771 Mainnet,
772 Testnet,
773 Regtest,
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779 use crate::constants::{BIP66_ACTIVATION_MAINNET, BIP147_ACTIVATION_MAINNET};
780
781 use crate::opcodes::{OP_0, OP_1, OP_2, OP_CHECKMULTISIG, OP_CHECKSIG};
782
783 #[test]
784 fn test_bip30_basic() {
785 let transactions: Vec<Transaction> = vec![Transaction {
787 version: 1,
788 inputs: vec![TransactionInput {
789 prevout: OutPoint {
790 hash: [0; 32],
791 index: 0xffffffff,
792 },
793 script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
794 sequence: 0xffffffff,
795 }]
796 .into(),
797 outputs: vec![TransactionOutput {
798 value: 50_0000_0000,
799 script_pubkey: vec![],
800 }]
801 .into(),
802 lock_time: 0,
803 }];
804 let block = Block {
805 header: BlockHeader {
806 version: 1,
807 prev_block_hash: [0; 32],
808 merkle_root: [0; 32],
809 timestamp: 1231006505,
810 bits: 0x1d00ffff,
811 nonce: 0,
812 },
813 transactions: transactions.into_boxed_slice(),
814 };
815
816 let utxo_set = UtxoSet::default();
817 let result = check_bip30_network(
818 &block,
819 &utxo_set,
820 None,
821 0,
822 crate::types::Network::Mainnet,
823 None,
824 )
825 .unwrap();
826 assert!(result, "BIP30 should pass for new coinbase");
827 }
828
829 #[test]
830 fn test_bip34_before_activation() {
831 let transactions: Vec<Transaction> = vec![Transaction {
832 version: 1,
833 inputs: vec![TransactionInput {
834 prevout: OutPoint {
835 hash: [0; 32],
836 index: 0xffffffff,
837 },
838 script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
839 sequence: 0xffffffff,
840 }]
841 .into(),
842 outputs: vec![TransactionOutput {
843 value: 50_0000_0000,
844 script_pubkey: vec![],
845 }]
846 .into(),
847 lock_time: 0,
848 }];
849 let block = Block {
850 header: BlockHeader {
851 version: 1,
852 prev_block_hash: [0; 32],
853 merkle_root: [0; 32],
854 timestamp: 1231006505,
855 bits: 0x1d00ffff,
856 nonce: 0,
857 },
858 transactions: transactions.into_boxed_slice(),
859 };
860
861 let result = check_bip34_network(&block, 100_000, crate::types::Network::Mainnet).unwrap();
863 assert!(result, "BIP34 should pass before activation");
864 }
865
866 #[test]
867 fn test_bip34_after_activation() {
868 let height = crate::constants::BIP34_ACTIVATION_MAINNET;
869 let transactions: Vec<Transaction> = vec![Transaction {
870 version: 1,
871 inputs: vec![TransactionInput {
872 prevout: OutPoint {
873 hash: [0; 32],
874 index: 0xffffffff,
875 },
876 script_sig: vec![
878 0x03,
879 (height & 0xff) as u8,
880 ((height >> 8) & 0xff) as u8,
881 ((height >> 16) & 0xff) as u8,
882 ],
883 sequence: 0xffffffff,
884 }]
885 .into(),
886 outputs: vec![TransactionOutput {
887 value: 50_0000_0000,
888 script_pubkey: vec![],
889 }]
890 .into(),
891 lock_time: 0,
892 }];
893 let block = Block {
894 header: BlockHeader {
895 version: 2, prev_block_hash: [0; 32],
897 merkle_root: [0; 32],
898 timestamp: 1231006505,
899 bits: 0x1d00ffff,
900 nonce: 0,
901 },
902 transactions: transactions.into_boxed_slice(),
903 };
904
905 let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
906 assert!(result, "BIP34 should pass with correct height encoding");
907 }
908
909 #[test]
910 fn test_bip90_version_enforcement() {
911 let result = check_bip90_network(1, 100_000, crate::types::Network::Mainnet).unwrap();
913 assert!(result, "Version 1 should be valid before BIP34");
914
915 let result = check_bip90_network(
917 1,
918 crate::constants::BIP34_ACTIVATION_MAINNET,
919 crate::types::Network::Mainnet,
920 )
921 .unwrap();
922 assert!(
923 !result,
924 "Version 1 should be invalid after BIP34 activation"
925 );
926
927 let result = check_bip90_network(
929 2,
930 crate::constants::BIP34_ACTIVATION_MAINNET,
931 crate::types::Network::Mainnet,
932 )
933 .unwrap();
934 assert!(result, "Version 2 should be valid after BIP34 activation");
935
936 let result = check_bip90_network(2, 363_725, crate::types::Network::Mainnet).unwrap();
939 assert!(
940 !result,
941 "Version 2 should be invalid after BIP66 activation"
942 );
943
944 let result = check_bip90_network(3, 363_725, crate::types::Network::Mainnet).unwrap();
947 assert!(result, "Version 3 should be valid after BIP66 activation");
948 }
949
950 #[test]
951 fn test_bip30_duplicate_coinbase() {
952 use crate::block::calculate_tx_id;
953
954 let coinbase_tx = Transaction {
956 version: 1,
957 inputs: vec![TransactionInput {
958 prevout: OutPoint {
959 hash: [0; 32],
960 index: 0xffffffff,
961 },
962 script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
963 sequence: 0xffffffff,
964 }]
965 .into(),
966 outputs: vec![TransactionOutput {
967 value: 50_0000_0000,
968 script_pubkey: vec![],
969 }]
970 .into(),
971 lock_time: 0,
972 };
973
974 let txid = calculate_tx_id(&coinbase_tx);
975
976 let mut utxo_set = UtxoSet::default();
978 utxo_set.insert(
979 OutPoint {
980 hash: txid,
981 index: 0,
982 },
983 std::sync::Arc::new(UTXO {
984 value: 50_0000_0000,
985 script_pubkey: vec![].into(),
986 height: 0,
987 is_coinbase: false,
988 }),
989 );
990
991 let transactions: Vec<Transaction> = vec![coinbase_tx];
993 let block = Block {
994 header: BlockHeader {
995 version: 1,
996 prev_block_hash: [0; 32],
997 merkle_root: [0; 32],
998 timestamp: 1231006505,
999 bits: 0x1d00ffff,
1000 nonce: 0,
1001 },
1002 transactions: transactions.into_boxed_slice(),
1003 };
1004
1005 let result = check_bip30_network(
1007 &block,
1008 &utxo_set,
1009 None,
1010 0,
1011 crate::types::Network::Mainnet,
1012 None,
1013 )
1014 .unwrap();
1015 assert!(!result, "BIP30 should fail for duplicate coinbase");
1016 }
1017
1018 #[test]
1019 fn test_bip34_invalid_height() {
1020 let height = crate::constants::BIP34_ACTIVATION_MAINNET;
1021 let transactions: Vec<Transaction> = vec![Transaction {
1022 version: 1,
1023 inputs: vec![TransactionInput {
1024 prevout: OutPoint {
1025 hash: [0; 32],
1026 index: 0xffffffff,
1027 },
1028 script_sig: vec![0x03, 0x00, 0x00, 0x00], sequence: 0xffffffff,
1031 }]
1032 .into(),
1033 outputs: vec![TransactionOutput {
1034 value: 50_0000_0000,
1035 script_pubkey: vec![],
1036 }]
1037 .into(),
1038 lock_time: 0,
1039 }];
1040 let block = Block {
1041 header: BlockHeader {
1042 version: 2,
1043 prev_block_hash: [0; 32],
1044 merkle_root: [0; 32],
1045 timestamp: 1231006505,
1046 bits: 0x1d00ffff,
1047 nonce: 0,
1048 },
1049 transactions: transactions.into_boxed_slice(),
1050 };
1051
1052 let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
1054 assert!(!result, "BIP34 should fail with incorrect height encoding");
1055 }
1056
1057 #[test]
1058 fn test_bip66_strict_der() {
1059 let valid_der = vec![0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00];
1061 let result = check_bip66_network(
1062 &valid_der,
1063 BIP66_ACTIVATION_MAINNET - 1,
1064 crate::types::Network::Mainnet,
1065 )
1066 .unwrap();
1067 assert!(
1069 result || !result,
1070 "BIP66 check should handle invalid DER gracefully"
1071 );
1072
1073 let result =
1075 check_bip66_network(&valid_der, 100_000, crate::types::Network::Mainnet).unwrap();
1076 assert!(result, "BIP66 should pass before activation");
1077 }
1078
1079 #[test]
1080 fn test_bip147_skips_checkmultisig_byte_in_push_data() {
1081 let script_pubkey = vec![0x01, OP_CHECKMULTISIG, OP_CHECKSIG];
1083 let script_sig = vec![1, OP_1];
1084 let result = check_bip147_network(
1085 &script_sig,
1086 &script_pubkey,
1087 BIP147_ACTIVATION_MAINNET,
1088 Bip147Network::Mainnet,
1089 )
1090 .unwrap();
1091 assert!(
1092 result,
1093 "BIP147 must not apply when CHECKMULTISIG is only in push data"
1094 );
1095 }
1096
1097 #[test]
1098 fn test_bip147_null_dummy() {
1099 let script_pubkey = vec![OP_CHECKMULTISIG];
1101 let script_sig_valid = vec![OP_0];
1102 let result = check_bip147_network(
1103 &script_sig_valid,
1104 &script_pubkey,
1105 BIP147_ACTIVATION_MAINNET,
1106 Bip147Network::Mainnet,
1107 )
1108 .unwrap();
1109 assert!(result, "BIP147 should pass with NULLDUMMY");
1110
1111 let script_sig_invalid = vec![1, OP_1];
1112 let result = check_bip147_network(
1113 &script_sig_invalid,
1114 &script_pubkey,
1115 BIP147_ACTIVATION_MAINNET,
1116 Bip147Network::Mainnet,
1117 )
1118 .unwrap();
1119 assert!(!result, "BIP147 should fail without NULLDUMMY");
1120
1121 let result = check_bip147_network(
1123 &script_sig_invalid,
1124 &script_pubkey,
1125 100_000,
1126 Bip147Network::Mainnet,
1127 )
1128 .unwrap();
1129 assert!(result, "BIP147 should pass before activation");
1130 }
1131}