roast

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

coordinator.rs (8894B)


      1 //! ROAST coordinator
      2 use std::{
      3     collections::{HashMap, HashSet},
      4     sync::{Arc, Mutex},
      5 };
      6 
      7 use secp256kfun::{
      8     digest::typenum::U32,
      9     marker::{Public, Zero},
     10     Scalar,
     11 };
     12 
     13 use schnorr_fun::{
     14     frost::{Frost, XOnlyFrostKey},
     15     musig::Nonce,
     16     Message, Signature,
     17 };
     18 use sha2::Digest;
     19 
     20 pub struct Coordinator<'a, H, NG> {
     21     pub frost: Frost<H, NG>,
     22     pub frost_key: XOnlyFrostKey,
     23     state: Arc<Mutex<RoastState<'a>>>,
     24 }
     25 
     26 #[derive(Debug)]
     27 pub struct RoastState<'a> {
     28     message: Message<'a, Public>,
     29     responsive_signers: HashSet<usize>,
     30     malicious_signers: HashSet<usize>,
     31     session_counter: usize,
     32     latest_nonces: HashMap<usize, Nonce>,
     33     sessions: HashMap<usize, Arc<Mutex<RoastSignSession>>>,
     34     signer_session_map: HashMap<usize, usize>,
     35 }
     36 
     37 #[derive(Debug)]
     38 pub struct RoastSignSession {
     39     pub signers: HashSet<usize>,
     40     nonces: Vec<(usize, Nonce)>,
     41     sig_shares: Vec<Scalar<Public, Zero>>,
     42 }
     43 
     44 #[derive(Debug)]
     45 pub struct RoastResponse {
     46     pub recipients: Vec<usize>,
     47     pub combined_signature: Option<Signature>,
     48     pub nonce_set: Option<Vec<(usize, Nonce)>>,
     49 }
     50 
     51 impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG> {
     52     pub fn new(
     53         frost: Frost<H, NG>,
     54         frost_key: XOnlyFrostKey,
     55         message: Message<'a, Public>,
     56     ) -> Self {
     57         return Self {
     58             frost,
     59             frost_key,
     60             state: Arc::new(Mutex::new(RoastState {
     61                 message,
     62                 responsive_signers: HashSet::new(),
     63                 malicious_signers: HashSet::new(),
     64                 latest_nonces: HashMap::new(),
     65                 sessions: HashMap::new(),
     66                 signer_session_map: HashMap::new(),
     67                 session_counter: 0,
     68             })),
     69         };
     70     }
     71 
     72     // pub fn mark_malicious(self, roast_state: &mut MutexGuard<RoastState>, index: &usize) {
     73     //     roast_state.malicious_signers.insert(*index);
     74     //     if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
     75     //         panic!("not enough singers left to continue!");
     76     //     }
     77     // }
     78 
     79     // Main body of the ROAST coordinator algorithm
     80     pub fn receive(
     81         &self,
     82         index: usize,
     83         signature_share: Option<Scalar<Public, Zero>>,
     84         new_nonce: Nonce,
     85     ) -> RoastResponse {
     86         let mut roast_state = self.state.lock().expect("got lock");
     87         // dbg!(&roast_state);
     88 
     89         if roast_state.malicious_signers.contains(&index) {
     90             println!("Malicious signer tried to send signature! {}", index);
     91             return RoastResponse {
     92                 recipients: vec![index],
     93                 combined_signature: None,
     94                 nonce_set: None,
     95             };
     96         }
     97 
     98         if roast_state.responsive_signers.contains(&index) {
     99             println!(
    100                 "Unsolicited reply from signer {}, marking malicious.",
    101                 index
    102             );
    103 
    104             // Mark malicious
    105             roast_state.malicious_signers.insert(index);
    106             if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
    107                 panic!("not enough singers left to continue!");
    108             }
    109 
    110             return RoastResponse {
    111                 recipients: vec![index],
    112                 combined_signature: None,
    113                 nonce_set: None,
    114             };
    115         }
    116 
    117         // If this is not the inital message from S_i
    118         match roast_state.signer_session_map.get(&index) {
    119             Some(session_id) => {
    120                 println!(
    121                     "Party {} sent a signature for sign session {}",
    122                     index, session_id
    123                 );
    124                 // Get session from roast_state
    125                 let session = {
    126                     let roast_session = roast_state
    127                         .sessions
    128                         .get(&session_id)
    129                         .unwrap()
    130                         .lock()
    131                         .expect("got lock");
    132 
    133                     self.frost.start_sign_session(
    134                         &self.frost_key.clone(),
    135                         roast_session.nonces.clone(),
    136                         roast_state.message,
    137                     )
    138                 };
    139 
    140                 if !self.frost.verify_signature_share(
    141                     &self.frost_key.clone(),
    142                     &session,
    143                     index,
    144                     signature_share.expect(
    145                         "party unexpectedly provided None signature share for a sign session",
    146                     ),
    147                 ) {
    148                     println!("Invalid signature, marking {} malicious.", index);
    149                     roast_state.malicious_signers.insert(index);
    150                     if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
    151                         panic!("not enough singers left to continue!");
    152                     }
    153 
    154                     return RoastResponse {
    155                         recipients: vec![index],
    156                         combined_signature: None,
    157                         nonce_set: None,
    158                     };
    159                 }
    160 
    161                 // Reopen session within the roast state for writting
    162                 let mut roast_session = roast_state
    163                     .sessions
    164                     .get(&session_id)
    165                     .unwrap()
    166                     .lock()
    167                     .expect("got lock");
    168 
    169                 // Store valid signature
    170                 roast_session
    171                     .sig_shares
    172                     .push(signature_share.expect("party provided None signature share"));
    173                 println!("New signature from party {}", index);
    174 
    175                 // if we have t-of-n, combine!
    176                 if roast_session.sig_shares.len() >= self.frost_key.clone().threshold() {
    177                     println!("We have the threshold number of signatures, combining!");
    178                     dbg!(&roast_session.sig_shares);
    179                     let combined_sig = self.frost.combine_signature_shares(
    180                         &self.frost_key.clone(),
    181                         &session,
    182                         roast_session.sig_shares.clone(),
    183                     );
    184                     // return combined signature
    185                     return RoastResponse {
    186                         recipients: (0..self.frost_key.n_signers()).collect(),
    187                         combined_signature: Some(combined_sig),
    188                         nonce_set: None,
    189                     };
    190                 }
    191             }
    192             None => {}
    193         }
    194 
    195         // Store the recieved presignature shares
    196         roast_state.latest_nonces.insert(index, new_nonce);
    197 
    198         // Mark S_i as responsive
    199         println!("Marked {} as responsive", index.clone());
    200         roast_state.responsive_signers.insert(index);
    201 
    202         // if we now have t responsive signers:
    203         if roast_state.responsive_signers.len() >= self.frost_key.clone().threshold() {
    204             println!("We now have threshold number of responsive signers!");
    205             dbg!(&roast_state.responsive_signers);
    206             roast_state.session_counter += 1;
    207 
    208             // Look up the nonces
    209             let r_signers = roast_state.responsive_signers.clone();
    210             // we're not actually aggregating any nonces in this core yet since this will
    211             // require changes to frost.rs
    212             let nonces: Vec<_> = r_signers
    213                 .iter()
    214                 .cloned()
    215                 .map(|i| {
    216                     (
    217                         i,
    218                         *roast_state
    219                             .latest_nonces
    220                             .get(&i)
    221                             .expect("has submitted nonce"),
    222                     )
    223                 })
    224                 .collect();
    225 
    226             let sid = roast_state.session_counter.clone();
    227             // Clear responsive signers (otherwise we ban everyone and hang)
    228             roast_state.responsive_signers = HashSet::new();
    229             roast_state.sessions.insert(
    230                 sid,
    231                 Arc::new(Mutex::new(RoastSignSession {
    232                     signers: r_signers.clone(),
    233                     nonces: nonces.clone(),
    234                     sig_shares: vec![],
    235                 })),
    236             );
    237 
    238             // Remember the session for signers S_i
    239             for i in &r_signers {
    240                 roast_state.signer_session_map.insert(*i, sid);
    241             }
    242 
    243             // Send nonces to each signer S_i
    244             return RoastResponse {
    245                 recipients: r_signers.into_iter().collect(),
    246                 combined_signature: None,
    247                 nonce_set: Some(nonces),
    248             };
    249         }
    250 
    251         // (None, Some(roast_state.latest_nonces))
    252         return RoastResponse {
    253             recipients: vec![index],
    254             combined_signature: None,
    255             nonce_set: None,
    256         };
    257     }
    258 }