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