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