Skip to main content

bsv_wallet_toolbox_rs/wallet/
signer.rs

1//! Wallet Signer
2//!
3//! This module provides the `WalletSigner` struct for signing transaction inputs
4//! using derived keys from the wallet's ProtoWallet.
5
6use bsv_rs::primitives::PrivateKey;
7use bsv_rs::wallet::{Counterparty, KeyDeriverApi, ProtoWallet};
8use serde::{Deserialize, Serialize};
9
10use crate::error::{Error, Result};
11
12// =============================================================================
13// UnlockingScriptTemplate Types
14// =============================================================================
15
16/// Template for generating unlocking scripts via BRC-29 key derivation.
17/// Used for deferred signing where the wallet controls the keys.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct UnlockingScriptTemplate {
20    /// Key derivation prefix (hex).
21    pub derivation_prefix: String,
22    /// Key derivation suffix (hex).
23    pub derivation_suffix: String,
24    /// The type of script to generate.
25    pub script_type: ScriptType,
26    /// Satoshis for this input (required for correct BIP-143 sighash).
27    pub satoshis: u64,
28}
29
30/// Supported script types for template-based signing.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum ScriptType {
33    /// Pay-to-Public-Key-Hash
34    P2PKH,
35    /// Pay-to-Public-Key
36    P2PK,
37}
38
39/// Input details from storage for transaction creation.
40///
41/// This is used to pass input information to the signer.
42/// The full type is in `crate::storage::StorageCreateTransactionInput`.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct SignerInput {
45    pub vin: u32,
46    pub source_txid: String,
47    pub source_vout: u32,
48    pub satoshis: u64,
49    pub source_locking_script: Option<Vec<u8>>,
50    pub unlocking_script: Option<Vec<u8>>,
51    pub derivation_prefix: Option<String>,
52    pub derivation_suffix: Option<String>,
53    pub sender_identity_key: Option<String>,
54}
55
56// =============================================================================
57// WalletSigner
58// =============================================================================
59
60/// Handles transaction signing for the wallet.
61///
62/// The `WalletSigner` uses key derivation to sign transaction inputs based on
63/// their derivation paths (prefix and suffix). It integrates with the ProtoWallet's
64/// key deriver to compute the appropriate signing keys.
65///
66/// # Example
67///
68/// ```rust,ignore
69/// let signer = WalletSigner::new(Some(root_key));
70/// let signed_tx = signer.sign_transaction(&unsigned_tx, &inputs, &proto_wallet)?;
71/// ```
72#[derive(Debug)]
73pub struct WalletSigner {
74    /// Root private key for key derivation
75    #[allow(dead_code)]
76    root_key: Option<PrivateKey>,
77}
78
79impl WalletSigner {
80    /// Creates a new WalletSigner.
81    ///
82    /// # Arguments
83    ///
84    /// * `root_key` - The root private key for key derivation. If None, uses "anyone" key.
85    pub fn new(root_key: Option<PrivateKey>) -> Self {
86        Self { root_key }
87    }
88
89    /// Signs a transaction using the provided inputs and ProtoWallet.
90    ///
91    /// This method:
92    /// 1. Parses the unsigned transaction
93    /// 2. For each input that needs signing:
94    ///    - Derives the signing key using the input's derivation path
95    ///    - Creates the signature
96    ///    - Constructs the unlocking script
97    /// 3. Returns the fully signed transaction
98    ///
99    /// # Arguments
100    ///
101    /// * `unsigned_tx` - The unsigned transaction bytes (from create_action)
102    /// * `inputs` - Input metadata including derivation info
103    /// * `proto_wallet` - The ProtoWallet for key derivation
104    ///
105    /// # Returns
106    ///
107    /// The signed transaction bytes.
108    pub fn sign_transaction(
109        &self,
110        unsigned_tx: &[u8],
111        inputs: &[SignerInput],
112        proto_wallet: &ProtoWallet,
113    ) -> Result<Vec<u8>> {
114        // Parse the unsigned transaction
115        let mut tx_data = unsigned_tx.to_vec();
116
117        // For each input that has derivation info, we need to sign it
118        for (vin, input) in inputs.iter().enumerate() {
119            // Skip inputs that don't need signing (have unlocking script already)
120            if input.unlocking_script.is_some() {
121                continue;
122            }
123
124            // Get the derivation info
125            let derivation_prefix = input.derivation_prefix.as_ref().ok_or_else(|| {
126                Error::ValidationError(format!(
127                    "Input {} requires signing but has no derivation_prefix",
128                    vin
129                ))
130            })?;
131
132            let derivation_suffix = input.derivation_suffix.as_ref().ok_or_else(|| {
133                Error::ValidationError(format!(
134                    "Input {} requires signing but has no derivation_suffix",
135                    vin
136                ))
137            })?;
138
139            // Get the source locking script to determine script type
140            let locking_script = input.source_locking_script.as_ref().ok_or_else(|| {
141                Error::ValidationError(format!(
142                    "Input {} requires signing but has no source_locking_script",
143                    vin
144                ))
145            })?;
146
147            // Determine the counterparty from sender_identity_key
148            let counterparty = if let Some(ref sender_key) = input.sender_identity_key {
149                // If there's a sender key, use it as counterparty
150                let pubkey = bsv_rs::primitives::PublicKey::from_hex(sender_key)
151                    .map_err(|e| Error::ValidationError(format!("Invalid sender key: {}", e)))?;
152                Counterparty::Other(pubkey)
153            } else {
154                // Default to self
155                Counterparty::Self_
156            };
157
158            // Derive the private key for signing using BRC-29 (SABPPP) protocol
159            // BRC-29 uses:
160            // - Security level 2 (Counterparty)
161            // - Protocol name: "3241645161d8"
162            // - Key ID: "{derivation_prefix} {derivation_suffix}" (WITH SPACE)
163            // This produces invoice number: "2-3241645161d8-{prefix} {suffix}"
164            use bsv_rs::wallet::{Protocol, SecurityLevel};
165
166            let brc29_protocol = Protocol::new(SecurityLevel::Counterparty, "3241645161d8");
167            let key_id = format!("{} {}", derivation_prefix, derivation_suffix);
168
169            tracing::debug!(
170                derivation_prefix = %derivation_prefix,
171                derivation_suffix = %derivation_suffix,
172                key_id = %key_id,
173                "Deriving key for input using BRC-29 protocol"
174            );
175
176            let signing_key = proto_wallet
177                .key_deriver()
178                .derive_private_key(&brc29_protocol, &key_id, &counterparty)
179                .map_err(|e| Error::TransactionError(format!("Key derivation failed: {}", e)))?;
180
181            // Create the sighash for this input
182            // This depends on the script type (P2PKH, P2PK, etc.)
183            let sighash = compute_sighash(&tx_data, vin as u32, locking_script, input.satoshis)?;
184
185            // Sign the sighash
186            let signature = signing_key
187                .sign(&sighash)
188                .map_err(|e| Error::TransactionError(format!("Signing failed: {}", e)))?;
189
190            // Get the public key for the unlocking script
191            let pubkey = signing_key.public_key();
192
193            // Validate that the derived pubkey matches the locking script (P2PKH only)
194            let pubkey_compressed = pubkey.to_compressed();
195            let pubkey_hash = hash160(&pubkey_compressed);
196            validate_p2pkh_pubkey_match(&pubkey_hash, locking_script)?;
197
198            // Build the unlocking script based on script type
199            let unlocking_script =
200                build_unlocking_script(locking_script, &signature.to_der(), &pubkey_compressed)?;
201
202            // Insert the unlocking script into the transaction
203            tx_data = insert_unlocking_script(&tx_data, vin as u32, &unlocking_script)?;
204        }
205
206        // Inject user-provided unlocking scripts for external inputs
207        // (these were skipped in the loop above)
208        for (vin, input) in inputs.iter().enumerate() {
209            if let Some(ref script) = input.unlocking_script {
210                tx_data = insert_unlocking_script(&tx_data, vin as u32, script)?;
211            }
212        }
213
214        Ok(tx_data)
215    }
216
217    /// Signs a single input and returns the unlocking script.
218    ///
219    /// This is useful when you need to sign inputs individually rather than
220    /// all at once.
221    ///
222    /// # Arguments
223    ///
224    /// * `tx_data` - The transaction data
225    /// * `input_index` - Index of the input to sign
226    /// * `input` - Input metadata
227    /// * `proto_wallet` - The ProtoWallet for key derivation
228    ///
229    /// # Returns
230    ///
231    /// The unlocking script bytes.
232    pub fn sign_input(
233        &self,
234        tx_data: &[u8],
235        input_index: u32,
236        input: &SignerInput,
237        proto_wallet: &ProtoWallet,
238    ) -> Result<Vec<u8>> {
239        let derivation_prefix = input.derivation_prefix.as_ref().ok_or_else(|| {
240            Error::ValidationError(format!(
241                "Input {} requires signing but has no derivation_prefix",
242                input_index
243            ))
244        })?;
245
246        let derivation_suffix = input.derivation_suffix.as_ref().ok_or_else(|| {
247            Error::ValidationError(format!(
248                "Input {} requires signing but has no derivation_suffix",
249                input_index
250            ))
251        })?;
252
253        let locking_script = input.source_locking_script.as_ref().ok_or_else(|| {
254            Error::ValidationError(format!(
255                "Input {} requires signing but has no source_locking_script",
256                input_index
257            ))
258        })?;
259
260        let counterparty = if let Some(ref sender_key) = input.sender_identity_key {
261            let pubkey = bsv_rs::primitives::PublicKey::from_hex(sender_key)
262                .map_err(|e| Error::ValidationError(format!("Invalid sender key: {}", e)))?;
263            Counterparty::Other(pubkey)
264        } else {
265            Counterparty::Self_
266        };
267
268        // Derive the private key using BRC-29 (SABPPP) protocol
269        // Same as sign_transaction: SecurityLevel::Counterparty, protocol "3241645161d8"
270        // Key ID: "{derivation_prefix} {derivation_suffix}" (WITH SPACE)
271        use bsv_rs::wallet::{Protocol, SecurityLevel};
272
273        let brc29_protocol = Protocol::new(SecurityLevel::Counterparty, "3241645161d8");
274        let key_id = format!("{} {}", derivation_prefix, derivation_suffix);
275
276        let signing_key = proto_wallet
277            .key_deriver()
278            .derive_private_key(&brc29_protocol, &key_id, &counterparty)
279            .map_err(|e| Error::TransactionError(format!("Key derivation failed: {}", e)))?;
280
281        let sighash = compute_sighash(tx_data, input_index, locking_script, input.satoshis)?;
282
283        let signature = signing_key
284            .sign(&sighash)
285            .map_err(|e| Error::TransactionError(format!("Signing failed: {}", e)))?;
286
287        let pubkey = signing_key.public_key();
288
289        // Validate that the derived pubkey matches the locking script (P2PKH only)
290        let pubkey_compressed = pubkey.to_compressed();
291        let pubkey_hash = hash160(&pubkey_compressed);
292        validate_p2pkh_pubkey_match(&pubkey_hash, locking_script)?;
293
294        build_unlocking_script(locking_script, &signature.to_der(), &pubkey_compressed)
295    }
296
297    /// Create an unlocking script template for deferred signing.
298    ///
299    /// This creates a template that captures the key derivation parameters
300    /// and script type needed to generate an unlocking script later.
301    ///
302    /// # Arguments
303    ///
304    /// * `prefix` - Key derivation prefix (hex)
305    /// * `suffix` - Key derivation suffix (hex)
306    /// * `script_type` - The type of script to generate (P2PKH or P2PK)
307    /// * `satoshis` - The satoshi value of the input (required for BIP-143 sighash)
308    pub fn create_unlock_template(
309        &self,
310        prefix: &str,
311        suffix: &str,
312        script_type: ScriptType,
313        satoshis: u64,
314    ) -> UnlockingScriptTemplate {
315        UnlockingScriptTemplate {
316            derivation_prefix: prefix.to_string(),
317            derivation_suffix: suffix.to_string(),
318            script_type,
319            satoshis,
320        }
321    }
322
323    /// Apply unlocking script templates to a transaction.
324    ///
325    /// This derives keys from the templates and creates proper unlocking scripts
326    /// for each input that has a template. Uses BRC-29 key derivation to compute
327    /// the signing key for each templated input.
328    ///
329    /// # Arguments
330    ///
331    /// * `raw_tx` - The unsigned transaction bytes (modified in place concept, but cloned)
332    /// * `templates` - Pairs of (input_index, template) for each input to sign
333    /// * `proto_wallet` - The ProtoWallet for key derivation
334    ///
335    /// # Returns
336    ///
337    /// The transaction bytes with unlocking scripts applied.
338    #[allow(clippy::ptr_arg)]
339    pub fn apply_templates(
340        &self,
341        raw_tx: &mut Vec<u8>,
342        templates: &[(usize, UnlockingScriptTemplate)],
343        proto_wallet: &ProtoWallet,
344    ) -> Result<Vec<u8>> {
345        // For each template, derive the key and create the unlocking script
346        // This is a stub that returns the raw_tx unchanged for now
347        // Full implementation would derive keys per template and sign inputs
348        tracing::debug!("Applying {} unlocking script templates", templates.len());
349
350        let mut tx_data = raw_tx.clone();
351
352        for (input_index, template) in templates {
353            tracing::debug!(
354                input_index = %input_index,
355                derivation_prefix = %template.derivation_prefix,
356                derivation_suffix = %template.derivation_suffix,
357                script_type = ?template.script_type,
358                "Applying template for input"
359            );
360
361            // Derive the signing key using BRC-29 protocol
362            use bsv_rs::wallet::{Protocol, SecurityLevel};
363
364            let brc29_protocol = Protocol::new(SecurityLevel::Counterparty, "3241645161d8");
365            let key_id = format!(
366                "{} {}",
367                template.derivation_prefix, template.derivation_suffix
368            );
369
370            let signing_key = proto_wallet
371                .key_deriver()
372                .derive_private_key(&brc29_protocol, &key_id, &Counterparty::Self_)
373                .map_err(|e| {
374                    Error::TransactionError(format!("Template key derivation failed: {}", e))
375                })?;
376
377            let pubkey = signing_key.public_key();
378            let pubkey_bytes = pubkey.to_compressed();
379
380            // Build a locking script for sighash computation based on script type
381            let locking_script = match template.script_type {
382                ScriptType::P2PKH => {
383                    // P2PKH locking script: OP_DUP OP_HASH160 <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
384                    let pubkey_hash = hash160(&pubkey_bytes);
385                    let mut script = vec![0x76, 0xa9, 0x14];
386                    script.extend_from_slice(&pubkey_hash);
387                    script.extend_from_slice(&[0x88, 0xac]);
388                    script
389                }
390                ScriptType::P2PK => {
391                    // P2PK locking script: <pubkey> OP_CHECKSIG
392                    let mut script = vec![pubkey_bytes.len() as u8];
393                    script.extend_from_slice(&pubkey_bytes);
394                    script.push(0xac);
395                    script
396                }
397            };
398
399            let sighash = compute_sighash(
400                &tx_data,
401                *input_index as u32,
402                &locking_script,
403                template.satoshis,
404            )?;
405
406            let signature = signing_key
407                .sign(&sighash)
408                .map_err(|e| Error::TransactionError(format!("Template signing failed: {}", e)))?;
409
410            let unlocking_script =
411                build_unlocking_script(&locking_script, &signature.to_der(), &pubkey_bytes)?;
412
413            tx_data = insert_unlocking_script(&tx_data, *input_index as u32, &unlocking_script)?;
414        }
415
416        Ok(tx_data)
417    }
418}
419
420// =============================================================================
421// Helper Functions
422// =============================================================================
423
424/// Computes the sighash for a transaction input.
425///
426/// This implements BIP-143 style sighash computation for SegWit-like signing,
427/// which BSV uses for all transactions.
428fn compute_sighash(
429    tx_data: &[u8],
430    input_index: u32,
431    locking_script: &[u8],
432    satoshis: u64,
433) -> Result<[u8; 32]> {
434    // Parse transaction to get components
435    let (version, inputs, outputs, locktime) = parse_transaction(tx_data)?;
436
437    // Compute hashPrevouts (double SHA256 of all outpoints)
438    let mut prevouts_data = Vec::new();
439    for input in &inputs {
440        prevouts_data.extend_from_slice(&input.txid);
441        prevouts_data.extend_from_slice(&input.vout.to_le_bytes());
442    }
443    let hash_prevouts = double_sha256(&prevouts_data);
444
445    // Compute hashSequence (double SHA256 of all sequences)
446    let mut sequence_data = Vec::new();
447    for input in &inputs {
448        sequence_data.extend_from_slice(&input.sequence.to_le_bytes());
449    }
450    let hash_sequence = double_sha256(&sequence_data);
451
452    // Compute hashOutputs (double SHA256 of all outputs)
453    let mut outputs_data = Vec::new();
454    for output in &outputs {
455        outputs_data.extend_from_slice(&output.satoshis.to_le_bytes());
456        write_varint(&mut outputs_data, output.script.len() as u64);
457        outputs_data.extend_from_slice(&output.script);
458    }
459    let hash_outputs = double_sha256(&outputs_data);
460
461    // Build the preimage for sighash
462    let mut preimage = Vec::new();
463
464    // nVersion
465    preimage.extend_from_slice(&version.to_le_bytes());
466
467    // hashPrevouts
468    preimage.extend_from_slice(&hash_prevouts);
469
470    // hashSequence
471    preimage.extend_from_slice(&hash_sequence);
472
473    // outpoint (this input's txid and vout)
474    let input = &inputs[input_index as usize];
475    preimage.extend_from_slice(&input.txid);
476    preimage.extend_from_slice(&input.vout.to_le_bytes());
477
478    // scriptCode (the locking script being spent)
479    write_varint(&mut preimage, locking_script.len() as u64);
480    preimage.extend_from_slice(locking_script);
481
482    // value (satoshis)
483    preimage.extend_from_slice(&satoshis.to_le_bytes());
484
485    // nSequence
486    preimage.extend_from_slice(&input.sequence.to_le_bytes());
487
488    // hashOutputs
489    preimage.extend_from_slice(&hash_outputs);
490
491    // nLockTime
492    preimage.extend_from_slice(&locktime.to_le_bytes());
493
494    // sighash type (SIGHASH_ALL | SIGHASH_FORKID = 0x41)
495    preimage.extend_from_slice(&0x41u32.to_le_bytes());
496
497    // Double SHA256 the preimage
498    Ok(double_sha256(&preimage))
499}
500
501/// Parses a transaction into its components.
502fn parse_transaction(tx_data: &[u8]) -> Result<(u32, Vec<TxInput>, Vec<TxOutput>, u32)> {
503    let mut offset = 0;
504
505    // Version (4 bytes)
506    if tx_data.len() < 4 {
507        return Err(Error::TransactionError("Transaction too short".to_string()));
508    }
509    let version = u32::from_le_bytes([
510        tx_data[offset],
511        tx_data[offset + 1],
512        tx_data[offset + 2],
513        tx_data[offset + 3],
514    ]);
515    offset += 4;
516
517    // Input count
518    let (input_count, bytes_read) = read_varint(&tx_data[offset..])?;
519    offset += bytes_read;
520
521    // Inputs
522    let mut inputs = Vec::with_capacity(input_count as usize);
523    for _ in 0..input_count {
524        // txid (32 bytes)
525        if offset + 32 > tx_data.len() {
526            return Err(Error::TransactionError(
527                "Unexpected end of transaction data".to_string(),
528            ));
529        }
530        let mut txid = [0u8; 32];
531        txid.copy_from_slice(&tx_data[offset..offset + 32]);
532        offset += 32;
533
534        // vout (4 bytes)
535        if offset + 4 > tx_data.len() {
536            return Err(Error::TransactionError(
537                "Unexpected end of transaction data".to_string(),
538            ));
539        }
540        let vout = u32::from_le_bytes([
541            tx_data[offset],
542            tx_data[offset + 1],
543            tx_data[offset + 2],
544            tx_data[offset + 3],
545        ]);
546        offset += 4;
547
548        // Script length and script
549        let (script_len, bytes_read) = read_varint(&tx_data[offset..])?;
550        offset += bytes_read;
551
552        if offset + script_len as usize > tx_data.len() {
553            return Err(Error::TransactionError(
554                "Unexpected end of transaction data".to_string(),
555            ));
556        }
557        let script = tx_data[offset..offset + script_len as usize].to_vec();
558        offset += script_len as usize;
559
560        // Sequence (4 bytes)
561        if offset + 4 > tx_data.len() {
562            return Err(Error::TransactionError(
563                "Unexpected end of transaction data".to_string(),
564            ));
565        }
566        let sequence = u32::from_le_bytes([
567            tx_data[offset],
568            tx_data[offset + 1],
569            tx_data[offset + 2],
570            tx_data[offset + 3],
571        ]);
572        offset += 4;
573
574        inputs.push(TxInput {
575            txid,
576            vout,
577            script,
578            sequence,
579        });
580    }
581
582    // Output count
583    let (output_count, bytes_read) = read_varint(&tx_data[offset..])?;
584    offset += bytes_read;
585
586    // Outputs
587    let mut outputs = Vec::with_capacity(output_count as usize);
588    for _ in 0..output_count {
589        // Satoshis (8 bytes)
590        if offset + 8 > tx_data.len() {
591            return Err(Error::TransactionError(
592                "Unexpected end of transaction data".to_string(),
593            ));
594        }
595        let satoshis = u64::from_le_bytes([
596            tx_data[offset],
597            tx_data[offset + 1],
598            tx_data[offset + 2],
599            tx_data[offset + 3],
600            tx_data[offset + 4],
601            tx_data[offset + 5],
602            tx_data[offset + 6],
603            tx_data[offset + 7],
604        ]);
605        offset += 8;
606
607        // Script length and script
608        let (script_len, bytes_read) = read_varint(&tx_data[offset..])?;
609        offset += bytes_read;
610
611        if offset + script_len as usize > tx_data.len() {
612            return Err(Error::TransactionError(
613                "Unexpected end of transaction data".to_string(),
614            ));
615        }
616        let script = tx_data[offset..offset + script_len as usize].to_vec();
617        offset += script_len as usize;
618
619        outputs.push(TxOutput { satoshis, script });
620    }
621
622    // Locktime (4 bytes)
623    if offset + 4 > tx_data.len() {
624        return Err(Error::TransactionError(
625            "Unexpected end of transaction data".to_string(),
626        ));
627    }
628    let locktime = u32::from_le_bytes([
629        tx_data[offset],
630        tx_data[offset + 1],
631        tx_data[offset + 2],
632        tx_data[offset + 3],
633    ]);
634
635    Ok((version, inputs, outputs, locktime))
636}
637
638/// Transaction input structure.
639struct TxInput {
640    txid: [u8; 32],
641    vout: u32,
642    script: Vec<u8>,
643    sequence: u32,
644}
645
646/// Transaction output structure.
647struct TxOutput {
648    satoshis: u64,
649    script: Vec<u8>,
650}
651
652/// Reads a varint from data and returns (value, bytes_read).
653fn read_varint(data: &[u8]) -> Result<(u64, usize)> {
654    if data.is_empty() {
655        return Err(Error::TransactionError("Empty varint".to_string()));
656    }
657
658    let first = data[0];
659    if first < 0xfd {
660        Ok((first as u64, 1))
661    } else if first == 0xfd {
662        if data.len() < 3 {
663            return Err(Error::TransactionError("Truncated varint".to_string()));
664        }
665        let val = u16::from_le_bytes([data[1], data[2]]) as u64;
666        Ok((val, 3))
667    } else if first == 0xfe {
668        if data.len() < 5 {
669            return Err(Error::TransactionError("Truncated varint".to_string()));
670        }
671        let val = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as u64;
672        Ok((val, 5))
673    } else {
674        if data.len() < 9 {
675            return Err(Error::TransactionError("Truncated varint".to_string()));
676        }
677        let val = u64::from_le_bytes([
678            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
679        ]);
680        Ok((val, 9))
681    }
682}
683
684/// Computes double SHA256.
685fn double_sha256(data: &[u8]) -> [u8; 32] {
686    use sha2::{Digest, Sha256};
687
688    let hash1 = Sha256::digest(data);
689    let hash2 = Sha256::digest(hash1);
690    let mut result = [0u8; 32];
691    result.copy_from_slice(&hash2);
692    result
693}
694
695/// Computes HASH160 (RIPEMD160(SHA256(data))).
696///
697/// Used to derive the public key hash for P2PKH locking scripts.
698fn hash160(data: &[u8]) -> [u8; 20] {
699    use sha2::{Digest, Sha256};
700
701    let sha256_hash = Sha256::digest(data);
702
703    use ripemd::Ripemd160;
704    let ripemd_hash = <Ripemd160 as ripemd::Digest>::digest(sha256_hash);
705    let mut result = [0u8; 20];
706    result.copy_from_slice(&ripemd_hash);
707    result
708}
709
710/// Validates that the derived public key's hash160 matches the P2PKH locking script.
711/// P2PKH format: 76 a9 14 <20-byte-hash160> 88 ac
712///
713/// For non-P2PKH scripts (not matching the 25-byte pattern), validation is skipped.
714/// For P2PKH scripts, a mismatch between the derived pubkey hash and the script hash
715/// is an error -- this catches wrong key derivation before broadcasting.
716fn validate_p2pkh_pubkey_match(pubkey_hash160: &[u8; 20], locking_script: &[u8]) -> Result<()> {
717    if locking_script.len() == 25
718        && locking_script[0] == 0x76
719        && locking_script[1] == 0xa9
720        && locking_script[2] == 0x14
721        && locking_script[23] == 0x88
722        && locking_script[24] == 0xac
723    {
724        let script_hash160 = &locking_script[3..23];
725        if pubkey_hash160 != script_hash160 {
726            return Err(Error::SigningError(format!(
727                "Derived pubkey hash160 {} does not match locking script hash160 {}",
728                hex::encode(pubkey_hash160),
729                hex::encode(script_hash160)
730            )));
731        }
732    }
733    Ok(())
734}
735
736/// Builds an unlocking script based on the locking script type.
737fn build_unlocking_script(
738    locking_script: &[u8],
739    signature: &[u8],
740    pubkey: &[u8],
741) -> Result<Vec<u8>> {
742    // Check if this is a P2PKH script
743    // P2PKH: OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
744    // Bytes: 76 a9 14 <20 bytes pubkey hash> 88 ac
745    if locking_script.len() == 25
746        && locking_script[0] == 0x76  // OP_DUP
747        && locking_script[1] == 0xa9  // OP_HASH160
748        && locking_script[2] == 0x14  // Push 20 bytes
749        && locking_script[23] == 0x88 // OP_EQUALVERIFY
750        && locking_script[24] == 0xac
751    // OP_CHECKSIG
752    {
753        // P2PKH unlocking script: <sig> <pubkey>
754        let mut unlocking = Vec::new();
755
756        // Signature with sighash byte
757        let sig_with_hashtype: Vec<u8> = signature
758            .iter()
759            .copied()
760            .chain(std::iter::once(0x41)) // SIGHASH_ALL | SIGHASH_FORKID
761            .collect();
762
763        // Push signature
764        unlocking.push(sig_with_hashtype.len() as u8);
765        unlocking.extend_from_slice(&sig_with_hashtype);
766
767        // Push pubkey
768        unlocking.push(pubkey.len() as u8);
769        unlocking.extend_from_slice(pubkey);
770
771        return Ok(unlocking);
772    }
773
774    // Check if this is a P2PK script
775    // P2PK: <pubkey> OP_CHECKSIG
776    if locking_script.len() >= 35
777        && (locking_script[0] == 33 || locking_script[0] == 65)
778        && locking_script[locking_script.len() - 1] == 0xac
779    {
780        // P2PK unlocking script: <sig>
781        let mut unlocking = Vec::new();
782
783        let sig_with_hashtype: Vec<u8> = signature
784            .iter()
785            .copied()
786            .chain(std::iter::once(0x41))
787            .collect();
788
789        unlocking.push(sig_with_hashtype.len() as u8);
790        unlocking.extend_from_slice(&sig_with_hashtype);
791
792        return Ok(unlocking);
793    }
794
795    // Unknown script type - return error
796    Err(Error::TransactionError(format!(
797        "Unknown locking script type: {}",
798        hex::encode(locking_script)
799    )))
800}
801
802/// Inserts an unlocking script into a transaction at the specified input index.
803fn insert_unlocking_script(
804    tx_data: &[u8],
805    input_index: u32,
806    unlocking_script: &[u8],
807) -> Result<Vec<u8>> {
808    // Parse the transaction
809    let (version, inputs, outputs, locktime) = parse_transaction(tx_data)?;
810
811    // Rebuild the transaction with the new unlocking script
812    let mut result = Vec::new();
813
814    // Version
815    result.extend_from_slice(&version.to_le_bytes());
816
817    // Input count
818    write_varint(&mut result, inputs.len() as u64);
819
820    // Inputs
821    for (i, input) in inputs.iter().enumerate() {
822        // txid
823        result.extend_from_slice(&input.txid);
824
825        // vout
826        result.extend_from_slice(&input.vout.to_le_bytes());
827
828        // Script (use new unlocking script for target input)
829        let script = if i == input_index as usize {
830            unlocking_script
831        } else {
832            &input.script
833        };
834
835        // Write varint for script length
836        write_varint(&mut result, script.len() as u64);
837        result.extend_from_slice(script);
838
839        // Sequence
840        result.extend_from_slice(&input.sequence.to_le_bytes());
841    }
842
843    // Output count
844    write_varint(&mut result, outputs.len() as u64);
845
846    // Outputs
847    for output in &outputs {
848        // Satoshis
849        result.extend_from_slice(&output.satoshis.to_le_bytes());
850
851        // Script
852        write_varint(&mut result, output.script.len() as u64);
853        result.extend_from_slice(&output.script);
854    }
855
856    // Locktime
857    result.extend_from_slice(&locktime.to_le_bytes());
858
859    Ok(result)
860}
861
862/// Writes a varint to the output buffer.
863fn write_varint(output: &mut Vec<u8>, value: u64) {
864    if value < 0xfd {
865        output.push(value as u8);
866    } else if value <= 0xffff {
867        output.push(0xfd);
868        output.extend_from_slice(&(value as u16).to_le_bytes());
869    } else if value <= 0xffffffff {
870        output.push(0xfe);
871        output.extend_from_slice(&(value as u32).to_le_bytes());
872    } else {
873        output.push(0xff);
874        output.extend_from_slice(&value.to_le_bytes());
875    }
876}
877
878// =============================================================================
879// Tests
880// =============================================================================
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885
886    #[test]
887    fn test_read_varint() {
888        // Single byte
889        assert_eq!(read_varint(&[0x00]).unwrap(), (0, 1));
890        assert_eq!(read_varint(&[0xfc]).unwrap(), (252, 1));
891
892        // Two bytes
893        assert_eq!(read_varint(&[0xfd, 0xfd, 0x00]).unwrap(), (253, 3));
894        assert_eq!(read_varint(&[0xfd, 0xff, 0xff]).unwrap(), (65535, 3));
895
896        // Four bytes
897        assert_eq!(
898            read_varint(&[0xfe, 0x00, 0x00, 0x01, 0x00]).unwrap(),
899            (65536, 5)
900        );
901
902        // Eight bytes
903        assert_eq!(
904            read_varint(&[0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]).unwrap(),
905            (4294967296, 9)
906        );
907    }
908
909    #[test]
910    fn test_write_varint() {
911        let mut buf = Vec::new();
912
913        // Single byte
914        write_varint(&mut buf, 0);
915        assert_eq!(buf, vec![0x00]);
916
917        buf.clear();
918        write_varint(&mut buf, 252);
919        assert_eq!(buf, vec![0xfc]);
920
921        // Two bytes
922        buf.clear();
923        write_varint(&mut buf, 253);
924        assert_eq!(buf, vec![0xfd, 0xfd, 0x00]);
925
926        buf.clear();
927        write_varint(&mut buf, 65535);
928        assert_eq!(buf, vec![0xfd, 0xff, 0xff]);
929    }
930
931    #[test]
932    fn test_double_sha256() {
933        // Test vector: empty string
934        let result = double_sha256(&[]);
935        let expected =
936            hex::decode("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456")
937                .unwrap();
938        assert_eq!(result.to_vec(), expected);
939    }
940
941    #[test]
942    fn test_build_unlocking_script_p2pkh() {
943        // Create a P2PKH locking script
944        let pubkey_hash = [0u8; 20];
945        let mut locking_script = vec![0x76, 0xa9, 0x14];
946        locking_script.extend_from_slice(&pubkey_hash);
947        locking_script.extend_from_slice(&[0x88, 0xac]);
948
949        // Create a dummy signature and pubkey
950        let signature = vec![0x30, 0x44]; // Simplified - just testing structure
951        let pubkey = vec![0x02; 33]; // Compressed pubkey prefix
952
953        let result = build_unlocking_script(&locking_script, &signature, &pubkey).unwrap();
954
955        // Should have: push_sig + sig + hashtype + push_pubkey + pubkey
956        assert!(!result.is_empty());
957        // First byte is length of signature + hashtype
958        assert_eq!(result[0], 3); // 2 byte sig + 1 byte hashtype
959    }
960
961    #[test]
962    fn test_wallet_signer_new() {
963        let signer = WalletSigner::new(None);
964        assert!(signer.root_key.is_none());
965
966        let key = PrivateKey::random();
967        let signer = WalletSigner::new(Some(key));
968        assert!(signer.root_key.is_some());
969    }
970
971    #[test]
972    fn test_create_unlock_template() {
973        let signer = WalletSigner::new(None);
974        let template = signer.create_unlock_template("aabbcc", "ddeeff", ScriptType::P2PKH, 5000);
975
976        assert_eq!(template.derivation_prefix, "aabbcc");
977        assert_eq!(template.derivation_suffix, "ddeeff");
978        assert_eq!(template.script_type, ScriptType::P2PKH);
979        assert_eq!(template.satoshis, 5000);
980    }
981
982    #[test]
983    fn test_create_unlock_template_p2pk() {
984        let signer = WalletSigner::new(None);
985        let template = signer.create_unlock_template("1234", "5678", ScriptType::P2PK, 10000);
986
987        assert_eq!(template.derivation_prefix, "1234");
988        assert_eq!(template.derivation_suffix, "5678");
989        assert_eq!(template.script_type, ScriptType::P2PK);
990        assert_eq!(template.satoshis, 10000);
991    }
992
993    #[test]
994    fn test_unlocking_script_template_serialization() {
995        let template = UnlockingScriptTemplate {
996            derivation_prefix: "aabb".to_string(),
997            derivation_suffix: "ccdd".to_string(),
998            script_type: ScriptType::P2PKH,
999            satoshis: 1000,
1000        };
1001
1002        let json = serde_json::to_string(&template).unwrap();
1003        let deserialized: UnlockingScriptTemplate = serde_json::from_str(&json).unwrap();
1004
1005        assert_eq!(deserialized.derivation_prefix, "aabb");
1006        assert_eq!(deserialized.derivation_suffix, "ccdd");
1007        assert_eq!(deserialized.script_type, ScriptType::P2PKH);
1008    }
1009
1010    #[test]
1011    fn test_script_type_equality() {
1012        assert_eq!(ScriptType::P2PKH, ScriptType::P2PKH);
1013        assert_eq!(ScriptType::P2PK, ScriptType::P2PK);
1014        assert_ne!(ScriptType::P2PKH, ScriptType::P2PK);
1015    }
1016
1017    #[test]
1018    fn test_hash160() {
1019        // Test with a known value - empty input
1020        let result = hash160(&[]);
1021        // SHA256 of empty = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
1022        // RIPEMD160 of that = b472a266d0bd89c13706a4132ccfb16f7c3b9fcb
1023        let expected = hex::decode("b472a266d0bd89c13706a4132ccfb16f7c3b9fcb").unwrap();
1024        assert_eq!(result.to_vec(), expected);
1025    }
1026}