kaspa_txscript/standard/
multisig.rs

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    // ErrTooManyRequiredSigs is returned from multisig_script when the
9    // specified number of required signatures is larger than the number of
10    // provided public keys.
11    #[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        // Taken from: d839d29b549469d0f9a23e51febe68d4084967a6a477868b511a5a8d88c5ae06
124        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        // ecdsa
208        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        // ecdsa
234        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        // ecdsa
258        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}