use crate::{
bls12381::{
dkg::{
ops::{construct_public, recover_public, verify_commitment, verify_share},
Error,
},
primitives::{group::Share, poly, variant::Variant},
},
PublicKey,
};
use commonware_utils::{max_faults, quorum, set::Ordered};
use std::collections::{BTreeMap, HashSet};
#[derive(Clone)]
pub struct Output<V: Variant> {
pub public: poly::Public<V>,
pub commitments: BTreeMap<u32, poly::Public<V>>,
pub reveals: BTreeMap<u32, Vec<Share>>,
}
pub struct Arbiter<P: PublicKey, V: Variant> {
previous: Option<poly::Public<V>>,
dealer_threshold: u32,
player_threshold: u32,
concurrency: usize,
dealers: Ordered<P>,
players: Ordered<P>,
#[allow(clippy::type_complexity)]
commitments: BTreeMap<u32, (poly::Public<V>, Vec<u32>, Vec<Share>)>,
disqualified: HashSet<P>,
}
impl<P: PublicKey, V: Variant> Arbiter<P, V> {
pub fn new(
previous: Option<poly::Public<V>>,
dealers: Ordered<P>,
players: Ordered<P>,
concurrency: usize,
) -> Self {
Self {
dealer_threshold: quorum(dealers.len() as u32),
player_threshold: quorum(players.len() as u32),
previous,
concurrency,
dealers,
players,
commitments: BTreeMap::new(),
disqualified: HashSet::new(),
}
}
pub fn disqualify(&mut self, participant: P) {
self.disqualified.insert(participant);
}
pub fn commitment(
&mut self,
dealer: P,
commitment: poly::Public<V>,
acks: Vec<u32>,
reveals: Vec<Share>,
) -> Result<(), Error> {
if self.disqualified.contains(&dealer) {
return Err(Error::DealerDisqualified);
}
let idx = match self.dealers.position(&dealer) {
Some(idx) => idx,
None => return Err(Error::DealerInvalid),
} as u32;
if self.commitments.contains_key(&idx) {
return Err(Error::DuplicateCommitment);
}
if let Err(e) = verify_commitment::<V>(
self.previous.as_ref(),
&commitment,
idx,
self.player_threshold,
) {
self.disqualified.insert(dealer);
return Err(e);
}
let players_len = self.players.len() as u32;
let mut active = HashSet::new();
for ack in &acks {
if *ack >= players_len {
self.disqualified.insert(dealer);
return Err(Error::PlayerInvalid);
}
if !active.insert(ack) {
self.disqualified.insert(dealer);
return Err(Error::AlreadyActive);
}
}
let reveals_len = reveals.len();
let max_faults = max_faults(players_len) as usize;
if reveals_len > max_faults {
self.disqualified.insert(dealer);
return Err(Error::TooManyReveals);
}
for share in &reveals {
if share.index >= players_len {
self.disqualified.insert(dealer);
return Err(Error::PlayerInvalid);
}
if !active.insert(&share.index) {
self.disqualified.insert(dealer);
return Err(Error::AlreadyActive);
}
verify_share::<V>(&commitment, share.index, share)?;
}
if active.len() != players_len as usize {
self.disqualified.insert(dealer);
return Err(Error::IncorrectActive);
}
self.commitments.insert(idx, (commitment, acks, reveals));
Ok(())
}
pub fn ready(&self) -> bool {
self.commitments.len() >= self.dealer_threshold as usize
}
pub fn finalize(mut self) -> (Result<Output<V>, Error>, HashSet<P>) {
for disqualified in self.disqualified.iter() {
let idx = self.dealers.position(disqualified).unwrap() as u32;
self.commitments.remove(&idx);
}
for (idx, dealer) in self.dealers.iter().enumerate() {
if self.commitments.contains_key(&(idx as u32)) {
continue;
}
self.disqualified.insert(dealer.clone());
}
let dealer_threshold = self.dealer_threshold as usize;
if self.commitments.len() < dealer_threshold {
return (Err(Error::InsufficientDealings), self.disqualified);
}
let mut commitments = BTreeMap::new();
let mut reveals = BTreeMap::new();
for (dealer_idx, (commitment, _, shares)) in
self.commitments.into_iter().take(dealer_threshold)
{
commitments.insert(dealer_idx, commitment);
if shares.is_empty() {
continue;
}
reveals.insert(dealer_idx, shares);
}
let public = match self.previous {
Some(previous) => {
match recover_public::<V>(
&previous,
&commitments,
self.player_threshold,
self.concurrency,
) {
Ok(public) => public,
Err(e) => return (Err(e), self.disqualified),
}
}
None => match construct_public::<V>(commitments.values(), self.player_threshold) {
Ok(public) => public,
Err(e) => return (Err(e), self.disqualified),
},
};
(
Ok(Output {
public,
commitments,
reveals,
}),
self.disqualified,
)
}
}