1use bitcoin::blockdata::opcodes::{all, All};
2use bitcoin::blockdata::script::Instruction;
3use bitcoin::hashes::{hash160, Hash};
4use bitcoin::util::address::Payload;
5use bitcoin::{Address, Network, PubkeyHash, PublicKey, Script};
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use Instruction::{Op, PushBytes};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
17pub enum ScriptType {
18 OpReturn,
19 Pay2MultiSig,
20 Pay2PublicKey,
21 Pay2PublicKeyHash,
22 Pay2ScriptHash,
23 Pay2WitnessPublicKeyHash,
24 Pay2WitnessScriptHash,
25 WitnessProgram,
26 Unspendable,
27 NotRecognised,
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize)]
34pub struct ScriptInfo {
35 pub addresses: Vec<Address>,
36 pub pattern: ScriptType,
37}
38
39pub fn evaluate_script(script: &Script, net: Network) -> ScriptInfo {
43 let address = Address::from_script(script, net);
44 if script.is_p2pk() {
45 ScriptInfo::new(p2pk_to_address(script), ScriptType::Pay2PublicKey)
46 } else if script.is_p2pkh() {
47 ScriptInfo::new(address, ScriptType::Pay2PublicKeyHash)
48 } else if script.is_p2sh() {
49 ScriptInfo::new(address, ScriptType::Pay2ScriptHash)
50 } else if script.is_v0_p2wpkh() {
51 ScriptInfo::new(address, ScriptType::Pay2WitnessPublicKeyHash)
52 } else if script.is_v0_p2wsh() {
53 ScriptInfo::new(address, ScriptType::Pay2WitnessScriptHash)
54 } else if script.is_witness_program() {
55 ScriptInfo::new(address, ScriptType::WitnessProgram)
56 } else if script.is_op_return() {
57 ScriptInfo::new(address, ScriptType::OpReturn)
58 } else if script.is_provably_unspendable() {
59 ScriptInfo::new(address, ScriptType::Unspendable)
60 } else if is_multisig(script) {
61 ScriptInfo::from_vec(multisig_addresses(script), ScriptType::Pay2MultiSig)
62 } else {
63 ScriptInfo::new(address, ScriptType::NotRecognised)
64 }
65}
66
67impl ScriptInfo {
68 pub(crate) fn new(address: Option<Address>, pattern: ScriptType) -> Self {
69 if let Some(address) = address {
70 Self::from_vec(vec![address], pattern)
71 } else {
72 Self::from_vec(Vec::new(), pattern)
73 }
74 }
75
76 pub(crate) fn from_vec(addresses: Vec<Address>, pattern: ScriptType) -> Self {
77 Self { addresses, pattern }
78 }
79}
80
81fn is_multisig(script: &Script) -> bool {
85 let mut chunks: Vec<Instruction> = Vec::new();
87 for i in script.instructions() {
88 if let Ok(i) = i {
89 chunks.push(i);
90 } else {
91 return false;
92 }
93 }
94
95 if chunks.len() < 4 {
97 return false;
98 }
99
100 match chunks.last().unwrap() {
102 PushBytes(_) => {
103 return false;
104 }
105 Op(op) => {
106 if !(op.eq(&all::OP_CHECKMULTISIG) || op.eq(&all::OP_CHECKMULTISIGVERIFY)) {
107 return false;
108 }
109 }
110 }
111
112 let second_last_chunk = chunks.get(chunks.len() - 2).unwrap();
114 if let Some(num_keys) = get_num_keys(second_last_chunk) {
115 if num_keys < 1 || (num_keys + 3) as usize != chunks.len() {
117 return false;
118 }
119 } else {
120 return false;
121 }
122
123 for chunk in chunks.iter().skip(1).take(chunks.len() - 3) {
125 if let Op(_) = chunk {
126 return false;
127 }
128 }
129
130 if let Some(num_keys) = get_num_keys(chunks.first().unwrap()) {
132 if num_keys < 1 {
134 return false;
135 }
136 } else {
137 return false;
138 }
139 true
140}
141
142fn multisig_addresses(script: &Script) -> Vec<Address> {
146 assert!(is_multisig(script));
147 let ops: Vec<Instruction> = script.instructions().filter_map(|o| o.ok()).collect();
148
149 let num_keys = {
151 if let Some(Op(op)) = ops.get(ops.len() - 2) {
152 decode_from_op_n(op)
153 } else {
154 unreachable!()
155 }
156 };
157 let mut public_keys = Vec::with_capacity(num_keys as usize);
159 for op in ops.iter().skip(1).take(num_keys as usize) {
160 if let PushBytes(data) = op {
161 match PublicKey::from_slice(data) {
162 Ok(pk) => public_keys.push(Address {
163 payload: Payload::PubkeyHash(pk.pubkey_hash()),
164 network: Network::Bitcoin,
165 }),
166 Err(_) => return Vec::new(),
167 }
168 } else {
169 unreachable!()
170 }
171 }
172 public_keys
173}
174
175#[inline]
182fn decode_from_op_n(op: &All) -> i32 {
183 if op.eq(&all::OP_PUSHBYTES_0) {
184 0
185 } else if op.eq(&all::OP_PUSHNUM_NEG1) {
186 -1
187 } else {
188 op.into_u8() as i32 + 1 - all::OP_PUSHNUM_1.into_u8() as i32
189 }
190}
191
192#[inline]
196fn get_num_keys(op: &Instruction) -> Option<i32> {
197 match op {
198 PushBytes(_) => None,
199 Op(op) => {
200 if !(op.eq(&all::OP_PUSHNUM_NEG1)
201 || op.eq(&all::OP_PUSHBYTES_0)
202 || (op.ge(&all::OP_PUSHNUM_1) && all::OP_PUSHNUM_16.ge(op)))
203 {
204 None
205 } else {
206 Some(decode_from_op_n(op))
207 }
208 }
209 }
210}
211
212#[inline]
219fn p2pk_to_address(script: &Script) -> Option<Address> {
220 assert!(script.is_p2pk());
221 if let Some(Ok(Instruction::PushBytes(pk))) = script.instructions().next() {
222 let pkh = hash160::Hash::hash(pk);
224 Some(Address {
225 payload: Payload::PubkeyHash(PubkeyHash::from_slice(&pkh).ok()?),
226 network: Network::Bitcoin,
227 })
228 } else {
229 unreachable!()
230 }
231}
232
233trait Cmp {
234 fn ge(&self, other: &Self) -> bool;
235}
236
237impl Cmp for bitcoin::blockdata::opcodes::All {
238 fn ge(&self, other: &Self) -> bool {
239 self.into_u8() >= other.into_u8()
240 }
241}
242
243impl fmt::Display for ScriptType {
244 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
245 match *self {
246 ScriptType::OpReturn => write!(f, "OpReturn"),
247 ScriptType::Pay2MultiSig => write!(f, "Pay2MultiSig"),
248 ScriptType::Pay2PublicKey => write!(f, "Pay2PublicKey"),
249 ScriptType::Pay2PublicKeyHash => write!(f, "Pay2PublicKeyHash"),
250 ScriptType::Pay2ScriptHash => write!(f, "Pay2ScriptHash"),
251 ScriptType::Pay2WitnessPublicKeyHash => write!(f, "Pay2WitnessPublicKeyHash"),
252 ScriptType::Pay2WitnessScriptHash => write!(f, "Pay2WitnessScriptHash"),
253 ScriptType::WitnessProgram => write!(f, "WitnessProgram"),
254 ScriptType::Unspendable => write!(f, "Unspendable"),
255 ScriptType::NotRecognised => write!(f, "NotRecognised"),
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::{evaluate_script, ScriptType};
263 use bitcoin::hashes::hex::{FromHex, ToHex};
264 use bitcoin::{Network, Script};
265
266 #[test]
267 fn test_bitcoin_script_p2pkh() {
268 let bytes = [
271 0x76 as u8, 0xa9, 0x14, 0x12, 0xab, 0x8d, 0xc5, 0x88, 0xca, 0x9d, 0x57, 0x87, 0xdd,
272 0xe7, 0xeb, 0x29, 0x56, 0x9d, 0xa6, 0x3c, 0x3a, 0x23, 0x8c, 0x88, 0xac,
273 ];
274 let result = evaluate_script(
275 &Script::from_hex(&bytes.to_hex()).unwrap(),
276 Network::Bitcoin,
277 );
278 assert_eq!(
279 result.addresses.get(0).unwrap().to_string(),
280 String::from("12higDjoCCNXSA95xZMWUdPvXNmkAduhWv")
281 );
282 assert_eq!(result.pattern, ScriptType::Pay2PublicKeyHash);
283 }
284
285 #[test]
286 fn test_bitcoin_script_p2pk() {
287 let bytes = [
291 0x41 as u8, 0x04, 0x4b, 0xca, 0x63, 0x3a, 0x91, 0xde, 0x10, 0xdf, 0x85, 0xa6, 0x3d, 0x0a, 0x24,
293 0xcb, 0x09, 0x78, 0x31, 0x48, 0xfe, 0x0e, 0x16, 0xc9, 0x2e, 0x93, 0x7f, 0xc4, 0x49,
294 0x15, 0x80, 0xc8, 0x60, 0x75, 0x71, 0x48, 0xef, 0xfa, 0x05, 0x95, 0xa9, 0x55, 0xf4,
295 0x40, 0x78, 0xb4, 0x8b, 0xa6, 0x7f, 0xa1, 0x98, 0x78, 0x2e, 0x8b, 0xb6, 0x81, 0x15,
296 0xda, 0x0d, 0xaa, 0x8f, 0xde, 0x53, 0x01, 0xf7, 0xf9, 0xac,
297 ]; let result = evaluate_script(
299 &Script::from_hex(&bytes.to_hex()).unwrap(),
300 Network::Bitcoin,
301 );
302 assert_eq!(
303 result.addresses.get(0).unwrap().to_string(),
304 String::from("1LEWwJkDj8xriE87ALzQYcHjTmD8aqDj1f")
305 );
306 assert_eq!(result.pattern, ScriptType::Pay2PublicKey);
307 }
308
309 #[test]
310 fn test_bitcoin_script_p2ms() {
311 let bytes = [
316 0x52 as u8, 0x21, 0x02, 0x2d, 0xf8, 0x75, 0x04, 0x80, 0xad, 0x5b, 0x26, 0x95, 0x0b,
317 0x25, 0xc7, 0xba, 0x79, 0xd3, 0xe3, 0x7d, 0x75, 0xf6, 0x40, 0xf8, 0xe5, 0xd9, 0xbc,
318 0xd5, 0xb1, 0x50, 0xa0, 0xf8, 0x50, 0x14, 0xda, 0x21, 0x03, 0xe3, 0x81, 0x8b, 0x65,
319 0xbc, 0xc7, 0x3a, 0x7d, 0x64, 0x06, 0x41, 0x06, 0xa8, 0x59, 0xcc, 0x1a, 0x5a, 0x72,
320 0x8c, 0x43, 0x45, 0xff, 0x0b, 0x64, 0x12, 0x09, 0xfb, 0xa0, 0xd9, 0x0d, 0xe6, 0xe9,
321 0x21, 0x02, 0x1f, 0x2f, 0x6e, 0x1e, 0x50, 0xcb, 0x6a, 0x95, 0x39, 0x35, 0xc3, 0x60,
322 0x12, 0x84, 0x92, 0x5d, 0xec, 0xd3, 0xfd, 0x21, 0xbc, 0x44, 0x57, 0x12, 0x57, 0x68,
323 0x73, 0xfb, 0x8c, 0x6e, 0xbc, 0x18, 0x53, 0xae,
324 ];
325
326 let result = evaluate_script(
327 &Script::from_hex(&bytes.to_hex()).unwrap(),
328 Network::Bitcoin,
329 );
330 assert_eq!(result.pattern, ScriptType::Pay2MultiSig);
331 }
332
333 #[test]
334 fn test_bitcoin_script_p2sh() {
335 let bytes = [
338 0xa9 as u8, 0x14, 0xe9, 0xc3, 0xdd, 0x0c, 0x07, 0xaa, 0xc7, 0x61, 0x79, 0xeb, 0xc7, 0x6a, 0x6c, 0x78,
340 0xd4, 0xd6, 0x7c, 0x6c, 0x16, 0x0a, 0x87,
341 ]; let result = evaluate_script(
343 &Script::from_hex(&bytes.to_hex()).unwrap(),
344 Network::Bitcoin,
345 );
346 assert_eq!(
347 result.addresses.get(0).unwrap().to_string(),
348 String::from("3P14159f73E4gFr7JterCCQh9QjiTjiZrG")
349 );
350 assert_eq!(result.pattern, ScriptType::Pay2ScriptHash);
351 }
352
353 #[test]
354 fn test_bitcoin_script_non_standard() {
355 let bytes = [0x73 as u8, 0x63, 0x72, 0x69, 0x70, 0x74];
358 let result = evaluate_script(
359 &Script::from_hex(&bytes.to_hex()).unwrap(),
360 Network::Bitcoin,
361 );
362 assert_eq!(result.addresses.get(0), None);
363 assert_eq!(result.pattern, ScriptType::NotRecognised);
364 }
365
366 #[test]
367 fn test_bitcoin_bogus_script() {
368 let bytes = [0x4c as u8, 0xFF, 0x00];
369 let result = evaluate_script(
370 &Script::from_hex(&bytes.to_hex()).unwrap(),
371 Network::Bitcoin,
372 );
373 assert_eq!(result.addresses.get(0), None);
374 assert_eq!(result.pattern, ScriptType::NotRecognised);
375 }
376}