Skip to main content

blvm_consensus/
transaction_hash.rs

1//! Transaction hash calculation for signature verification
2//!
3//! Implements Bitcoin's transaction sighash algorithm for ECDSA signature verification.
4//! This is critical for proper signature validation in script execution.
5//!
6//! Performance optimizations:
7//! - Precomputed sighash templates for common transaction patterns
8
9use crate::crypto::OptimizedSha256;
10use crate::error::Result;
11use crate::types::*;
12use blvm_spec_lock::spec_locked;
13
14// OPTIMIZATION: Inline varint encoding helper to avoid Vec allocations in hot path
15#[inline]
16fn write_varint_to_vec(vec: &mut Vec<u8>, value: u64) {
17    if value < 0xfd {
18        vec.push(value as u8);
19    } else if value <= 0xffff {
20        vec.push(0xfd);
21        vec.extend_from_slice(&(value as u16).to_le_bytes());
22    } else if value <= 0xffffffff {
23        vec.push(0xfe);
24        vec.extend_from_slice(&(value as u32).to_le_bytes());
25    } else {
26        vec.push(0xff);
27        vec.extend_from_slice(&value.to_le_bytes());
28    }
29}
30
31#[cfg(feature = "production")]
32use hashbrown::HashMap as HashBrownMap;
33#[cfg(feature = "production")]
34use lru::LruCache;
35#[cfg(feature = "production")]
36use rustc_hash::{FxBuildHasher, FxHasher};
37#[cfg(feature = "production")]
38use std::cell::RefCell;
39#[cfg(feature = "production")]
40use std::hash::{Hash as StdHash, Hasher};
41
42/// Per-block sighash cache: (prevout, code_hash, sighash_byte) -> hash. Core-style.
43/// Uses hash of scriptCode instead of owned Vec to avoid allocation on insert.
44#[cfg(feature = "production")]
45#[derive(Clone, Copy, PartialEq, Eq, Debug)]
46pub struct SighashCacheKey {
47    prevout: crate::types::OutPoint,
48    code_hash: u64,
49    sighash_byte: u8,
50}
51
52#[cfg(feature = "production")]
53impl std::hash::Hash for SighashCacheKey {
54    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
55        state.write_u64(self.code_hash);
56    }
57}
58
59#[cfg(feature = "production")]
60pub type SighashMidstateCache =
61    std::sync::Arc<std::sync::Mutex<HashBrownMap<SighashCacheKey, [u8; 32], FxBuildHasher>>>;
62
63/// Thread-local midstate cache: (prevout, code_hash, sighash_byte) -> hash. Avoids Mutex contention
64/// across script-check workers. Used when block passes None (CCheckQueue/rayon path).
65#[cfg(feature = "production")]
66thread_local! {
67    static SIGHASH_MIDSTATE_CACHE: RefCell<HashBrownMap<SighashCacheKey, [u8; 32], FxBuildHasher>> =
68        const { RefCell::new(HashBrownMap::with_hasher(FxBuildHasher)) };
69}
70
71#[cfg(feature = "production")]
72fn insert_midstate_cache(
73    sighash_cache: Option<&SighashMidstateCache>,
74    prevout: crate::types::OutPoint,
75    code: &[u8],
76    sighash_byte: u8,
77    hash: [u8; 32],
78) {
79    let key_hash = sighash_cache_hash(&prevout, code, sighash_byte);
80    let key = SighashCacheKey {
81        prevout,
82        code_hash: key_hash,
83        sighash_byte,
84    };
85    if let Some(c) = sighash_cache {
86        let _ = c.lock().map(|mut g| g.insert(key, hash));
87    } else {
88        SIGHASH_MIDSTATE_CACHE.with(|cell| {
89            cell.borrow_mut().insert(key, hash);
90        });
91    }
92}
93
94/// Hash (prevout, code, sighash_byte) with FxHasher for cache bucket lookup.
95#[cfg(feature = "production")]
96#[inline]
97fn sighash_cache_hash(prevout: &crate::types::OutPoint, code: &[u8], sighash_byte: u8) -> u64 {
98    let mut hasher = FxHasher::default();
99    prevout.hash(&mut hasher);
100    code.hash(&mut hasher);
101    sighash_byte.hash(&mut hasher);
102    hasher.finish()
103}
104
105/// Sighash cache: first_hash (SHA256 of preimage) -> final hash (double-SHA256).
106/// Thread-local to avoid Mutex contention across script-check workers.
107/// Saves one SHA256 per cache hit. LRU evicts oldest entries when capacity reached.
108/// Capacity: 256k default (BLVM_SIGHASH_CACHE_SIZE); 65k min. IBD: larger helps reorg/assumeutxo.
109#[cfg(feature = "production")]
110thread_local! {
111    static SIGHASH_CACHE: RefCell<LruCache<[u8; 32], [u8; 32]>> = RefCell::new({
112        let cap = std::env::var("BLVM_SIGHASH_CACHE_SIZE")
113            .ok()
114            .and_then(|s| s.parse().ok())
115            .unwrap_or(262_144)
116            .clamp(65_536, 2_097_152);
117        LruCache::new(std::num::NonZeroUsize::new(cap).unwrap())
118    });
119}
120
121/// Thread-local buffer for sighash preimage (avoids ~3-6k Vec allocs/block in non-template path)
122#[cfg(feature = "production")]
123thread_local! {
124    static SIGHASH_PREIMAGE_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(4096));
125}
126
127/// Thread-local buffer for Bip143PrecomputedHashes (prevouts/sequence/outputs serialization)
128/// Reused across hash_prevouts, hash_sequence, hash_outputs to avoid 3 Vec allocs per SegWit tx
129#[cfg(feature = "production")]
130thread_local! {
131    static BIP143_SERIALIZE_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(131_072)); // 128KB, covers max tx
132}
133
134/// Thread-local buffer for BIP143 per-input sighash preimage (avoids alloc per input in batch)
135#[cfg(feature = "production")]
136thread_local! {
137    static BIP143_PREIMAGE_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(1024));
138}
139
140/// Thread-local buffer for SIGHASH_SINGLE per-output serialization (8+var+script < 256 bytes)
141#[cfg(feature = "production")]
142thread_local! {
143    static BIP143_SINGLE_OUTPUT_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(256));
144}
145
146/// Thread-local reusable preimage buffers for batch_compute_legacy_sighashes.
147/// Avoids N Vec allocs per block.
148#[cfg(feature = "production")]
149thread_local! {
150    static LEGACY_BATCH_PREIMAGES: std::cell::RefCell<Vec<Vec<u8>>> =
151        const { std::cell::RefCell::new(Vec::new()) };
152}
153
154/// SIGHASH types for transaction signature verification
155///
156/// IMPORTANT: The enum values match the canonical sighash bytes used in sighash computation.
157/// Early Bitcoin allowed sighash type 0x00 (treated as SIGHASH_ALL behavior), which we
158/// Wraps the raw sighash byte from the signature, preserving its exact value for
159/// preimage serialization. consensus uses the raw byte directly in the sighash
160/// preimage — before STRICTENC activation (BIP66), ANY sighash byte was accepted.
161/// The base type is determined by masking with 0x1f: NONE=2, SINGLE=3, else ALL.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
163pub struct SighashType(pub u8);
164
165impl SighashType {
166    // Standard sighash type constants
167    pub const ALL_LEGACY: Self = SighashType(0x00);
168    pub const ALL: Self = SighashType(0x01);
169    pub const NONE: Self = SighashType(0x02);
170    pub const SINGLE: Self = SighashType(0x03);
171    pub const ALL_ANYONECANPAY: Self = SighashType(0x81);
172    pub const NONE_ANYONECANPAY: Self = SighashType(0x82);
173    pub const SINGLE_ANYONECANPAY: Self = SighashType(0x83);
174
175    /// Create from raw sighash byte — accepts ANY value (pre-STRICTENC compatibility).
176    /// consensus determines behavior from `byte & 0x1f` and uses the raw byte in the preimage.
177    pub fn from_byte(byte: u8) -> Self {
178        SighashType(byte)
179    }
180
181    /// Raw byte value for preimage serialization
182    pub fn as_u32(&self) -> u32 {
183        self.0 as u32
184    }
185
186    /// Base sighash type (lower 5 bits), matching consensus's `nHashType & 0x1f`
187    pub fn base_type(&self) -> u8 {
188        self.0 & 0x1f
189    }
190
191    /// Whether ANYONECANPAY flag is set (bit 7)
192    pub fn is_anyonecanpay(&self) -> bool {
193        self.0 & 0x80 != 0
194    }
195
196    /// Whether this has SIGHASH_ALL behavior (base type is not NONE or SINGLE)
197    pub fn is_all(&self) -> bool {
198        let base = self.base_type();
199        base != 0x02 && base != 0x03
200    }
201
202    /// Whether base type is SIGHASH_NONE
203    pub fn is_none(&self) -> bool {
204        self.base_type() == 0x02
205    }
206
207    /// Whether base type is SIGHASH_SINGLE
208    pub fn is_single(&self) -> bool {
209        self.base_type() == 0x03
210    }
211}
212
213/// Common sighash patterns (documentation; cache applies to all).
214#[cfg(feature = "production")]
215#[allow(dead_code)]
216#[inline]
217fn is_cacheable_sighash_pattern(
218    tx: &Transaction,
219    input_index: usize,
220    sighash_type: SighashType,
221) -> bool {
222    if sighash_type.is_anyonecanpay() {
223        return false;
224    }
225    // SIGHASH_ALL: 1-in-1-out, 1-in-2-out, 2-in-1-out, 2-in-2-out, 1-in-N, N-in-1 (N<=4)
226    let base = sighash_type.base_type();
227    if base == 0x01 || base == 0x00 {
228        let ni = tx.inputs.len();
229        let no = tx.outputs.len();
230        (ni == 1 && (1..=4).contains(&no))
231            || ((1..=4).contains(&ni) && no == 1)
232            || (ni == 2 && no == 2)
233            || (ni == 1 && no == 1)
234    } else if base == 0x02 {
235        // SIGHASH_NONE: no outputs
236        !tx.inputs.is_empty() && tx.inputs.len() <= 4
237    } else if base == 0x03 {
238        // SIGHASH_SINGLE: output at input index
239        input_index < tx.outputs.len() && tx.inputs.len() <= 4
240    } else {
241        false
242    }
243}
244
245/// Compute sighash with cache. First hash (of preimage) is cache key.
246/// On hit: return cached double-SHA256. On miss: compute, cache, return.
247/// Uses OptimizedSha256 (SHA-NI when available) for ~10× faster hashing vs generic sha2.
248/// Thread-local cache avoids Mutex contention across script-check workers.
249#[cfg(feature = "production")]
250fn sighash_with_cache(preimage: &[u8]) -> Hash {
251    let hasher = OptimizedSha256::new();
252    let first_hash: [u8; 32] = hasher.hash(preimage);
253    SIGHASH_CACHE.with(|cell| {
254        let mut cache = cell.borrow_mut();
255        if let Some(cached) = cache.get(&first_hash) {
256            return *cached;
257        }
258        let second_hash = hasher.hash(&first_hash);
259        let mut result = [0u8; 32];
260        result.copy_from_slice(&second_hash);
261        cache.put(first_hash, result);
262        result
263    })
264}
265
266/// Compute legacy sighash without any caching layers.
267/// Uses incremental SHA256 - feeds data directly to the hasher, avoiding
268/// the preimage buffer allocation and double memory pass.
269#[cfg(feature = "production")]
270#[spec_locked("5.1.1")]
271#[inline]
272pub fn compute_legacy_sighash_nocache(
273    tx: &Transaction,
274    input_index: usize,
275    script_code: &[u8],
276    sighash_byte: u8,
277) -> [u8; 32] {
278    use sha2::{Digest, Sha256};
279
280    let sighash_u32 = sighash_byte as u32;
281    let base_type = sighash_u32 & 0x1f;
282    let anyone_can_pay = (sighash_u32 & 0x80) != 0;
283    let hash_none = base_type == 0x02;
284    let hash_single = base_type == 0x03;
285
286    if hash_single && input_index >= tx.outputs.len() {
287        let mut result = [0u8; 32];
288        result[0] = 1;
289        return result;
290    }
291
292    let mut h = Sha256::new();
293    h.update((tx.version as u32).to_le_bytes());
294
295    let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
296    update_varint(&mut h, n_inputs as u64);
297
298    for i in 0..n_inputs {
299        let actual_i = if anyone_can_pay { input_index } else { i };
300        let input = &tx.inputs[actual_i];
301        h.update(input.prevout.hash);
302        h.update(input.prevout.index.to_le_bytes());
303
304        if actual_i == input_index {
305            update_varint(&mut h, script_code.len() as u64);
306            h.update(script_code);
307        } else {
308            h.update([0u8]);
309        }
310
311        if actual_i != input_index && (hash_single || hash_none) {
312            h.update(0u32.to_le_bytes());
313        } else {
314            h.update((input.sequence as u32).to_le_bytes());
315        }
316    }
317
318    let n_outputs = if hash_none {
319        0
320    } else if hash_single {
321        input_index + 1
322    } else {
323        tx.outputs.len()
324    };
325    update_varint(&mut h, n_outputs as u64);
326
327    for i in 0..n_outputs {
328        if hash_single && i != input_index {
329            h.update((-1i64).to_le_bytes());
330            h.update([0u8]);
331        } else {
332            let output = &tx.outputs[i];
333            h.update(output.value.to_le_bytes());
334            update_varint(&mut h, output.script_pubkey.len() as u64);
335            h.update(output.script_pubkey.as_slice());
336        }
337    }
338
339    h.update((tx.lock_time as u32).to_le_bytes());
340    h.update(sighash_u32.to_le_bytes());
341
342    let first_hash = h.finalize();
343    let second_hash = Sha256::digest(first_hash);
344    let mut result = [0u8; 32];
345    result.copy_from_slice(&second_hash);
346    result
347}
348
349/// Helper: write varint directly to a sha2::Sha256 hasher (no intermediate buffer).
350#[cfg(feature = "production")]
351#[inline]
352fn update_varint(hasher: &mut sha2::Sha256, value: u64) {
353    use sha2::Digest;
354    if value < 0xfd {
355        hasher.update([value as u8]);
356    } else if value <= 0xffff {
357        hasher.update([0xfd]);
358        hasher.update((value as u16).to_le_bytes());
359    } else if value <= 0xffffffff {
360        hasher.update([0xfe]);
361        hasher.update((value as u32).to_le_bytes());
362    } else {
363        hasher.update([0xff]);
364        hasher.update(value.to_le_bytes());
365    }
366}
367
368/// Write varint to a byte buffer.
369#[cfg(feature = "production")]
370#[inline]
371fn push_varint(buf: &mut Vec<u8>, value: u64) {
372    if value < 0xfd {
373        buf.push(value as u8);
374    } else if value <= 0xffff {
375        buf.push(0xfd);
376        buf.extend_from_slice(&(value as u16).to_le_bytes());
377    } else if value <= 0xffffffff {
378        buf.push(0xfe);
379        buf.extend_from_slice(&(value as u32).to_le_bytes());
380    } else {
381        buf.push(0xff);
382        buf.extend_from_slice(&value.to_le_bytes());
383    }
384}
385
386/// Compute legacy sighash by pre-serializing the full preimage into a thread-local buffer,
387/// then hashing in one pass. Reduces function call overhead vs streaming h.update() calls.
388#[cfg(feature = "production")]
389#[spec_locked("5.1.1")]
390#[inline]
391pub fn compute_legacy_sighash_buffered(
392    tx: &Transaction,
393    input_index: usize,
394    script_code: &[u8],
395    sighash_byte: u8,
396) -> [u8; 32] {
397    use sha2::{Digest, Sha256};
398
399    let sighash_u32 = sighash_byte as u32;
400    let base_type = sighash_u32 & 0x1f;
401    let anyone_can_pay = (sighash_u32 & 0x80) != 0;
402    let hash_none = base_type == 0x02;
403    let hash_single = base_type == 0x03;
404
405    if hash_single && input_index >= tx.outputs.len() {
406        let mut result = [0u8; 32];
407        result[0] = 1;
408        return result;
409    }
410
411    thread_local! {
412        static BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(4096));
413    }
414
415    BUF.with(|cell| {
416        let mut buf = cell.borrow_mut();
417        buf.clear();
418
419        buf.extend_from_slice(&(tx.version as u32).to_le_bytes());
420
421        let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
422        push_varint(&mut buf, n_inputs as u64);
423
424        for i in 0..n_inputs {
425            let actual_i = if anyone_can_pay { input_index } else { i };
426            let input = &tx.inputs[actual_i];
427            buf.extend_from_slice(&input.prevout.hash);
428            buf.extend_from_slice(&input.prevout.index.to_le_bytes());
429
430            if actual_i == input_index {
431                push_varint(&mut buf, script_code.len() as u64);
432                buf.extend_from_slice(script_code);
433            } else {
434                buf.push(0u8);
435            }
436
437            if actual_i != input_index && (hash_single || hash_none) {
438                buf.extend_from_slice(&0u32.to_le_bytes());
439            } else {
440                buf.extend_from_slice(&(input.sequence as u32).to_le_bytes());
441            }
442        }
443
444        let n_outputs = if hash_none {
445            0
446        } else if hash_single {
447            input_index + 1
448        } else {
449            tx.outputs.len()
450        };
451        push_varint(&mut buf, n_outputs as u64);
452
453        for i in 0..n_outputs {
454            if hash_single && i != input_index {
455                buf.extend_from_slice(&(-1i64).to_le_bytes());
456                buf.push(0u8);
457            } else {
458                let output = &tx.outputs[i];
459                buf.extend_from_slice(&output.value.to_le_bytes());
460                push_varint(&mut buf, output.script_pubkey.len() as u64);
461                buf.extend_from_slice(&output.script_pubkey);
462            }
463        }
464
465        buf.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
466        buf.extend_from_slice(&sighash_u32.to_le_bytes());
467
468        let first_hash = Sha256::digest(buf.as_slice());
469        let second_hash = Sha256::digest(first_hash);
470        let mut result = [0u8; 32];
471        result.copy_from_slice(&second_hash);
472        result
473    })
474}
475
476/// Batch-compute legacy sighashes for all inputs of a single transaction using
477/// SHA256 forward midstate caching. For SIGHASH_ALL (most common), the SHA256 state
478/// after processing inputs 0..i-1 with blank scripts is reused for input i,
479/// cutting the O(N²) hashing work roughly in half.
480///
481/// Falls back to per-input compute for ANYONECANPAY/SINGLE/NONE hash types.
482#[cfg(feature = "production")]
483#[spec_locked("5.1.1")]
484pub fn compute_sighashes_batch(
485    tx: &Transaction,
486    script_codes: &[&[u8]],
487    sighash_bytes: &[u8],
488) -> Vec<[u8; 32]> {
489    use sha2::{Digest, Sha256};
490    let n = tx.inputs.len();
491    debug_assert_eq!(script_codes.len(), n);
492    debug_assert_eq!(sighash_bytes.len(), n);
493
494    let mut results = Vec::with_capacity(n);
495
496    let all_sighash_all = sighash_bytes.iter().all(|&b| {
497        let base = (b as u32) & 0x1f;
498        let acp = (b as u32) & 0x80;
499        base == 0x01 && acp == 0
500    });
501
502    if !all_sighash_all || n <= 1 {
503        for i in 0..n {
504            results.push(compute_legacy_sighash_nocache(
505                tx,
506                i,
507                script_codes[i],
508                sighash_bytes[i],
509            ));
510        }
511        return results;
512    }
513
514    // Pre-serialize outputs + locktime into reusable buffer
515    let mut outputs_buf: Vec<u8> = Vec::with_capacity(tx.outputs.len() * 40 + 16);
516    write_varint_to_vec(&mut outputs_buf, tx.outputs.len() as u64);
517    for output in tx.outputs.iter() {
518        outputs_buf.extend_from_slice(&output.value.to_le_bytes());
519        write_varint_to_vec(&mut outputs_buf, output.script_pubkey.len() as u64);
520        outputs_buf.extend_from_slice(&output.script_pubkey);
521    }
522    outputs_buf.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
523
524    // Forward midstates: state after version + varint(n) + blank_input_0 + ... + blank_input_{j-1}
525    let mut running = Sha256::new();
526    running.update((tx.version as u32).to_le_bytes());
527    update_varint(&mut running, n as u64);
528
529    let mut midstates: Vec<Sha256> = Vec::with_capacity(n);
530    for j in 0..n {
531        midstates.push(running.clone());
532        running.update(tx.inputs[j].prevout.hash);
533        running.update(tx.inputs[j].prevout.index.to_le_bytes());
534        running.update([0u8]);
535        running.update((tx.inputs[j].sequence as u32).to_le_bytes());
536    }
537
538    let sighash_u32_le = 0x01u32.to_le_bytes();
539
540    for i in 0..n {
541        let mut h = midstates[i].clone();
542
543        // Input i with script_code
544        h.update(tx.inputs[i].prevout.hash);
545        h.update(tx.inputs[i].prevout.index.to_le_bytes());
546        update_varint(&mut h, script_codes[i].len() as u64);
547        h.update(script_codes[i]);
548        h.update((tx.inputs[i].sequence as u32).to_le_bytes());
549
550        // Remaining blank inputs i+1..N-1
551        for j in (i + 1)..n {
552            h.update(tx.inputs[j].prevout.hash);
553            h.update(tx.inputs[j].prevout.index.to_le_bytes());
554            h.update([0u8]);
555            h.update((tx.inputs[j].sequence as u32).to_le_bytes());
556        }
557
558        // Outputs + locktime + sighash_type
559        h.update(outputs_buf.as_slice());
560        h.update(sighash_u32_le);
561
562        let first_hash = h.finalize();
563        let second_hash = Sha256::digest(first_hash);
564        let mut result = [0u8; 32];
565        result.copy_from_slice(&second_hash);
566        results.push(result);
567    }
568
569    results
570}
571
572/// Calculate transaction sighash for signature verification
573///
574/// This implements the Bitcoin transaction hash algorithm used for ECDSA signatures.
575/// The sighash determines which parts of the transaction are signed.
576///
577/// Checks for precomputed templates
578/// before computing sighash from scratch.
579///
580/// # Arguments
581/// * `tx` - The transaction being signed
582/// * `input_index` - Index of the input being signed
583/// * `prevouts` - Previous transaction outputs (for input validation)
584/// * `sighash_type` - Type of sighash to calculate
585/// * `script_code` - Optional script code to use instead of scriptPubKey (for P2SH redeem script)
586///
587/// # Returns
588/// 32-byte hash to be signed with ECDSA
589#[spec_locked("5.1")]
590pub fn calculate_transaction_sighash(
591    tx: &Transaction,
592    input_index: usize,
593    prevouts: &[TransactionOutput],
594    sighash_type: SighashType,
595) -> Result<Hash> {
596    // Convert prevouts to parallel slices for the optimized API
597    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
598    let prevout_script_pubkeys: Vec<&[u8]> =
599        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
600    // Validate prevouts match inputs
601    if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
602        return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
603            prevout_values.len(),
604            tx.inputs.len(),
605        ));
606    }
607    calculate_transaction_sighash_with_script_code(
608        tx,
609        input_index,
610        &prevout_values,
611        &prevout_script_pubkeys,
612        sighash_type,
613        None,
614        #[cfg(feature = "production")]
615        None,
616    )
617}
618
619/// Calculate sighash for a single input without requiring full prevout arrays.
620/// Takes only (script_for_signing, prevout_value) for the current input. Non-signing inputs
621/// use empty script internally. Eliminates need for workers to build full refs per tx.
622/// script_for_signing is the script that goes into the preimage (scriptPubKey or redeem script).
623#[spec_locked("5.1")]
624pub fn calculate_transaction_sighash_single_input(
625    tx: &Transaction,
626    input_index: usize,
627    script_for_signing: &[u8],
628    prevout_value: i64,
629    sighash_type: SighashType,
630    #[cfg(feature = "production")] sighash_cache: Option<&SighashMidstateCache>,
631) -> Result<Hash> {
632    if input_index >= tx.inputs.len() {
633        return Err(crate::error::ConsensusError::InvalidInputIndex(input_index));
634    }
635    // Pass script_code=Some directly — avoids building SmallVec of N refs and Vec of N
636    // prevout_values. Legacy sighash doesn't use prevout_values, and the fast-path helpers
637    // + build_preimage_and_hash only access script_code for the signing input.
638    #[cfg(feature = "production")]
639    return calculate_transaction_sighash_with_script_code(
640        tx,
641        input_index,
642        &[],
643        &[],
644        sighash_type,
645        Some(script_for_signing),
646        sighash_cache,
647    );
648    #[cfg(not(feature = "production"))]
649    {
650        let mut prevout_values = vec![0i64; tx.inputs.len()];
651        prevout_values[input_index] = prevout_value;
652        let prevout_script_pubkeys: Vec<&[u8]> = (0..tx.inputs.len())
653            .map(|i| {
654                if i == input_index {
655                    script_for_signing
656                } else {
657                    &[]
658                }
659            })
660            .collect();
661        calculate_transaction_sighash_with_script_code(
662            tx,
663            input_index,
664            &prevout_values,
665            &prevout_script_pubkeys,
666            sighash_type,
667            Some(script_for_signing),
668        )
669    }
670}
671
672/// Calculate transaction sighash with optional script code override
673///
674/// For P2SH transactions, script_code should be the redeem script (not the scriptPubKey).
675/// For non-P2SH, script_code should be None (uses scriptPubKey from prevout).
676/// When sighash_cache is provided (CCheckQueue path), caches (scriptCode, sighash_byte) -> hash for multisig reuse.
677#[spec_locked("5.1")]
678pub fn calculate_transaction_sighash_with_script_code(
679    tx: &Transaction,
680    input_index: usize,
681    prevout_values: &[i64],
682    prevout_script_pubkeys: &[&[u8]],
683    sighash_type: SighashType,
684    script_code: Option<&[u8]>,
685    #[cfg(feature = "production")] sighash_cache: Option<&SighashMidstateCache>,
686) -> Result<Hash> {
687    #[cfg(all(feature = "production", feature = "profile"))]
688    let _t0 = std::time::Instant::now();
689
690    // Validate input index
691    if input_index >= tx.inputs.len() {
692        return Err(crate::error::ConsensusError::InvalidInputIndex(input_index));
693    }
694
695    // When script_code is provided, prevout_script_pubkeys/prevout_values aren't needed
696    // for legacy sighash (only the signing input's scriptCode matters, and prevout_values
697    // aren't part of the legacy preimage). Skip validation to allow empty slices.
698    if script_code.is_none()
699        && (prevout_values.len() != tx.inputs.len()
700            || prevout_script_pubkeys.len() != tx.inputs.len())
701    {
702        return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
703            prevout_values.len(),
704            tx.inputs.len(),
705        ));
706    }
707
708    let sighash_byte = sighash_type.as_u32();
709    let base_type = sighash_byte & 0x1f;
710    let anyone_can_pay = (sighash_byte & 0x80) != 0;
711    let hash_none = base_type == 0x02; // SIGHASH_NONE
712    let hash_single = base_type == 0x03; // SIGHASH_SINGLE
713
714    // SIGHASH_SINGLE special case: if input_index >= outputs count,
715    // consensus returns the hash 0x0000...0001 (a historical quirk)
716    if hash_single && input_index >= tx.outputs.len() {
717        let mut result = [0u8; 32];
718        result[0] = 1; // Little-endian 1
719        return Ok(result);
720    }
721
722    // Core-style midstate cache: (prevout, scriptCode, sighash_byte) -> hash. Key must include prevout.
723    // When sighash_cache is None, use thread-local (avoids Mutex contention across workers).
724    #[cfg(feature = "production")]
725    {
726        let prevout = &tx.inputs[input_index].prevout;
727        let code = script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]);
728        let sighash_byte_u8 = sighash_byte as u8;
729        let hash = sighash_cache_hash(prevout, code, sighash_byte_u8);
730        let cached = if let Some(cache) = sighash_cache {
731            cache.lock().ok().and_then(|guard| {
732                (*guard)
733                    .raw_entry()
734                    .from_hash(hash, |k: &SighashCacheKey| {
735                        k.prevout == *prevout
736                            && k.code_hash == hash
737                            && k.sighash_byte == sighash_byte_u8
738                    })
739                    .map(|(_, v)| *v)
740            })
741        } else {
742            SIGHASH_MIDSTATE_CACHE.with(|cell| {
743                let map = cell.borrow();
744                map.raw_entry()
745                    .from_hash(hash, |k: &SighashCacheKey| {
746                        k.prevout == *prevout
747                            && k.code_hash == hash
748                            && k.sighash_byte == sighash_byte_u8
749                    })
750                    .map(|(_, v)| *v)
751            })
752        };
753        if let Some(cached) = cached {
754            return Ok(cached);
755        }
756    }
757
758    // Fast path: 1-in-1-out SIGHASH_ALL (common P2PKH pattern). Avoids loop overhead and branches.
759    #[cfg(feature = "production")]
760    if tx.inputs.len() == 1 && input_index == 0 && !anyone_can_pay && !hash_none && !hash_single {
761        let base_type = sighash_byte & 0x1f;
762        if base_type == 0x01 || base_type == 0x00 {
763            let n_out = tx.outputs.len();
764            if n_out == 1 {
765                if let Ok(h) = build_preimage_1in1out_sighash_all(
766                    tx,
767                    prevout_values,
768                    prevout_script_pubkeys,
769                    script_code,
770                    sighash_byte,
771                ) {
772                    #[cfg(all(feature = "production", feature = "profile"))]
773                    crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
774                    #[cfg(feature = "production")]
775                    insert_midstate_cache(
776                        sighash_cache,
777                        tx.inputs[0].prevout,
778                        script_code.unwrap_or_else(|| prevout_script_pubkeys[0]),
779                        sighash_byte as u8,
780                        h,
781                    );
782                    return Ok(h);
783                }
784            } else if (2..=16).contains(&n_out) {
785                if let Ok(h) = build_preimage_1in_nout_sighash_all(
786                    tx,
787                    prevout_script_pubkeys,
788                    script_code,
789                    sighash_byte,
790                ) {
791                    #[cfg(all(feature = "production", feature = "profile"))]
792                    crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
793                    #[cfg(feature = "production")]
794                    insert_midstate_cache(
795                        sighash_cache,
796                        tx.inputs[0].prevout,
797                        script_code.unwrap_or_else(|| prevout_script_pubkeys[0]),
798                        sighash_byte as u8,
799                        h,
800                    );
801                    return Ok(h);
802                }
803            }
804        }
805    }
806
807    // Fast path: 2-in-1-out and 2-in-2-out SIGHASH_ALL (common batched/swap patterns).
808    #[cfg(feature = "production")]
809    if tx.inputs.len() == 2 && input_index < 2 && !anyone_can_pay && !hash_none && !hash_single {
810        let base_type = sighash_byte & 0x1f;
811        if base_type == 0x01 || base_type == 0x00 {
812            let n_out = tx.outputs.len();
813            if n_out == 1 {
814                if let Ok(h) = build_preimage_2in1out_sighash_all(
815                    tx,
816                    input_index,
817                    prevout_values,
818                    prevout_script_pubkeys,
819                    script_code,
820                    sighash_byte,
821                ) {
822                    #[cfg(all(feature = "production", feature = "profile"))]
823                    crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
824                    #[cfg(feature = "production")]
825                    insert_midstate_cache(
826                        sighash_cache,
827                        tx.inputs[input_index].prevout,
828                        script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]),
829                        sighash_byte as u8,
830                        h,
831                    );
832                    return Ok(h);
833                }
834            } else if n_out == 2 {
835                if let Ok(h) = build_preimage_2in2out_sighash_all(
836                    tx,
837                    input_index,
838                    prevout_script_pubkeys,
839                    script_code,
840                    sighash_byte,
841                ) {
842                    #[cfg(all(feature = "production", feature = "profile"))]
843                    crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
844                    #[cfg(feature = "production")]
845                    insert_midstate_cache(
846                        sighash_cache,
847                        tx.inputs[input_index].prevout,
848                        script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]),
849                        sighash_byte as u8,
850                        h,
851                    );
852                    return Ok(h);
853                }
854            }
855        }
856    }
857
858    // Build sighash preimage matching consensus's CTransactionSignatureSerializer
859    let estimated_size = 4 + 2 + (tx.inputs.len() * 50) + 2 + (tx.outputs.len() * 30) + 4 + 4;
860    let capacity = estimated_size.min(4096);
861
862    #[cfg(feature = "production")]
863    let (result, preimage_vec) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
864        let mut preimage = buf_cell.borrow_mut();
865        preimage.clear();
866        if preimage.capacity() < capacity {
867            preimage.reserve(capacity);
868        }
869        build_preimage_and_hash(
870            tx,
871            input_index,
872            prevout_values,
873            prevout_script_pubkeys,
874            script_code,
875            sighash_byte,
876            anyone_can_pay,
877            hash_none,
878            hash_single,
879            &mut preimage,
880        )
881    });
882
883    #[cfg(not(feature = "production"))]
884    let (result, preimage_vec) = {
885        let mut preimage = Vec::with_capacity(capacity);
886        build_preimage_and_hash(
887            tx,
888            input_index,
889            prevout_values,
890            prevout_script_pubkeys,
891            script_code,
892            sighash_byte,
893            anyone_can_pay,
894            hash_none,
895            hash_single,
896            &mut preimage,
897        )
898    };
899
900    #[cfg(all(feature = "production", feature = "profile"))]
901    crate::script_profile::add_sighash_ns(_t0.elapsed().as_nanos() as u64);
902
903    #[cfg(feature = "production")]
904    if let Ok(ref h) = result {
905        insert_midstate_cache(
906            sighash_cache,
907            tx.inputs[input_index].prevout,
908            script_code.unwrap_or_else(|| prevout_script_pubkeys[input_index]),
909            sighash_byte as u8,
910            *h,
911        );
912    }
913    result
914}
915
916/// Fast path for 1-in-1-out SIGHASH_ALL (common P2PKH). Unrolled serialization, no loop overhead.
917#[cfg(feature = "production")]
918#[inline]
919fn build_preimage_1in1out_sighash_all(
920    tx: &crate::types::Transaction,
921    prevout_values: &[i64],
922    prevout_script_pubkeys: &[&[u8]],
923    script_code: Option<&[u8]>,
924    sighash_byte: u32,
925) -> Result<Hash> {
926    let input = &tx.inputs[0];
927    let output = &tx.outputs[0];
928    let code = script_code.unwrap_or_else(|| prevout_script_pubkeys[0]);
929
930    let capacity = 4 + 2 + 36 + 2 + code.len() + 4 + 2 + 8 + 2 + output.script_pubkey.len() + 4 + 4;
931    let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
932        let mut preimage = buf_cell.borrow_mut();
933        preimage.clear();
934        if preimage.capacity() < capacity {
935            preimage.reserve(capacity);
936        }
937        preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
938        preimage.push(1); // n_inputs
939        preimage.extend_from_slice(&input.prevout.hash);
940        preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
941        write_varint_to_vec(&mut preimage, code.len() as u64);
942        preimage.extend_from_slice(code);
943        preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
944        preimage.push(1); // n_outputs
945        preimage.extend_from_slice(&output.value.to_le_bytes());
946        write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
947        preimage.extend_from_slice(&output.script_pubkey);
948        preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
949        preimage.extend_from_slice(&sighash_byte.to_le_bytes());
950        let r = sighash_with_cache(&preimage);
951        (Ok(r), ())
952    });
953    result
954}
955
956/// Fast path for 1-in-N-out SIGHASH_ALL (N=2..16, spend+change). Same structure as 1-in-1-out, small output loop.
957#[cfg(feature = "production")]
958#[inline]
959fn build_preimage_1in_nout_sighash_all(
960    tx: &crate::types::Transaction,
961    prevout_script_pubkeys: &[&[u8]],
962    script_code: Option<&[u8]>,
963    sighash_byte: u32,
964) -> Result<Hash> {
965    let input = &tx.inputs[0];
966    let code = script_code.unwrap_or_else(|| prevout_script_pubkeys[0]);
967    let mut capacity = 4 + 2 + 36 + 2 + code.len() + 4 + 2; // version, n_in, input, n_out
968    for out in &tx.outputs {
969        capacity += 8 + 2 + out.script_pubkey.len();
970    }
971    capacity += 4 + 4; // lock_time, sighash_type
972
973    let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
974        let mut preimage = buf_cell.borrow_mut();
975        preimage.clear();
976        if preimage.capacity() < capacity {
977            preimage.reserve(capacity);
978        }
979        preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
980        preimage.push(1); // n_inputs
981        preimage.extend_from_slice(&input.prevout.hash);
982        preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
983        write_varint_to_vec(&mut preimage, code.len() as u64);
984        preimage.extend_from_slice(code);
985        preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
986        write_varint_to_vec(&mut preimage, tx.outputs.len() as u64);
987        for output in &tx.outputs {
988            preimage.extend_from_slice(&output.value.to_le_bytes());
989            write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
990            preimage.extend_from_slice(&output.script_pubkey);
991        }
992        preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
993        preimage.extend_from_slice(&sighash_byte.to_le_bytes());
994        let r = sighash_with_cache(&preimage);
995        (Ok(r), ())
996    });
997    result
998}
999
1000/// Fast path for 2-in-1-out SIGHASH_ALL (consolidation, batched). Unrolled, no loop.
1001/// Non-signing inputs MUST use empty script (0x00) per consensus.
1002#[cfg(feature = "production")]
1003#[inline]
1004fn build_preimage_2in1out_sighash_all(
1005    tx: &crate::types::Transaction,
1006    input_index: usize,
1007    _prevout_values: &[i64],
1008    prevout_script_pubkeys: &[&[u8]],
1009    script_code: Option<&[u8]>,
1010    sighash_byte: u32,
1011) -> Result<Hash> {
1012    let output = &tx.outputs[0];
1013    let code_len = script_code
1014        .map(|s| s.len())
1015        .unwrap_or_else(|| prevout_script_pubkeys[input_index].len());
1016    let capacity =
1017        4 + 2 + 36 + 2 + code_len + 4 + 36 + 2 + 4 + 8 + 2 + output.script_pubkey.len() + 4 + 4;
1018
1019    let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
1020        let mut preimage = buf_cell.borrow_mut();
1021        preimage.clear();
1022        if preimage.capacity() < capacity {
1023            preimage.reserve(capacity);
1024        }
1025        preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
1026        preimage.push(2);
1027        for (i, inp) in tx.inputs.iter().enumerate().take(2) {
1028            let (script_len, script_slice): (usize, &[u8]) = if i == input_index {
1029                let c = script_code.unwrap_or_else(|| prevout_script_pubkeys[i]);
1030                (c.len(), c)
1031            } else {
1032                (0, &[][..]) // Non-signing input: empty script per consensus
1033            };
1034            preimage.extend_from_slice(&inp.prevout.hash);
1035            preimage.extend_from_slice(&inp.prevout.index.to_le_bytes());
1036            write_varint_to_vec(&mut preimage, script_len as u64);
1037            preimage.extend_from_slice(script_slice);
1038            preimage.extend_from_slice(&(inp.sequence as u32).to_le_bytes());
1039        }
1040        preimage.push(1);
1041        preimage.extend_from_slice(&output.value.to_le_bytes());
1042        write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
1043        preimage.extend_from_slice(&output.script_pubkey);
1044        preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
1045        preimage.extend_from_slice(&sighash_byte.to_le_bytes());
1046        let r = sighash_with_cache(&preimage);
1047        (Ok(r), ())
1048    });
1049    result
1050}
1051
1052/// Fast path for 2-in-2-out SIGHASH_ALL (swap, batched). Unrolled, no loop.
1053/// Non-signing inputs MUST use empty script (0x00) per consensus.
1054#[cfg(feature = "production")]
1055#[inline]
1056fn build_preimage_2in2out_sighash_all(
1057    tx: &crate::types::Transaction,
1058    input_index: usize,
1059    prevout_script_pubkeys: &[&[u8]],
1060    script_code: Option<&[u8]>,
1061    sighash_byte: u32,
1062) -> Result<Hash> {
1063    let code_len = script_code
1064        .map(|s| s.len())
1065        .unwrap_or_else(|| prevout_script_pubkeys[input_index].len());
1066    let mut capacity = 4 + 2 + 36 + 2 + code_len + 4 + 36 + 2 + 4;
1067    for out in &tx.outputs {
1068        capacity += 8 + 2 + out.script_pubkey.len();
1069    }
1070    capacity += 4 + 4;
1071
1072    let (result, _) = SIGHASH_PREIMAGE_BUF.with(|buf_cell| {
1073        let mut preimage = buf_cell.borrow_mut();
1074        preimage.clear();
1075        if preimage.capacity() < capacity {
1076            preimage.reserve(capacity);
1077        }
1078        preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
1079        preimage.push(2);
1080        for (i, inp) in tx.inputs.iter().enumerate().take(2) {
1081            let (script_len, script_slice): (usize, &[u8]) = if i == input_index {
1082                let c = script_code.unwrap_or_else(|| prevout_script_pubkeys[i]);
1083                (c.len(), c)
1084            } else {
1085                (0, &[][..])
1086            };
1087            preimage.extend_from_slice(&inp.prevout.hash);
1088            preimage.extend_from_slice(&inp.prevout.index.to_le_bytes());
1089            write_varint_to_vec(&mut preimage, script_len as u64);
1090            preimage.extend_from_slice(script_slice);
1091            preimage.extend_from_slice(&(inp.sequence as u32).to_le_bytes());
1092        }
1093        write_varint_to_vec(&mut preimage, 2);
1094        for output in &tx.outputs {
1095            preimage.extend_from_slice(&output.value.to_le_bytes());
1096            write_varint_to_vec(&mut preimage, output.script_pubkey.len() as u64);
1097            preimage.extend_from_slice(&output.script_pubkey);
1098        }
1099        preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
1100        preimage.extend_from_slice(&sighash_byte.to_le_bytes());
1101        let r = sighash_with_cache(&preimage);
1102        (Ok(r), ())
1103    });
1104    result
1105}
1106
1107#[inline]
1108fn build_preimage_and_hash(
1109    tx: &crate::types::Transaction,
1110    input_index: usize,
1111    prevout_values: &[i64],
1112    prevout_script_pubkeys: &[&[u8]],
1113    script_code: Option<&[u8]>,
1114    sighash_byte: u32,
1115    anyone_can_pay: bool,
1116    hash_none: bool,
1117    hash_single: bool,
1118    preimage: &mut Vec<u8>,
1119) -> (Result<Hash>, ()) {
1120    // 1. Transaction version (4 bytes LE)
1121    preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
1122
1123    // 2. Input count: ANYONECANPAY → 1, otherwise all inputs
1124    let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
1125    write_varint_to_vec(preimage, n_inputs as u64);
1126
1127    // 3. Inputs
1128    for i in 0..n_inputs {
1129        // ANYONECANPAY remaps input index to the signing input
1130        let actual_i = if anyone_can_pay { input_index } else { i };
1131        let input = &tx.inputs[actual_i];
1132
1133        // Prevout (always serialized)
1134        preimage.extend_from_slice(&input.prevout.hash);
1135        preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
1136
1137        // Script: signing input gets script_code/scriptPubKey, others get empty
1138        if actual_i == input_index {
1139            let code = match script_code {
1140                Some(s) => s,
1141                None => prevout_script_pubkeys[actual_i],
1142            };
1143            write_varint_to_vec(preimage, code.len() as u64);
1144            preimage.extend_from_slice(code);
1145        } else {
1146            preimage.push(0); // empty script
1147        }
1148
1149        // Sequence: for SIGHASH_NONE/SINGLE, non-signing inputs get sequence 0
1150        if actual_i != input_index && (hash_single || hash_none) {
1151            preimage.extend_from_slice(&0u32.to_le_bytes());
1152        } else {
1153            preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
1154        }
1155    }
1156
1157    // 4. Output count: NONE → 0, SINGLE → input_index+1, ALL → all
1158    let n_outputs = if hash_none {
1159        0
1160    } else if hash_single {
1161        input_index + 1
1162    } else {
1163        tx.outputs.len()
1164    };
1165    write_varint_to_vec(preimage, n_outputs as u64);
1166
1167    // 5. Outputs
1168    for i in 0..n_outputs {
1169        if hash_single && i != input_index {
1170            // SIGHASH_SINGLE: non-matching outputs are CTxOut() (value=-1, empty script)
1171            preimage.extend_from_slice(&(-1i64).to_le_bytes()); // -1 as i64 = 0xffffffffffffffff
1172            preimage.push(0); // empty script
1173        } else {
1174            let output = &tx.outputs[i];
1175            preimage.extend_from_slice(&output.value.to_le_bytes());
1176            write_varint_to_vec(preimage, output.script_pubkey.len() as u64);
1177            preimage.extend_from_slice(&output.script_pubkey);
1178        }
1179    }
1180
1181    // 6. Lock time (4 bytes LE)
1182    preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
1183
1184    // 7. SIGHASH type (4 bytes LE) - use the raw sighash byte value
1185    preimage.extend_from_slice(&sighash_byte.to_le_bytes());
1186
1187    // Double SHA256; in production use cache (first_hash as key) to save one SHA256 on hit
1188    #[cfg(feature = "production")]
1189    let result = sighash_with_cache(preimage);
1190    #[cfg(not(feature = "production"))]
1191    let result = {
1192        let hasher = OptimizedSha256::new();
1193        let first_hash = hasher.hash(&preimage);
1194        let second_hash = hasher.hash(&first_hash);
1195        let mut r = [0u8; 32];
1196        r.copy_from_slice(&second_hash);
1197        r
1198    };
1199    (Ok(result), ())
1200}
1201
1202/// Batch compute sighashes for all inputs of a transaction
1203///
1204/// This function computes sighashes for all inputs at once, which is useful when
1205/// validating transactions with many inputs. The sighashes are computed in parallel
1206/// when the production feature is enabled.
1207///
1208/// # Arguments
1209/// * `tx` - The transaction being signed
1210/// * `prevouts` - Previous transaction outputs (for input validation)
1211/// * `sighash_type` - Type of sighash to calculate (must be the same for all inputs)
1212///
1213/// # Returns
1214/// Vector of 32-byte hashes, one per input (in same order)
1215#[spec_locked("5.1.1")]
1216pub fn batch_compute_sighashes(
1217    tx: &Transaction,
1218    prevouts: &[TransactionOutput],
1219    sighash_type: SighashType,
1220) -> Result<Vec<Hash>> {
1221    // Validate prevouts match inputs
1222    if prevouts.len() != tx.inputs.len() {
1223        return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
1224            prevouts.len(),
1225            tx.inputs.len(),
1226        ));
1227    }
1228
1229    // Convert prevouts to parallel slices for the optimized API
1230    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
1231    let prevout_script_pubkeys: Vec<&[u8]> =
1232        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
1233
1234    #[cfg(feature = "production")]
1235    {
1236        // Use correct legacy sighash preimage (build_legacy_sighash_preimage_into)
1237        // via batch_compute_legacy_sighashes. Fixes ANYONECANPAY/NONE/SINGLE handling.
1238        let sighash_byte = sighash_type.as_u32() as u8;
1239        let specs: Vec<(usize, u8, &[u8])> = (0..tx.inputs.len())
1240            .map(|i| (i, sighash_byte, prevout_script_pubkeys[i]))
1241            .collect();
1242        let hashes =
1243            batch_compute_legacy_sighashes(tx, &prevout_values, &prevout_script_pubkeys, &specs)?;
1244        Ok(hashes)
1245    }
1246
1247    #[cfg(not(feature = "production"))]
1248    {
1249        // Sequential fallback for non-production builds
1250        let mut results = Vec::with_capacity(tx.inputs.len());
1251        for i in 0..tx.inputs.len() {
1252            results.push(calculate_transaction_sighash_with_script_code(
1253                tx,
1254                i,
1255                &prevout_values,
1256                &prevout_script_pubkeys,
1257                sighash_type,
1258                None,
1259            )?);
1260        }
1261        Ok(results)
1262    }
1263}
1264
1265/// Build legacy sighash preimage into a reusable buffer (zero alloc).
1266#[cfg(feature = "production")]
1267fn build_legacy_sighash_preimage_into(
1268    preimage: &mut Vec<u8>,
1269    tx: &Transaction,
1270    input_index: usize,
1271    prevout_values: &[i64],
1272    prevout_script_pubkeys: &[&[u8]],
1273    script_code: &[u8],
1274    sighash_byte: u32,
1275) {
1276    let anyone_can_pay = (sighash_byte & 0x80) != 0;
1277    let hash_none = (sighash_byte & 0x1f) == 0x02;
1278    let hash_single = (sighash_byte & 0x1f) == 0x03;
1279    let n_inputs = if anyone_can_pay { 1 } else { tx.inputs.len() };
1280    let n_outputs = if hash_none {
1281        0
1282    } else if hash_single {
1283        input_index + 1
1284    } else {
1285        tx.outputs.len()
1286    };
1287    preimage.clear();
1288    preimage.reserve(512);
1289    preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
1290    write_varint_to_vec(preimage, n_inputs as u64);
1291    for i in 0..n_inputs {
1292        let actual_i = if anyone_can_pay { input_index } else { i };
1293        let input = &tx.inputs[actual_i];
1294        preimage.extend_from_slice(&input.prevout.hash);
1295        preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
1296        if actual_i == input_index {
1297            write_varint_to_vec(preimage, script_code.len() as u64);
1298            preimage.extend_from_slice(script_code);
1299        } else {
1300            preimage.push(0);
1301        }
1302        if actual_i != input_index && (hash_single || hash_none) {
1303            preimage.extend_from_slice(&0u32.to_le_bytes());
1304        } else {
1305            preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
1306        }
1307    }
1308    write_varint_to_vec(preimage, n_outputs as u64);
1309    for i in 0..n_outputs {
1310        // SIGHASH_SINGLE: non-matching outputs, or input_index >= outputs.len() (invalid but must not panic)
1311        let use_missing_output =
1312            hash_single && (i != input_index || input_index >= tx.outputs.len());
1313        if use_missing_output {
1314            preimage.extend_from_slice(&(-1i64).to_le_bytes());
1315            preimage.push(0);
1316        } else {
1317            let output = &tx.outputs[i];
1318            preimage.extend_from_slice(&output.value.to_le_bytes());
1319            write_varint_to_vec(preimage, output.script_pubkey.len() as u64);
1320            preimage.extend_from_slice(&output.script_pubkey);
1321        }
1322    }
1323    preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
1324    preimage.extend_from_slice(&sighash_byte.to_le_bytes());
1325}
1326
1327/// Batch compute legacy sighashes for specified inputs.
1328/// Roadmap #12: Precompute before script execution for P2PKH-heavy blocks (100k band).
1329/// Each spec is (input_index, sighash_byte, script_code). Returns hashes in spec order.
1330/// Uses thread-local reusable buffers; no per-spec Vec allocs.
1331#[cfg(feature = "production")]
1332#[spec_locked("5.1.1")]
1333pub fn batch_compute_legacy_sighashes(
1334    tx: &Transaction,
1335    prevout_values: &[i64],
1336    prevout_script_pubkeys: &[&[u8]],
1337    specs: &[(usize, u8, &[u8])],
1338) -> Result<Vec<[u8; 32]>> {
1339    if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
1340        return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
1341            prevout_values.len(),
1342            tx.inputs.len(),
1343        ));
1344    }
1345    // SIGHASH_SINGLE with input_index >= outputs.len(): consensus hash is 0x0000...0001
1346    const SIGHASH_SINGLE_INVALID: [u8; 32] = {
1347        let mut h = [0u8; 32];
1348        h[0] = 1;
1349        h
1350    };
1351
1352    LEGACY_BATCH_PREIMAGES.with(|cell| {
1353        let mut storage = cell.borrow_mut();
1354        storage.resize_with(specs.len(), || Vec::with_capacity(512));
1355        let mut fixed_indices: Vec<usize> = Vec::new();
1356        for (i, &(input_index, sighash_byte, script_code)) in specs.iter().enumerate() {
1357            let hash_single = (sighash_byte & 0x1f) == 0x03;
1358            if hash_single && input_index >= tx.outputs.len() {
1359                fixed_indices.push(i);
1360                continue;
1361            }
1362            build_legacy_sighash_preimage_into(
1363                &mut storage[i],
1364                tx,
1365                input_index,
1366                prevout_values,
1367                prevout_script_pubkeys,
1368                script_code,
1369                sighash_byte as u32,
1370            );
1371        }
1372        // Batch hash only preimages we built; fixed_indices get SIGHASH_SINGLE_INVALID
1373        let preimage_refs: Vec<&[u8]> = storage
1374            .iter()
1375            .enumerate()
1376            .filter(|(i, _)| !fixed_indices.contains(i))
1377            .map(|(_, v)| v.as_slice())
1378            .collect();
1379        let batch_hashes =
1380            crate::optimizations::simd_vectorization::batch_double_sha256(&preimage_refs);
1381        // Merge: fill result in spec order
1382        let mut result = vec![[0u8; 32]; specs.len()];
1383        let mut batch_idx = 0;
1384        for (i, slot) in result.iter_mut().enumerate() {
1385            if fixed_indices.contains(&i) {
1386                *slot = SIGHASH_SINGLE_INVALID;
1387            } else {
1388                *slot = batch_hashes[batch_idx];
1389                batch_idx += 1;
1390            }
1391        }
1392        Ok(result)
1393    })
1394}
1395
1396/// Clear sighash cache. Useful for benchmarking to ensure consistent results.
1397/// Clears the thread-local SIGHASH_CACHE on the current thread.
1398#[cfg(all(feature = "production", feature = "benchmarking"))]
1399pub fn clear_sighash_templates() {
1400    SIGHASH_CACHE.with(|cell| {
1401        cell.borrow_mut().clear();
1402    });
1403}
1404
1405fn encode_varint(value: u64) -> Vec<u8> {
1406    if value < 0xfd {
1407        vec![value as u8]
1408    } else if value <= 0xffff {
1409        let mut result = vec![0xfd];
1410        result.extend_from_slice(&(value as u16).to_le_bytes());
1411        result
1412    } else if value <= 0xffffffff {
1413        let mut result = vec![0xfe];
1414        result.extend_from_slice(&(value as u32).to_le_bytes());
1415        result
1416    } else {
1417        let mut result = vec![0xff];
1418        result.extend_from_slice(&value.to_le_bytes());
1419        result
1420    }
1421}
1422
1423// =============================================================================
1424// BIP143: Segregated Witness Sighash Algorithm
1425// =============================================================================
1426//
1427// BIP143 defines a new transaction sighash algorithm for SegWit transactions.
1428// Key optimization: hashPrevouts, hashSequence, and hashOutputs are computed
1429// ONCE for all inputs, instead of once per input like legacy sighash.
1430//
1431// Reference: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
1432
1433/// Precomputed hash components for BIP143 sighash.
1434/// These are computed once per transaction and reused for all inputs.
1435#[derive(Clone, Debug)]
1436pub struct Bip143PrecomputedHashes {
1437    /// SHA256(SHA256(all input prevouts)) - 0 if ANYONECANPAY
1438    pub hash_prevouts: [u8; 32],
1439    /// SHA256(SHA256(all input sequences)) - 0 if ANYONECANPAY/NONE/SINGLE  
1440    pub hash_sequence: [u8; 32],
1441    /// SHA256(SHA256(all outputs)) - varies by sighash type
1442    pub hash_outputs: [u8; 32],
1443}
1444
1445impl Bip143PrecomputedHashes {
1446    /// Compute precomputed hashes for a transaction.
1447    /// This is the expensive part - compute once, reuse for all inputs.
1448    /// Production: uses thread-local buffer to avoid 3 Vec allocs per SegWit tx.
1449    #[inline]
1450    pub fn compute(
1451        tx: &Transaction,
1452        _prevout_values: &[i64],
1453        _prevout_script_pubkeys: &[&[u8]],
1454    ) -> Self {
1455        #[cfg(feature = "production")]
1456        {
1457            let hash_prevouts = BIP143_SERIALIZE_BUF.with(|cell| {
1458                let mut data = cell.borrow_mut();
1459                data.clear();
1460                data.reserve(tx.inputs.len() * 36);
1461                for input in tx.inputs.iter() {
1462                    data.extend_from_slice(&input.prevout.hash);
1463                    data.extend_from_slice(&input.prevout.index.to_le_bytes());
1464                }
1465                double_sha256(&data)
1466            });
1467
1468            let hash_sequence = BIP143_SERIALIZE_BUF.with(|cell| {
1469                let mut data = cell.borrow_mut();
1470                data.clear();
1471                data.reserve(tx.inputs.len() * 4);
1472                for input in tx.inputs.iter() {
1473                    data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
1474                }
1475                double_sha256(&data)
1476            });
1477
1478            let hash_outputs = BIP143_SERIALIZE_BUF.with(|cell| {
1479                let mut data = cell.borrow_mut();
1480                data.clear();
1481                let cap = tx
1482                    .outputs
1483                    .iter()
1484                    .map(|o| 8 + 5 + o.script_pubkey.len())
1485                    .sum::<usize>();
1486                data.reserve(cap);
1487                for output in tx.outputs.iter() {
1488                    data.extend_from_slice(&output.value.to_le_bytes());
1489                    write_varint_to_vec(&mut data, output.script_pubkey.len() as u64);
1490                    data.extend_from_slice(&output.script_pubkey);
1491                }
1492                double_sha256(&data)
1493            });
1494
1495            Self {
1496                hash_prevouts,
1497                hash_sequence,
1498                hash_outputs,
1499            }
1500        }
1501
1502        #[cfg(not(feature = "production"))]
1503        {
1504            // hashPrevouts = SHA256(SHA256(all outpoints))
1505            let hash_prevouts = {
1506                let mut data = Vec::with_capacity(tx.inputs.len() * 36);
1507                for input in tx.inputs.iter() {
1508                    data.extend_from_slice(&input.prevout.hash);
1509                    data.extend_from_slice(&input.prevout.index.to_le_bytes());
1510                }
1511                double_sha256(&data)
1512            };
1513
1514            let hash_sequence = {
1515                let mut data = Vec::with_capacity(tx.inputs.len() * 4);
1516                for input in tx.inputs.iter() {
1517                    data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
1518                }
1519                double_sha256(&data)
1520            };
1521
1522            let hash_outputs = {
1523                let mut data = Vec::with_capacity(tx.outputs.len() * 34);
1524                for output in tx.outputs.iter() {
1525                    data.extend_from_slice(&output.value.to_le_bytes());
1526                    write_varint_to_vec(&mut data, output.script_pubkey.len() as u64);
1527                    data.extend_from_slice(&output.script_pubkey);
1528                }
1529                double_sha256(&data)
1530            };
1531
1532            Self {
1533                hash_prevouts,
1534                hash_sequence,
1535                hash_outputs,
1536            }
1537        }
1538    }
1539}
1540
1541/// Double SHA256 helper. Uses OptimizedSha256 (SHA-NI when available).
1542#[inline(always)]
1543fn double_sha256(data: &[u8]) -> [u8; 32] {
1544    let hasher = OptimizedSha256::new();
1545    hasher.hash256(data)
1546}
1547
1548/// Calculate BIP143 sighash for SegWit transactions.
1549///
1550/// This is significantly faster than legacy sighash for transactions with
1551/// multiple inputs because hashPrevouts, hashSequence, and hashOutputs are
1552/// computed once and reused.
1553///
1554/// # Arguments
1555/// * `tx` - The transaction being signed
1556/// * `input_index` - Index of the input being signed
1557/// * `script_code` - The scriptCode for this input (P2WPKH: pubkeyhash script, P2WSH: witness script)
1558/// * `amount` - Value of the UTXO being spent (in satoshis)
1559/// * `sighash_type` - Sighash type byte
1560/// * `precomputed` - Optional precomputed hashes (compute once, pass to all inputs)
1561///
1562/// # Returns
1563/// 32-byte sighash for signature verification
1564#[spec_locked("11.1.9")]
1565pub fn calculate_bip143_sighash(
1566    tx: &Transaction,
1567    input_index: usize,
1568    script_code: &[u8],
1569    amount: i64,
1570    sighash_type: u8,
1571    precomputed: Option<&Bip143PrecomputedHashes>,
1572) -> Result<Hash> {
1573    if input_index >= tx.inputs.len() {
1574        return Err(crate::error::ConsensusError::InvalidInputIndex(input_index));
1575    }
1576
1577    // Parse sighash flags
1578    let anyone_can_pay = (sighash_type & 0x80) != 0;
1579    let base_type = sighash_type & 0x1f;
1580    let is_none = base_type == 0x02;
1581    let is_single = base_type == 0x03;
1582
1583    // Use precomputed hashes or compute them
1584    let computed;
1585    let hashes = match precomputed {
1586        Some(h) => h,
1587        None => {
1588            computed = Bip143PrecomputedHashes::compute(tx, &[], &[]);
1589            &computed
1590        }
1591    };
1592
1593    // Build sighash preimage according to BIP143
1594    // Estimated size: 4+32+32+36+var+8+4+32+4+4 = ~160 bytes + script_code
1595    #[cfg(feature = "production")]
1596    let preimage_result = BIP143_PREIMAGE_BUF.with(|buf_cell| {
1597        let mut preimage = buf_cell.borrow_mut();
1598        preimage.clear();
1599        let cap = 160 + script_code.len();
1600        if preimage.capacity() < cap {
1601            preimage.reserve(cap);
1602        }
1603        build_bip143_preimage(
1604            tx,
1605            input_index,
1606            script_code,
1607            amount,
1608            sighash_type,
1609            anyone_can_pay,
1610            is_none,
1611            is_single,
1612            hashes,
1613            &mut preimage,
1614        )
1615    });
1616    #[cfg(not(feature = "production"))]
1617    let preimage_result = {
1618        let mut preimage = Vec::with_capacity(160 + script_code.len());
1619        build_bip143_preimage(
1620            tx,
1621            input_index,
1622            script_code,
1623            amount,
1624            sighash_type,
1625            anyone_can_pay,
1626            is_none,
1627            is_single,
1628            hashes,
1629            &mut preimage,
1630        )
1631    };
1632    preimage_result
1633}
1634
1635#[inline]
1636fn build_bip143_preimage(
1637    tx: &Transaction,
1638    input_index: usize,
1639    script_code: &[u8],
1640    amount: i64,
1641    sighash_type: u8,
1642    anyone_can_pay: bool,
1643    is_none: bool,
1644    is_single: bool,
1645    hashes: &Bip143PrecomputedHashes,
1646    preimage: &mut Vec<u8>,
1647) -> Result<Hash> {
1648    // 1. nVersion (4 bytes LE)
1649    preimage.extend_from_slice(&(tx.version as u32).to_le_bytes());
1650
1651    // 2. hashPrevouts (32 bytes) - 0 if ANYONECANPAY
1652    if anyone_can_pay {
1653        preimage.extend_from_slice(&[0u8; 32]);
1654    } else {
1655        preimage.extend_from_slice(&hashes.hash_prevouts);
1656    }
1657
1658    // 3. hashSequence (32 bytes) - 0 if ANYONECANPAY/NONE/SINGLE
1659    if anyone_can_pay || is_none || is_single {
1660        preimage.extend_from_slice(&[0u8; 32]);
1661    } else {
1662        preimage.extend_from_slice(&hashes.hash_sequence);
1663    }
1664
1665    // 4. outpoint (36 bytes: 32 hash + 4 index)
1666    let input = &tx.inputs[input_index];
1667    preimage.extend_from_slice(&input.prevout.hash);
1668    preimage.extend_from_slice(&input.prevout.index.to_le_bytes());
1669
1670    // 5. scriptCode (varint + script)
1671    write_varint_to_vec(preimage, script_code.len() as u64);
1672    preimage.extend_from_slice(script_code);
1673
1674    // 6. amount (8 bytes LE) - value of the UTXO being spent
1675    preimage.extend_from_slice(&amount.to_le_bytes());
1676
1677    // 7. nSequence (4 bytes LE)
1678    preimage.extend_from_slice(&(input.sequence as u32).to_le_bytes());
1679
1680    // 8. hashOutputs (32 bytes) - varies by sighash type
1681    if is_none {
1682        preimage.extend_from_slice(&[0u8; 32]);
1683    } else if is_single {
1684        if input_index < tx.outputs.len() {
1685            // Hash only the output at same index (reuse buffer to avoid per-input alloc)
1686            let output = &tx.outputs[input_index];
1687            #[cfg(feature = "production")]
1688            let hash_outputs = BIP143_SINGLE_OUTPUT_BUF.with(|buf_cell| {
1689                let mut output_data = buf_cell.borrow_mut();
1690                output_data.clear();
1691                let cap = 8 + 9 + output.script_pubkey.len(); // value + varint + script
1692                if output_data.capacity() < cap {
1693                    output_data.reserve(cap);
1694                }
1695                output_data.extend_from_slice(&output.value.to_le_bytes());
1696                write_varint_to_vec(&mut output_data, output.script_pubkey.len() as u64);
1697                output_data.extend_from_slice(&output.script_pubkey);
1698                double_sha256(&output_data)
1699            });
1700            #[cfg(not(feature = "production"))]
1701            let hash_outputs = {
1702                let mut output_data = Vec::with_capacity(8 + 9 + output.script_pubkey.len());
1703                output_data.extend_from_slice(&output.value.to_le_bytes());
1704                write_varint_to_vec(&mut output_data, output.script_pubkey.len() as u64);
1705                output_data.extend_from_slice(&output.script_pubkey);
1706                double_sha256(&output_data)
1707            };
1708            preimage.extend_from_slice(&hash_outputs);
1709        } else {
1710            // SIGHASH_SINGLE with no corresponding output
1711            preimage.extend_from_slice(&[0u8; 32]);
1712        }
1713    } else {
1714        preimage.extend_from_slice(&hashes.hash_outputs);
1715    }
1716
1717    // 9. nLockTime (4 bytes LE)
1718    preimage.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
1719
1720    // 10. sighash type (4 bytes LE)
1721    preimage.extend_from_slice(&(sighash_type as u32).to_le_bytes());
1722
1723    // Double SHA256 the preimage
1724    Ok(double_sha256(preimage))
1725}
1726
1727/// Batch compute BIP143 sighashes for all inputs.
1728/// This is the optimal way to verify a SegWit transaction - compute precomputed
1729/// hashes once, then calculate sighash for each input.
1730#[spec_locked("11.1.9")]
1731pub fn batch_compute_bip143_sighashes(
1732    tx: &Transaction,
1733    prevout_values: &[i64],
1734    prevout_script_pubkeys: &[&[u8]],
1735    script_codes: &[&[u8]],
1736    sighash_type: u8,
1737) -> Result<Vec<Hash>> {
1738    if prevout_values.len() != tx.inputs.len()
1739        || prevout_script_pubkeys.len() != tx.inputs.len()
1740        || script_codes.len() != tx.inputs.len()
1741    {
1742        return Err(crate::error::ConsensusError::InvalidPrevoutsCount(
1743            prevout_values.len(),
1744            tx.inputs.len(),
1745        ));
1746    }
1747
1748    // Compute precomputed hashes ONCE
1749    let precomputed = Bip143PrecomputedHashes::compute(tx, prevout_values, prevout_script_pubkeys);
1750
1751    // Calculate sighash for each input using precomputed hashes
1752    let mut results = Vec::with_capacity(tx.inputs.len());
1753    for (i, (value, script_code)) in prevout_values.iter().zip(script_codes.iter()).enumerate() {
1754        let sighash =
1755            calculate_bip143_sighash(tx, i, script_code, *value, sighash_type, Some(&precomputed))?;
1756        results.push(sighash);
1757    }
1758    Ok(results)
1759}
1760
1761#[cfg(test)]
1762mod tests {
1763    use super::*;
1764    use crate::opcodes::*;
1765
1766    #[test]
1767    fn test_sighash_type_parsing() {
1768        // Standard types
1769        assert_eq!(SighashType::from_byte(0x01), SighashType::ALL);
1770        assert_eq!(SighashType::from_byte(0x02), SighashType::NONE);
1771        assert_eq!(SighashType::from_byte(0x03), SighashType::SINGLE);
1772        assert_eq!(SighashType::from_byte(0x00), SighashType::ALL_LEGACY);
1773        assert_eq!(SighashType::from_byte(0x81), SighashType::ALL_ANYONECANPAY);
1774        assert_eq!(SighashType::from_byte(0x82), SighashType::NONE_ANYONECANPAY);
1775        assert_eq!(
1776            SighashType::from_byte(0x83),
1777            SighashType::SINGLE_ANYONECANPAY
1778        );
1779        // Verify the byte values are preserved correctly for sighash preimage
1780        assert_eq!(SighashType::ALL_ANYONECANPAY.as_u32(), 0x81);
1781        assert_eq!(SighashType::NONE_ANYONECANPAY.as_u32(), 0x82);
1782        assert_eq!(SighashType::SINGLE_ANYONECANPAY.as_u32(), 0x83);
1783        // Non-standard types are accepted (pre-STRICTENC) with raw byte preserved
1784        let st = SighashType::from_byte(0x04);
1785        assert!(st.is_all()); // base_type 0x04 acts as ALL
1786        assert_eq!(st.as_u32(), 0x04); // raw byte preserved in preimage
1787        let st84 = SighashType::from_byte(0x84);
1788        assert!(st84.is_all());
1789        assert!(st84.is_anyonecanpay());
1790        assert_eq!(st84.as_u32(), 0x84);
1791    }
1792
1793    #[test]
1794    fn test_varint_encoding() {
1795        assert_eq!(encode_varint(0), vec![0]);
1796        assert_eq!(encode_varint(252), vec![252]);
1797        assert_eq!(encode_varint(253), vec![0xfd, 253, 0]);
1798        assert_eq!(encode_varint(65535), vec![0xfd, 255, 255]);
1799        assert_eq!(encode_varint(65536), vec![0xfe, 0, 0, 1, 0]);
1800    }
1801
1802    #[test]
1803    fn test_sighash_calculation() {
1804        // Create a simple transaction for testing
1805        let tx = Transaction {
1806            version: 1,
1807            inputs: vec![TransactionInput {
1808                prevout: OutPoint {
1809                    hash: [1u8; 32].into(),
1810                    index: 0,
1811                },
1812                script_sig: vec![OP_1],
1813                sequence: 0xffffffff,
1814            }]
1815            .into(),
1816            outputs: vec![TransactionOutput {
1817                value: 5000000000,
1818                script_pubkey: vec![
1819                    OP_DUP,
1820                    OP_HASH160,
1821                    PUSH_20_BYTES,
1822                    0x89,
1823                    0xab,
1824                    0xcd,
1825                    0xef,
1826                    0x12,
1827                    0x34,
1828                    0x56,
1829                    0x78,
1830                    0x9a,
1831                    0xbc,
1832                    0xde,
1833                    0xf0,
1834                    0x12,
1835                    0x34,
1836                    0x56,
1837                    0x78,
1838                    0x9a,
1839                    OP_EQUALVERIFY,
1840                    OP_CHECKSIG,
1841                ]
1842                .into(), // P2PKH
1843            }]
1844            .into(),
1845            lock_time: 0,
1846        };
1847
1848        let prevouts = vec![TransactionOutput {
1849            value: 10000000000,
1850            script_pubkey: vec![
1851                OP_DUP,
1852                OP_HASH160,
1853                PUSH_20_BYTES,
1854                0x89,
1855                0xab,
1856                0xcd,
1857                0xef,
1858                0x12,
1859                0x34,
1860                0x56,
1861                0x78,
1862                0x9a,
1863                0xbc,
1864                0xde,
1865                0xf0,
1866                0x12,
1867                0x34,
1868                0x56,
1869                0x78,
1870                0x9a,
1871                OP_EQUALVERIFY,
1872                OP_CHECKSIG,
1873            ],
1874        }];
1875
1876        // Test SIGHASH_ALL
1877        let sighash = calculate_transaction_sighash(&tx, 0, &prevouts, SighashType::ALL).unwrap();
1878        assert_eq!(sighash.len(), 32);
1879
1880        // Test SIGHASH_NONE
1881        let sighash_none =
1882            calculate_transaction_sighash(&tx, 0, &prevouts, SighashType::NONE).unwrap();
1883        assert_ne!(sighash, sighash_none);
1884
1885        // Test SIGHASH_SINGLE
1886        let sighash_single =
1887            calculate_transaction_sighash(&tx, 0, &prevouts, SighashType::SINGLE).unwrap();
1888        assert_ne!(sighash, sighash_single);
1889    }
1890
1891    #[test]
1892    fn test_sighash_invalid_input_index() {
1893        let tx = Transaction {
1894            version: 1,
1895            inputs: vec![].into(),
1896            outputs: vec![].into(),
1897            lock_time: 0,
1898        };
1899
1900        let result = calculate_transaction_sighash(&tx, 0, &[], SighashType::ALL);
1901        assert!(result.is_err());
1902    }
1903
1904    #[test]
1905    fn test_bip143_sighash() {
1906        // Create a SegWit transaction for testing
1907        let tx = Transaction {
1908            version: 1,
1909            inputs: vec![
1910                TransactionInput {
1911                    prevout: OutPoint {
1912                        hash: [1u8; 32].into(),
1913                        index: 0,
1914                    },
1915                    script_sig: vec![], // Empty for SegWit
1916                    sequence: 0xffffffff,
1917                },
1918                TransactionInput {
1919                    prevout: OutPoint {
1920                        hash: [2u8; 32].into(),
1921                        index: 1,
1922                    },
1923                    script_sig: vec![],
1924                    sequence: 0xfffffffe,
1925                },
1926            ]
1927            .into(),
1928            outputs: vec![TransactionOutput {
1929                value: 5000000000,
1930                script_pubkey: vec![
1931                    OP_0,
1932                    PUSH_20_BYTES,
1933                    0x89,
1934                    0xab,
1935                    0xcd,
1936                    0xef,
1937                    0x12,
1938                    0x34,
1939                    0x56,
1940                    0x78,
1941                    0x9a,
1942                    0xbc,
1943                    0xde,
1944                    0xf0,
1945                    0x12,
1946                    0x34,
1947                    0x56,
1948                    0x78,
1949                    0x9a,
1950                    0xbc,
1951                    0xde,
1952                    0xf0,
1953                ]
1954                .into(),
1955            }]
1956            .into(),
1957            lock_time: 0,
1958        };
1959
1960        let prevouts = vec![
1961            TransactionOutput {
1962                value: 10000000000,
1963                script_pubkey: vec![
1964                    OP_0,
1965                    PUSH_20_BYTES,
1966                    0x11,
1967                    0x22,
1968                    0x33,
1969                    0x44,
1970                    0x55,
1971                    0x66,
1972                    0x77,
1973                    0x88,
1974                    0x99,
1975                    0xaa,
1976                    0xbb,
1977                    0xcc,
1978                    0xdd,
1979                    0xee,
1980                    0xff,
1981                    0x00,
1982                    0x11,
1983                    0x22,
1984                    0x33,
1985                    0x44,
1986                ],
1987            },
1988            TransactionOutput {
1989                value: 8000000000,
1990                script_pubkey: vec![
1991                    OP_0,
1992                    PUSH_20_BYTES,
1993                    0xaa,
1994                    0xbb,
1995                    0xcc,
1996                    0xdd,
1997                    0xee,
1998                    0xff,
1999                    0x00,
2000                    0x11,
2001                    0x22,
2002                    0x33,
2003                    0x44,
2004                    0x55,
2005                    0x66,
2006                    0x77,
2007                    0x88,
2008                    0x99,
2009                    0xaa,
2010                    0xbb,
2011                    0xcc,
2012                    0xdd,
2013                ],
2014            },
2015        ];
2016
2017        // P2WPKH scriptCode is OP_DUP OP_HASH160 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG
2018        let script_code = vec![
2019            OP_DUP,
2020            OP_HASH160,
2021            PUSH_20_BYTES,
2022            0x11,
2023            0x22,
2024            0x33,
2025            0x44,
2026            0x55,
2027            0x66,
2028            0x77,
2029            0x88,
2030            0x99,
2031            0xaa,
2032            0xbb,
2033            0xcc,
2034            0xdd,
2035            0xee,
2036            0xff,
2037            0x00,
2038            0x11,
2039            0x22,
2040            0x33,
2041            OP_EQUALVERIFY,
2042            OP_CHECKSIG,
2043        ];
2044
2045        // Test BIP143 sighash for first input
2046        let sighash0 =
2047            calculate_bip143_sighash(&tx, 0, &script_code, prevouts[0].value, 0x01, None).unwrap();
2048        assert_eq!(sighash0.len(), 32);
2049
2050        // Test BIP143 sighash for second input (should be different)
2051        let sighash1 =
2052            calculate_bip143_sighash(&tx, 1, &script_code, prevouts[1].value, 0x01, None).unwrap();
2053        assert_ne!(sighash0, sighash1);
2054
2055        // Test with precomputed hashes (should match)
2056        let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
2057        let prevout_script_pubkeys: Vec<&[u8]> =
2058            prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
2059        let precomputed =
2060            Bip143PrecomputedHashes::compute(&tx, &prevout_values, &prevout_script_pubkeys);
2061        let sighash0_precomputed = calculate_bip143_sighash(
2062            &tx,
2063            0,
2064            &script_code,
2065            prevout_values[0],
2066            0x01,
2067            Some(&precomputed),
2068        )
2069        .unwrap();
2070        assert_eq!(sighash0, sighash0_precomputed);
2071    }
2072
2073    #[test]
2074    fn test_bip143_anyonecanpay() {
2075        let tx = Transaction {
2076            version: 1,
2077            inputs: vec![TransactionInput {
2078                prevout: OutPoint {
2079                    hash: [1u8; 32].into(),
2080                    index: 0,
2081                },
2082                script_sig: vec![],
2083                sequence: 0xffffffff,
2084            }]
2085            .into(),
2086            outputs: vec![TransactionOutput {
2087                value: 5000000000,
2088                script_pubkey: vec![OP_0, PUSH_20_BYTES].into(),
2089            }]
2090            .into(),
2091            lock_time: 0,
2092        };
2093
2094        let script_code = {
2095            let mut s = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
2096            s.extend_from_slice(&[0u8; 20]); // 20 zero bytes (pubkey hash)
2097            s.push(OP_EQUALVERIFY);
2098            s.push(OP_CHECKSIG);
2099            s // 25 bytes total
2100        };
2101        let amount = 10000000000i64;
2102
2103        // SIGHASH_ALL
2104        let sighash_all =
2105            calculate_bip143_sighash(&tx, 0, &script_code, amount, 0x01, None).unwrap();
2106
2107        // SIGHASH_ALL | ANYONECANPAY (0x81)
2108        let sighash_anyonecanpay =
2109            calculate_bip143_sighash(&tx, 0, &script_code, amount, 0x81, None).unwrap();
2110
2111        // Should be different (ANYONECANPAY zeroes hashPrevouts and hashSequence)
2112        assert_ne!(sighash_all, sighash_anyonecanpay);
2113    }
2114
2115    /// Regression: 2-input legacy sighash must use EMPTY script for non-signing input.
2116    /// Per consensus, only the signing input's scriptCode is included; others get 0x00.
2117    /// Bug: build_preimage_2in1out/2in2out used full scriptPubKey for non-signing input → wrong sighash → IBD failure.
2118    #[test]
2119    fn test_2input_legacy_sighash_non_signing_empty_script() {
2120        // 2-in-1-out tx: when signing input 0, input 1's script must be empty in preimage
2121        let script_a = vec![
2122            PUSH_33_BYTES,
2123            0x02,
2124            0x00,
2125            0x00,
2126            0x00,
2127            0x00,
2128            0x00,
2129            0x00,
2130            0x00,
2131            0x00,
2132            0x00,
2133            0x00,
2134            0x00,
2135            0x00,
2136            0x00,
2137            0x00,
2138            0x00,
2139            0x00,
2140            0x00,
2141            0x00,
2142            0x00,
2143            0x00,
2144            0x00,
2145            0x00,
2146            0x00,
2147            0x00,
2148            0x00,
2149            0x00,
2150            0x00,
2151            0x00,
2152            0x00,
2153            0x00,
2154            OP_CHECKSIG,
2155        ]; // P2PK 35 bytes
2156        let script_b = vec![
2157            PUSH_33_BYTES,
2158            0x03,
2159            0x11,
2160            0x11,
2161            0x11,
2162            0x11,
2163            0x11,
2164            0x11,
2165            0x11,
2166            0x11,
2167            0x11,
2168            0x11,
2169            0x11,
2170            0x11,
2171            0x11,
2172            0x11,
2173            0x11,
2174            0x11,
2175            0x11,
2176            0x11,
2177            0x11,
2178            0x11,
2179            0x11,
2180            0x11,
2181            0x11,
2182            0x11,
2183            0x11,
2184            0x11,
2185            0x11,
2186            0x11,
2187            0x11,
2188            0x11,
2189            OP_CHECKSIG,
2190        ]; // Different P2PK
2191        let tx = Transaction {
2192            version: 1,
2193            inputs: vec![
2194                TransactionInput {
2195                    prevout: OutPoint {
2196                        hash: [1u8; 32].into(),
2197                        index: 0,
2198                    },
2199                    script_sig: vec![].into(),
2200                    sequence: 0xffffffff,
2201                },
2202                TransactionInput {
2203                    prevout: OutPoint {
2204                        hash: [2u8; 32].into(),
2205                        index: 1,
2206                    },
2207                    script_sig: vec![].into(),
2208                    sequence: 0xffffffff,
2209                },
2210            ]
2211            .into(),
2212            outputs: vec![TransactionOutput {
2213                value: 5000000000,
2214                script_pubkey: vec![OP_DUP, OP_HASH160, PUSH_20_BYTES].into(),
2215            }]
2216            .into(),
2217            lock_time: 0,
2218        };
2219        let pv: Vec<i64> = vec![10_000_000_000, 8_000_000_000];
2220        let psp_ab: Vec<&[u8]> = vec![script_a.as_slice(), script_b.as_slice()];
2221        let psp_aa: Vec<&[u8]> = vec![script_a.as_slice(), script_a.as_slice()];
2222
2223        // Signing input 0: input 1's script must be empty. So (script_a, script_b) and (script_a, script_a)
2224        // must produce the SAME sighash for input 0 — because input 1 is empty in preimage.
2225        let sighash_ab = calculate_transaction_sighash_with_script_code(
2226            &tx,
2227            0,
2228            &pv,
2229            &psp_ab,
2230            SighashType::ALL,
2231            None,
2232            #[cfg(feature = "production")]
2233            None,
2234        )
2235        .unwrap();
2236        let sighash_aa = calculate_transaction_sighash_with_script_code(
2237            &tx,
2238            0,
2239            &pv,
2240            &psp_aa,
2241            SighashType::ALL,
2242            None,
2243            #[cfg(feature = "production")]
2244            None,
2245        )
2246        .unwrap();
2247        assert_eq!(sighash_ab, sighash_aa,
2248            "2-input legacy: signing input 0 — input 1 script must be empty; changing input 1 scriptPubKey must not change sighash");
2249    }
2250
2251    #[cfg(feature = "production")]
2252    #[test]
2253    fn test_batch_sighash_single_input_index_ge_outputs() {
2254        // 2 inputs, 1 output: SIGHASH_SINGLE for input_index=1 has no corresponding output.
2255        // Must not panic; must return consensus hash 0x0000...0001.
2256        let tx = Transaction {
2257            version: 1,
2258            inputs: vec![
2259                TransactionInput {
2260                    prevout: OutPoint {
2261                        hash: [1u8; 32].into(),
2262                        index: 0,
2263                    },
2264                    script_sig: vec![].into(),
2265                    sequence: 0xffffffff,
2266                },
2267                TransactionInput {
2268                    prevout: OutPoint {
2269                        hash: [2u8; 32].into(),
2270                        index: 1,
2271                    },
2272                    script_sig: vec![].into(),
2273                    sequence: 0xffffffff,
2274                },
2275            ]
2276            .into(),
2277            outputs: vec![TransactionOutput {
2278                value: 5000000000,
2279                script_pubkey: vec![OP_DUP, OP_HASH160, PUSH_20_BYTES].into(),
2280            }]
2281            .into(),
2282            lock_time: 0,
2283        };
2284        let prevout_values = vec![10_000_000_000i64, 8_000_000_000i64];
2285        let script = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
2286        let prevout_script_pubkeys: Vec<&[u8]> = vec![script.as_slice(), script.as_slice()];
2287        let specs = vec![(1usize, 0x03u8, script.as_slice() as &[u8])]; // SIGHASH_SINGLE, input 1
2288        let hashes = super::batch_compute_legacy_sighashes(
2289            &tx,
2290            &prevout_values,
2291            &prevout_script_pubkeys,
2292            &specs,
2293        )
2294        .unwrap();
2295        assert_eq!(hashes.len(), 1);
2296        let mut expected = [0u8; 32];
2297        expected[0] = 1;
2298        assert_eq!(
2299            hashes[0], expected,
2300            "SIGHASH_SINGLE with input_index>=outputs.len() must return 0x0000...0001"
2301        );
2302    }
2303}