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