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::{EngineContext, TxScriptEngine, caches::Cache, opcodes::codes::OpData65, pay_to_script_hash_script};
74 use core::str::FromStr;
75 use kaspa_consensus_core::{
76 hashing::{
77 sighash::{SigHashReusedValuesUnsync, calc_ecdsa_signature_hash, calc_schnorr_signature_hash},
78 sighash_type::SIG_HASH_ALL,
79 },
80 subnets::SubnetworkId,
81 tx::*,
82 };
83 use kaspa_utils::hex::FromHex;
84 use rand::thread_rng;
85 use secp256k1::Keypair;
86 use std::{iter, iter::empty};
87
88 struct Input {
89 kp: Keypair,
90 required: bool,
91 sign: bool,
92 }
93
94 fn kp() -> [Keypair; 3] {
95 let kp1 = Keypair::from_seckey_slice(
96 secp256k1::SECP256K1,
97 Vec::from_hex("1d99c236b1f37b3b845336e6c568ba37e9ced4769d83b7a096eec446b940d160").unwrap().as_slice(),
98 )
99 .unwrap();
100 let kp2 = Keypair::from_seckey_slice(
101 secp256k1::SECP256K1,
102 Vec::from_hex("349ca0c824948fed8c2c568ce205e9d9be4468ef099cad76e3e5ec918954aca4").unwrap().as_slice(),
103 )
104 .unwrap();
105 let kp3 = Keypair::new(secp256k1::SECP256K1, &mut thread_rng());
106 [kp1, kp2, kp3]
107 }
108
109 #[test]
110 fn test_too_many_required_sigs() {
111 let result = multisig_redeem_script(iter::once([0u8; 32]), 2);
112 assert_eq!(result, Err(Error::ErrTooManyRequiredSigs));
113 let result = multisig_redeem_script_ecdsa(iter::once(&[0u8; 33]), 2);
114 assert_eq!(result, Err(Error::ErrTooManyRequiredSigs));
115 }
116
117 #[test]
118 fn test_empty_keys() {
119 let result = multisig_redeem_script(empty::<[u8; 32]>(), 0);
120 assert_eq!(result, Err(Error::EmptyKeys));
121 }
122
123 fn check_multisig_scenario(inputs: Vec<Input>, required: usize, is_ok: bool, is_ecdsa: bool) {
124 let prev_tx_id = TransactionId::from_str("63020db736215f8b1105a9281f7bcbb6473d965ecc45bb2fb5da59bd35e6ff84").unwrap();
126 let filtered = inputs.iter().filter(|input| input.required);
127 let script = if !is_ecdsa {
128 let pks = filtered.map(|input| input.kp.x_only_public_key().0.serialize());
129 multisig_redeem_script(pks, required).unwrap()
130 } else {
131 let pks = filtered.map(|input| input.kp.public_key().serialize());
132 multisig_redeem_script_ecdsa(pks, required).unwrap()
133 };
134
135 let tx = Transaction::new(
136 0,
137 vec![TransactionInput {
138 previous_outpoint: TransactionOutpoint { transaction_id: prev_tx_id, index: 0 },
139 signature_script: vec![],
140 sequence: 0,
141 compute_commit: ComputeCommit::SigopCount(4.into()),
142 }],
143 vec![],
144 0,
145 SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
146 0,
147 vec![],
148 );
149
150 let entries = vec![UtxoEntry {
151 amount: 12793000000000,
152 script_public_key: pay_to_script_hash_script(&script),
153 block_daa_score: 36151168,
154 is_coinbase: false,
155 covenant_id: None,
156 }];
157 let mut tx = MutableTransaction::with_entries(tx, entries);
158
159 let reused_values = SigHashReusedValuesUnsync::new();
160 let sig_hash = if !is_ecdsa {
161 calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values)
162 } else {
163 calc_ecdsa_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values)
164 };
165 let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
166 let signatures: Vec<_> = inputs
167 .iter()
168 .filter(|input| input.sign)
169 .flat_map(|input| {
170 if !is_ecdsa {
171 let sig = *input.kp.sign_schnorr(msg).as_ref();
172 iter::once(OpData65).chain(sig).chain([SIG_HASH_ALL.to_u8()])
173 } else {
174 let sig = input.kp.secret_key().sign_ecdsa(msg).serialize_compact();
175 iter::once(OpData65).chain(sig).chain([SIG_HASH_ALL.to_u8()])
176 }
177 })
178 .collect();
179
180 {
181 tx.tx.inputs[0].signature_script =
182 signatures.into_iter().chain(ScriptBuilder::new().add_data(&script).unwrap().drain()).collect();
183 }
184
185 let tx = tx.as_verifiable();
186 let (input, entry) = tx.populated_inputs().next().unwrap();
187
188 let cache = Cache::new(10_000);
189 let ctx = EngineContext::new(&cache).with_reused(&reused_values);
190 let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, ctx, Default::default());
191 assert_eq!(engine.execute().is_ok(), is_ok);
192 }
193 #[test]
194 fn test_multisig_1_2() {
195 let [kp1, kp2, ..] = kp();
196 check_multisig_scenario(
197 vec![Input { kp: kp1, required: true, sign: false }, Input { kp: kp2, required: true, sign: true }],
198 1,
199 true,
200 false,
201 );
202 let [kp1, kp2, ..] = kp();
203 check_multisig_scenario(
204 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: false }],
205 1,
206 true,
207 false,
208 );
209
210 check_multisig_scenario(
212 vec![Input { kp: kp1, required: true, sign: false }, Input { kp: kp2, required: true, sign: true }],
213 1,
214 true,
215 true,
216 );
217 let [kp1, kp2, ..] = kp();
218 check_multisig_scenario(
219 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: false }],
220 1,
221 true,
222 true,
223 );
224 }
225
226 #[test]
227 fn test_multisig_2_2() {
228 let [kp1, kp2, ..] = kp();
229 check_multisig_scenario(
230 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: true }],
231 2,
232 true,
233 false,
234 );
235
236 let [kp1, kp2, ..] = kp();
238 check_multisig_scenario(
239 vec![Input { kp: kp1, required: true, sign: true }, Input { kp: kp2, required: true, sign: true }],
240 2,
241 true,
242 true,
243 );
244 }
245
246 #[test]
247 fn test_multisig_wrong_signer() {
248 let [kp1, kp2, kp3] = kp();
249 check_multisig_scenario(
250 vec![
251 Input { kp: kp1, required: true, sign: false },
252 Input { kp: kp2, required: true, sign: false },
253 Input { kp: kp3, required: false, sign: true },
254 ],
255 1,
256 false,
257 false,
258 );
259
260 let [kp1, kp2, kp3] = kp();
262 check_multisig_scenario(
263 vec![
264 Input { kp: kp1, required: true, sign: false },
265 Input { kp: kp2, required: true, sign: false },
266 Input { kp: kp3, required: false, sign: true },
267 ],
268 1,
269 false,
270 true,
271 );
272 }
273
274 #[test]
275 fn test_multisig_not_enough() {
276 let [kp1, kp2, kp3] = kp();
277 check_multisig_scenario(
278 vec![
279 Input { kp: kp1, required: true, sign: true },
280 Input { kp: kp2, required: true, sign: true },
281 Input { kp: kp3, required: true, sign: false },
282 ],
283 3,
284 false,
285 false,
286 );
287
288 let [kp1, kp2, kp3] = kp();
289 check_multisig_scenario(
290 vec![
291 Input { kp: kp1, required: true, sign: true },
292 Input { kp: kp2, required: true, sign: true },
293 Input { kp: kp3, required: true, sign: false },
294 ],
295 3,
296 false,
297 true,
298 );
299 }
300}