use frost_evm::{
keys::{KeyPackage, PublicKeyPackage},
round1::{self, SigningCommitments, SigningNonces},
round2::{self, SignatureShare},
Identifier, Signature, SigningPackage, VerifyingKey,
};
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
use tracing::{Level, Span};
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct RoastSignerRequest {
session_id: u16,
commitments: BTreeMap<Identifier, SigningCommitments>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct RoastSignerResponse {
session_id: u16,
signature_share: SignatureShare,
commitment: SigningCommitments,
}
struct RoastSigner {
key_package: KeyPackage,
data: Option<Vec<u8>>,
coordinators: BTreeMap<Identifier, SigningNonces>,
requests: VecDeque<(Identifier, RoastSignerRequest)>,
}
impl RoastSigner {
pub fn new(key_package: KeyPackage) -> Self {
Self {
key_package,
data: None,
coordinators: Default::default(),
requests: Default::default(),
}
}
pub fn set_data(&mut self, data: Vec<u8>) {
self.data = Some(data);
}
pub fn data(&self) -> Option<&[u8]> {
self.data.as_deref()
}
pub fn commit(&mut self, coordinator: Identifier) -> SigningCommitments {
let (nonces, commitment) = round1::commit(self.key_package.signing_share(), &mut OsRng);
self.coordinators.insert(coordinator, nonces);
commitment
}
pub fn sign(&mut self, coordinator: Identifier, request: RoastSignerRequest) {
self.requests.push_back((coordinator, request));
}
pub fn message(&mut self, span: &Span) -> Option<(Identifier, RoastSignerResponse)> {
let data = self.data.as_deref()?;
loop {
let (coordinator, request) = self.requests.pop_front()?;
let session_id = request.session_id;
let signing_package = SigningPackage::new(request.commitments, data);
let nonces = self
.coordinators
.remove(&coordinator)
.expect("we sent the coordinator a commitment");
let signature_share = match round2::sign(&signing_package, &nonces, &self.key_package) {
Ok(ss) => ss,
Err(err) => {
tracing::error!(parent: span, tss_session_id = session_id, "invalid signing package {err:?}");
continue;
},
};
let commitment = self.commit(coordinator);
return Some((
coordinator,
RoastSignerResponse {
session_id,
signature_share,
commitment,
},
));
}
}
}
struct RoastSession {
commitments: BTreeMap<Identifier, SigningCommitments>,
signature_shares: HashMap<Identifier, SignatureShare>,
}
impl RoastSession {
fn new(commitments: BTreeMap<Identifier, SigningCommitments>) -> Self {
Self {
commitments,
signature_shares: Default::default(),
}
}
fn on_signature_share(
&mut self,
peer: Identifier,
signature_share: SignatureShare,
span: &Span,
) {
if self.commitments.contains_key(&peer) {
self.signature_shares.insert(peer, signature_share);
}
tracing::debug!(
parent: span,
"signing shares {}/{}",
self.signature_shares.len(),
self.commitments.len(),
);
}
fn is_complete(&self) -> bool {
self.commitments.len() == self.signature_shares.len()
}
}
struct RoastCoordinator {
threshold: u16,
session_id: u16,
commitments: BTreeMap<Identifier, SigningCommitments>,
sessions: BTreeMap<u16, RoastSession>,
committed: BTreeSet<Identifier>,
}
impl RoastCoordinator {
fn new(threshold: u16) -> Self {
Self {
threshold,
session_id: 0,
commitments: Default::default(),
sessions: Default::default(),
committed: Default::default(),
}
}
fn on_commit(&mut self, peer: Identifier, commitment: SigningCommitments) {
if !self.committed.contains(&peer) {
self.commitments.insert(peer, commitment);
self.committed.insert(peer);
}
}
fn on_response(&mut self, peer: Identifier, message: RoastSignerResponse, span: &Span) {
let span =
tracing::span!(parent: span, Level::DEBUG, "session", tss_session_id = self.session_id);
if let Some(session) = self.sessions.get_mut(&message.session_id) {
self.commitments.insert(peer, message.commitment);
session.on_signature_share(peer, message.signature_share, &span);
}
}
fn start_session(&mut self, span: &Span) -> Option<RoastSignerRequest> {
tracing::debug!(
parent: span,
tss_session_id = self.session_id,
"commitments {}/{}",
self.commitments.len(),
self.threshold
);
if self.commitments.len() < self.threshold as _ {
return None;
}
let session_id = self.session_id;
self.session_id = session_id.wrapping_add(1);
let mut commitments = std::mem::take(&mut self.commitments);
while commitments.len() > self.threshold as _ {
let (peer, commitment) = commitments.pop_last().unwrap();
self.commitments.insert(peer, commitment);
}
self.sessions.insert(session_id, RoastSession::new(commitments.clone()));
Some(RoastSignerRequest { session_id, commitments })
}
fn aggregate_signature(&mut self, span: &Span) -> Option<RoastSession> {
let session_id = self
.sessions
.iter()
.filter(|(_, session)| session.is_complete())
.map(|(session_id, _)| *session_id)
.next()?;
tracing::debug!(parent: span, tss_session_id = session_id, "aggregate");
self.sessions.remove(&session_id)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub enum RoastMessage {
Commit(SigningCommitments),
Sign(RoastSignerRequest),
Signature(RoastSignerResponse),
}
impl RoastMessage {
pub fn is_response(&self) -> bool {
matches!(self, Self::Signature(_))
}
}
impl std::fmt::Display for RoastMessage {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Commit(_) => write!(f, "commit"),
Self::Sign(_) => write!(f, "sign"),
Self::Signature(_) => write!(f, "signature"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RoastAction {
Send(Identifier, RoastMessage),
SendMany(Vec<Identifier>, RoastMessage),
Complete([u8; 32], Signature),
}
pub struct Roast {
signer: RoastSigner,
coordinator: Option<RoastCoordinator>,
public_key_package: PublicKeyPackage,
coordinators: BTreeSet<Identifier>,
}
impl Roast {
pub fn new(
id: Identifier,
threshold: u16,
key_package: KeyPackage,
public_key_package: PublicKeyPackage,
coordinators: BTreeSet<Identifier>,
) -> Self {
let is_coordinator = coordinators.contains(&id);
Self {
signer: RoastSigner::new(key_package),
coordinator: if is_coordinator { Some(RoastCoordinator::new(threshold)) } else { None },
public_key_package,
coordinators,
}
}
pub fn set_data(&mut self, data: Vec<u8>) {
self.signer.set_data(data);
}
pub fn on_message(&mut self, peer: Identifier, msg: RoastMessage, span: &Span) {
match msg {
RoastMessage::Commit(commitment) => {
if let Some(coordinator) = self.coordinator.as_mut() {
coordinator.on_commit(peer, commitment);
}
},
RoastMessage::Sign(request) => {
self.signer.sign(peer, request);
},
RoastMessage::Signature(response) => {
if let Some(coordinator) = self.coordinator.as_mut() {
coordinator.on_response(peer, response, span);
}
},
}
}
pub fn next_action(&mut self, span: &Span) -> Option<RoastAction> {
if let Some(coordinator) = self.coordinator.as_mut() {
if let Some(data) = self.signer.data() {
if let Some(session) = coordinator.aggregate_signature(span) {
let signing_package = SigningPackage::new(session.commitments, data);
if let Ok(signature) = frost_evm::aggregate(
&signing_package,
&session.signature_shares,
&self.public_key_package,
) {
let hash = VerifyingKey::message_hash(data);
self.coordinator.take();
return Some(RoastAction::Complete(hash, signature));
}
}
}
if let Some(request) = coordinator.start_session(span) {
let peers = request.commitments.keys().copied().collect();
return Some(RoastAction::SendMany(peers, RoastMessage::Sign(request)));
}
}
if let Some(coordinator) = self.coordinators.pop_last() {
return Some(RoastAction::Send(
coordinator,
RoastMessage::Commit(self.signer.commit(coordinator)),
));
}
if let Some((coordinator, response)) = self.signer.message(span) {
return Some(RoastAction::Send(coordinator, RoastMessage::Signature(response)));
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use frost_evm::keys::{generate_with_dealer, IdentifierList};
use tracing::Level;
#[test]
fn test_roast() -> Result<()> {
crate::tests::init_logger();
let span = tracing::span!(Level::INFO, "shard");
let signers = 3;
let threshold = 2;
let coordinator = 1;
let data = [1u8; 32];
let (secret_shares, public_key_package) =
generate_with_dealer(signers, threshold, IdentifierList::Default, OsRng).unwrap();
let coordinators: BTreeSet<_> = secret_shares.keys().copied().take(coordinator).collect();
let mut roasts: BTreeMap<_, _> = secret_shares
.into_iter()
.map(|(peer, secret_share)| {
(
peer,
Roast::new(
peer,
threshold,
KeyPackage::try_from(secret_share).unwrap(),
public_key_package.clone(),
coordinators.clone(),
),
)
})
.collect();
let members: Vec<_> = roasts.keys().copied().collect();
for roast in roasts.values_mut() {
roast.set_data(data.into());
}
loop {
for from in &members {
if let Some(action) = roasts.get_mut(from).unwrap().next_action(&span) {
match action {
RoastAction::Send(to, commitment) => {
roasts.get_mut(&to).unwrap().on_message(*from, commitment, &span);
},
RoastAction::SendMany(peers, request) => {
for to in peers {
roasts.get_mut(&to).unwrap().on_message(
*from,
request.clone(),
&span,
);
}
},
RoastAction::Complete(_hash, _signature) => {
return Ok(());
},
}
}
}
}
}
}