use bsv_rs::primitives::bsv::sighash::{SIGHASH_ALL, SIGHASH_FORKID};
use bsv_rs::primitives::ec::PrivateKey;
use bsv_rs::primitives::BigNumber;
use bsv_rs::script::templates::{Multisig, RPuzzle, RPuzzleType, P2PK, P2PKH};
use bsv_rs::script::ScriptTemplate;
#[test]
fn test_p2pkh_end_to_end_spend() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let pubkey_hash = public_key.hash160();
let template = P2PKH::new();
let locking_script = template.lock(&pubkey_hash).unwrap();
assert!(locking_script.to_asm().contains("OP_DUP"));
assert!(locking_script.to_asm().contains("OP_HASH160"));
assert!(locking_script.to_asm().contains("OP_EQUALVERIFY"));
assert!(locking_script.to_asm().contains("OP_CHECKSIG"));
let mock_sighash = [1u8; 32];
let unlocking_script = P2PKH::sign_with_sighash(
&private_key,
&mock_sighash,
bsv_rs::script::SignOutputs::All,
false,
)
.unwrap();
let chunks = unlocking_script.chunks();
assert_eq!(chunks.len(), 2);
let sig_data = chunks[0].data.as_ref().unwrap();
assert!(sig_data.len() >= 70 && sig_data.len() <= 73);
assert_eq!(
*sig_data.last().unwrap(),
(SIGHASH_ALL | SIGHASH_FORKID) as u8
);
let pubkey_data = chunks[1].data.as_ref().unwrap();
assert_eq!(pubkey_data.len(), 33);
assert_eq!(
pubkey_data.as_slice(),
public_key.to_compressed().as_slice()
);
}
#[test]
fn test_p2pkh_from_address() {
let private_key =
PrivateKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
.unwrap();
let public_key = private_key.public_key();
let address = public_key.to_address();
let locking_from_address = P2PKH::lock_from_address(&address).unwrap();
let pubkey_hash = public_key.hash160();
let template = P2PKH::new();
let locking_from_hash = template.lock(&pubkey_hash).unwrap();
assert_eq!(locking_from_address.to_hex(), locking_from_hash.to_hex());
}
#[test]
fn test_rpuzzle_lock_script_structure() {
let k = BigNumber::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
.unwrap();
let r_value = RPuzzle::compute_r_from_k(&k).unwrap();
let template = RPuzzle::new(RPuzzleType::Raw);
let locking = template.lock(&r_value).unwrap();
let asm = locking.to_asm();
assert!(asm.contains("OP_OVER"));
assert!(asm.contains("OP_3"));
assert!(asm.contains("OP_SPLIT"));
assert!(asm.contains("OP_NIP"));
assert!(asm.contains("OP_SWAP"));
assert!(asm.contains("OP_DROP"));
assert!(asm.contains("OP_EQUALVERIFY"));
assert!(asm.contains("OP_CHECKSIG"));
}
#[test]
fn test_rpuzzle_hashed_lock() {
let r_value = [0x42u8; 32];
let hash160 = bsv_rs::primitives::hash160(&r_value);
let template = RPuzzle::new(RPuzzleType::Hash160);
let locking = template.lock(&hash160).unwrap();
assert!(locking.to_asm().contains("OP_HASH160"));
let sha256 = bsv_rs::primitives::sha256(&r_value);
let template = RPuzzle::new(RPuzzleType::Sha256);
let locking = template.lock(&sha256).unwrap();
assert!(locking.to_asm().contains("OP_SHA256"));
}
#[test]
fn test_rpuzzle_unlock_k_value() {
let k = BigNumber::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
.unwrap();
let expected_r = RPuzzle::compute_r_from_k(&k).unwrap();
let private_key = PrivateKey::random();
let mock_sighash = [1u8; 32];
let unlocking = RPuzzle::sign_with_sighash(
&k,
&private_key,
&mock_sighash,
bsv_rs::script::SignOutputs::All,
false,
)
.unwrap();
let chunks = unlocking.chunks();
let sig_data = chunks[0].data.as_ref().unwrap();
let r_len = sig_data[3] as usize;
let r_start = 4;
let r_bytes = &sig_data[r_start..r_start + r_len];
let r_trimmed: Vec<u8> = r_bytes.iter().copied().skip_while(|&b| b == 0).collect();
let expected_trimmed: Vec<u8> = expected_r.iter().copied().skip_while(|&b| b == 0).collect();
assert_eq!(
r_trimmed, expected_trimmed,
"R value in signature should match k*G"
);
}
#[test]
fn test_template_unlock_estimate_length() {
let private_key = PrivateKey::random();
let p2pkh_unlock = P2PKH::unlock(&private_key, bsv_rs::script::SignOutputs::All, false);
assert_eq!(p2pkh_unlock.estimate_length(), 108);
let k = BigNumber::from_i64(1);
let rpuzzle_unlock = RPuzzle::unlock(&k, &private_key, bsv_rs::script::SignOutputs::All, false);
assert_eq!(rpuzzle_unlock.estimate_length(), 108);
}
#[test]
fn test_sighash_types() {
use bsv_rs::primitives::bsv::sighash::{SIGHASH_ANYONECANPAY, SIGHASH_NONE, SIGHASH_SINGLE};
use bsv_rs::script::SignOutputs;
let private_key = PrivateKey::random();
let sighash = [1u8; 32];
let unlocking =
P2PKH::sign_with_sighash(&private_key, &sighash, SignOutputs::All, false).unwrap();
let chunks = unlocking.chunks();
let sig_data = chunks[0].data.as_ref().unwrap();
assert_eq!(
*sig_data.last().unwrap(),
(SIGHASH_ALL | SIGHASH_FORKID) as u8
);
let unlocking =
P2PKH::sign_with_sighash(&private_key, &sighash, SignOutputs::None, false).unwrap();
let chunks = unlocking.chunks();
let sig_data = chunks[0].data.as_ref().unwrap();
assert_eq!(
*sig_data.last().unwrap(),
(SIGHASH_NONE | SIGHASH_FORKID) as u8
);
let unlocking =
P2PKH::sign_with_sighash(&private_key, &sighash, SignOutputs::Single, false).unwrap();
let chunks = unlocking.chunks();
let sig_data = chunks[0].data.as_ref().unwrap();
assert_eq!(
*sig_data.last().unwrap(),
(SIGHASH_SINGLE | SIGHASH_FORKID) as u8
);
let unlocking =
P2PKH::sign_with_sighash(&private_key, &sighash, SignOutputs::All, true).unwrap();
let chunks = unlocking.chunks();
let sig_data = chunks[0].data.as_ref().unwrap();
assert_eq!(
*sig_data.last().unwrap(),
(SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY) as u8
);
}
#[test]
fn test_p2pkh_spend_validation() {
let private_key = PrivateKey::random();
let pubkey_hash = private_key.public_key().hash160();
let template = P2PKH::new();
let locking_script = template.lock(&pubkey_hash).unwrap();
let mock_sighash = [0x42u8; 32];
let unlocking_script = P2PKH::sign_with_sighash(
&private_key,
&mock_sighash,
bsv_rs::script::SignOutputs::All,
false,
)
.unwrap();
assert!(!locking_script.to_binary().is_empty());
assert!(!unlocking_script.to_binary().is_empty());
assert_eq!(unlocking_script.chunks().len(), 2);
}
#[test]
fn test_rpuzzle_type_hash_functions() {
let data = b"test data for hashing";
assert_eq!(RPuzzleType::Raw.hash(data).len(), data.len());
assert_eq!(RPuzzleType::Sha1.hash(data).len(), 20);
assert_eq!(RPuzzleType::Sha256.hash(data).len(), 32);
assert_eq!(RPuzzleType::Hash256.hash(data).len(), 32);
assert_eq!(RPuzzleType::Ripemd160.hash(data).len(), 20);
assert_eq!(RPuzzleType::Hash160.hash(data).len(), 20);
assert_eq!(
RPuzzleType::Sha256.hash(data),
bsv_rs::primitives::sha256(data).to_vec()
);
assert_eq!(
RPuzzleType::Hash160.hash(data),
bsv_rs::primitives::hash160(data).to_vec()
);
}
#[test]
fn test_compute_r_from_k_known_values() {
let k1 = BigNumber::from_i64(1);
let r1 = RPuzzle::compute_r_from_k(&k1).unwrap();
let expected_gx =
hex::decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798").unwrap();
assert_eq!(r1.to_vec(), expected_gx);
let k2 = BigNumber::from_i64(2);
let r2 = RPuzzle::compute_r_from_k(&k2).unwrap();
assert_ne!(r1, r2);
}
#[test]
fn test_p2pk_end_to_end() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let pubkey_bytes = public_key.to_compressed();
let template = P2PK::new();
let locking = template.lock(&pubkey_bytes).unwrap();
let asm = locking.to_asm();
assert!(asm.contains("OP_CHECKSIG"));
assert!(!asm.contains("OP_DUP"));
assert!(!asm.contains("OP_HASH160"));
assert!(locking.as_script().is_p2pk());
assert!(!locking.as_script().is_p2pkh());
let mock_sighash = [1u8; 32];
let unlocking = P2PK::sign_with_sighash(
&private_key,
&mock_sighash,
bsv_rs::script::SignOutputs::All,
false,
)
.unwrap();
let chunks = unlocking.chunks();
assert_eq!(chunks.len(), 1);
let sig_data = chunks[0].data.as_ref().unwrap();
assert!(sig_data.len() >= 70 && sig_data.len() <= 73);
assert_eq!(
*sig_data.last().unwrap(),
(SIGHASH_ALL | SIGHASH_FORKID) as u8
);
}
#[test]
fn test_p2pk_compressed_vs_uncompressed_detection() {
let private_key = PrivateKey::random();
let compressed = private_key.public_key().to_compressed();
let template = P2PK::new();
let locking = template.lock(&compressed).unwrap();
assert!(locking.as_script().is_p2pk());
}
#[test]
fn test_p2pk_vs_p2pkh_estimate_length() {
let private_key = PrivateKey::random();
let p2pk_unlock = P2PK::unlock(&private_key, bsv_rs::script::SignOutputs::All, false);
let p2pkh_unlock = P2PKH::unlock(&private_key, bsv_rs::script::SignOutputs::All, false);
assert_eq!(p2pk_unlock.estimate_length(), 74);
assert_eq!(p2pkh_unlock.estimate_length(), 108);
assert!(p2pk_unlock.estimate_length() < p2pkh_unlock.estimate_length());
}
#[test]
fn test_p2pk_invalid_pubkey() {
let template = P2PK::new();
assert!(template.lock(&[0x02; 20]).is_err());
assert!(template.lock(&[0x02; 32]).is_err());
let mut bad = [0u8; 33];
bad[0] = 0x05;
assert!(template.lock(&bad).is_err());
}
#[test]
fn test_multisig_2_of_3_end_to_end() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let key3 = PrivateKey::random();
let template = Multisig::new(2);
let pubkeys = vec![key1.public_key(), key2.public_key(), key3.public_key()];
let locking = template.lock_from_keys(&pubkeys).unwrap();
assert_eq!(locking.as_script().is_multisig(), Some((2, 3)));
let asm = locking.to_asm();
assert!(asm.contains("OP_2"));
assert!(asm.contains("OP_3"));
assert!(asm.contains("OP_CHECKMULTISIG"));
let mock_sighash = [1u8; 32];
let unlocking = Multisig::sign_with_sighash(
&[key1.clone(), key2.clone()],
&mock_sighash,
bsv_rs::script::SignOutputs::All,
false,
)
.unwrap();
let chunks = unlocking.chunks();
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0].op, 0x00);
assert!(chunks[0].data.is_none());
for chunk in &chunks[1..=2] {
let sig = chunk.data.as_ref().unwrap();
assert!(sig.len() >= 70 && sig.len() <= 73);
}
}
#[test]
fn test_multisig_1_of_1() {
let key = PrivateKey::random();
let template = Multisig::new(1);
let locking = template.lock_from_keys(&[key.public_key()]).unwrap();
assert_eq!(locking.as_script().is_multisig(), Some((1, 1)));
let chunks = locking.chunks();
assert_eq!(chunks.len(), 4);
}
#[test]
fn test_multisig_3_of_3() {
let keys: Vec<PrivateKey> = (0..3).map(|_| PrivateKey::random()).collect();
let pubkeys: Vec<_> = keys.iter().map(|k| k.public_key()).collect();
let template = Multisig::new(3);
let locking = template.lock_from_keys(&pubkeys).unwrap();
assert_eq!(locking.as_script().is_multisig(), Some((3, 3)));
let mock_sighash = [1u8; 32];
let unlocking = Multisig::sign_with_sighash(
&keys,
&mock_sighash,
bsv_rs::script::SignOutputs::All,
false,
)
.unwrap();
assert_eq!(unlocking.chunks().len(), 4);
}
#[test]
fn test_multisig_script_template_trait() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let key3 = PrivateKey::random();
let pk1 = key1.public_key().to_compressed();
let pk2 = key2.public_key().to_compressed();
let pk3 = key3.public_key().to_compressed();
let mut params = Vec::with_capacity(99);
params.extend_from_slice(&pk1);
params.extend_from_slice(&pk2);
params.extend_from_slice(&pk3);
let template = Multisig::new(2);
let locking = template.lock(¶ms).unwrap();
assert_eq!(locking.as_script().is_multisig(), Some((2, 3)));
let locking2 = template
.lock_from_keys(&[key1.public_key(), key2.public_key(), key3.public_key()])
.unwrap();
assert_eq!(locking.to_hex(), locking2.to_hex());
}
#[test]
fn test_multisig_validation_errors() {
let key = PrivateKey::random();
let template = Multisig::new(3);
assert!(template.lock_from_keys(&[key.public_key()]).is_err());
let template = Multisig::new(0);
assert!(template.lock_from_keys(&[key.public_key()]).is_err());
let keys: Vec<_> = (0..17).map(|_| PrivateKey::random().public_key()).collect();
let template = Multisig::new(1);
assert!(template.lock_from_keys(&keys).is_err());
let template = Multisig::new(1);
assert!(template.lock_from_keys(&[]).is_err());
}
#[test]
fn test_multisig_estimate_length_scaling() {
let key = PrivateKey::random();
let unlock1 = Multisig::unlock(
std::slice::from_ref(&key),
bsv_rs::script::SignOutputs::All,
false,
);
assert_eq!(unlock1.estimate_length(), 75);
let unlock2 = Multisig::unlock(
&[key.clone(), key.clone()],
bsv_rs::script::SignOutputs::All,
false,
);
assert_eq!(unlock2.estimate_length(), 149);
let unlock3 = Multisig::unlock(
&[key.clone(), key.clone(), key.clone()],
bsv_rs::script::SignOutputs::All,
false,
);
assert_eq!(unlock3.estimate_length(), 223);
}
#[test]
fn test_multisig_max_keys() {
let keys: Vec<PrivateKey> = (0..16).map(|_| PrivateKey::random()).collect();
let pubkeys: Vec<_> = keys.iter().map(|k| k.public_key()).collect();
let template = Multisig::new(16);
let locking = template.lock_from_keys(&pubkeys).unwrap();
assert_eq!(locking.as_script().is_multisig(), Some((16, 16)));
}
#[test]
fn test_multisig_hex_roundtrip() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let template = Multisig::new(1);
let locking = template
.lock_from_keys(&[key1.public_key(), key2.public_key()])
.unwrap();
let hex = locking.to_hex();
let parsed = bsv_rs::script::LockingScript::from_hex(&hex).unwrap();
assert_eq!(parsed.to_hex(), hex);
assert_eq!(parsed.as_script().is_multisig(), Some((1, 2)));
}