use crate::primitives::bsv::sighash::{
compute_sighash_for_signing, parse_transaction, SighashParams, SIGHASH_ALL,
SIGHASH_ANYONECANPAY, SIGHASH_FORKID, SIGHASH_NONE, SIGHASH_SINGLE,
};
use crate::primitives::bsv::TransactionSignature;
use crate::primitives::ec::PrivateKey;
use crate::script::{LockingScript, UnlockingScript};
use crate::Result;
pub use crate::script::Script;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignOutputs {
All,
None,
Single,
}
impl SignOutputs {
pub fn to_sighash_flag(self) -> u32 {
match self {
SignOutputs::All => SIGHASH_ALL,
SignOutputs::None => SIGHASH_NONE,
SignOutputs::Single => SIGHASH_SINGLE,
}
}
}
#[derive(Debug, Clone)]
pub struct SigningContext<'a> {
pub raw_tx: &'a [u8],
pub input_index: usize,
pub source_satoshis: u64,
pub locking_script: &'a Script,
}
impl<'a> SigningContext<'a> {
pub fn new(
raw_tx: &'a [u8],
input_index: usize,
source_satoshis: u64,
locking_script: &'a Script,
) -> Self {
Self {
raw_tx,
input_index,
source_satoshis,
locking_script,
}
}
pub fn compute_sighash(&self, scope: u32) -> Result<[u8; 32]> {
let tx = parse_transaction(self.raw_tx)?;
let subscript = self.locking_script.to_binary();
Ok(compute_sighash_for_signing(&SighashParams {
version: tx.version,
inputs: &tx.inputs,
outputs: &tx.outputs,
locktime: tx.locktime,
input_index: self.input_index,
subscript: &subscript,
satoshis: self.source_satoshis,
scope,
}))
}
}
pub fn compute_sighash_scope(sign_outputs: SignOutputs, anyone_can_pay: bool) -> u32 {
let mut scope = SIGHASH_FORKID | sign_outputs.to_sighash_flag();
if anyone_can_pay {
scope |= SIGHASH_ANYONECANPAY;
}
scope
}
pub trait ScriptTemplate {
fn lock(&self, params: &[u8]) -> Result<LockingScript>;
}
#[allow(clippy::type_complexity)]
pub struct ScriptTemplateUnlock {
sign_fn: Box<dyn Fn(&SigningContext) -> Result<UnlockingScript> + Send + Sync>,
estimate_length_fn: Box<dyn Fn() -> usize + Send + Sync>,
}
impl ScriptTemplateUnlock {
pub fn new<S, E>(sign_fn: S, estimate_length_fn: E) -> Self
where
S: Fn(&SigningContext) -> Result<UnlockingScript> + Send + Sync + 'static,
E: Fn() -> usize + Send + Sync + 'static,
{
Self {
sign_fn: Box::new(sign_fn),
estimate_length_fn: Box::new(estimate_length_fn),
}
}
pub fn sign(&self, context: &SigningContext) -> Result<UnlockingScript> {
(self.sign_fn)(context)
}
pub fn estimate_length(&self) -> usize {
(self.estimate_length_fn)()
}
}
impl std::fmt::Debug for ScriptTemplateUnlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScriptTemplateUnlock")
.field("estimate_length", &self.estimate_length())
.finish_non_exhaustive()
}
}
pub fn create_transaction_signature(
private_key: &PrivateKey,
sighash: &[u8; 32],
scope: u32,
) -> Result<TransactionSignature> {
let signature = private_key.sign(sighash)?;
Ok(TransactionSignature::new(signature, scope))
}
pub fn build_p2pkh_unlocking_script(
tx_sig: &TransactionSignature,
private_key: &PrivateKey,
) -> UnlockingScript {
let sig_bytes = tx_sig.to_checksig_format();
let pubkey_bytes = private_key.public_key().to_compressed();
let mut script = Script::new();
script.write_bin(&sig_bytes).write_bin(&pubkey_bytes);
UnlockingScript::from_script(script)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_outputs_to_sighash() {
assert_eq!(SignOutputs::All.to_sighash_flag(), SIGHASH_ALL);
assert_eq!(SignOutputs::None.to_sighash_flag(), SIGHASH_NONE);
assert_eq!(SignOutputs::Single.to_sighash_flag(), SIGHASH_SINGLE);
}
#[test]
fn test_compute_sighash_scope() {
assert_eq!(
compute_sighash_scope(SignOutputs::All, false),
SIGHASH_ALL | SIGHASH_FORKID
);
assert_eq!(
compute_sighash_scope(SignOutputs::All, true),
SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY
);
assert_eq!(
compute_sighash_scope(SignOutputs::None, false),
SIGHASH_NONE | SIGHASH_FORKID
);
assert_eq!(
compute_sighash_scope(SignOutputs::Single, true),
SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY
);
}
}