rustywallet_multisig/
script.rs1use crate::config::MultisigConfig;
4
5pub mod opcodes {
7 pub const OP_0: u8 = 0x00;
8 pub const OP_1: u8 = 0x51;
9 pub const OP_16: u8 = 0x60;
10 pub const OP_CHECKMULTISIG: u8 = 0xae;
11 pub const OP_HASH160: u8 = 0xa9;
12 pub const OP_EQUAL: u8 = 0x87;
13}
14
15pub fn build_multisig_script(config: &MultisigConfig) -> Vec<u8> {
19 let mut script = Vec::new();
20
21 script.push(number_to_opcode(config.threshold()));
23
24 for pubkey in config.public_keys() {
26 script.push(33); script.extend_from_slice(pubkey);
28 }
29
30 script.push(number_to_opcode(config.total()));
32
33 script.push(opcodes::OP_CHECKMULTISIG);
35
36 script
37}
38
39pub fn build_p2sh_script_pubkey(script_hash: &[u8; 20]) -> Vec<u8> {
43 let mut script = Vec::with_capacity(23);
44 script.push(opcodes::OP_HASH160);
45 script.push(20); script.extend_from_slice(script_hash);
47 script.push(opcodes::OP_EQUAL);
48 script
49}
50
51pub fn build_p2wsh_script_pubkey(script_hash: &[u8; 32]) -> Vec<u8> {
55 let mut script = Vec::with_capacity(34);
56 script.push(opcodes::OP_0);
57 script.push(32); script.extend_from_slice(script_hash);
59 script
60}
61
62pub fn build_p2sh_p2wsh_redeem_script(witness_script_hash: &[u8; 32]) -> Vec<u8> {
66 build_p2wsh_script_pubkey(witness_script_hash)
67}
68
69fn number_to_opcode(n: u8) -> u8 {
71 if n == 0 {
72 opcodes::OP_0
73 } else if n <= 16 {
74 opcodes::OP_1 + n - 1
75 } else {
76 panic!("Number too large for opcode: {}", n);
79 }
80}
81
82pub fn parse_multisig_script(script: &[u8]) -> Option<(u8, u8, Vec<[u8; 33]>)> {
84 if script.len() < 3 {
85 return None;
86 }
87
88 let m = opcode_to_number(script[0])?;
90 if m == 0 || m > 15 {
91 return None;
92 }
93
94 let mut pos = 1;
96 let mut pubkeys = Vec::new();
97
98 while pos < script.len() {
99 let byte = script[pos];
100
101 if (opcodes::OP_1..=opcodes::OP_16).contains(&byte) {
103 break;
104 }
105
106 if byte != 33 {
108 return None;
109 }
110
111 if pos + 34 > script.len() {
112 return None;
113 }
114
115 let mut pubkey = [0u8; 33];
116 pubkey.copy_from_slice(&script[pos + 1..pos + 34]);
117 pubkeys.push(pubkey);
118 pos += 34;
119 }
120
121 if pos >= script.len() {
122 return None;
123 }
124
125 let n = opcode_to_number(script[pos])?;
127 if n as usize != pubkeys.len() {
128 return None;
129 }
130
131 if pos + 1 >= script.len() || script[pos + 1] != opcodes::OP_CHECKMULTISIG {
133 return None;
134 }
135
136 Some((m, n, pubkeys))
137}
138
139fn opcode_to_number(opcode: u8) -> Option<u8> {
141 if opcode == opcodes::OP_0 {
142 Some(0)
143 } else if (opcodes::OP_1..=opcodes::OP_16).contains(&opcode) {
144 Some(opcode - opcodes::OP_1 + 1)
145 } else {
146 None
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::config::MultisigConfig;
154
155 fn make_pubkey(seed: u8) -> [u8; 33] {
156 let mut key = [seed; 33];
157 key[0] = 0x02;
158 key
159 }
160
161 #[test]
162 fn test_build_2_of_3_script() {
163 let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
164 let config = MultisigConfig::new(2, keys).unwrap();
165 let script = build_multisig_script(&config);
166
167 assert_eq!(script.len(), 105);
169 assert_eq!(script[0], opcodes::OP_1 + 1); assert_eq!(script[script.len() - 2], opcodes::OP_1 + 2); assert_eq!(script[script.len() - 1], opcodes::OP_CHECKMULTISIG);
172 }
173
174 #[test]
175 fn test_parse_multisig_script() {
176 let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
177 let config = MultisigConfig::new(2, keys.clone()).unwrap();
178 let script = build_multisig_script(&config);
179
180 let (m, n, parsed_keys) = parse_multisig_script(&script).unwrap();
181 assert_eq!(m, 2);
182 assert_eq!(n, 3);
183 assert_eq!(parsed_keys.len(), 3);
184 }
185
186 #[test]
187 fn test_p2sh_script_pubkey() {
188 let hash = [0xab; 20];
189 let script = build_p2sh_script_pubkey(&hash);
190
191 assert_eq!(script.len(), 23);
192 assert_eq!(script[0], opcodes::OP_HASH160);
193 assert_eq!(script[1], 20);
194 assert_eq!(script[22], opcodes::OP_EQUAL);
195 }
196
197 #[test]
198 fn test_p2wsh_script_pubkey() {
199 let hash = [0xcd; 32];
200 let script = build_p2wsh_script_pubkey(&hash);
201
202 assert_eq!(script.len(), 34);
203 assert_eq!(script[0], opcodes::OP_0);
204 assert_eq!(script[1], 32);
205 }
206}