use std::collections::HashMap;
use std::io::Cursor;
use std::sync::Arc;
use async_trait::async_trait;
use bsv::primitives::public_key::PublicKey;
use bsv::transaction::transaction::Transaction;
use bsv::wallet::cached_key_deriver::CachedKeyDeriver;
use bsv::wallet::interfaces::AbortActionResult;
use crate::error::{WalletError, WalletResult};
use crate::services::traits::WalletServices;
use crate::signer::traits::WalletSigner;
use crate::signer::types::{
PendingSignAction, PendingStorageInput, SignerCreateActionResult,
SignerInternalizeActionResult, SignerSignActionResult, ValidAbortActionArgs,
ValidCreateActionArgs, ValidInternalizeActionArgs, ValidSignActionArgs,
};
use crate::status::TransactionStatus;
use crate::storage::action_types::StorageCreateActionResult;
use crate::storage::find_args::{
FindOutputsArgs, FindTransactionsArgs, OutputPartial, TransactionPartial,
};
use crate::storage::manager::WalletStorageManager;
use crate::types::Chain;
pub struct DefaultWalletSigner {
pub storage: Arc<WalletStorageManager>,
pub services: Arc<dyn WalletServices>,
pub key_deriver: Arc<CachedKeyDeriver>,
pub chain: Chain,
pub identity_key: PublicKey,
pending_sign_actions: tokio::sync::Mutex<HashMap<String, PendingSignAction>>,
}
impl DefaultWalletSigner {
pub fn new(
storage: Arc<WalletStorageManager>,
services: Arc<dyn WalletServices>,
key_deriver: Arc<CachedKeyDeriver>,
chain: Chain,
identity_key: PublicKey,
) -> Self {
Self {
storage,
services,
key_deriver,
chain,
identity_key,
pending_sign_actions: tokio::sync::Mutex::new(HashMap::new()),
}
}
fn auth(&self) -> String {
self.identity_key.to_der_hex()
}
async fn recover_pending_sign_action(
&self,
reference: &str,
) -> WalletResult<PendingSignAction> {
let auth = self.auth();
let user = self
.storage
.find_user_by_identity_key(&auth)
.await?
.ok_or_else(|| WalletError::Unauthorized("User not found".to_string()))?;
let txs = self
.storage
.find_transactions(&FindTransactionsArgs {
partial: TransactionPartial {
user_id: Some(user.user_id),
reference: Some(reference.to_string()),
..Default::default()
},
no_raw_tx: false,
..Default::default()
})
.await?;
let tx_record = txs
.into_iter()
.next()
.ok_or_else(|| WalletError::InvalidParameter {
parameter: "reference".to_string(),
must_be: "a reference for an existing unsigned transaction".to_string(),
})?;
let signable_statuses = [TransactionStatus::Unsigned, TransactionStatus::Nosend];
if !signable_statuses.contains(&tx_record.status) {
return Err(WalletError::InvalidOperation(format!(
"Transaction '{}' has status '{}' and cannot be signed",
reference, tx_record.status
)));
}
let raw_tx_bytes = tx_record.raw_tx.ok_or_else(|| {
WalletError::Internal(format!(
"Transaction '{}' has no raw_tx stored for signing recovery",
reference
))
})?;
let input_beef = tx_record.input_beef;
let mut cursor = Cursor::new(&raw_tx_bytes);
let unsigned_tx = Transaction::from_binary(&mut cursor).map_err(|e| {
WalletError::Internal(format!(
"Failed to parse stored unsigned transaction for reference '{}': {}",
reference, e
))
})?;
let spent_outputs = self
.storage
.find_outputs_storage(&FindOutputsArgs {
partial: OutputPartial {
spent_by: Some(tx_record.transaction_id),
..Default::default()
},
no_script: false,
..Default::default()
})
.await?;
let mut pdi: Vec<PendingStorageInput> = Vec::new();
for (vin, input) in unsigned_tx.inputs.iter().enumerate() {
let source_txid = input.source_txid.as_deref().unwrap_or("");
let source_vout = input.source_output_index;
let spent_output = spent_outputs
.iter()
.find(|o| o.txid.as_deref() == Some(source_txid) && o.vout == source_vout as i32);
if let Some(output) = spent_output {
let derivation_prefix = output.derivation_prefix.clone().unwrap_or_default();
let derivation_suffix = output.derivation_suffix.clone().unwrap_or_default();
if !derivation_prefix.is_empty() || !derivation_suffix.is_empty() {
let locking_script_hex = output
.locking_script
.as_ref()
.map(|b| {
b.iter()
.map(|byte| format!("{:02x}", byte))
.collect::<String>()
})
.unwrap_or_default();
pdi.push(PendingStorageInput {
vin: vin as u32,
derivation_prefix,
derivation_suffix,
unlocker_pub_key: output.sender_identity_key.clone(),
source_satoshis: output.satoshis as u64,
locking_script: locking_script_hex,
});
}
}
}
let dcr = StorageCreateActionResult {
reference: reference.to_string(),
version: tx_record.version.unwrap_or(1) as u32,
lock_time: tx_record.lock_time.unwrap_or(0) as u32,
inputs: vec![],
outputs: vec![],
derivation_prefix: String::new(),
input_beef,
no_send_change_output_vouts: None,
};
use bsv::wallet::interfaces::CreateActionOptions;
let args_stub = ValidCreateActionArgs {
description: tx_record.description,
inputs: vec![],
outputs: vec![],
lock_time: dcr.lock_time,
version: dcr.version,
labels: vec![],
options: CreateActionOptions::default(),
input_beef: None,
is_new_tx: true,
is_sign_action: true,
is_no_send: tx_record.status == TransactionStatus::Nosend,
is_delayed: false,
is_send_with: false,
};
Ok(PendingSignAction {
reference: reference.to_string(),
dcr,
args: args_stub,
tx: raw_tx_bytes,
amount: 0, pdi,
})
}
}
#[async_trait]
impl WalletSigner for DefaultWalletSigner {
async fn create_action(
&self,
args: ValidCreateActionArgs,
) -> WalletResult<SignerCreateActionResult> {
let (result, pending) = crate::signer::methods::create_action::signer_create_action(
self.storage.as_ref(),
self.services.as_ref(),
&self.key_deriver,
&self.identity_key,
&self.auth(),
&args,
)
.await?;
if let Some(p) = pending {
let mut map = self.pending_sign_actions.lock().await;
map.insert(p.reference.clone(), p);
}
Ok(result)
}
async fn sign_action(&self, args: ValidSignActionArgs) -> WalletResult<SignerSignActionResult> {
let pending = {
let mut map = self.pending_sign_actions.lock().await;
map.remove(&args.reference)
};
let pending = match pending {
Some(p) => p,
None => self.recover_pending_sign_action(&args.reference).await?,
};
let result = crate::signer::methods::sign_action::signer_sign_action(
self.storage.as_ref(),
self.services.as_ref(),
&self.key_deriver,
&self.identity_key,
&self.auth(),
&args,
&pending,
)
.await?;
Ok(result)
}
async fn internalize_action(
&self,
args: ValidInternalizeActionArgs,
) -> WalletResult<SignerInternalizeActionResult> {
crate::signer::methods::internalize_action::signer_internalize_action(
self.storage.as_ref(),
self.services.as_ref(),
&self.key_deriver,
&self.auth(),
&args,
)
.await
}
async fn abort_action(&self, args: ValidAbortActionArgs) -> WalletResult<AbortActionResult> {
{
let mut map = self.pending_sign_actions.lock().await;
map.remove(&args.reference);
}
crate::signer::methods::abort_action::signer_abort_action(
self.storage.as_ref(),
&self.auth(),
&args,
)
.await
}
}