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