use std::collections::HashMap;
use bsv::primitives::public_key::PublicKey;
use bsv::primitives::transaction_signature::{SIGHASH_ALL, SIGHASH_FORKID};
use bsv::script::locking_script::LockingScript;
use bsv::script::unlocking_script::UnlockingScript;
use bsv::transaction::transaction::Transaction;
use bsv::transaction::transaction_output::TransactionOutput;
use bsv::wallet::cached_key_deriver::CachedKeyDeriver;
use bsv::wallet::interfaces::SignActionSpend;
use crate::error::{WalletError, WalletResult};
use crate::signer::types::PendingStorageInput;
use crate::utility::script_template_brc29::ScriptTemplateBRC29;
fn hex_to_bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.filter_map(|i| {
if i + 2 <= hex.len() {
u8::from_str_radix(&hex[i..i + 2], 16).ok()
} else {
None
}
})
.collect()
}
pub fn complete_signed_transaction(
tx: &mut Transaction,
pending_inputs: &[PendingStorageInput],
spends: &HashMap<u32, SignActionSpend>,
key_deriver: &CachedKeyDeriver,
identity_pub_key: &PublicKey,
) -> WalletResult<Vec<u8>> {
let sighash = SIGHASH_ALL | SIGHASH_FORKID;
for (vin_key, spend) in spends {
let vin = *vin_key as usize;
if vin >= tx.inputs.len() {
return Err(WalletError::InvalidParameter {
parameter: "spends".to_string(),
must_be: format!("valid input index. vin {} out of range", vin),
});
}
tx.inputs[vin].unlocking_script =
Some(UnlockingScript::from_binary(&spend.unlocking_script));
if let Some(seq) = spend.sequence_number {
tx.inputs[vin].sequence = seq;
}
}
for pdi in pending_inputs {
let vin = pdi.vin as usize;
if vin >= tx.inputs.len() {
return Err(WalletError::InvalidParameter {
parameter: "pendingInputs".to_string(),
must_be: format!("valid input index. vin {} out of range", vin),
});
}
let sabppp =
ScriptTemplateBRC29::new(pdi.derivation_prefix.clone(), pdi.derivation_suffix.clone());
let unlocker_pub_key = if let Some(ref pub_key_hex) = pdi.unlocker_pub_key {
PublicKey::from_string(pub_key_hex)
.map_err(|e| WalletError::Internal(format!("Invalid unlocker pub key: {}", e)))?
} else {
identity_pub_key.clone()
};
let p2pkh = sabppp.unlock(key_deriver.root_key(), &unlocker_pub_key)?;
let source_locking_script = LockingScript::from_binary(&hex_to_bytes(&pdi.locking_script));
let mut source_tx = Transaction::new();
for _ in 0..tx.inputs[vin].source_output_index {
source_tx.add_output(TransactionOutput {
satoshis: Some(0),
locking_script: LockingScript::from_binary(&[]),
change: false,
});
}
source_tx.add_output(TransactionOutput {
satoshis: Some(pdi.source_satoshis),
locking_script: source_locking_script.clone(),
change: false,
});
tx.inputs[vin].source_transaction = Some(Box::new(source_tx));
tx.sign(
vin,
&p2pkh,
sighash,
pdi.source_satoshis,
&source_locking_script,
)
.map_err(|e| WalletError::Internal(format!("Failed to sign input {}: {}", vin, e)))?;
}
let mut buf = Vec::new();
tx.to_binary(&mut buf)
.map_err(|e| WalletError::Internal(format!("Failed to serialize signed tx: {}", e)))?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
use bsv::primitives::private_key::PrivateKey;
fn test_keys() -> (CachedKeyDeriver, PublicKey) {
let priv_key = PrivateKey::from_hex("aa").unwrap();
let pub_key = priv_key.to_public_key();
let key_deriver = CachedKeyDeriver::new(priv_key, None);
(key_deriver, pub_key)
}
#[test]
fn test_complete_signed_with_no_inputs() {
let (key_deriver, pub_key) = test_keys();
let mut tx = Transaction::new();
use bsv::script::locking_script::LockingScript;
use bsv::transaction::transaction_output::TransactionOutput;
tx.add_output(TransactionOutput {
satoshis: Some(1000),
locking_script: LockingScript::from_binary(&[
0x76, 0xa9, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xac,
]),
change: false,
});
let result =
complete_signed_transaction(&mut tx, &[], &HashMap::new(), &key_deriver, &pub_key);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(!bytes.is_empty());
}
#[test]
fn test_complete_signed_with_brc29_input() {
let (key_deriver, pub_key) = test_keys();
let sabppp = ScriptTemplateBRC29::new("prefix1".to_string(), "suffix1".to_string());
let lock_script_bytes = sabppp.lock(key_deriver.root_key(), &pub_key).unwrap();
let lock_script_hex: String = lock_script_bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect();
let mut tx = Transaction::new();
use bsv::transaction::transaction_input::TransactionInput;
tx.add_input(TransactionInput {
source_transaction: None,
source_txid: Some(
"aaaa1111bbbb2222cccc3333dddd4444aaaa1111bbbb2222cccc3333dddd4444".to_string(),
),
source_output_index: 0,
unlocking_script: Some(UnlockingScript::from_binary(&[])),
sequence: 0xFFFFFFFF,
});
use bsv::transaction::transaction_output::TransactionOutput;
tx.add_output(TransactionOutput {
satoshis: Some(5000),
locking_script: LockingScript::from_binary(&[
0x76, 0xa9, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xac,
]),
change: false,
});
let pdi = vec![PendingStorageInput {
vin: 0,
derivation_prefix: "prefix1".to_string(),
derivation_suffix: "suffix1".to_string(),
unlocker_pub_key: None,
source_satoshis: 10_000,
locking_script: lock_script_hex,
}];
let result =
complete_signed_transaction(&mut tx, &pdi, &HashMap::new(), &key_deriver, &pub_key);
assert!(result.is_ok(), "signing should succeed: {:?}", result.err());
let bytes = result.unwrap();
assert!(!bytes.is_empty(), "signed tx should not be empty");
let input = &tx.inputs[0];
assert!(
input.unlocking_script.is_some(),
"input should have unlocking script after signing"
);
let unlock_bytes = input.unlocking_script.as_ref().unwrap().to_binary();
assert!(
!unlock_bytes.is_empty(),
"unlocking script should not be empty after signing"
);
}
#[test]
fn test_complete_signed_invalid_vin_errors() {
let (key_deriver, pub_key) = test_keys();
let mut tx = Transaction::new();
let pdi = vec![PendingStorageInput {
vin: 5, derivation_prefix: "prefix".to_string(),
derivation_suffix: "suffix".to_string(),
unlocker_pub_key: None,
source_satoshis: 1000,
locking_script: "76a914000000000000000000000000000000000000000088ac".to_string(),
}];
let result =
complete_signed_transaction(&mut tx, &pdi, &HashMap::new(), &key_deriver, &pub_key);
assert!(result.is_err());
}
}