use crate::{
simplex::scheme::bls12381_threshold::vrf as bls12381_threshold_vrf,
types::{Participant, Round, View},
};
use commonware_codec::Encode;
use commonware_cryptography::{
bls12381::primitives::variant::Variant, certificate::Scheme, Hasher, PublicKey, Sha256,
};
use commonware_utils::{modulo, ordered::Set};
use std::marker::PhantomData;
pub trait Config<S: Scheme>: Clone + Default + Send + 'static {
type Elector: Elector<S>;
fn build(self, participants: &Set<S::PublicKey>) -> Self::Elector;
}
pub trait Elector<S: Scheme>: Clone + Send + 'static {
fn elect(&self, round: Round, certificate: Option<&S::Certificate>) -> Participant;
}
#[derive(Clone, Debug, Default)]
pub struct RoundRobin<H: Hasher = Sha256> {
seed: Option<Vec<u8>>,
_phantom: PhantomData<H>,
}
impl<H: Hasher> RoundRobin<H> {
pub fn shuffled(seed: &[u8]) -> Self {
Self {
seed: Some(seed.to_vec()),
_phantom: PhantomData,
}
}
}
impl<S: Scheme, H: Hasher> Config<S> for RoundRobin<H> {
type Elector = RoundRobinElector<S>;
fn build(self, participants: &Set<S::PublicKey>) -> RoundRobinElector<S> {
assert!(!participants.is_empty(), "no participants");
let mut permutation: Vec<Participant> = (0..participants.len())
.map(Participant::from_usize)
.collect();
if let Some(seed) = &self.seed {
let mut hasher = H::new();
permutation.sort_by_key(|&index| {
hasher.update(seed);
hasher.update(&index.get().encode());
hasher.finalize()
});
}
RoundRobinElector {
permutation,
_phantom: PhantomData,
}
}
}
#[derive(Clone, Debug)]
pub struct RoundRobinElector<S: Scheme> {
permutation: Vec<Participant>,
_phantom: PhantomData<S>,
}
impl<S: Scheme> Elector<S> for RoundRobinElector<S> {
fn elect(&self, round: Round, _certificate: Option<&S::Certificate>) -> Participant {
let n = self.permutation.len();
let idx = (round.epoch().get().wrapping_add(round.view().get())) as usize % n;
self.permutation[idx]
}
}
#[derive(Clone, Debug, Default)]
pub struct Random;
impl Random {
pub fn select_leader<V: Variant>(
round: Round,
n: u32,
seed_signature: Option<V::Signature>,
) -> Participant {
assert!(seed_signature.is_some() || round.view() == View::new(1));
let Some(seed_signature) = seed_signature else {
return Participant::new(
(round.epoch().get().wrapping_add(round.view().get())) as u32 % n,
);
};
Participant::new(modulo(seed_signature.encode().as_ref(), n as u64) as u32)
}
}
impl<P, V> Config<bls12381_threshold_vrf::Scheme<P, V>> for Random
where
P: PublicKey,
V: Variant,
{
type Elector = RandomElector<bls12381_threshold_vrf::Scheme<P, V>>;
fn build(self, participants: &Set<P>) -> RandomElector<bls12381_threshold_vrf::Scheme<P, V>> {
assert!(!participants.is_empty(), "no participants");
RandomElector {
n: participants.len() as u32,
_phantom: PhantomData,
}
}
}
#[derive(Clone, Debug)]
pub struct RandomElector<S: Scheme> {
n: u32,
_phantom: PhantomData<S>,
}
impl<P, V> Elector<bls12381_threshold_vrf::Scheme<P, V>>
for RandomElector<bls12381_threshold_vrf::Scheme<P, V>>
where
P: PublicKey,
V: Variant,
{
fn elect(
&self,
round: Round,
certificate: Option<&bls12381_threshold_vrf::Certificate<V>>,
) -> Participant {
Random::select_leader::<V>(
round,
self.n,
certificate.map(|c| {
c.get()
.expect("verified certificate must decode")
.seed_signature
}),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
simplex::{
scheme::{bls12381_threshold::vrf as bls12381_threshold_vrf, ed25519},
types::Subject,
},
types::{Epoch, View},
};
use commonware_cryptography::{
bls12381::primitives::variant::MinPk, certificate::mocks::Fixture,
sha256::Digest as Sha256Digest, Sha256,
};
use commonware_parallel::Sequential;
use commonware_utils::{test_rng, Faults, N3f1, TryFromIterator};
const NAMESPACE: &[u8] = b"test";
type ThresholdScheme =
bls12381_threshold_vrf::Scheme<commonware_cryptography::ed25519::PublicKey, MinPk>;
#[test]
fn round_robin_rotates_through_participants() {
let mut rng = test_rng();
let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 4);
let participants = Set::try_from_iter(participants).unwrap();
let n = participants.len() as u32;
let elector: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::default().build(&participants);
let epoch = Epoch::new(0);
let mut leaders = Vec::new();
for view in 1..=(3 * n as u64) {
let round = Round::new(epoch, View::new(view));
leaders.push(elector.elect(round, None));
}
for i in 0..leaders.len() - 1 {
assert_eq!(Participant::new((leaders[i].get() + 1) % n), leaders[i + 1]);
}
}
#[test]
fn round_robin_cycles_through_epochs() {
let mut rng = test_rng();
let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 5);
let participants = Set::try_from_iter(participants).unwrap();
let n = participants.len();
let elector: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::default().build(&participants);
let leaders: Vec<_> = (0..n as u64)
.map(|e| {
let round = Round::new(Epoch::new(e), View::new(1));
elector.elect(round, None)
})
.collect();
let mut seen = vec![false; n];
for leader in &leaders {
assert!(!seen[usize::from(*leader)]);
seen[usize::from(*leader)] = true;
}
assert!(seen.iter().all(|x| *x));
}
#[test]
fn round_robin_shuffled_changes_order() {
let mut rng = test_rng();
let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 5);
let participants = Set::try_from_iter(participants).unwrap();
let elector_no_seed: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::default().build(&participants);
let elector_seed_1: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::shuffled(b"seed1").build(&participants);
let elector_seed_2: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::shuffled(b"seed2").build(&participants);
let epoch = Epoch::new(0);
let leaders_no_seed: Vec<_> = (1..=5)
.map(|v| elector_no_seed.elect(Round::new(epoch, View::new(v)), None))
.collect();
let leaders_seed_1: Vec<_> = (1..=5)
.map(|v| elector_seed_1.elect(Round::new(epoch, View::new(v)), None))
.collect();
let leaders_seed_2: Vec<_> = (1..=5)
.map(|v| elector_seed_2.elect(Round::new(epoch, View::new(v)), None))
.collect();
assert_eq!(
leaders_no_seed,
vec![
Participant::new(1),
Participant::new(2),
Participant::new(3),
Participant::new(4),
Participant::new(0)
]
);
assert_ne!(leaders_seed_1, leaders_no_seed);
assert_ne!(leaders_seed_2, leaders_no_seed);
assert_ne!(leaders_seed_1, leaders_seed_2);
for leaders in [&leaders_seed_1, &leaders_seed_2] {
let mut sorted = leaders.clone();
sorted.sort();
assert_eq!(
sorted,
vec![
Participant::new(0),
Participant::new(1),
Participant::new(2),
Participant::new(3),
Participant::new(4)
]
);
}
}
#[test]
fn round_robin_same_seed_is_deterministic() {
let mut rng = test_rng();
let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 5);
let participants = Set::try_from_iter(participants).unwrap();
let elector1: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::shuffled(b"same_seed").build(&participants);
let elector2: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::shuffled(b"same_seed").build(&participants);
let epoch = Epoch::new(0);
for view in 1..=10 {
let round = Round::new(epoch, View::new(view));
assert_eq!(elector1.elect(round, None), elector2.elect(round, None));
}
}
#[test]
#[should_panic(expected = "no participants")]
fn round_robin_build_panics_on_empty_participants() {
let participants: Set<commonware_cryptography::ed25519::PublicKey> = Set::default();
let _: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::default().build(&participants);
}
#[test]
fn random_falls_back_to_round_robin_for_view_1() {
let mut rng = test_rng();
let Fixture { participants, .. } =
bls12381_threshold_vrf::fixture::<MinPk, _>(&mut rng, NAMESPACE, 5);
let participants = Set::try_from_iter(participants).unwrap();
let n = participants.len();
let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
let leaders: Vec<_> = (0..n as u64)
.map(|e| {
let round = Round::new(Epoch::new(e), View::new(1));
elector.elect(round, None)
})
.collect();
let mut seen = vec![false; n];
for leader in &leaders {
assert!(!seen[usize::from(*leader)]);
seen[usize::from(*leader)] = true;
}
assert!(seen.iter().all(|x| *x));
}
#[test]
fn random_uses_certificate_randomness() {
let mut rng = test_rng();
let Fixture {
participants,
schemes,
..
} = bls12381_threshold_vrf::fixture::<MinPk, _>(&mut rng, NAMESPACE, 5);
let participants = Set::try_from_iter(participants).unwrap();
let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
let quorum = N3f1::quorum(schemes.len()) as usize;
let round1 = Round::new(Epoch::new(1), View::new(2));
let attestations1: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(Subject::Nullify { round: round1 })
.unwrap()
})
.collect();
let cert1 = schemes[0]
.assemble::<_, N3f1>(attestations1, &Sequential)
.unwrap();
let round2 = Round::new(Epoch::new(1), View::new(3));
let attestations2: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(Subject::Nullify { round: round2 })
.unwrap()
})
.collect();
let cert2 = schemes[0]
.assemble::<_, N3f1>(attestations2, &Sequential)
.unwrap();
let leader1a = elector.elect(round1, Some(&cert1));
let leader1b = elector.elect(round1, Some(&cert1));
assert_eq!(leader1a, leader1b);
let leader2 = elector.elect(round1, Some(&cert2));
assert_ne!(leader1a, leader2);
}
#[test]
#[should_panic(expected = "no participants")]
fn random_build_panics_on_empty_participants() {
let participants: Set<commonware_cryptography::ed25519::PublicKey> = Set::default();
let _: RandomElector<ThresholdScheme> = Random.build(&participants);
}
#[test]
#[should_panic]
fn random_panics_on_none_certificate_after_view_1() {
let mut rng = test_rng();
let Fixture { participants, .. } =
bls12381_threshold_vrf::fixture::<MinPk, _>(&mut rng, NAMESPACE, 5);
let participants = Set::try_from_iter(participants).unwrap();
let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
let round = Round::new(Epoch::new(1), View::new(2));
elector.elect(round, None);
}
mod conformance {
use super::*;
use commonware_codec::{Encode, Write};
use commonware_conformance::Conformance;
use commonware_cryptography::Sha256;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
struct RoundRobinShuffleConformance;
impl Conformance for RoundRobinShuffleConformance {
async fn commit(seed: u64) -> Vec<u8> {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let n = rng.gen_range(1..=100);
let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, n);
let participants = Set::try_from_iter(participants).unwrap();
let shuffle_seed: [u8; 32] = rng.gen();
let elector: RoundRobinElector<ed25519::Scheme> =
RoundRobin::<Sha256>::shuffled(&shuffle_seed).build(&participants);
elector.permutation.encode().to_vec()
}
}
struct RandomSelectLeaderConformance;
impl Conformance for RandomSelectLeaderConformance {
async fn commit(seed: u64) -> Vec<u8> {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let n = rng.gen_range(4..=10);
let Fixture {
participants,
schemes,
..
} = bls12381_threshold_vrf::fixture::<MinPk, _>(&mut rng, NAMESPACE, n);
let participants = Set::try_from_iter(participants).unwrap();
let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
let quorum = N3f1::quorum(schemes.len()) as usize;
let epoch = rng.gen_range(0..1000);
let view = rng.gen_range(2..=101);
let round = Round::new(Epoch::new(epoch), View::new(view));
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| s.sign::<Sha256Digest>(Subject::Nullify { round }).unwrap())
.collect();
let cert = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let leader = elector.elect(round, Some(&cert));
let round_v1 = Round::new(Epoch::new(epoch), View::new(1));
let leader_v1 = elector.elect(round_v1, None);
let mut result = leader.encode_mut();
leader_v1.write(&mut result);
result.to_vec()
}
}
commonware_conformance::conformance_tests! {
RoundRobinShuffleConformance => 512,
RandomSelectLeaderConformance => 512,
}
}
}