rustywallet_multisig/
musig.rs

1//! MuSig2 Schnorr multisig support
2//!
3//! Implements key aggregation for n-of-n Schnorr multisig (BIP327).
4//! Note: This is a simplified implementation for key aggregation only.
5//! Full MuSig2 signing requires nonce handling which is complex.
6
7use crate::error::{MultisigError, Result};
8use secp256k1::{Secp256k1, PublicKey, SecretKey};
9use sha2::{Sha256, Digest};
10
11/// MuSig2 key aggregation context
12#[derive(Debug, Clone)]
13pub struct MuSigKeyAgg {
14    /// Individual public keys (33 bytes compressed)
15    pub pubkeys: Vec<[u8; 33]>,
16    /// Aggregated public key (33 bytes compressed)
17    pub aggregated_pubkey: [u8; 33],
18    /// X-only aggregated public key (32 bytes)
19    pub xonly_pubkey: [u8; 32],
20    /// Key aggregation coefficients
21    pub coefficients: Vec<[u8; 32]>,
22    /// Whether the aggregated key needed negation
23    pub parity: bool,
24}
25
26impl MuSigKeyAgg {
27    /// Aggregate multiple public keys into a single key (BIP327 KeyAgg)
28    ///
29    /// # Arguments
30    /// * `pubkeys` - List of 33-byte compressed public keys
31    ///
32    /// # Returns
33    /// MuSigKeyAgg context with aggregated key
34    pub fn new(pubkeys: Vec<[u8; 33]>) -> Result<Self> {
35        if pubkeys.len() < 2 {
36            return Err(MultisigError::NotEnoughKeys {
37                need: 2,
38                got: pubkeys.len(),
39            });
40        }
41
42        if pubkeys.len() > 100 {
43            return Err(MultisigError::TooManyKeys { count: pubkeys.len() });
44        }
45
46        // Check for duplicates
47        for (i, pk1) in pubkeys.iter().enumerate() {
48            for pk2 in pubkeys.iter().skip(i + 1) {
49                if pk1 == pk2 {
50                    return Err(MultisigError::DuplicateKey { index: i });
51                }
52            }
53        }
54
55        let secp = Secp256k1::new();
56
57        // Sort pubkeys lexicographically (BIP327)
58        let mut sorted_pubkeys = pubkeys.clone();
59        sorted_pubkeys.sort();
60
61        // Compute L = H(pk1 || pk2 || ... || pkn)
62        let l_hash = compute_l_hash(&sorted_pubkeys);
63
64        // Compute coefficients for each key
65        let mut coefficients = Vec::with_capacity(sorted_pubkeys.len());
66        for pk in &sorted_pubkeys {
67            let coeff = compute_key_agg_coeff(&l_hash, pk, &sorted_pubkeys);
68            coefficients.push(coeff);
69        }
70
71        // Aggregate: Q = sum(a_i * P_i)
72        let mut agg_point: Option<PublicKey> = None;
73
74        for (pk_bytes, coeff) in sorted_pubkeys.iter().zip(coefficients.iter()) {
75            let pk = PublicKey::from_slice(pk_bytes)
76                .map_err(|e| MultisigError::InvalidPublicKey(e.to_string()))?;
77
78            // Multiply pubkey by coefficient
79            let tweaked = tweak_pubkey_mul(&secp, &pk, coeff)?;
80
81            agg_point = match agg_point {
82                None => Some(tweaked),
83                Some(acc) => Some(acc.combine(&tweaked)
84                    .map_err(|e| MultisigError::InvalidPublicKey(e.to_string()))?),
85            };
86        }
87
88        let agg_pubkey = agg_point.ok_or_else(|| {
89            MultisigError::InvalidPublicKey("Failed to aggregate keys".to_string())
90        })?;
91
92        // Get x-only pubkey and parity
93        let (xonly, parity) = agg_pubkey.x_only_public_key();
94        let mut xonly_bytes = [0u8; 32];
95        xonly_bytes.copy_from_slice(&xonly.serialize());
96
97        Ok(Self {
98            pubkeys: sorted_pubkeys,
99            aggregated_pubkey: agg_pubkey.serialize(),
100            xonly_pubkey: xonly_bytes,
101            coefficients,
102            parity: parity == secp256k1::Parity::Odd,
103        })
104    }
105
106    /// Get the aggregated public key (33 bytes compressed)
107    pub fn aggregated_pubkey(&self) -> &[u8; 33] {
108        &self.aggregated_pubkey
109    }
110
111    /// Get the x-only aggregated public key (32 bytes, for Taproot)
112    pub fn xonly_pubkey(&self) -> &[u8; 32] {
113        &self.xonly_pubkey
114    }
115
116    /// Get the coefficient for a specific public key
117    pub fn coefficient_for(&self, pubkey: &[u8; 33]) -> Option<&[u8; 32]> {
118        self.pubkeys
119            .iter()
120            .position(|pk| pk == pubkey)
121            .map(|idx| &self.coefficients[idx])
122    }
123
124    /// Check if a public key is part of this aggregation
125    pub fn contains(&self, pubkey: &[u8; 33]) -> bool {
126        self.pubkeys.contains(pubkey)
127    }
128
129    /// Get number of participants
130    pub fn participant_count(&self) -> usize {
131        self.pubkeys.len()
132    }
133
134    /// Tweak the aggregated key (for Taproot)
135    pub fn tweak_add(&self, tweak: &[u8; 32]) -> Result<[u8; 33]> {
136        let secp = Secp256k1::new();
137        let pk = PublicKey::from_slice(&self.aggregated_pubkey)
138            .map_err(|e| MultisigError::InvalidPublicKey(e.to_string()))?;
139
140        let tweaked = pk.add_exp_tweak(&secp, &secp256k1::Scalar::from_be_bytes(*tweak).unwrap())
141            .map_err(|e| MultisigError::InvalidPublicKey(e.to_string()))?;
142
143        Ok(tweaked.serialize())
144    }
145}
146
147/// Compute L = tagged_hash("KeyAgg list", pk1 || pk2 || ... || pkn)
148fn compute_l_hash(pubkeys: &[[u8; 33]]) -> [u8; 32] {
149    let tag = b"KeyAgg list";
150    let tag_hash = Sha256::digest(tag);
151
152    let mut hasher = Sha256::new();
153    hasher.update(tag_hash);
154    hasher.update(tag_hash);
155    for pk in pubkeys {
156        hasher.update(pk);
157    }
158
159    let mut result = [0u8; 32];
160    result.copy_from_slice(&hasher.finalize());
161    result
162}
163
164/// Compute key aggregation coefficient for a pubkey
165fn compute_key_agg_coeff(l_hash: &[u8; 32], pubkey: &[u8; 33], all_pubkeys: &[[u8; 33]]) -> [u8; 32] {
166    // If this is the "second" unique pubkey, coefficient is 1
167    // This is an optimization from BIP327
168    if is_second_unique(pubkey, all_pubkeys) {
169        let mut one = [0u8; 32];
170        one[31] = 1;
171        return one;
172    }
173
174    // Otherwise: a_i = tagged_hash("KeyAgg coefficient", L || pk_i)
175    let tag = b"KeyAgg coefficient";
176    let tag_hash = Sha256::digest(tag);
177
178    let mut hasher = Sha256::new();
179    hasher.update(tag_hash);
180    hasher.update(tag_hash);
181    hasher.update(l_hash);
182    hasher.update(pubkey);
183
184    let mut result = [0u8; 32];
185    result.copy_from_slice(&hasher.finalize());
186    result
187}
188
189/// Check if pubkey is the "second" unique pubkey (BIP327 optimization)
190fn is_second_unique(pubkey: &[u8; 33], all_pubkeys: &[[u8; 33]]) -> bool {
191    if all_pubkeys.len() < 2 {
192        return false;
193    }
194
195    // Find first pubkey that's different from the first one
196    let first = &all_pubkeys[0];
197    for pk in all_pubkeys.iter().skip(1) {
198        if pk != first {
199            return pk == pubkey;
200        }
201    }
202    false
203}
204
205/// Multiply a public key by a scalar
206fn tweak_pubkey_mul(secp: &Secp256k1<secp256k1::All>, pk: &PublicKey, scalar: &[u8; 32]) -> Result<PublicKey> {
207    // Convert scalar to SecretKey (which acts as a scalar)
208    let sk = SecretKey::from_slice(scalar)
209        .map_err(|e| MultisigError::InvalidPublicKey(format!("Invalid scalar: {}", e)))?;
210
211    // pk * scalar = (sk * G) where we want pk * scalar
212    // We use the fact that pk.mul_tweak multiplies by scalar
213    pk.mul_tweak(secp, &sk.into())
214        .map_err(|e| MultisigError::InvalidPublicKey(e.to_string()))
215}
216
217/// Generate a P2TR address from aggregated MuSig key
218pub fn musig_to_p2tr_address(key_agg: &MuSigKeyAgg, network: crate::address::Network) -> Result<String> {
219    use bech32::Hrp;
220
221    let hrp = match network {
222        crate::address::Network::Mainnet => Hrp::parse("bc").unwrap(),
223        crate::address::Network::Testnet => Hrp::parse("tb").unwrap(),
224    };
225
226    bech32::segwit::encode(hrp, bech32::segwit::VERSION_1, &key_agg.xonly_pubkey)
227        .map_err(|e| MultisigError::AddressFailed(e.to_string()))
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use rustywallet_keys::prelude::PrivateKey;
234
235    fn generate_pubkeys(count: usize) -> Vec<[u8; 33]> {
236        (0..count)
237            .map(|_| PrivateKey::random().public_key().to_compressed())
238            .collect()
239    }
240
241    #[test]
242    fn test_key_aggregation_2_of_2() {
243        let pubkeys = generate_pubkeys(2);
244        let key_agg = MuSigKeyAgg::new(pubkeys.clone()).unwrap();
245
246        assert_eq!(key_agg.participant_count(), 2);
247        assert!(key_agg.contains(&pubkeys[0]));
248        assert!(key_agg.contains(&pubkeys[1]));
249
250        // Aggregated key should be different from individual keys
251        assert_ne!(&key_agg.aggregated_pubkey, &pubkeys[0]);
252        assert_ne!(&key_agg.aggregated_pubkey, &pubkeys[1]);
253    }
254
255    #[test]
256    fn test_key_aggregation_3_of_3() {
257        let pubkeys = generate_pubkeys(3);
258        let key_agg = MuSigKeyAgg::new(pubkeys).unwrap();
259
260        assert_eq!(key_agg.participant_count(), 3);
261        assert_eq!(key_agg.xonly_pubkey.len(), 32);
262    }
263
264    #[test]
265    fn test_deterministic_aggregation() {
266        let pubkeys = generate_pubkeys(3);
267
268        let key_agg1 = MuSigKeyAgg::new(pubkeys.clone()).unwrap();
269        let key_agg2 = MuSigKeyAgg::new(pubkeys).unwrap();
270
271        assert_eq!(key_agg1.aggregated_pubkey, key_agg2.aggregated_pubkey);
272        assert_eq!(key_agg1.xonly_pubkey, key_agg2.xonly_pubkey);
273    }
274
275    #[test]
276    fn test_order_independent() {
277        let pubkeys = generate_pubkeys(3);
278        let mut reversed = pubkeys.clone();
279        reversed.reverse();
280
281        let key_agg1 = MuSigKeyAgg::new(pubkeys).unwrap();
282        let key_agg2 = MuSigKeyAgg::new(reversed).unwrap();
283
284        // Should produce same result regardless of input order
285        assert_eq!(key_agg1.aggregated_pubkey, key_agg2.aggregated_pubkey);
286    }
287
288    #[test]
289    fn test_duplicate_key_rejected() {
290        let pk = PrivateKey::random().public_key().to_compressed();
291        let pubkeys = vec![pk, pk];
292
293        let result = MuSigKeyAgg::new(pubkeys);
294        assert!(result.is_err());
295    }
296
297    #[test]
298    fn test_single_key_rejected() {
299        let pubkeys = generate_pubkeys(1);
300        let result = MuSigKeyAgg::new(pubkeys);
301        assert!(result.is_err());
302    }
303
304    #[test]
305    fn test_coefficients_exist() {
306        let pubkeys = generate_pubkeys(3);
307        let key_agg = MuSigKeyAgg::new(pubkeys.clone()).unwrap();
308
309        for pk in &key_agg.pubkeys {
310            assert!(key_agg.coefficient_for(pk).is_some());
311        }
312    }
313
314    #[test]
315    fn test_p2tr_address_generation() {
316        let pubkeys = generate_pubkeys(2);
317        let key_agg = MuSigKeyAgg::new(pubkeys).unwrap();
318
319        let address = musig_to_p2tr_address(&key_agg, crate::address::Network::Mainnet).unwrap();
320        assert!(address.starts_with("bc1p"));
321
322        let testnet_addr = musig_to_p2tr_address(&key_agg, crate::address::Network::Testnet).unwrap();
323        assert!(testnet_addr.starts_with("tb1p"));
324    }
325}