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")]
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")]
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")]
161pub fn is_bip54_active_at(
162 height: Natural,
163 network: crate::types::Network,
164 activation_override: Option<u64>,
165) -> bool {
166 let activation = match activation_override {
167 Some(h) => h,
168 None => match network {
169 crate::types::Network::Mainnet => crate::constants::BIP54_ACTIVATION_MAINNET,
170 crate::types::Network::Testnet => crate::constants::BIP54_ACTIVATION_TESTNET,
171 crate::types::Network::Regtest => crate::constants::BIP54_ACTIVATION_REGTEST,
172 },
173 };
174 height >= activation
175}
176
177#[spec_locked("5.4")]
184pub fn is_bip54_active(height: Natural, network: crate::types::Network) -> bool {
185 is_bip54_active_at(height, network, None)
186}
187
188#[spec_locked("5.4")]
192pub fn check_bip54_coinbase(coinbase: &Transaction, height: Natural) -> bool {
193 let required_lock_time = height.saturating_sub(13);
194 if coinbase.lock_time != required_lock_time {
195 return false;
196 }
197 if coinbase.inputs.is_empty() {
198 return false;
199 }
200 if coinbase.inputs[0].sequence == 0xffff_ffff {
201 return false;
202 }
203 true
204}
205
206fn extract_height_from_script_sig(script_sig: &[u8]) -> Result<Natural> {
208 if script_sig.is_empty() {
209 return Err(ConsensusError::BlockValidation(
210 "Empty coinbase scriptSig".into(),
211 ));
212 }
213
214 let first_byte = script_sig[0];
215
216 if first_byte == 0x00 {
220 return Ok(0);
221 }
222
223 if (1..=0x4b).contains(&first_byte) {
225 let len = first_byte as usize;
226 if script_sig.len() < 1 + len {
227 return Err(ConsensusError::BlockValidation(
228 "Invalid scriptSig length".into(),
229 ));
230 }
231
232 let height_bytes = &script_sig[1..1 + len];
233
234 let mut height = 0u64;
236 for (i, &byte) in height_bytes.iter().enumerate() {
237 if i >= 8 {
238 return Err(ConsensusError::BlockValidation(
239 "Height value too large".into(),
240 ));
241 }
242 height |= (byte as u64) << (i * 8);
243 }
244
245 return Ok(height);
246 }
247
248 if first_byte == 0x4c {
250 if script_sig.len() < 2 {
251 return Err(ConsensusError::BlockValidation(
252 "Invalid OP_PUSHDATA1".into(),
253 ));
254 }
255 let len = script_sig[1] as usize;
256 if script_sig.len() < 2 + len {
257 return Err(ConsensusError::BlockValidation(
258 "Invalid scriptSig length".into(),
259 ));
260 }
261
262 let height_bytes = &script_sig[2..2 + len];
263
264 let mut height = 0u64;
265 for (i, &byte) in height_bytes.iter().enumerate() {
266 if i >= 8 {
267 return Err(ConsensusError::BlockValidation(
268 "Height value too large".into(),
269 ));
270 }
271 height |= (byte as u64) << (i * 8);
272 }
273
274 return Ok(height);
275 }
276
277 if first_byte == 0x4d {
279 if script_sig.len() < 3 {
280 return Err(ConsensusError::BlockValidation(
281 "Invalid OP_PUSHDATA2".into(),
282 ));
283 }
284 let len = u16::from_le_bytes([script_sig[1], script_sig[2]]) as usize;
285 if script_sig.len() < 3 + len {
286 return Err(ConsensusError::BlockValidation(
287 "Invalid scriptSig length".into(),
288 ));
289 }
290
291 let height_bytes = &script_sig[3..3 + len];
292
293 let mut height = 0u64;
294 for (i, &byte) in height_bytes.iter().enumerate() {
295 if i >= 8 {
296 return Err(ConsensusError::BlockValidation(
297 "Height value too large".into(),
298 ));
299 }
300 height |= (byte as u64) << (i * 8);
301 }
302
303 return Ok(height);
304 }
305
306 Err(ConsensusError::BlockValidation(
307 "Invalid height encoding in scriptSig".into(),
308 ))
309}
310
311#[spec_locked("5.4.3")]
325pub fn check_bip66(
326 signature: &[u8],
327 height: Natural,
328 activation: &impl IsForkActive,
329) -> Result<bool> {
330 if !activation.is_fork_active(ForkId::Bip66, height) {
331 return Ok(true);
332 }
333
334 is_strict_der(signature)
338}
339
340fn is_strict_der(signature: &[u8]) -> Result<bool> {
348 if signature.len() < 9 {
362 return Ok(false);
363 }
364 if signature.len() > 73 {
365 return Ok(false);
366 }
367
368 if signature[0] != 0x30 {
370 return Ok(false);
371 }
372
373 if signature[1] != (signature.len() - 3) as u8 {
375 return Ok(false);
376 }
377
378 let len_r = signature[3] as usize;
380
381 if 5 + len_r >= signature.len() {
383 return Ok(false);
384 }
385
386 let len_s = signature[5 + len_r] as usize;
388
389 if (len_r + len_s + 7) != signature.len() {
392 return Ok(false);
393 }
394
395 if signature[2] != 0x02 {
397 return Ok(false);
398 }
399
400 if len_r == 0 {
402 return Ok(false);
403 }
404
405 if (signature[4] & 0x80) != 0 {
407 return Ok(false);
408 }
409
410 if len_r > 1 && signature[4] == 0x00 && (signature[5] & 0x80) == 0 {
413 return Ok(false);
414 }
415
416 if signature[len_r + 4] != 0x02 {
418 return Ok(false);
419 }
420
421 if len_s == 0 {
423 return Ok(false);
424 }
425
426 if (signature[len_r + 6] & 0x80) != 0 {
428 return Ok(false);
429 }
430
431 if len_s > 1 && signature[len_r + 6] == 0x00 && (signature[len_r + 7] & 0x80) == 0 {
434 return Ok(false);
435 }
436
437 Ok(true)
438}
439
440#[spec_locked("5.4.4")]
454pub fn check_bip90(
455 block_version: i64,
456 height: Natural,
457 activation: &impl IsForkActive,
458) -> Result<bool> {
459 if activation.is_fork_active(ForkId::Bip34, height) && block_version < 2 {
460 return Ok(false);
461 }
462 if activation.is_fork_active(ForkId::Bip66, height) && block_version < 3 {
463 return Ok(false);
464 }
465 if activation.is_fork_active(ForkId::Bip65, height) && block_version < 4 {
466 return Ok(false);
467 }
468
469 Ok(true)
470}
471
472pub fn check_bip30_network(
474 block: &Block,
475 utxo_set: &UtxoSet,
476 bip30_index: Option<&Bip30Index>,
477 height: Natural,
478 network: crate::types::Network,
479 coinbase_txid: Option<&Hash>,
480) -> Result<bool> {
481 let table = crate::activation::ForkActivationTable::from_network(network);
482 check_bip30(block, utxo_set, bip30_index, height, &table, coinbase_txid)
483}
484
485pub fn check_bip34_network(
487 block: &Block,
488 height: Natural,
489 network: crate::types::Network,
490) -> Result<bool> {
491 let table = crate::activation::ForkActivationTable::from_network(network);
492 check_bip34(block, height, &table)
493}
494
495pub fn check_bip66_network(
497 signature: &[u8],
498 height: Natural,
499 network: crate::types::Network,
500) -> Result<bool> {
501 let table = crate::activation::ForkActivationTable::from_network(network);
502 check_bip66(signature, height, &table)
503}
504
505pub fn check_bip90_network(
507 block_version: i64,
508 height: Natural,
509 network: crate::types::Network,
510) -> Result<bool> {
511 let table = crate::activation::ForkActivationTable::from_network(network);
512 check_bip90(block_version, height, &table)
513}
514
515pub fn check_bip147_network(
517 script_sig: &[u8],
518 script_pubkey: &[u8],
519 height: Natural,
520 network: Bip147Network,
521) -> Result<bool> {
522 let table = match network {
523 Bip147Network::Mainnet => {
524 crate::activation::ForkActivationTable::from_network(crate::types::Network::Mainnet)
525 }
526 Bip147Network::Testnet => {
527 crate::activation::ForkActivationTable::from_network(crate::types::Network::Testnet)
528 }
529 Bip147Network::Regtest => {
530 crate::activation::ForkActivationTable::from_network(crate::types::Network::Regtest)
531 }
532 };
533 check_bip147(script_sig, script_pubkey, height, &table)
534}
535
536#[spec_locked("5.4.5")]
548pub fn check_bip147(
549 script_sig: &[u8],
550 script_pubkey: &[u8],
551 height: Natural,
552 activation: &impl IsForkActive,
553) -> Result<bool> {
554 if !activation.is_fork_active(ForkId::Bip147, height) {
555 return Ok(true);
556 }
557
558 if !script_pubkey.contains(&0xae) {
560 return Ok(true);
562 }
563
564 is_null_dummy(script_sig)
571}
572
573fn is_null_dummy(script_sig: &[u8]) -> Result<bool> {
584 if script_sig.is_empty() {
589 return Ok(false);
590 }
591
592 if script_sig.ends_with(&[0x00]) {
599 return Ok(true);
600 }
601
602 Ok(false)
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
612pub enum Bip147Network {
613 Mainnet,
614 Testnet,
615 Regtest,
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::constants::{BIP147_ACTIVATION_MAINNET, BIP66_ACTIVATION_MAINNET};
622
623 #[test]
624 fn test_bip30_basic() {
625 let transactions: Vec<Transaction> = vec![Transaction {
627 version: 1,
628 inputs: vec![TransactionInput {
629 prevout: OutPoint {
630 hash: [0; 32].into(),
631 index: 0xffffffff,
632 },
633 script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
634 sequence: 0xffffffff,
635 }]
636 .into(),
637 outputs: vec![TransactionOutput {
638 value: 50_0000_0000,
639 script_pubkey: vec![].into(),
640 }]
641 .into(),
642 lock_time: 0,
643 }];
644 let block = Block {
645 header: BlockHeader {
646 version: 1,
647 prev_block_hash: [0; 32],
648 merkle_root: [0; 32],
649 timestamp: 1231006505,
650 bits: 0x1d00ffff,
651 nonce: 0,
652 },
653 transactions: transactions.into_boxed_slice(),
654 };
655
656 let utxo_set = UtxoSet::default();
657 let result = check_bip30_network(
658 &block,
659 &utxo_set,
660 None,
661 0,
662 crate::types::Network::Mainnet,
663 None,
664 )
665 .unwrap();
666 assert!(result, "BIP30 should pass for new coinbase");
667 }
668
669 #[test]
670 fn test_bip34_before_activation() {
671 let transactions: Vec<Transaction> = vec![Transaction {
672 version: 1,
673 inputs: vec![TransactionInput {
674 prevout: OutPoint {
675 hash: [0; 32].into(),
676 index: 0xffffffff,
677 },
678 script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
679 sequence: 0xffffffff,
680 }]
681 .into(),
682 outputs: vec![TransactionOutput {
683 value: 50_0000_0000,
684 script_pubkey: vec![].into(),
685 }]
686 .into(),
687 lock_time: 0,
688 }];
689 let block = Block {
690 header: BlockHeader {
691 version: 1,
692 prev_block_hash: [0; 32],
693 merkle_root: [0; 32],
694 timestamp: 1231006505,
695 bits: 0x1d00ffff,
696 nonce: 0,
697 },
698 transactions: transactions.into_boxed_slice(),
699 };
700
701 let result = check_bip34_network(&block, 100_000, crate::types::Network::Mainnet).unwrap();
703 assert!(result, "BIP34 should pass before activation");
704 }
705
706 #[test]
707 fn test_bip34_after_activation() {
708 let height = crate::constants::BIP34_ACTIVATION_MAINNET;
709 let transactions: Vec<Transaction> = vec![Transaction {
710 version: 1,
711 inputs: vec![TransactionInput {
712 prevout: OutPoint {
713 hash: [0; 32].into(),
714 index: 0xffffffff,
715 },
716 script_sig: vec![
718 0x03,
719 (height & 0xff) as u8,
720 ((height >> 8) & 0xff) as u8,
721 ((height >> 16) & 0xff) as u8,
722 ],
723 sequence: 0xffffffff,
724 }]
725 .into(),
726 outputs: vec![TransactionOutput {
727 value: 50_0000_0000,
728 script_pubkey: vec![].into(),
729 }]
730 .into(),
731 lock_time: 0,
732 }];
733 let block = Block {
734 header: BlockHeader {
735 version: 2, prev_block_hash: [0; 32],
737 merkle_root: [0; 32],
738 timestamp: 1231006505,
739 bits: 0x1d00ffff,
740 nonce: 0,
741 },
742 transactions: transactions.into_boxed_slice(),
743 };
744
745 let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
746 assert!(result, "BIP34 should pass with correct height encoding");
747 }
748
749 #[test]
750 fn test_bip90_version_enforcement() {
751 let result = check_bip90_network(1, 100_000, crate::types::Network::Mainnet).unwrap();
753 assert!(result, "Version 1 should be valid before BIP34");
754
755 let result = check_bip90_network(
757 1,
758 crate::constants::BIP34_ACTIVATION_MAINNET,
759 crate::types::Network::Mainnet,
760 )
761 .unwrap();
762 assert!(
763 !result,
764 "Version 1 should be invalid after BIP34 activation"
765 );
766
767 let result = check_bip90_network(
769 2,
770 crate::constants::BIP34_ACTIVATION_MAINNET,
771 crate::types::Network::Mainnet,
772 )
773 .unwrap();
774 assert!(result, "Version 2 should be valid after BIP34 activation");
775
776 let result = check_bip90_network(2, 363_725, crate::types::Network::Mainnet).unwrap();
779 assert!(
780 !result,
781 "Version 2 should be invalid after BIP66 activation"
782 );
783
784 let result = check_bip90_network(3, 363_725, crate::types::Network::Mainnet).unwrap();
787 assert!(result, "Version 3 should be valid after BIP66 activation");
788 }
789
790 #[test]
791 fn test_bip30_duplicate_coinbase() {
792 use crate::block::calculate_tx_id;
793
794 let coinbase_tx = Transaction {
796 version: 1,
797 inputs: vec![TransactionInput {
798 prevout: OutPoint {
799 hash: [0; 32].into(),
800 index: 0xffffffff,
801 },
802 script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
803 sequence: 0xffffffff,
804 }]
805 .into(),
806 outputs: vec![TransactionOutput {
807 value: 50_0000_0000,
808 script_pubkey: vec![].into(),
809 }]
810 .into(),
811 lock_time: 0,
812 };
813
814 let txid = calculate_tx_id(&coinbase_tx);
815
816 let mut utxo_set = UtxoSet::default();
818 utxo_set.insert(
819 OutPoint {
820 hash: txid,
821 index: 0,
822 },
823 std::sync::Arc::new(UTXO {
824 value: 50_0000_0000,
825 script_pubkey: vec![].into(),
826 height: 0,
827 is_coinbase: false,
828 }),
829 );
830
831 let transactions: Vec<Transaction> = vec![coinbase_tx];
833 let block = Block {
834 header: BlockHeader {
835 version: 1,
836 prev_block_hash: [0; 32],
837 merkle_root: [0; 32],
838 timestamp: 1231006505,
839 bits: 0x1d00ffff,
840 nonce: 0,
841 },
842 transactions: transactions.into_boxed_slice(),
843 };
844
845 let result = check_bip30_network(
847 &block,
848 &utxo_set,
849 None,
850 0,
851 crate::types::Network::Mainnet,
852 None,
853 )
854 .unwrap();
855 assert!(!result, "BIP30 should fail for duplicate coinbase");
856 }
857
858 #[test]
859 fn test_bip34_invalid_height() {
860 let height = crate::constants::BIP34_ACTIVATION_MAINNET;
861 let transactions: Vec<Transaction> = vec![Transaction {
862 version: 1,
863 inputs: vec![TransactionInput {
864 prevout: OutPoint {
865 hash: [0; 32].into(),
866 index: 0xffffffff,
867 },
868 script_sig: vec![0x03, 0x00, 0x00, 0x00], sequence: 0xffffffff,
871 }]
872 .into(),
873 outputs: vec![TransactionOutput {
874 value: 50_0000_0000,
875 script_pubkey: vec![].into(),
876 }]
877 .into(),
878 lock_time: 0,
879 }];
880 let block = Block {
881 header: BlockHeader {
882 version: 2,
883 prev_block_hash: [0; 32],
884 merkle_root: [0; 32],
885 timestamp: 1231006505,
886 bits: 0x1d00ffff,
887 nonce: 0,
888 },
889 transactions: transactions.into_boxed_slice(),
890 };
891
892 let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
894 assert!(!result, "BIP34 should fail with incorrect height encoding");
895 }
896
897 #[test]
898 fn test_bip66_strict_der() {
899 let valid_der = vec![0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00];
901 let result = check_bip66_network(
902 &valid_der,
903 BIP66_ACTIVATION_MAINNET - 1,
904 crate::types::Network::Mainnet,
905 )
906 .unwrap();
907 assert!(
909 result || !result,
910 "BIP66 check should handle invalid DER gracefully"
911 );
912
913 let result =
915 check_bip66_network(&valid_der, 100_000, crate::types::Network::Mainnet).unwrap();
916 assert!(result, "BIP66 should pass before activation");
917 }
918
919 #[test]
920 fn test_bip147_null_dummy() {
921 let script_pubkey = vec![0x52, 0x21, 0x00, 0x21, 0x00, 0x52, 0xae]; let script_sig_valid = vec![0x00]; let result = check_bip147_network(
927 &script_sig_valid,
928 &script_pubkey,
929 BIP147_ACTIVATION_MAINNET,
930 Bip147Network::Mainnet,
931 )
932 .unwrap();
933 assert!(result, "BIP147 should pass with NULLDUMMY");
934
935 let script_sig_invalid = vec![0x01, 0x01]; let result = check_bip147_network(
938 &script_sig_invalid,
939 &script_pubkey,
940 BIP147_ACTIVATION_MAINNET,
941 Bip147Network::Mainnet,
942 )
943 .unwrap();
944 assert!(!result, "BIP147 should fail without NULLDUMMY");
945
946 let result = check_bip147_network(
948 &script_sig_invalid,
949 &script_pubkey,
950 100_000,
951 Bip147Network::Mainnet,
952 )
953 .unwrap();
954 assert!(result, "BIP147 should pass before activation");
955 }
956}