Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl DkgPublicShares {
&self.comms,
&self.kex_public_key,
);
&self.kex_proof.s * &G == &self.kex_proof.R + c * &self.kex_public_key
self.kex_proof.s * G == self.kex_proof.R + c * self.kex_public_key
}

/// construct a proof of knowledge of kex_private_key
Expand Down
82 changes: 81 additions & 1 deletion src/state_machine/coordinator/fire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
threshold = %self.config.threshold,
"Received NonceResponse"
);
if nonce_info.nonce_recv_key_ids.len() >= self.config.threshold as usize {
if nonce_info.nonce_recv_key_ids.len() >= self.config.sign_threshold as usize {
// We have a winning message!
self.message.clone_from(&nonce_response.message);
let aggregate_nonce = self.compute_aggregate_nonce()?;
Expand Down Expand Up @@ -3215,6 +3215,86 @@ pub mod test {
}
}

#[test]
#[cfg(feature = "with_v1")]
fn sign_threshold_sign_v1() {
sign_threshold_sign::<v1::Aggregator, v1::Signer>();
}

#[test]
fn sign_threshold_sign_v2() {
sign_threshold_sign::<v2::Aggregator, v2::Signer>();
}

fn sign_threshold_sign<Aggregator: AggregatorTrait, Signer: SignerTrait>() {
let (mut coordinators, mut signers) = all_signers_dkg::<Aggregator, Signer>(5, 2);

// change the sign_threshold to num_keys
for coordinator in &mut coordinators {
coordinator.config.sign_threshold = coordinator.config.num_keys;
}

// We have started a signing round
let msg = "It was many and many a year ago, in a kingdom by the sea"
.as_bytes()
.to_vec();
let signature_type = SignatureType::Frost;
let message = coordinators
.first_mut()
.unwrap()
.start_signing_round(&msg, signature_type, None)
.unwrap();
assert_eq!(
coordinators.first().unwrap().state,
State::NonceGather(signature_type)
);

// Send the message to all signers and gather responses by sharing with all other signers and coordinator
let (outbound_messages, operation_results) =
feedback_messages(&mut coordinators, &mut signers, &[message]);
assert!(operation_results.is_empty());
for coordinator in &coordinators {
assert_eq!(coordinator.state, State::SigShareGather(signature_type));
}

// check to see that we have nonces from all signers
for coordinator in &coordinators {
let sign_round_info = &coordinator.message_nonces[&msg];
assert_eq!(sign_round_info.public_nonces.len(), signers.len());
}

assert_eq!(outbound_messages.len(), 1);
assert!(
matches!(outbound_messages[0].msg, Message::SignatureShareRequest(_)),
"Expected SignatureShareRequest message"
);
// Send the SignatureShareRequest message to all signers and share their responses with the coordinator and signers
let (outbound_messages, operation_results) =
feedback_messages(&mut coordinators, &mut signers, &outbound_messages);

// check to see that we have signature shares from all signers
for coordinator in &coordinators {
assert_eq!(coordinator.signature_shares.len(), signers.len());
}

assert!(outbound_messages.is_empty());
assert_eq!(operation_results.len(), 1);
let OperationResult::Sign(sig) = &operation_results[0] else {
panic!("Expected Signature Operation result")
};
assert!(sig.verify(
&coordinators
.first()
.unwrap()
.aggregate_public_key
.expect("No aggregate public key set!"),
&msg
));
for coordinator in &coordinators {
assert_eq!(coordinator.state, State::Idle);
}
}

#[test]
#[cfg(feature = "with_v1")]
fn minimum_signers_sign_v1() {
Expand Down
123 changes: 108 additions & 15 deletions src/state_machine/coordinator/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,13 +663,20 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
"NonceResponse received"
);
}
if self.ids_to_await.is_empty() {
let recv_key_ids = self
.public_nonces
.values()
.flat_map(|nr| nr.key_ids.iter().copied())
.collect::<HashSet<u32>>();
if recv_key_ids.len() >= self.config.sign_threshold as usize {
let aggregate_nonce = self.compute_aggregate_nonce()?;
info!(
%aggregate_nonce,
"Aggregate nonce"
);

// Lock in the set of signers that will participate in the signing round.
self.ids_to_await = self.public_nonces.keys().copied().collect();
self.move_to(State::SigShareRequest(signature_type))?;
}
Ok(())
Expand All @@ -681,8 +688,10 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
sign_id = %self.current_sign_id,
"Requesting Signature Shares"
);
let nonce_responses = (0..self.config.num_signers)
.map(|i| self.public_nonces[&i].clone())
let nonce_responses = self
.public_nonces
.values()
.cloned()
.collect::<Vec<NonceResponse>>();
let sig_share_request = SignatureShareRequest {
dkg_id: self.current_dkg_id,
Expand All @@ -698,7 +707,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
.expect(""),
msg: Message::SignatureShareRequest(sig_share_request),
};
self.ids_to_await = (0..self.config.num_signers).collect();
self.ids_to_await = self.public_nonces.keys().copied().collect();
self.move_to(State::SigShareGather(signature_type))?;

Ok(sig_share_request_msg)
Expand Down Expand Up @@ -784,9 +793,11 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
);
}
if self.ids_to_await.is_empty() {
// Calculate the aggregate signature
let nonce_responses = (0..self.config.num_signers)
.map(|i| self.public_nonces[&i].clone())
// Calculate the aggregate signature using the signers that participated.
let nonce_responses = self
.public_nonces
.values()
.cloned()
.collect::<Vec<NonceResponse>>();

let nonces = nonce_responses
Expand Down Expand Up @@ -1144,15 +1155,19 @@ pub mod test {
curve::scalar::Scalar,
net::{DkgBegin, Message, NonceRequest, Packet, SignatureShareResponse, SignatureType},
schnorr::{self, ID},
state_machine::coordinator::{
frost::Coordinator as FrostCoordinator,
test::{
bad_signature_share_request, btc_sign_verify, check_signature_shares,
coordinator_state_machine, empty_private_shares, empty_public_shares,
equal_after_save_load, invalid_nonce, new_coordinator, run_dkg_sign, setup,
start_dkg_round, start_signing_round, verify_packet_sigs,
state_machine::{
coordinator::{
frost::Coordinator as FrostCoordinator,
test::{
bad_signature_share_request, btc_sign_verify, check_signature_shares,
coordinator_state_machine, empty_private_shares, empty_public_shares,
equal_after_save_load, feedback_messages, invalid_nonce, new_coordinator,
run_dkg, run_dkg_sign, setup, start_dkg_round, start_signing_round,
verify_packet_sigs,
},
Config, Coordinator as CoordinatorTrait, State,
},
Config, Coordinator as CoordinatorTrait, State,
OperationResult,
},
traits::{Aggregator as AggregatorTrait, Signer as SignerTrait},
util::create_rng,
Expand Down Expand Up @@ -1576,6 +1591,84 @@ pub mod test {
run_dkg_sign::<FrostCoordinator<v2::Aggregator>, v2::Signer>(5, 2);
}

#[test]
#[cfg(feature = "with_v1")]
fn sign_threshold_sign_v1() {
sign_threshold_sign::<v1::Aggregator, v1::Signer>();
}

#[test]
fn sign_threshold_sign_v2() {
sign_threshold_sign::<v2::Aggregator, v2::Signer>();
}

fn sign_threshold_sign<Aggregator: AggregatorTrait, Signer: SignerTrait>() {
let (mut coordinators, mut signers) = run_dkg::<FrostCoordinator<Aggregator>, Signer>(5, 2);

// Require all key_ids to participate in the signing round.
for coordinator in &mut coordinators {
coordinator.config.sign_threshold = coordinator.config.num_keys;
}

let msg = "It was many and many a year ago, in a kingdom by the sea"
.as_bytes()
.to_vec();
let signature_type = SignatureType::Frost;
let message = coordinators
.first_mut()
.unwrap()
.start_signing_round(&msg, signature_type, None)
.unwrap();
assert_eq!(
coordinators.first().unwrap().state,
State::NonceGather(signature_type)
);

// Drive nonce gathering: every signer responds, so we should reach the threshold.
let (outbound_messages, operation_results) =
feedback_messages(&mut coordinators, &mut signers, &[message]);
assert!(operation_results.is_empty());
for coordinator in &coordinators {
assert_eq!(coordinator.state, State::SigShareGather(signature_type));
}

// All signers contributed nonces.
for coordinator in &coordinators {
assert_eq!(coordinator.public_nonces.len(), signers.len());
}

assert_eq!(outbound_messages.len(), 1);
assert!(
matches!(outbound_messages[0].msg, Message::SignatureShareRequest(_)),
"Expected SignatureShareRequest message"
);

// Drive sig share gathering and aggregation.
let (outbound_messages, operation_results) =
feedback_messages(&mut coordinators, &mut signers, &outbound_messages);

for coordinator in &coordinators {
assert_eq!(coordinator.signature_shares.len(), signers.len());
}

assert!(outbound_messages.is_empty());
assert_eq!(operation_results.len(), 1);
let OperationResult::Sign(sig) = &operation_results[0] else {
panic!("Expected Signature Operation result")
};
assert!(sig.verify(
&coordinators
.first()
.unwrap()
.aggregate_public_key
.expect("No aggregate public key set!"),
&msg
));
for coordinator in &coordinators {
assert_eq!(coordinator.state, State::Idle);
}
}

#[test]
#[cfg(feature = "with_v1")]
fn check_signature_shares_v1() {
Expand Down
7 changes: 7 additions & 0 deletions src/state_machine/coordinator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ pub struct Config {
pub threshold: u32,
/// threshold of keys needed to complete DKG (must be >= threshold)
pub dkg_threshold: u32,
/// threshold of keys needed to start a signing round (must be >= threshold)
Comment thread
xoloki marked this conversation as resolved.
pub sign_threshold: u32,
/// private key used to sign network messages
pub message_private_key: Scalar,
/// timeout to gather DkgPublicShares messages
Expand Down Expand Up @@ -188,6 +190,8 @@ impl fmt::Debug for Config {

impl Config {
/// Create a new config object with no timeouts
/// dkg_threshold defaults to num_keys
/// sign_threshold defaults to threshold, and must be >= threshold
pub fn new(
num_signers: u32,
num_keys: u32,
Expand All @@ -199,6 +203,7 @@ impl Config {
num_keys,
threshold,
dkg_threshold: num_keys,
sign_threshold: threshold,
message_private_key,
dkg_public_timeout: None,
dkg_private_timeout: None,
Expand All @@ -212,6 +217,7 @@ impl Config {

#[allow(clippy::too_many_arguments)]
/// Create a new config object with the passed timeouts
/// sign_threshold defaults to threshold, and must be >= threshold
pub fn with_timeouts(
num_signers: u32,
num_keys: u32,
Expand All @@ -230,6 +236,7 @@ impl Config {
num_keys,
threshold,
dkg_threshold,
sign_threshold: threshold,
message_private_key,
dkg_public_timeout,
dkg_private_timeout,
Expand Down
Loading