subcoin_script/
solver.rs

1use bitcoin::opcodes::all::{OP_CHECKMULTISIG, OP_PUSHNUM_1, OP_PUSHNUM_16};
2use bitcoin::script::Instruction;
3use bitcoin::{Opcode, PublicKey, Script, WitnessVersion};
4
5/// Transaction output types
6#[derive(Debug, PartialEq, Eq, Clone)]
7pub enum TxoutType {
8    NonStandard,
9    // anyone can spend script.
10    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    // unspendable OP_RETURN script that carries data.
20    NullData,
21    WitnessV0ScriptHash([u8; 32]),
22    WitnessV0KeyHash([u8; 20]),
23    WitnessV1Taproot([u8; 32]),
24    // Only for Witness versions not already defined above.
25    WitnessUnknown(Vec<Vec<u8>>),
26}
27
28impl TxoutType {
29    /// Returns the script type name as used in Bitcoin Core RPC responses.
30    pub fn script_type(&self) -> &'static str {
31        match self {
32            Self::NonStandard => "nonstandard",
33            Self::Anchor => "anchor",
34            Self::PubKey(_) => "pubkey",
35            Self::PubKeyHash(_) => "pubkeyhash",
36            Self::ScriptHash(_) => "scripthash",
37            Self::Multisig { .. } => "multisig",
38            Self::NullData => "nulldata",
39            Self::WitnessV0ScriptHash(_) => "witness_v0_scripthash",
40            Self::WitnessV0KeyHash(_) => "witness_v0_keyhash",
41            Self::WitnessV1Taproot(_) => "witness_v1_taproot",
42            Self::WitnessUnknown(_) => "witness_unknown",
43        }
44    }
45}
46
47pub fn solve(script_pubkey: &Script) -> TxoutType {
48    if script_pubkey.is_p2sh() {
49        let hash: [u8; 20] = script_pubkey.as_bytes()[2..22].try_into().unwrap();
50        return TxoutType::ScriptHash(hash);
51    }
52
53    if let Some(version) = script_pubkey.witness_version() {
54        let program = &script_pubkey.as_bytes()[2..];
55
56        match (version, program.len()) {
57            (WitnessVersion::V0, 20) => {
58                return TxoutType::WitnessV0KeyHash(program.try_into().unwrap());
59            }
60            (WitnessVersion::V0, 32) => {
61                return TxoutType::WitnessV0ScriptHash(program.try_into().unwrap());
62            }
63            (WitnessVersion::V1, 32) => {
64                return TxoutType::WitnessV1Taproot(program.try_into().unwrap());
65            }
66            _ => {}
67        }
68
69        if is_pay_to_anchor(script_pubkey) {
70            return TxoutType::Anchor;
71        }
72
73        if version != WitnessVersion::V0 {
74            return TxoutType::WitnessUnknown(vec![vec![version as u8], program.to_vec()]);
75        }
76
77        return TxoutType::NonStandard;
78    }
79
80    // TODO: check IsPushOnly(script_pubkey[0])
81    if script_pubkey.is_op_return() {
82        return TxoutType::NullData;
83    }
84
85    if let Some(pubkey) = script_pubkey.p2pk_public_key() {
86        return TxoutType::PubKey(pubkey);
87    }
88
89    if script_pubkey.is_p2pkh() {
90        return TxoutType::PubKeyHash(script_pubkey.as_bytes()[3..23].try_into().unwrap());
91    }
92
93    if let Some((required_sigs, keys_count, keys)) = match_multisig(script_pubkey) {
94        return TxoutType::Multisig {
95            required_sigs,
96            keys_count,
97            keys,
98        };
99    }
100
101    TxoutType::NonStandard
102}
103
104fn is_pay_to_anchor(script: &Script) -> bool {
105    let script_bytes = script.as_bytes();
106    script.len() == 4
107        && script_bytes[0] == OP_PUSHNUM_1.to_u8()
108        && script_bytes[1] == 0x02
109        && script_bytes[2] == 0x4e
110        && script_bytes[3] == 0x73
111}
112
113/// Checks whether a script pubkey is a bare multisig output.
114///
115/// In a bare multisig pubkey script the keys are not hashed, the script
116/// is of the form:
117///
118///    `2 <pubkey1> <pubkey2> <pubkey3> 3 OP_CHECKMULTISIG`
119#[inline]
120fn match_multisig(script_pubkey: &Script) -> Option<(u8, u8, Vec<Vec<u8>>)> {
121    let mut instructions = script_pubkey.instructions();
122
123    let required_sigs = if let Ok(Instruction::Op(op)) = instructions.next()? {
124        decode_pushnum(op)?
125    } else {
126        return None;
127    };
128
129    let mut keys = Vec::new();
130    while let Some(Ok(instruction)) = instructions.next() {
131        match instruction {
132            Instruction::PushBytes(key) => {
133                keys.push(key.as_bytes().to_vec());
134            }
135            Instruction::Op(op) => {
136                if let Some(pushnum) = decode_pushnum(op) {
137                    if pushnum as usize != keys.len() {
138                        return None;
139                    }
140                }
141                break;
142            }
143        }
144    }
145
146    let num_pubkeys: u8 = keys
147        .len()
148        .try_into()
149        .expect("Must fit into u8 due to the MAX_PUBKEYS_PER_MULTISIG limit; qed");
150
151    if required_sigs > num_pubkeys {
152        return None;
153    }
154
155    if let Ok(Instruction::Op(op)) = instructions.next()? {
156        if op != OP_CHECKMULTISIG {
157            return None;
158        }
159    }
160
161    if instructions.next().is_none() {
162        Some((required_sigs, num_pubkeys, keys))
163    } else {
164        None
165    }
166}
167
168fn decode_pushnum(opcode: Opcode) -> Option<u8> {
169    if (OP_PUSHNUM_1.to_u8()..=OP_PUSHNUM_16.to_u8()).contains(&opcode.to_u8()) {
170        Some(opcode.to_u8() - 0x50)
171    } else {
172        None
173    }
174}