use bsv_rs::script::{LockingScript, Script, Spend, SpendParams, UnlockingScript};
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SpendVector {
script_sig: String,
script_pub_key: String,
comment: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ScriptVector {
script_sig: String,
script_pub_key: String,
flags: String,
comment: String,
}
fn load_spend_vectors() -> Vec<SpendVector> {
let data = fs::read_to_string("tests/vectors/spend_valid.json")
.expect("Failed to read spend_valid.json");
serde_json::from_str(&data).expect("Failed to parse spend_valid.json")
}
fn run_spend_vector(index: usize, vector: &SpendVector) -> Result<bool, String> {
let unlocking_script = if vector.script_sig.is_empty() {
UnlockingScript::new()
} else {
UnlockingScript::from_hex(&vector.script_sig).map_err(|e| {
format!(
"Vector {}: Failed to parse scriptSig '{}': {}",
index, vector.script_sig, e
)
})?
};
let locking_script = if vector.script_pub_key.is_empty() {
LockingScript::new()
} else {
LockingScript::from_hex(&vector.script_pub_key).map_err(|e| {
format!(
"Vector {}: Failed to parse scriptPubKey '{}': {}",
index, vector.script_pub_key, e
)
})?
};
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
spend.validate().map_err(|e| {
format!(
"Vector {}: Validation failed - {} (comment: {})",
index, e, vector.comment
)
})
}
#[test]
fn test_spend_valid_vectors() {
let vectors = load_spend_vectors();
let total = vectors.len();
let mut passed = 0;
let mut failed = 0;
let mut failures: Vec<String> = Vec::new();
for (i, vector) in vectors.iter().enumerate() {
match run_spend_vector(i, vector) {
Ok(true) => {
passed += 1;
}
Ok(false) => {
failed += 1;
failures.push(format!(
"Vector {}: Validation returned false (expected true) - scriptSig: '{}', scriptPubKey: '{}', comment: '{}'",
i, vector.script_sig, vector.script_pub_key, vector.comment
));
}
Err(e) => {
failed += 1;
failures.push(e);
}
}
}
println!("\n=== Spend Valid Vectors Summary ===");
println!("Total: {}, Passed: {}, Failed: {}", total, passed, failed);
if !failures.is_empty() {
println!("\nFirst 20 failures:");
for (i, failure) in failures.iter().take(20).enumerate() {
println!(" {}. {}", i + 1, failure);
}
panic!(
"Spend valid vectors: {}/{} passed ({} failed)",
passed, total, failed
);
}
}
fn load_script_valid_vectors() -> Vec<ScriptVector> {
let data = fs::read_to_string("tests/vectors/script_valid.json")
.expect("Failed to read script_valid.json");
serde_json::from_str(&data).expect("Failed to parse script_valid.json")
}
#[test]
fn test_script_valid_vectors_parsing() {
let vectors = load_script_valid_vectors();
let total = vectors.len();
let mut passed = 0;
let mut failed = 0;
let mut failures: Vec<String> = Vec::new();
for (i, vector) in vectors.iter().enumerate() {
let mut test_passed = true;
if !vector.script_sig.is_empty() {
match Script::from_hex(&vector.script_sig) {
Ok(script) => {
let _asm = script.to_asm();
let _hex = script.to_hex();
let roundtrip_hex = Script::from_hex(&vector.script_sig)
.map(|s| s.to_hex())
.unwrap_or_default();
if roundtrip_hex.to_lowercase() != vector.script_sig.to_lowercase() {
test_passed = false;
failures.push(format!(
"Vector {}: scriptSig hex roundtrip mismatch - original: '{}', roundtrip: '{}'",
i, vector.script_sig, roundtrip_hex
));
}
}
Err(e) => {
test_passed = false;
failures.push(format!(
"Vector {}: Failed to parse scriptSig '{}': {}",
i, vector.script_sig, e
));
}
}
}
if !vector.script_pub_key.is_empty() {
match Script::from_hex(&vector.script_pub_key) {
Ok(script) => {
let _asm = script.to_asm();
let roundtrip_hex = script.to_hex();
if roundtrip_hex.to_lowercase() != vector.script_pub_key.to_lowercase() {
test_passed = false;
failures.push(format!(
"Vector {}: scriptPubKey hex roundtrip mismatch - original: '{}', roundtrip: '{}'",
i, vector.script_pub_key, roundtrip_hex
));
}
}
Err(e) => {
test_passed = false;
failures.push(format!(
"Vector {}: Failed to parse scriptPubKey '{}': {}",
i, vector.script_pub_key, e
));
}
}
}
if test_passed {
passed += 1;
} else {
failed += 1;
}
}
println!("\n=== Script Valid Vectors (Parsing) Summary ===");
println!("Total: {}, Passed: {}, Failed: {}", total, passed, failed);
if !failures.is_empty() {
println!("\nFirst 10 failures:");
for (i, failure) in failures.iter().take(10).enumerate() {
println!(" {}. {}", i + 1, failure);
}
if failed > 10 {
panic!(
"Script valid vectors (parsing): {}/{} passed ({} failed)",
passed, total, failed
);
}
}
}
#[test]
fn test_script_valid_vectors_execution() {
let vectors = load_script_valid_vectors();
let total = vectors.len();
let mut passed = 0;
let mut skipped = 0;
let mut failed = 0;
let mut failures: Vec<String> = Vec::new();
for (i, vector) in vectors.iter().enumerate() {
if vector.flags.contains("P2SH") && !vector.flags.contains("STRICTENC") {
skipped += 1;
continue;
}
let unlocking_script = if vector.script_sig.is_empty() {
UnlockingScript::new()
} else {
match UnlockingScript::from_hex(&vector.script_sig) {
Ok(s) => s,
Err(e) => {
failed += 1;
failures.push(format!("Vector {}: Parse error: {}", i, e));
continue;
}
}
};
let locking_script = if vector.script_pub_key.is_empty() {
LockingScript::new()
} else {
match LockingScript::from_hex(&vector.script_pub_key) {
Ok(s) => s,
Err(e) => {
failed += 1;
failures.push(format!("Vector {}: Parse error: {}", i, e));
continue;
}
}
};
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
match spend.validate() {
Ok(true) => {
passed += 1;
}
Ok(false) => {
failed += 1;
failures.push(format!(
"Vector {}: Validation returned false (flags: {}, comment: {})",
i, vector.flags, vector.comment
));
}
Err(e) => {
failed += 1;
failures.push(format!(
"Vector {}: Execution failed - {} (flags: {}, comment: {})",
i, e, vector.flags, vector.comment
));
}
}
}
println!("\n=== Script Valid Vectors (Execution) Summary ===");
println!(
"Total: {}, Passed: {}, Skipped: {}, Failed: {}",
total, passed, skipped, failed
);
println!(
"\nNote: Some vectors fail due to BTC-specific behavior (P2SH, non-push scriptSig, etc.)"
);
println!("The BSV-specific spend.valid.vectors should all pass.");
let pass_rate = (passed as f64) / (total as f64) * 100.0;
let min_pass_rate = 35.0;
assert!(
pass_rate >= min_pass_rate,
"Script valid vectors execution pass rate {:.1}% is below minimum {:.1}%. \
Passed: {}/{} (skipped: {}, failed: {}). This indicates a regression.",
pass_rate,
min_pass_rate,
passed,
total,
skipped,
failed
);
}
fn load_script_invalid_vectors() -> Vec<ScriptVector> {
let data = fs::read_to_string("tests/vectors/script_invalid.json")
.expect("Failed to read script_invalid.json");
serde_json::from_str(&data).expect("Failed to parse script_invalid.json")
}
#[test]
fn test_script_invalid_vectors() {
let vectors = load_script_invalid_vectors();
let total = vectors.len();
let mut passed = 0;
let mut failed = 0;
let mut failures: Vec<String> = Vec::new();
for (i, vector) in vectors.iter().enumerate() {
let unlocking_result = if vector.script_sig.is_empty() {
Ok(UnlockingScript::new())
} else {
UnlockingScript::from_hex(&vector.script_sig)
};
let locking_result = if vector.script_pub_key.is_empty() {
Ok(LockingScript::new())
} else {
LockingScript::from_hex(&vector.script_pub_key)
};
let (unlocking_script, locking_script) = match (unlocking_result, locking_result) {
(Ok(u), Ok(l)) => (u, l),
_ => {
passed += 1;
continue;
}
};
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
match spend.validate() {
Ok(true) => {
failed += 1;
failures.push(format!(
"Vector {}: Expected to fail but passed - scriptSig: '{}', scriptPubKey: '{}', comment: '{}'",
i, vector.script_sig, vector.script_pub_key, vector.comment
));
}
Ok(false) | Err(_) => {
passed += 1;
}
}
}
println!("\n=== Script Invalid Vectors Summary ===");
println!("Total: {}, Passed: {}, Failed: {}", total, passed, failed);
if !failures.is_empty() {
println!("\nFirst 20 failures (vectors that passed but should have failed):");
for (i, failure) in failures.iter().take(20).enumerate() {
println!(" {}. {}", i + 1, failure);
}
let false_positive_rate = (failed as f64) / (total as f64) * 100.0;
let max_false_positive_rate = 5.0;
if false_positive_rate > max_false_positive_rate {
panic!(
"Script invalid vectors: {:.1}% false positives ({} of {} incorrectly passed). \
Maximum allowed is {:.1}%. {}/{} correctly failed.",
false_positive_rate, failed, total, max_false_positive_rate, passed, total
);
}
}
}
#[test]
fn test_arithmetic_script() {
let unlocking = UnlockingScript::from_hex("5152").unwrap(); let locking = LockingScript::from_hex("935387").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap(), "1 + 2 should equal 3");
}
#[test]
fn test_op_depth() {
let unlocking = UnlockingScript::new();
let locking = LockingScript::from_hex("740087").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_conditional() {
let unlocking = UnlockingScript::from_hex("51").unwrap();
let locking = LockingScript::from_hex("635167005168").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_cat() {
let unlocking = UnlockingScript::from_hex("026162026364").unwrap(); let locking = LockingScript::from_hex("7e046162636487").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_split() {
let unlocking = UnlockingScript::from_hex("0461626364").unwrap(); let locking = LockingScript::from_hex("527f0263648802616287").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_mul() {
let unlocking = UnlockingScript::from_hex("5357").unwrap(); let locking = LockingScript::from_hex("95011587").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_div() {
let unlocking = UnlockingScript::from_hex("011557").unwrap(); let locking = LockingScript::from_hex("965387").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_hash_operations() {
use bsv_rs::primitives::sha256;
let data = vec![0x01, 0x02, 0x03];
let expected_hash = sha256(&data);
let mut unlocking = Script::new();
unlocking.write_bin(&data);
let unlocking = UnlockingScript::from_script(unlocking);
let mut locking = Script::new();
locking.write_opcode(0xa8); locking.write_bin(&expected_hash);
locking.write_opcode(0x87); let locking = LockingScript::from_script(locking);
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
assert!(spend.validate().unwrap());
}
#[test]
fn test_first_spend_vector_detailed() {
let vectors = load_spend_vectors();
if vectors.is_empty() {
return;
}
let v = &vectors[0];
println!("First vector:");
println!(" scriptSig: '{}'", v.script_sig);
println!(" scriptPubKey: '{}'", v.script_pub_key);
println!(" comment: '{}'", v.comment);
let unlocking = if v.script_sig.is_empty() {
UnlockingScript::new()
} else {
UnlockingScript::from_hex(&v.script_sig).expect("Failed to parse scriptSig")
};
let locking = if v.script_pub_key.is_empty() {
LockingScript::new()
} else {
LockingScript::from_hex(&v.script_pub_key).expect("Failed to parse scriptPubKey")
};
println!(" unlocking ASM: '{}'", unlocking.to_asm());
println!(" locking ASM: '{}'", locking.to_asm());
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
let result = spend.validate();
println!(" result: {:?}", result);
assert!(result.is_ok() && result.unwrap());
}