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:
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())
+ }
+}