use std::collections::{BTreeMap, BTreeSet, VecDeque};
use frost_ed25519 as frost;
use crate::rpc::NonceSlotId;
pub struct RoastSession {
pub session_id: u128,
pub participants: BTreeSet<frost::Identifier>,
pub signing_package: frost::SigningPackage,
pub collected_shares: BTreeMap<frost::Identifier, frost::round2::SignatureShare>,
}
pub struct RoastCoordinator {
pub threshold: usize,
pub pubkey_package: frost::keys::PublicKeyPackage,
pub message: Vec<u8>,
pub responsive_signers: BTreeSet<frost::Identifier>,
pub commitment_queues:
BTreeMap<frost::Identifier, VecDeque<(NonceSlotId, frost::round1::SigningCommitments)>>,
pub active_sessions: BTreeMap<u128, RoastSession>,
pub signer_to_session: BTreeMap<frost::Identifier, u128>,
pub banished_signers: BTreeSet<frost::Identifier>,
next_session_id: u128,
}
impl RoastCoordinator {
pub fn new(
threshold: usize,
pubkey_package: frost::keys::PublicKeyPackage,
message: Vec<u8>,
) -> Self {
Self {
threshold,
pubkey_package,
message,
responsive_signers: BTreeSet::new(),
commitment_queues: BTreeMap::new(),
active_sessions: BTreeMap::new(),
signer_to_session: BTreeMap::new(),
banished_signers: BTreeSet::new(),
next_session_id: 1,
}
}
pub fn register_commitments(
&mut self,
signer_id: frost::Identifier,
commitments: Vec<(NonceSlotId, frost::round1::SigningCommitments)>,
) {
if self.banished_signers.contains(&signer_id) {
return;
}
let queue = self.commitment_queues.entry(signer_id).or_default();
queue.extend(commitments);
if !self.signer_to_session.contains_key(&signer_id) {
self.responsive_signers.insert(signer_id);
}
}
pub fn try_spawn_session(
&mut self,
) -> Option<(
u128,
frost::SigningPackage,
BTreeMap<frost::Identifier, NonceSlotId>,
)> {
let eligible: Vec<frost::Identifier> = self
.responsive_signers
.iter()
.copied()
.filter(|id| {
!self.banished_signers.contains(id)
&& self
.commitment_queues
.get(id)
.is_some_and(|q| !q.is_empty())
})
.take(self.threshold)
.collect();
if eligible.len() < self.threshold {
return None;
}
let session_id = self.next_session_id;
self.next_session_id = self.next_session_id.checked_add(1).unwrap();
let mut commitments_map = BTreeMap::new();
let mut slot_map = BTreeMap::new();
let mut participants = BTreeSet::new();
for id in eligible {
self.responsive_signers.remove(&id);
self.signer_to_session.insert(id, session_id);
let (slot_id, comm) = self
.commitment_queues
.get_mut(&id)
.expect("queue exists")
.pop_front()
.expect("queue not empty");
commitments_map.insert(id, comm);
slot_map.insert(id, slot_id);
participants.insert(id);
}
let signing_package = frost::SigningPackage::new(commitments_map, &self.message);
self.active_sessions.insert(
session_id,
RoastSession {
session_id,
participants,
signing_package: signing_package.clone(),
collected_shares: BTreeMap::new(),
},
);
Some((session_id, signing_package, slot_map))
}
pub fn receive_share(
&mut self,
session_id: u128,
signer_id: frost::Identifier,
share: frost::round2::SignatureShare,
replenish: Option<(NonceSlotId, frost::round1::SigningCommitments)>,
) -> Result<Option<frost::Signature>, frost::Error> {
let session = match self.active_sessions.get_mut(&session_id) {
Some(s) => s,
None => return Ok(None),
};
if !session.participants.contains(&signer_id) {
return Ok(None);
}
let verifying_share = self
.pubkey_package
.verifying_shares()
.get(&signer_id)
.ok_or(frost::Error::UnknownIdentifier)?;
if let Err(err) = frost_core::verify_signature_share(
signer_id,
verifying_share,
&share,
&session.signing_package,
self.pubkey_package.verifying_key(),
) {
self.banished_signers.insert(signer_id);
self.signer_to_session.remove(&signer_id);
return Err(err);
}
if let Some(rep) = replenish {
self.commitment_queues
.entry(signer_id)
.or_default()
.push_back(rep);
}
self.signer_to_session.remove(&signer_id);
self.responsive_signers.insert(signer_id);
session.collected_shares.insert(signer_id, share);
if session.collected_shares.len() == self.threshold {
let final_sig = frost::aggregate(
&session.signing_package,
&session.collected_shares,
&self.pubkey_package,
)?;
return Ok(Some(final_sig));
}
Ok(None)
}
}