1use core::{marker::PhantomData, ops::Deref, fmt};
2use std::{
3 io::{self, Read, Write},
4 collections::HashMap,
5};
6
7use rand_core::{RngCore, CryptoRng};
8
9use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
10
11use transcript::{Transcript, RecommendedTranscript};
12
13use ciphersuite::{
14 group::{
15 ff::{Field, PrimeField},
16 Group, GroupEncoding,
17 },
18 Ciphersuite,
19};
20use multiexp::{multiexp_vartime, BatchVerifier};
21
22use schnorr::SchnorrSignature;
23
24use crate::{
25 Participant, DkgError, ThresholdParams, ThresholdCore, validate_map,
26 encryption::{
27 ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption, EncryptionKeyProof,
28 DecryptionError,
29 },
30};
31
32type FrostError<C> = DkgError<EncryptionKeyProof<C>>;
33
34#[allow(non_snake_case)]
35fn challenge<C: Ciphersuite>(context: &str, l: Participant, R: &[u8], Am: &[u8]) -> C::F {
36 let mut transcript = RecommendedTranscript::new(b"DKG FROST v0.2");
37 transcript.domain_separate(b"schnorr_proof_of_knowledge");
38 transcript.append_message(b"context", context.as_bytes());
39 transcript.append_message(b"participant", l.to_bytes());
40 transcript.append_message(b"nonce", R);
41 transcript.append_message(b"commitments", Am);
42 C::hash_to_F(b"DKG-FROST-proof_of_knowledge-0", &transcript.challenge(b"schnorr"))
43}
44
45#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
52pub struct Commitments<C: Ciphersuite> {
53 commitments: Vec<C::G>,
54 cached_msg: Vec<u8>,
55 sig: SchnorrSignature<C>,
56}
57
58impl<C: Ciphersuite> ReadWrite for Commitments<C> {
59 fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
60 let mut commitments = Vec::with_capacity(params.t().into());
61 let mut cached_msg = vec![];
62
63 #[allow(non_snake_case)]
64 let mut read_G = || -> io::Result<C::G> {
65 let mut buf = <C::G as GroupEncoding>::Repr::default();
66 reader.read_exact(buf.as_mut())?;
67 let point = C::read_G(&mut buf.as_ref())?;
68 cached_msg.extend(buf.as_ref());
69 Ok(point)
70 };
71
72 for _ in 0 .. params.t() {
73 commitments.push(read_G()?);
74 }
75
76 Ok(Commitments { commitments, cached_msg, sig: SchnorrSignature::read(reader)? })
77 }
78
79 fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
80 writer.write_all(&self.cached_msg)?;
81 self.sig.write(writer)
82 }
83}
84
85#[derive(Debug, Zeroize)]
87pub struct KeyGenMachine<C: Ciphersuite> {
88 params: ThresholdParams,
89 context: String,
90 _curve: PhantomData<C>,
91}
92
93impl<C: Ciphersuite> KeyGenMachine<C> {
94 pub fn new(params: ThresholdParams, context: String) -> KeyGenMachine<C> {
98 KeyGenMachine { params, context, _curve: PhantomData }
99 }
100
101 pub fn generate_coefficients<R: RngCore + CryptoRng>(
106 self,
107 rng: &mut R,
108 ) -> (SecretShareMachine<C>, EncryptionKeyMessage<C, Commitments<C>>) {
109 let t = usize::from(self.params.t);
110 let mut coefficients = Vec::with_capacity(t);
111 let mut commitments = Vec::with_capacity(t);
112 let mut cached_msg = vec![];
113
114 for i in 0 .. t {
115 coefficients.push(Zeroizing::new(C::random_nonzero_F(&mut *rng)));
117 commitments.push(C::generator() * coefficients[i].deref());
119 cached_msg.extend(commitments[i].to_bytes().as_ref());
120 }
121
122 let r = Zeroizing::new(C::random_nonzero_F(rng));
124 let nonce = C::generator() * r.deref();
125 let sig = SchnorrSignature::<C>::sign(
126 &coefficients[0],
127 r,
132 challenge::<C>(&self.context, self.params.i(), nonce.to_bytes().as_ref(), &cached_msg),
133 );
134
135 let encryption = Encryption::new(self.context.clone(), Some(self.params.i), rng);
137
138 let msg =
140 encryption.registration(Commitments { commitments: commitments.clone(), cached_msg, sig });
141 (
142 SecretShareMachine {
143 params: self.params,
144 context: self.context,
145 coefficients,
146 our_commitments: commitments,
147 encryption,
148 },
149 msg,
150 )
151 }
152}
153
154fn polynomial<F: PrimeField + Zeroize>(
155 coefficients: &[Zeroizing<F>],
156 l: Participant,
157) -> Zeroizing<F> {
158 let l = F::from(u64::from(u16::from(l)));
159 assert!(l != F::ZERO, "zero participant passed to polynomial");
161 let mut share = Zeroizing::new(F::ZERO);
162 for (idx, coefficient) in coefficients.iter().rev().enumerate() {
163 *share += coefficient.deref();
164 if idx != (coefficients.len() - 1) {
165 *share *= l;
166 }
167 }
168 share
169}
170
171#[derive(Clone, PartialEq, Eq)]
180pub struct SecretShare<F: PrimeField>(F::Repr);
181impl<F: PrimeField> AsRef<[u8]> for SecretShare<F> {
182 fn as_ref(&self) -> &[u8] {
183 self.0.as_ref()
184 }
185}
186impl<F: PrimeField> AsMut<[u8]> for SecretShare<F> {
187 fn as_mut(&mut self) -> &mut [u8] {
188 self.0.as_mut()
189 }
190}
191impl<F: PrimeField> fmt::Debug for SecretShare<F> {
192 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
193 fmt.debug_struct("SecretShare").finish_non_exhaustive()
194 }
195}
196impl<F: PrimeField> Zeroize for SecretShare<F> {
197 fn zeroize(&mut self) {
198 self.0.as_mut().zeroize()
199 }
200}
201impl<F: PrimeField> Drop for SecretShare<F> {
206 fn drop(&mut self) {
207 self.zeroize();
208 }
209}
210impl<F: PrimeField> ZeroizeOnDrop for SecretShare<F> {}
211
212impl<F: PrimeField> ReadWrite for SecretShare<F> {
213 fn read<R: Read>(reader: &mut R, _: ThresholdParams) -> io::Result<Self> {
214 let mut repr = F::Repr::default();
215 reader.read_exact(repr.as_mut())?;
216 Ok(SecretShare(repr))
217 }
218
219 fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
220 writer.write_all(self.0.as_ref())
221 }
222}
223
224#[derive(Zeroize)]
226pub struct SecretShareMachine<C: Ciphersuite> {
227 params: ThresholdParams,
228 context: String,
229 coefficients: Vec<Zeroizing<C::F>>,
230 our_commitments: Vec<C::G>,
231 encryption: Encryption<C>,
232}
233
234impl<C: Ciphersuite> fmt::Debug for SecretShareMachine<C> {
235 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
236 fmt
237 .debug_struct("SecretShareMachine")
238 .field("params", &self.params)
239 .field("context", &self.context)
240 .field("our_commitments", &self.our_commitments)
241 .field("encryption", &self.encryption)
242 .finish_non_exhaustive()
243 }
244}
245
246impl<C: Ciphersuite> SecretShareMachine<C> {
247 #[allow(clippy::type_complexity)]
249 fn verify_r1<R: RngCore + CryptoRng>(
250 &mut self,
251 rng: &mut R,
252 mut commitment_msgs: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
253 ) -> Result<HashMap<Participant, Vec<C::G>>, FrostError<C>> {
254 validate_map(
255 &commitment_msgs,
256 &(1 ..= self.params.n()).map(Participant).collect::<Vec<_>>(),
257 self.params.i(),
258 )?;
259
260 let mut batch = BatchVerifier::<Participant, C::G>::new(commitment_msgs.len());
261 let mut commitments = HashMap::new();
262 for l in (1 ..= self.params.n()).map(Participant) {
263 let Some(msg) = commitment_msgs.remove(&l) else { continue };
264 let mut msg = self.encryption.register(l, msg);
265
266 if msg.commitments.len() != self.params.t().into() {
267 Err(FrostError::InvalidCommitments(l))?;
268 }
269
270 msg.sig.batch_verify(
273 rng,
274 &mut batch,
275 l,
276 msg.commitments[0],
277 challenge::<C>(&self.context, l, msg.sig.R.to_bytes().as_ref(), &msg.cached_msg),
278 );
279
280 commitments.insert(l, msg.commitments.drain(..).collect::<Vec<_>>());
281 }
282
283 batch.verify_vartime_with_vartime_blame().map_err(FrostError::InvalidCommitments)?;
284
285 commitments.insert(self.params.i, self.our_commitments.drain(..).collect());
286 Ok(commitments)
287 }
288
289 #[allow(clippy::type_complexity)]
296 pub fn generate_secret_shares<R: RngCore + CryptoRng>(
297 mut self,
298 rng: &mut R,
299 commitments: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
300 ) -> Result<
301 (KeyMachine<C>, HashMap<Participant, EncryptedMessage<C, SecretShare<C::F>>>),
302 FrostError<C>,
303 > {
304 let commitments = self.verify_r1(&mut *rng, commitments)?;
305
306 let mut res = HashMap::new();
308 for l in (1 ..= self.params.n()).map(Participant) {
309 if l == self.params.i() {
312 continue;
313 }
314
315 let mut share = polynomial(&self.coefficients, l);
316 let share_bytes = Zeroizing::new(SecretShare::<C::F>(share.to_repr()));
317 share.zeroize();
318 res.insert(l, self.encryption.encrypt(rng, l, share_bytes));
319 }
320
321 let share = polynomial(&self.coefficients, self.params.i());
323 self.coefficients.zeroize();
324
325 Ok((
326 KeyMachine { params: self.params, secret: share, commitments, encryption: self.encryption },
327 res,
328 ))
329 }
330}
331
332pub struct KeyMachine<C: Ciphersuite> {
338 params: ThresholdParams,
339 secret: Zeroizing<C::F>,
340 commitments: HashMap<Participant, Vec<C::G>>,
341 encryption: Encryption<C>,
342}
343
344impl<C: Ciphersuite> fmt::Debug for KeyMachine<C> {
345 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
346 fmt
347 .debug_struct("KeyMachine")
348 .field("params", &self.params)
349 .field("commitments", &self.commitments)
350 .field("encryption", &self.encryption)
351 .finish_non_exhaustive()
352 }
353}
354
355impl<C: Ciphersuite> Zeroize for KeyMachine<C> {
356 fn zeroize(&mut self) {
357 self.params.zeroize();
358 self.secret.zeroize();
359 for commitments in self.commitments.values_mut() {
360 commitments.zeroize();
361 }
362 self.encryption.zeroize();
363 }
364}
365
366fn exponential<C: Ciphersuite>(i: Participant, values: &[C::G]) -> Vec<(C::F, C::G)> {
370 let i = C::F::from(u16::from(i).into());
371 let mut res = Vec::with_capacity(values.len());
372 (0 .. values.len()).fold(C::F::ONE, |exp, l| {
373 res.push((exp, values[l]));
374 exp * i
375 });
376 res
377}
378
379fn share_verification_statements<C: Ciphersuite>(
380 target: Participant,
381 commitments: &[C::G],
382 mut share: Zeroizing<C::F>,
383) -> Vec<(C::F, C::G)> {
384 let mut values = exponential::<C>(target, commitments);
389
390 let neg_share_pub = C::generator() * -*share;
394 share.zeroize();
395 values.push((C::F::ONE, neg_share_pub));
396
397 values
398}
399
400#[derive(Clone, Copy, Hash, Debug, Zeroize)]
401enum BatchId {
402 Decryption(Participant),
403 Share(Participant),
404}
405
406impl<C: Ciphersuite> KeyMachine<C> {
407 pub fn calculate_share<R: RngCore + CryptoRng>(
413 mut self,
414 rng: &mut R,
415 mut shares: HashMap<Participant, EncryptedMessage<C, SecretShare<C::F>>>,
416 ) -> Result<BlameMachine<C>, FrostError<C>> {
417 validate_map(
418 &shares,
419 &(1 ..= self.params.n()).map(Participant).collect::<Vec<_>>(),
420 self.params.i(),
421 )?;
422
423 let mut batch = BatchVerifier::new(shares.len());
424 let mut blames = HashMap::new();
425 for (l, share_bytes) in shares.drain() {
426 let (mut share_bytes, blame) =
427 self.encryption.decrypt(rng, &mut batch, BatchId::Decryption(l), l, share_bytes);
428 let share =
429 Zeroizing::new(Option::<C::F>::from(C::F::from_repr(share_bytes.0)).ok_or_else(|| {
430 FrostError::InvalidShare { participant: l, blame: Some(blame.clone()) }
431 })?);
432 share_bytes.zeroize();
433 *self.secret += share.deref();
434
435 blames.insert(l, blame);
436 batch.queue(
437 rng,
438 BatchId::Share(l),
439 share_verification_statements::<C>(self.params.i(), &self.commitments[&l], share),
440 );
441 }
442 batch.verify_with_vartime_blame().map_err(|id| {
443 let (l, blame) = match id {
444 BatchId::Decryption(l) => (l, None),
445 BatchId::Share(l) => (l, Some(blames.remove(&l).unwrap())),
446 };
447 FrostError::InvalidShare { participant: l, blame }
448 })?;
449
450 let mut stripes = Vec::with_capacity(usize::from(self.params.t()));
455 for t in 0 .. usize::from(self.params.t()) {
456 stripes.push(self.commitments.values().map(|commitments| commitments[t]).sum());
457 }
458
459 let mut verification_shares = HashMap::new();
461 for i in (1 ..= self.params.n()).map(Participant) {
462 verification_shares.insert(
463 i,
464 if i == self.params.i() {
465 C::generator() * self.secret.deref()
466 } else {
467 multiexp_vartime(&exponential::<C>(i, &stripes))
468 },
469 );
470 }
471
472 let KeyMachine { commitments, encryption, params, secret } = self;
473 Ok(BlameMachine {
474 commitments,
475 encryption,
476 result: Some(ThresholdCore {
477 params,
478 secret_share: secret,
479 group_key: stripes[0],
480 verification_shares,
481 }),
482 })
483 }
484}
485
486pub struct BlameMachine<C: Ciphersuite> {
488 commitments: HashMap<Participant, Vec<C::G>>,
489 encryption: Encryption<C>,
490 result: Option<ThresholdCore<C>>,
491}
492
493impl<C: Ciphersuite> fmt::Debug for BlameMachine<C> {
494 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
495 fmt
496 .debug_struct("BlameMachine")
497 .field("commitments", &self.commitments)
498 .field("encryption", &self.encryption)
499 .finish_non_exhaustive()
500 }
501}
502
503impl<C: Ciphersuite> Zeroize for BlameMachine<C> {
504 fn zeroize(&mut self) {
505 for commitments in self.commitments.values_mut() {
506 commitments.zeroize();
507 }
508 self.encryption.zeroize();
509 self.result.zeroize();
510 }
511}
512
513impl<C: Ciphersuite> BlameMachine<C> {
514 pub fn complete(self) -> ThresholdCore<C> {
524 self.result.unwrap()
525 }
526
527 fn blame_internal(
528 &self,
529 sender: Participant,
530 recipient: Participant,
531 msg: EncryptedMessage<C, SecretShare<C::F>>,
532 proof: Option<EncryptionKeyProof<C>>,
533 ) -> Participant {
534 let share_bytes = match self.encryption.decrypt_with_proof(sender, recipient, msg, proof) {
535 Ok(share_bytes) => share_bytes,
536 Err(DecryptionError::InvalidSignature) => return sender,
538 Err(DecryptionError::InvalidProof) => return recipient,
540 };
541
542 let Some(share) = Option::<C::F>::from(C::F::from_repr(share_bytes.0)) else {
543 return sender;
545 };
546
547 if !bool::from(
549 multiexp_vartime(&share_verification_statements::<C>(
550 recipient,
551 &self.commitments[&sender],
552 Zeroizing::new(share),
553 ))
554 .is_identity(),
555 ) {
556 return sender;
557 }
558
559 recipient
561 }
562
563 pub fn blame(
576 self,
577 sender: Participant,
578 recipient: Participant,
579 msg: EncryptedMessage<C, SecretShare<C::F>>,
580 proof: Option<EncryptionKeyProof<C>>,
581 ) -> (AdditionalBlameMachine<C>, Participant) {
582 let faulty = self.blame_internal(sender, recipient, msg, proof);
583 (AdditionalBlameMachine(self), faulty)
584 }
585}
586
587#[derive(Debug, Zeroize)]
589pub struct AdditionalBlameMachine<C: Ciphersuite>(BlameMachine<C>);
590impl<C: Ciphersuite> AdditionalBlameMachine<C> {
591 pub fn new<R: RngCore + CryptoRng>(
602 rng: &mut R,
603 context: String,
604 n: u16,
605 mut commitment_msgs: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
606 ) -> Result<Self, FrostError<C>> {
607 let mut commitments = HashMap::new();
608 let mut encryption = Encryption::new(context, None, rng);
609 for i in 1 ..= n {
610 let i = Participant::new(i).unwrap();
611 let Some(msg) = commitment_msgs.remove(&i) else { Err(DkgError::MissingParticipant(i))? };
612 commitments.insert(i, encryption.register(i, msg).commitments);
613 }
614 Ok(AdditionalBlameMachine(BlameMachine { commitments, encryption, result: None }))
615 }
616
617 pub fn blame(
628 &self,
629 sender: Participant,
630 recipient: Participant,
631 msg: EncryptedMessage<C, SecretShare<C::F>>,
632 proof: Option<EncryptionKeyProof<C>>,
633 ) -> Participant {
634 self.0.blame_internal(sender, recipient, msg, proof)
635 }
636}