pub mod arbiter;
pub use arbiter::Arbiter;
pub mod dealer;
pub use dealer::Dealer;
pub mod ops;
pub mod player;
pub use player::Player;
pub mod types;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("unexpected polynomial")]
UnexpectedPolynomial,
#[error("commitment has wrong degree")]
CommitmentWrongDegree,
#[error("misdirected share")]
MisdirectedShare,
#[error("share does not on commitment")]
ShareWrongCommitment,
#[error("insufficient dealings")]
InsufficientDealings,
#[error("reshare mismatch")]
ReshareMismatch,
#[error("share interpolation failed")]
ShareInterpolationFailed,
#[error("public key interpolation failed")]
PublicKeyInterpolationFailed,
#[error("dealer is invalid")]
DealerInvalid,
#[error("player invalid")]
PlayerInvalid,
#[error("missing share")]
MissingShare,
#[error("missing commitment")]
MissingCommitment,
#[error("too many commitments")]
TooManyCommitments,
#[error("duplicate commitment")]
DuplicateCommitment,
#[error("duplicate share")]
DuplicateShare,
#[error("duplicate ack")]
DuplicateAck,
#[error("mismatched commitment")]
MismatchedCommitment,
#[error("mismatched share")]
MismatchedShare,
#[error("too many reveals")]
TooManyReveals,
#[error("incorrect active")]
IncorrectActive,
#[error("already active")]
AlreadyActive,
#[error("invalid commitments")]
InvalidCommitments,
#[error("dealer disqualified")]
DealerDisqualified,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
bls12381::primitives::{
ops::{
partial_sign_proof_of_possession, threshold_signature_recover,
verify_proof_of_possession,
},
poly::{self, public},
variant::{MinPk, MinSig, Variant},
},
ed25519::{PrivateKey, PublicKey},
PrivateKeyExt as _, Signer as _,
};
use commonware_utils::{quorum, set::Ordered};
use rand::{rngs::StdRng, SeedableRng};
use std::collections::{BTreeMap, HashMap};
#[test]
fn test_invalid_commitment() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, _, shares) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let t = quorum(n);
let (public, _) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let result = player.share(contributors[0].clone(), public, shares[0].clone());
assert!(matches!(result, Err(Error::ShareWrongCommitment)));
}
#[test]
fn test_mismatched_commitment() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let t = quorum(n);
let (other_commitment, _) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
player
.share(contributors[0].clone(), commitment, shares[0].clone())
.unwrap();
let result = player.share(contributors[0].clone(), other_commitment, shares[0].clone());
assert!(matches!(result, Err(Error::MismatchedCommitment)));
}
#[test]
fn test_mismatched_share() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let t = quorum(n);
let (_, other_shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
player
.share(
contributors[0].clone(),
commitment.clone(),
shares[0].clone(),
)
.unwrap();
let result = player.share(contributors[0].clone(), commitment, other_shares[0].clone());
assert!(matches!(result, Err(Error::MismatchedShare)));
}
#[test]
fn test_duplicate_share() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
player
.share(
contributors[0].clone(),
commitment.clone(),
shares[0].clone(),
)
.unwrap();
let result = player.share(contributors[0].clone(), commitment, shares[0].clone());
assert!(matches!(result, Err(Error::DuplicateShare)));
}
#[test]
fn test_misdirected_share() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let result = player.share(
contributors[0].clone(),
commitment.clone(),
shares[1].clone(),
);
assert!(matches!(result, Err(Error::MisdirectedShare)));
}
#[test]
fn test_invalid_dealer() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let dealer = PrivateKey::from_seed(n as u64).public_key();
let result = player.share(dealer.clone(), commitment.clone(), shares[0].clone());
assert!(matches!(result, Err(Error::DealerInvalid)));
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(dealer, commitment, vec![0, 1, 2, 3], Vec::new());
assert!(matches!(result, Err(Error::DealerInvalid)));
}
#[test]
fn test_invalid_commitment_degree() {
let n = 5;
let t = quorum(n);
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut commitments = Vec::new();
let (public, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, 1);
commitments.push((public, shares));
let (public, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t - 1);
commitments.push((public, shares));
let (public, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t + 1);
commitments.push((public, shares));
for (public, shares) in commitments {
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let result = player.share(contributors[0].clone(), public.clone(), shares[0].clone());
assert!(matches!(result, Err(Error::CommitmentWrongDegree)));
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
public,
vec![0, 1, 2, 3, 4],
Vec::new(),
);
assert!(matches!(result, Err(Error::CommitmentWrongDegree)));
}
}
#[test]
fn test_reveal() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3],
vec![shares[4].clone()],
)
.unwrap();
}
#[test]
fn test_arbiter_reveals() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let mut commitments = Vec::with_capacity(n);
let mut reveals = Vec::with_capacity(n);
for con in &contributors {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.push(commitment.clone());
reveals.push(shares[q].clone());
let acks: Vec<u32> = (0..q as u32).collect();
let reveals = shares[q..n].to_vec();
arb.commitment(con.clone(), commitment, acks, reveals)
.unwrap();
}
let (result, _) = arb.finalize();
let output = result.unwrap();
assert_eq!(output.commitments.len(), q);
for (dealer_idx, commitment) in commitments.iter().enumerate().take(q) {
let dealer_idx = dealer_idx as u32;
assert_eq!(output.commitments.get(&dealer_idx).unwrap(), commitment);
assert_eq!(
output.reveals.get(&dealer_idx).unwrap()[0],
reveals[dealer_idx as usize]
);
}
}
#[test]
fn test_duplicate_commitment() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
arb.commitment(
contributors[0].clone(),
commitment.clone(),
vec![0, 1, 2, 3],
vec![shares[4].clone()],
)
.unwrap();
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3],
vec![shares[4].clone()],
);
assert!(matches!(result, Err(Error::DuplicateCommitment)));
}
#[test]
fn test_reveal_duplicate_player() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3],
vec![shares[3].clone()],
);
assert!(matches!(result, Err(Error::AlreadyActive)));
}
#[test]
fn test_insufficient_active() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
commitment.clone(),
vec![0, 1, 2, 3],
Vec::new(),
);
assert!(matches!(result, Err(Error::IncorrectActive)));
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3, 4],
Vec::new(),
);
assert!(matches!(result, Err(Error::DealerDisqualified)));
}
#[test]
fn test_manual_disqualify() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
arb.disqualify(contributors[0].clone());
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3, 4],
Vec::new(),
);
assert!(matches!(result, Err(Error::DealerDisqualified)));
}
#[test]
fn test_too_many_reveals() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2],
vec![shares[3].clone(), shares[4].clone()],
);
assert!(matches!(result, Err(Error::TooManyReveals)));
}
#[test]
fn test_incorrect_reveal() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let t = quorum(n);
let (_, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n, t);
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3],
vec![shares[4].clone()],
);
assert!(matches!(result, Err(Error::ShareWrongCommitment)));
}
#[test]
fn test_reveal_corrupt_share() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let mut share = shares[3].clone();
share.index = 4;
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3],
vec![share],
);
assert!(matches!(result, Err(Error::ShareWrongCommitment)));
}
#[test]
fn test_reveal_duplicate_ack() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 2],
Vec::new(),
);
assert!(matches!(result, Err(Error::AlreadyActive)));
}
#[test]
fn test_reveal_invalid_ack() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 10],
Vec::new(),
);
assert!(matches!(result, Err(Error::PlayerInvalid)));
}
#[test]
fn test_reveal_invalid_share() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let mut arb =
Arbiter::<_, MinSig>::new(None, contributors.clone(), contributors.clone(), 1);
let mut share = shares[3].clone();
share.index = 10;
let result = arb.commitment(
contributors[0].clone(),
commitment,
vec![0, 1, 2, 3],
vec![share],
);
assert!(matches!(result, Err(Error::PlayerInvalid)));
}
#[test]
fn test_dealer_acks() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
for player in &contributors {
dealer.ack(player.clone()).unwrap();
}
let output = dealer.finalize().unwrap();
assert_eq!(Vec::from(output.active), vec![0, 1, 2, 3, 4]);
assert!(output.inactive.is_empty());
}
#[test]
fn test_dealer_inactive() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
for player in contributors.iter().take(4) {
dealer.ack(player.clone()).unwrap();
}
let output = dealer.finalize().unwrap();
assert_eq!(Vec::from(output.active), vec![0, 1, 2, 3]);
assert_eq!(Vec::from(output.inactive), vec![4]);
}
#[test]
fn test_dealer_insufficient() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
for player in contributors.iter().take(2) {
dealer.ack(player.clone()).unwrap();
}
assert!(dealer.finalize().is_none());
}
#[test]
fn test_dealer_duplicate_ack() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let player = contributors[0].clone();
dealer.ack(player.clone()).unwrap();
let result = dealer.ack(player);
assert!(matches!(result, Err(Error::DuplicateAck)));
}
#[test]
fn test_dealer_invalid_player() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let (mut dealer, _, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
let player = PrivateKey::from_seed(n as u64).public_key();
let result = dealer.ack(player);
assert!(matches!(result, Err(Error::PlayerInvalid)));
}
#[test]
fn test_player_reveals() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.insert(last, commitment);
let mut reveals = BTreeMap::new();
reveals.insert(last, shares[0].clone());
player.finalize(commitments, reveals).unwrap();
}
#[test]
fn test_player_missing_reveal() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.insert(last, commitment);
let result = player.finalize(commitments, BTreeMap::new());
assert!(matches!(result, Err(Error::MissingShare)));
}
#[test]
fn test_player_insufficient_commitments() {
let n = 5;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(2) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let result = player.finalize(commitments, BTreeMap::new());
assert!(matches!(result, Err(Error::InvalidCommitments)));
}
#[test]
fn test_player_misdirected_reveal() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.insert(last, commitment);
let mut reveals = BTreeMap::new();
reveals.insert(last, shares[1].clone());
let result = player.finalize(commitments, reveals);
assert!(matches!(result, Err(Error::MisdirectedShare)));
}
#[test]
fn test_player_invalid_commitment() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (commitment, shares) = ops::generate_shares::<_, MinSig>(&mut rng, None, n as u32, 1);
commitments.insert(last, commitment);
let mut reveals = BTreeMap::new();
reveals.insert(last, shares[0].clone());
let result = player.finalize(commitments, reveals);
assert!(matches!(result, Err(Error::CommitmentWrongDegree)));
}
#[test]
fn test_player_invalid_reveal() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.insert(last, commitment);
let mut reveals = BTreeMap::new();
let mut share = shares[1].clone();
share.index = 0;
reveals.insert(last, share);
let result = player.finalize(commitments, reveals);
assert!(matches!(result, Err(Error::ShareWrongCommitment)));
}
#[test]
fn test_player_dealer_equivocation() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.insert(last, commitment);
let mut public = poly::Public::<MinSig>::zero();
for commitment in commitments.values() {
public.add(commitment);
}
let mut reveals = BTreeMap::new();
reveals.insert(last, shares[0].clone());
let result = player.finalize(commitments, reveals).unwrap();
assert_eq!(result.public, public);
}
#[test]
fn test_player_dealer_equivocation_missing_reveal() {
let n = 11;
let q = quorum(n as u32) as usize;
let mut rng = StdRng::seed_from_u64(0);
let contributors = (0..n)
.map(|i| PrivateKey::from_seed(i as u64).public_key())
.collect::<Ordered<_>>();
let mut player = Player::<_, MinSig>::new(
contributors[0].clone(),
None,
contributors.clone(),
contributors.clone(),
1,
);
let mut commitments = BTreeMap::new();
for (i, con) in contributors.iter().enumerate().take(q - 1) {
let (_, commitment, shares) =
Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
player
.share(con.clone(), commitment.clone(), shares[0].clone())
.unwrap();
commitments.insert(i as u32, commitment);
}
let last = (q - 1) as u32;
let (_, commitment, _) = Dealer::<_, MinSig>::new(&mut rng, None, contributors.clone());
commitments.insert(last, commitment);
let result = player.finalize(commitments, BTreeMap::new());
assert!(matches!(result, Err(Error::MissingShare)));
}
#[derive(Clone)]
struct Round {
players: Vec<u64>,
absent_dealers: Vec<u64>,
absent_players: Vec<u64>,
}
impl From<Vec<u64>> for Round {
fn from(players: Vec<u64>) -> Self {
Self {
players,
absent_dealers: Vec::new(),
absent_players: Vec::new(),
}
}
}
impl Round {
fn with_absent_dealers(mut self, absent_dealers: Vec<u64>) -> Self {
self.absent_dealers = absent_dealers;
self
}
fn with_absent_players(mut self, absent_players: Vec<u64>) -> Self {
self.absent_players = absent_players;
self
}
}
#[derive(Clone)]
struct Plan {
rounds: Vec<Round>,
seed: u64,
concurrency: usize,
}
impl Plan {
fn from(rounds: Vec<Round>) -> Self {
Self {
rounds,
seed: 0,
concurrency: 1,
}
}
fn with_concurrency(mut self, concurrency: usize) -> Self {
self.concurrency = concurrency;
self
}
fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
fn run<V: Variant>(&self) -> V::Public {
assert!(
!self.rounds.is_empty(),
"plan must contain at least one round"
);
let mut rng = StdRng::seed_from_u64(self.seed);
let mut current_public: Option<poly::Public<V>> = None;
let mut participant_states: HashMap<PublicKey, player::Output<V>> = HashMap::new();
let mut share_holders: Option<Ordered<PublicKey>> = None;
for (round_idx, round) in self.rounds.iter().enumerate() {
assert!(
!round.players.is_empty(),
"round {round_idx} must include at least one player",
);
let player_set = participants(&round.players);
let dealer_candidates = if let Some(ref registry) = share_holders {
registry.clone()
} else {
player_set.clone()
};
assert!(
!dealer_candidates.is_empty(),
"round {round_idx} must have at least one dealer",
);
let absent_dealers = participants(&round.absent_dealers);
for absent in absent_dealers.iter() {
assert!(
dealer_candidates.position(absent).is_some(),
"round {round_idx} absent dealer not in committee"
);
}
let dealer_registry = if let Some(ref registry) = share_holders {
for dealer in dealer_candidates.iter() {
assert!(
registry.position(dealer).is_some(),
"round {round_idx} dealer not in previous committee",
);
}
registry.clone()
} else {
dealer_candidates.clone()
};
let mut active_dealers = Vec::new();
for dealer in dealer_candidates.iter() {
if absent_dealers.position(dealer).is_some() {
continue;
}
active_dealers.push(dealer.clone());
}
let active_len = active_dealers.len();
let min_dealers = match current_public.as_ref() {
None => quorum(player_set.len() as u32),
Some(previous) => previous.required(),
} as usize;
assert!(
active_len >= min_dealers,
"round {} requires at least {} active dealers for {} players, got {}",
round_idx,
min_dealers,
player_set.len(),
active_len
);
let absent_players = participants(&round.absent_players);
for absent in absent_players.iter() {
assert!(
player_set.position(absent).is_some(),
"round {round_idx} absent player not in committee"
);
}
let mut dealers = BTreeMap::new();
let mut dealer_outputs = BTreeMap::new();
let mut expected_reveals = BTreeMap::new();
let expected_inactive: Ordered<u32> = absent_players
.iter()
.map(|player_pk| player_set.position(player_pk).unwrap() as u32)
.collect();
for dealer_pk in active_dealers.iter() {
let previous_share = participant_states
.get(dealer_pk)
.map(|out| out.share.clone());
if current_public.is_some() && previous_share.is_none() {
panic!("dealer missing share required for reshare in round {round_idx}",);
}
let (dealer, commitment, shares) =
Dealer::<_, V>::new(&mut rng, previous_share, player_set.clone());
dealers.insert(dealer_pk.clone(), dealer);
dealer_outputs.insert(dealer_pk.clone(), (commitment, shares));
}
let mut players = BTreeMap::new();
for player_pk in player_set.iter() {
if absent_players.position(player_pk).is_some() {
continue;
}
let player = Player::<_, V>::new(
player_pk.clone(),
current_public.clone(),
dealer_registry.clone(),
player_set.clone(),
self.concurrency,
);
players.insert(player_pk.clone(), player);
}
let mut arbiter = Arbiter::<_, V>::new(
current_public.clone(),
dealer_registry.clone(),
player_set.clone(),
self.concurrency,
);
for dealer_pk in active_dealers.iter() {
let (commitment, shares) = dealer_outputs
.get(dealer_pk)
.expect("missing dealer output");
let commitment = commitment.clone();
let shares = shares.clone();
let mut dealer_reveals = Vec::new();
{
let dealer = dealers.get_mut(dealer_pk).expect("missing dealer instance");
for (idx, player_pk) in player_set.iter().enumerate() {
let share = shares[idx].clone();
if absent_players.position(player_pk).is_some() {
dealer_reveals.push(share);
continue;
}
let player_obj = players
.get_mut(player_pk)
.expect("missing player for share delivery");
if let Err(err) =
player_obj.share(dealer_pk.clone(), commitment.clone(), share)
{
panic!(
"failed to deliver share from dealer {dealer_pk:?} to player {player_pk:?}: {err:?}",
);
}
dealer.ack(player_pk.clone()).unwrap();
}
}
let dealer = dealers
.remove(dealer_pk)
.expect("missing dealer instance after distribution");
let dealer_output = dealer.finalize().expect("insufficient acknowledgements");
assert_eq!(
dealer_output.inactive, expected_inactive,
"inactive set mismatch for dealer in round {round_idx}",
);
let dealer_pos = dealer_registry.position(dealer_pk).unwrap() as u32;
if !dealer_reveals.is_empty() {
expected_reveals.insert(dealer_pos, dealer_reveals.clone());
}
arbiter
.commitment(
dealer_pk.clone(),
commitment,
dealer_output.active.into(),
dealer_reveals,
)
.unwrap();
}
assert!(arbiter.ready(), "arbiter not ready in round {round_idx}");
let (result, disqualified) = arbiter.finalize();
let expected_disqualified =
dealer_registry.len().saturating_sub(active_dealers.len());
assert_eq!(
disqualified.len(),
expected_disqualified,
"unexpected disqualified dealers in round {round_idx}",
);
let output = result.unwrap();
for (&dealer_idx, _) in output.commitments.iter() {
let expected = expected_reveals.remove(&dealer_idx).unwrap_or_default();
match output.reveals.get(&dealer_idx) {
Some(reveals) => assert_eq!(
reveals, &expected,
"unexpected reveal content for dealer {dealer_idx} in round {round_idx}",
),
None => assert!(
expected.is_empty(),
"missing reveals for dealer {dealer_idx} in round {round_idx}",
),
}
}
for dealer_idx in output.reveals.keys() {
assert!(
output.commitments.contains_key(dealer_idx),
"reveals present for unselected dealer {dealer_idx} in round {round_idx}",
);
}
let expected_commitments = quorum(dealer_registry.len() as u32) as usize;
assert_eq!(
output.commitments.len(),
expected_commitments,
"unexpected number of commitments in round {round_idx}",
);
let mut round_results = Vec::new();
let mut next_states = HashMap::new();
for player_pk in player_set.iter() {
if absent_players.position(player_pk).is_some() {
continue;
}
let player_obj = players.remove(player_pk).unwrap();
let result = player_obj
.finalize(output.commitments.clone(), BTreeMap::new())
.unwrap();
assert_eq!(result.public, output.public);
next_states.insert(player_pk.clone(), result.clone());
round_results.push(result);
}
assert!(
!round_results.is_empty(),
"round {round_idx} produced no outputs",
);
let public_key = public::<V>(&round_results[0].public);
if let Some(previous) = current_public.as_ref() {
assert_eq!(public_key, public::<V>(previous));
}
let threshold = quorum(player_set.len() as u32);
let partials = round_results
.iter()
.map(|res| partial_sign_proof_of_possession::<V>(&res.public, &res.share))
.collect::<Vec<_>>();
let signature = threshold_signature_recover::<V, _>(threshold, &partials)
.expect("unable to recover threshold signature");
verify_proof_of_possession::<V>(public_key, &signature)
.expect("invalid proof of possession");
current_public = Some(round_results[0].public.clone());
share_holders = Some(player_set);
participant_states = next_states;
}
*public::<V>(¤t_public.expect("plan must produce a public constant"))
}
}
fn participants(ids: &[u64]) -> Ordered<PublicKey> {
ids.iter()
.map(|id| PrivateKey::from_seed(*id).public_key())
.collect::<Ordered<_>>()
}
#[test]
fn test_dkg() {
let plan = Plan::from(vec![Round::from((0..5).collect::<Vec<_>>())]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_dkg_with_absent_dealer() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3]).with_absent_dealers(vec![3])
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_dkg_with_absent_player() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3]).with_absent_players(vec![3])
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_dkg_determinism() {
let plan_template = || Plan::from(vec![Round::from((0..5).collect::<Vec<_>>())]);
let public_a_pk = plan_template().with_seed(1).run::<MinPk>();
assert_eq!(public_a_pk, plan_template().with_seed(1).run::<MinPk>());
let public_b_pk = plan_template().with_seed(2).run::<MinPk>();
assert_eq!(public_b_pk, plan_template().with_seed(2).run::<MinPk>());
assert_ne!(public_a_pk, public_b_pk);
let public_a_sig = plan_template().with_seed(1).run::<MinSig>();
assert_eq!(public_a_sig, plan_template().with_seed(1).run::<MinSig>());
let public_b_sig = plan_template().with_seed(2).run::<MinSig>();
assert_eq!(public_b_sig, plan_template().with_seed(2).run::<MinSig>());
assert_ne!(public_a_sig, public_b_sig);
}
#[test]
fn test_reshare_distinct() {
let plan = Plan::from(vec![
Round::from((0..5).collect::<Vec<_>>()),
Round::from((5..15).collect::<Vec<_>>()),
Round::from((15..30).collect::<Vec<_>>()),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_increasing_committee() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2]),
Round::from(vec![0, 1, 2, 3]),
Round::from(vec![0, 1, 2, 3, 4]),
Round::from(vec![0, 1, 2, 3, 4, 5]),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_decreasing_committee() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3, 4, 5]),
Round::from(vec![0, 1, 2, 3, 4]),
Round::from(vec![0, 1, 2, 3]),
Round::from(vec![0, 1, 2]),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_with_absent_dealer() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3]),
Round::from(vec![4, 5, 6, 7]).with_absent_dealers(vec![3]),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_with_absent_player() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3]),
Round::from(vec![4, 5, 6, 7]).with_absent_players(vec![4]),
Round::from(vec![8, 9, 10, 11]).with_absent_dealers(vec![4]),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_min_active() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3]).with_absent_players(vec![3]),
Round::from(vec![4, 5, 6, 7]).with_absent_dealers(vec![3]),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_min_active_different_sizes() {
let plan = Plan::from(vec![
Round::from(vec![0, 1, 2, 3]).with_absent_players(vec![3]),
Round::from(vec![4, 5, 6, 7, 8, 9]).with_absent_dealers(vec![3]),
]);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_min_active_large() {
let plan = Plan::from(vec![
Round::from((0..20).collect::<Vec<_>>())
.with_absent_dealers((14..20).collect::<Vec<_>>())
.with_absent_players((14..20).collect::<Vec<_>>()),
Round::from((100..200).collect::<Vec<_>>())
.with_absent_dealers((14..20).collect::<Vec<_>>()),
])
.with_concurrency(4);
plan.run::<MinPk>();
plan.run::<MinSig>();
}
#[test]
fn test_reshare_determinism() {
let plan_template = || {
Plan::from(vec![
Round::from((0..5).collect::<Vec<_>>()),
Round::from((5..10).collect::<Vec<_>>()),
])
};
let public_a_pk = plan_template().with_seed(1).run::<MinPk>();
assert_eq!(public_a_pk, plan_template().with_seed(1).run::<MinPk>());
let public_b_pk = plan_template().with_seed(2).run::<MinPk>();
assert_eq!(public_b_pk, plan_template().with_seed(2).run::<MinPk>());
assert_ne!(public_a_pk, public_b_pk);
let public_a_sig = plan_template().with_seed(1).run::<MinSig>();
assert_eq!(public_a_sig, plan_template().with_seed(1).run::<MinSig>());
let public_b_sig = plan_template().with_seed(2).run::<MinSig>();
assert_eq!(public_b_sig, plan_template().with_seed(2).run::<MinSig>());
assert_ne!(public_a_sig, public_b_sig);
}
}