zeldhash_protocol/
helpers.rs

1use std::io::Cursor;
2
3use bitcoin::{
4    ecdsa::Signature as EcdsaSignature,
5    opcodes,
6    script::Instruction,
7    sighash::{EcdsaSighashType, TapSighashType},
8    taproot::Signature as SchnorrSignature,
9    Address, Network, Script, ScriptBuf, TxIn, Txid,
10};
11use ciborium::de::from_reader;
12use xxhash_rust::xxh3::xxh3_128;
13
14use crate::types::{Amount, UtxoKey};
15
16/// Extracts the Bitcoin address from a script, if possible.
17///
18/// Returns `None` for non-standard scripts (e.g., bare multisig, OP_RETURN, unknown scripts).
19/// Supported script types: P2PKH, P2SH, P2WPKH, P2WSH, P2TR (Taproot).
20pub fn extract_address(script: &Script, network: Network) -> Option<String> {
21    Address::from_script(script, network)
22        .ok()
23        .map(|addr| addr.to_string())
24}
25
26/// Returns true if bytes resemble a DER-encoded ECDSA signature plus sighash byte.
27fn looks_like_der_signature(data: &[u8]) -> bool {
28    // DER signatures are typically 70-73 bytes + 1 sighash byte,
29    // but may be shorter in degenerate cases; enforce a broad valid range.
30    (9..=74).contains(&data.len()) && data.first() == Some(&0x30)
31}
32
33pub fn compute_utxo_key(txid: &Txid, vout: u32) -> UtxoKey {
34    let mut payload = [0u8; 36];
35    payload[..32].copy_from_slice(txid.as_ref());
36    payload[32..].copy_from_slice(&vout.to_le_bytes());
37
38    // xxh3_128 is extremely fast and provides enough entropy; truncate to 96 bits.
39    let hash = xxh3_128(&payload).to_le_bytes();
40    let mut key = [0u8; 12];
41    key.copy_from_slice(&hash[..12]);
42    key
43}
44
45pub fn leading_zero_count(txid: &Txid) -> u8 {
46    let mut count: u8 = 0;
47    let bytes: &[u8] = txid.as_ref();
48
49    // bitcoin::Txid stores the hash in little-endian order; scan it in
50    // reverse so we count the human-visible leading zeros.
51    for &byte in bytes.iter().rev() {
52        if byte == 0 {
53            count += 2;
54            continue;
55        }
56
57        if byte >> 4 == 0 {
58            count += 1;
59        }
60        break;
61    }
62
63    count
64}
65
66pub fn parse_op_return(script: &ScriptBuf, prefix: &[u8]) -> Option<Vec<u64>> {
67    let mut instructions = script.instructions();
68
69    let op_return = instructions.next()?;
70    match op_return.ok()? {
71        Instruction::Op(opcodes::all::OP_RETURN) => {}
72        _ => return None,
73    }
74
75    let push = instructions.next()?;
76    let data = match push.ok()? {
77        Instruction::PushBytes(bytes) => bytes.as_bytes(),
78        _ => return None,
79    };
80
81    if data.len() < prefix.len() || &data[..prefix.len()] != prefix {
82        return None;
83    }
84
85    let mut reader = Cursor::new(&data[prefix.len()..]);
86    from_reader::<Vec<u64>, _>(&mut reader).ok()
87}
88
89pub fn calculate_reward(
90    zero_count: u8,
91    max_zero_count: u8,
92    min_zero_count: u8,
93    base_reward: Amount,
94) -> Amount {
95    if zero_count < min_zero_count {
96        return 0;
97    }
98
99    let diff = max_zero_count.saturating_sub(zero_count);
100    let mut reward = base_reward;
101
102    for _ in 0..diff {
103        reward /= 16;
104        if reward == 0 {
105            break;
106        }
107    }
108
109    reward
110}
111
112/// Checks if an ECDSA signature (DER-encoded + sighash byte) uses SIGHASH_ALL.
113fn is_ecdsa_sighash_all(sig_bytes: &[u8]) -> bool {
114    EcdsaSignature::from_slice(sig_bytes)
115        .map(|sig| sig.sighash_type == EcdsaSighashType::All)
116        .unwrap_or(false)
117}
118
119/// Checks if a Schnorr signature uses SIGHASH_ALL or SIGHASH_DEFAULT (equivalent for Taproot).
120/// Schnorr signatures are 64 bytes (SIGHASH_DEFAULT) or 65 bytes (explicit sighash).
121fn is_schnorr_sighash_all(sig_bytes: &[u8]) -> bool {
122    SchnorrSignature::from_slice(sig_bytes)
123        .map(|sig| {
124            // SIGHASH_DEFAULT (0x00) and SIGHASH_ALL (0x01) are both valid for our purposes
125            matches!(
126                sig.sighash_type,
127                TapSighashType::Default | TapSighashType::All
128            )
129        })
130        .unwrap_or(false)
131}
132
133/// Extracts ECDSA signatures from a script_sig (for P2PKH and P2SH-multisig).
134/// Returns the signature bytes for each push that looks like a DER signature.
135fn extract_scriptsig_signatures(script_sig: &bitcoin::Script) -> Vec<&[u8]> {
136    let mut signatures = Vec::new();
137    for instruction in script_sig.instructions().flatten() {
138        if let Instruction::PushBytes(bytes) = instruction {
139            let data = bytes.as_bytes();
140            // DER signatures are typically 70-73 bytes + 1 sighash byte
141            // Minimum: 8 bytes (degenerate) + 1 sighash = 9 bytes
142            // Maximum: ~73 bytes + 1 sighash = 74 bytes
143            // They start with 0x30 (SEQUENCE tag)
144            if data.len() >= 9 && data.len() <= 74 && data.first() == Some(&0x30) {
145                signatures.push(data);
146            }
147        }
148    }
149    signatures
150}
151
152/// Returns true if all inputs are signed with SIGHASH_ALL (or SIGHASH_DEFAULT for Taproot).
153/// Returns false if any input uses a different sighash type or if the sighash cannot be determined.
154///
155/// Supported script types:
156/// - P2PKH: Signature in script_sig
157/// - P2WPKH: Signature in witness[0]
158/// - P2SH-multisig: Multiple signatures in script_sig
159/// - P2WSH: Signatures in witness[0..n-1]
160/// - P2TR (Taproot): Schnorr signature in witness[0]
161pub fn all_inputs_sighash_all(inputs: &[TxIn]) -> bool {
162    for input in inputs {
163        let has_witness = !input.witness.is_empty();
164        let has_scriptsig = !input.script_sig.is_empty();
165
166        if has_witness {
167            // SegWit input (P2WPKH, P2WSH, or P2TR)
168            let witness_len = input.witness.len();
169
170            // Check if this looks like a Taproot key-path spend (single 64 or 65 byte signature)
171            // or Taproot script-path spend (multiple witness elements ending with control block)
172            let first_elem = input.witness.nth(0).unwrap_or(&[]);
173
174            if witness_len == 1 && (first_elem.len() == 64 || first_elem.len() == 65) {
175                // Taproot key-path spend: single Schnorr signature
176                if !is_schnorr_sighash_all(first_elem) {
177                    return false;
178                }
179            } else if witness_len == 2 && (first_elem.len() == 64 || first_elem.len() == 65) {
180                // Taproot key-path spend with 2 witness elements
181                // (e.g., annex present: witness[0] = Schnorr sig, witness[1] = annex)
182                if !is_schnorr_sighash_all(first_elem) {
183                    return false;
184                }
185            } else if witness_len == 2 && looks_like_der_signature(first_elem) {
186                // P2WPKH: witness[0] = ECDSA signature, witness[1] = pubkey
187                if !is_ecdsa_sighash_all(first_elem) {
188                    return false;
189                }
190            } else if witness_len > 2 {
191                // P2WSH or Taproot script-path spend
192                // For P2WSH: witness = [OP_0_placeholder, sig1, sig2, ..., redeem_script]
193                // For Taproot script-path: witness = [args..., script, control_block]
194
195                // Check if the last element looks like a control block (starts with leaf version)
196                let last_elem = input.witness.nth(witness_len - 1).unwrap_or(&[]);
197
198                if !last_elem.is_empty() && (last_elem[0] & 0xfe) == 0xc0 {
199                    // Taproot script-path: control block starts with 0xc0 or 0xc1
200                    // Signatures are in the witness elements before the script and control block
201                    // This is complex to parse generically; check all 64/65 byte elements as Schnorr
202                    let mut saw_signature = false;
203                    for i in 0..witness_len.saturating_sub(2) {
204                        let elem = input.witness.nth(i).unwrap_or(&[]);
205                        if elem.len() == 64 || elem.len() == 65 {
206                            saw_signature = true;
207                            if !is_schnorr_sighash_all(elem) {
208                                return false;
209                            }
210                        }
211                    }
212                    if !saw_signature {
213                        // No recognizable Schnorr signatures found
214                        return false;
215                    }
216                } else {
217                    // P2WSH: all elements except the last (redeem script) are signatures or OP_0
218                    // Skip empty elements (OP_0 placeholder for CHECKMULTISIG bug)
219                    let mut saw_signature = false;
220                    for i in 0..witness_len.saturating_sub(1) {
221                        let elem = input.witness.nth(i).unwrap_or(&[]);
222                        // Skip empty elements (OP_0) and non-signature data
223                        if elem.is_empty() {
224                            continue;
225                        }
226                        // Check if it looks like a DER signature and verify sighash
227                        if elem.len() >= 9 && elem.first() == Some(&0x30) {
228                            saw_signature = true;
229                            if !is_ecdsa_sighash_all(elem) {
230                                return false;
231                            }
232                        }
233                    }
234                    if !saw_signature {
235                        // No recognizable DER signatures found
236                        return false;
237                    }
238                }
239            } else {
240                // Unknown witness structure
241                return false;
242            }
243        } else if has_scriptsig {
244            // Legacy input (P2PKH or P2SH)
245            let signatures = extract_scriptsig_signatures(&input.script_sig);
246
247            if signatures.is_empty() {
248                // No recognizable signatures found
249                return false;
250            }
251
252            for sig_bytes in signatures {
253                if !is_ecdsa_sighash_all(sig_bytes) {
254                    return false;
255                }
256            }
257        } else {
258            // No witness and no script_sig - cannot determine sighash
259            return false;
260        }
261    }
262
263    true
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use bitcoin::{
270        hashes::Hash, opcodes, script::PushBytesBuf, Network, OutPoint, ScriptBuf, Sequence, Txid,
271        Witness,
272    };
273    use ciborium::ser::into_writer;
274    use std::str::FromStr;
275
276    fn txid_from_hex(hex: &str) -> Txid {
277        Txid::from_str(hex).expect("invalid txid hex")
278    }
279
280    fn txid_from_bytes(bytes: [u8; 32]) -> Txid {
281        Txid::from_slice(&bytes).expect("invalid txid bytes")
282    }
283
284    fn build_op_return_script(data: &[u8]) -> ScriptBuf {
285        let push = PushBytesBuf::try_from(data.to_vec()).expect("invalid push data length");
286        ScriptBuf::builder()
287            .push_opcode(opcodes::all::OP_RETURN)
288            .push_slice(push)
289            .into_script()
290    }
291
292    fn encode_values(values: &[u64]) -> Vec<u8> {
293        let mut encoded = Vec::new();
294        into_writer(values, &mut encoded).expect("failed to encode cbor");
295        encoded
296    }
297
298    #[test]
299    fn compute_utxo_key_varies_with_inputs() {
300        let txid_a =
301            txid_from_hex("0101010101010101010101010101010101010101010101010101010101010101");
302        let txid_b =
303            txid_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
304
305        let key_a0 = compute_utxo_key(&txid_a, 0);
306        let key_a1 = compute_utxo_key(&txid_a, 1);
307        let key_b0 = compute_utxo_key(&txid_b, 0);
308
309        assert_eq!(key_a0.len(), 12);
310        assert_ne!(key_a0, key_a1);
311        assert_ne!(key_a0, key_b0);
312    }
313
314    #[test]
315    fn leading_zero_count_handles_full_and_partial_bytes() {
316        let all_zero = txid_from_bytes([0u8; 32]);
317        assert_eq!(leading_zero_count(&all_zero), 64);
318
319        let mut half = [0xffu8; 32];
320        half[31] = 0x0f;
321        let half_byte = txid_from_bytes(half);
322        assert_eq!(leading_zero_count(&half_byte), 1);
323
324        let mut bytes = [0xffu8; 32];
325        bytes[31] = 0xf0;
326        let non_zero = txid_from_bytes(bytes);
327        assert_eq!(leading_zero_count(&non_zero), 0);
328    }
329
330    #[test]
331    fn leading_zero_count_ignores_trailing_zero_bytes() {
332        let mut bytes = [0xffu8; 32];
333        bytes[0] = 0x00;
334        let txid = txid_from_bytes(bytes);
335
336        assert_eq!(leading_zero_count(&txid), 0);
337    }
338
339    #[test]
340    fn parse_op_return_succeeds_with_valid_prefix_and_cbor() {
341        const PREFIX: &[u8] = b"ZELD";
342        let mut payload = PREFIX.to_vec();
343        payload.extend(encode_values(&[1, 2, 3]));
344        let script = build_op_return_script(&payload);
345
346        let parsed = parse_op_return(&script, PREFIX).expect("expected values");
347        assert_eq!(parsed, vec![1, 2, 3]);
348    }
349
350    #[test]
351    fn parse_op_return_rejects_missing_op_return() {
352        let script = ScriptBuf::builder().push_slice(b"data").into_script();
353        assert!(parse_op_return(&script, b"ZELD").is_none());
354    }
355
356    #[test]
357    fn parse_op_return_rejects_non_push_instruction() {
358        let script = ScriptBuf::builder()
359            .push_opcode(opcodes::all::OP_RETURN)
360            .push_opcode(opcodes::all::OP_ADD)
361            .into_script();
362        assert!(parse_op_return(&script, b"ZELD").is_none());
363    }
364
365    #[test]
366    fn parse_op_return_enforces_prefix_length() {
367        let script = build_op_return_script(b"\x01");
368        assert!(parse_op_return(&script, b"ZELD").is_none());
369    }
370
371    #[test]
372    fn parse_op_return_enforces_prefix_match() {
373        const PREFIX: &[u8] = b"ZELD";
374        let mut payload = b"BADP".to_vec();
375        payload.extend(encode_values(&[9]));
376        let script = build_op_return_script(&payload);
377
378        assert!(parse_op_return(&script, PREFIX).is_none());
379    }
380
381    #[test]
382    fn parse_op_return_rejects_invalid_cbor() {
383        const PREFIX: &[u8] = b"ZELD";
384        let mut payload = PREFIX.to_vec();
385        payload.extend([0xff, 0x00]);
386        let script = build_op_return_script(&payload);
387
388        assert!(parse_op_return(&script, PREFIX).is_none());
389    }
390
391    #[test]
392    fn parse_op_return_rejects_empty_script() {
393        let script = ScriptBuf::new();
394        assert!(parse_op_return(&script, b"ZELD").is_none());
395    }
396
397    #[test]
398    fn parse_op_return_bubbles_error_before_op_return() {
399        let bytes = vec![opcodes::all::OP_PUSHDATA1.to_u8(), 0x02];
400        let script = ScriptBuf::from_bytes(bytes);
401        assert!(parse_op_return(&script, b"ZELD").is_none());
402    }
403
404    #[test]
405    fn parse_op_return_requires_push_after_op_return() {
406        let script = ScriptBuf::builder()
407            .push_opcode(opcodes::all::OP_RETURN)
408            .into_script();
409        assert!(parse_op_return(&script, b"ZELD").is_none());
410    }
411
412    #[test]
413    fn parse_op_return_bubbles_error_after_op_return() {
414        let bytes = vec![
415            opcodes::all::OP_RETURN.to_u8(),
416            opcodes::all::OP_PUSHDATA1.to_u8(),
417            0x01,
418        ];
419        let script = ScriptBuf::from_bytes(bytes);
420        assert!(parse_op_return(&script, b"ZELD").is_none());
421    }
422
423    #[test]
424    fn calculate_reward_returns_zero_when_below_min() {
425        assert_eq!(calculate_reward(1, 5, 2, 1_000), 0);
426    }
427
428    #[test]
429    fn calculate_reward_scales_by_zero_difference() {
430        let reward = calculate_reward(5, 5, 0, 1_000);
431        assert_eq!(reward, 1_000);
432
433        let scaled = calculate_reward(4, 5, 0, 1_000);
434        assert_eq!(scaled, 62);
435    }
436
437    #[test]
438    fn calculate_reward_exhausts_to_zero() {
439        let reward = calculate_reward(0, 10, 0, 1);
440        assert_eq!(reward, 0);
441    }
442
443    // Helper to create a TxIn with a witness
444    fn make_witness_input(witness_elements: Vec<Vec<u8>>) -> TxIn {
445        let mut witness = Witness::new();
446        for elem in witness_elements {
447            witness.push(elem);
448        }
449        TxIn {
450            previous_output: OutPoint::null(),
451            script_sig: ScriptBuf::new(),
452            sequence: Sequence::MAX,
453            witness,
454        }
455    }
456
457    // Helper to create a TxIn with a script_sig
458    fn make_scriptsig_input(script_sig: ScriptBuf) -> TxIn {
459        TxIn {
460            previous_output: OutPoint::null(),
461            script_sig,
462            sequence: Sequence::MAX,
463            witness: Witness::new(),
464        }
465    }
466
467    // A valid DER-encoded ECDSA signature with SIGHASH_ALL (0x01)
468    // This is a minimal valid DER signature structure
469    fn make_ecdsa_sig_sighash_all() -> Vec<u8> {
470        // DER signature: 0x30 [total-len] 0x02 [r-len] [r] 0x02 [s-len] [s] [sighash]
471        // Using minimal r and s values for testing
472        let mut sig = vec![
473            0x30, 0x44, // SEQUENCE, length 68
474            0x02, 0x20, // INTEGER, length 32 (r)
475        ];
476        sig.extend([0x01; 32]); // r value (32 bytes)
477        sig.extend([
478            0x02, 0x20, // INTEGER, length 32 (s)
479        ]);
480        sig.extend([0x02; 32]); // s value (32 bytes)
481        sig.push(0x01); // SIGHASH_ALL
482        sig
483    }
484
485    // A valid DER-encoded ECDSA signature with SIGHASH_NONE (0x02)
486    fn make_ecdsa_sig_sighash_none() -> Vec<u8> {
487        let mut sig = make_ecdsa_sig_sighash_all();
488        *sig.last_mut().unwrap() = 0x02; // SIGHASH_NONE
489        sig
490    }
491
492    // A very short but valid DER-encoded ECDSA signature with SIGHASH_ALL (minimal R/S)
493    fn make_short_ecdsa_sig_sighash_all() -> Vec<u8> {
494        // 0x30 len 0x02 lenR R 0x02 lenS S sighash
495        // minimal R/S: single-byte integers >= 0x01
496        vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01]
497    }
498
499    // Malformed short DER (still starts with 0x30 but invalid structure)
500    fn make_short_malformed_der() -> Vec<u8> {
501        vec![0x30, 0x02, 0x01, 0x01, 0x01] // too short to encode two integers + sighash
502    }
503
504    // A Schnorr signature (64 bytes = SIGHASH_DEFAULT)
505    fn make_schnorr_sig_default() -> Vec<u8> {
506        vec![0x01; 64]
507    }
508
509    // A Schnorr signature (65 bytes with explicit SIGHASH_ALL)
510    fn make_schnorr_sig_sighash_all() -> Vec<u8> {
511        let mut sig = vec![0x01; 64];
512        sig.push(0x01); // SIGHASH_ALL
513        sig
514    }
515
516    // A Schnorr signature (65 bytes with SIGHASH_NONE)
517    fn make_schnorr_sig_sighash_none() -> Vec<u8> {
518        let mut sig = vec![0x01; 64];
519        sig.push(0x02); // SIGHASH_NONE
520        sig
521    }
522
523    // A 33-byte compressed public key
524    fn make_pubkey() -> Vec<u8> {
525        let mut pk = vec![0x02]; // compressed pubkey prefix
526        pk.extend([0xab; 32]);
527        pk
528    }
529
530    #[test]
531    fn all_inputs_sighash_all_accepts_p2wpkh_with_sighash_all() {
532        let sig = make_ecdsa_sig_sighash_all();
533        let pubkey = make_pubkey();
534        let input = make_witness_input(vec![sig, pubkey]);
535
536        assert!(all_inputs_sighash_all(&[input]));
537    }
538
539    #[test]
540    fn all_inputs_sighash_all_accepts_p2wpkh_with_short_valid_der() {
541        let sig = make_short_ecdsa_sig_sighash_all();
542        let pubkey = make_pubkey();
543        let input = make_witness_input(vec![sig, pubkey]);
544
545        assert!(all_inputs_sighash_all(&[input]));
546    }
547
548    #[test]
549    fn all_inputs_sighash_all_rejects_p2wpkh_with_short_malformed_der() {
550        let sig = make_short_malformed_der();
551        let pubkey = make_pubkey();
552        let input = make_witness_input(vec![sig, pubkey]);
553
554        assert!(!all_inputs_sighash_all(&[input]));
555    }
556
557    #[test]
558    fn all_inputs_sighash_all_rejects_p2wpkh_with_sighash_none() {
559        let sig = make_ecdsa_sig_sighash_none();
560        let pubkey = make_pubkey();
561        let input = make_witness_input(vec![sig, pubkey]);
562
563        assert!(!all_inputs_sighash_all(&[input]));
564    }
565
566    #[test]
567    fn all_inputs_sighash_all_accepts_taproot_with_sighash_default() {
568        let sig = make_schnorr_sig_default();
569        let input = make_witness_input(vec![sig]);
570
571        assert!(all_inputs_sighash_all(&[input]));
572    }
573
574    #[test]
575    fn all_inputs_sighash_all_accepts_taproot_with_sighash_all() {
576        let sig = make_schnorr_sig_sighash_all();
577        let input = make_witness_input(vec![sig]);
578
579        assert!(all_inputs_sighash_all(&[input]));
580    }
581
582    #[test]
583    fn all_inputs_sighash_all_rejects_taproot_with_sighash_none() {
584        let sig = make_schnorr_sig_sighash_none();
585        let input = make_witness_input(vec![sig]);
586
587        assert!(!all_inputs_sighash_all(&[input]));
588    }
589
590    #[test]
591    fn all_inputs_sighash_all_rejects_empty_witness() {
592        let input = make_witness_input(vec![]);
593
594        assert!(!all_inputs_sighash_all(&[input]));
595    }
596
597    #[test]
598    fn all_inputs_sighash_all_rejects_empty_input() {
599        let input = TxIn {
600            previous_output: OutPoint::null(),
601            script_sig: ScriptBuf::new(),
602            sequence: Sequence::MAX,
603            witness: Witness::new(),
604        };
605
606        assert!(!all_inputs_sighash_all(&[input]));
607    }
608
609    #[test]
610    fn all_inputs_sighash_all_accepts_p2pkh_with_sighash_all() {
611        let sig = make_ecdsa_sig_sighash_all();
612        let pubkey = make_pubkey();
613
614        // P2PKH script_sig: <sig> <pubkey>
615        let script_sig = ScriptBuf::builder()
616            .push_slice(PushBytesBuf::try_from(sig).unwrap())
617            .push_slice(PushBytesBuf::try_from(pubkey).unwrap())
618            .into_script();
619        let input = make_scriptsig_input(script_sig);
620
621        assert!(all_inputs_sighash_all(&[input]));
622    }
623
624    #[test]
625    fn all_inputs_sighash_all_rejects_p2pkh_with_sighash_none() {
626        let sig = make_ecdsa_sig_sighash_none();
627        let pubkey = make_pubkey();
628
629        let script_sig = ScriptBuf::builder()
630            .push_slice(PushBytesBuf::try_from(sig).unwrap())
631            .push_slice(PushBytesBuf::try_from(pubkey).unwrap())
632            .into_script();
633        let input = make_scriptsig_input(script_sig);
634
635        assert!(!all_inputs_sighash_all(&[input]));
636    }
637
638    #[test]
639    fn all_inputs_sighash_all_accepts_multiple_valid_inputs() {
640        let sig1 = make_ecdsa_sig_sighash_all();
641        let pubkey1 = make_pubkey();
642        let input1 = make_witness_input(vec![sig1, pubkey1]);
643
644        let sig2 = make_schnorr_sig_default();
645        let input2 = make_witness_input(vec![sig2]);
646
647        assert!(all_inputs_sighash_all(&[input1, input2]));
648    }
649
650    #[test]
651    fn all_inputs_sighash_all_rejects_if_any_input_invalid() {
652        let sig1 = make_ecdsa_sig_sighash_all();
653        let pubkey1 = make_pubkey();
654        let input1 = make_witness_input(vec![sig1, pubkey1]);
655
656        let sig2 = make_schnorr_sig_sighash_none();
657        let input2 = make_witness_input(vec![sig2]);
658
659        assert!(!all_inputs_sighash_all(&[input1, input2]));
660    }
661
662    #[test]
663    fn all_inputs_sighash_all_returns_true_for_empty_inputs() {
664        // Edge case: no inputs means all (zero) inputs satisfy the condition
665        assert!(all_inputs_sighash_all(&[]));
666    }
667
668    #[test]
669    fn all_inputs_sighash_all_accepts_p2wsh_multisig_with_sighash_all() {
670        let sig1 = make_ecdsa_sig_sighash_all();
671        let sig2 = make_ecdsa_sig_sighash_all();
672        let redeem_script = vec![0x52, 0x21]; // OP_2 OP_PUSHBYTES_33 (start of 2-of-3 multisig)
673
674        // P2WSH witness: [OP_0, sig1, sig2, redeem_script]
675        let input = make_witness_input(vec![vec![], sig1, sig2, redeem_script]);
676
677        assert!(all_inputs_sighash_all(&[input]));
678    }
679
680    #[test]
681    fn all_inputs_sighash_all_rejects_p2wsh_multisig_with_mixed_sighash() {
682        let sig1 = make_ecdsa_sig_sighash_all();
683        let sig2 = make_ecdsa_sig_sighash_none();
684        let redeem_script = vec![0x52, 0x21];
685
686        let input = make_witness_input(vec![vec![], sig1, sig2, redeem_script]);
687
688        assert!(!all_inputs_sighash_all(&[input]));
689    }
690
691    #[test]
692    fn all_inputs_sighash_all_accepts_taproot_with_annex() {
693        // Taproot with 2-element witness: signature + annex
694        let sig = make_schnorr_sig_sighash_all();
695        let annex = vec![0x50, 0x01, 0x02]; // Annex starts with 0x50
696        let input = make_witness_input(vec![sig, annex]);
697
698        assert!(all_inputs_sighash_all(&[input]));
699    }
700
701    #[test]
702    fn all_inputs_sighash_all_rejects_taproot_with_annex_and_sighash_none() {
703        // Taproot with 2-element witness but invalid sighash
704        let sig = make_schnorr_sig_sighash_none();
705        let annex = vec![0x50, 0x01, 0x02];
706        let input = make_witness_input(vec![sig, annex]);
707
708        assert!(!all_inputs_sighash_all(&[input]));
709    }
710
711    #[test]
712    fn all_inputs_sighash_all_accepts_taproot_script_path_with_valid_sigs() {
713        // Taproot script-path: [sig, script, control_block]
714        // Control block starts with 0xc0 or 0xc1 (leaf version)
715        let sig = make_schnorr_sig_sighash_all();
716        let script = vec![0x51]; // OP_TRUE
717        let control_block = vec![0xc0, 0x01, 0x02, 0x03]; // Starts with 0xc0
718
719        let input = make_witness_input(vec![sig, script, control_block]);
720
721        assert!(all_inputs_sighash_all(&[input]));
722    }
723
724    #[test]
725    fn all_inputs_sighash_all_rejects_taproot_script_path_with_invalid_sig() {
726        // Taproot script-path with invalid sighash
727        let sig = make_schnorr_sig_sighash_none();
728        let script = vec![0x51]; // OP_TRUE
729        let control_block = vec![0xc1, 0x01, 0x02, 0x03]; // Starts with 0xc1
730
731        let input = make_witness_input(vec![sig, script, control_block]);
732
733        assert!(!all_inputs_sighash_all(&[input]));
734    }
735
736    #[test]
737    fn all_inputs_sighash_all_rejects_taproot_script_path_without_signatures() {
738        // Taproot script-path witness missing any 64/65-byte signature elements
739        let stack_value = vec![0x01, 0x02, 0x03]; // Non-signature stack element
740        let script = vec![0x51]; // OP_TRUE
741        let control_block = vec![0xc0, 0x01, 0x02, 0x03]; // Starts with 0xc0
742
743        let input = make_witness_input(vec![stack_value, script, control_block]);
744
745        assert!(!all_inputs_sighash_all(&[input]));
746    }
747
748    #[test]
749    fn all_inputs_sighash_all_rejects_p2wsh_without_signatures() {
750        // P2WSH witness that contains no DER signatures before the redeem script
751        let placeholder = vec![]; // OP_0 placeholder
752        let data = vec![0x01, 0x02, 0x03]; // Not a DER signature (does not start with 0x30)
753        let redeem_script = vec![0x51]; // OP_TRUE redeem script
754
755        let input = make_witness_input(vec![placeholder, data, redeem_script]);
756
757        assert!(!all_inputs_sighash_all(&[input]));
758    }
759
760    #[test]
761    fn all_inputs_sighash_all_rejects_unknown_witness_structure() {
762        // Single element witness that's not a valid Schnorr signature size
763        let unknown_data = vec![0x01, 0x02, 0x03]; // 3 bytes, not 64 or 65
764        let input = make_witness_input(vec![unknown_data]);
765
766        assert!(!all_inputs_sighash_all(&[input]));
767    }
768
769    #[test]
770    fn all_inputs_sighash_all_rejects_scriptsig_without_signatures() {
771        // Script_sig with only a pubkey push (no signature)
772        let pubkey = make_pubkey();
773        let script_sig = ScriptBuf::builder()
774            .push_slice(PushBytesBuf::try_from(pubkey).unwrap())
775            .into_script();
776        let input = make_scriptsig_input(script_sig);
777
778        assert!(!all_inputs_sighash_all(&[input]));
779    }
780
781    #[test]
782    fn all_inputs_sighash_all_ignores_non_signature_pushes_in_scriptsig() {
783        // Script_sig with a valid signature followed by non-signature data
784        let sig = make_ecdsa_sig_sighash_all();
785        let pubkey = make_pubkey();
786        let extra_data = vec![0x01, 0x02, 0x03]; // Too short to be a signature
787
788        let script_sig = ScriptBuf::builder()
789            .push_slice(PushBytesBuf::try_from(sig).unwrap())
790            .push_slice(PushBytesBuf::try_from(pubkey).unwrap())
791            .push_slice(PushBytesBuf::try_from(extra_data).unwrap())
792            .into_script();
793        let input = make_scriptsig_input(script_sig);
794
795        assert!(all_inputs_sighash_all(&[input]));
796    }
797
798    #[test]
799    fn all_inputs_sighash_all_handles_scriptsig_with_opcodes() {
800        // Script_sig with a valid signature followed by an opcode
801        let sig = make_ecdsa_sig_sighash_all();
802        let pubkey = make_pubkey();
803
804        // P2SH-like script_sig: <sig> <pubkey> <redeem_script_with_opcodes>
805        // The redeem script contains opcodes that are not push instructions
806        let script_sig = ScriptBuf::builder()
807            .push_slice(PushBytesBuf::try_from(sig).unwrap())
808            .push_slice(PushBytesBuf::try_from(pubkey).unwrap())
809            .push_opcode(opcodes::all::OP_CHECKSIG)
810            .into_script();
811        let input = make_scriptsig_input(script_sig);
812
813        // Should still work because we found valid signatures
814        assert!(all_inputs_sighash_all(&[input]));
815    }
816
817    #[test]
818    fn extract_address_returns_none_for_op_return() {
819        let script = build_op_return_script(b"ZELD");
820        assert!(extract_address(&script, Network::Bitcoin).is_none());
821    }
822
823    #[test]
824    fn extract_address_returns_none_for_unknown_script() {
825        // OP_CHECKSIG alone is not a valid address script
826        let script = ScriptBuf::builder()
827            .push_opcode(opcodes::all::OP_CHECKSIG)
828            .into_script();
829        assert!(extract_address(&script, Network::Bitcoin).is_none());
830    }
831
832    #[test]
833    fn extract_address_returns_address_for_p2pkh() {
834        // P2PKH: OP_DUP OP_HASH160 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG
835        let hash = [0xab; 20];
836        let script = ScriptBuf::builder()
837            .push_opcode(opcodes::all::OP_DUP)
838            .push_opcode(opcodes::all::OP_HASH160)
839            .push_slice(hash)
840            .push_opcode(opcodes::all::OP_EQUALVERIFY)
841            .push_opcode(opcodes::all::OP_CHECKSIG)
842            .into_script();
843        let addr = extract_address(&script, Network::Bitcoin);
844        assert!(addr.is_some());
845        assert!(addr.unwrap().starts_with('1')); // Mainnet P2PKH starts with '1'
846    }
847
848    #[test]
849    fn extract_address_returns_address_for_p2sh() {
850        // P2SH: OP_HASH160 <20-byte-hash> OP_EQUAL
851        let hash = [0xcd; 20];
852        let script = ScriptBuf::builder()
853            .push_opcode(opcodes::all::OP_HASH160)
854            .push_slice(hash)
855            .push_opcode(opcodes::all::OP_EQUAL)
856            .into_script();
857        let addr = extract_address(&script, Network::Bitcoin);
858        assert!(addr.is_some());
859        assert!(addr.unwrap().starts_with('3')); // Mainnet P2SH starts with '3'
860    }
861
862    #[test]
863    fn extract_address_returns_address_for_p2wpkh() {
864        // P2WPKH: OP_0 <20-byte-hash>
865        let hash = [0xef; 20];
866        let script = ScriptBuf::builder()
867            .push_opcode(opcodes::OP_0)
868            .push_slice(hash)
869            .into_script();
870        let addr = extract_address(&script, Network::Bitcoin);
871        assert!(addr.is_some());
872        assert!(addr.unwrap().starts_with("bc1q")); // Mainnet P2WPKH bech32
873    }
874
875    #[test]
876    fn extract_address_returns_address_for_p2wsh() {
877        // P2WSH: OP_0 <32-byte-hash>
878        let hash = [0x12; 32];
879        let script = ScriptBuf::builder()
880            .push_opcode(opcodes::OP_0)
881            .push_slice(hash)
882            .into_script();
883        let addr = extract_address(&script, Network::Bitcoin);
884        assert!(addr.is_some());
885        assert!(addr.unwrap().starts_with("bc1q")); // Mainnet P2WSH bech32
886    }
887
888    #[test]
889    fn extract_address_returns_address_for_p2tr() {
890        // P2TR: OP_1 <32-byte-x-only-pubkey>
891        let pubkey = [0x34; 32];
892        let script = ScriptBuf::builder()
893            .push_opcode(opcodes::all::OP_PUSHNUM_1)
894            .push_slice(pubkey)
895            .into_script();
896        let addr = extract_address(&script, Network::Bitcoin);
897        assert!(addr.is_some());
898        assert!(addr.unwrap().starts_with("bc1p")); // Mainnet P2TR bech32m
899    }
900
901    #[test]
902    fn extract_address_uses_correct_network_prefix() {
903        // P2WPKH on testnet
904        let hash = [0xef; 20];
905        let script = ScriptBuf::builder()
906            .push_opcode(opcodes::OP_0)
907            .push_slice(hash)
908            .into_script();
909
910        let mainnet_addr = extract_address(&script, Network::Bitcoin).unwrap();
911        let testnet_addr = extract_address(&script, Network::Testnet4).unwrap();
912
913        assert!(mainnet_addr.starts_with("bc1"));
914        assert!(testnet_addr.starts_with("tb1"));
915    }
916}