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
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    // TODO: check IsPushOnly(script_pubkey[0])
62    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/// Checks whether a script pubkey is a bare multisig output.
95///
96/// In a bare multisig pubkey script the keys are not hashed, the script
97/// is of the form:
98///
99///    `2 <pubkey1> <pubkey2> <pubkey3> 3 OP_CHECKMULTISIG`
100#[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}