frost_core/keys/
refresh.rs

1//! Refresh Shares
2//!
3//! Refreshing shares has two purposes:
4//!
5//! - Mitigate against share compromise.
6//! - Remove participants from a group.
7//!
8//! Refer to the [FROST
9//! book](https://frost.zfnd.org/frost.html#refreshing-shares) for important
10//! details.
11//!
12//! This modules supports refreshing shares using a Trusted Dealer or DKG. You
13//! probably want to use the same approach as the original share generation.
14//!
15//! For the Trusted Dealer approach, the trusted dealer should call
16//! [`compute_refreshing_shares()`] and send the returned refreshing shares to
17//! the participants. Each participant should then call [`refresh_share()`].
18//!
19//! For the DKG approach, the flow is very similar to [DKG
20//! itself](`https://frost.zfnd.org/tutorial/dkg.html`). Each participant calls
21//! [`refresh_dkg_part_1()`], keeps the returned secret package and sends the
22//! returned package to other participants. Then each participants calls
23//! [`refresh_dkg_part2()`] and sends the returned packages to the other
24//! participants. Finally each participant calls [`refresh_dkg_shares()`].
25
26use alloc::collections::BTreeMap;
27use alloc::vec::Vec;
28
29use crate::{
30    keys::dkg::{compute_proof_of_knowledge, round1, round2},
31    keys::{
32        evaluate_polynomial, generate_coefficients, generate_secret_polynomial,
33        generate_secret_shares, validate_num_of_signers, CoefficientCommitment, PublicKeyPackage,
34        SigningKey, SigningShare, VerifyingShare,
35    },
36    Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, RngCore,
37};
38
39use core::iter;
40
41use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretSharingCommitment};
42
43/// Compute refreshing shares for the Trusted Dealer refresh procedure.
44///
45/// - `pub_key_package`: the current public key package.
46/// - `max_signers`: the number of participants that are refreshing their
47///   shares. It can be smaller than the original value, but still equal to or
48///   greater than `min_signers`.
49/// - `min_signers`: the threshold needed to sign. It must be equal to the
50///   original value for the group (i.e. the refresh process can't reduce
51///   the threshold).
52/// - `identifiers`: The identifiers of all participants that want to refresh
53///   their shares. Must be the same length as `max_signers`.
54///
55/// It returns a vectors of [`SecretShare`] that must be sent to the participants
56/// in the same order as `identifiers`, and the refreshed [`PublicKeyPackage`].
57pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>(
58    pub_key_package: PublicKeyPackage<C>,
59    max_signers: u16,
60    min_signers: u16,
61    identifiers: &[Identifier<C>],
62    rng: &mut R,
63) -> Result<(Vec<SecretShare<C>>, PublicKeyPackage<C>), Error<C>> {
64    // Validate inputs
65    if identifiers.len() != max_signers as usize {
66        return Err(Error::IncorrectNumberOfIdentifiers);
67    }
68    validate_num_of_signers(min_signers, max_signers)?;
69
70    // Build refreshing shares
71    let refreshing_key = SigningKey {
72        scalar: <<C::Group as Group>::Field>::zero(),
73    };
74
75    let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, rng);
76    let refreshing_shares = generate_secret_shares(
77        &refreshing_key,
78        max_signers,
79        min_signers,
80        coefficients,
81        identifiers,
82    )?;
83
84    let mut refreshed_verifying_shares: BTreeMap<Identifier<C>, VerifyingShare<C>> =
85        BTreeMap::new();
86    let mut refreshing_shares_minus_identity: Vec<SecretShare<C>> = Vec::new();
87
88    for mut share in refreshing_shares {
89        let refreshing_verifying_share: VerifyingShare<C> = SigningShare::into(share.signing_share);
90
91        let verifying_share = pub_key_package.verifying_shares.get(&share.identifier);
92
93        match verifying_share {
94            Some(verifying_share) => {
95                let refreshed_verifying_share =
96                    refreshing_verifying_share.to_element() + verifying_share.to_element();
97                refreshed_verifying_shares.insert(
98                    share.identifier,
99                    VerifyingShare::new(refreshed_verifying_share),
100                );
101            }
102            None => return Err(Error::UnknownIdentifier),
103        };
104
105        share.commitment.0.remove(0);
106        refreshing_shares_minus_identity.push(share);
107    }
108
109    let refreshed_pub_key_package = PublicKeyPackage::<C> {
110        header: pub_key_package.header,
111        verifying_shares: refreshed_verifying_shares,
112        verifying_key: pub_key_package.verifying_key,
113    };
114
115    Ok((refreshing_shares_minus_identity, refreshed_pub_key_package))
116}
117
118/// Refresh a share in the Trusted Dealer refresh procedure.
119///
120/// Must be called by each participant refreshing the shares, with the
121/// `refreshing_share` received from the trusted dealer and the
122/// `current_key_package` of the participant.
123pub fn refresh_share<C: Ciphersuite>(
124    mut refreshing_share: SecretShare<C>,
125    current_key_package: &KeyPackage<C>,
126) -> Result<KeyPackage<C>, Error<C>> {
127    // The identity commitment needs to be added to the VSS commitment
128    let identity_commitment: Vec<CoefficientCommitment<C>> =
129        vec![CoefficientCommitment::new(C::Group::identity())];
130
131    let refreshing_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
132        .into_iter()
133        .chain(refreshing_share.commitment.0.clone())
134        .collect();
135
136    refreshing_share.commitment =
137        VerifiableSecretSharingCommitment::<C>::new(refreshing_share_commitments);
138
139    // Verify refreshing_share secret share
140    let refreshed_share_package = KeyPackage::<C>::try_from(refreshing_share)?;
141
142    if refreshed_share_package.min_signers() != current_key_package.min_signers() {
143        return Err(Error::InvalidMinSigners);
144    }
145
146    let signing_share: SigningShare<C> = SigningShare::new(
147        refreshed_share_package.signing_share.to_scalar()
148            + current_key_package.signing_share.to_scalar(),
149    );
150
151    let mut new_key_package = current_key_package.clone();
152    new_key_package.signing_share = signing_share;
153
154    Ok(new_key_package)
155}
156
157/// Part 1 of refresh share with DKG.
158///
159/// - `identifier`: The identifier of the participant that wants to refresh
160///   their share.
161/// - `max_signers`: the number of participants that are refreshing their
162///   shares. It can be smaller than the original value, but still equal to or
163///   greater than `min_signers`.
164/// - `min_signers`: the threshold needed to sign. It must be equal to the
165///   original value for the group (i.e. the refresh process can't reduce
166///   the threshold).
167///
168/// It returns the [`round1::SecretPackage`] that must be kept in memory
169/// by the participant for the other steps, and the [`round1::Package`] that
170/// must be sent to each other participant in the refresh run.
171pub fn refresh_dkg_part_1<C: Ciphersuite, R: RngCore + CryptoRng>(
172    identifier: Identifier<C>,
173    max_signers: u16,
174    min_signers: u16,
175    mut rng: R,
176) -> Result<(round1::SecretPackage<C>, round1::Package<C>), Error<C>> {
177    validate_num_of_signers::<C>(min_signers, max_signers)?;
178
179    // Build refreshing shares
180    let refreshing_key = SigningKey {
181        scalar: <<C::Group as Group>::Field>::zero(),
182    };
183
184    // Round 1, Step 1
185    let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, &mut rng);
186
187    let (coefficients, commitment) =
188        generate_secret_polynomial(&refreshing_key, max_signers, min_signers, coefficients)?;
189
190    // Remove identity element from coefficients
191    let mut coeff_comms = commitment.0;
192    coeff_comms.remove(0);
193    let commitment = VerifiableSecretSharingCommitment::new(coeff_comms.clone());
194
195    let proof_of_knowledge =
196        compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?;
197
198    let secret_package = round1::SecretPackage::new(
199        identifier,
200        coefficients.clone(),
201        commitment.clone(),
202        min_signers,
203        max_signers,
204    );
205    let package = round1::Package {
206        header: Header::default(),
207        commitment,
208        proof_of_knowledge,
209    };
210
211    Ok((secret_package, package))
212}
213
214/// Performs the second part of the refresh procedure for the
215/// participant holding the given [`round1::SecretPackage`], given the received
216/// [`round1::Package`]s received from the other participants.
217///
218/// `round1_packages` maps the identifier of each other participant to the
219/// [`round1::Package`] they sent to the current participant (the owner of
220/// `secret_package`). These identifiers must come from whatever mapping the
221/// participant has between communication channels and participants, i.e. they
222/// must have assurance that the [`round1::Package`] came from the participant
223/// with that identifier.
224///
225/// It returns the [`round2::SecretPackage`] that must be kept in memory by the
226/// participant for the final step, and the map of [`round2::Package`]s that
227/// must be sent to each other participant who has the given identifier in the
228/// map key.
229pub fn refresh_dkg_part2<C: Ciphersuite>(
230    mut secret_package: round1::SecretPackage<C>,
231    round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
232) -> Result<
233    (
234        round2::SecretPackage<C>,
235        BTreeMap<Identifier<C>, round2::Package<C>>,
236    ),
237    Error<C>,
238> {
239    if round1_packages.len() != (secret_package.max_signers - 1) as usize {
240        return Err(Error::IncorrectNumberOfPackages);
241    }
242
243    // The identity commitment needs to be added to the VSS commitment for secret package
244    let identity_commitment: Vec<CoefficientCommitment<C>> =
245        vec![CoefficientCommitment::new(C::Group::identity())];
246
247    let refreshing_secret_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
248        .into_iter()
249        .chain(secret_package.commitment.0.clone())
250        .collect();
251
252    secret_package.commitment =
253        VerifiableSecretSharingCommitment::<C>::new(refreshing_secret_share_commitments);
254
255    let mut round2_packages = BTreeMap::new();
256
257    for (sender_identifier, round1_package) in round1_packages {
258        // The identity commitment needs to be added to the VSS commitment for every round 1 package
259        let identity_commitment: Vec<CoefficientCommitment<C>> =
260            vec![CoefficientCommitment::new(C::Group::identity())];
261
262        let refreshing_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
263            .into_iter()
264            .chain(round1_package.commitment.0.clone())
265            .collect();
266
267        if refreshing_share_commitments.clone().len() != secret_package.min_signers as usize {
268            return Err(Error::IncorrectNumberOfCommitments);
269        }
270
271        let ell = *sender_identifier;
272
273        // Round 1, Step 5
274        // We don't need to verify the proof of knowledge
275
276        // Round 2, Step 1
277        //
278        // > Each P_i securely sends to each other participant P_ℓ a secret share (ℓ, f_i(ℓ)),
279        // > deleting f_i and each share afterward except for (i, f_i(i)),
280        // > which they keep for themselves.
281        let signing_share = SigningShare::from_coefficients(&secret_package.coefficients(), ell);
282
283        round2_packages.insert(
284            ell,
285            round2::Package {
286                header: Header::default(),
287                signing_share,
288            },
289        );
290    }
291    let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients());
292
293    Ok((
294        round2::SecretPackage::new(
295            secret_package.identifier,
296            secret_package.commitment,
297            fii,
298            secret_package.min_signers,
299            secret_package.max_signers,
300        ),
301        round2_packages,
302    ))
303}
304
305/// Performs the third and final part of the refresh procedure for the
306/// participant holding the given [`round2::SecretPackage`], given the received
307/// [`round1::Package`]s and [`round2::Package`]s received from the other
308/// participants.
309///
310/// `round1_packages` must be the same used in [`refresh_dkg_part2()`].
311///
312/// `round2_packages` maps the identifier of each other participant to the
313/// [`round2::Package`] they sent to the current participant (the owner of
314/// `secret_package`). These identifiers must come from whatever mapping the
315/// participant has between communication channels and participants, i.e. they
316/// must have assurance that the [`round2::Package`] came from the participant
317/// with that identifier.
318///
319/// `old_pub_key_package` and `old_key_package` are the old values from the
320/// participant, which are being refreshed.
321///
322/// It returns the refreshed [`KeyPackage`] that has the long-lived key share
323/// for the participant, and the refreshed [`PublicKeyPackage`]s that has public
324/// information about all participants; both of which are required to compute
325/// FROST signatures. Note that while the verifying (group) key of the
326/// [`PublicKeyPackage`] will stay the same, the verifying shares will change.
327pub fn refresh_dkg_shares<C: Ciphersuite>(
328    round2_secret_package: &round2::SecretPackage<C>,
329    round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
330    round2_packages: &BTreeMap<Identifier<C>, round2::Package<C>>,
331    old_pub_key_package: PublicKeyPackage<C>,
332    old_key_package: KeyPackage<C>,
333) -> Result<(KeyPackage<C>, PublicKeyPackage<C>), Error<C>> {
334    if round2_secret_package.min_signers() != old_key_package.min_signers() {
335        return Err(Error::InvalidMinSigners);
336    }
337
338    // Add identity commitment back into round1_packages
339    let mut new_round_1_packages = BTreeMap::new();
340    for (sender_identifier, round1_package) in round1_packages {
341        // The identity commitment needs to be added to the VSS commitment for every round 1 package
342        let identity_commitment: Vec<CoefficientCommitment<C>> =
343            vec![CoefficientCommitment::new(C::Group::identity())];
344
345        let refreshing_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
346            .into_iter()
347            .chain(round1_package.commitment.0.clone())
348            .collect();
349
350        let new_commitments =
351            VerifiableSecretSharingCommitment::<C>::new(refreshing_share_commitments);
352
353        let new_round_1_package = Package {
354            header: round1_package.header,
355            commitment: new_commitments,
356            proof_of_knowledge: round1_package.proof_of_knowledge,
357        };
358
359        new_round_1_packages.insert(*sender_identifier, new_round_1_package);
360    }
361
362    if new_round_1_packages.len() != (round2_secret_package.max_signers - 1) as usize {
363        return Err(Error::IncorrectNumberOfPackages);
364    }
365    if new_round_1_packages.len() != round2_packages.len() {
366        return Err(Error::IncorrectNumberOfPackages);
367    }
368    if new_round_1_packages
369        .keys()
370        .any(|id| !round2_packages.contains_key(id))
371    {
372        return Err(Error::IncorrectPackage);
373    }
374
375    let mut signing_share = <<C::Group as Group>::Field>::zero();
376
377    for (sender_identifier, round2_package) in round2_packages {
378        // Round 2, Step 2
379        //
380        // > Each P_i verifies their shares by calculating:
381        // > g^{f_ℓ(i)} ≟ ∏^{t−1}_{k=0} φ^{i^k mod q}_{ℓk}, aborting if the
382        // > check fails.
383        let ell = *sender_identifier;
384        let f_ell_i = round2_package.signing_share;
385
386        let commitment = &new_round_1_packages
387            .get(&ell)
388            .ok_or(Error::PackageNotFound)?
389            .commitment;
390
391        // The verification is exactly the same as the regular SecretShare verification;
392        // however the required components are in different places.
393        // Build a temporary SecretShare so what we can call verify().
394        let secret_share = SecretShare {
395            header: Header::default(),
396            identifier: round2_secret_package.identifier,
397            signing_share: f_ell_i,
398            commitment: commitment.clone(),
399        };
400
401        // Verify the share. We don't need the result.
402        let _ = secret_share.verify()?;
403
404        // Round 2, Step 3
405        //
406        // > Each P_i calculates their long-lived private signing share by computing
407        // > s_i = ∑^n_{ℓ=1} f_ℓ(i), stores s_i securely, and deletes each f_ℓ(i).
408        signing_share = signing_share + f_ell_i.to_scalar();
409    }
410
411    signing_share = signing_share + round2_secret_package.secret_share();
412
413    // Build new signing share
414    let old_signing_share = old_key_package.signing_share.to_scalar();
415    signing_share = signing_share + old_signing_share;
416    let signing_share = SigningShare::new(signing_share);
417
418    // Round 2, Step 4
419    //
420    // > Each P_i calculates their public verification share Y_i = g^{s_i}.
421    let verifying_share = signing_share.into();
422
423    let commitments: BTreeMap<_, _> = new_round_1_packages
424        .iter()
425        .map(|(id, package)| (*id, &package.commitment))
426        .chain(iter::once((
427            round2_secret_package.identifier,
428            &round2_secret_package.commitment,
429        )))
430        .collect();
431
432    let zero_shares_public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?;
433
434    let mut new_verifying_shares = BTreeMap::new();
435
436    for (identifier, verifying_share) in zero_shares_public_key_package.verifying_shares {
437        let new_verifying_share = verifying_share.to_element()
438            + old_pub_key_package
439                .verifying_shares
440                .get(&identifier)
441                .ok_or(Error::UnknownIdentifier)?
442                .to_element();
443        new_verifying_shares.insert(identifier, VerifyingShare::new(new_verifying_share));
444    }
445
446    let public_key_package = PublicKeyPackage {
447        header: old_pub_key_package.header,
448        verifying_shares: new_verifying_shares,
449        verifying_key: old_pub_key_package.verifying_key,
450    };
451
452    let key_package = KeyPackage {
453        header: Header::default(),
454        identifier: round2_secret_package.identifier,
455        signing_share,
456        verifying_share,
457        verifying_key: public_key_package.verifying_key,
458        min_signers: round2_secret_package.min_signers,
459    };
460
461    Ok((key_package, public_key_package))
462}