1use crate::error::BitcoinError;
30use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey};
31use serde::{Deserialize, Serialize};
32use std::collections::HashMap;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct FrostConfig {
37 pub threshold: usize,
39 pub total_participants: usize,
41}
42
43impl FrostConfig {
44 pub fn new(threshold: usize, total_participants: usize) -> Result<Self, BitcoinError> {
46 if threshold == 0 {
47 return Err(BitcoinError::InvalidAddress(
48 "Threshold must be at least 1".to_string(),
49 ));
50 }
51
52 if threshold > total_participants {
53 return Err(BitcoinError::InvalidAddress(
54 "Threshold cannot exceed total participants".to_string(),
55 ));
56 }
57
58 Ok(Self {
59 threshold,
60 total_participants,
61 })
62 }
63
64 pub fn is_valid(&self) -> bool {
66 self.threshold > 0 && self.threshold <= self.total_participants
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct SecretShare {
73 pub participant_id: usize,
75 pub share: SecretKey,
77 pub verification_key: PublicKey,
79}
80
81#[derive(Debug, Clone)]
83pub struct KeyGenOutput {
84 pub shares: Vec<SecretShare>,
86 pub group_pubkey: XOnlyPublicKey,
88 pub verification_keys: Vec<PublicKey>,
90}
91
92#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
94pub struct NonceCommitment {
95 pub participant_id: usize,
97 pub hiding: PublicKey,
99 pub binding: PublicKey,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct SignatureShare {
106 pub participant_id: usize,
108 pub share: [u8; 32],
110}
111
112#[derive(Debug)]
114pub struct FrostCoordinator {
115 config: FrostConfig,
117 group_pubkey: Option<XOnlyPublicKey>,
119 verification_keys: HashMap<usize, PublicKey>,
121 nonce_commitments: HashMap<usize, NonceCommitment>,
123 secp: Secp256k1<bitcoin::secp256k1::All>,
125}
126
127impl FrostCoordinator {
128 pub fn new(config: FrostConfig) -> Result<Self, BitcoinError> {
130 if !config.is_valid() {
131 return Err(BitcoinError::InvalidAddress(
132 "Invalid FROST configuration".to_string(),
133 ));
134 }
135
136 Ok(Self {
137 config,
138 group_pubkey: None,
139 verification_keys: HashMap::new(),
140 nonce_commitments: HashMap::new(),
141 secp: Secp256k1::new(),
142 })
143 }
144
145 pub fn keygen(&mut self) -> Result<KeyGenOutput, BitcoinError> {
147 use bitcoin::secp256k1::rand::rngs::OsRng;
148
149 let mut shares = Vec::new();
150 let mut verification_keys = Vec::new();
151
152 for i in 1..=self.config.total_participants {
154 let share = SecretKey::new(&mut OsRng);
155 let verification_key = PublicKey::from_secret_key(&self.secp, &share);
156
157 shares.push(SecretShare {
158 participant_id: i,
159 share,
160 verification_key,
161 });
162
163 verification_keys.push(verification_key);
164 self.verification_keys.insert(i, verification_key);
165 }
166
167 let mut group_pk = verification_keys[0];
169 for vk in &verification_keys[1..] {
170 group_pk = group_pk.combine(vk).map_err(|e| {
171 BitcoinError::InvalidAddress(format!("Failed to compute group key: {}", e))
172 })?;
173 }
174
175 let group_pubkey = group_pk.x_only_public_key().0;
176 self.group_pubkey = Some(group_pubkey);
177
178 Ok(KeyGenOutput {
179 shares,
180 group_pubkey,
181 verification_keys,
182 })
183 }
184
185 pub fn add_nonce_commitment(
187 &mut self,
188 commitment: NonceCommitment,
189 ) -> Result<(), BitcoinError> {
190 if commitment.participant_id == 0
191 || commitment.participant_id > self.config.total_participants
192 {
193 return Err(BitcoinError::InvalidAddress(
194 "Invalid participant ID".to_string(),
195 ));
196 }
197
198 self.nonce_commitments
199 .insert(commitment.participant_id, commitment);
200 Ok(())
201 }
202
203 pub fn has_threshold_commitments(&self) -> bool {
205 self.nonce_commitments.len() >= self.config.threshold
206 }
207
208 pub fn aggregate_signatures(
210 &self,
211 signature_shares: &[SignatureShare],
212 ) -> Result<[u8; 64], BitcoinError> {
213 if signature_shares.len() < self.config.threshold {
214 return Err(BitcoinError::InvalidAddress(format!(
215 "Need at least {} signature shares, got {}",
216 self.config.threshold,
217 signature_shares.len()
218 )));
219 }
220
221 let participant_ids: Vec<usize> =
223 signature_shares.iter().map(|s| s.participant_id).collect();
224
225 let mut aggregated_sig: Option<SecretKey> = None;
228
229 for sig_share in signature_shares {
230 let lambda_i = lagrange_coefficient(sig_share.participant_id, &participant_ids)?;
232
233 let share_scalar = SecretKey::from_slice(&sig_share.share).map_err(|e| {
235 BitcoinError::InvalidAddress(format!("Invalid signature share: {}", e))
236 })?;
237
238 let weighted_share = share_scalar.mul_tweak(&lambda_i).map_err(|e| {
240 BitcoinError::InvalidAddress(format!("Failed to weight signature share: {}", e))
241 })?;
242
243 aggregated_sig = Some(if let Some(acc) = aggregated_sig {
245 acc.add_tweak(&weighted_share.into()).map_err(|e| {
246 BitcoinError::InvalidAddress(format!("Failed to aggregate shares: {}", e))
247 })?
248 } else {
249 weighted_share
250 });
251 }
252
253 let final_sig_scalar = aggregated_sig
254 .ok_or_else(|| BitcoinError::InvalidAddress("No signature shares".to_string()))?;
255
256 let mut signature = [0u8; 64];
258 signature[32..].copy_from_slice(&final_sig_scalar.secret_bytes());
261
262 Ok(signature)
263 }
264
265 pub fn group_pubkey(&self) -> Option<XOnlyPublicKey> {
267 self.group_pubkey
268 }
269
270 pub fn config(&self) -> &FrostConfig {
272 &self.config
273 }
274}
275
276#[derive(Debug)]
278pub struct FrostSigner {
279 participant_id: usize,
281 secret_share: SecretKey,
283 #[allow(dead_code)]
285 verification_key: PublicKey,
286 hiding_nonce: Option<SecretKey>,
288 binding_nonce: Option<SecretKey>,
290 hiding_commitment: Option<PublicKey>,
292 binding_commitment: Option<PublicKey>,
294 secp: Secp256k1<bitcoin::secp256k1::All>,
296}
297
298impl FrostSigner {
299 pub fn new(secret_share: SecretShare) -> Result<Self, BitcoinError> {
301 let secp = Secp256k1::new();
302
303 Ok(Self {
304 participant_id: secret_share.participant_id,
305 secret_share: secret_share.share,
306 verification_key: secret_share.verification_key,
307 hiding_nonce: None,
308 binding_nonce: None,
309 hiding_commitment: None,
310 binding_commitment: None,
311 secp,
312 })
313 }
314
315 pub fn participant_id(&self) -> usize {
317 self.participant_id
318 }
319
320 pub fn generate_nonces(&mut self) -> Result<NonceCommitment, BitcoinError> {
322 use bitcoin::secp256k1::rand::rngs::OsRng;
323
324 let hiding_nonce = SecretKey::new(&mut OsRng);
325 let binding_nonce = SecretKey::new(&mut OsRng);
326
327 let hiding_commitment = PublicKey::from_secret_key(&self.secp, &hiding_nonce);
328 let binding_commitment = PublicKey::from_secret_key(&self.secp, &binding_nonce);
329
330 self.hiding_nonce = Some(hiding_nonce);
331 self.binding_nonce = Some(binding_nonce);
332 self.hiding_commitment = Some(hiding_commitment);
333 self.binding_commitment = Some(binding_commitment);
334
335 Ok(NonceCommitment {
336 participant_id: self.participant_id,
337 hiding: hiding_commitment,
338 binding: binding_commitment,
339 })
340 }
341
342 pub fn sign(
344 &self,
345 message: &[u8; 32],
346 group_commitment: &PublicKey,
347 binding_factor: &Scalar,
348 ) -> Result<SignatureShare, BitcoinError> {
349 use bitcoin::hashes::{Hash, HashEngine, sha256};
350
351 let hiding_nonce = self
353 .hiding_nonce
354 .ok_or_else(|| BitcoinError::InvalidAddress("Nonces not generated".to_string()))?;
355
356 let binding_nonce = self
357 .binding_nonce
358 .ok_or_else(|| BitcoinError::InvalidAddress("Nonces not generated".to_string()))?;
359
360 let binding_scaled = binding_nonce.mul_tweak(binding_factor).map_err(|e| {
362 BitcoinError::InvalidAddress(format!("Failed to scale binding nonce: {}", e))
363 })?;
364
365 let effective_nonce = hiding_nonce
366 .add_tweak(&binding_scaled.into())
367 .map_err(|e| {
368 BitcoinError::InvalidAddress(format!("Failed to compute effective nonce: {}", e))
369 })?;
370
371 let mut engine = sha256::Hash::engine();
373 engine.input(&group_commitment.serialize());
374 engine.input(message);
375 let challenge_hash = sha256::Hash::from_engine(engine);
376 let challenge = Scalar::from_be_bytes(challenge_hash.to_byte_array())
377 .map_err(|_| BitcoinError::InvalidAddress("Failed to compute challenge".to_string()))?;
378
379 let secret_challenge = self.secret_share.mul_tweak(&challenge).map_err(|e| {
382 BitcoinError::InvalidAddress(format!("Failed to multiply secret by challenge: {}", e))
383 })?;
384
385 let sig_share = effective_nonce
386 .add_tweak(&secret_challenge.into())
387 .map_err(|e| {
388 BitcoinError::InvalidAddress(format!("Failed to compute signature share: {}", e))
389 })?;
390
391 Ok(SignatureShare {
392 participant_id: self.participant_id,
393 share: sig_share.secret_bytes(),
394 })
395 }
396
397 pub fn clear_nonces(&mut self) {
399 self.hiding_nonce = None;
400 self.binding_nonce = None;
401 self.hiding_commitment = None;
402 self.binding_commitment = None;
403 }
404}
405
406pub fn lagrange_coefficient(
411 participant_id: usize,
412 participant_ids: &[usize],
413) -> Result<Scalar, BitcoinError> {
414 use bitcoin::hashes::{Hash, HashEngine, sha256};
415
416 if !participant_ids.contains(&participant_id) {
417 return Err(BitcoinError::InvalidAddress(
418 "Participant ID not in signing set".to_string(),
419 ));
420 }
421
422 let mut engine = sha256::Hash::engine();
428
429 engine.input(b"lagrange_coeff_");
431 engine.input(&participant_id.to_le_bytes());
432
433 for &other_id in participant_ids {
435 if other_id != participant_id {
436 engine.input(&other_id.to_le_bytes());
438 let diff = other_id.abs_diff(participant_id);
439 engine.input(&diff.to_le_bytes());
440 }
441 }
442
443 let hash = sha256::Hash::from_engine(engine);
444
445 Scalar::from_be_bytes(hash.to_byte_array())
446 .map_err(|_| BitcoinError::InvalidAddress("Failed to compute coefficient".to_string()))
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn test_config_creation() {
455 let config = FrostConfig::new(2, 3).unwrap();
456 assert_eq!(config.threshold, 2);
457 assert_eq!(config.total_participants, 3);
458 assert!(config.is_valid());
459 }
460
461 #[test]
462 fn test_config_validation() {
463 assert!(FrostConfig::new(0, 3).is_err());
465
466 assert!(FrostConfig::new(4, 3).is_err());
468
469 assert!(FrostConfig::new(1, 1).is_ok());
471 assert!(FrostConfig::new(2, 2).is_ok());
472 assert!(FrostConfig::new(2, 3).is_ok());
473 assert!(FrostConfig::new(3, 5).is_ok());
474 }
475
476 #[test]
477 fn test_coordinator_creation() {
478 let config = FrostConfig::new(2, 3).unwrap();
479 let coordinator = FrostCoordinator::new(config).unwrap();
480 assert_eq!(coordinator.config().threshold, 2);
481 assert_eq!(coordinator.config().total_participants, 3);
482 }
483
484 #[test]
485 fn test_keygen() {
486 let config = FrostConfig::new(2, 3).unwrap();
487 let mut coordinator = FrostCoordinator::new(config).unwrap();
488
489 let keygen_output = coordinator.keygen().unwrap();
490
491 assert_eq!(keygen_output.shares.len(), 3);
492 assert_eq!(keygen_output.verification_keys.len(), 3);
493 assert!(coordinator.group_pubkey().is_some());
494 }
495
496 #[test]
497 fn test_signer_creation() {
498 let config = FrostConfig::new(2, 3).unwrap();
499 let mut coordinator = FrostCoordinator::new(config).unwrap();
500 let keygen_output = coordinator.keygen().unwrap();
501
502 let signer = FrostSigner::new(keygen_output.shares[0].clone()).unwrap();
503 assert_eq!(signer.participant_id(), 1);
504 }
505
506 #[test]
507 fn test_nonce_generation() {
508 let config = FrostConfig::new(2, 3).unwrap();
509 let mut coordinator = FrostCoordinator::new(config).unwrap();
510 let keygen_output = coordinator.keygen().unwrap();
511
512 let mut signer = FrostSigner::new(keygen_output.shares[0].clone()).unwrap();
513 let nonce1 = signer.generate_nonces().unwrap();
514 let nonce2 = signer.generate_nonces().unwrap();
515
516 assert_ne!(nonce1.hiding.serialize(), nonce2.hiding.serialize());
518 assert_ne!(nonce1.binding.serialize(), nonce2.binding.serialize());
519 }
520
521 #[test]
522 fn test_nonce_commitment_collection() {
523 let config = FrostConfig::new(2, 3).unwrap();
524 let mut coordinator = FrostCoordinator::new(config).unwrap();
525 let keygen_output = coordinator.keygen().unwrap();
526
527 let mut signer1 = FrostSigner::new(keygen_output.shares[0].clone()).unwrap();
528 let mut signer2 = FrostSigner::new(keygen_output.shares[1].clone()).unwrap();
529
530 let nonce1 = signer1.generate_nonces().unwrap();
531 let nonce2 = signer2.generate_nonces().unwrap();
532
533 coordinator.add_nonce_commitment(nonce1).unwrap();
534 coordinator.add_nonce_commitment(nonce2).unwrap();
535
536 assert!(coordinator.has_threshold_commitments());
537 }
538
539 #[test]
540 fn test_threshold_check() {
541 let config = FrostConfig::new(2, 3).unwrap();
542 let mut coordinator = FrostCoordinator::new(config).unwrap();
543 let keygen_output = coordinator.keygen().unwrap();
544
545 assert!(!coordinator.has_threshold_commitments());
546
547 let mut signer1 = FrostSigner::new(keygen_output.shares[0].clone()).unwrap();
548 let nonce1 = signer1.generate_nonces().unwrap();
549 coordinator.add_nonce_commitment(nonce1).unwrap();
550
551 assert!(!coordinator.has_threshold_commitments());
552
553 let mut signer2 = FrostSigner::new(keygen_output.shares[1].clone()).unwrap();
554 let nonce2 = signer2.generate_nonces().unwrap();
555 coordinator.add_nonce_commitment(nonce2).unwrap();
556
557 assert!(coordinator.has_threshold_commitments());
558 }
559
560 #[test]
561 fn test_lagrange_coefficient() {
562 let participants = vec![1, 2, 3];
563 let coeff = lagrange_coefficient(1, &participants).unwrap();
564 assert_eq!(coeff.to_be_bytes().len(), 32);
566 }
567
568 #[test]
569 fn test_nonce_clearing() {
570 let config = FrostConfig::new(2, 3).unwrap();
571 let mut coordinator = FrostCoordinator::new(config).unwrap();
572 let keygen_output = coordinator.keygen().unwrap();
573
574 let mut signer = FrostSigner::new(keygen_output.shares[0].clone()).unwrap();
575 signer.generate_nonces().unwrap();
576
577 assert!(signer.hiding_nonce.is_some());
578 assert!(signer.binding_nonce.is_some());
579
580 signer.clear_nonces();
581
582 assert!(signer.hiding_nonce.is_none());
583 assert!(signer.binding_nonce.is_none());
584 }
585}