use crate::activation::IsForkActive;
use crate::block::calculate_tx_id;
use crate::error::Result;
use crate::opcodes::{
OP_0, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4,
};
use crate::transaction::is_coinbase;
use crate::types::*;
use blvm_spec_lock::spec_locked;
#[cfg(feature = "production")]
pub type Bip30Index = rustc_hash::FxHashMap<crate::types::Hash, usize>;
#[cfg(not(feature = "production"))]
pub type Bip30Index = std::collections::HashMap<crate::types::Hash, usize>;
pub fn build_bip30_index(utxo_set: &UtxoSet) -> Bip30Index {
let mut index = Bip30Index::default();
for (outpoint, utxo) in utxo_set.iter() {
if utxo.is_coinbase {
*index.entry(outpoint.hash).or_insert(0) += 1;
}
}
index
}
const BIP30_REPEAT_91842: Hash = [
0xec, 0xca, 0xe0, 0x00, 0xe3, 0xc8, 0xe4, 0xe0, 0x93, 0x93, 0x63, 0x60, 0x43, 0x1f, 0x3b, 0x76,
0x03, 0xc5, 0x63, 0xc1, 0xff, 0x61, 0x81, 0x39, 0x0a, 0x4d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const BIP30_REPEAT_91880: Hash = [
0x21, 0xd7, 0x7c, 0xcb, 0x4c, 0x08, 0x38, 0x6a, 0x04, 0xac, 0x01, 0x96, 0xae, 0x10, 0xf6, 0xa1,
0xd2, 0xc2, 0xa3, 0x77, 0x55, 0x8c, 0xa1, 0x90, 0xf1, 0x43, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const BIP34_MAINNET_HASH: Hash = [
0xb8, 0x08, 0x08, 0x9c, 0x75, 0x6a, 0xdd, 0x15, 0x91, 0xb1, 0xd1, 0x7b, 0xab, 0x44, 0xbb, 0xa3,
0xfe, 0xd9, 0xe0, 0x2f, 0x94, 0x2a, 0xb4, 0x89, 0x4b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const BIP34_TESTNET_HASH: Hash = [
0xf8, 0x8e, 0xcd, 0x99, 0x12, 0xd0, 0x0d, 0x3f, 0x5c, 0x2a, 0x8e, 0x0f, 0x50, 0x41, 0x7d, 0x3e,
0x41, 0x5c, 0x75, 0xb3, 0xab, 0xe5, 0x84, 0x34, 0x6d, 0xa9, 0xb3, 0x23, 0x00, 0x00, 0x00, 0x00,
];
pub const BIP34_IMPLIES_BIP30_LIMIT: u64 = 1_983_702;
pub fn is_bip30_repeat_block(height: u64, block_hash: &Hash) -> bool {
match height {
91_842 => block_hash == &BIP30_REPEAT_91842,
91_880 => block_hash == &BIP30_REPEAT_91880,
_ => false,
}
}
pub fn is_known_bip34_header(network: Network, height: u64, header: &BlockHeader) -> bool {
let expected = match network {
Network::Mainnet if height == crate::constants::BIP34_ACTIVATION_MAINNET => {
&BIP34_MAINNET_HASH
}
Network::Testnet if height == crate::constants::BIP34_ACTIVATION_TESTNET => {
&BIP34_TESTNET_HASH
}
_ => return false,
};
&crate::block::block_header_hash(header) == expected
}
pub fn is_known_bip34_block(network: Network, height: u64, block_hash: &Hash) -> bool {
match network {
Network::Mainnet => {
height == crate::constants::BIP34_ACTIVATION_MAINNET
&& block_hash == &BIP34_MAINNET_HASH
}
Network::Testnet => {
height == crate::constants::BIP34_ACTIVATION_TESTNET
&& block_hash == &BIP34_TESTNET_HASH
}
Network::Regtest | Network::Signet => false,
}
}
pub fn bip30_lookup_skippable(network: Network, height: u64, bip34_hash_matches: bool) -> bool {
if !bip34_hash_matches || height >= BIP34_IMPLIES_BIP30_LIMIT {
return false;
}
let activation = match network {
Network::Mainnet => crate::constants::BIP34_ACTIVATION_MAINNET,
Network::Testnet => crate::constants::BIP34_ACTIVATION_TESTNET,
Network::Regtest | Network::Signet => return false,
};
height > activation
}
#[spec_locked("5.4.1", "BIP30Check")]
pub fn check_bip30(
block: &Block,
utxo_set: &UtxoSet,
bip30_index: Option<&Bip30Index>,
height: Natural,
activation: &impl IsForkActive,
coinbase_txid: Option<&Hash>,
) -> Result<bool> {
if !activation.is_fork_active(ForkId::Bip30, height) {
return Ok(true);
}
if height == 91_842 || height == 91_880 {
let hash = crate::block::block_header_hash(&block.header);
if is_bip30_repeat_block(height, &hash) {
return Ok(true);
}
}
let coinbase = block.transactions.first();
if let Some(tx) = coinbase {
if !is_coinbase(tx) {
return Ok(true);
}
let txid = coinbase_txid
.copied()
.unwrap_or_else(|| calculate_tx_id(tx));
if let Some(index) = bip30_index {
if index.get(&txid).is_some_and(|&c| c > 0) {
return Ok(false);
}
return Ok(true);
}
for (outpoint, _utxo) in utxo_set.iter() {
if outpoint.hash == txid {
return Ok(false);
}
}
}
Ok(true)
}
#[spec_locked("5.4.2", "BIP34Check")]
pub fn check_bip34(block: &Block, height: Natural, activation: &impl IsForkActive) -> Result<bool> {
if !activation.is_fork_active(ForkId::Bip34, height) {
return Ok(true);
}
let coinbase = block.transactions.first();
if let Some(tx) = coinbase {
if !is_coinbase(tx) {
return Ok(true);
}
if !script_sig_starts_with_height(&tx.inputs[0].script_sig, height) {
return Ok(false);
}
}
Ok(true)
}
fn script_sig_starts_with_height(script_sig: &[u8], height: u64) -> bool {
let mut raw = [0u8; 9];
let mut n = height;
let mut len = 0usize;
while n > 0 {
raw[len] = (n & 0xff) as u8;
n >>= 8;
len += 1;
}
if len == 0 {
return script_sig.first().copied() == Some(0x00);
}
if raw[len - 1] & 0x80 != 0 {
raw[len] = 0x00;
len += 1;
}
script_sig.len() > len && script_sig[0] == len as u8 && script_sig[1..1 + len] == raw[..len]
}
#[spec_locked("5.4.9", "IsBip54ActiveAt")]
pub fn is_bip54_active_at(
height: Natural,
network: crate::types::Network,
activation_override: Option<u64>,
) -> bool {
let activation = match activation_override {
Some(h) => h,
None => match network {
crate::types::Network::Mainnet => crate::constants::BIP54_ACTIVATION_MAINNET,
crate::types::Network::Testnet => crate::constants::BIP54_ACTIVATION_TESTNET,
crate::types::Network::Regtest | crate::types::Network::Signet => {
crate::constants::BIP54_ACTIVATION_REGTEST
}
},
};
height >= activation
}
#[spec_locked("5.4.9", "IsBip54Active")]
pub fn is_bip54_active(height: Natural, network: crate::types::Network) -> bool {
is_bip54_active_at(height, network, None)
}
#[spec_locked("5.4.9", "BIP54TimewarpCheck")]
pub fn check_bip54_timewarp(
header: &BlockHeader,
height: Natural,
boundary: Option<&Bip54BoundaryTimestamps>,
bip54_active: bool,
) -> bool {
if !bip54_active {
return true;
}
let rem = height % 2016;
if rem == 2015 {
let Some(b) = boundary else {
return false;
};
header.timestamp >= b.timestamp_n_minus_2015
} else if rem == 0 {
let Some(b) = boundary else {
return false;
};
const TWOHOURS: u64 = 7200;
let min_ts = b.timestamp_n_minus_1.saturating_sub(TWOHOURS);
header.timestamp >= min_ts
} else {
true
}
}
#[spec_locked("5.4.9", "CheckBip54TxStrippedSize")]
pub fn check_bip54_tx_stripped_size(tx: &Transaction) -> bool {
is_coinbase(tx) || crate::transaction::calculate_transaction_size(tx) != 64
}
#[spec_locked("5.4.9", "CheckBip54SigOpLimit")]
pub fn check_bip54_sigop_limit<U: crate::utxo_overlay::UtxoLookup>(
bip54_active: bool,
tx: &Transaction,
utxo_lookup: &U,
wits: Option<&[Witness]>,
tx_flags: u32,
) -> Result<Option<&'static str>> {
if !bip54_active || is_coinbase(tx) {
return Ok(None);
}
let sigop_count =
crate::sigop::get_transaction_sigop_count_for_bip54(tx, utxo_lookup, wits, tx_flags)?;
if sigop_count > crate::constants::BIP54_MAX_SIGOPS_PER_TX {
return Ok(Some("BIP54: Transaction sigop count exceeds 2500"));
}
Ok(None)
}
#[spec_locked("5.4.9", "CheckBip54Coinbase")]
pub fn check_bip54_coinbase(coinbase: &Transaction, height: Natural) -> bool {
let required_lock_time = height.saturating_sub(13);
if coinbase.lock_time != required_lock_time {
return false;
}
if coinbase.inputs.is_empty() {
return false;
}
if coinbase.inputs[0].sequence == 0xffff_ffff {
return false;
}
true
}
#[spec_locked("5.4.3", "BIP66Check")]
pub fn check_bip66(
signature: &[u8],
height: Natural,
activation: &impl IsForkActive,
) -> Result<bool> {
if !activation.is_fork_active(ForkId::Bip66, height) {
return Ok(true);
}
is_strict_der(signature)
}
fn is_strict_der(signature: &[u8]) -> Result<bool> {
if signature.len() < 9 {
return Ok(false);
}
if signature.len() > 73 {
return Ok(false);
}
if signature[0] != 0x30 {
return Ok(false);
}
if signature[1] != (signature.len() - 3) as u8 {
return Ok(false);
}
let len_r = signature[3] as usize;
if 5 + len_r >= signature.len() {
return Ok(false);
}
let len_s = signature[5 + len_r] as usize;
if (len_r + len_s + 7) != signature.len() {
return Ok(false);
}
if signature[2] != 0x02 {
return Ok(false);
}
if len_r == 0 {
return Ok(false);
}
if (signature[4] & 0x80) != 0 {
return Ok(false);
}
if len_r > 1 && signature[4] == 0x00 && (signature[5] & 0x80) == 0 {
return Ok(false);
}
if signature[len_r + 4] != 0x02 {
return Ok(false);
}
if len_s == 0 {
return Ok(false);
}
if (signature[len_r + 6] & 0x80) != 0 {
return Ok(false);
}
if len_s > 1 && signature[len_r + 6] == 0x00 && (signature[len_r + 7] & 0x80) == 0 {
return Ok(false);
}
Ok(true)
}
#[spec_locked("5.4.4", "BIP90Check")]
pub fn check_bip90(
block_version: i64,
height: Natural,
activation: &impl IsForkActive,
) -> Result<bool> {
if activation.is_fork_active(ForkId::Bip34, height) && block_version < 2 {
return Ok(false);
}
if activation.is_fork_active(ForkId::Bip66, height) && block_version < 3 {
return Ok(false);
}
if activation.is_fork_active(ForkId::Bip65, height) && block_version < 4 {
return Ok(false);
}
Ok(true)
}
pub fn check_bip30_network(
block: &Block,
utxo_set: &UtxoSet,
bip30_index: Option<&Bip30Index>,
height: Natural,
network: crate::types::Network,
coinbase_txid: Option<&Hash>,
) -> Result<bool> {
let table = crate::activation::ForkActivationTable::from_network(network);
check_bip30(block, utxo_set, bip30_index, height, &table, coinbase_txid)
}
pub fn check_bip34_network(
block: &Block,
height: Natural,
network: crate::types::Network,
) -> Result<bool> {
let table = crate::activation::ForkActivationTable::from_network(network);
check_bip34(block, height, &table)
}
pub fn check_bip66_network(
signature: &[u8],
height: Natural,
network: crate::types::Network,
) -> Result<bool> {
let table = crate::activation::ForkActivationTable::from_network(network);
check_bip66(signature, height, &table)
}
pub fn check_bip90_network(
block_version: i64,
height: Natural,
network: crate::types::Network,
) -> Result<bool> {
let table = crate::activation::ForkActivationTable::from_network(network);
check_bip90(block_version, height, &table)
}
pub fn check_bip147_network(
script_sig: &[u8],
script_pubkey: &[u8],
height: Natural,
network: Bip147Network,
) -> Result<bool> {
let table = match network {
Bip147Network::Mainnet => {
crate::activation::ForkActivationTable::from_network(crate::types::Network::Mainnet)
}
Bip147Network::Testnet => {
crate::activation::ForkActivationTable::from_network(crate::types::Network::Testnet)
}
Bip147Network::Regtest => {
crate::activation::ForkActivationTable::from_network(crate::types::Network::Regtest)
}
};
check_bip147(script_sig, script_pubkey, height, &table)
}
pub(crate) fn script_contains_executable_opcode(script: &[u8], opcode: u8) -> bool {
let mut i = 0;
while i < script.len() {
let op = script[i];
if op > 0 && op < OP_PUSHDATA1 {
i += 1 + op as usize;
continue;
}
match op {
OP_PUSHDATA1 => {
if i + 1 >= script.len() {
break;
}
let len = script[i + 1] as usize;
i += 2 + len;
}
OP_PUSHDATA2 => {
if i + 2 >= script.len() {
break;
}
let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
i += 3 + len;
}
OP_PUSHDATA4 => {
if i + 4 >= script.len() {
break;
}
let len = u32::from_le_bytes([
script[i + 1],
script[i + 2],
script[i + 3],
script[i + 4],
]) as usize;
i += 5 + len;
}
_ => {
if op == opcode {
return true;
}
i += 1;
}
}
}
false
}
fn script_has_checkmultisig(script_pubkey: &[u8]) -> bool {
script_contains_executable_opcode(script_pubkey, OP_CHECKMULTISIG)
|| script_contains_executable_opcode(script_pubkey, OP_CHECKMULTISIGVERIFY)
}
#[spec_locked("5.4.5", "BIP147Check")]
pub fn check_bip147(
script_sig: &[u8],
script_pubkey: &[u8],
height: Natural,
activation: &impl IsForkActive,
) -> Result<bool> {
if !activation.is_fork_active(ForkId::Bip147, height) {
return Ok(true);
}
if !script_has_checkmultisig(script_pubkey) {
return Ok(true);
}
is_null_dummy(script_sig)
}
fn is_null_dummy(script_sig: &[u8]) -> Result<bool> {
if script_sig.is_empty() {
return Ok(false);
}
let mut pc = 0usize;
let mut first_push_empty = false;
let mut saw_push = false;
while pc < script_sig.len() {
let opcode = script_sig[pc];
pc += 1;
match opcode {
OP_0 => {
if !saw_push {
first_push_empty = true;
saw_push = true;
}
}
0x01..=0x4b => {
let len = opcode as usize;
if pc + len > script_sig.len() {
return Ok(false);
}
if !saw_push {
first_push_empty = len == 0;
saw_push = true;
}
pc += len;
}
OP_PUSHDATA1 => {
if pc >= script_sig.len() {
return Ok(false);
}
let len = script_sig[pc] as usize;
pc += 1;
if pc + len > script_sig.len() {
return Ok(false);
}
if !saw_push {
first_push_empty = len == 0;
saw_push = true;
}
pc += len;
}
OP_PUSHDATA2 => {
if pc + 2 > script_sig.len() {
return Ok(false);
}
let len = u16::from_le_bytes([script_sig[pc], script_sig[pc + 1]]) as usize;
pc += 2;
if pc + len > script_sig.len() {
return Ok(false);
}
if !saw_push {
first_push_empty = len == 0;
saw_push = true;
}
pc += len;
}
OP_PUSHDATA4 => {
if pc + 4 > script_sig.len() {
return Ok(false);
}
let len = u32::from_le_bytes([
script_sig[pc],
script_sig[pc + 1],
script_sig[pc + 2],
script_sig[pc + 3],
]) as usize;
pc += 4;
if pc + len > script_sig.len() {
return Ok(false);
}
if !saw_push {
first_push_empty = len == 0;
saw_push = true;
}
pc += len;
}
_ => return Ok(false),
}
}
Ok(first_push_empty)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bip147Network {
Mainnet,
Testnet,
Regtest,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::BIP147_ACTIVATION_MAINNET;
use crate::opcodes::{OP_0, OP_1, OP_2, OP_CHECKMULTISIG, OP_CHECKSIG};
#[test]
fn test_bip30_repeat_hashes_are_display_ids_reversed() {
fn digest(display: &str) -> Hash {
let mut bytes = hex::decode(display).unwrap();
bytes.reverse();
bytes.try_into().unwrap()
}
assert!(is_bip30_repeat_block(
91_842,
&digest("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")
));
assert!(is_bip30_repeat_block(
91_880,
&digest("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")
));
assert!(!is_bip30_repeat_block(91_842, &[0; 32]));
assert!(!is_bip30_repeat_block(
91_722,
&digest("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")
));
assert!(is_known_bip34_block(
Network::Mainnet,
crate::constants::BIP34_ACTIVATION_MAINNET,
&digest("000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8")
));
assert!(!is_known_bip34_block(
Network::Mainnet,
crate::constants::BIP34_ACTIVATION_MAINNET,
&[0; 32]
));
}
#[test]
fn test_bip30_basic() {
let transactions: Vec<Transaction> = vec![Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32],
index: 0xffffffff,
},
script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
}];
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: transactions.into_boxed_slice(),
};
let utxo_set = UtxoSet::default();
let result = check_bip30_network(
&block,
&utxo_set,
None,
0,
crate::types::Network::Mainnet,
None,
)
.unwrap();
assert!(result, "BIP30 should pass for new coinbase");
}
#[test]
fn test_bip34_before_activation() {
let transactions: Vec<Transaction> = vec![Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32],
index: 0xffffffff,
},
script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
}];
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: transactions.into_boxed_slice(),
};
let result = check_bip34_network(&block, 100_000, crate::types::Network::Mainnet).unwrap();
assert!(result, "BIP34 should pass before activation");
}
#[test]
fn test_bip34_after_activation() {
let height = crate::constants::BIP34_ACTIVATION_MAINNET;
let transactions: Vec<Transaction> = vec![Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32],
index: 0xffffffff,
},
script_sig: vec![
0x03,
(height & 0xff) as u8,
((height >> 8) & 0xff) as u8,
((height >> 16) & 0xff) as u8,
],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
}];
let block = Block {
header: BlockHeader {
version: 2, prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: transactions.into_boxed_slice(),
};
let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
assert!(result, "BIP34 should pass with correct height encoding");
}
#[test]
fn test_bip90_version_enforcement() {
let result = check_bip90_network(1, 100_000, crate::types::Network::Mainnet).unwrap();
assert!(result, "Version 1 should be valid before BIP34");
let result = check_bip90_network(
1,
crate::constants::BIP34_ACTIVATION_MAINNET,
crate::types::Network::Mainnet,
)
.unwrap();
assert!(
!result,
"Version 1 should be invalid after BIP34 activation"
);
let result = check_bip90_network(
2,
crate::constants::BIP34_ACTIVATION_MAINNET,
crate::types::Network::Mainnet,
)
.unwrap();
assert!(result, "Version 2 should be valid after BIP34 activation");
let result = check_bip90_network(2, 363_725, crate::types::Network::Mainnet).unwrap();
assert!(
!result,
"Version 2 should be invalid after BIP66 activation"
);
let result = check_bip90_network(3, 363_725, crate::types::Network::Mainnet).unwrap();
assert!(result, "Version 3 should be valid after BIP66 activation");
}
#[test]
fn test_bip30_duplicate_coinbase() {
use crate::block::calculate_tx_id;
let coinbase_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32],
index: 0xffffffff,
},
script_sig: vec![0x04, 0x00, 0x00, 0x00, 0x00],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
};
let txid = calculate_tx_id(&coinbase_tx);
let mut utxo_set = UtxoSet::default();
utxo_set.insert(
OutPoint {
hash: txid,
index: 0,
},
std::sync::Arc::new(UTXO {
value: 50_0000_0000,
script_pubkey: vec![].into(),
height: 0,
is_coinbase: false,
}),
);
let transactions: Vec<Transaction> = vec![coinbase_tx];
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: transactions.into_boxed_slice(),
};
let result = check_bip30_network(
&block,
&utxo_set,
None,
0,
crate::types::Network::Mainnet,
None,
)
.unwrap();
assert!(!result, "BIP30 should fail for duplicate coinbase");
}
#[test]
fn test_bip34_invalid_height() {
let height = crate::constants::BIP34_ACTIVATION_MAINNET;
let transactions: Vec<Transaction> = vec![Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32],
index: 0xffffffff,
},
script_sig: vec![0x03, 0x00, 0x00, 0x00], sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_0000_0000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
}];
let block = Block {
header: BlockHeader {
version: 2,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: transactions.into_boxed_slice(),
};
let result = check_bip34_network(&block, height, crate::types::Network::Mainnet).unwrap();
assert!(!result, "BIP34 should fail with incorrect height encoding");
}
#[test]
fn test_bip66_strict_der() {
use crate::constants::BIP66_ACTIVATION_MAINNET;
use crate::types::Network;
let accept: Vec<u8> = vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01];
assert_eq!(
check_bip66_network(&accept, BIP66_ACTIVATION_MAINNET, Network::Mainnet).unwrap(),
true,
"9-byte strict DER must pass at activation"
);
let mut high_bit_r = accept.clone();
high_bit_r[4] = 0x81;
let leading_zero_r: Vec<u8> =
vec![0x30, 0x07, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x01, 0x01];
let mut bad_length = accept.clone();
bad_length[1] = 0x05;
let rejects = [
vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01],
vec![0u8; 74],
high_bit_r.clone(),
leading_zero_r,
bad_length,
];
for sig in &rejects {
assert_eq!(
check_bip66_network(sig, BIP66_ACTIVATION_MAINNET, Network::Mainnet).unwrap(),
false,
"non-strict encoding must fail at activation: {sig:?}"
);
}
assert_eq!(
check_bip66_network(&high_bit_r, BIP66_ACTIVATION_MAINNET - 1, Network::Mainnet)
.unwrap(),
true,
"pre-activation must accept a non-strict signature"
);
}
#[test]
fn test_bip147_skips_checkmultisig_byte_in_push_data() {
let script_pubkey = vec![0x01, OP_CHECKMULTISIG, OP_CHECKSIG];
let script_sig = vec![1, OP_1];
let result = check_bip147_network(
&script_sig,
&script_pubkey,
BIP147_ACTIVATION_MAINNET,
Bip147Network::Mainnet,
)
.unwrap();
assert!(
result,
"BIP147 must not apply when CHECKMULTISIG is only in push data"
);
}
#[test]
fn test_bip147_null_dummy() {
let script_pubkey = vec![OP_CHECKMULTISIG];
let script_sig_valid = vec![OP_0];
let result = check_bip147_network(
&script_sig_valid,
&script_pubkey,
BIP147_ACTIVATION_MAINNET,
Bip147Network::Mainnet,
)
.unwrap();
assert!(result, "BIP147 should pass with NULLDUMMY");
let script_sig_invalid = vec![1, OP_1];
let result = check_bip147_network(
&script_sig_invalid,
&script_pubkey,
BIP147_ACTIVATION_MAINNET,
Bip147Network::Mainnet,
)
.unwrap();
assert!(!result, "BIP147 should fail without NULLDUMMY");
let result = check_bip147_network(
&script_sig_invalid,
&script_pubkey,
100_000,
Bip147Network::Mainnet,
)
.unwrap();
assert!(result, "BIP147 should pass before activation");
}
}