Skip to main content

frost_core/keys/
dkg.rs

1//! Distributed Key Generation functions and structures.
2//!
3//! The DKG module supports generating FROST key shares in a distributed manner,
4//! without a trusted dealer, via two rounds of communication between all
5//! participants (with one round requiring a broadcast channel, so totalling
6//! three rounds of communication if using echo broadcast).
7//!
8//! For a higher level tutorial on how to use it, refer to the [ZF FROST
9//! Book](https://frost.zfnd.org/tutorial/dkg.html).
10//!
11//! This implements FROST KeyGen from the original [FROST paper], specifically
12//! Figure 1. This protocol is a variant of [Pedersen's DKG] that additionally
13//! requires each participant to demonstrate knowledge of their secret by providing
14//! other participants with proof in zero knowledge, instantiated as a Schnorr signature,
15//! to protect against rogue-key attacks in the setting where `t ≥ n/2`.
16//!
17//! In Pedersen's DKG, each of the `n` participants executes [Feldman's
18//! Verifiable Secret Sharing (VSS)][Feldman's VSS] as the dealer in parallel,
19//! and derives their secret share as the sum of the shares received from each
20//! of the `n` VSS executions.
21//!
22//! As required for any multi-party protocol using Feldman's VSS, the key
23//! generation stage in FROST requires participants to maintain a consistent
24//! view of the public commitments to the secret polynomial coefficients. This
25//! DKG protocol requires participants to broadcast the commitment values
26//! honestly (e.g., participants do not provide different commitment values to a
27//! subset of participants) over a _[secure broadcast channel]_.
28//!
29//! For more details and an example, see the ciphersuite-specific crates, e.g.
30//! [`frost_ristretto255::keys::dkg`](../../../../frost_ristretto255/keys/dkg).
31//!
32//! [FROST paper]: https://eprint.iacr.org/2020/852.pdf
33//! [Pedersen's DKG]: https://link.springer.com/chapter/10.1007/3-540-46416-6_47
34//! [Feldman's VSS]: https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf
35//! [secure broadcast channel]: https://frost.zfnd.org/terminology.html#broadcast-channel
36
37use core::iter;
38
39use alloc::collections::BTreeMap;
40
41use rand_core::{CryptoRng, RngCore};
42
43use crate::{
44    Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
45    SigningKey, VerifyingKey,
46};
47
48#[cfg(feature = "serialization")]
49use crate::serialization::{Deserialize, Serialize};
50
51use super::{
52    evaluate_polynomial, generate_coefficients, generate_secret_polynomial,
53    validate_num_of_signers, KeyPackage, PublicKeyPackage, SecretShare, SigningShare,
54    VerifiableSecretSharingCommitment,
55};
56
57/// DKG Round 1 structures.
58pub mod round1 {
59    use alloc::vec::Vec;
60    use derive_getters::Getters;
61    use zeroize::{Zeroize, ZeroizeOnDrop};
62
63    use super::*;
64
65    use crate::serialization::SerializableScalar;
66    #[cfg(feature = "serialization")]
67    use crate::serialization::{Deserialize, Serialize};
68
69    /// The package that must be broadcast by each participant to all other participants
70    /// between the first and second parts of the DKG protocol (round 1).
71    #[derive(Clone, Debug, PartialEq, Eq, Getters)]
72    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73    #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
74    #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
75    pub struct Package<C: Ciphersuite> {
76        /// Serialization header
77        #[getter(skip)]
78        pub(crate) header: Header<C>,
79        /// The public commitment from the participant (C_i)
80        pub(crate) commitment: VerifiableSecretSharingCommitment<C>,
81        /// The proof of knowledge of the temporary secret (σ_i = (R_i, μ_i))
82        pub(crate) proof_of_knowledge: Signature<C>,
83    }
84
85    impl<C> Package<C>
86    where
87        C: Ciphersuite,
88    {
89        /// Create a new [`Package`] instance.
90        pub fn new(
91            commitment: VerifiableSecretSharingCommitment<C>,
92            proof_of_knowledge: Signature<C>,
93        ) -> Self {
94            Self {
95                header: Header::default(),
96                commitment,
97                proof_of_knowledge,
98            }
99        }
100    }
101
102    #[cfg(feature = "serialization")]
103    impl<C> Package<C>
104    where
105        C: Ciphersuite,
106    {
107        /// Serialize the struct into a Vec.
108        pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
109            Serialize::serialize(&self)
110        }
111
112        /// Deserialize the struct from a slice of bytes.
113        pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
114            Deserialize::deserialize(bytes)
115        }
116    }
117
118    /// The secret package that must be kept in memory by the participant
119    /// between the first and second parts of the DKG protocol (round 1).
120    ///
121    /// # Security
122    ///
123    /// This package MUST NOT be sent to other participants!
124    #[derive(Clone, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)]
125    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126    #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
127    #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
128    pub struct SecretPackage<C: Ciphersuite> {
129        /// The identifier of the participant holding the secret.
130        #[zeroize(skip)]
131        pub(crate) identifier: Identifier<C>,
132        /// Coefficients of the temporary secret polynomial for the participant.
133        /// These are (a_{i0}, ..., a_{i(t−1)})) which define the polynomial f_i(x)
134        #[getter(skip)]
135        pub(crate) coefficients: Vec<SerializableScalar<C>>,
136        /// The public commitment for the participant (C_i)
137        #[zeroize(skip)]
138        pub(crate) commitment: VerifiableSecretSharingCommitment<C>,
139        /// The minimum number of signers.
140        pub(crate) min_signers: u16,
141        /// The total number of signers.
142        pub(crate) max_signers: u16,
143    }
144
145    impl<C> SecretPackage<C>
146    where
147        C: Ciphersuite,
148    {
149        /// Create a new Secret Package. This should be called only if
150        /// custom serialization is required; run the DKG to create
151        /// a valid SecretPackage.
152        pub fn new(
153            identifier: Identifier<C>,
154            coefficients: Vec<Scalar<C>>,
155            commitment: VerifiableSecretSharingCommitment<C>,
156            min_signers: u16,
157            max_signers: u16,
158        ) -> Self {
159            Self {
160                identifier,
161                coefficients: coefficients.into_iter().map(SerializableScalar).collect(),
162                commitment,
163                min_signers,
164                max_signers,
165            }
166        }
167
168        /// Returns the secret coefficients.
169        #[cfg_attr(feature = "internals", visibility::make(pub))]
170        pub(crate) fn coefficients(&self) -> Vec<Scalar<C>> {
171            self.coefficients.iter().map(|s| s.0).collect()
172        }
173    }
174
175    #[cfg(feature = "serialization")]
176    impl<C> SecretPackage<C>
177    where
178        C: Ciphersuite,
179    {
180        /// Serialize the struct into a Vec.
181        pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
182            Serialize::serialize(&self)
183        }
184
185        /// Deserialize the struct from a slice of bytes.
186        pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
187            Deserialize::deserialize(bytes)
188        }
189    }
190
191    impl<C> core::fmt::Debug for SecretPackage<C>
192    where
193        C: Ciphersuite,
194    {
195        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196            f.debug_struct("SecretPackage")
197                .field("identifier", &self.identifier)
198                .field("coefficients", &"<redacted>")
199                .field("commitment", &self.commitment)
200                .field("min_signers", &self.min_signers)
201                .field("max_signers", &self.max_signers)
202                .finish()
203        }
204    }
205}
206
207/// DKG Round 2 structures.
208pub mod round2 {
209    use derive_getters::Getters;
210    use zeroize::{Zeroize, ZeroizeOnDrop};
211
212    #[cfg(feature = "serialization")]
213    use alloc::vec::Vec;
214
215    use crate::serialization::SerializableScalar;
216
217    use super::*;
218
219    /// A package that must be sent by each participant to some other participants
220    /// in Round 2 of the DKG protocol. Note that there is one specific package
221    /// for each specific recipient, in contrast to Round 1.
222    ///
223    /// # Security
224    ///
225    /// The package must be sent on an *confidential* and *authenticated* channel.
226    #[derive(Clone, Debug, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)]
227    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
228    #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
229    #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
230    pub struct Package<C: Ciphersuite> {
231        /// Serialization header
232        #[getter(skip)]
233        pub(crate) header: Header<C>,
234        /// The secret share being sent.
235        pub(crate) signing_share: SigningShare<C>,
236    }
237
238    impl<C> Package<C>
239    where
240        C: Ciphersuite,
241    {
242        /// Create a new [`Package`] instance.
243        pub fn new(signing_share: SigningShare<C>) -> Self {
244            Self {
245                header: Header::default(),
246                signing_share,
247            }
248        }
249    }
250
251    #[cfg(feature = "serialization")]
252    impl<C> Package<C>
253    where
254        C: Ciphersuite,
255    {
256        /// Serialize the struct into a Vec.
257        pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
258            Serialize::serialize(&self)
259        }
260
261        /// Deserialize the struct from a slice of bytes.
262        pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
263            Deserialize::deserialize(bytes)
264        }
265    }
266
267    /// The secret package that must be kept in memory by the participant
268    /// between the second and third parts of the DKG protocol (round 2).
269    ///
270    /// # Security
271    ///
272    /// This package MUST NOT be sent to other participants!
273    #[derive(Clone, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)]
274    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
275    #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
276    #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
277    pub struct SecretPackage<C: Ciphersuite> {
278        /// The identifier of the participant holding the secret.
279        #[zeroize(skip)]
280        pub(crate) identifier: Identifier<C>,
281        /// The public commitment from the participant (C_i)
282        #[zeroize(skip)]
283        pub(crate) commitment: VerifiableSecretSharingCommitment<C>,
284        /// The participant's own secret share (f_i(i)).
285        #[getter(skip)]
286        pub(crate) secret_share: SerializableScalar<C>,
287        /// The minimum number of signers.
288        pub(crate) min_signers: u16,
289        /// The total number of signers.
290        pub(crate) max_signers: u16,
291    }
292
293    impl<C> SecretPackage<C>
294    where
295        C: Ciphersuite,
296    {
297        /// Create a new SecretPackage. Use this only if custom serialization is
298        /// required; run the DKG to create a valid SecretPackage.
299        pub fn new(
300            identifier: Identifier<C>,
301            commitment: VerifiableSecretSharingCommitment<C>,
302            secret_share: Scalar<C>,
303            min_signers: u16,
304            max_signers: u16,
305        ) -> Self {
306            Self {
307                identifier,
308                commitment,
309                secret_share: SerializableScalar(secret_share),
310                min_signers,
311                max_signers,
312            }
313        }
314
315        /// Return the SecretShare.
316        pub fn secret_share(&self) -> Scalar<C> {
317            self.secret_share.0
318        }
319    }
320
321    #[cfg(feature = "serialization")]
322    impl<C> SecretPackage<C>
323    where
324        C: Ciphersuite,
325    {
326        /// Serialize the struct into a Vec.
327        pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
328            Serialize::serialize(&self)
329        }
330
331        /// Deserialize the struct from a slice of bytes.
332        pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
333            Deserialize::deserialize(bytes)
334        }
335    }
336
337    impl<C> core::fmt::Debug for SecretPackage<C>
338    where
339        C: Ciphersuite,
340    {
341        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342            f.debug_struct("SecretPackage")
343                .field("identifier", &self.identifier)
344                .field("commitment", &self.commitment)
345                .field("secret_share", &"<redacted>")
346                .field("min_signers", &self.min_signers)
347                .field("max_signers", &self.max_signers)
348                .finish()
349        }
350    }
351}
352
353/// Performs the first part of the distributed key generation protocol
354/// for the given participant.
355///
356/// It returns the [`round1::SecretPackage`] that must be kept in memory
357/// by the participant for the other steps, and the [`round1::Package`] that
358/// must be sent to each other participant in the DKG run.
359pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
360    identifier: Identifier<C>,
361    max_signers: u16,
362    min_signers: u16,
363    mut rng: R,
364) -> Result<(round1::SecretPackage<C>, round1::Package<C>), Error<C>> {
365    validate_num_of_signers::<C>(min_signers, max_signers)?;
366
367    let secret: SigningKey<C> = SigningKey::new(&mut rng);
368
369    // Round 1, Step 1
370    //
371    // > Every participant P_i samples t random values (a_{i0}, ..., a_{i(t−1)}) ← Z_q
372    //
373    // Round 1, Step 3
374    //
375    // > Every participant P_i computes a public commitment
376    // > C⃗_i = 〈φ_{i0}, ..., φ_{i(t−1)}〉, where φ_{ij} = g^{a_{ij}}, 0 ≤ j ≤ t − 1
377    let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, &mut rng);
378
379    let (coefficients, commitment) =
380        generate_secret_polynomial(&secret, max_signers, min_signers, coefficients)?;
381    let proof_of_knowledge =
382        compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?;
383
384    let secret_package = round1::SecretPackage::new(
385        identifier,
386        coefficients,
387        commitment.clone(),
388        min_signers,
389        max_signers,
390    );
391    let package = round1::Package {
392        header: Header::default(),
393        commitment,
394        proof_of_knowledge,
395    };
396
397    Ok((secret_package, package))
398}
399
400/// Generates the challenge for the proof of knowledge to a secret for the DKG.
401fn challenge<C>(
402    identifier: Identifier<C>,
403    verifying_key: &VerifyingKey<C>,
404    R: &Element<C>,
405) -> Result<Challenge<C>, Error<C>>
406where
407    C: Ciphersuite,
408{
409    let mut preimage = vec![];
410
411    preimage.extend_from_slice(identifier.serialize().as_ref());
412    preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.to_element())?.as_ref());
413    preimage.extend_from_slice(<C::Group>::serialize(R)?.as_ref());
414
415    Ok(Challenge(
416        C::HDKG(&preimage[..]).ok_or(Error::DKGNotSupported)?,
417    ))
418}
419
420/// Compute the proof of knowledge of the secret coefficients used to generate
421/// the public secret sharing commitment.
422#[cfg_attr(feature = "internals", visibility::make(pub))]
423pub(crate) fn compute_proof_of_knowledge<C: Ciphersuite, R: RngCore + CryptoRng>(
424    identifier: Identifier<C>,
425    coefficients: &[Scalar<C>],
426    commitment: &VerifiableSecretSharingCommitment<C>,
427    mut rng: R,
428) -> Result<Signature<C>, Error<C>> {
429    // Round 1, Step 2
430    //
431    // > Every P_i computes a proof of knowledge to the corresponding secret
432    // > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k,
433    // > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being
434    // > a context string to prevent replay attacks.
435    let (k, R_i) = <C>::generate_nonce(&mut rng);
436    let c_i = challenge::<C>(identifier, &commitment.verifying_key()?, &R_i)?;
437    let a_i0 = *coefficients.first().ok_or(Error::InvalidCoefficients)?;
438    let mu_i = k + a_i0 * c_i.0;
439    Ok(Signature { R: R_i, z: mu_i })
440}
441
442/// Verifies the proof of knowledge of the secret coefficients used to generate the
443/// public secret sharing commitment.
444#[cfg_attr(feature = "internals", visibility::make(pub))]
445pub(crate) fn verify_proof_of_knowledge<C: Ciphersuite>(
446    identifier: Identifier<C>,
447    commitment: &VerifiableSecretSharingCommitment<C>,
448    proof_of_knowledge: &Signature<C>,
449) -> Result<(), Error<C>> {
450    // Round 1, Step 5
451    //
452    // > Upon receiving C⃗_ℓ, σ_ℓ from participants 1 ≤ ℓ ≤ n, ℓ ≠ i, participant
453    // > P_i verifies σ_ℓ = (R_ℓ, μ_ℓ), aborting on failure, by checking
454    // > R_ℓ ? ≟ g^{μ_ℓ} · φ^{-c_ℓ}_{ℓ0}, where c_ℓ = H(ℓ, Φ, φ_{ℓ0}, R_ℓ).
455    let ell = identifier;
456    let R_ell = proof_of_knowledge.R;
457    let mu_ell = proof_of_knowledge.z;
458    let phi_ell0 = commitment.verifying_key()?;
459    let c_ell = challenge::<C>(ell, &phi_ell0, &R_ell)?;
460    if R_ell != <C::Group>::generator() * mu_ell - phi_ell0.to_element() * c_ell.0 {
461        return Err(Error::InvalidProofOfKnowledge { culprit: ell });
462    }
463    Ok(())
464}
465
466/// Performs the second part of the distributed key generation protocol for the
467/// participant holding the given [`round1::SecretPackage`], given the received
468/// [`round1::Package`]s received from the other participants.
469///
470/// `round1_packages` maps the identifier of each other participant to the
471/// [`round1::Package`] they sent to the current participant (the owner of
472/// `secret_package`). These identifiers must come from whatever mapping the
473/// participant has between communication channels and participants, i.e. they
474/// must have assurance that the [`round1::Package`] came from the participant
475/// with that identifier.
476///
477/// It returns the [`round2::SecretPackage`] that must be kept in memory by the
478/// participant for the final step, and the map of [`round2::Package`]s that
479/// must be sent to each other participant who has the given identifier in the
480/// map key.
481pub fn part2<C: Ciphersuite>(
482    secret_package: round1::SecretPackage<C>,
483    round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
484) -> Result<
485    (
486        round2::SecretPackage<C>,
487        BTreeMap<Identifier<C>, round2::Package<C>>,
488    ),
489    Error<C>,
490> {
491    if round1_packages.len() != (secret_package.max_signers - 1) as usize {
492        return Err(Error::IncorrectNumberOfPackages);
493    }
494
495    if round1_packages.contains_key(&secret_package.identifier) {
496        return Err(Error::UnknownIdentifier);
497    }
498
499    for package in round1_packages.values() {
500        if package.commitment.min_signers() != secret_package.min_signers {
501            return Err(Error::IncorrectNumberOfCommitments);
502        }
503    }
504
505    let mut round2_packages = BTreeMap::new();
506
507    for (sender_identifier, round1_package) in round1_packages {
508        let ell = *sender_identifier;
509        // Round 1, Step 5
510        verify_proof_of_knowledge(
511            ell,
512            &round1_package.commitment,
513            &round1_package.proof_of_knowledge,
514        )?;
515
516        // Round 2, Step 1
517        //
518        // > Each P_i securely sends to each other participant P_ℓ a secret share (ℓ, f_i(ℓ)),
519        // > deleting f_i and each share afterward except for (i, f_i(i)),
520        // > which they keep for themselves.
521        let signing_share = SigningShare::from_coefficients(&secret_package.coefficients(), ell);
522
523        round2_packages.insert(
524            ell,
525            round2::Package {
526                header: Header::default(),
527                signing_share,
528            },
529        );
530    }
531    let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients());
532    Ok((
533        round2::SecretPackage::new(
534            secret_package.identifier,
535            secret_package.commitment.clone(),
536            fii,
537            secret_package.min_signers,
538            secret_package.max_signers,
539        ),
540        round2_packages,
541    ))
542}
543
544/// Performs the third and final part of the distributed key generation protocol
545/// for the participant holding the given [`round2::SecretPackage`], given the
546/// received [`round1::Package`]s and [`round2::Package`]s received from the
547/// other participants.
548///
549/// `round1_packages` must be the same used in [`part2()`].
550///
551/// `round2_packages` maps the identifier of each other participant to the
552/// [`round2::Package`] they sent to the current participant (the owner of
553/// `secret_package`). These identifiers must come from whatever mapping the
554/// participant has between communication channels and participants, i.e. they
555/// must have assurance that the [`round2::Package`] came from the participant
556/// with that identifier.
557///
558/// It returns the [`KeyPackage`] that has the long-lived key share for the
559/// participant, and the [`PublicKeyPackage`]s that has public information about
560/// all participants; both of which are required to compute FROST signatures.
561pub fn part3<C: Ciphersuite>(
562    round2_secret_package: &round2::SecretPackage<C>,
563    round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
564    round2_packages: &BTreeMap<Identifier<C>, round2::Package<C>>,
565) -> Result<(KeyPackage<C>, PublicKeyPackage<C>), Error<C>> {
566    if round1_packages.len() != (round2_secret_package.max_signers - 1) as usize {
567        return Err(Error::IncorrectNumberOfPackages);
568    }
569    if round1_packages.contains_key(&round2_secret_package.identifier) {
570        return Err(Error::UnknownIdentifier);
571    }
572    if round2_packages.contains_key(&round2_secret_package.identifier) {
573        return Err(Error::UnknownIdentifier);
574    }
575    if round1_packages.len() != round2_packages.len() {
576        return Err(Error::IncorrectNumberOfPackages);
577    }
578    if round1_packages
579        .keys()
580        .any(|id| !round2_packages.contains_key(id))
581    {
582        return Err(Error::IncorrectPackage);
583    }
584
585    let mut signing_share = <<C::Group as Group>::Field>::zero();
586
587    for (sender_identifier, round2_package) in round2_packages {
588        // Round 2, Step 2
589        //
590        // > Each P_i verifies their shares by calculating:
591        // > g^{f_ℓ(i)} ≟ ∏^{t−1}_{k=0} φ^{i^k mod q}_{ℓk}, aborting if the
592        // > check fails.
593        let ell = *sender_identifier;
594        let f_ell_i = round2_package.signing_share;
595
596        let commitment = &round1_packages
597            .get(&ell)
598            .ok_or(Error::PackageNotFound)?
599            .commitment;
600
601        // The verification is exactly the same as the regular SecretShare verification;
602        // however the required components are in different places.
603        // Build a temporary SecretShare so what we can call verify().
604        let secret_share = SecretShare {
605            header: Header::default(),
606            identifier: round2_secret_package.identifier,
607            signing_share: f_ell_i,
608            commitment: commitment.clone(),
609        };
610
611        // Verify the share. We don't need the result.
612        // Identify the culprit if an InvalidSecretShare error is returned.
613        let _ = secret_share.verify().map_err(|e| {
614            if let Error::InvalidSecretShare { .. } = e {
615                Error::InvalidSecretShare {
616                    culprit: Some(*sender_identifier),
617                }
618            } else {
619                e
620            }
621        })?;
622
623        // Round 2, Step 3
624        //
625        // > Each P_i calculates their long-lived private signing share by computing
626        // > s_i = ∑^n_{ℓ=1} f_ℓ(i), stores s_i securely, and deletes each f_ℓ(i).
627        signing_share = signing_share + f_ell_i.to_scalar();
628    }
629
630    signing_share = signing_share + round2_secret_package.secret_share();
631    let signing_share = SigningShare::new(signing_share);
632
633    // Round 2, Step 4
634    //
635    // > Each P_i calculates their public verification share Y_i = g^{s_i}.
636    let verifying_share = signing_share.into();
637
638    let commitments: BTreeMap<_, _> = round1_packages
639        .iter()
640        .map(|(id, package)| (*id, &package.commitment))
641        .chain(iter::once((
642            round2_secret_package.identifier,
643            &round2_secret_package.commitment,
644        )))
645        .collect();
646    let public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?;
647
648    let key_package = KeyPackage {
649        header: Header::default(),
650        identifier: round2_secret_package.identifier,
651        signing_share,
652        verifying_share,
653        verifying_key: public_key_package.verifying_key,
654        min_signers: round2_secret_package.min_signers,
655    };
656
657    C::post_dkg(key_package, public_key_package)
658}