chie_crypto/
musig2.rs

1//! MuSig2: Secure multi-signature aggregation scheme.
2//!
3//! MuSig2 is a modern multi-signature protocol that provides:
4//! - Key aggregation: Multiple public keys → single aggregated key
5//! - Signature aggregation: N partial signatures → single signature
6//! - Security against rogue key attacks
7//! - Three-round protocol with nonce commitments
8//!
9//! # Protocol Overview
10//!
11//! 1. **Key Aggregation**: Aggregate public keys with coefficients
12//! 2. **Round 1 - Nonce Commitment**: Each signer commits to their nonces
13//! 3. **Round 2 - Nonce Reveal**: Nonces are revealed and aggregated
14//! 4. **Round 3 - Partial Signing**: Each signer creates partial signature
15//! 5. **Aggregation**: Partial signatures combined into final signature
16//!
17//! # Example
18//!
19//! ```
20//! use chie_crypto::musig2::*;
21//!
22//! // Three signers want to sign a message
23//! let signer1 = MuSig2Signer::new();
24//! let signer2 = MuSig2Signer::new();
25//! let signer3 = MuSig2Signer::new();
26//!
27//! let public_keys = vec![
28//!     signer1.public_key(),
29//!     signer2.public_key(),
30//!     signer3.public_key(),
31//! ];
32//!
33//! // Aggregate public keys
34//! let agg_key = aggregate_public_keys(&public_keys).unwrap();
35//!
36//! let message = b"Multi-signature test message";
37//!
38//! // Round 1: Generate nonce commitments
39//! let (nonce1, commit1) = signer1.nonce_commitment();
40//! let (nonce2, commit2) = signer2.nonce_commitment();
41//! let (nonce3, commit3) = signer3.nonce_commitment();
42//!
43//! let commitments = vec![commit1, commit2, commit3];
44//!
45//! // Round 2: Reveal nonces
46//! let nonces = vec![nonce1.public_nonce(), nonce2.public_nonce(), nonce3.public_nonce()];
47//! let agg_nonce = aggregate_nonces(&nonces, &commitments).unwrap();
48//!
49//! // Round 3: Partial signatures
50//! let partial1 = signer1.partial_sign(message, &nonce1, &public_keys, &agg_nonce).unwrap();
51//! let partial2 = signer2.partial_sign(message, &nonce2, &public_keys, &agg_nonce).unwrap();
52//! let partial3 = signer3.partial_sign(message, &nonce3, &public_keys, &agg_nonce).unwrap();
53//!
54//! // Aggregate signatures
55//! let signature = aggregate_partial_signatures_with_nonce(&[partial1, partial2, partial3], &agg_nonce).unwrap();
56//!
57//! // Verify
58//! assert!(verify_musig2(&agg_key, message, &signature));
59//! ```
60
61use crate::ct::ct_eq_32;
62use blake3::Hasher;
63use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
64use curve25519_dalek::ristretto::RistrettoPoint;
65use curve25519_dalek::scalar::Scalar;
66use rand::RngCore;
67use serde::{Deserialize, Serialize};
68use thiserror::Error;
69
70#[derive(Debug, Error)]
71pub enum MuSig2Error {
72    #[error("Invalid public key")]
73    InvalidPublicKey,
74    #[error("Invalid nonce commitment")]
75    InvalidNonceCommitment,
76    #[error("Empty signer list")]
77    EmptySigners,
78    #[error("Mismatched lengths")]
79    MismatchedLengths,
80    #[error("Invalid signature")]
81    InvalidSignature,
82    #[error("Serialization error: {0}")]
83    Serialization(String),
84}
85
86pub type MuSig2Result<T> = Result<T, MuSig2Error>;
87
88/// Generate a random scalar
89fn random_scalar() -> Scalar {
90    let mut bytes = [0u8; 32];
91    rand::thread_rng().fill_bytes(&mut bytes);
92    Scalar::from_bytes_mod_order(bytes)
93}
94
95/// Secret key for MuSig2 signer
96#[derive(Clone, Serialize, Deserialize)]
97pub struct MuSig2SecretKey(Scalar);
98
99/// Public key for MuSig2 signer
100#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
101pub struct MuSig2PublicKey(RistrettoPoint);
102
103/// A MuSig2 signer with keypair
104#[derive(Clone)]
105pub struct MuSig2Signer {
106    secret_key: MuSig2SecretKey,
107    public_key: MuSig2PublicKey,
108}
109
110/// Nonce used in the signing protocol (secret)
111#[derive(Clone)]
112pub struct SigningNonce {
113    secret: Scalar,
114    public: MuSig2Nonce,
115}
116
117/// Public nonce used in MuSig2 protocol
118#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
119pub struct MuSig2Nonce(RistrettoPoint);
120
121/// Nonce commitment (hash of public nonce)
122#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
123pub struct NonceCommitment([u8; 32]);
124
125/// Partial signature from a signer
126#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
127pub struct PartialSignature(Scalar);
128
129/// Final aggregated MuSig2 signature
130#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
131pub struct MuSig2Signature {
132    r: RistrettoPoint,
133    s: Scalar,
134}
135
136impl MuSig2Signer {
137    /// Generate a new random signer
138    pub fn new() -> Self {
139        let secret = random_scalar();
140        let public = RISTRETTO_BASEPOINT_POINT * secret;
141
142        Self {
143            secret_key: MuSig2SecretKey(secret),
144            public_key: MuSig2PublicKey(public),
145        }
146    }
147
148    /// Create signer from secret key bytes
149    pub fn from_bytes(bytes: &[u8; 32]) -> MuSig2Result<Self> {
150        let secret = Scalar::from_bytes_mod_order(*bytes);
151        let public = RISTRETTO_BASEPOINT_POINT * secret;
152
153        Ok(Self {
154            secret_key: MuSig2SecretKey(secret),
155            public_key: MuSig2PublicKey(public),
156        })
157    }
158
159    /// Get the public key
160    pub fn public_key(&self) -> MuSig2PublicKey {
161        self.public_key
162    }
163
164    /// Export secret key bytes
165    pub fn to_bytes(&self) -> [u8; 32] {
166        self.secret_key.0.to_bytes()
167    }
168
169    /// Generate a nonce commitment (Round 1)
170    pub fn nonce_commitment(&self) -> (SigningNonce, NonceCommitment) {
171        let secret = random_scalar();
172        let public = RISTRETTO_BASEPOINT_POINT * secret;
173        let nonce = MuSig2Nonce(public);
174
175        // Commit to the nonce
176        let commitment = NonceCommitment(blake3::hash(&public.compress().to_bytes()).into());
177
178        (
179            SigningNonce {
180                secret,
181                public: nonce,
182            },
183            commitment,
184        )
185    }
186
187    /// Create a partial signature (Round 3)
188    pub fn partial_sign(
189        &self,
190        message: &[u8],
191        nonce: &SigningNonce,
192        public_keys: &[MuSig2PublicKey],
193        aggregated_nonce: &MuSig2Nonce,
194    ) -> MuSig2Result<PartialSignature> {
195        if public_keys.is_empty() {
196            return Err(MuSig2Error::EmptySigners);
197        }
198
199        // Compute key aggregation coefficient
200        let coeff = key_aggregation_coefficient(&self.public_key, public_keys);
201
202        // Compute challenge
203        let challenge = compute_challenge(
204            aggregated_nonce,
205            &aggregate_public_keys(public_keys)?,
206            message,
207        );
208
209        // Partial signature: s_i = r_i + c * a_i * x_i
210        let s = nonce.secret + challenge * coeff * self.secret_key.0;
211
212        Ok(PartialSignature(s))
213    }
214}
215
216impl Default for MuSig2Signer {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222impl SigningNonce {
223    /// Get the public nonce
224    pub fn public_nonce(&self) -> MuSig2Nonce {
225        self.public
226    }
227
228    /// Verify that the nonce matches the commitment
229    pub fn verify_commitment(&self, commitment: &NonceCommitment) -> bool {
230        let computed = NonceCommitment(blake3::hash(&self.public.0.compress().to_bytes()).into());
231        ct_eq_32(&computed.0, &commitment.0)
232    }
233}
234
235impl MuSig2PublicKey {
236    /// Create from bytes
237    pub fn from_bytes(bytes: &[u8; 32]) -> MuSig2Result<Self> {
238        let point = curve25519_dalek::ristretto::CompressedRistretto(*bytes)
239            .decompress()
240            .ok_or(MuSig2Error::InvalidPublicKey)?;
241        Ok(Self(point))
242    }
243
244    /// Export to bytes
245    pub fn to_bytes(&self) -> [u8; 32] {
246        self.0.compress().to_bytes()
247    }
248}
249
250/// Compute key aggregation coefficient for a public key
251fn key_aggregation_coefficient(
252    pubkey: &MuSig2PublicKey,
253    all_pubkeys: &[MuSig2PublicKey],
254) -> Scalar {
255    let mut hasher = Hasher::new();
256
257    // Hash all public keys to derive coefficient
258    for pk in all_pubkeys {
259        hasher.update(&pk.0.compress().to_bytes());
260    }
261    hasher.update(&pubkey.0.compress().to_bytes());
262
263    let hash = hasher.finalize();
264    Scalar::from_bytes_mod_order(*hash.as_bytes())
265}
266
267/// Aggregate public keys with coefficients
268pub fn aggregate_public_keys(public_keys: &[MuSig2PublicKey]) -> MuSig2Result<MuSig2PublicKey> {
269    if public_keys.is_empty() {
270        return Err(MuSig2Error::EmptySigners);
271    }
272
273    let mut aggregated = RistrettoPoint::default();
274
275    for pk in public_keys {
276        let coeff = key_aggregation_coefficient(pk, public_keys);
277        aggregated += coeff * pk.0;
278    }
279
280    Ok(MuSig2PublicKey(aggregated))
281}
282
283/// Aggregate nonces (Round 2)
284pub fn aggregate_nonces(
285    nonces: &[MuSig2Nonce],
286    commitments: &[NonceCommitment],
287) -> MuSig2Result<MuSig2Nonce> {
288    if nonces.is_empty() {
289        return Err(MuSig2Error::EmptySigners);
290    }
291
292    if nonces.len() != commitments.len() {
293        return Err(MuSig2Error::MismatchedLengths);
294    }
295
296    // Verify all nonces match commitments
297    for (nonce, commitment) in nonces.iter().zip(commitments.iter()) {
298        let computed = NonceCommitment(blake3::hash(&nonce.0.compress().to_bytes()).into());
299        if !ct_eq_32(&computed.0, &commitment.0) {
300            return Err(MuSig2Error::InvalidNonceCommitment);
301        }
302    }
303
304    // Aggregate nonces
305    let mut aggregated = RistrettoPoint::default();
306    for nonce in nonces {
307        aggregated += nonce.0;
308    }
309
310    Ok(MuSig2Nonce(aggregated))
311}
312
313/// Aggregate partial signatures into final signature
314pub fn aggregate_partial_signatures(
315    partials: &[PartialSignature],
316) -> MuSig2Result<MuSig2Signature> {
317    if partials.is_empty() {
318        return Err(MuSig2Error::EmptySigners);
319    }
320
321    let mut s = Scalar::ZERO;
322    for partial in partials {
323        s += partial.0;
324    }
325
326    // Note: We don't have access to the aggregated nonce here,
327    // so we'll need to modify this. For now, we'll use a placeholder.
328    // In practice, the caller should provide the aggregated nonce.
329    Ok(MuSig2Signature {
330        r: RistrettoPoint::default(),
331        s,
332    })
333}
334
335/// Aggregate partial signatures with the aggregated nonce
336pub fn aggregate_partial_signatures_with_nonce(
337    partials: &[PartialSignature],
338    aggregated_nonce: &MuSig2Nonce,
339) -> MuSig2Result<MuSig2Signature> {
340    if partials.is_empty() {
341        return Err(MuSig2Error::EmptySigners);
342    }
343
344    let mut s = Scalar::ZERO;
345    for partial in partials {
346        s += partial.0;
347    }
348
349    Ok(MuSig2Signature {
350        r: aggregated_nonce.0,
351        s,
352    })
353}
354
355/// Compute challenge for Schnorr-like signature
356fn compute_challenge(nonce: &MuSig2Nonce, pubkey: &MuSig2PublicKey, message: &[u8]) -> Scalar {
357    let mut hasher = Hasher::new();
358    hasher.update(&nonce.0.compress().to_bytes());
359    hasher.update(&pubkey.0.compress().to_bytes());
360    hasher.update(message);
361
362    let hash = hasher.finalize();
363    Scalar::from_bytes_mod_order(*hash.as_bytes())
364}
365
366/// Verify a MuSig2 signature
367pub fn verify_musig2(
368    pubkey: &MuSig2PublicKey,
369    message: &[u8],
370    signature: &MuSig2Signature,
371) -> bool {
372    // Compute challenge
373    let challenge = compute_challenge(&MuSig2Nonce(signature.r), pubkey, message);
374
375    // Verify: s * G = R + c * X
376    let lhs = RISTRETTO_BASEPOINT_POINT * signature.s;
377    let rhs = signature.r + challenge * pubkey.0;
378
379    lhs == rhs
380}
381
382impl MuSig2Signature {
383    /// Serialize signature to bytes
384    pub fn to_bytes(&self) -> [u8; 64] {
385        let mut bytes = [0u8; 64];
386        bytes[..32].copy_from_slice(&self.r.compress().to_bytes());
387        bytes[32..].copy_from_slice(&self.s.to_bytes());
388        bytes
389    }
390
391    /// Deserialize signature from bytes
392    pub fn from_bytes(bytes: &[u8; 64]) -> MuSig2Result<Self> {
393        let r = curve25519_dalek::ristretto::CompressedRistretto(bytes[..32].try_into().unwrap())
394            .decompress()
395            .ok_or(MuSig2Error::InvalidSignature)?;
396        let s = Scalar::from_bytes_mod_order(bytes[32..].try_into().unwrap());
397
398        Ok(Self { r, s })
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn test_musig2_single_signer() {
408        let signer = MuSig2Signer::new();
409        let pubkeys = vec![signer.public_key()];
410        let agg_key = aggregate_public_keys(&pubkeys).unwrap();
411
412        let message = b"Single signer test";
413
414        let (nonce, commitment) = signer.nonce_commitment();
415        let commitments = vec![commitment];
416        let nonces = vec![nonce.public_nonce()];
417        let agg_nonce = aggregate_nonces(&nonces, &commitments).unwrap();
418
419        let partial = signer
420            .partial_sign(message, &nonce, &pubkeys, &agg_nonce)
421            .unwrap();
422        let signature = aggregate_partial_signatures_with_nonce(&[partial], &agg_nonce).unwrap();
423
424        assert!(verify_musig2(&agg_key, message, &signature));
425    }
426
427    #[test]
428    fn test_musig2_three_signers() {
429        let signer1 = MuSig2Signer::new();
430        let signer2 = MuSig2Signer::new();
431        let signer3 = MuSig2Signer::new();
432
433        let pubkeys = vec![
434            signer1.public_key(),
435            signer2.public_key(),
436            signer3.public_key(),
437        ];
438        let agg_key = aggregate_public_keys(&pubkeys).unwrap();
439
440        let message = b"Three signer test";
441
442        let (nonce1, commit1) = signer1.nonce_commitment();
443        let (nonce2, commit2) = signer2.nonce_commitment();
444        let (nonce3, commit3) = signer3.nonce_commitment();
445
446        let commitments = vec![commit1, commit2, commit3];
447        let nonces = vec![
448            nonce1.public_nonce(),
449            nonce2.public_nonce(),
450            nonce3.public_nonce(),
451        ];
452
453        let agg_nonce = aggregate_nonces(&nonces, &commitments).unwrap();
454
455        let partial1 = signer1
456            .partial_sign(message, &nonce1, &pubkeys, &agg_nonce)
457            .unwrap();
458        let partial2 = signer2
459            .partial_sign(message, &nonce2, &pubkeys, &agg_nonce)
460            .unwrap();
461        let partial3 = signer3
462            .partial_sign(message, &nonce3, &pubkeys, &agg_nonce)
463            .unwrap();
464
465        let signature =
466            aggregate_partial_signatures_with_nonce(&[partial1, partial2, partial3], &agg_nonce)
467                .unwrap();
468
469        assert!(verify_musig2(&agg_key, message, &signature));
470    }
471
472    #[test]
473    fn test_musig2_wrong_message() {
474        let signer1 = MuSig2Signer::new();
475        let signer2 = MuSig2Signer::new();
476
477        let pubkeys = vec![signer1.public_key(), signer2.public_key()];
478        let agg_key = aggregate_public_keys(&pubkeys).unwrap();
479
480        let message = b"Original message";
481
482        let (nonce1, commit1) = signer1.nonce_commitment();
483        let (nonce2, commit2) = signer2.nonce_commitment();
484
485        let commitments = vec![commit1, commit2];
486        let nonces = vec![nonce1.public_nonce(), nonce2.public_nonce()];
487        let agg_nonce = aggregate_nonces(&nonces, &commitments).unwrap();
488
489        let partial1 = signer1
490            .partial_sign(message, &nonce1, &pubkeys, &agg_nonce)
491            .unwrap();
492        let partial2 = signer2
493            .partial_sign(message, &nonce2, &pubkeys, &agg_nonce)
494            .unwrap();
495
496        let signature =
497            aggregate_partial_signatures_with_nonce(&[partial1, partial2], &agg_nonce).unwrap();
498
499        assert!(!verify_musig2(&agg_key, b"Different message", &signature));
500    }
501
502    #[test]
503    fn test_nonce_commitment_verification() {
504        let signer = MuSig2Signer::new();
505        let (nonce, commitment) = signer.nonce_commitment();
506
507        assert!(nonce.verify_commitment(&commitment));
508    }
509
510    #[test]
511    fn test_invalid_nonce_commitment() {
512        let signer = MuSig2Signer::new();
513        let (nonce1, _) = signer.nonce_commitment();
514        let (_, commitment2) = signer.nonce_commitment();
515
516        assert!(!nonce1.verify_commitment(&commitment2));
517    }
518
519    #[test]
520    fn test_aggregate_nonces_mismatch() {
521        let signer1 = MuSig2Signer::new();
522        let signer2 = MuSig2Signer::new();
523
524        let (nonce1, _) = signer1.nonce_commitment();
525        let (_, commit2) = signer2.nonce_commitment();
526
527        let result = aggregate_nonces(&[nonce1.public_nonce()], &[commit2]);
528        assert!(result.is_err());
529    }
530
531    #[test]
532    fn test_empty_signers() {
533        let result = aggregate_public_keys(&[]);
534        assert!(result.is_err());
535
536        let result = aggregate_nonces(&[], &[]);
537        assert!(result.is_err());
538
539        let result = aggregate_partial_signatures(&[]);
540        assert!(result.is_err());
541    }
542
543    #[test]
544    fn test_signature_serialization() {
545        let signer = MuSig2Signer::new();
546        let pubkeys = vec![signer.public_key()];
547        let agg_key = aggregate_public_keys(&pubkeys).unwrap();
548
549        let message = b"Serialization test";
550
551        let (nonce, commitment) = signer.nonce_commitment();
552        let commitments = vec![commitment];
553        let nonces = vec![nonce.public_nonce()];
554        let agg_nonce = aggregate_nonces(&nonces, &commitments).unwrap();
555
556        let partial = signer
557            .partial_sign(message, &nonce, &pubkeys, &agg_nonce)
558            .unwrap();
559        let signature = aggregate_partial_signatures_with_nonce(&[partial], &agg_nonce).unwrap();
560
561        let bytes = signature.to_bytes();
562        let recovered = MuSig2Signature::from_bytes(&bytes).unwrap();
563
564        assert!(verify_musig2(&agg_key, message, &recovered));
565    }
566
567    #[test]
568    fn test_signer_serialization() {
569        let signer = MuSig2Signer::new();
570        let bytes = signer.to_bytes();
571        let recovered = MuSig2Signer::from_bytes(&bytes).unwrap();
572
573        assert_eq!(
574            signer.public_key().to_bytes(),
575            recovered.public_key().to_bytes()
576        );
577    }
578
579    #[test]
580    fn test_deterministic_key_aggregation() {
581        let signer1 = MuSig2Signer::new();
582        let signer2 = MuSig2Signer::new();
583
584        let pubkeys = vec![signer1.public_key(), signer2.public_key()];
585        let agg1 = aggregate_public_keys(&pubkeys).unwrap();
586        let agg2 = aggregate_public_keys(&pubkeys).unwrap();
587
588        assert_eq!(agg1.to_bytes(), agg2.to_bytes());
589    }
590
591    #[test]
592    fn test_musig2_ten_signers() {
593        let signers: Vec<MuSig2Signer> = (0..10).map(|_| MuSig2Signer::new()).collect();
594        let pubkeys: Vec<MuSig2PublicKey> = signers.iter().map(|s| s.public_key()).collect();
595        let agg_key = aggregate_public_keys(&pubkeys).unwrap();
596
597        let message = b"Ten signer test";
598
599        let nonces_and_commits: Vec<(SigningNonce, NonceCommitment)> =
600            signers.iter().map(|s| s.nonce_commitment()).collect();
601
602        let nonces: Vec<MuSig2Nonce> = nonces_and_commits
603            .iter()
604            .map(|(n, _)| n.public_nonce())
605            .collect();
606        let commitments: Vec<NonceCommitment> =
607            nonces_and_commits.iter().map(|(_, c)| *c).collect();
608
609        let agg_nonce = aggregate_nonces(&nonces, &commitments).unwrap();
610
611        let partials: Vec<PartialSignature> = signers
612            .iter()
613            .zip(nonces_and_commits.iter())
614            .map(|(signer, (nonce, _))| {
615                signer
616                    .partial_sign(message, nonce, &pubkeys, &agg_nonce)
617                    .unwrap()
618            })
619            .collect();
620
621        let signature = aggregate_partial_signatures_with_nonce(&partials, &agg_nonce).unwrap();
622
623        assert!(verify_musig2(&agg_key, message, &signature));
624    }
625
626    #[test]
627    fn test_partial_signature_subset_fails() {
628        let signer1 = MuSig2Signer::new();
629        let signer2 = MuSig2Signer::new();
630        let signer3 = MuSig2Signer::new();
631
632        let (nonce1, commit1) = signer1.nonce_commitment();
633        let (nonce2, commit2) = signer2.nonce_commitment();
634        let (_, commit3) = signer3.nonce_commitment();
635
636        let commitments = vec![commit1, commit2, commit3];
637        let nonces = vec![nonce1.public_nonce(), nonce2.public_nonce()];
638
639        // This should fail because we have 3 commitments but only 2 nonces
640        let result = aggregate_nonces(&nonces, &commitments);
641        assert!(result.is_err());
642    }
643}