Skip to main content

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::{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        // Taken from: d839d29b549469d0f9a23e51febe68d4084967a6a477868b511a5a8d88c5ae06
125        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        // ecdsa
211        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        // ecdsa
237        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        // ecdsa
261        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}