1use bitcoin::opcodes::all::{OP_CHECKMULTISIG, OP_PUSHNUM_1, OP_PUSHNUM_16};
2use bitcoin::script::Instruction;
3use bitcoin::{Opcode, PublicKey, Script, WitnessVersion};
4
5#[derive(Debug, PartialEq, Eq, Clone)]
7pub enum TxoutType {
8 NonStandard,
9 Anchor,
11 PubKey(PublicKey),
12 PubKeyHash([u8; 20]),
13 ScriptHash([u8; 20]),
14 Multisig {
15 required_sigs: u8,
16 keys_count: u8,
17 keys: Vec<Vec<u8>>,
18 },
19 NullData,
21 WitnessV0ScriptHash([u8; 32]),
22 WitnessV0KeyHash([u8; 20]),
23 WitnessV1Taproot([u8; 32]),
24 WitnessUnknown(Vec<Vec<u8>>),
26}
27
28pub fn solve(script_pubkey: &Script) -> TxoutType {
29 if script_pubkey.is_p2sh() {
30 let hash: [u8; 20] = script_pubkey.as_bytes()[2..22].try_into().unwrap();
31 return TxoutType::ScriptHash(hash);
32 }
33
34 if let Some(version) = script_pubkey.witness_version() {
35 let program = &script_pubkey.as_bytes()[2..];
36
37 match (version, program.len()) {
38 (WitnessVersion::V0, 20) => {
39 return TxoutType::WitnessV0KeyHash(program.try_into().unwrap());
40 }
41 (WitnessVersion::V0, 32) => {
42 return TxoutType::WitnessV0ScriptHash(program.try_into().unwrap());
43 }
44 (WitnessVersion::V1, 32) => {
45 return TxoutType::WitnessV1Taproot(program.try_into().unwrap());
46 }
47 _ => {}
48 }
49
50 if is_pay_to_anchor(script_pubkey) {
51 return TxoutType::Anchor;
52 }
53
54 if version != WitnessVersion::V0 {
55 return TxoutType::WitnessUnknown(vec![vec![version as u8], program.to_vec()]);
56 }
57
58 return TxoutType::NonStandard;
59 }
60
61 if script_pubkey.is_op_return() {
63 return TxoutType::NullData;
64 }
65
66 if let Some(pubkey) = script_pubkey.p2pk_public_key() {
67 return TxoutType::PubKey(pubkey);
68 }
69
70 if script_pubkey.is_p2pkh() {
71 return TxoutType::PubKeyHash(script_pubkey.as_bytes()[3..23].try_into().unwrap());
72 }
73
74 if let Some((required_sigs, keys_count, keys)) = match_multisig(script_pubkey) {
75 return TxoutType::Multisig {
76 required_sigs,
77 keys_count,
78 keys,
79 };
80 }
81
82 TxoutType::NonStandard
83}
84
85fn is_pay_to_anchor(script: &Script) -> bool {
86 let script_bytes = script.as_bytes();
87 script.len() == 4
88 && script_bytes[0] == OP_PUSHNUM_1.to_u8()
89 && script_bytes[1] == 0x02
90 && script_bytes[2] == 0x4e
91 && script_bytes[3] == 0x73
92}
93
94#[inline]
101fn match_multisig(script_pubkey: &Script) -> Option<(u8, u8, Vec<Vec<u8>>)> {
102 let mut instructions = script_pubkey.instructions();
103
104 let required_sigs = if let Ok(Instruction::Op(op)) = instructions.next()? {
105 decode_pushnum(op)?
106 } else {
107 return None;
108 };
109
110 let mut keys = Vec::new();
111 while let Some(Ok(instruction)) = instructions.next() {
112 match instruction {
113 Instruction::PushBytes(key) => {
114 keys.push(key.as_bytes().to_vec());
115 }
116 Instruction::Op(op) => {
117 if let Some(pushnum) = decode_pushnum(op) {
118 if pushnum as usize != keys.len() {
119 return None;
120 }
121 }
122 break;
123 }
124 }
125 }
126
127 let num_pubkeys: u8 = keys
128 .len()
129 .try_into()
130 .expect("Must fit into u8 due to the MAX_PUBKEYS_PER_MULTISIG limit; qed");
131
132 if required_sigs > num_pubkeys {
133 return None;
134 }
135
136 if let Ok(Instruction::Op(op)) = instructions.next()? {
137 if op != OP_CHECKMULTISIG {
138 return None;
139 }
140 }
141
142 if instructions.next().is_none() {
143 Some((required_sigs, num_pubkeys, keys))
144 } else {
145 None
146 }
147}
148
149fn decode_pushnum(opcode: Opcode) -> Option<u8> {
150 if (OP_PUSHNUM_1.to_u8()..=OP_PUSHNUM_16.to_u8()).contains(&opcode.to_u8()) {
151 Some(opcode.to_u8() - 0x50)
152 } else {
153 None
154 }
155}