clementine_core/
musig2.rs

1//! # MuSig2
2//!
3//! Helper functions for the MuSig2 signature scheme.
4
5use crate::{aggregator::VerifierId, bitvm_client::SECP, errors::BridgeError};
6use bitcoin::{
7    hashes::Hash,
8    key::Keypair,
9    secp256k1::{schnorr, Message, PublicKey, SecretKey},
10    TapNodeHash, XOnlyPublicKey,
11};
12use eyre::Context;
13use lazy_static::lazy_static;
14use secp256k1::{
15    musig::{
16        new_nonce_pair, AggregatedNonce, KeyAggCache, PartialSignature, PublicNonce, SecretNonce,
17        Session, SessionSecretRand,
18    },
19    Scalar, SECP256K1,
20};
21use sha2::{Digest, Sha256};
22
23pub type MuSigNoncePair = (SecretNonce, PublicNonce);
24
25pub fn from_secp_xonly(xpk: secp256k1::XOnlyPublicKey) -> XOnlyPublicKey {
26    XOnlyPublicKey::from_slice(&xpk.serialize()).expect("serialized pubkey is valid")
27}
28
29pub fn to_secp_pk(pk: PublicKey) -> secp256k1::PublicKey {
30    secp256k1::PublicKey::from_slice(&pk.serialize()).expect("serialized pubkey is valid")
31}
32pub fn from_secp_pk(pk: secp256k1::PublicKey) -> PublicKey {
33    PublicKey::from_slice(&pk.serialize()).expect("serialized pubkey is valid")
34}
35
36pub fn to_secp_sk(sk: SecretKey) -> secp256k1::SecretKey {
37    secp256k1::SecretKey::from_slice(&sk.secret_bytes()).expect("serialized secret key is valid")
38}
39
40pub fn to_secp_kp(kp: &Keypair) -> secp256k1::Keypair {
41    secp256k1::Keypair::from_seckey_slice(SECP256K1, &kp.secret_bytes())
42        .expect("serialized secret key is valid")
43}
44pub fn from_secp_kp(kp: &secp256k1::Keypair) -> Keypair {
45    Keypair::from_seckey_slice(&SECP, &kp.secret_bytes()).expect("serialized secret key is valid")
46}
47
48pub fn from_secp_sig(sig: secp256k1::schnorr::Signature) -> schnorr::Signature {
49    schnorr::Signature::from_slice(&sig.to_byte_array()).expect("serialized signature is valid")
50}
51
52pub fn to_secp_msg(msg: &Message) -> secp256k1::Message {
53    secp256k1::Message::from_digest(*msg.as_ref())
54}
55
56/// Possible Musig2 modes.
57#[derive(Debug, Clone, Copy)]
58pub enum Musig2Mode {
59    /// No taproot tweak.
60    ScriptSpend,
61    /// Taproot tweak with aggregated public key.
62    OnlyKeySpend,
63    /// Taproot tweak with tweaked aggregated public key.
64    KeySpendWithScript(TapNodeHash),
65}
66
67/// sha256(b"TapTweak")
68const TAPROOT_TWEAK_TAG_DIGEST: [u8; 32] = [
69    0xe8, 0x0f, 0xe1, 0x63, 0x9c, 0x9c, 0xa0, 0x50, 0xe3, 0xaf, 0x1b, 0x39, 0xc1, 0x43, 0xc6, 0x3e,
70    0x42, 0x9c, 0xbc, 0xeb, 0x15, 0xd9, 0x40, 0xfb, 0xb5, 0xc5, 0xa1, 0xf4, 0xaf, 0x57, 0xc5, 0xe9,
71];
72
73lazy_static! {
74    pub static ref TAPROOT_TWEAK_TAGGED_HASH: Sha256 = Sha256::new()
75        .chain_update(TAPROOT_TWEAK_TAG_DIGEST)
76        .chain_update(TAPROOT_TWEAK_TAG_DIGEST);
77}
78
79fn create_key_agg_cache(
80    mut public_keys: Vec<PublicKey>,
81    mode: Option<Musig2Mode>,
82) -> Result<KeyAggCache, BridgeError> {
83    if public_keys.is_empty() {
84        return Err(BridgeError::from(eyre::eyre!(
85            "MuSig2 Error: cannot create key aggregation cache (no public keys provided)"
86        )));
87    }
88    public_keys.sort();
89    let secp_pubkeys: Vec<secp256k1::PublicKey> =
90        public_keys.iter().map(|pk| to_secp_pk(*pk)).collect();
91    let pubkeys_ref: Vec<&secp256k1::PublicKey> = secp_pubkeys.iter().collect();
92    let pubkeys_ref = pubkeys_ref.as_slice();
93
94    let mut musig_key_agg_cache = KeyAggCache::new(SECP256K1, pubkeys_ref);
95    let agg_key = musig_key_agg_cache.agg_pk();
96
97    if let Some(mode) = mode {
98        match mode {
99            Musig2Mode::ScriptSpend => (),
100            Musig2Mode::OnlyKeySpend => {
101                // sha256(C, C, IPK) where C = sha256("TapTweak")
102                let xonly_tweak = TAPROOT_TWEAK_TAGGED_HASH
103                    .clone()
104                    .chain_update(agg_key.serialize())
105                    .finalize();
106
107                musig_key_agg_cache
108                    .pubkey_xonly_tweak_add(
109                        SECP256K1,
110                        &Scalar::from_be_bytes(xonly_tweak.into())
111                            .wrap_err("Failed to create scalar from xonly tweak bytes")?,
112                    )
113                    .wrap_err("Failed to tweak aggregated public key")?;
114            }
115            Musig2Mode::KeySpendWithScript(merkle_root) => {
116                // sha256(C, C, IPK, s) where C = sha256("TapTweak")
117                let xonly_tweak = TAPROOT_TWEAK_TAGGED_HASH
118                    .clone()
119                    .chain_update(agg_key.serialize())
120                    .chain_update(merkle_root.to_raw_hash().to_byte_array())
121                    .finalize();
122
123                musig_key_agg_cache
124                    .pubkey_xonly_tweak_add(
125                        SECP256K1,
126                        &Scalar::from_be_bytes(xonly_tweak.into())
127                            .wrap_err("Failed to create scalar from xonly tweak bytes")?,
128                    )
129                    .wrap_err("Failed to tweak aggregated public key")?;
130            }
131        }
132    };
133
134    Ok(musig_key_agg_cache)
135}
136
137pub trait AggregateFromPublicKeys {
138    fn from_musig2_pks(
139        pks: Vec<PublicKey>,
140        tweak: Option<Musig2Mode>,
141    ) -> Result<XOnlyPublicKey, BridgeError>;
142}
143
144impl AggregateFromPublicKeys for XOnlyPublicKey {
145    fn from_musig2_pks(
146        pks: Vec<PublicKey>,
147        tweak: Option<Musig2Mode>,
148    ) -> Result<XOnlyPublicKey, BridgeError> {
149        let musig_key_agg_cache = create_key_agg_cache(pks, tweak)?;
150
151        Ok(
152            XOnlyPublicKey::from_slice(&musig_key_agg_cache.agg_pk().serialize())
153                .wrap_err("Failed to create XOnlyPublicKey from aggregated public key")?,
154        )
155    }
156}
157
158// Aggregates the public nonces into a single aggregated nonce.
159pub fn aggregate_nonces(pub_nonces: &[&PublicNonce]) -> Result<AggregatedNonce, BridgeError> {
160    if pub_nonces.is_empty() {
161        return Err(BridgeError::from(eyre::eyre!(
162            "MuSig2 Error: cannot aggregate nonces (no public nonces provided)"
163        )));
164    }
165    Ok(AggregatedNonce::new(SECP256K1, pub_nonces))
166}
167
168/// Aggregates the partial signatures into a single aggregated signature and verifies the aggregated signature. If PARTIAL_SIG_VERIFICATION is set to true, each partial signature will also be verified.
169pub fn aggregate_partial_signatures(
170    pks: Vec<PublicKey>,
171    tweak: Option<Musig2Mode>,
172    agg_nonce: AggregatedNonce,
173    partial_sigs: &[(PartialSignature, PublicNonce)],
174    message: Message,
175) -> Result<schnorr::Signature, BridgeError> {
176    let musig_key_agg_cache = create_key_agg_cache(pks.clone(), tweak)?;
177    let secp_message = to_secp_msg(&message);
178
179    let session = Session::new(SECP256K1, &musig_key_agg_cache, agg_nonce, secp_message);
180
181    let partial_sigs_and_nonces: Vec<&(PartialSignature, PublicNonce)> =
182        partial_sigs.iter().collect();
183    let partial_sigs: Vec<&PartialSignature> =
184        partial_sigs_and_nonces.iter().map(|(sig, _)| sig).collect();
185    // enable partial signature verification with an environment variable to see which verifier is giving bad partial signatures
186    // this is an env var so that we can turn it off for better performance
187    let enable_partial_sig_verification = std::env::var("PARTIAL_SIG_VERIFICATION")
188        .ok()
189        .map(|value| {
190            matches!(
191                value.to_ascii_lowercase().as_str(),
192                "1" | "true" | "yes" | "y" | "on"
193            )
194        })
195        .unwrap_or(false);
196
197    if enable_partial_sig_verification {
198        let mut partial_sig_verification_errors = Vec::new();
199        for ((partial_sig, pub_nonce), pub_key) in
200            partial_sigs_and_nonces.into_iter().zip(pks.into_iter())
201        {
202            if !session.partial_verify(
203                SECP256K1,
204                &musig_key_agg_cache,
205                *partial_sig,
206                *pub_nonce,
207                to_secp_pk(pub_key),
208            ) {
209                let verifier_id = VerifierId(pub_key);
210                partial_sig_verification_errors.push(format!("{verifier_id}"));
211            }
212        }
213        if !partial_sig_verification_errors.is_empty() {
214            let error_msg = format!(
215                "MuSig2 Error: partial signature verification failed for verifiers: {}",
216                partial_sig_verification_errors.join(", ")
217            );
218            tracing::error!("{error_msg}");
219            return Err(BridgeError::from(eyre::eyre!(error_msg)));
220        }
221    }
222    let final_sig = session.partial_sig_agg(&partial_sigs);
223
224    SECP256K1
225        .verify_schnorr(
226            &final_sig.assume_valid(),
227            secp_message.as_ref(),
228            &musig_key_agg_cache.agg_pk(),
229        )
230        .wrap_err("Failed to verify schnorr signature")?;
231
232    Ok(from_secp_sig(final_sig.assume_valid()))
233}
234
235/// Generates a pair of nonces, one secret and one public. Be careful,
236/// DO NOT REUSE the same pair of nonces for multiple transactions. It will cause
237/// you to leak your secret key. For more information. See:
238/// <https://medium.com/blockstream/musig-dn-schnorr-multisignatures-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6>.
239pub fn nonce_pair(keypair: &Keypair) -> Result<(SecretNonce, PublicNonce), BridgeError> {
240    let musig_session_sec_rand = SessionSecretRand::new();
241
242    Ok(new_nonce_pair(
243        SECP256K1,
244        musig_session_sec_rand,
245        None,
246        None,
247        to_secp_kp(keypair).public_key(),
248        None,
249        None,
250    ))
251}
252
253pub fn partial_sign(
254    pks: Vec<PublicKey>,
255    // Aggregated tweak, if there is any. This is useful for
256    // Taproot key-spends, since we might have script-spend conditions.
257    tweak: Option<Musig2Mode>,
258    sec_nonce: SecretNonce,
259    agg_nonce: AggregatedNonce,
260    keypair: Keypair,
261    sighash: Message,
262) -> Result<PartialSignature, BridgeError> {
263    let musig_key_agg_cache = create_key_agg_cache(pks, tweak)?;
264
265    let session = Session::new(
266        SECP256K1,
267        &musig_key_agg_cache,
268        agg_nonce,
269        to_secp_msg(&sighash),
270    );
271
272    Ok(session.partial_sign(
273        SECP256K1,
274        sec_nonce,
275        &to_secp_kp(&keypair),
276        &musig_key_agg_cache,
277    ))
278}
279
280#[cfg(test)]
281mod tests {
282    use super::{nonce_pair, MuSigNoncePair, Musig2Mode};
283    use crate::builder::script::{CheckSig, OtherSpendable, SpendableScript};
284    use crate::builder::transaction::{TransactionType, DEFAULT_SEQUENCE};
285    use crate::rpc::clementine::NormalSignatureKind;
286    use crate::{
287        bitvm_client::{self, SECP},
288        builder::{
289            self,
290            transaction::{input::SpendableTxIn, output::UnspentTxOut, TxHandlerBuilder},
291        },
292        errors::BridgeError,
293        musig2::{
294            aggregate_nonces, aggregate_partial_signatures, create_key_agg_cache, from_secp_xonly,
295            partial_sign, AggregateFromPublicKeys,
296        },
297    };
298    use bitcoin::{
299        hashes::Hash,
300        key::Keypair,
301        script,
302        secp256k1::{schnorr, Message, PublicKey},
303        Amount, OutPoint, TapNodeHash, TapSighashType, TxOut, Txid, XOnlyPublicKey,
304    };
305    use secp256k1::rand::Rng;
306    use std::sync::Arc;
307    use std::vec;
308
309    /// Generates random key and nonce pairs for a given number of signers.
310    fn create_key_and_nonce_pairs(num_signers: usize) -> (Vec<Keypair>, Vec<MuSigNoncePair>) {
311        let mut key_pairs = Vec::new();
312        let mut nonce_pairs = Vec::new();
313
314        for _ in 0..num_signers {
315            let key_pair = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
316            let nonce_pair = nonce_pair(&key_pair).unwrap();
317
318            key_pairs.push(key_pair);
319            nonce_pairs.push(nonce_pair);
320        }
321
322        (key_pairs, nonce_pairs)
323    }
324
325    #[test]
326    fn musig2_raw_without_a_tweak() {
327        let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(3);
328        let message = Message::from_digest(secp256k1::rand::rng().random());
329
330        let public_keys = key_pairs
331            .iter()
332            .map(|kp| kp.public_key())
333            .collect::<Vec<PublicKey>>();
334        let agg_pk = XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap();
335
336        let aggregated_nonce = super::aggregate_nonces(
337            nonce_pairs
338                .iter()
339                .map(|(_, musig_pub_nonce)| musig_pub_nonce)
340                .collect::<Vec<_>>()
341                .as_slice(),
342        )
343        .unwrap();
344
345        let partial_sigs = key_pairs
346            .into_iter()
347            .zip(nonce_pairs)
348            .map(|(kp, nonce_pair)| {
349                (
350                    super::partial_sign(
351                        public_keys.clone(),
352                        None,
353                        nonce_pair.0,
354                        aggregated_nonce,
355                        kp,
356                        message,
357                    )
358                    .unwrap(),
359                    nonce_pair.1,
360                )
361            })
362            .collect::<Vec<_>>();
363
364        let final_signature = super::aggregate_partial_signatures(
365            public_keys,
366            None,
367            aggregated_nonce,
368            &partial_sigs,
369            message,
370        )
371        .unwrap();
372
373        SECP.verify_schnorr(&final_signature, &message, &agg_pk)
374            .unwrap();
375    }
376
377    #[test]
378    fn musig2_raw_fail_if_partial_sigs_invalid() {
379        let kp_0 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
380        let kp_1 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
381        let kp_2 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
382
383        let message = Message::from_digest(secp256k1::rand::rng().random());
384
385        let pks = vec![kp_0.public_key(), kp_1.public_key(), kp_2.public_key()];
386
387        let (sec_nonce_0, pub_nonce_0) = super::nonce_pair(&kp_0).unwrap();
388        let (sec_nonce_1, pub_nonce_1) = super::nonce_pair(&kp_1).unwrap();
389        let (sec_nonce_2, pub_nonce_2) = super::nonce_pair(&kp_2).unwrap();
390
391        let agg_nonce =
392            super::aggregate_nonces(&[&pub_nonce_0, &pub_nonce_1, &pub_nonce_2]).unwrap();
393
394        let partial_sig_0 = (
395            super::partial_sign(pks.clone(), None, sec_nonce_0, agg_nonce, kp_0, message).unwrap(),
396            pub_nonce_0,
397        );
398        let partial_sig_1 = (
399            super::partial_sign(pks.clone(), None, sec_nonce_1, agg_nonce, kp_1, message).unwrap(),
400            pub_nonce_1,
401        );
402        // Oops, a verifier accidentally added some tweak!
403        let partial_sig_2 = (
404            super::partial_sign(
405                pks.clone(),
406                Some(Musig2Mode::KeySpendWithScript(
407                    TapNodeHash::from_slice(&[1u8; 32]).unwrap(),
408                )),
409                sec_nonce_2,
410                agg_nonce,
411                kp_2,
412                message,
413            )
414            .unwrap(),
415            pub_nonce_2,
416        );
417        let partial_sigs = vec![partial_sig_0, partial_sig_1, partial_sig_2];
418
419        let final_signature: Result<schnorr::Signature, BridgeError> =
420            super::aggregate_partial_signatures(pks, None, agg_nonce, &partial_sigs, message);
421
422        println!("final_signature: {final_signature:?}");
423
424        assert!(final_signature.is_err());
425    }
426
427    #[test]
428    fn musig2_sig_with_tweak() {
429        let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(3);
430        let message = Message::from_digest(secp256k1::rand::rng().random());
431        let tweak: [u8; 32] = secp256k1::rand::rng().random();
432
433        let public_keys = key_pairs
434            .iter()
435            .map(|kp| kp.public_key())
436            .collect::<Vec<PublicKey>>();
437        let aggregated_pk: XOnlyPublicKey = XOnlyPublicKey::from_musig2_pks(
438            public_keys.clone(),
439            Some(Musig2Mode::KeySpendWithScript(
440                TapNodeHash::from_slice(&tweak).unwrap(),
441            )),
442        )
443        .unwrap();
444
445        let aggregated_nonce = super::aggregate_nonces(
446            nonce_pairs
447                .iter()
448                .map(|(_, musig_pub_nonce)| musig_pub_nonce)
449                .collect::<Vec<_>>()
450                .as_slice(),
451        )
452        .unwrap();
453
454        let partial_sigs = key_pairs
455            .into_iter()
456            .zip(nonce_pairs)
457            .map(|(kp, nonce_pair)| {
458                (
459                    super::partial_sign(
460                        public_keys.clone(),
461                        Some(Musig2Mode::KeySpendWithScript(
462                            TapNodeHash::from_slice(&tweak).unwrap(),
463                        )),
464                        nonce_pair.0,
465                        aggregated_nonce,
466                        kp,
467                        message,
468                    )
469                    .unwrap(),
470                    nonce_pair.1,
471                )
472            })
473            .collect::<Vec<_>>();
474
475        let final_signature = super::aggregate_partial_signatures(
476            public_keys,
477            Some(Musig2Mode::KeySpendWithScript(
478                TapNodeHash::from_slice(&tweak).unwrap(),
479            )),
480            aggregated_nonce,
481            &partial_sigs,
482            message,
483        )
484        .unwrap();
485
486        SECP.verify_schnorr(&final_signature, &message, &aggregated_pk)
487            .unwrap();
488    }
489
490    #[test]
491    fn musig2_tweak_fail() {
492        let kp_0 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
493        let kp_1 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
494        let kp_2 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
495
496        let message = Message::from_digest(secp256k1::rand::rng().random::<[u8; 32]>());
497
498        let tweak: [u8; 32] = secp256k1::rand::rng().random();
499
500        let pks = vec![kp_0.public_key(), kp_1.public_key(), kp_2.public_key()];
501
502        let (sec_nonce_0, pub_nonce_0) = super::nonce_pair(&kp_0).unwrap();
503        let (sec_nonce_1, pub_nonce_1) = super::nonce_pair(&kp_1).unwrap();
504        let (sec_nonce_2, pub_nonce_2) = super::nonce_pair(&kp_2).unwrap();
505
506        let agg_nonce =
507            super::aggregate_nonces(&[&pub_nonce_0, &pub_nonce_1, &pub_nonce_2]).unwrap();
508
509        let partial_sig_0 = (
510            super::partial_sign(
511                pks.clone(),
512                Some(Musig2Mode::KeySpendWithScript(
513                    TapNodeHash::from_slice(&tweak).unwrap(),
514                )),
515                sec_nonce_0,
516                agg_nonce,
517                kp_0,
518                message,
519            )
520            .unwrap(),
521            pub_nonce_0,
522        );
523        let partial_sig_1 = (
524            super::partial_sign(
525                pks.clone(),
526                Some(Musig2Mode::KeySpendWithScript(
527                    TapNodeHash::from_slice(&tweak).unwrap(),
528                )),
529                sec_nonce_1,
530                agg_nonce,
531                kp_1,
532                message,
533            )
534            .unwrap(),
535            pub_nonce_1,
536        );
537        // Oops, a verifier accidentally forgot to put the tweak!
538        let partial_sig_2 = (
539            super::partial_sign(pks.clone(), None, sec_nonce_2, agg_nonce, kp_2, message).unwrap(),
540            pub_nonce_2,
541        );
542        let partial_sigs = vec![partial_sig_0, partial_sig_1, partial_sig_2];
543
544        let final_signature = super::aggregate_partial_signatures(
545            pks,
546            Some(Musig2Mode::KeySpendWithScript(
547                TapNodeHash::from_slice(&tweak).unwrap(),
548            )),
549            agg_nonce,
550            &partial_sigs,
551            message,
552        );
553
554        assert!(final_signature.is_err());
555    }
556
557    #[test]
558    fn musig2_key_spend() {
559        let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(2);
560        let public_keys = key_pairs
561            .iter()
562            .map(|key_pair| key_pair.public_key())
563            .collect::<Vec<PublicKey>>();
564
565        let untweaked_xonly_pubkey =
566            XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap();
567
568        let agg_nonce = super::aggregate_nonces(
569            nonce_pairs
570                .iter()
571                .map(|(_, musig_pub_nonce)| musig_pub_nonce)
572                .collect::<Vec<_>>()
573                .as_slice(),
574        )
575        .unwrap();
576
577        let dummy_script = script::Builder::new().push_int(1).into_script();
578        let scripts: Vec<Arc<dyn SpendableScript>> =
579            vec![Arc::new(OtherSpendable::new(dummy_script))];
580        let receiving_address = bitcoin::Address::p2tr(
581            &SECP,
582            *bitvm_client::UNSPENDABLE_XONLY_PUBKEY,
583            None,
584            bitcoin::Network::Regtest,
585        );
586        let (sending_address, sending_address_spend_info) =
587            builder::address::create_taproot_address(
588                &scripts
589                    .iter()
590                    .map(|a| a.to_script_buf())
591                    .collect::<Vec<_>>(),
592                Some(untweaked_xonly_pubkey),
593                bitcoin::Network::Regtest,
594            );
595
596        let prevout = TxOut {
597            value: Amount::from_sat(100_000_000),
598            script_pubkey: sending_address.script_pubkey(),
599        };
600        let utxo = OutPoint {
601            txid: Txid::from_byte_array([0u8; 32]),
602            vout: 0,
603        };
604        let mut builder = TxHandlerBuilder::new(TransactionType::Dummy);
605        builder = builder
606            .add_input(
607                NormalSignatureKind::OperatorSighashDefault,
608                SpendableTxIn::new(
609                    utxo,
610                    prevout.clone(),
611                    scripts.clone(),
612                    Some(sending_address_spend_info.clone()),
613                ),
614                builder::script::SpendPath::Unknown,
615                DEFAULT_SEQUENCE,
616            )
617            .add_output(UnspentTxOut::from_partial(TxOut {
618                value: Amount::from_sat(99_000_000),
619                script_pubkey: receiving_address.script_pubkey(),
620            }));
621
622        let tx_details = builder.finalize();
623
624        let message = Message::from_digest(
625            tx_details
626                .calculate_pubkey_spend_sighash(0, TapSighashType::Default)
627                .unwrap()
628                .to_byte_array(),
629        );
630        let merkle_root = sending_address_spend_info.merkle_root().unwrap();
631
632        let partial_sigs = key_pairs
633            .into_iter()
634            .zip(nonce_pairs)
635            .map(|(kp, nonce_pair)| {
636                (
637                    super::partial_sign(
638                        public_keys.clone(),
639                        Some(Musig2Mode::KeySpendWithScript(merkle_root)),
640                        nonce_pair.0,
641                        agg_nonce,
642                        kp,
643                        message,
644                    )
645                    .unwrap(),
646                    nonce_pair.1,
647                )
648            })
649            .collect::<Vec<_>>();
650
651        let final_signature = super::aggregate_partial_signatures(
652            public_keys.clone(),
653            Some(Musig2Mode::KeySpendWithScript(merkle_root)),
654            agg_nonce,
655            &partial_sigs,
656            message,
657        )
658        .unwrap();
659
660        let musig_agg_xonly_pubkey = XOnlyPublicKey::from_musig2_pks(
661            public_keys,
662            Some(Musig2Mode::KeySpendWithScript(merkle_root)),
663        )
664        .unwrap();
665
666        SECP.verify_schnorr(&final_signature, &message, &musig_agg_xonly_pubkey)
667            .unwrap();
668    }
669
670    #[test]
671    fn musig2_script_spend() {
672        let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(2);
673        let public_keys = key_pairs
674            .iter()
675            .map(|key_pair| key_pair.public_key())
676            .collect::<Vec<PublicKey>>();
677
678        let agg_nonce = super::aggregate_nonces(
679            nonce_pairs
680                .iter()
681                .map(|x| &x.1)
682                .collect::<Vec<_>>()
683                .as_slice(),
684        )
685        .unwrap();
686        let musig_agg_xonly_pubkey_wrapped =
687            XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap();
688
689        let scripts: Vec<Arc<dyn SpendableScript>> =
690            vec![Arc::new(CheckSig::new(musig_agg_xonly_pubkey_wrapped))];
691
692        let receiving_address = bitcoin::Address::p2tr(
693            &SECP,
694            *bitvm_client::UNSPENDABLE_XONLY_PUBKEY,
695            None,
696            bitcoin::Network::Regtest,
697        );
698        let (sending_address, sending_address_spend_info) =
699            builder::address::create_taproot_address(
700                &scripts
701                    .iter()
702                    .map(|a| a.to_script_buf())
703                    .collect::<Vec<_>>(),
704                None,
705                bitcoin::Network::Regtest,
706            );
707
708        let prevout = TxOut {
709            value: Amount::from_sat(100_000_000),
710            script_pubkey: sending_address.script_pubkey(),
711        };
712        let utxo = OutPoint {
713            txid: Txid::from_byte_array([0u8; 32]),
714            vout: 0,
715        };
716
717        let tx_details = TxHandlerBuilder::new(TransactionType::Dummy)
718            .add_input(
719                NormalSignatureKind::OperatorSighashDefault,
720                SpendableTxIn::new(
721                    utxo,
722                    prevout,
723                    scripts,
724                    Some(sending_address_spend_info.clone()),
725                ),
726                builder::script::SpendPath::Unknown,
727                DEFAULT_SEQUENCE,
728            )
729            .add_output(UnspentTxOut::from_partial(TxOut {
730                value: Amount::from_sat(99_000_000),
731                script_pubkey: receiving_address.script_pubkey(),
732            }))
733            .finalize();
734
735        let message = Message::from_digest(
736            tx_details
737                .calculate_script_spend_sighash_indexed(0, 0, bitcoin::TapSighashType::Default)
738                .unwrap()
739                .to_byte_array(),
740        );
741
742        let partial_sigs = key_pairs
743            .into_iter()
744            .zip(nonce_pairs)
745            .map(|(kp, nonce_pair)| {
746                (
747                    super::partial_sign(
748                        public_keys.clone(),
749                        None,
750                        nonce_pair.0,
751                        agg_nonce,
752                        kp,
753                        message,
754                    )
755                    .unwrap(),
756                    nonce_pair.1,
757                )
758            })
759            .collect::<Vec<_>>();
760
761        let final_signature = super::aggregate_partial_signatures(
762            public_keys,
763            None,
764            agg_nonce,
765            &partial_sigs,
766            message,
767        )
768        .unwrap();
769
770        SECP.verify_schnorr(&final_signature, &message, &musig_agg_xonly_pubkey_wrapped)
771            .unwrap();
772    }
773
774    #[test]
775    fn different_aggregated_keys_for_different_musig2_modes() {
776        let kp1 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
777        let kp2 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
778        let public_keys = vec![kp1.public_key(), kp2.public_key()];
779
780        let key_agg_cache = create_key_agg_cache(public_keys.clone(), None).unwrap();
781        let agg_pk_no_tweak = from_secp_xonly(key_agg_cache.agg_pk());
782
783        let key_agg_cache =
784            create_key_agg_cache(public_keys.clone(), Some(Musig2Mode::ScriptSpend)).unwrap();
785        let agg_pk_script_spend = from_secp_xonly(key_agg_cache.agg_pk());
786
787        let key_agg_cache =
788            create_key_agg_cache(public_keys.clone(), Some(Musig2Mode::OnlyKeySpend)).unwrap();
789        let agg_pk_key_tweak = from_secp_xonly(key_agg_cache.agg_pk());
790
791        let key_agg_cache = create_key_agg_cache(
792            public_keys.clone(),
793            Some(Musig2Mode::KeySpendWithScript(
794                TapNodeHash::from_slice(&[1u8; 32]).unwrap(),
795            )),
796        )
797        .unwrap();
798        let agg_pk_script_tweak = from_secp_xonly(key_agg_cache.agg_pk());
799
800        assert_eq!(agg_pk_no_tweak, agg_pk_script_spend);
801
802        assert_ne!(agg_pk_no_tweak, agg_pk_script_tweak);
803        assert_ne!(agg_pk_no_tweak, agg_pk_key_tweak);
804        assert_ne!(agg_pk_script_tweak, agg_pk_key_tweak);
805        assert_ne!(agg_pk_script_tweak, agg_pk_script_spend);
806        assert_ne!(agg_pk_key_tweak, agg_pk_script_spend);
807    }
808
809    #[test]
810    fn signing_checks_for_different_musig2_modes() {
811        let kp1 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
812        let kp2 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
813        let public_keys = vec![kp1.public_key(), kp2.public_key()];
814
815        let message = Message::from_digest(secp256k1::rand::rng().random());
816        let key_spend_with_script_tweak =
817            Musig2Mode::KeySpendWithScript(TapNodeHash::from_slice(&[0x45u8; 32]).unwrap());
818
819        let key_agg_cache =
820            create_key_agg_cache(public_keys.clone(), Some(key_spend_with_script_tweak)).unwrap();
821        let agg_pk_script_tweak = from_secp_xonly(key_agg_cache.agg_pk());
822
823        let (sec_nonce1, pub_nonce1) = nonce_pair(&kp1).unwrap();
824        let (sec_nonce2, pub_nonce2) = nonce_pair(&kp2).unwrap();
825        let agg_nonce = aggregate_nonces(&[&pub_nonce1, &pub_nonce2]).unwrap();
826
827        let partial_sig1 = partial_sign(
828            public_keys.clone(),
829            Some(key_spend_with_script_tweak),
830            sec_nonce1,
831            agg_nonce,
832            kp1,
833            message,
834        )
835        .unwrap();
836        let partial_sig2 = partial_sign(
837            public_keys.clone(),
838            Some(key_spend_with_script_tweak),
839            sec_nonce2,
840            agg_nonce,
841            kp2,
842            message,
843        )
844        .unwrap();
845
846        let final_sig = aggregate_partial_signatures(
847            public_keys.clone(),
848            Some(key_spend_with_script_tweak),
849            agg_nonce,
850            &[(partial_sig1, pub_nonce1), (partial_sig2, pub_nonce2)],
851            message,
852        )
853        .unwrap();
854
855        SECP.verify_schnorr(&final_sig, &message, &agg_pk_script_tweak)
856            .unwrap();
857
858        // Verification will fail with a untweaked aggregate public key against
859        // a signature created with a tweaked aggregate public key.
860        let key_agg_cache = create_key_agg_cache(public_keys, None).unwrap();
861        let agg_pk_no_tweak = from_secp_xonly(key_agg_cache.agg_pk());
862        assert!(SECP
863            .verify_schnorr(&final_sig, &message, &agg_pk_no_tweak)
864            .is_err());
865    }
866}