commit 8c4c4317687a74a12cf3af423fb252b44ac3de65
parent 03513f02a9a5b39c714442fd4029c7926fd9a73a
Author: nickfarrow <nick@nickfarrow.com>
Date: Thu, 29 Sep 2022 14:14:31 +1000
t-of-n loop test sequential
Diffstat:
M | README.md | | | 69 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
M | roastlib/src/coordinator.rs | | | 65 | +++++++++++++++++++++++++++++++++++++++++++---------------------- |
M | roastlib/src/main.rs | | | 146 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
3 files changed, 252 insertions(+), 28 deletions(-)
diff --git a/README.md b/README.md
@@ -1,18 +1,74 @@
-Experimental
--> unfishished and not ready for use
+# roast for secp256kfun [frost](https://github.com/LLFourn/secp256kfun/blob/master/schnorr_fun/src/frost.rs)
+## unfishished and not ready for use
-![image](https://user-images.githubusercontent.com/24557779/192925900-3c15cddf-a467-47be-80a5-3b04b0acbd47.png)
+[roast paper](https://eprint.iacr.org/2022/550.pdf)
## todo
+Later will be made agnostic to the threshold signature scheme that is used / frost implementation that is used.
+
Finish roastlib and test without specifying any io methods from client to server
Create communication endpoints for both ROAST server (coordinator) and signer clients using new layout, then use them to communicate a test session.
* Current plan is to communicate via http requests and rocket with some mutable state for client and server.
-Make agnostic to the threshold signature scheme that is used
+
+```
+let frost = secp_frost::Frost::<Sha256, Deterministic<Sha256>>::default();
+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);
+
+// 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(),
+ [].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 the nonces for this sign session,
+// and responds to ROAST 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());
+
+// Once the threshold number of signature shares have been received,
+// ROAST combines the signature shares into the aggregate signature
+dbg!(combined_signature);
+```
## ROAST Paper Notes
-[paper](https://eprint.iacr.org/2022/550.pdf)
+[roast paper](https://eprint.iacr.org/2022/550.pdf)
Roast is a simple wrapper that turns a given threshold signature scheme into a scheme with a robust and asynchronous signing protocol, as long as the underlying signing protocol is semi-interactive (i.e. has one preprocessing round and one actual signing round), proviceds identifiable aborts, and is unforgable under concurrent signing sessions.
@@ -70,3 +126,5 @@ A simple method to eliminate the need for semi-strusted coordinator is to let th
The concurrent runs of ROAST do not need to be started simultaneously - e.g. honest signers can resend their reply in the run with coorinator_2 only after d seconds and only if they have not obtained a valid signature from any other run (is that a concern?)
+
+![image](https://user-images.githubusercontent.com/24557779/192925900-3c15cddf-a467-47be-80a5-3b04b0acbd47.png)
+\ No newline at end of file
diff --git a/roastlib/src/coordinator.rs b/roastlib/src/coordinator.rs
@@ -59,13 +59,12 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG
};
}
- pub fn mark_malicious(&self, index: &usize) {
- let mut roast_state = self.state.lock().expect("got lock");
- roast_state.malicious_signers.insert(*index);
- if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
- panic!("not enough singers left to continue!");
- }
- }
+ // pub fn mark_malicious(self, roast_state: &mut MutexGuard<RoastState>, index: &usize) {
+ // roast_state.malicious_signers.insert(*index);
+ // if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
+ // panic!("not enough singers left to continue!");
+ // }
+ // }
// Main body of the ROAST coordinator algorithm
pub fn process(
@@ -86,25 +85,35 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG
"Unsolicited reply from signer {}, marking malicious.",
index
);
- self.mark_malicious(&index);
+
+ // Mark malicious
+ roast_state.malicious_signers.insert(index);
+ if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
+ panic!("not enough singers left to continue!");
+ }
+
return (None, None);
}
// If this is not the inital message from S_i
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,
- );
+ // Get session from roast_state
+ let session = {
+ let roast_session = roast_state
+ .sessions
+ .get(&session_id)
+ .unwrap()
+ .lock()
+ .expect("got lock");
+
+ self.frost.start_sign_session(
+ &self.frost_key.clone(),
+ roast_session.nonces.clone(),
+ roast_state.message,
+ )
+ };
+ println!("Party {} is loading signing session {}", index, session_id);
// dbg!(&self.frost_key.clone(), &session, index, signature_share);
@@ -112,13 +121,25 @@ impl<'a, H: Digest + Clone + Digest<OutputSize = U32>, NG> Coordinator<'a, H, NG
&self.frost_key.clone(),
&session,
index,
- signature_share.expect("party provided None signature share"),
+ signature_share.expect("party unexpectedly provided None signature share"),
) {
println!("Invalid signature, marking {} malicious.", index);
- self.mark_malicious(&index);
+ roast_state.malicious_signers.insert(index);
+ if roast_state.malicious_signers.len() >= self.frost_key.clone().threshold() {
+ panic!("not enough singers left to continue!");
+ }
+
return (None, None);
}
+ // Reopen session within the roast state for writting
+ let mut roast_session = roast_state
+ .sessions
+ .get(&session_id)
+ .unwrap()
+ .lock()
+ .expect("got lock");
+
// Store valid signature
roast_session
.sig_shares
diff --git a/roastlib/src/main.rs b/roastlib/src/main.rs
@@ -2,11 +2,152 @@ use roast::coordinator;
use roast::frost;
use roast::signer;
use schnorr_fun::frost as secp_frost;
+use schnorr_fun::musig::Nonce;
use schnorr_fun::nonce::Deterministic;
use schnorr_fun::Message;
use sha2::Sha256;
fn main() {
+ test_t_of_n_sequential(9, 15);
+ // test_2_of_3()
+}
+
+fn test_t_of_n_sequential(threshold: usize, n_parties: usize) {
+ let frost = secp_frost::Frost::<Sha256, Deterministic<Sha256>>::default();
+ let (secret_shares, frost_keys) = frost::frost_keygen(threshold, n_parties);
+
+ let message = Message::plain("test", b"test");
+ let roast = coordinator::Coordinator::new(frost.clone(), frost_keys[0].clone(), message);
+
+ // Create each signer session and create an initial nonce
+ let (mut signers, mut nonces): (Vec<_>, Vec<_>) = frost_keys
+ .into_iter()
+ .zip(secret_shares)
+ .enumerate()
+ .map(|(i, (frost_key, secret_share))| {
+ signer::RoastSigner::new(
+ frost.clone(),
+ frost_key,
+ i,
+ secret_share,
+ [i as u8].as_slice(),
+ message,
+ )
+ })
+ .unzip();
+
+ let mut sig_shares = vec![];
+ let mut nonce_set: Vec<Option<Vec<(usize, Nonce)>>> = vec![None; n_parties + 1];
+ let mut finished_signature = None;
+ let mut n_rounds = 0;
+
+ while finished_signature.is_none() {
+ n_rounds += 1;
+ for signer_index in 0..n_parties {
+ // Check to see if this signer has recieved any nonces
+ let (sig, new_nonce) = match nonce_set[signer_index].clone() {
+ // Sign if we have recieved nonces, and create new nonce
+ Some(signing_nonces) => {
+ // dbg!(&signing_nonces);
+ let (sig, nonce) = signers[signer_index].sign(signing_nonces);
+ (Some(sig), nonce)
+ }
+ // Otherwise, just create a new nonce
+ None => (
+ None,
+ signers[signer_index]
+ .new_nonce([signer_index as u8].as_slice())
+ .public(),
+ ),
+ };
+ // Send signature and our next nonce to ROAST
+ let (combined_sig, updated_nonce_set) = roast.process(signer_index, sig, new_nonce);
+
+ if combined_sig.is_some() {
+ finished_signature = combined_sig;
+ break;
+ }
+
+ // hacky mimic broadcast
+ // Store the new nonce_set for this caller,
+ // and for peers who are have not recieved any nonces yet.
+ // this will probably break when introducing malicious signers
+ if updated_nonce_set.is_some() {
+ nonce_set[signer_index] = updated_nonce_set.clone();
+ nonce_set = nonce_set
+ .into_iter()
+ .map(|nonce| {
+ if nonce.is_some() {
+ nonce
+ } else {
+ updated_nonce_set.clone()
+ }
+ })
+ .collect()
+ }
+
+ nonces[signer_index] = new_nonce;
+
+ if sig.is_some() {
+ sig_shares.push(sig);
+ }
+ // dbg!(&sig_shares);
+ }
+ }
+ dbg!(finished_signature, n_rounds);
+
+ // let sig_shares: Vec<_> = signers
+ // .into_iter()
+ // .zip(nonces.clone())
+ // .enumerate()
+ // .map(|(i, (mut signer, nonce))| {
+ // let (combined_sig, nonce_set) = roast.process(i, None, nonce);
+ // let sig = match nonce_set {
+ // Some(nonce_set) => Some(signer.sign(nonce_set)),
+ // None => None,
+ // };
+ // sig
+ // })
+ // .collect();
+
+ // dbg!(&sig_shares);
+
+ // let combined_sigs: Vec<_> = sig_shares
+ // .into_iter()
+ // .filter(|sig_share| sig_share.is_some())
+ // .enumerate()
+ // .map(|(i, sig_share)| {
+ // let (sig, new_nonce) = sig_share.expect("filtered");
+ // let (combined_sig, nonce_set) = roast.process(i, Some(sig), new_nonce);
+ // combined_sig
+ // })
+ // .collect();
+ // dbg!(combined_sigs);
+}
+
+// send_sigs
+
+// // 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());
+
+// // Once the threshold number of signature shares have been received,
+// // ROAST combines the signature shares into the aggregate signature
+// dbg!(combined_signature);
+
+fn test_2_of_3_sequential() {
let frost = secp_frost::Frost::<Sha256, Deterministic<Sha256>>::default();
let (secret_shares, frost_keys) = frost::frost_keygen(2, 3);
@@ -42,7 +183,8 @@ fn main() {
// 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
+ // The signer signs using this the nonces for this sign session,
+ // and responds to ROAST 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());
@@ -59,3 +201,5 @@ fn main() {
// ROAST combines the signature shares into the aggregate signature
dbg!(combined_signature);
}
+// #[cfg(test)]
+// mod test {}