commonware_consensus/simplex/
elector.rs1use crate::{
29 simplex::scheme::bls12381_threshold,
30 types::{Participant, Round, View},
31};
32use commonware_codec::Encode;
33use commonware_cryptography::{
34 bls12381::primitives::variant::Variant, certificate::Scheme, Hasher, PublicKey, Sha256,
35};
36use commonware_utils::{modulo, ordered::Set};
37use std::marker::PhantomData;
38
39pub trait Config<S: Scheme>: Clone + Default + Send + 'static {
52 type Elector: Elector<S>;
54
55 fn build(self, participants: &Set<S::PublicKey>) -> Self::Elector;
63}
64
65pub trait Elector<S: Scheme>: Clone + Send + 'static {
78 fn elect(&self, round: Round, certificate: Option<&S::Certificate>) -> Participant;
86}
87
88#[derive(Clone, Debug, Default)]
95pub struct RoundRobin<H: Hasher = Sha256> {
96 seed: Option<Vec<u8>>,
97 _phantom: PhantomData<H>,
98}
99
100impl<H: Hasher> RoundRobin<H> {
101 pub fn shuffled(seed: &[u8]) -> Self {
106 Self {
107 seed: Some(seed.to_vec()),
108 _phantom: PhantomData,
109 }
110 }
111}
112
113impl<S: Scheme, H: Hasher> Config<S> for RoundRobin<H> {
114 type Elector = RoundRobinElector<S>;
115
116 fn build(self, participants: &Set<S::PublicKey>) -> RoundRobinElector<S> {
117 assert!(!participants.is_empty(), "no participants");
118
119 let mut permutation: Vec<Participant> = (0..participants.len())
120 .map(Participant::from_usize)
121 .collect();
122
123 if let Some(seed) = &self.seed {
124 let mut hasher = H::new();
125 permutation.sort_by_key(|&index| {
126 hasher.update(seed);
127 hasher.update(&index.get().encode());
128 hasher.finalize()
129 });
130 }
131
132 RoundRobinElector {
133 permutation,
134 _phantom: PhantomData,
135 }
136 }
137}
138
139#[derive(Clone, Debug)]
143pub struct RoundRobinElector<S: Scheme> {
144 permutation: Vec<Participant>,
145 _phantom: PhantomData<S>,
146}
147
148impl<S: Scheme> Elector<S> for RoundRobinElector<S> {
149 fn elect(&self, round: Round, _certificate: Option<&S::Certificate>) -> Participant {
150 let n = self.permutation.len();
151 let idx = (round.epoch().get().wrapping_add(round.view().get())) as usize % n;
152 self.permutation[idx]
153 }
154}
155
156#[derive(Clone, Debug, Default)]
164pub struct Random;
165
166impl Random {
167 pub fn select_leader<V: Variant>(
169 round: Round,
170 n: u32,
171 seed_signature: Option<V::Signature>,
172 ) -> Participant {
173 assert!(seed_signature.is_some() || round.view() == View::new(1));
174
175 let Some(seed_signature) = seed_signature else {
176 return Participant::new(
178 (round.epoch().get().wrapping_add(round.view().get())) as u32 % n,
179 );
180 };
181
182 Participant::new(modulo(seed_signature.encode().as_ref(), n as u64) as u32)
184 }
185}
186
187impl<P, V> Config<bls12381_threshold::Scheme<P, V>> for Random
188where
189 P: PublicKey,
190 V: Variant,
191{
192 type Elector = RandomElector<bls12381_threshold::Scheme<P, V>>;
193
194 fn build(self, participants: &Set<P>) -> RandomElector<bls12381_threshold::Scheme<P, V>> {
195 assert!(!participants.is_empty(), "no participants");
196 RandomElector {
197 n: participants.len() as u32,
198 _phantom: PhantomData,
199 }
200 }
201}
202
203#[derive(Clone, Debug)]
207pub struct RandomElector<S: Scheme> {
208 n: u32,
209 _phantom: PhantomData<S>,
210}
211
212impl<P, V> Elector<bls12381_threshold::Scheme<P, V>>
213 for RandomElector<bls12381_threshold::Scheme<P, V>>
214where
215 P: PublicKey,
216 V: Variant,
217{
218 fn elect(
219 &self,
220 round: Round,
221 certificate: Option<&bls12381_threshold::Signature<V>>,
222 ) -> Participant {
223 Random::select_leader::<V>(round, self.n, certificate.map(|c| c.seed_signature))
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::{
231 simplex::{
232 scheme::{bls12381_threshold, ed25519},
233 types::Subject,
234 },
235 types::{Epoch, View},
236 };
237 use commonware_cryptography::{
238 bls12381::primitives::variant::MinPk, certificate::mocks::Fixture,
239 sha256::Digest as Sha256Digest, Sha256,
240 };
241 use commonware_parallel::Sequential;
242 use commonware_utils::{test_rng, Faults, N3f1, TryFromIterator};
243
244 const NAMESPACE: &[u8] = b"test";
245
246 type ThresholdScheme =
247 bls12381_threshold::Scheme<commonware_cryptography::ed25519::PublicKey, MinPk>;
248
249 #[test]
250 fn round_robin_rotates_through_participants() {
251 let mut rng = test_rng();
252 let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 4);
253 let participants = Set::try_from_iter(participants).unwrap();
254 let n = participants.len() as u32;
255 let elector: RoundRobinElector<ed25519::Scheme> =
256 RoundRobin::<Sha256>::default().build(&participants);
257 let epoch = Epoch::new(0);
258
259 let mut leaders = Vec::new();
261 for view in 1..=(3 * n as u64) {
262 let round = Round::new(epoch, View::new(view));
263 leaders.push(elector.elect(round, None));
264 }
265
266 for i in 0..leaders.len() - 1 {
268 assert_eq!(Participant::new((leaders[i].get() + 1) % n), leaders[i + 1]);
269 }
270 }
271
272 #[test]
273 fn round_robin_cycles_through_epochs() {
274 let mut rng = test_rng();
275 let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 5);
276 let participants = Set::try_from_iter(participants).unwrap();
277 let n = participants.len();
278 let elector: RoundRobinElector<ed25519::Scheme> =
279 RoundRobin::<Sha256>::default().build(&participants);
280
281 let leaders: Vec<_> = (0..n as u64)
283 .map(|e| {
284 let round = Round::new(Epoch::new(e), View::new(1));
285 elector.elect(round, None)
286 })
287 .collect();
288
289 let mut seen = vec![false; n];
291 for leader in &leaders {
292 assert!(!seen[usize::from(*leader)]);
293 seen[usize::from(*leader)] = true;
294 }
295 assert!(seen.iter().all(|x| *x));
296 }
297
298 #[test]
299 fn round_robin_shuffled_changes_order() {
300 let mut rng = test_rng();
301 let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 5);
302 let participants = Set::try_from_iter(participants).unwrap();
303
304 let elector_no_seed: RoundRobinElector<ed25519::Scheme> =
305 RoundRobin::<Sha256>::default().build(&participants);
306 let elector_seed_1: RoundRobinElector<ed25519::Scheme> =
307 RoundRobin::<Sha256>::shuffled(b"seed1").build(&participants);
308 let elector_seed_2: RoundRobinElector<ed25519::Scheme> =
309 RoundRobin::<Sha256>::shuffled(b"seed2").build(&participants);
310
311 let epoch = Epoch::new(0);
313 let leaders_no_seed: Vec<_> = (1..=5)
314 .map(|v| elector_no_seed.elect(Round::new(epoch, View::new(v)), None))
315 .collect();
316 let leaders_seed_1: Vec<_> = (1..=5)
317 .map(|v| elector_seed_1.elect(Round::new(epoch, View::new(v)), None))
318 .collect();
319 let leaders_seed_2: Vec<_> = (1..=5)
320 .map(|v| elector_seed_2.elect(Round::new(epoch, View::new(v)), None))
321 .collect();
322
323 assert_eq!(
325 leaders_no_seed,
326 vec![
327 Participant::new(1),
328 Participant::new(2),
329 Participant::new(3),
330 Participant::new(4),
331 Participant::new(0)
332 ]
333 );
334
335 assert_ne!(leaders_seed_1, leaders_no_seed);
337 assert_ne!(leaders_seed_2, leaders_no_seed);
338 assert_ne!(leaders_seed_1, leaders_seed_2);
339
340 for leaders in [&leaders_seed_1, &leaders_seed_2] {
342 let mut sorted = leaders.clone();
343 sorted.sort();
344 assert_eq!(
345 sorted,
346 vec![
347 Participant::new(0),
348 Participant::new(1),
349 Participant::new(2),
350 Participant::new(3),
351 Participant::new(4)
352 ]
353 );
354 }
355 }
356
357 #[test]
358 fn round_robin_same_seed_is_deterministic() {
359 let mut rng = test_rng();
360 let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, 5);
361 let participants = Set::try_from_iter(participants).unwrap();
362
363 let elector1: RoundRobinElector<ed25519::Scheme> =
364 RoundRobin::<Sha256>::shuffled(b"same_seed").build(&participants);
365 let elector2: RoundRobinElector<ed25519::Scheme> =
366 RoundRobin::<Sha256>::shuffled(b"same_seed").build(&participants);
367
368 let epoch = Epoch::new(0);
369 for view in 1..=10 {
370 let round = Round::new(epoch, View::new(view));
371 assert_eq!(elector1.elect(round, None), elector2.elect(round, None));
372 }
373 }
374
375 #[test]
376 #[should_panic(expected = "no participants")]
377 fn round_robin_build_panics_on_empty_participants() {
378 let participants: Set<commonware_cryptography::ed25519::PublicKey> = Set::default();
379 let _: RoundRobinElector<ed25519::Scheme> =
380 RoundRobin::<Sha256>::default().build(&participants);
381 }
382
383 #[test]
384 fn random_falls_back_to_round_robin_for_view_1() {
385 let mut rng = test_rng();
386 let Fixture { participants, .. } =
387 bls12381_threshold::fixture::<MinPk, _>(&mut rng, NAMESPACE, 5);
388 let participants = Set::try_from_iter(participants).unwrap();
389 let n = participants.len();
390 let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
391
392 let leaders: Vec<_> = (0..n as u64)
394 .map(|e| {
395 let round = Round::new(Epoch::new(e), View::new(1));
396 elector.elect(round, None)
397 })
398 .collect();
399
400 let mut seen = vec![false; n];
402 for leader in &leaders {
403 assert!(!seen[usize::from(*leader)]);
404 seen[usize::from(*leader)] = true;
405 }
406 assert!(seen.iter().all(|x| *x));
407 }
408
409 #[test]
410 fn random_uses_certificate_randomness() {
411 let mut rng = test_rng();
412 let Fixture {
413 participants,
414 schemes,
415 ..
416 } = bls12381_threshold::fixture::<MinPk, _>(&mut rng, NAMESPACE, 5);
417 let participants = Set::try_from_iter(participants).unwrap();
418 let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
419 let quorum = N3f1::quorum_from_slice(&schemes) as usize;
420
421 let round1 = Round::new(Epoch::new(1), View::new(2));
423 let attestations1: Vec<_> = schemes
424 .iter()
425 .take(quorum)
426 .map(|s| {
427 s.sign::<Sha256Digest>(Subject::Nullify { round: round1 })
428 .unwrap()
429 })
430 .collect();
431 let cert1 = schemes[0]
432 .assemble::<_, N3f1>(attestations1, &Sequential)
433 .unwrap();
434
435 let round2 = Round::new(Epoch::new(1), View::new(3));
437 let attestations2: Vec<_> = schemes
438 .iter()
439 .take(quorum)
440 .map(|s| {
441 s.sign::<Sha256Digest>(Subject::Nullify { round: round2 })
442 .unwrap()
443 })
444 .collect();
445 let cert2 = schemes[0]
446 .assemble::<_, N3f1>(attestations2, &Sequential)
447 .unwrap();
448
449 let leader1a = elector.elect(round1, Some(&cert1));
451 let leader1b = elector.elect(round1, Some(&cert1));
452 assert_eq!(leader1a, leader1b);
453
454 let leader2 = elector.elect(round1, Some(&cert2));
460 assert_ne!(leader1a, leader2);
461 }
462
463 #[test]
464 #[should_panic(expected = "no participants")]
465 fn random_build_panics_on_empty_participants() {
466 let participants: Set<commonware_cryptography::ed25519::PublicKey> = Set::default();
467 let _: RandomElector<ThresholdScheme> = Random.build(&participants);
468 }
469
470 #[test]
471 #[should_panic]
472 fn random_panics_on_none_certificate_after_view_1() {
473 let mut rng = test_rng();
474 let Fixture { participants, .. } =
475 bls12381_threshold::fixture::<MinPk, _>(&mut rng, NAMESPACE, 5);
476 let participants = Set::try_from_iter(participants).unwrap();
477 let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
478
479 let round = Round::new(Epoch::new(1), View::new(2));
481 elector.elect(round, None);
482 }
483
484 mod conformance {
485 use super::*;
486 use commonware_codec::{Encode, Write};
487 use commonware_conformance::Conformance;
488 use commonware_cryptography::Sha256;
489 use rand::{Rng, SeedableRng};
490 use rand_chacha::ChaCha8Rng;
491
492 struct RoundRobinShuffleConformance;
498
499 impl Conformance for RoundRobinShuffleConformance {
500 async fn commit(seed: u64) -> Vec<u8> {
501 let mut rng = ChaCha8Rng::seed_from_u64(seed);
502
503 let n = rng.gen_range(1..=100);
505 let Fixture { participants, .. } = ed25519::fixture(&mut rng, NAMESPACE, n);
506 let participants = Set::try_from_iter(participants).unwrap();
507
508 let shuffle_seed: [u8; 32] = rng.gen();
510
511 let elector: RoundRobinElector<ed25519::Scheme> =
513 RoundRobin::<Sha256>::shuffled(&shuffle_seed).build(&participants);
514
515 elector.permutation.encode().to_vec()
517 }
518 }
519
520 struct RandomSelectLeaderConformance;
526
527 impl Conformance for RandomSelectLeaderConformance {
528 async fn commit(seed: u64) -> Vec<u8> {
529 let mut rng = ChaCha8Rng::seed_from_u64(seed);
530
531 let n = rng.gen_range(4..=10);
533 let Fixture {
534 participants,
535 schemes,
536 ..
537 } = bls12381_threshold::fixture::<MinPk, _>(&mut rng, NAMESPACE, n);
538 let participants = Set::try_from_iter(participants).unwrap();
539 let elector: RandomElector<ThresholdScheme> = Random.build(&participants);
540 let quorum = N3f1::quorum_from_slice(&schemes) as usize;
541
542 let epoch = rng.gen_range(0..1000);
544 let view = rng.gen_range(2..=101);
545
546 let round = Round::new(Epoch::new(epoch), View::new(view));
547
548 let attestations: Vec<_> = schemes
550 .iter()
551 .take(quorum)
552 .map(|s| s.sign::<Sha256Digest>(Subject::Nullify { round }).unwrap())
553 .collect();
554 let cert = schemes[0]
555 .assemble::<_, N3f1>(attestations, &Sequential)
556 .unwrap();
557
558 let leader = elector.elect(round, Some(&cert));
560
561 let round_v1 = Round::new(Epoch::new(epoch), View::new(1));
563 let leader_v1 = elector.elect(round_v1, None);
564
565 let mut result = leader.encode_mut();
567 leader_v1.write(&mut result);
568 result.to_vec()
569 }
570 }
571
572 commonware_conformance::conformance_tests! {
573 RoundRobinShuffleConformance => 512,
574 RandomSelectLeaderConformance => 512,
575 }
576 }
577}