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
148 .instructions()
149 .filter_map(|o| o.ok())
150 .collect();
151
152 let num_keys = {
154 if let Some(Op(op)) = ops.get(ops.len() - 2) {
155 decode_from_op_n(op)
156 } else {
157 unreachable!()
158 }
159 };
160 let mut public_keys = Vec::with_capacity(num_keys as usize);
162 for op in ops.iter().skip(1).take(num_keys as usize) {
163 if let PushBytes(data) = op {
164 match PublicKey::from_slice(data) {
165 Ok(pk) => public_keys.push(Address {
166 payload: Payload::PubkeyHash(pk.pubkey_hash()),
167 network: Network::Bitcoin,
168 }),
169 Err(_) => return Vec::new(),
170 }
171 } else {
172 unreachable!()
173 }
174 }
175 public_keys
176}
177
178#[inline]
185fn decode_from_op_n(op: &All) -> i32 {
186 if op.eq(&all::OP_PUSHBYTES_0) {
187 0
188 } else if op.eq(&all::OP_PUSHNUM_NEG1) {
189 -1
190 } else {
191 op.into_u8() as i32 + 1 - all::OP_PUSHNUM_1.into_u8() as i32
192 }
193}
194
195#[inline]
199fn get_num_keys(op: &Instruction) -> Option<i32> {
200 match op {
201 PushBytes(_) => None,
202 Op(op) => {
203 if !(op.eq(&all::OP_PUSHNUM_NEG1)
204 || op.eq(&all::OP_PUSHBYTES_0)
205 || (op.ge(&all::OP_PUSHNUM_1) && all::OP_PUSHNUM_16.ge(op)))
206 {
207 None
208 } else {
209 Some(decode_from_op_n(op))
210 }
211 }
212 }
213}
214
215#[inline]
222fn p2pk_to_address(script: &Script) -> Option<Address> {
223 assert!(script.is_p2pk());
224 if let Some(Ok(Instruction::PushBytes(pk))) = script.instructions().next() {
225 let pkh = hash160::Hash::hash(pk);
227 Some(Address {
228 payload: Payload::PubkeyHash(PubkeyHash::from_slice(&pkh).ok()?),
229 network: Network::Bitcoin,
230 })
231 } else {
232 unreachable!()
233 }
234}
235
236trait Cmp {
237 fn ge(&self, other: &Self) -> bool;
238}
239
240impl Cmp for bitcoin::blockdata::opcodes::All {
241 fn ge(&self, other: &Self) -> bool {
242 self.into_u8() >= other.into_u8()
243 }
244}
245
246impl fmt::Display for ScriptType {
247 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
248 match *self {
249 ScriptType::OpReturn => write!(f, "OpReturn"),
250 ScriptType::Pay2MultiSig => write!(f, "Pay2MultiSig"),
251 ScriptType::Pay2PublicKey => write!(f, "Pay2PublicKey"),
252 ScriptType::Pay2PublicKeyHash => write!(f, "Pay2PublicKeyHash"),
253 ScriptType::Pay2ScriptHash => write!(f, "Pay2ScriptHash"),
254 ScriptType::Pay2WitnessPublicKeyHash => write!(f, "Pay2WitnessPublicKeyHash"),
255 ScriptType::Pay2WitnessScriptHash => write!(f, "Pay2WitnessScriptHash"),
256 ScriptType::WitnessProgram => write!(f, "WitnessProgram"),
257 ScriptType::Unspendable => write!(f, "Unspendable"),
258 ScriptType::NotRecognised => write!(f, "NotRecognised"),
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::{evaluate_script, ScriptType};
266 use bitcoin::hashes::hex::{FromHex, ToHex};
267 use bitcoin::{Network, Script};
268
269 #[test]
270 fn test_bitcoin_script_p2pkh() {
271 let bytes = [
274 0x76 as u8, 0xa9, 0x14, 0x12, 0xab, 0x8d, 0xc5, 0x88, 0xca, 0x9d, 0x57, 0x87, 0xdd,
275 0xe7, 0xeb, 0x29, 0x56, 0x9d, 0xa6, 0x3c, 0x3a, 0x23, 0x8c, 0x88, 0xac,
276 ];
277 let result = evaluate_script(
278 &Script::from_hex(&bytes.to_hex()).unwrap(),
279 Network::Bitcoin,
280 );
281 assert_eq!(
282 result.addresses.get(0).unwrap().to_string(),
283 String::from("12higDjoCCNXSA95xZMWUdPvXNmkAduhWv")
284 );
285 assert_eq!(result.pattern, ScriptType::Pay2PublicKeyHash);
286 }
287
288 #[test]
289 fn test_bitcoin_script_p2pk() {
290 let bytes = [
294 0x41 as u8, 0x04, 0x4b, 0xca, 0x63, 0x3a, 0x91, 0xde, 0x10, 0xdf, 0x85, 0xa6, 0x3d, 0x0a, 0x24,
296 0xcb, 0x09, 0x78, 0x31, 0x48, 0xfe, 0x0e, 0x16, 0xc9, 0x2e, 0x93, 0x7f, 0xc4, 0x49,
297 0x15, 0x80, 0xc8, 0x60, 0x75, 0x71, 0x48, 0xef, 0xfa, 0x05, 0x95, 0xa9, 0x55, 0xf4,
298 0x40, 0x78, 0xb4, 0x8b, 0xa6, 0x7f, 0xa1, 0x98, 0x78, 0x2e, 0x8b, 0xb6, 0x81, 0x15,
299 0xda, 0x0d, 0xaa, 0x8f, 0xde, 0x53, 0x01, 0xf7, 0xf9, 0xac,
300 ]; let result = evaluate_script(
302 &Script::from_hex(&bytes.to_hex()).unwrap(),
303 Network::Bitcoin,
304 );
305 assert_eq!(
306 result.addresses.get(0).unwrap().to_string(),
307 String::from("1LEWwJkDj8xriE87ALzQYcHjTmD8aqDj1f")
308 );
309 assert_eq!(result.pattern, ScriptType::Pay2PublicKey);
310 }
311
312 #[test]
313 fn test_bitcoin_script_p2ms() {
314 let bytes = [
319 0x52 as u8, 0x21, 0x02, 0x2d, 0xf8, 0x75, 0x04, 0x80, 0xad, 0x5b, 0x26, 0x95, 0x0b,
320 0x25, 0xc7, 0xba, 0x79, 0xd3, 0xe3, 0x7d, 0x75, 0xf6, 0x40, 0xf8, 0xe5, 0xd9, 0xbc,
321 0xd5, 0xb1, 0x50, 0xa0, 0xf8, 0x50, 0x14, 0xda, 0x21, 0x03, 0xe3, 0x81, 0x8b, 0x65,
322 0xbc, 0xc7, 0x3a, 0x7d, 0x64, 0x06, 0x41, 0x06, 0xa8, 0x59, 0xcc, 0x1a, 0x5a, 0x72,
323 0x8c, 0x43, 0x45, 0xff, 0x0b, 0x64, 0x12, 0x09, 0xfb, 0xa0, 0xd9, 0x0d, 0xe6, 0xe9,
324 0x21, 0x02, 0x1f, 0x2f, 0x6e, 0x1e, 0x50, 0xcb, 0x6a, 0x95, 0x39, 0x35, 0xc3, 0x60,
325 0x12, 0x84, 0x92, 0x5d, 0xec, 0xd3, 0xfd, 0x21, 0xbc, 0x44, 0x57, 0x12, 0x57, 0x68,
326 0x73, 0xfb, 0x8c, 0x6e, 0xbc, 0x18, 0x53, 0xae,
327 ];
328
329 let result = evaluate_script(
330 &Script::from_hex(&bytes.to_hex()).unwrap(),
331 Network::Bitcoin,
332 );
333 assert_eq!(result.pattern, ScriptType::Pay2MultiSig);
334 }
335
336 #[test]
337 fn test_bitcoin_script_p2sh() {
338 let bytes = [
341 0xa9 as u8, 0x14, 0xe9, 0xc3, 0xdd, 0x0c, 0x07, 0xaa, 0xc7, 0x61, 0x79, 0xeb, 0xc7, 0x6a, 0x6c, 0x78,
343 0xd4, 0xd6, 0x7c, 0x6c, 0x16, 0x0a, 0x87,
344 ]; let result = evaluate_script(
346 &Script::from_hex(&bytes.to_hex()).unwrap(),
347 Network::Bitcoin,
348 );
349 assert_eq!(
350 result.addresses.get(0).unwrap().to_string(),
351 String::from("3P14159f73E4gFr7JterCCQh9QjiTjiZrG")
352 );
353 assert_eq!(result.pattern, ScriptType::Pay2ScriptHash);
354 }
355
356 #[test]
357 fn test_bitcoin_script_non_standard() {
358 let bytes = [0x73 as u8, 0x63, 0x72, 0x69, 0x70, 0x74];
361 let result = evaluate_script(
362 &Script::from_hex(&bytes.to_hex()).unwrap(),
363 Network::Bitcoin,
364 );
365 assert_eq!(result.addresses.get(0), None);
366 assert_eq!(result.pattern, ScriptType::NotRecognised);
367 }
368
369 #[test]
370 fn test_bitcoin_bogus_script() {
371 let bytes = [0x4c as u8, 0xFF, 0x00];
372 let result = evaluate_script(
373 &Script::from_hex(&bytes.to_hex()).unwrap(),
374 Network::Bitcoin,
375 );
376 assert_eq!(result.addresses.get(0), None);
377 assert_eq!(result.pattern, ScriptType::NotRecognised);
378 }
379}