roast

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

commit 03513f02a9a5b39c714442fd4029c7926fd9a73a
parent 445c90af2747487d6acc46f9b1dee16351c98b22
Author: nickfarrow <nick@nickfarrow.com>
Date:   Thu, 29 Sep 2022 11:53:41 +1000

successful manual test 2-of-3 combined sig

Diffstat:
M.gitignore | 2+-
MREADME.md | 3+++
Mendpoints/src/coordinator.rs | 37+++++++++++++++++++++++++++++++++++++
Droastlib/src/client.rs | 130-------------------------------------------------------------------------------
Mroastlib/src/coordinator.rs | 125+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mroastlib/src/frost.rs | 11+++--------
Mroastlib/src/lib.rs | 2+-
Mroastlib/src/main.rs | 48+++++++++++++++++++++++++++++++++++++++++-------
Aroastlib/src/signer.rs | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 243 insertions(+), 201 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,3 @@ /target roastlib/target/ -server-client/target/ +endpoints/target/ diff --git a/README.md b/README.md @@ -1,6 +1,9 @@ Experimental -> unfishished and not ready for use +![image](https://user-images.githubusercontent.com/24557779/192925900-3c15cddf-a467-47be-80a5-3b04b0acbd47.png) + + ## todo Finish roastlib and test without specifying any io methods from client to server diff --git a/endpoints/src/coordinator.rs b/endpoints/src/coordinator.rs @@ -93,3 +93,40 @@ pub async fn main() -> () { // [0].as_slice(), // message, // ); + + + +// OLD HTTP TRASH +// +// +// HTTP call roast_server receive +// let (combined_sig, nonce_set) = +// let request_url = roast_server.clone() +// + "/init?" +// + &serde_json::to_string(&frost_key).expect("valid serialization"); +// let response = reqwest::get(&request_url).await.expect("valid request"); +// dbg!(response); + +// roast_server +// .receive_signature(my_index, None, initial_nonce.public) +// .await; + +// match combined_sig { +// Some(_) => { +// println!("got combined sig!"); +// } +// None => { +// // println!("Sent partial signature {} to roast..", i) +// } +// }; +// match nonce_set { +// Some(nonces) => { +// println!("Got nonces {:?}", nonces); +// } +// None => println!("No new nonces!?"), +// }; + +// let mut my_nonces = HashMap::new(); +// my_nonces.insert(0 as usize, initial_nonce); + +// pub async fn sign diff --git a/roastlib/src/client.rs b/roastlib/src/client.rs @@ -1,130 +0,0 @@ -use secp256kfun::{ - digest::typenum::U32, - marker::{Public, Zero}, - Scalar, -}; - -use schnorr_fun::{ - frost::{Frost, XOnlyFrostKey}, - musig::{Nonce, NonceKeyPair}, - nonce::NonceGen, - Message, -}; -use sha2::Digest; - -pub struct RoastClient<'a, H, NG> { - frost: Frost<H, NG>, - frost_key: XOnlyFrostKey, - my_index: usize, - secret_share: Scalar, - message: Message<'a, Public>, - my_nonces: Vec<NonceKeyPair>, -} - -impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG: NonceGen> RoastClient<'a, H, NG> { - pub fn new( - frost: Frost<H, NG>, - frost_key: XOnlyFrostKey, - my_index: usize, - secret_share: Scalar, - initial_nonce_sid: &[u8], - message: Message<'a>, - ) -> RoastClient<'a, H, NG> { - let initial_nonce = frost.gen_nonce( - &secret_share, - &initial_nonce_sid, - Some(frost_key.public_key().normalize()), - Some(message), - ); - let my_nonces = vec![initial_nonce]; - - RoastClient { - frost, - frost_key, - my_index, - secret_share, - message, - my_nonces, - } - } - - fn new_nonce(&self, sid: &[u8]) -> NonceKeyPair { - self.frost.gen_nonce( - &self.secret_share, - sid, - Some(self.frost_key.public_key().normalize()), - Some(self.message), - ) - } - - pub fn start(self) -> (Option<Scalar<Public, Zero>>, Nonce) { - ( - None, - self.my_nonces.last().expect("some nonce").clone().public(), - ) - } - - pub fn sign( - &mut self, - nonce_set: Vec<(usize, Nonce)>, - ) -> (Option<Scalar<Public, Zero>>, Nonce) { - let session = self.frost.start_sign_session( - &self.frost_key, - nonce_set, - Message::plain("test", b"test"), - ); - let my_nonce = self - .my_nonces - .pop() - .expect("some nonce available to use for signing"); - let sig = self.frost.sign( - &self.frost_key, - &session, - self.my_index, - &self.secret_share, - my_nonce, - ); - // call server with (sig, self.new_nonce()) - self.my_nonces.push(self.new_nonce(&[0])); - - ( - Some(sig), - self.my_nonces.last().expect("some nonce").public(), - ) - } -} - -// OLD HTTP TRASH -// -// -// HTTP call roast_server receive -// let (combined_sig, nonce_set) = -// let request_url = roast_server.clone() -// + "/init?" -// + &serde_json::to_string(&frost_key).expect("valid serialization"); -// let response = reqwest::get(&request_url).await.expect("valid request"); -// dbg!(response); - -// roast_server -// .receive_signature(my_index, None, initial_nonce.public) -// .await; - -// match combined_sig { -// Some(_) => { -// println!("got combined sig!"); -// } -// None => { -// // println!("Sent partial signature {} to roast..", i) -// } -// }; -// match nonce_set { -// Some(nonces) => { -// println!("Got nonces {:?}", nonces); -// } -// None => println!("No new nonces!?"), -// }; - -// let mut my_nonces = HashMap::new(); -// my_nonces.insert(0 as usize, initial_nonce); - -// pub async fn sign diff --git a/roastlib/src/coordinator.rs b/roastlib/src/coordinator.rs @@ -11,8 +11,7 @@ use secp256kfun::{ use schnorr_fun::{ frost::{Frost, XOnlyFrostKey}, - musig::{Nonce, NonceKeyPair}, - nonce::NonceGen, + musig::Nonce, Message, Signature, }; use sha2::Digest; @@ -29,11 +28,12 @@ pub struct RoastState<'a> { malicious_signers: HashSet<usize>, latest_nonces: HashMap<usize, Nonce>, sessions: HashMap<usize, Arc<Mutex<RoastSignSession>>>, + signer_session_map: HashMap<usize, usize>, session_counter: usize, } pub struct RoastSignSession { - signers: HashSet<usize>, + pub signers: HashSet<usize>, nonces: Vec<(usize, Nonce)>, sig_shares: Vec<Scalar<Public, Zero>>, } @@ -53,6 +53,7 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG malicious_signers: HashSet::new(), latest_nonces: HashMap::new(), sessions: HashMap::new(), + signer_session_map: HashMap::new(), session_counter: 0, })), }; @@ -67,7 +68,7 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG } // Main body of the ROAST coordinator algorithm - pub fn receive_signature( + pub fn process( &self, index: usize, signature_share: Option<Scalar<Public, Zero>>, @@ -90,61 +91,71 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG } // If this is not the inital message from S_i - if roast_state.sessions.contains_key(&index) { - let mut roast_session = roast_state - .sessions - .get(&index) - .unwrap() - .lock() - .expect("got lock"); - - let session = self.frost.start_sign_session( - &self.frost_key.clone(), - roast_session.nonces.clone(), - roast_state.message, - ); - - if !self.frost.verify_signature_share( - &self.frost_key.clone(), - &session, - index, - signature_share.expect("party provided None signature share"), - ) { - println!("Invalid signature, marking {} malicious.", index); - self.mark_malicious(&index); - return (None, None); - } + match roast_state.signer_session_map.get(&index) { + Some(session_id) => { + let mut roast_session = roast_state + .sessions + .get(&session_id) + .unwrap() + .lock() + .expect("got lock"); + + let session = self.frost.start_sign_session( + &self.frost_key.clone(), + roast_session.nonces.clone(), + roast_state.message, + ); - // Store valid signature - roast_session - .sig_shares - .push(signature_share.expect("party provided None signature share")); - println!("New signature from party {}", index); + // dbg!(&self.frost_key.clone(), &session, index, signature_share); - // if we have t-of-n, combine! - if roast_session.sig_shares.len() >= self.frost_key.clone().threshold() { - println!("We have the threshold number of signatures, combining!"); - let combined_sig = self.frost.combine_signature_shares( + if !self.frost.verify_signature_share( &self.frost_key.clone(), &session, - roast_session.sig_shares.clone(), - ); - // return combined signature - return (Some(combined_sig), None); + index, + signature_share.expect("party provided None signature share"), + ) { + println!("Invalid signature, marking {} malicious.", index); + self.mark_malicious(&index); + return (None, None); + } + + // Store valid signature + roast_session + .sig_shares + .push(signature_share.expect("party provided None signature share")); + println!("New signature from party {}", index); + + // if we have t-of-n, combine! + + if roast_session.sig_shares.len() >= self.frost_key.clone().threshold() { + println!("We have the threshold number of signatures, combining!"); + dbg!(&roast_session.sig_shares); + let combined_sig = self.frost.combine_signature_shares( + &self.frost_key.clone(), + &session, + roast_session.sig_shares.clone(), + ); + // return combined signature + return (Some(combined_sig), None); + } } + None => {} } // Store the recieved presignature shares roast_state.latest_nonces.insert(index, new_nonce); // Mark S_i as responsive + println!("Marked {} as responsive", index.clone()); roast_state.responsive_signers.insert(index); // if we now have t responsive signers: if roast_state.responsive_signers.len() >= self.frost_key.clone().threshold() { println!("We now have threshold number of responsive signers!"); + dbg!(&roast_state.responsive_signers); roast_state.session_counter += 1; - // build the presignature (aggregate the nonces). + + // Look up the nonces let r_signers = roast_state.responsive_signers.clone(); // we're not actually aggregating any nonces in this core yet since this will // require changes to frost.rs @@ -162,27 +173,33 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG }) .collect(); + let sid = roast_state.session_counter.clone(); for i in r_signers.clone() { + // Remember the session of this signer + roast_state.signer_session_map.insert(i, sid); + // send agg nonce to signers (rho, R) - roast_state.sessions.insert( - i, - Arc::new(Mutex::new(RoastSignSession { - signers: r_signers.clone(), - nonces: nonces.clone(), - sig_shares: vec![], - })), - ); - let nonces = roast_state + let _nonces: Vec<_> = roast_state .latest_nonces .iter() .map(|(i, nonce)| (*i, *nonce)) .collect(); - println!("Responding with nonces:"); // DO THIS FOR EVERY S_i...>!>!> need async - // OPEN MANY THREADS AND THEN AWAIT COLLECTION - return (None, Some(nonces)); } + + // Clear responsive signers (otherwise we ban everyone and hang) + roast_state.responsive_signers = HashSet::new(); + roast_state.sessions.insert( + sid, + Arc::new(Mutex::new(RoastSignSession { + signers: r_signers, + nonces: nonces.clone(), + sig_shares: vec![], + })), + ); + + return (None, Some(nonces)); } // (None, Some(roast_state.latest_nonces)) diff --git a/roastlib/src/frost.rs b/roastlib/src/frost.rs @@ -1,8 +1,3 @@ -use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, Mutex}, -}; - use secp256kfun::{rand_core::RngCore, Scalar}; use schnorr_fun::{ @@ -58,7 +53,7 @@ pub fn frost_keygen(threshold: usize, n_parties: usize) -> (Vec<Scalar>, Vec<XOn } } - println!("{:?}", recieved_shares); + // println!("{:?}", recieved_shares); // finish keygen for each party let (secret_shares, frost_keys): (Vec<Scalar>, Vec<XOnlyFrostKey>) = (0..n_parties) @@ -73,12 +68,12 @@ pub fn frost_keygen(threshold: usize, n_parties: usize) -> (Vec<Scalar>, Vec<XOn proofs_of_possession.clone(), ) .expect("collected shares"); - println!("got secret share"); + println!("Calculated secret share."); let xonly_frost_key = frost_key.into_xonly_key(); (secret_share, xonly_frost_key) }) .unzip(); - println!("Finished keygen!"); + println!("Finished keygen!\n\n"); (secret_shares, frost_keys) } diff --git a/roastlib/src/lib.rs b/roastlib/src/lib.rs @@ -1,3 +1,3 @@ -pub mod client; pub mod coordinator; pub mod frost; +pub mod signer; diff --git a/roastlib/src/main.rs b/roastlib/src/main.rs @@ -1,6 +1,6 @@ -use roast::client; use roast::coordinator; use roast::frost; +use roast::signer; use schnorr_fun::frost as secp_frost; use schnorr_fun::nonce::Deterministic; use schnorr_fun::Message; @@ -8,20 +8,54 @@ use sha2::Sha256; fn main() { let frost = secp_frost::Frost::<Sha256, Deterministic<Sha256>>::default(); - let (secret_shares, frost_keys) = frost::frost_keygen(5, 10); + let (secret_shares, frost_keys) = frost::frost_keygen(2, 3); let message = Message::plain("test", b"test"); let roast = coordinator::Coordinator::new(frost.clone(), frost_keys[0].clone(), message); - let client1 = client::RoastClient::new( - frost, + // Create each signer session and create an initial nonce + let (mut signer1, nonce1) = signer::RoastSigner::new( + frost.clone(), frost_keys[0].clone(), 0, secret_shares[0].clone(), - [0].as_slice(), + [].as_slice(), message, ); + let (mut signer2, nonce2) = signer::RoastSigner::new( + frost, + frost_keys[1].clone(), + 1, + secret_shares[1].clone(), + [1].as_slice(), + message, + ); + + // Begin with each signer sending a nonce to ROAST + let (combined_signature, nonce_set) = roast.process(0, None, nonce1); + assert!(nonce_set.is_none()); + assert!(combined_signature.is_none()); + + let (_combined_signature, nonce_set) = roast.process(1, None, nonce2); + assert!(nonce_set.is_some()); + + // Once ROAST receives the threshold number of nonces, it responds with a nonce set + let sign_session_nonces = nonce_set.expect("roast responded with nonces"); + + // The signer signs using this nonce set and response with a signature share + let (sig_share2, nonce2) = signer2.sign(sign_session_nonces.clone()); + let (combined_signature, nonce_set) = roast.process(1, Some(sig_share2), nonce2); + dbg!(&combined_signature.is_some(), &nonce_set.is_some()); + assert!(combined_signature.is_none()); + + // ROAST also sends the nonce set to the other signer, who also signs + let (sig_share1, nonce1) = signer1.sign(sign_session_nonces); + + let (combined_signature, nonce_set) = roast.process(0, Some(sig_share1), nonce1); + dbg!(&combined_signature.is_some(), &nonce_set.is_some()); + assert!(combined_signature.is_some()); - let (sig, nonce) = client1.start(); - roast.receive_signature(0, sig, nonce); + // Once the threshold number of signature shares have been received, + // ROAST combines the signature shares into the aggregate signature + dbg!(combined_signature); } diff --git a/roastlib/src/signer.rs b/roastlib/src/signer.rs @@ -0,0 +1,86 @@ +use secp256kfun::{ + digest::typenum::U32, + marker::{Public, Zero}, + Scalar, +}; + +use schnorr_fun::{ + frost::{Frost, XOnlyFrostKey}, + musig::{Nonce, NonceKeyPair}, + nonce::NonceGen, + Message, +}; +use sha2::Digest; + +pub struct RoastSigner<'a, H, NG> { + frost: Frost<H, NG>, + frost_key: XOnlyFrostKey, + my_index: usize, + secret_share: Scalar, + message: Message<'a, Public>, + my_nonces: Vec<NonceKeyPair>, +} + +impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG: NonceGen> RoastSigner<'a, H, NG> { + pub fn new( + frost: Frost<H, NG>, + frost_key: XOnlyFrostKey, + my_index: usize, + secret_share: Scalar, + initial_nonce_sid: &[u8], + message: Message<'a>, + ) -> (RoastSigner<'a, H, NG>, Nonce) { + let initial_nonce = frost.gen_nonce( + &secret_share, + &initial_nonce_sid, + Some(frost_key.public_key().normalize()), + Some(message), + ); + let my_nonces = vec![initial_nonce.clone()]; + + ( + RoastSigner { + frost, + frost_key, + my_index, + secret_share, + message, + my_nonces, + }, + initial_nonce.public(), + ) + } + + pub fn new_nonce(&self, sid: &[u8]) -> NonceKeyPair { + let nonce = self.frost.gen_nonce( + &self.secret_share, + sid, + Some(self.frost_key.public_key().normalize()), + Some(self.message), + ); + nonce + } + + pub fn sign(&mut self, nonce_set: Vec<(usize, Nonce)>) -> (Scalar<Public, Zero>, Nonce) { + let session = self.frost.start_sign_session( + &self.frost_key, + nonce_set, + Message::plain("test", b"test"), + ); + let my_nonce = self + .my_nonces + .pop() + .expect("some nonce available to use for signing"); + let sig = self.frost.sign( + &self.frost_key, + &session, + self.my_index, + &self.secret_share, + my_nonce, + ); + // call server with (sig, self.new_nonce()) + self.my_nonces.push(self.new_nonce(&[0])); + + (sig, self.my_nonces.last().expect("some nonce").public()) + } +}