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