btc_transaction_utils/
multisig.rs1use bitcoin::{
22 blockdata::{
23 opcodes::{all::OP_CHECKMULTISIG, Class},
24 script::{read_uint, Builder, Instruction, Script},
25 },
26 util::psbt::serialize::Serialize,
27 PublicKey,
28};
29use hex;
30use thiserror::Error;
31
32use std::{fmt, str::FromStr};
33
34#[derive(Debug, PartialEq, Clone)]
36pub struct RedeemScript(pub(crate) Script);
37
38impl RedeemScript {
39 pub fn from_script(script: Script) -> Result<RedeemScript, RedeemScriptError> {
42 RedeemScriptContent::parse(&script)?;
43 Ok(RedeemScript(script))
44 }
45
46 pub fn content(&self) -> RedeemScriptContent {
48 RedeemScriptContent::parse(&self.0).unwrap()
49 }
50}
51
52impl fmt::Display for RedeemScript {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 fmt::LowerHex::fmt(&self.0, f)
55 }
56}
57
58impl FromStr for RedeemScript {
59 type Err = anyhow::Error;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 let script = Script::from(hex::decode(s)?);
63 RedeemScript::from_script(script).map_err(Into::into)
64 }
65}
66
67impl From<&'static str> for RedeemScript {
68 fn from(s: &'static str) -> RedeemScript {
69 RedeemScript::from_str(s).unwrap()
70 }
71}
72
73impl From<RedeemScript> for Script {
74 fn from(s: RedeemScript) -> Script {
75 s.0
76 }
77}
78
79impl AsRef<Script> for RedeemScript {
80 fn as_ref(&self) -> &Script {
81 &self.0
82 }
83}
84
85impl ::serde::Serialize for RedeemScript {
86 fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
87 where
88 S: ::serde::Serializer,
89 {
90 ::serde_str::serialize(self, ser)
91 }
92}
93
94impl<'de> ::serde::Deserialize<'de> for RedeemScript {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: ::serde::Deserializer<'de>,
98 {
99 ::serde_str::deserialize(deserializer)
100 }
101}
102
103#[derive(Debug, PartialEq)]
105pub struct RedeemScriptContent {
106 pub public_keys: Vec<PublicKey>,
108 pub quorum: usize,
111}
112
113impl RedeemScriptContent {
114 pub fn parse(script: &Script) -> Result<RedeemScriptContent, RedeemScriptError> {
117 fn read_usize(instruction: Instruction) -> Option<usize> {
118 match instruction {
119 Instruction::Op(op) => {
120 if let Class::PushNum(num) = op.classify() {
121 Some(num as usize)
122 } else {
123 None
124 }
125 }
126 Instruction::PushBytes(data) => {
127 let num = read_uint(data, data.len()).ok()?;
128 Some(num as usize)
129 }
130 _ => None,
131 }
132 };
133
134 let mut instructions = script.iter(true).peekable();
135 let quorum = instructions
137 .next()
138 .and_then(read_usize)
139 .ok_or_else(|| RedeemScriptError::NoQuorum)?;
140 let public_keys = {
141 let mut public_keys = Vec::new();
143 while let Some(Instruction::PushBytes(slice)) = instructions.peek().cloned() {
144 if slice.len() == 1 {
147 break;
148 }
149 let pub_key =
151 PublicKey::from_slice(slice).map_err(|_| RedeemScriptError::NotStandard)?;
152 public_keys.push(pub_key);
153 instructions.next();
154 }
155 let public_keys_len = instructions
157 .next()
158 .and_then(read_usize)
159 .ok_or_else(|| RedeemScriptError::NotStandard)?;
160 ensure!(
161 public_keys.len() == public_keys_len,
162 RedeemScriptError::NotEnoughPublicKeys
163 );
164 ensure!(
165 Some(Instruction::Op(OP_CHECKMULTISIG)) == instructions.next(),
166 RedeemScriptError::NotStandard
167 );
168 public_keys
169 };
170 Ok(RedeemScriptContent {
172 quorum,
173 public_keys,
174 })
175 }
176}
177
178#[derive(Debug)]
180pub struct RedeemScriptBuilder(RedeemScriptContent);
181
182impl RedeemScriptBuilder {
183 pub fn new() -> RedeemScriptBuilder {
185 RedeemScriptBuilder(RedeemScriptContent {
186 quorum: 0,
187 public_keys: Vec::default(),
188 })
189 }
190
191 pub fn with_quorum(quorum: usize) -> RedeemScriptBuilder {
193 RedeemScriptBuilder(RedeemScriptContent {
194 quorum,
195 public_keys: Vec::default(),
196 })
197 }
198
199 pub fn with_public_keys<I: IntoIterator<Item = PublicKey>>(
201 public_keys: I,
202 ) -> RedeemScriptBuilder {
203 let public_keys = public_keys.into_iter().collect::<Vec<_>>();
204 let quorum = public_keys.len();
205
206 RedeemScriptBuilder(RedeemScriptContent {
207 public_keys,
208 quorum,
209 })
210 }
211
212 pub fn public_key<K: Into<PublicKey>>(&mut self, pub_key: K) -> &mut RedeemScriptBuilder {
214 self.0.public_keys.push(pub_key.into());
215 self
216 }
217
218 pub fn quorum(&mut self, quorum: usize) -> &mut RedeemScriptBuilder {
220 self.0.quorum = quorum;
221 self
222 }
223
224 pub fn to_script(&self) -> Result<RedeemScript, RedeemScriptError> {
226 let total_count = self.0.public_keys.len();
227 ensure!(self.0.quorum > 0, RedeemScriptError::NoQuorum);
229 ensure!(total_count != 0, RedeemScriptError::NotEnoughPublicKeys);
230 ensure!(
231 total_count >= self.0.quorum,
232 RedeemScriptError::IncorrectQuorum
233 );
234 let mut builder = Builder::default().push_int(self.0.quorum as i64);
237 let compressed_keys = self.0.public_keys.iter().map(Serialize::serialize);
238 for key in compressed_keys {
239 builder = builder.push_slice(key.as_ref());
240 }
241 let inner = builder
242 .push_int(total_count as i64)
243 .push_opcode(OP_CHECKMULTISIG)
244 .into_script();
245 Ok(RedeemScript(inner))
246 }
247}
248
249impl Default for RedeemScriptBuilder {
250 fn default() -> Self {
251 RedeemScriptBuilder::new()
252 }
253}
254
255#[derive(Debug, Copy, Clone, Error, PartialEq)]
257pub enum RedeemScriptError {
258 #[error("Not enough keys for the quorum.")]
260 IncorrectQuorum,
261 #[error("Quorum was not set.")]
263 NoQuorum,
264 #[error("Not enough public keys. At least one public key must be specified.")]
266 NotEnoughPublicKeys,
267 #[error("Given script is not the standard redeem script.")]
269 NotStandard,
270}
271
272#[cfg(test)]
273mod tests {
274 use std::str::FromStr;
275
276 use crate::{
277 multisig::{RedeemScript, RedeemScriptBuilder, RedeemScriptError},
278 test_data::keypair_from_wif,
279 };
280
281 #[test]
282 fn test_redeem_script_builder_no_quorum() {
283 assert_eq!(
284 RedeemScriptBuilder::with_quorum(0).to_script(),
285 Err(RedeemScriptError::NoQuorum)
286 );
287 }
288
289 #[test]
290 fn test_redeem_script_builder_not_enough_keys() {
291 assert_eq!(
292 RedeemScriptBuilder::with_quorum(3).to_script(),
293 Err(RedeemScriptError::NotEnoughPublicKeys)
294 );
295 }
296
297 #[test]
298 fn test_redeem_script_builder_incorrect_quorum() {
299 let keys = vec![
300 "cPHmynxvqfr7sXsJcohiGzoPGBShggxL6VWUdW14skohFZ1LQoeV",
301 "cTtSTL1stvg2tmK349WTmQDfHLMLqkkxwuo8ZJeQov9zEhtYtb4u",
302 ]
303 .into_iter()
304 .map(|wif| keypair_from_wif(wif).0)
305 .collect::<Vec<_>>();
306
307 assert_eq!(
308 RedeemScriptBuilder::with_public_keys(keys)
309 .quorum(3)
310 .to_script(),
311 Err(RedeemScriptError::IncorrectQuorum)
312 );
313 }
314
315 #[test]
316 fn test_redeem_script_from_hex_standard_short() {
317 RedeemScript::from(
318 "5321027db7837e51888e94c094703030d162c682c8dba312210f44ff440fbd5e5c24732102bdd272891c9\
319 e4dfc3962b1fdffd5a59732019816f9db4833634dbdaf01a401a52103280883dc31ccaee34218819aaa24\
320 5480c35a33acd91283586ff6d1284ed681e52103e2bc790a6e32bf5a766919ff55b1f9e9914e13aed84f5\
321 02c0e4171976e19deb054ae",
322 );
323 }
324
325 #[test]
326 fn test_redeem_script_from_hex_standard_long() {
327 RedeemScript::from(
328 "5c21031cf96b4fef362af7d86ee6c7159fa89485730dac8e3090163dd0c282dbc84f2221028839757bba9\
329 bdf46ae553c124479e5c3ded609495f3e93e88ab23c0f559e8be521035c70ffb21d1b454ec650e511e76f6\
330 bd3fe76f49c471522ee187abac8d0131a18210234acd7dee22bc23688beed0c7e42c0930cfe024204b7298\
331 b0b59d0e76a46476521033897e8dd88ee04cb42b69838c3167471880da23944c10eb9f67de2b5ca32a9d12\
332 1027a715cf0aeec55482c1d42bfeb75c8f54348ec8b0ca0f9b535ed50a739b8ad632103a2be0380e248ec3\
333 6401e99680e0fb4f8c03a0a5e00d5dda107aee6cba77b639521038bdb47da82981776e8b0e5d4175f27930\
334 339a32e77ee7052ec51a1f2f0a46e88210312c4fb516caeb5eaec8ffdeecd4a507b69d6808651ae02a4a61\
335 165cc56bfe55121039e021ca4d7969e5db181e0905b9baab2afe395e84587b588a6b039207c91135521025\
336 9c9f752846c7bd514a042d53ea305f2d4ca7873cb21937dc6b5e82afbb8fb922102c52c3dc6e080ea4e74b\
337 a2e6797548bd79a692a01baeba1c757a18fd0ef519fb42102f5010ab66dd7a8dc06caefeceb9bb7e6e42c5\
338 d4afdab527a2f02d87b758920612103efbcec8bcc6ea4e58b44214b14eae2677399c28df8bb81fcd120cb4\
339 c88ce3bd92103e88aa50f0d7f43cb3171a69675385f130c6abafacadde87fc84d5a194da5ad9c21025ed88\
340 603b59882c3ec6ef43c0b33ac9db315ecca8e7073e60d9b56145fc0efa02103643277862c4a8ab27913e3d\
341 2bcea109b6637c7454a03410aac8ccad445e81a502103380785c3e1c105e366ff445227cdde68e6a6461d6\
342 793a1437db847ecd04129dc0112ae",
343 );
344 }
345
346 #[test]
347 fn test_redeem_script_from_hex_not_standard() {
348 assert!(
349 "0020e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
350 .parse::<RedeemScript>()
351 .is_err()
352 );
353 }
354
355 #[test]
356 fn test_redeem_script_convert_hex() {
357 let public_keys = vec![
358 "cMs8EwSJwfQ5DrVqYcDgjKV52k3DrGZhK1MDNrabY16WxPjvACgG",
359 "cVwwcsdqRGV1cV1HLX1y7ccg2iu7aSHvSVRW3sPZpgZGr6Wzg9VR",
360 "cNqiotwcBrkLsFMC5wwehvSQ6CcjXu74U4mEeZn6vx3ZLYH2k3QY",
361 "cSAyWaxS6SwWQ5REE1LuNp1Vqi771JsTFRU1ZisUHkKRiYLg6grq",
362 ]
363 .into_iter()
364 .map(|wif| keypair_from_wif(wif).0)
365 .collect::<Vec<_>>();
366
367 let script = RedeemScriptBuilder::with_quorum(3)
368 .public_key(public_keys[0])
369 .public_key(public_keys[1])
370 .public_key(public_keys[2])
371 .public_key(public_keys[3])
372 .to_script()
373 .unwrap();
374 let string = script.to_string();
375 let script2 = RedeemScript::from_str(&string).unwrap();
376 assert_eq!(script, script2);
377 }
378}