1use crate::opcodes::codes::{OpCheckMultiSig, OpCheckMultiSigECDSA};
2use crate::script_builder::{ScriptBuilder, ScriptBuilderError};
3use std::borrow::Borrow;
4use thiserror::Error;
5
6#[derive(Error, PartialEq, Eq, Debug, Clone)]
7pub enum Error {
8 #[error("too many required signatures")]
12 ErrTooManyRequiredSigs,
13 #[error(transparent)]
14 ScriptBuilderError(#[from] ScriptBuilderError),
15 #[error("provided public keys should not be empty")]
16 EmptyKeys,
17}
18pub fn multisig_redeem_script(pub_keys: impl Iterator<Item = impl Borrow<[u8; 32]>>, required: usize) -> Result<Vec<u8>, Error> {
19 if pub_keys.size_hint().1.is_some_and(|upper| upper < required) {
20 return Err(Error::ErrTooManyRequiredSigs);
21 }
22 let mut builder = ScriptBuilder::new();
23 builder.add_i64(required as i64)?;
24
25 let mut count = 0i64;
26 for pub_key in pub_keys {
27 count += 1;
28 builder.add_data(pub_key.borrow().as_slice())?;
29 }
30
31 if (count as usize) < required {
32 return Err(Error::ErrTooManyRequiredSigs);
33 }
34 if count == 0 {
35 return Err(Error::EmptyKeys);
36 }
37
38 builder.add_i64(count)?;
39 builder.add_op(OpCheckMultiSig)?;
40
41 Ok(builder.drain())
42}
43
44pub fn multisig_redeem_script_ecdsa(pub_keys: impl Iterator<Item = impl Borrow<[u8; 33]>>, required: usize) -> Result<Vec<u8>, Error> {
45 if pub_keys.size_hint().1.is_some_and(|upper| upper < required) {
46 return Err(Error::ErrTooManyRequiredSigs);
47 }
48 let mut builder = ScriptBuilder::new();
49 builder.add_i64(required as i64)?;
50
51 let mut count = 0i64;
52 for pub_key in pub_keys {
53 count += 1;
54 builder.add_data(pub_key.borrow().as_slice())?;
55 }
56
57 if (count as usize) < required {
58 return Err(Error::ErrTooManyRequiredSigs);
59 }
60 if count == 0 {
61 return Err(Error::EmptyKeys);
62 }
63
64 builder.add_i64(count)?;
65 builder.add_op(OpCheckMultiSigECDSA)?;
66
67 Ok(builder.drain())
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::{caches::Cache, opcodes::codes::OpData65, pay_to_script_hash_script, TxScriptEngine};
74 use core::str::FromStr;
75 use kaspa_consensus_core::{
76 hashing::{
77 sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValues},
78 sighash_type::SIG_HASH_ALL,
79 },
80 subnets::SubnetworkId,
81 tx::*,
82 };
83 use rand::thread_rng;
84 use secp256k1::Keypair;
85 use std::{iter, iter::empty};
86
87 struct Input {
88 kp: Keypair,
89 required: bool,
90 sign: bool,
91 }
92
93 fn kp() -> [Keypair; 3] {
94 let kp1 = Keypair::from_seckey_slice(
95 secp256k1::SECP256K1,
96 hex::decode("1d99c236b1f37b3b845336e6c568ba37e9ced4769d83b7a096eec446b940d160").unwrap().as_slice(),
97 )
98 .unwrap();
99 let kp2 = Keypair::from_seckey_slice(
100 secp256k1::SECP256K1,
101 hex::decode("349ca0c824948fed8c2c568ce205e9d9be4468ef099cad76e3e5ec918954aca4").unwrap().as_slice(),
102 )
103 .unwrap();
104 let kp3 = Keypair::new(secp256k1::SECP256K1, &mut thread_rng());
105 [kp1, kp2, kp3]
106 }
107
108 #[test]
109 fn test_too_many_required_sigs() {
110 let result = multisig_redeem_script(iter::once([0u8; 32]), 2);
111 assert_eq!(result, Err(Error::ErrTooManyRequiredSigs));
112 let result = multisig_redeem_script_ecdsa(iter::once(&[0u8; 33]), 2);
113 assert_eq!(result, Err(Error::ErrTooManyRequiredSigs));
114 }
115
116 #[test]
117 fn test_empty_keys() {
118 let result = multisig_redeem_script(empty::<[u8; 32]>(), 0);
119 assert_eq!(result, Err(Error::EmptyKeys));
120 }
121
122 fn check_multisig_scenario(inputs: Vec<Input>, required: usize, is_ok: bool, is_ecdsa: bool) {
123 let prev_tx_id = TransactionId::from_str("63020db736215f8b1105a9281f7bcbb6473d965ecc45bb2fb5da59bd35e6ff84").unwrap();
125 let filtered = inputs.iter().filter(|input| input.required);
126 let script = if !is_ecdsa {
127 let pks = filtered.map(|input| input.kp.x_only_public_key().0.serialize());
128 multisig_redeem_script(pks, required).unwrap()
129 } else {
130 let pks = filtered.map(|input| input.kp.public_key().serialize());
131 multisig_redeem_script_ecdsa(pks, required).unwrap()
132 };
133
134 let tx = Transaction::new(
135 0,
136 vec![TransactionInput {
137 previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 0 },
138 signature_script: vec![],
139 sequence: 0,
140 sig_op_count: 4,
141 }],
142 vec![],
143 0,
144 SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
145 0,
146 vec![],
147 );
148
149 let entries = vec![UtxoEntry {
150 amount: 12793000000000,
151 script_public_key: pay_to_script_hash_script(&script),
152 block_daa_score: 36151168,
153 is_coinbase: false,
154 }];
155 let mut tx = MutableTransaction::with_entries(tx, entries);
156
157 let mut reused_values = SigHashReusedValues::new();
158 let sig_hash = if !is_ecdsa {
159 calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values)
160 } else {
161 calc_ecdsa_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values)
162 };
163 let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
164 let signatures: Vec<_> = inputs
165 .iter()
166 .filter(|input| input.sign)
167 .flat_map(|input| {
168 if !is_ecdsa {
169 let sig = *input.kp.sign_schnorr(msg).as_ref();
170 iter::once(OpData65).chain(sig).chain([SIG_HASH_ALL.to_u8()])
171 } else {
172 let sig = input.kp.secret_key().sign_ecdsa(msg).serialize_compact();
173 iter::once(OpData65).chain(sig).chain([SIG_HASH_ALL.to_u8()])
174 }
175 })
176 .collect();
177
178 {
179 tx.tx.inputs[0].signature_script =
180 signatures.into_iter().chain(ScriptBuilder::new().add_data(&script).unwrap().drain()).collect();
181 }
182
183 let tx = tx.as_verifiable();
184 let (input, entry) = tx.populated_inputs().next().unwrap();
185
186 let cache = Cache::new(10_000);
187 let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &mut reused_values, &cache).unwrap();
188 assert_eq!(engine.execute().is_ok(), is_ok);
189 }
190 #[test]
191 fn test_multisig_1_2() {
192 let [kp1, kp2, ..] = kp();
193 check_multisig_scenario(
194 vec![Input { kp: kp1, required: true, sign: false }, Input { kp: kp2, required: true, sign: true }],
195 1,
196 true,
197 false,
198 );
199 let [kp1, kp2, ..] = kp();
200 check_multisig_scenario(
201 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: false }],
202 1,
203 true,
204 false,
205 );
206
207 check_multisig_scenario(
209 vec![Input { kp: kp1, required: true, sign: false }, Input { kp: kp2, required: true, sign: true }],
210 1,
211 true,
212 true,
213 );
214 let [kp1, kp2, ..] = kp();
215 check_multisig_scenario(
216 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: false }],
217 1,
218 true,
219 true,
220 );
221 }
222
223 #[test]
224 fn test_multisig_2_2() {
225 let [kp1, kp2, ..] = kp();
226 check_multisig_scenario(
227 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: true }],
228 2,
229 true,
230 false,
231 );
232
233 let [kp1, kp2, ..] = kp();
235 check_multisig_scenario(
236 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: true }],
237 2,
238 true,
239 true,
240 );
241 }
242
243 #[test]
244 fn test_multisig_wrong_signer() {
245 let [kp1, kp2, kp3] = kp();
246 check_multisig_scenario(
247 vec![
248 Input { kp: kp1, required: true, sign: false },
249 Input { kp: kp2, required: true, sign: false },
250 Input { kp: kp3, required: false, sign: true },
251 ],
252 1,
253 false,
254 false,
255 );
256
257 let [kp1, kp2, kp3] = kp();
259 check_multisig_scenario(
260 vec![
261 Input { kp: kp1, required: true, sign: false },
262 Input { kp: kp2, required: true, sign: false },
263 Input { kp: kp3, required: false, sign: true },
264 ],
265 1,
266 false,
267 true,
268 );
269 }
270
271 #[test]
272 fn test_multisig_not_enough() {
273 let [kp1, kp2, kp3] = kp();
274 check_multisig_scenario(
275 vec![
276 Input { kp: kp1, required: true, sign: true },
277 Input { kp: kp2, required: true, sign: true },
278 Input { kp: kp3, required: true, sign: false },
279 ],
280 3,
281 false,
282 false,
283 );
284
285 let [kp1, kp2, kp3] = kp();
286 check_multisig_scenario(
287 vec![
288 Input { kp: kp1, required: true, sign: true },
289 Input { kp: kp2, required: true, sign: true },
290 Input { kp: kp3, required: true, sign: false },
291 ],
292 3,
293 false,
294 true,
295 );
296 }
297}