rustywallet_multisig/
musig.rs1use crate::error::{MultisigError, Result};
8use secp256k1::{Secp256k1, PublicKey, SecretKey};
9use sha2::{Sha256, Digest};
10
11#[derive(Debug, Clone)]
13pub struct MuSigKeyAgg {
14 pub pubkeys: Vec<[u8; 33]>,
16 pub aggregated_pubkey: [u8; 33],
18 pub xonly_pubkey: [u8; 32],
20 pub coefficients: Vec<[u8; 32]>,
22 pub parity: bool,
24}
25
26impl MuSigKeyAgg {
27 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 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 let mut sorted_pubkeys = pubkeys.clone();
59 sorted_pubkeys.sort();
60
61 let l_hash = compute_l_hash(&sorted_pubkeys);
63
64 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 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 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 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 pub fn aggregated_pubkey(&self) -> &[u8; 33] {
108 &self.aggregated_pubkey
109 }
110
111 pub fn xonly_pubkey(&self) -> &[u8; 32] {
113 &self.xonly_pubkey
114 }
115
116 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 pub fn contains(&self, pubkey: &[u8; 33]) -> bool {
126 self.pubkeys.contains(pubkey)
127 }
128
129 pub fn participant_count(&self) -> usize {
131 self.pubkeys.len()
132 }
133
134 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
147fn 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
164fn compute_key_agg_coeff(l_hash: &[u8; 32], pubkey: &[u8; 33], all_pubkeys: &[[u8; 33]]) -> [u8; 32] {
166 if is_second_unique(pubkey, all_pubkeys) {
169 let mut one = [0u8; 32];
170 one[31] = 1;
171 return one;
172 }
173
174 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
189fn is_second_unique(pubkey: &[u8; 33], all_pubkeys: &[[u8; 33]]) -> bool {
191 if all_pubkeys.len() < 2 {
192 return false;
193 }
194
195 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
205fn tweak_pubkey_mul(secp: &Secp256k1<secp256k1::All>, pk: &PublicKey, scalar: &[u8; 32]) -> Result<PublicKey> {
207 let sk = SecretKey::from_slice(scalar)
209 .map_err(|e| MultisigError::InvalidPublicKey(format!("Invalid scalar: {}", e)))?;
210
211 pk.mul_tweak(secp, &sk.into())
214 .map_err(|e| MultisigError::InvalidPublicKey(e.to_string()))
215}
216
217pub 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 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 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}