1#![cfg_attr(docsrs, feature(doc_cfg))]
10#![warn(
11 missing_docs,
12 missing_debug_implementations,
13 missing_copy_implementations,
14 trivial_casts,
15 trivial_numeric_casts,
16 unused,
17 clippy::mod_module_files
18)]
19#![deny(clippy::unwrap_used)]
20
21mod data;
22mod error;
23mod parameters;
24mod participant;
25mod traits;
26
27pub use data::*;
28pub use error::*;
29pub use parameters::*;
30pub use participant::*;
31pub use traits::*;
32
33pub use elliptic_curve;
34pub use elliptic_curve_tools;
35pub use rand_core;
36pub use vsss_rs;
37
38use elliptic_curve::{
39 Field, Group, PrimeField,
40 group::GroupEncoding,
41 subtle::{Choice, ConditionallySelectable},
42};
43use elliptic_curve_tools::SumOfProducts;
44use std::collections::BTreeSet;
45use vsss_rs::{IdentifierPrimeField, ParticipantIdGeneratorCollection, ShareVerifierGroup};
46
47pub fn publicly_verify_dkg_results<G>(
49 round1_data: &[Round1Data<G>],
50 parameters: &Parameters<G>,
51 public_key: G,
52) -> DkgResult<()>
53where
54 G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
55 G::Scalar: ScalarHash,
56{
57 if round1_data.len() < parameters.threshold {
60 return Err(Error::Pvss(format!(
61 "Not enough round 1 records. Expected at least {}, found {}",
62 parameters.threshold,
63 round1_data.len()
64 )));
65 }
66 if round1_data.len() > parameters.limit {
67 return Err(Error::Pvss(format!(
68 "Too many round 1 records. Expected at most {}, found {}",
69 parameters.limit,
70 round1_data.len()
71 )));
72 }
73
74 let all_participant_ids: Vec<IdentifierPrimeField<G::Scalar>> =
75 ParticipantIdGeneratorCollection::from(¶meters.participant_number_generators)
76 .iter()
77 .take(parameters.limit)
78 .collect();
79 if all_participant_ids.len() != parameters.limit {
80 return Err(Error::Pvss(format!(
81 "Participant ID generators produced {} identifiers, expected {}",
82 all_participant_ids.len(),
83 parameters.limit
84 )));
85 }
86
87 let mut computed_public_key = G::default();
88 let mut all_refresh = true;
89 let mut sender_ordinals = BTreeSet::new();
90
91 for (i, round1_data) in round1_data.iter().enumerate() {
92 if !sender_ordinals.insert(round1_data.sender_ordinal) {
93 return Err(Error::Pvss(format!(
94 "Data at {} duplicates sender ordinal {}",
95 i + 1,
96 round1_data.sender_ordinal
97 )));
98 }
99 let Some(id) = all_participant_ids.get(round1_data.sender_ordinal) else {
100 return Err(Error::Pvss(format!(
101 "Data at {} does not exist in the set of participants",
102 i + 1
103 )));
104 };
105 if *id != round1_data.sender_id {
106 return Err(Error::Pvss(format!(
107 "Data at {} does not match the expected sender ID",
108 i + 1
109 )));
110 }
111 if id.is_zero().into() {
112 return Err(Error::Pvss(format!(
113 "Data at {} contains an ID that is zero",
114 i + 1
115 )));
116 }
117 if round1_data.feldman_commitments.is_empty() {
118 return Err(Error::Pvss(format!(
119 "Data at {} has no Feldman commitments",
120 i + 1
121 )));
122 }
123 if round1_data.feldman_commitments.len() != parameters.threshold {
124 return Err(Error::Pvss(format!(
125 "Data at {} has commitments that do not match the expected threshold. Expected {}, found {}",
126 i + 1,
127 parameters.threshold,
128 round1_data.feldman_commitments.len()
129 )));
130 }
131 if round1_data.feldman_commitments[1..]
132 .iter()
133 .fold(Choice::from(0u8), |acc, c| acc | c.is_identity())
134 .into()
135 {
136 return Err(Error::Pvss(format!(
137 "Data at {} has a Feldman commitment that is the identity element, which is not allowed",
138 i + 1
139 )));
140 }
141
142 let feldman_valid = match round1_data.sender_type {
143 ParticipantType::Secret => {
144 SecretParticipantImpl::check_feldman_verifier(*round1_data.feldman_commitments[0])
145 && round1_data.feldman_commitments[0].0 == round1_data.verifying_share
146 }
147 ParticipantType::Refresh => {
148 RefreshParticipantImpl::check_feldman_verifier(*round1_data.feldman_commitments[0])
149 && round1_data.feldman_commitments[0].0 != round1_data.verifying_share
150 }
151 };
152
153 if !feldman_valid {
154 return Err(Error::Pvss(format!(
155 "Data at {} has an invalid Feldman commitment for its participant type",
156 i + 1
157 )));
158 }
159
160 verify_signature(
161 SchnorrContext {
162 ordinal: round1_data.sender_ordinal,
163 id: &round1_data.sender_id,
164 participant_type: &round1_data.sender_type,
165 threshold: parameters.threshold,
166 limit: parameters.limit,
167 message_generator: ¶meters.message_generator,
168 feldman_verifiers: &round1_data.feldman_commitments,
169 verifying_share: &round1_data.verifying_share,
170 all_participant_ids: &all_participant_ids,
171 },
172 &round1_data.signature,
173 )
174 .map_err(|_e| Error::Pvss(format!("Data at {} failed signature verification", i + 1)))?;
175
176 all_refresh &= matches!(round1_data.sender_type, ParticipantType::Refresh);
177 computed_public_key += round1_data.feldman_commitments[0].0;
178 }
179
180 let public_key_identity = bool::from(computed_public_key.is_identity());
181 if all_refresh && !public_key_identity || !all_refresh && public_key_identity {
182 return Err(Error::Pvss(
183 "The computed public key is not valid for the given participants".to_string(),
184 ));
185 }
186
187 if computed_public_key != public_key {
188 return Err(Error::Pvss(format!(
189 "The public keys do not match: expected {}, computed {}",
190 hex::encode(public_key.to_bytes()),
191 hex::encode(computed_public_key.to_bytes())
192 )));
193 }
194
195 Ok(())
196}
197
198struct SchnorrContext<'a, G>
199where
200 G: Group + GroupEncoding + Default,
201{
202 ordinal: usize,
203 id: &'a IdentifierPrimeField<G::Scalar>,
204 participant_type: &'a ParticipantType,
205 threshold: usize,
206 limit: usize,
207 message_generator: &'a G,
208 feldman_verifiers: &'a [ShareVerifierGroup<G>],
209 verifying_share: &'a G,
210 all_participant_ids: &'a [IdentifierPrimeField<G::Scalar>],
211}
212
213pub(crate) fn verify_signature<G>(
214 context: SchnorrContext<'_, G>,
215 signature: &Signature<G>,
216) -> DkgResult<()>
217where
218 G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
219 G::Scalar: ScalarHash,
220{
221 let bytes = bytes_for_schnorr(&context, &signature.r);
222 let challenge = G::Scalar::hash_to_scalar(&bytes);
223
224 let computed_r =
225 *context.message_generator * signature.s - *context.verifying_share * challenge;
226 if signature.r != computed_r {
227 return Err(Error::Round(format!(
228 "Round {}: received an invalid round 1 signature proof from ordinal '{}', ID '{:?}'",
229 Round::One,
230 context.ordinal,
231 context.id,
232 )));
233 }
234 Ok(())
235}
236
237pub(crate) fn bytes_for_schnorr<G>(context: &SchnorrContext<'_, G>, r_i: &G) -> Vec<u8>
238where
239 G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
240 G::Scalar: ScalarHash,
241{
242 let mut bytes = Vec::with_capacity(512);
243 bytes.extend_from_slice(context.id.0.to_repr().as_ref());
245 bytes.extend_from_slice(&(context.ordinal as u16).to_be_bytes());
247 bytes.extend_from_slice(&u16::from(*context.participant_type).to_be_bytes());
248 bytes.extend_from_slice(&(context.threshold as u16).to_be_bytes());
249 bytes.extend_from_slice(&(context.limit as u16).to_be_bytes());
250 bytes.extend_from_slice(context.message_generator.to_bytes().as_ref());
251 for id in context.all_participant_ids {
252 bytes.extend_from_slice(id.0.to_repr().as_ref());
253 }
254 bytes.extend_from_slice(r_i.to_bytes().as_ref());
256 bytes.extend_from_slice(context.verifying_share.to_bytes().as_ref());
258 for vf in context.feldman_verifiers {
260 bytes.extend_from_slice(vf.0.to_bytes().as_ref());
261 }
262 bytes
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use elliptic_curve::{Field, group::GroupEncoding, subtle::ConditionallySelectable};
269 use elliptic_curve_tools::SumOfProducts;
270 use rand_core::SeedableRng;
271 use serde::{Deserialize, Serialize};
272 use std::num::NonZeroUsize;
273 use vsss_rs::{
274 DefaultShare, IdentifierPrimeField, ParticipantIdGenerator, ReadableShareSet,
275 ValuePrimeField, shamir,
276 };
277
278 #[test]
279 fn works() {
280 const THRESHOLD: usize = 2;
281 const LIMIT: usize = 3;
282
283 let threshold = NonZeroUsize::new(THRESHOLD).expect("threshold is non-zero");
284 let limit = NonZeroUsize::new(LIMIT).expect("limit is non-zero");
285
286 let parameters =
287 Parameters::<k256::ProjectivePoint>::new(threshold, limit).expect("valid parameters");
288
289 let mut participants = (1..=3)
290 .map(|id| {
291 let id = IdentifierPrimeField(k256::Scalar::from(id as u64));
292 SecretParticipant::<k256::ProjectivePoint>::new_secret(id, ¶meters)
293 .expect("create secret participant")
294 })
295 .collect::<Vec<_>>();
296
297 for _ in [Round::One, Round::Two, Round::Three] {
298 let generators = next_round(&mut participants);
299 receive(&mut participants, generators);
300 }
301
302 let shares = participants
303 .iter()
304 .map(|p| p.secret_share().expect("participant has a secret share"))
305 .collect::<Vec<_>>();
306
307 let res = shares.combine();
308 assert!(res.is_ok());
309 let secret = res.expect("combine shares");
310
311 let expected_pk = k256::ProjectivePoint::GENERATOR * *secret;
312
313 assert_eq!(
314 participants[1]
315 .public_key()
316 .expect("participant has public key"),
317 expected_pk
318 );
319
320 let participant: Box<dyn AnyParticipant<k256::ProjectivePoint>> =
321 Box::new(participants.pop().expect("participant exists"));
322 let output = participant.into_output().expect("completed DKG output");
323 assert_eq!(output.public_key(), expected_pk);
324 assert_eq!(output.participant_ids().len(), LIMIT);
325 assert_eq!(output.feldman_verifiers().len(), THRESHOLD);
326 assert_eq!(*output.secret_share().identifier, k256::Scalar::from(3u64));
327 assert!(!format!("{output:?}").contains("secret_share"));
328 }
329
330 #[test]
331 fn public_verification_rejects_invalid_record_sets() {
332 const THRESHOLD: usize = 2;
333 const LIMIT: usize = 3;
334
335 let parameters = Parameters::<k256::ProjectivePoint>::new(
336 NonZeroUsize::new(THRESHOLD).expect("threshold is non-zero"),
337 NonZeroUsize::new(LIMIT).expect("limit is non-zero"),
338 )
339 .expect("valid parameters");
340 let mut participants = (1..=LIMIT)
341 .map(|id| {
342 SecretParticipant::<k256::ProjectivePoint>::new_secret(
343 IdentifierPrimeField(k256::Scalar::from(id as u64)),
344 ¶meters,
345 )
346 .expect("create secret participant")
347 })
348 .collect::<Vec<_>>();
349
350 for _ in [Round::One, Round::Two, Round::Three] {
351 let generators = next_round(&mut participants);
352 receive(&mut participants, generators);
353 }
354
355 let round1_data = participants[0]
356 .received_round1_data()
357 .iter()
358 .flatten()
359 .cloned()
360 .collect::<Vec<_>>();
361 let public_key = participants[0]
362 .public_key()
363 .expect("participant has public key");
364 assert!(publicly_verify_dkg_results(&round1_data, ¶meters, public_key).is_ok());
365
366 let threshold_records = &round1_data[..THRESHOLD];
367 let threshold_public_key = threshold_records
368 .iter()
369 .map(|data| data.feldman_commitments[0].0)
370 .sum();
371 assert!(
372 publicly_verify_dkg_results(threshold_records, ¶meters, threshold_public_key)
373 .is_ok()
374 );
375
376 let too_few =
377 publicly_verify_dkg_results(&round1_data[..THRESHOLD - 1], ¶meters, public_key);
378 assert!(matches!(too_few, Err(Error::Pvss(message)) if message.contains("Not enough")));
379
380 let duplicate_records = vec![round1_data[0].clone(), round1_data[0].clone()];
381 let duplicate = publicly_verify_dkg_results(&duplicate_records, ¶meters, public_key);
382 assert!(
383 matches!(duplicate, Err(Error::Pvss(message)) if message.contains("duplicates sender ordinal"))
384 );
385
386 let mut empty_commitments = round1_data[..THRESHOLD].to_vec();
387 empty_commitments[0].feldman_commitments.clear();
388 let empty =
389 publicly_verify_dkg_results(&empty_commitments, ¶meters, threshold_public_key);
390 assert!(
391 matches!(empty, Err(Error::Pvss(message)) if message.contains("no Feldman commitments"))
392 );
393
394 let mut too_many_records = round1_data.clone();
395 too_many_records.push(round1_data[0].clone());
396 let too_many = publicly_verify_dkg_results(&too_many_records, ¶meters, public_key);
397 assert!(matches!(too_many, Err(Error::Pvss(message)) if message.contains("Too many")));
398 }
399
400 #[test]
401 fn advance_produces_opaque_transport_messages() {
402 const THRESHOLD: usize = 2;
403 const LIMIT: usize = 3;
404
405 let parameters = Parameters::<k256::ProjectivePoint>::new(
406 NonZeroUsize::new(THRESHOLD).expect("threshold is non-zero"),
407 NonZeroUsize::new(LIMIT).expect("limit is non-zero"),
408 )
409 .expect("valid parameters");
410 let mut participants = (1..=LIMIT)
411 .map(|id| {
412 SecretParticipant::<k256::ProjectivePoint>::new_secret(
413 IdentifierPrimeField(k256::Scalar::from(id as u64)),
414 ¶meters,
415 )
416 .expect("create secret participant")
417 })
418 .collect::<Vec<_>>();
419
420 for round in [Round::One, Round::Two] {
421 let batches = participants
422 .iter_mut()
423 .map(
424 |participant| match participant.advance().expect("advance participant") {
425 AdvanceResult::Messages(messages) => messages,
426 AdvanceResult::Complete => panic!("protocol completed too early"),
427 },
428 )
429 .collect::<Vec<_>>();
430
431 for batch in batches {
432 match round {
433 Round::One => {
434 assert_eq!(batch.len(), 1);
435 assert!(!batch.is_empty());
436 assert!(matches!(
437 batch.messages()[0].destination(),
438 MessageDestination::Broadcast
439 ));
440 }
441 Round::Two => {
442 assert_eq!(batch.len(), LIMIT - 1);
443 assert!((&batch).into_iter().all(|message| matches!(
444 message.destination(),
445 MessageDestination::Direct { .. }
446 )));
447 }
448 _ => unreachable!("only messaging rounds are tested here"),
449 }
450
451 for output in batch.into_per_recipient() {
452 let recipient = &mut participants[output.dst_ordinal];
453 assert_eq!(recipient.id(), output.dst_id);
454 recipient
455 .receive(output.data.as_bytes())
456 .expect("receive opaque protocol message");
457 }
458 }
459 }
460
461 for participant in &mut participants {
462 assert!(matches!(
463 participant.advance().expect("complete participant"),
464 AdvanceResult::Complete
465 ));
466 }
467 assert!(participants.iter().all(Participant::completed));
468 }
469
470 #[test]
471 fn recovery() {
472 type SecretShare =
473 DefaultShare<IdentifierPrimeField<k256::Scalar>, ValuePrimeField<k256::Scalar>>;
474 const THRESHOLD: usize = 2;
475 const LIMIT: usize = 3;
476
477 let threshold = NonZeroUsize::new(THRESHOLD).expect("threshold is non-zero");
478 let limit = NonZeroUsize::new(LIMIT).expect("limit is non-zero");
479 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
480
481 let original_secret = k256::Scalar::random(&mut rng);
482 let public_key = k256::ProjectivePoint::GENERATOR * original_secret;
483
484 let original_peer_ids = (1..=LIMIT)
485 .map(|_| IdentifierPrimeField(k256::Scalar::random(&mut rng)))
486 .collect::<Vec<_>>();
487 let original_peer_id_list = ParticipantIdGenerator::list(&original_peer_ids);
488 let original_shares = shamir::split_secret_with_participant_generators::<SecretShare>(
489 THRESHOLD,
490 LIMIT,
491 &IdentifierPrimeField(original_secret),
492 &mut rng,
493 &[original_peer_id_list],
494 )
495 .expect("split original secret");
496
497 let new_peer_ids = (1..=LIMIT)
498 .map(|_| IdentifierPrimeField(k256::Scalar::random(&mut rng)))
499 .collect::<Vec<_>>();
500
501 let parameters = Parameters::<k256::ProjectivePoint>::new(threshold, limit)
502 .expect("valid parameters")
503 .with_participant_number_generators(vec![ParticipantIdGenerator::list(&new_peer_ids)])
504 .expect("valid participant identifiers");
505 let mut participants = Vec::with_capacity(LIMIT);
506 for i in 0..LIMIT {
507 let participant = SecretParticipant::<k256::ProjectivePoint>::with_secret(
508 new_peer_ids[i],
509 &original_shares[i],
510 ¶meters,
511 &ReconstructionSet::new(&original_peer_ids).expect("valid reconstruction set"),
512 )
513 .expect("create participant from existing share");
514 participants.push(participant);
515 }
516
517 for _ in [Round::One, Round::Two, Round::Three] {
518 let generators = next_round(&mut participants);
519 receive(&mut participants, generators);
520 }
521
522 let shares = participants
523 .iter()
524 .map(|p| p.secret_share().expect("participant has a secret share"))
525 .collect::<Vec<_>>();
526
527 let res = shares.combine();
528 assert!(res.is_ok());
529 let secret = res.expect("combine shares");
530
531 assert_eq!(secret.0, original_secret);
532 assert_eq!(
533 participants[1]
534 .public_key()
535 .expect("participant has public key"),
536 public_key
537 );
538 }
539
540 #[test]
541 fn secret_participants_resume_from_checkpoints_between_rounds() {
542 let parameters = test_parameters();
543 let mut participants = (1u64..=3)
544 .map(|id| {
545 SecretParticipant::<k256::ProjectivePoint>::new_secret(
546 IdentifierPrimeField(k256::Scalar::from(id)),
547 ¶meters,
548 )
549 .expect("create secret participant")
550 })
551 .collect::<Vec<_>>();
552
553 run_with_checkpoints(&mut participants);
554
555 let public_key = participants[0]
556 .public_key()
557 .expect("participant has public key");
558 assert!(
559 participants
560 .iter()
561 .all(|participant| participant.public_key() == Some(public_key))
562 );
563 }
564
565 #[test]
566 fn refresh_participants_resume_from_checkpoints_between_rounds() {
567 let parameters = test_parameters();
568 let mut initial_participants = (1u64..=3)
569 .map(|id| {
570 SecretParticipant::<k256::ProjectivePoint>::new_secret(
571 IdentifierPrimeField(k256::Scalar::from(id)),
572 ¶meters,
573 )
574 .expect("create secret participant")
575 })
576 .collect::<Vec<_>>();
577 run_with_checkpoints(&mut initial_participants);
578
579 let mut participants = initial_participants
580 .iter()
581 .map(|participant| {
582 RefreshParticipant::<k256::ProjectivePoint>::new_refresh(
583 participant.id(),
584 Some(
585 &participant
586 .secret_share()
587 .expect("participant has a secret share"),
588 ),
589 ¶meters,
590 )
591 .expect("create refresh participant")
592 })
593 .collect::<Vec<_>>();
594
595 run_with_checkpoints(&mut participants);
596
597 let refreshed_shares = participants
598 .iter()
599 .map(|participant| {
600 participant
601 .secret_share()
602 .expect("participant has a refreshed secret share")
603 })
604 .collect::<Vec<_>>();
605 let refreshed_secret = refreshed_shares
606 .combine()
607 .expect("combine refreshed shares");
608 assert_eq!(refreshed_secret.0.is_zero().unwrap_u8(), 1);
609 assert!(participants.iter().all(|participant| {
610 participant
611 .public_key()
612 .is_some_and(|public_key| bool::from(public_key.is_identity()))
613 }));
614 }
615
616 fn test_parameters() -> Parameters<'static, k256::ProjectivePoint> {
617 Parameters::new(
618 NonZeroUsize::new(2).expect("threshold is non-zero"),
619 NonZeroUsize::new(3).expect("limit is non-zero"),
620 )
621 .expect("valid parameters")
622 }
623
624 fn run_with_checkpoints<I>(participants: &mut [Participant<I, k256::ProjectivePoint>])
625 where
626 I: ParticipantImpl<k256::ProjectivePoint> + Default + Serialize + for<'de> Deserialize<'de>,
627 {
628 for _ in [Round::One, Round::Two, Round::Three] {
629 checkpoint_participants(participants);
630
631 let round_generators = participants
632 .iter_mut()
633 .map(|participant| participant.run().expect("run participant round"))
634 .collect::<Vec<_>>();
635 for round_generator in round_generators {
636 for output in round_generator.iter().expect("serialize round output") {
637 participants[output.dst_ordinal]
638 .receive(output.data.as_bytes())
639 .expect("receive round output");
640 }
641 }
642
643 checkpoint_participants(participants);
644 }
645 }
646
647 fn checkpoint_participants<I>(participants: &mut [Participant<I, k256::ProjectivePoint>])
648 where
649 I: ParticipantImpl<k256::ProjectivePoint> + Default + Serialize + for<'de> Deserialize<'de>,
650 {
651 for participant in participants {
652 let encoded = postcard::to_stdvec(&*participant).expect("serialize participant state");
653 *participant = postcard::from_bytes(&encoded).expect("deserialize participant state");
654 }
655 }
656
657 fn next_round<G>(participants: &mut [SecretParticipant<G>]) -> Vec<RoundOutputGenerator<G>>
658 where
659 G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
660 G::Scalar: ScalarHash,
661 {
662 let mut round_generators = Vec::with_capacity(participants.len());
663 for participant in participants {
664 let generator = participant.run().expect("run participant round");
665 round_generators.push(generator);
666 }
667 round_generators
668 }
669
670 fn receive<G>(
671 participants: &mut [SecretParticipant<G>],
672 round_generators: Vec<RoundOutputGenerator<G>>,
673 ) where
674 G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
675 G::Scalar: ScalarHash,
676 {
677 for round_generator in &round_generators {
678 for ParticipantRoundOutput {
679 dst_ordinal: ordinal,
680 dst_id: id,
681 data,
682 ..
683 } in round_generator.iter().expect("serialize round output")
684 {
685 if let Some(participant) = participants.get_mut(ordinal) {
686 assert_eq!(participant.ordinal, ordinal);
687 assert_eq!(participant.id, id);
688 let res = participant.receive(data.as_slice());
689 assert!(res.is_ok());
690 }
691 }
692 }
693 }
694}