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
16pub 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
26fn looks_like_der_signature(data: &[u8]) -> bool {
28 (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 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 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
112fn 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
119fn is_schnorr_sighash_all(sig_bytes: &[u8]) -> bool {
122 SchnorrSignature::from_slice(sig_bytes)
123 .map(|sig| {
124 matches!(
126 sig.sighash_type,
127 TapSighashType::Default | TapSighashType::All
128 )
129 })
130 .unwrap_or(false)
131}
132
133fn 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 if data.len() >= 9 && data.len() <= 74 && data.first() == Some(&0x30) {
145 signatures.push(data);
146 }
147 }
148 }
149 signatures
150}
151
152pub 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 let witness_len = input.witness.len();
169
170 let first_elem = input.witness.nth(0).unwrap_or(&[]);
173
174 if witness_len == 1 && (first_elem.len() == 64 || first_elem.len() == 65) {
175 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 if !is_schnorr_sighash_all(first_elem) {
183 return false;
184 }
185 } else if witness_len == 2 && looks_like_der_signature(first_elem) {
186 if !is_ecdsa_sighash_all(first_elem) {
188 return false;
189 }
190 } else if witness_len > 2 {
191 let last_elem = input.witness.nth(witness_len - 1).unwrap_or(&[]);
197
198 if !last_elem.is_empty() && (last_elem[0] & 0xfe) == 0xc0 {
199 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 return false;
215 }
216 } else {
217 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 if elem.is_empty() {
224 continue;
225 }
226 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 return false;
237 }
238 }
239 } else {
240 return false;
242 }
243 } else if has_scriptsig {
244 let signatures = extract_scriptsig_signatures(&input.script_sig);
246
247 if signatures.is_empty() {
248 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 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 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 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 fn make_ecdsa_sig_sighash_all() -> Vec<u8> {
470 let mut sig = vec![
473 0x30, 0x44, 0x02, 0x20, ];
476 sig.extend([0x01; 32]); sig.extend([
478 0x02, 0x20, ]);
480 sig.extend([0x02; 32]); sig.push(0x01); sig
483 }
484
485 fn make_ecdsa_sig_sighash_none() -> Vec<u8> {
487 let mut sig = make_ecdsa_sig_sighash_all();
488 *sig.last_mut().unwrap() = 0x02; sig
490 }
491
492 fn make_short_ecdsa_sig_sighash_all() -> Vec<u8> {
494 vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01]
497 }
498
499 fn make_short_malformed_der() -> Vec<u8> {
501 vec![0x30, 0x02, 0x01, 0x01, 0x01] }
503
504 fn make_schnorr_sig_default() -> Vec<u8> {
506 vec![0x01; 64]
507 }
508
509 fn make_schnorr_sig_sighash_all() -> Vec<u8> {
511 let mut sig = vec![0x01; 64];
512 sig.push(0x01); sig
514 }
515
516 fn make_schnorr_sig_sighash_none() -> Vec<u8> {
518 let mut sig = vec![0x01; 64];
519 sig.push(0x02); sig
521 }
522
523 fn make_pubkey() -> Vec<u8> {
525 let mut pk = vec![0x02]; 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 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 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]; 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 let sig = make_schnorr_sig_sighash_all();
695 let annex = vec![0x50, 0x01, 0x02]; 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 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 let sig = make_schnorr_sig_sighash_all();
716 let script = vec![0x51]; let control_block = vec![0xc0, 0x01, 0x02, 0x03]; 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 let sig = make_schnorr_sig_sighash_none();
728 let script = vec![0x51]; let control_block = vec![0xc1, 0x01, 0x02, 0x03]; 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 let stack_value = vec![0x01, 0x02, 0x03]; let script = vec![0x51]; let control_block = vec![0xc0, 0x01, 0x02, 0x03]; 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 let placeholder = vec![]; let data = vec![0x01, 0x02, 0x03]; let redeem_script = vec![0x51]; 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 let unknown_data = vec![0x01, 0x02, 0x03]; 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 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 let sig = make_ecdsa_sig_sighash_all();
785 let pubkey = make_pubkey();
786 let extra_data = vec![0x01, 0x02, 0x03]; 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 let sig = make_ecdsa_sig_sighash_all();
802 let pubkey = make_pubkey();
803
804 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 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 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 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')); }
847
848 #[test]
849 fn extract_address_returns_address_for_p2sh() {
850 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')); }
861
862 #[test]
863 fn extract_address_returns_address_for_p2wpkh() {
864 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")); }
874
875 #[test]
876 fn extract_address_returns_address_for_p2wsh() {
877 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")); }
887
888 #[test]
889 fn extract_address_returns_address_for_p2tr() {
890 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")); }
900
901 #[test]
902 fn extract_address_uses_correct_network_prefix() {
903 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}