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 }