Skip to main content

chains_sdk/bitcoin/
sighash.rs

1//! Bitcoin sighash computation for SegWit v0 (BIP-143) and Taproot (BIP-341/342).
2//!
3//! Provides the hash preimage construction used by signers to commit to
4//! transaction data before signing.
5
6use crate::crypto;
7use crate::error::SignerError;
8use sha2::{Digest, Sha256};
9
10use super::tapscript::SighashType;
11use super::transaction::{Transaction, TxOut};
12
13// ─── SegWit v0 Sighash (BIP-143) ────────────────────────────────────
14
15/// Previous output info needed for SegWit sighash computation.
16pub struct PrevOut {
17    /// The scriptCode for this input (typically P2WPKH witness program).
18    pub script_code: Vec<u8>,
19    /// The value of the output being spent (in satoshis).
20    pub value: u64,
21}
22
23/// Compute the BIP-143 SegWit v0 sighash for a specific input.
24///
25/// This is the hash that is signed for P2WPKH and P2WSH inputs.
26///
27/// # Arguments
28/// - `tx` — The unsigned transaction
29/// - `input_idx` — Index of the input being signed
30/// - `prev_out` — Script code and value of the output being spent
31/// - `sighash_type` — Sighash flag (typically `All` = 0x01)
32pub fn segwit_v0_sighash(
33    tx: &Transaction,
34    input_idx: usize,
35    prev_out: &PrevOut,
36    sighash_type: SighashType,
37) -> Result<[u8; 32], SignerError> {
38    if input_idx >= tx.inputs.len() {
39        return Err(SignerError::SigningFailed(format!(
40            "input index {} out of range ({})",
41            input_idx,
42            tx.inputs.len()
43        )));
44    }
45
46    let sighash_u32 = sighash_type.to_byte() as u32;
47    let anyone_can_pay = sighash_u32 & 0x80 != 0;
48    let base_type = sighash_u32 & 0x1f;
49
50    // hashPrevouts
51    let hash_prevouts = if !anyone_can_pay {
52        let mut buf = Vec::new();
53        for input in &tx.inputs {
54            buf.extend_from_slice(&input.previous_output.txid);
55            buf.extend_from_slice(&input.previous_output.vout.to_le_bytes());
56        }
57        crypto::double_sha256(&buf)
58    } else {
59        [0u8; 32]
60    };
61
62    // hashSequence
63    let hash_sequence = if !anyone_can_pay && base_type != 0x02 && base_type != 0x03 {
64        let mut buf = Vec::new();
65        for input in &tx.inputs {
66            buf.extend_from_slice(&input.sequence.to_le_bytes());
67        }
68        crypto::double_sha256(&buf)
69    } else {
70        [0u8; 32]
71    };
72
73    // hashOutputs
74    let hash_outputs = if base_type != 0x02 && base_type != 0x03 {
75        // SIGHASH_ALL: hash all outputs
76        let mut buf = Vec::new();
77        for output in &tx.outputs {
78            buf.extend_from_slice(&output.value.to_le_bytes());
79            crate::encoding::encode_compact_size(&mut buf, output.script_pubkey.len() as u64);
80            buf.extend_from_slice(&output.script_pubkey);
81        }
82        crypto::double_sha256(&buf)
83    } else if base_type == 0x03 && input_idx < tx.outputs.len() {
84        // SIGHASH_SINGLE: hash only the corresponding output
85        let mut buf = Vec::new();
86        let output = &tx.outputs[input_idx];
87        buf.extend_from_slice(&output.value.to_le_bytes());
88        crate::encoding::encode_compact_size(&mut buf, output.script_pubkey.len() as u64);
89        buf.extend_from_slice(&output.script_pubkey);
90        crypto::double_sha256(&buf)
91    } else {
92        [0u8; 32]
93    };
94
95    // Build the preimage
96    let input = &tx.inputs[input_idx];
97    let mut preimage = Vec::with_capacity(256);
98    preimage.extend_from_slice(&tx.version.to_le_bytes());
99    preimage.extend_from_slice(&hash_prevouts);
100    preimage.extend_from_slice(&hash_sequence);
101    // outpoint
102    preimage.extend_from_slice(&input.previous_output.txid);
103    preimage.extend_from_slice(&input.previous_output.vout.to_le_bytes());
104    // scriptCode
105    crate::encoding::encode_compact_size(&mut preimage, prev_out.script_code.len() as u64);
106    preimage.extend_from_slice(&prev_out.script_code);
107    // value
108    preimage.extend_from_slice(&prev_out.value.to_le_bytes());
109    // sequence
110    preimage.extend_from_slice(&input.sequence.to_le_bytes());
111    preimage.extend_from_slice(&hash_outputs);
112    preimage.extend_from_slice(&tx.locktime.to_le_bytes());
113    preimage.extend_from_slice(&sighash_u32.to_le_bytes());
114
115    Ok(crypto::double_sha256(&preimage))
116}
117
118// ─── Taproot Key-Path Sighash (BIP-341 §4) ─────────────────────────
119
120/// Compute the BIP-341 Taproot key-path sighash for a specific input.
121///
122/// # Arguments
123/// - `tx` — The unsigned transaction
124/// - `input_idx` — Index of the input being signed
125/// - `prevouts` — All previous outputs (values and scriptPubKeys) in input order
126/// - `sighash_type` — Sighash flag (Default = 0x00 means ALL)
127pub fn taproot_key_path_sighash(
128    tx: &Transaction,
129    input_idx: usize,
130    prevouts: &[TxOut],
131    sighash_type: SighashType,
132) -> Result<[u8; 32], SignerError> {
133    if input_idx >= tx.inputs.len() {
134        return Err(SignerError::SigningFailed(format!(
135            "input index {} out of range ({})",
136            input_idx,
137            tx.inputs.len()
138        )));
139    }
140    if prevouts.len() != tx.inputs.len() {
141        return Err(SignerError::SigningFailed(format!(
142            "prevouts length {} != inputs length {}",
143            prevouts.len(),
144            tx.inputs.len()
145        )));
146    }
147
148    let sighash_byte = sighash_type.to_byte();
149    let anyone_can_pay = sighash_byte & 0x80 != 0;
150    let base_type = sighash_byte & 0x03;
151    // Default (0x00) is treated as ALL
152    let effective_base = if sighash_byte == 0x00 {
153        0x01
154    } else {
155        base_type
156    };
157
158    // Epoch
159    let mut sig_msg = Vec::with_capacity(256);
160    sig_msg.push(0x00); // epoch = 0
161
162    // hash_type
163    sig_msg.push(sighash_byte);
164
165    // nVersion
166    sig_msg.extend_from_slice(&tx.version.to_le_bytes());
167    // nLocktime
168    sig_msg.extend_from_slice(&tx.locktime.to_le_bytes());
169
170    // If not ANYONECANPAY:
171    if !anyone_can_pay {
172        // sha_prevouts (SHA256 of all outpoints)
173        let mut h = Sha256::new();
174        for input in &tx.inputs {
175            h.update(input.previous_output.txid);
176            h.update(input.previous_output.vout.to_le_bytes());
177        }
178        sig_msg.extend_from_slice(&h.finalize());
179
180        // sha_amounts (SHA256 of all input amounts)
181        let mut h = Sha256::new();
182        for p in prevouts {
183            h.update(p.value.to_le_bytes());
184        }
185        sig_msg.extend_from_slice(&h.finalize());
186
187        // sha_scriptpubkeys (SHA256 of all input scriptPubKeys)
188        let mut h = Sha256::new();
189        for p in prevouts {
190            // compact size + scriptPubKey
191            let mut tmp = Vec::new();
192            crate::encoding::encode_compact_size(&mut tmp, p.script_pubkey.len() as u64);
193            h.update(&tmp);
194            h.update(&p.script_pubkey);
195        }
196        sig_msg.extend_from_slice(&h.finalize());
197
198        // sha_sequences (SHA256 of all sequences)
199        let mut h = Sha256::new();
200        for input in &tx.inputs {
201            h.update(input.sequence.to_le_bytes());
202        }
203        sig_msg.extend_from_slice(&h.finalize());
204    }
205
206    // If SIGHASH_ALL (base 0x01, or Default which maps to 0x01):
207    if effective_base == 0x01 {
208        let mut h = Sha256::new();
209        for output in &tx.outputs {
210            h.update(output.value.to_le_bytes());
211            let mut tmp = Vec::new();
212            crate::encoding::encode_compact_size(&mut tmp, output.script_pubkey.len() as u64);
213            h.update(&tmp);
214            h.update(&output.script_pubkey);
215        }
216        sig_msg.extend_from_slice(&h.finalize());
217    }
218
219    // spend_type (key path = 0, script path = 1, + ext_flag bit 0)
220    sig_msg.push(0x00); // key-path, no annex
221
222    // If ANYONECANPAY:
223    if anyone_can_pay {
224        // outpoint
225        let input = &tx.inputs[input_idx];
226        sig_msg.extend_from_slice(&input.previous_output.txid);
227        sig_msg.extend_from_slice(&input.previous_output.vout.to_le_bytes());
228        // amount
229        sig_msg.extend_from_slice(&prevouts[input_idx].value.to_le_bytes());
230        // scriptPubKey
231        let mut tmp = Vec::new();
232        crate::encoding::encode_compact_size(
233            &mut tmp,
234            prevouts[input_idx].script_pubkey.len() as u64,
235        );
236        sig_msg.extend_from_slice(&tmp);
237        sig_msg.extend_from_slice(&prevouts[input_idx].script_pubkey);
238        // sequence
239        sig_msg.extend_from_slice(&tx.inputs[input_idx].sequence.to_le_bytes());
240    } else {
241        // input_index
242        sig_msg.extend_from_slice(&(input_idx as u32).to_le_bytes());
243    }
244
245    // SIGHASH_SINGLE: hash the corresponding output
246    if effective_base == 0x03 && input_idx < tx.outputs.len() {
247        let mut h = Sha256::new();
248        let output = &tx.outputs[input_idx];
249        h.update(output.value.to_le_bytes());
250        let mut tmp = Vec::new();
251        crate::encoding::encode_compact_size(&mut tmp, output.script_pubkey.len() as u64);
252        h.update(&tmp);
253        h.update(&output.script_pubkey);
254        sig_msg.extend_from_slice(&h.finalize());
255    }
256
257    // Tagged hash: "TapSighash"
258    Ok(crypto::tagged_hash(b"TapSighash", &sig_msg))
259}
260
261// ─── P2WPKH Script Code Helper ─────────────────────────────────────
262
263/// Build the BIP-143 script code for a P2WPKH input.
264///
265/// For P2WPKH, the script code is `OP_DUP OP_HASH160 PUSH20(hash160) OP_EQUALVERIFY OP_CHECKSIG`.
266#[must_use]
267pub fn p2wpkh_script_code(pubkey_hash: &[u8; 20]) -> Vec<u8> {
268    let mut script = Vec::with_capacity(25);
269    script.push(0x76); // OP_DUP
270    script.push(0xa9); // OP_HASH160
271    script.push(0x14); // PUSH 20 bytes
272    script.extend_from_slice(pubkey_hash);
273    script.push(0x88); // OP_EQUALVERIFY
274    script.push(0xac); // OP_CHECKSIG
275    script
276}
277
278// ─── Taproot Script-Path Sighash (BIP-342) ─────────────────────────
279
280/// Compute the BIP-342 Taproot script-path sighash for a specific input.
281///
282/// This extends the key-path sighash (BIP-341) with:
283/// - `ext_flag = 1` (spend_type has bit 1 set)
284/// - `tapleaf_hash` — the tagged hash of the leaf being executed
285/// - `key_version = 0`
286/// - `codesep_pos` — position of the last executed OP_CODESEPARATOR (0xFFFFFFFF if none)
287///
288/// # Arguments
289/// - `tx` — The unsigned transaction
290/// - `input_idx` — Input being signed
291/// - `prevouts` — All previous outputs
292/// - `sighash_type` — Sighash flag
293/// - `tapleaf_hash` — 32-byte tagged hash `TapLeaf` of the executing script
294/// - `codesep_pos` — Last OP_CODESEPARATOR position, or `0xFFFFFFFF` if none
295pub fn taproot_script_path_sighash(
296    tx: &Transaction,
297    input_idx: usize,
298    prevouts: &[TxOut],
299    sighash_type: SighashType,
300    tapleaf_hash: &[u8; 32],
301    codesep_pos: u32,
302) -> Result<[u8; 32], SignerError> {
303    if input_idx >= tx.inputs.len() {
304        return Err(SignerError::SigningFailed(format!(
305            "input index {} out of range ({})",
306            input_idx,
307            tx.inputs.len()
308        )));
309    }
310    if prevouts.len() != tx.inputs.len() {
311        return Err(SignerError::SigningFailed(format!(
312            "prevouts length {} != inputs length {}",
313            prevouts.len(),
314            tx.inputs.len()
315        )));
316    }
317
318    let sighash_byte = sighash_type.to_byte();
319    let anyone_can_pay = sighash_byte & 0x80 != 0;
320    let base_type = sighash_byte & 0x03;
321    let effective_base = if sighash_byte == 0x00 {
322        0x01
323    } else {
324        base_type
325    };
326
327    let mut sig_msg = Vec::with_capacity(300);
328    sig_msg.push(0x00); // epoch = 0
329    sig_msg.push(sighash_byte);
330    sig_msg.extend_from_slice(&tx.version.to_le_bytes());
331    sig_msg.extend_from_slice(&tx.locktime.to_le_bytes());
332
333    if !anyone_can_pay {
334        let mut h = Sha256::new();
335        for input in &tx.inputs {
336            h.update(input.previous_output.txid);
337            h.update(input.previous_output.vout.to_le_bytes());
338        }
339        sig_msg.extend_from_slice(&h.finalize());
340
341        let mut h = Sha256::new();
342        for p in prevouts {
343            h.update(p.value.to_le_bytes());
344        }
345        sig_msg.extend_from_slice(&h.finalize());
346
347        let mut h = Sha256::new();
348        for p in prevouts {
349            let mut tmp = Vec::new();
350            crate::encoding::encode_compact_size(&mut tmp, p.script_pubkey.len() as u64);
351            h.update(&tmp);
352            h.update(&p.script_pubkey);
353        }
354        sig_msg.extend_from_slice(&h.finalize());
355
356        let mut h = Sha256::new();
357        for input in &tx.inputs {
358            h.update(input.sequence.to_le_bytes());
359        }
360        sig_msg.extend_from_slice(&h.finalize());
361    }
362
363    if effective_base == 0x01 {
364        let mut h = Sha256::new();
365        for output in &tx.outputs {
366            h.update(output.value.to_le_bytes());
367            let mut tmp = Vec::new();
368            crate::encoding::encode_compact_size(&mut tmp, output.script_pubkey.len() as u64);
369            h.update(&tmp);
370            h.update(&output.script_pubkey);
371        }
372        sig_msg.extend_from_slice(&h.finalize());
373    }
374
375    // spend_type: ext_flag = 1 (bit 1), no annex (bit 0 = 0)
376    sig_msg.push(0x02); // ext_flag=1, annex=0 → 0b10 = 2
377
378    if anyone_can_pay {
379        let input = &tx.inputs[input_idx];
380        sig_msg.extend_from_slice(&input.previous_output.txid);
381        sig_msg.extend_from_slice(&input.previous_output.vout.to_le_bytes());
382        sig_msg.extend_from_slice(&prevouts[input_idx].value.to_le_bytes());
383        let mut tmp = Vec::new();
384        crate::encoding::encode_compact_size(
385            &mut tmp,
386            prevouts[input_idx].script_pubkey.len() as u64,
387        );
388        sig_msg.extend_from_slice(&tmp);
389        sig_msg.extend_from_slice(&prevouts[input_idx].script_pubkey);
390        sig_msg.extend_from_slice(&tx.inputs[input_idx].sequence.to_le_bytes());
391    } else {
392        sig_msg.extend_from_slice(&(input_idx as u32).to_le_bytes());
393    }
394
395    if effective_base == 0x03 && input_idx < tx.outputs.len() {
396        let mut h = Sha256::new();
397        let output = &tx.outputs[input_idx];
398        h.update(output.value.to_le_bytes());
399        let mut tmp = Vec::new();
400        crate::encoding::encode_compact_size(&mut tmp, output.script_pubkey.len() as u64);
401        h.update(&tmp);
402        h.update(&output.script_pubkey);
403        sig_msg.extend_from_slice(&h.finalize());
404    }
405
406    // Script-path extension (BIP-342):
407    sig_msg.extend_from_slice(tapleaf_hash);
408    sig_msg.push(0x00); // key_version = 0
409    sig_msg.extend_from_slice(&codesep_pos.to_le_bytes());
410
411    Ok(crypto::tagged_hash(b"TapSighash", &sig_msg))
412}
413
414// ─── Tests ──────────────────────────────────────────────────────────
415
416#[cfg(test)]
417#[allow(clippy::unwrap_used, clippy::expect_used)]
418mod tests {
419    use super::super::transaction::*;
420    use super::*;
421
422    fn sample_segwit_tx() -> Transaction {
423        let mut tx = Transaction::new(2);
424        tx.inputs.push(TxIn {
425            previous_output: OutPoint {
426                txid: [0x01; 32],
427                vout: 0,
428            },
429            script_sig: vec![],
430            sequence: 0xFFFFFFFF,
431        });
432        tx.outputs.push(TxOut {
433            value: 49_000,
434            script_pubkey: {
435                let mut spk = vec![0x00, 0x14];
436                spk.extend_from_slice(&[0xAA; 20]);
437                spk
438            },
439        });
440        tx
441    }
442
443    #[test]
444    fn test_segwit_sighash_deterministic() {
445        let tx = sample_segwit_tx();
446        let prev = PrevOut {
447            script_code: p2wpkh_script_code(&[0xBB; 20]),
448            value: 50_000,
449        };
450        let h1 = segwit_v0_sighash(&tx, 0, &prev, SighashType::All).unwrap();
451        let h2 = segwit_v0_sighash(&tx, 0, &prev, SighashType::All).unwrap();
452        assert_eq!(h1, h2);
453    }
454
455    #[test]
456    fn test_segwit_sighash_different_types() {
457        let tx = sample_segwit_tx();
458        let prev = PrevOut {
459            script_code: p2wpkh_script_code(&[0xBB; 20]),
460            value: 50_000,
461        };
462        let h_all = segwit_v0_sighash(&tx, 0, &prev, SighashType::All).unwrap();
463        let h_none = segwit_v0_sighash(&tx, 0, &prev, SighashType::None).unwrap();
464        assert_ne!(h_all, h_none);
465    }
466
467    #[test]
468    fn test_segwit_sighash_out_of_range() {
469        let tx = sample_segwit_tx();
470        let prev = PrevOut {
471            script_code: p2wpkh_script_code(&[0xBB; 20]),
472            value: 50_000,
473        };
474        assert!(segwit_v0_sighash(&tx, 5, &prev, SighashType::All).is_err());
475    }
476
477    #[test]
478    fn test_taproot_sighash_deterministic() {
479        let tx = sample_segwit_tx();
480        let prevouts = vec![TxOut {
481            value: 50_000,
482            script_pubkey: {
483                let mut spk = vec![0x51, 0x20];
484                spk.extend_from_slice(&[0xCC; 32]);
485                spk
486            },
487        }];
488        let h1 = taproot_key_path_sighash(&tx, 0, &prevouts, SighashType::Default).unwrap();
489        let h2 = taproot_key_path_sighash(&tx, 0, &prevouts, SighashType::Default).unwrap();
490        assert_eq!(h1, h2);
491    }
492
493    #[test]
494    fn test_taproot_sighash_different_from_segwit() {
495        let tx = sample_segwit_tx();
496        let prev = PrevOut {
497            script_code: p2wpkh_script_code(&[0xBB; 20]),
498            value: 50_000,
499        };
500        let prevouts = vec![TxOut {
501            value: 50_000,
502            script_pubkey: {
503                let mut spk = vec![0x51, 0x20];
504                spk.extend_from_slice(&[0xCC; 32]);
505                spk
506            },
507        }];
508        let h_segwit = segwit_v0_sighash(&tx, 0, &prev, SighashType::All).unwrap();
509        let h_taproot = taproot_key_path_sighash(&tx, 0, &prevouts, SighashType::Default).unwrap();
510        assert_ne!(h_segwit, h_taproot);
511    }
512
513    #[test]
514    fn test_taproot_sighash_mismatched_prevouts() {
515        let tx = sample_segwit_tx();
516        // Wrong number of prevouts
517        assert!(taproot_key_path_sighash(&tx, 0, &[], SighashType::Default).is_err());
518    }
519
520    #[test]
521    fn test_p2wpkh_script_code_structure() {
522        let hash = [0xAA; 20];
523        let code = p2wpkh_script_code(&hash);
524        assert_eq!(code.len(), 25);
525        assert_eq!(code[0], 0x76); // OP_DUP
526        assert_eq!(code[1], 0xa9); // OP_HASH160
527        assert_eq!(code[2], 0x14); // PUSH 20
528        assert_eq!(&code[3..23], &hash);
529        assert_eq!(code[23], 0x88); // OP_EQUALVERIFY
530        assert_eq!(code[24], 0xac); // OP_CHECKSIG
531    }
532
533    // ─── BIP-342 Script-Path Tests ─────────────────────────────────
534
535    #[test]
536    fn test_script_path_sighash_deterministic() {
537        let tx = sample_segwit_tx();
538        let prevouts = vec![TxOut {
539            value: 50_000,
540            script_pubkey: {
541                let mut spk = vec![0x51, 0x20];
542                spk.extend_from_slice(&[0xCC; 32]);
543                spk
544            },
545        }];
546        let leaf_hash = [0xDD; 32];
547        let h1 = taproot_script_path_sighash(
548            &tx,
549            0,
550            &prevouts,
551            SighashType::Default,
552            &leaf_hash,
553            0xFFFFFFFF,
554        )
555        .unwrap();
556        let h2 = taproot_script_path_sighash(
557            &tx,
558            0,
559            &prevouts,
560            SighashType::Default,
561            &leaf_hash,
562            0xFFFFFFFF,
563        )
564        .unwrap();
565        assert_eq!(h1, h2);
566    }
567
568    #[test]
569    fn test_script_path_different_from_key_path() {
570        let tx = sample_segwit_tx();
571        let prevouts = vec![TxOut {
572            value: 50_000,
573            script_pubkey: {
574                let mut spk = vec![0x51, 0x20];
575                spk.extend_from_slice(&[0xCC; 32]);
576                spk
577            },
578        }];
579        let leaf_hash = [0xDD; 32];
580        let h_key = taproot_key_path_sighash(&tx, 0, &prevouts, SighashType::Default).unwrap();
581        let h_script = taproot_script_path_sighash(
582            &tx,
583            0,
584            &prevouts,
585            SighashType::Default,
586            &leaf_hash,
587            0xFFFFFFFF,
588        )
589        .unwrap();
590        assert_ne!(h_key, h_script);
591    }
592
593    #[test]
594    fn test_script_path_different_leaf_hashes() {
595        let tx = sample_segwit_tx();
596        let prevouts = vec![TxOut {
597            value: 50_000,
598            script_pubkey: {
599                let mut spk = vec![0x51, 0x20];
600                spk.extend_from_slice(&[0xCC; 32]);
601                spk
602            },
603        }];
604        let h1 = taproot_script_path_sighash(
605            &tx,
606            0,
607            &prevouts,
608            SighashType::Default,
609            &[0xAA; 32],
610            0xFFFFFFFF,
611        )
612        .unwrap();
613        let h2 = taproot_script_path_sighash(
614            &tx,
615            0,
616            &prevouts,
617            SighashType::Default,
618            &[0xBB; 32],
619            0xFFFFFFFF,
620        )
621        .unwrap();
622        assert_ne!(h1, h2);
623    }
624
625    #[test]
626    fn test_script_path_codesep_affects_hash() {
627        let tx = sample_segwit_tx();
628        let prevouts = vec![TxOut {
629            value: 50_000,
630            script_pubkey: {
631                let mut spk = vec![0x51, 0x20];
632                spk.extend_from_slice(&[0xCC; 32]);
633                spk
634            },
635        }];
636        let leaf = [0xDD; 32];
637        let h1 =
638            taproot_script_path_sighash(&tx, 0, &prevouts, SighashType::Default, &leaf, 0xFFFFFFFF)
639                .unwrap();
640        let h2 =
641            taproot_script_path_sighash(&tx, 0, &prevouts, SighashType::Default, &leaf, 5).unwrap();
642        assert_ne!(h1, h2, "codesep_pos should affect the sighash");
643    }
644}