Skip to content

Commit

Permalink
add interactive signing, docs to public API #426
Browse files Browse the repository at this point in the history
  • Loading branch information
marsella authored Sep 19, 2023
1 parent 199d02c commit a0dde1d
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 6 deletions.
33 changes: 33 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@
//! generated in this threshold manner are indistinguishable from signatures
//! generated using a normal ECDSA signing method.
//!
//! # Usage
//! The [`Participant`] type is the main driver for protocol execution. A given
//! `Participant` is parameterized by the subprotocol that it runs:
//! [`keygen`](`keygen::KeygenParticipant`),
//! [`auxinfo`](auxinfo::AuxInfoParticipant),
//! [`presign`](presign::PresignParticipant), or
//! [`sign`](sign::InteractiveSignParticipant).
//!
//! The public API is not currently complete: we provide an interface to call
//! interactive signing (e.g. running the presign and sign protocols from
//! Canetti et al. in sequence), but not for non-interactive signing. You can
//! run presigning alone and generate individual [`SignatureShare`]s, but you
//! must then manually combine those shares to create the generated signature;
//! this does not provide the same security guarantees as the protocol.
//!
//! A valid protocol run requires a lot of setup so we won't try to provide a
//! code example here; please see the examples directory. At a high level,
//! though, the user can run a protocol as follows:
//! 1. Create a new [`Participant`], parameterized by the
//! [`ProtocolParticipant`] describing the protocol you want to run.
//! 2. Initialize the `Participant` by calling
//! [`initialize_message()`](Participant::initialize_message()) and passing
//! the result to
//! [`process_single_message`](Participant::process_single_message()).
//! 3. Processing a message returns an optional output and a (possibly empty)
//! set of messages. Send any messages to the other participants. If there's
//! an output, the protocol is complete for this party.
//! 4. On receiving a message from another participant, call
//! `process_single_message`. Repeat 3 and 4.
//!
//! Note that a `Participant` can receive messages before it has been
//! initialized. They will be stored and processed after initialization.
//!
//! # 🔒 Requirements of the calling application
//! This library **does not** implement the complete protocol. There are several
//! security-critical steps that must be handled by the calling application. We
Expand Down
204 changes: 199 additions & 5 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub enum ProtocolType {
AuxInfo,
Presign,
Broadcast,
InteractiveSign,
}

/// The driver for a party executing a sub-protocol of the threshold signing
Expand All @@ -60,8 +61,10 @@ pub enum ProtocolType {
/// [`Participant`] in order to begin the protocol execution.
/// 2. Receiving messages sent by other participants, and passing
/// them to the participant by calling
/// [`Participant::process_single_message()`]. 3. Sending all messages generated
/// by [`Participant::process_single_message()`] to the correct recipient.
/// [`process_single_message()`](Participant::process_single_message()).
/// 3. Sending all messages generated
/// by [`process_single_message()`](Participant::process_single_message()) to
/// the correct recipient.
///
/// [`Message`]s contain a `to: ParticipantIdentifier` field which specifies the
/// recipient of a message. The calling application is responsible for
Expand Down Expand Up @@ -164,7 +167,10 @@ impl<P: ProtocolParticipant> Participant<P> {
match (message.message_type(), P::protocol_type()) {
(MessageType::Auxinfo(_), ProtocolType::AuxInfo)
| (MessageType::Keygen(_), ProtocolType::Keygen)
| (MessageType::Presign(_), ProtocolType::Presign) => {}
| (MessageType::Presign(_), ProtocolType::Presign)
// Interactive sign runs presign and sign in sequence, so we allow both message types
| (MessageType::Presign(_), ProtocolType::InteractiveSign)
| (MessageType::Sign(_), ProtocolType::InteractiveSign) => {}
_ => {
error!(
"Message type did not match type of this participant: got {:?}, expected {:?}",
Expand Down Expand Up @@ -650,8 +656,13 @@ impl std::fmt::Display for Identifier {
mod tests {
use super::*;
use crate::{
auxinfo::AuxInfoParticipant, keygen::KeygenParticipant, participant::Status, presign,
utils::testing::init_testing, PresignParticipant,
auxinfo::AuxInfoParticipant,
keygen::KeygenParticipant,
participant::Status,
presign,
sign::{self, InteractiveSignParticipant},
utils::testing::init_testing,
PresignParticipant,
};
use k256::ecdsa::signature::{DigestVerifier, Verifier};
use rand::seq::IteratorRandom;
Expand Down Expand Up @@ -1013,4 +1024,187 @@ mod tests {
flame::dump_html(&mut std::fs::File::create("dev/flame-graph.html").unwrap()).unwrap();
Ok(())
}

#[test]
fn full_protocol_execution_with_interactive_signing_works() -> Result<()> {
let rng = &mut init_testing();
let QUORUM_SIZE = 4;
// Set GLOBAL config for participants
let configs = ParticipantConfig::random_quorum(QUORUM_SIZE, rng)?;
let mut inboxes = Vec::new();

// Set up keygen participants
let keygen_sid = Identifier::random(rng);
let mut keygen_quorum = configs
.clone()
.into_iter()
.map(|config| {
Participant::<KeygenParticipant>::from_config(config, keygen_sid, ()).unwrap()
})
.collect::<Vec<_>>();
let mut keygen_outputs: HashMap<
ParticipantIdentifier,
<KeygenParticipant as ProtocolParticipant>::Output,
> = HashMap::new();

// Initialize keygen for all participants
for participant in &keygen_quorum {
inboxes.push(participant.initialize_message()?);
}

// Run keygen until all parties have outputs
while keygen_outputs.len() < QUORUM_SIZE {
assert!(
!inboxes.is_empty(),
"No more messages but we're not done with keygen"
);

// Pick a random message to process
let message = inboxes.swap_remove(rng.gen_range(0..inboxes.len()));
let participant = keygen_quorum
.iter_mut()
.find(|p| p.id() == message.to())
.unwrap();
let (output, messages) = participant.process_single_message(&message, rng)?;

// Save any outgoing messages and the output
inboxes.extend(messages);
if let Some(output) = output {
// Save the output, and make sure this participant didn't already return an
// output.
assert!(keygen_outputs.insert(participant.id(), output).is_none());
}
}

// Keygen is done! Make sure there are no more messages and that everyone
// finished
assert!(inboxes.is_empty());
assert!(keygen_quorum
.iter()
.all(|p| *p.status() == Status::TerminatedSuccessfully));

// Save the public key for later
let saved_public_key = keygen_outputs[&configs[0].id()].public_key()?;

// Set up auxinfo participants
let auxinfo_sid = Identifier::random(rng);
let mut auxinfo_quorum = configs
.clone()
.into_iter()
.map(|config| {
Participant::<AuxInfoParticipant>::from_config(config, auxinfo_sid, ()).unwrap()
})
.collect::<Vec<_>>();

let mut auxinfo_outputs: HashMap<
ParticipantIdentifier,
<AuxInfoParticipant as ProtocolParticipant>::Output,
> = HashMap::new();

// Initialize auxinfo for all parties
for participant in &auxinfo_quorum {
inboxes.push(participant.initialize_message()?);
}

// Run auxinfo until all parties have outputs
while auxinfo_outputs.len() < QUORUM_SIZE {
assert!(
!inboxes.is_empty(),
"No more messages but we're not done with auxinfo"
);

// Pick a random message to process
let message = inboxes.swap_remove(rng.gen_range(0..inboxes.len()));
let participant = auxinfo_quorum
.iter_mut()
.find(|p| p.id() == message.to())
.unwrap();
let (output, messages) = participant.process_single_message(&message, rng)?;

// Save any outgoing messages and the output
inboxes.extend(messages);
if let Some(output) = output {
// Save the output, and make sure this participant didn't already return an
// output.
assert!(auxinfo_outputs.insert(participant.id(), output).is_none());
}
}

// Auxinfo is done! Make sure there are no more messages.
assert!(inboxes.is_empty());
// And make sure all participants have successfully terminated.
assert!(auxinfo_quorum
.iter()
.all(|p| *p.status() == Status::TerminatedSuccessfully));

// Prepare inputs for sign
let message = b"Signing a message to test full protocol execution with interactive sign";
let sign_inputs = configs
.iter()
.map(|config| {
(
auxinfo_outputs.remove(&config.id()).unwrap(),
keygen_outputs.remove(&config.id()).unwrap(),
)
})
.map(|(auxinfo_output, keygen_output)| {
sign::InteractiveInput::new(message, keygen_output, auxinfo_output)
})
.collect::<Result<Vec<_>>>()?;

// Set up signing participants
let sign_sid = Identifier::random(rng);
let mut sign_quorum = std::iter::zip(configs, sign_inputs)
.map(|(config, input)| {
Participant::<InteractiveSignParticipant>::from_config(config, sign_sid, input)
})
.collect::<Result<Vec<_>>>()?;

let mut sign_outputs = Vec::new();

// Initialize signing for all parties
for participant in &sign_quorum {
inboxes.push(participant.initialize_message()?);
}

// Run sign until all parties have outputs
while sign_outputs.len() < QUORUM_SIZE {
assert!(
!inboxes.is_empty(),
"No more messages but we're not done with signing"
);

// Pick a random message to process
let message = inboxes.swap_remove(rng.gen_range(0..inboxes.len()));
let participant = sign_quorum
.iter_mut()
.find(|p| p.id() == message.to())
.unwrap();
let (output, messages) = participant.process_single_message(&message, rng)?;

// Save any outgoing messages and the output
inboxes.extend(messages);
if let Some(output) = output {
// Save the output, and make sure this participant didn't already return an
// output.
sign_outputs.push(output);
}
}

// Make sure all messages sent and every party finished
assert!(inboxes.is_empty());
assert!(sign_quorum
.iter()
.all(|p| *p.status() == Status::TerminatedSuccessfully));

// Validate output: everyone should get the same signature...
assert!(sign_outputs.windows(2).all(|sig| sig[0] == sig[1]));

// ...and the signature should be valid under the public key we saved
assert!(saved_public_key
.verify(message, sign_outputs[0].as_ref())
.is_ok());

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/sign/interactive_sign/participant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ impl ProtocolParticipant for InteractiveSignParticipant {
}

fn protocol_type() -> ProtocolType {
todo!()
ProtocolType::InteractiveSign
}

fn new(
Expand Down

0 comments on commit a0dde1d

Please sign in to comment.