use bitcoin_hashes::{sha256d, Hash as BitcoinHash};
use blvm_consensus::*;
use blvm_consensus::segwit::*;
use blvm_consensus::script::verify_script_with_context_full;
use blvm_consensus::constants::MAX_BLOCK_WEIGHT;
use super::bip_test_helpers::*;
fn create_witness_commitment_script(witness_root: &[u8; 32], nonce: &[u8; 32]) -> Vec<u8> {
let mut preimage = [0u8; 64];
preimage[..32].copy_from_slice(witness_root);
preimage[32..].copy_from_slice(nonce);
let h = sha256d::Hash::hash(&preimage);
let mut script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
script.extend_from_slice(&h[..]);
script
}
#[test]
fn test_segwit_witness_validation() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(), }].into(),
lock_time: 0,
};
let witness = vec![vec![0x51]];
let mut utxo_set = UtxoSet::default();
utxo_set.insert(
OutPoint { hash: [1; 32], index: 0 },
std::sync::Arc::new(UTXO {
value: 1000000,
script_pubkey: vec![0x51].into(), height: 0,
}),
);
let input = &tx.inputs[0];
let utxo = utxo_set.get(&input.prevout).unwrap();
let pv = vec![utxo.value];
let psp: Vec<&crate::types::ByteString> = vec![&utxo.script_pubkey];
let witness_script = witness[0].clone();
let result = verify_script_with_context_full(
&input.script_sig,
&utxo.script_pubkey,
Some(&witness_script), 0, &tx,
0, &pv,
&psp,
None, None, crate::types::Network::Mainnet,
crate::script::SigVersion::WitnessV0,
None,
None,
None,
None, #[cfg(feature = "production")] None,
);
assert!(result.is_ok());
}
#[test]
fn test_segwit_transaction_weight() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
};
let witness = vec![vec![0x51; 100]];
let weight = calculate_transaction_weight(&tx, Some(&witness), crate::types::Network::Mainnet).unwrap();
assert!(weight > 0);
assert!(weight > 4 * 100); }
#[test]
fn test_segwit_block_weight() {
let block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![
Transaction {
version: 1,
inputs: vec![].into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}].into(),
lock_time: 0,
},
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
},
],
};
let witnesses = vec![
vec![], vec![vec![0x51]], ];
let block_weight = calculate_block_weight(&block, &witnesses).unwrap();
assert!(block_weight > 0);
assert!(block_weight <= MAX_BLOCK_WEIGHT); }
#[test]
fn test_segwit_block_weight_boundary() {
let mut block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![].into(),
};
for _ in 0..100 {
block.transactions.push(Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51; 100].into(), }].into(),
lock_time: 0,
});
}
let witnesses: Vec<Witness> = (0..block.transactions.len())
.map(|i| if i == 0 { vec![] } else { vec![vec![0x51; 50]] })
.collect();
let block_weight = calculate_block_weight(&block, &witnesses).unwrap();
assert!(block_weight > 0);
}
#[test]
fn test_segwit_witness_commitment() {
let mut coinbase_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [0; 32].into(), index: 0xffffffff },
script_sig: vec![0x51],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}].into(),
lock_time: 0,
};
let witness_root = [1u8; 32];
coinbase_tx.outputs[0].script_pubkey =
create_witness_commitment_script(&witness_root, &[0u8; 32]);
let is_valid = validate_witness_commitment(&coinbase_tx, &witness_root, &[]).unwrap();
assert!(is_valid);
}
#[test]
fn test_segwit_p2wpkh_validation() {
let p2wpkh_hash = [0x51; 20]; let mut script_pubkey = vec![0x00]; script_pubkey.extend_from_slice(&p2wpkh_hash);
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
};
let witness = vec![
vec![0x51; 72], vec![0x51; 33], ];
let mut utxo_set = UtxoSet::default();
utxo_set.insert(
OutPoint { hash: [1; 32], index: 0 },
std::sync::Arc::new(UTXO {
value: 1000000,
script_pubkey: script_pubkey.clone().into(),
height: 0,
}),
);
let input = &tx.inputs[0];
let utxo = utxo_set.get(&input.prevout).unwrap();
let pv = vec![utxo.value];
let psp: Vec<&crate::types::ByteString> = vec![&utxo.script_pubkey];
let witness_script = witness.iter().flat_map(|w| w.iter().cloned()).collect::<Vec<u8>>();
let result = verify_script_with_context_full(
&input.script_sig,
&utxo.script_pubkey,
Some(&witness_script),
0,
&tx,
0,
&pv,
&psp,
None,
None,
crate::types::Network::Mainnet,
crate::script::SigVersion::WitnessV0,
None,
None,
None,
None, #[cfg(feature = "production")] None,
);
assert!(result.is_ok());
}
#[test]
fn test_segwit_p2wsh_validation() {
let p2wsh_hash = [0x51; 32]; let mut script_pubkey = vec![0x00]; script_pubkey.extend_from_slice(&p2wsh_hash);
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
};
let witness = vec![
vec![0x51], vec![0x51; 100], ];
let mut utxo_set = UtxoSet::default();
utxo_set.insert(
OutPoint { hash: [1; 32], index: 0 },
std::sync::Arc::new(UTXO {
value: 1000000,
script_pubkey: script_pubkey.clone().into(),
height: 0,
}),
);
let input = &tx.inputs[0];
let utxo = utxo_set.get(&input.prevout).unwrap();
let pv = vec![utxo.value];
let psp: Vec<&crate::types::ByteString> = vec![&utxo.script_pubkey];
let witness_script = witness.iter().flat_map(|w| w.iter().cloned()).collect::<Vec<u8>>();
let result = verify_script_with_context_full(
&input.script_sig,
&utxo.script_pubkey,
Some(&witness_script),
0,
&tx,
0,
&pv,
&psp,
None,
None,
crate::types::Network::Mainnet,
crate::script::SigVersion::WitnessV0,
None,
None,
None,
None, #[cfg(feature = "production")] None,
);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "production")]
fn test_p2wsh_multisig_fast_path() {
use blvm_consensus::crypto::OptimizedSha256;
use blvm_consensus::constants::BIP147_ACTIVATION_MAINNET;
let pk1 = [0x02u8; 33];
let pk2 = [0x03u8; 33];
let mut witness_script = vec![0x52]; witness_script.extend_from_slice(&pk1);
witness_script.extend_from_slice(&pk2);
witness_script.push(0x52); witness_script.push(0xae);
let wsh_hash = OptimizedSha256::new().hash(&witness_script);
let mut script_pubkey = vec![0x00, 0x20];
script_pubkey.extend_from_slice(&wsh_hash);
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}]
.into(),
lock_time: 0,
};
let witness: Witness = vec![
vec![0x00],
vec![0x30u8; 72],
vec![0x30u8; 72],
witness_script.clone(),
];
let mut utxo_set = UtxoSet::default();
utxo_set.insert(
OutPoint { hash: [1; 32], index: 0 },
std::sync::Arc::new(UTXO {
value: 1000000,
script_pubkey: script_pubkey.into(),
height: 0,
}),
);
let input = &tx.inputs[0];
let utxo = utxo_set.get(&input.prevout).unwrap();
let pv = vec![utxo.value];
let psp: Vec<&crate::types::ByteString> = vec![&utxo.script_pubkey];
let result = verify_script_with_context_full(
&input.script_sig,
&utxo.script_pubkey,
Some(&witness),
0x810,
&tx,
0,
&pv,
&psp,
Some(BIP147_ACTIVATION_MAINNET + 1),
None,
crate::types::Network::Mainnet,
crate::script::SigVersion::Base,
None,
None,
None,
None,
#[cfg(feature = "production")] None,
#[cfg(feature = "production")] None,
#[cfg(feature = "production")] None,
#[cfg(feature = "production")] None,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_segwit_weight_exceeds_limit() {
let mut block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![].into(),
};
let large_witness = vec![vec![0x51; 1000000]];
block.transactions.push(Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
});
let witnesses = vec![vec![], large_witness];
let block_weight = calculate_block_weight(&block, &witnesses).unwrap();
assert!(block_weight > 0);
let is_valid = validate_segwit_block(&block, &witnesses, MAX_BLOCK_WEIGHT).unwrap();
if block_weight > MAX_BLOCK_WEIGHT {
assert!(!is_valid);
}
}
#[test]
fn test_segwit_mixed_block() {
let block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![
Transaction {
version: 1,
inputs: vec![].into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}].into(),
lock_time: 0,
},
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
},
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [2; 32].into(), index: 0 },
script_sig: vec![0x51], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
},
],
};
let witnesses = vec![
vec![], vec![vec![0x51]], vec![], ];
let block_weight = calculate_block_weight(&block, &witnesses).unwrap();
assert!(block_weight > 0);
}
#[test]
fn test_segwit_witness_merkle_root() {
let block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![
Transaction {
version: 1,
inputs: vec![].into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}].into(),
lock_time: 0,
},
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
},
],
};
let witnesses = vec![
vec![], vec![vec![0x51]], ];
let witness_root = compute_witness_merkle_root(&block, &witnesses).unwrap();
assert_eq!(witness_root.len(), 32);
}
#[test]
fn test_segwit_witness_merkle_root_empty_block() {
let block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![].into(),
};
let witnesses = vec![];
let result = compute_witness_merkle_root(&block, &witnesses);
assert!(result.is_err());
}
#[test]
fn test_segwit_no_witness_weight() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x51],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
};
let weight_no_witness = calculate_transaction_weight(&tx, None).unwrap();
let weight_with_empty_witness = calculate_transaction_weight(&tx, Some(&vec![]), crate::types::Network::Mainnet).unwrap();
assert_eq!(weight_no_witness, weight_with_empty_witness);
}
#[test]
fn test_segwit_witness_commitment_validation() {
let mut coinbase_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [0; 32].into(), index: 0xffffffff },
script_sig: vec![0x51],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}].into(),
lock_time: 0,
};
let witness_root = [0x42u8; 32];
coinbase_tx.outputs[0].script_pubkey =
create_witness_commitment_script(&witness_root, &[0u8; 32]);
let is_valid = validate_witness_commitment(&coinbase_tx, &witness_root, &[]).unwrap();
assert!(is_valid);
let wrong_root = [0x99u8; 32];
let is_invalid = validate_witness_commitment(&coinbase_tx, &wrong_root, &[]).unwrap();
assert!(!is_invalid);
}
#[test]
fn test_segwit_is_segwit_transaction() {
let mut tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
};
assert!(is_segwit_transaction(&tx));
tx.inputs[0].script_sig = vec![0x51];
assert!(!is_segwit_transaction(&tx));
}
#[test]
fn test_segwit_weight_base_size() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x51; 50], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51; 25].into(), }].into(),
lock_time: 0,
};
let weight_no_witness = calculate_transaction_weight(&tx, None).unwrap();
let weight_with_witness = calculate_transaction_weight(&tx, Some(&vec![vec![0x51; 100]]), crate::types::Network::Mainnet).unwrap();
assert!(weight_with_witness > weight_no_witness);
}
#[test]
fn test_segwit_weight_precise_calculation() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00], sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
};
let witness = vec![vec![0x51; 100]];
let weight = calculate_transaction_weight(&tx, Some(&witness), crate::types::Network::Mainnet).unwrap();
assert!(weight > 0);
}
#[test]
fn test_segwit_block_weight_sum() {
let block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![
Transaction {
version: 1,
inputs: vec![].into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}].into(),
lock_time: 0,
},
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
},
],
};
let witnesses = vec![
vec![],
vec![vec![0x51]],
];
let block_weight = calculate_block_weight(&block, &witnesses).unwrap();
let tx0_weight = calculate_transaction_weight(&block.transactions[0], Some(&witnesses[0]), crate::types::Network::Mainnet).unwrap();
let tx1_weight = calculate_transaction_weight(&block.transactions[1], Some(&witnesses[1]), crate::types::Network::Mainnet).unwrap();
assert_eq!(block_weight, tx0_weight + tx1_weight);
}
#[test]
fn test_segwit_validate_block_weight_limit() {
let mut block = Block {
header: create_test_header(1234567890, [0; 32]),
transactions: vec![].into(),
};
block.transactions.push(Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint { hash: [1; 32].into(), index: 0 },
script_sig: vec![0x00],
sequence: 0xffffffff,
}].into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![0x51].into(),
}].into(),
lock_time: 0,
});
let witnesses = vec![vec![vec![0x51]]];
let is_valid = validate_segwit_block(&block, &witnesses, MAX_BLOCK_WEIGHT).unwrap();
assert!(is_valid);
}