1use 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#[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
23pub 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#[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 let coinbase = block.transactions.first();
68
69 if let Some(tx) = coinbase {
70 if !is_coinbase(tx) {
71 return Ok(true);
73 }
74
75 let txid = coinbase_txid
76 .copied()
77 .unwrap_or_else(|| calculate_tx_id(tx));
78
79 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 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#[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 let coinbase = block.transactions.first();
119
120 if let Some(tx) = coinbase {
121 if !is_coinbase(tx) {
122 return Ok(true);
123 }
124
125 let script_sig = &tx.inputs[0].script_sig;
128
129 if script_sig.is_empty() {
130 return Ok(false);
131 }
132
133 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#[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#[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#[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#[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#[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#[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
263fn 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 if first_byte == 0x00 {
277 return Ok(0);
278 }
279
280 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 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 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 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#[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 is_strict_der(signature)
395}
396
397fn is_strict_der(signature: &[u8]) -> Result<bool> {
405 if signature.len() < 9 {
419 return Ok(false);
420 }
421 if signature.len() > 73 {
422 return Ok(false);
423 }
424
425 if signature[0] != 0x30 {
427 return Ok(false);
428 }
429
430 if signature[1] != (signature.len() - 3) as u8 {
432 return Ok(false);
433 }
434
435 let len_r = signature[3] as usize;
437
438 if 5 + len_r >= signature.len() {
440 return Ok(false);
441 }
442
443 let len_s = signature[5 + len_r] as usize;
445
446 if (len_r + len_s + 7) != signature.len() {
449 return Ok(false);
450 }
451
452 if signature[2] != 0x02 {
454 return Ok(false);
455 }
456
457 if len_r == 0 {
459 return Ok(false);
460 }
461
462 if (signature[4] & 0x80) != 0 {
464 return Ok(false);
465 }
466
467 if len_r > 1 && signature[4] == 0x00 && (signature[5] & 0x80) == 0 {
470 return Ok(false);
471 }
472
473 if signature[len_r + 4] != 0x02 {
475 return Ok(false);
476 }
477
478 if len_s == 0 {
480 return Ok(false);
481 }
482
483 if (signature[len_r + 6] & 0x80) != 0 {
485 return Ok(false);
486 }
487
488 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#[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
529pub 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
542pub 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
552pub 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
562pub 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
572pub 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#[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 if !script_pubkey.contains(&0xae) {
617 return Ok(true);
619 }
620
621 is_null_dummy(script_sig)
628}
629
630fn is_null_dummy(script_sig: &[u8]) -> Result<bool> {
641 if script_sig.is_empty() {
646 return Ok(false);
647 }
648
649 if script_sig.ends_with(&[0x00]) {
656 return Ok(true);
657 }
658
659 Ok(false)
665}
666
667#[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 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 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 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, 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 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 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 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 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 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 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 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 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 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 script_sig: vec![0x03, 0x00, 0x00, 0x00], 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 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 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 assert!(
966 result || !result,
967 "BIP66 check should handle invalid DER gracefully"
968 );
969
970 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 let script_pubkey = vec![0x52, 0x21, 0x00, 0x21, 0x00, 0x52, 0xae]; let script_sig_valid = vec![0x00]; 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 let script_sig_invalid = vec![0x01, 0x01]; 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 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}