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