key_share/
lib.rs

1//! Key share of Threshold Signature Scheme (TSS)
2//!
3//! TSS protocols often share the same structure of key share. Having a separate crate with definition of the
4//! key share struct help reusing the code, keeping different implementations compatible and interopable.
5//!
6//! The crate provides [`DirtyCoreKeyShare`] that contains data such as: secret share, other signers commitments,
7//! public key and etc.
8//!
9//! [`DirtyCoreKeyShare`] may contain any data, not necessarily consistent. TSS protocol implementations typically
10//! don't want to handle inconsistent key shares and would rather assume that it's valid. [`Valid<T>`](Valid)
11//! is a type-guard stating that the value `T` it holds was validated. So, `Valid<DirtyCoreKeyShare>` (or
12//! [`CoreKeyShare`] type alias) can be used to express that only valid key shares are accepted.
13
14#![allow(non_snake_case)]
15#![deny(missing_docs, clippy::unwrap_used, clippy::expect_used, clippy::panic)]
16#![forbid(unused_crate_dependencies)]
17#![cfg_attr(docsrs, feature(doc_auto_cfg))]
18#![no_std]
19
20#[cfg(feature = "std")]
21extern crate std;
22
23extern crate alloc;
24
25use alloc::vec::Vec;
26use core::ops;
27
28use generic_ec::{serde::CurveName, Curve, NonZero, Point, Scalar, SecretScalar};
29use generic_ec_zkp::polynomial::lagrange_coefficient;
30
31#[cfg(feature = "serde")]
32mod serde_fix;
33#[cfg(feature = "spof")]
34pub mod trusted_dealer;
35mod utils;
36mod valid;
37
38pub use self::valid::{Valid, Validate, ValidateError, ValidateFromParts};
39
40/// Core key share
41///
42/// Core key share is type alias to [`DirtyCoreKeyShare`] wrapped into [`Valid<T>`](Valid), meaning
43/// that the key share has been validated that:
44/// * Number of signers `n` doesn't overflow [`u16::MAX`], and that n >= 2
45/// * Signer index `i` is less than `n`
46/// * Signer public commitment matches the secret share
47/// * Threshold value is within range `2 <= t <= n`
48/// * All signers commitments sum up to public key
49///
50/// It's impossible to obtain [`CoreKeyShare`] for the key share that doesn't meet above requirements.
51///
52/// Only immutable access to the key share is provided. If you need to change content of the key share,
53/// you need to obtain dirty key share via [`Valid::into_inner`], modify the key share, and validate it
54/// again to obtain `CoreKeyShare`.
55pub type CoreKeyShare<E> = Valid<DirtyCoreKeyShare<E>>;
56/// Public Key Info
57///
58/// Type alias to [`DirtyKeyInfo`] wrapped into [`Valid<T>`](Valid), meaning that the key info
59/// has been validated that:
60/// * Number of signers `n` doesn't overflow [`u16::MAX`], and that n >= 2
61/// * Threshold value is within range `2 <= t <= n`
62/// * All signers commitments sum up to public key
63///
64/// It's impossible to obtain [`KeyInfo`] that doesn't meet above requirements.
65///
66/// Only immutable access to the key info is provided. If you need to change content of the key info,
67/// you need to obtain dirty key info via [`Valid::into_inner`], modify the key info, and validate it
68/// again to obtain [`KeyInfo`].
69pub type KeyInfo<E> = Valid<DirtyKeyInfo<E>>;
70
71#[cfg(feature = "serde")]
72use serde_with::As;
73
74/// Dirty (unvalidated) core key share
75///
76/// Key share can be either polynomial or additive:
77/// * Polynomial key share:
78///   * Supports any threshold $2 \le t \le n$
79///   * All signers co-share a secret polynomial $F(x)$ with degree $deg(F) = t-1$
80///   * Signer with index $i$ (index is in range $0 \le i < n$) holds secret share $x_i = F(I_i)$
81///   * Shared secret key is $\sk = F(0)$.
82///
83///   If key share is polynomial, [`vss_setup`](DirtyKeyInfo::vss_setup) fiels should be `Some(_)`.
84///
85///   $I_j$ mentioned above is defined in [`VssSetup::I`]. Reasonable default would be $I_j = j+1$.
86/// * Additive key share:
87///   * Always non-threshold (i.e. $t=n$)
88///   * Signer with index $i$ holds a secret share $x_i$
89///   * All signers share a secret key that is sum of all secret shares $\sk = \sum_{j \in \[n\]} x_j$.
90///
91///   Advantage of additive share is that DKG protocol that yields additive share is a bit more efficient.
92///
93/// # HD wallets support
94/// If `hd-wallets` feature is enabled, key share provides basic support of deterministic key derivation:
95/// * [`chain_code`](DirtyKeyInfo::chain_code) field is added. If it's `Some(_)`, then the key is HD-capable.
96///   `(shared_public_key, chain_code)` is extended public key of the wallet (can be retrieved via
97///   [extended_public_key](DirtyCoreKeyShare::extended_public_key) method).
98///   * Setting `chain_code` to `None` disables HD wallets support for the key
99/// * Convenient methods are provided such as [derive_child_public_key](DirtyCoreKeyShare::derive_child_public_key)
100///
101/// # Serialization format via `serde`
102/// We make our best effort to keep serialization format the same between the versions (even with breaking changes),
103/// and so far we've never introduced breaking change into the serialization format. This ensures that newer versions
104/// of library are able to deserialize the key shares produced by the old version version of the library.
105///
106/// It's unlikely, but at some point, we might introduce a breaking change into the serialization format. In this case,
107/// we'll announce it and publish the migration instructions.
108///
109/// Not every serde backend supports features that we use to ensure backwards compatibility. We require that field names
110/// are being serialized, that helps us adding new fields as the library grows. We strongly advise using either
111/// [`serde_json`](https://docs.rs/serde_json/), if verbose/human-readable format is needed, or
112/// [`ciborium`](https://docs.rs/ciborium/latest/ciborium/), if you'd like to opt for binary format. Other serialization
113/// backends are not tested and may not work or stop working at some point (like [bincode](https://github.com/LFDT-Lockness/cggmp21/issues/89) did)
114/// or be not backwards compatible between certain versions.
115///
116/// If you need the smallest size of serialized key share, we advise implementing serialization manually (all fields of
117/// the key share are public!).
118#[derive(Clone)]
119pub struct DirtyCoreKeyShare<E: Curve> {
120    /// Index of local party in key generation protocol
121    pub i: u16,
122    /// Public key info
123    pub key_info: DirtyKeyInfo<E>,
124    /// Secret share $x_i$
125    pub x: NonZero<SecretScalar<E>>,
126}
127
128#[cfg(feature = "serde")]
129impl<E: Curve> serde::Serialize for DirtyCoreKeyShare<E> {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: serde::Serializer,
133    {
134        // See [`crate::serde_fix`] module docs
135        let Self {
136            i,
137            key_info:
138                DirtyKeyInfo {
139                    curve,
140                    shared_public_key,
141                    public_shares,
142                    vss_setup,
143                    #[cfg(feature = "hd-wallet")]
144                    chain_code,
145                },
146            x,
147        } = &self;
148        serde_fix::ser::CoreKeyShare {
149            i,
150            curve,
151            shared_public_key,
152            public_shares,
153            vss_setup,
154            x,
155            #[cfg(feature = "hd-wallet")]
156            chain_code,
157        }
158        .serialize(serializer)
159    }
160}
161
162#[cfg(feature = "serde")]
163impl<'de, E: Curve> serde::Deserialize<'de> for DirtyCoreKeyShare<E> {
164    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165    where
166        D: serde::Deserializer<'de>,
167    {
168        // See [`crate::serde_fix`] module docs
169        let serde_fix::de::CoreKeyShare {
170            curve,
171            i,
172            shared_public_key,
173            public_shares,
174            vss_setup,
175            x,
176            #[cfg(feature = "hd-wallet")]
177            chain_code,
178        } = serde::Deserialize::deserialize(deserializer)?;
179        Ok(Self {
180            i,
181            key_info: DirtyKeyInfo {
182                curve,
183                shared_public_key,
184                public_shares,
185                vss_setup,
186                #[cfg(feature = "hd-wallet")]
187                chain_code,
188            },
189            x,
190        })
191    }
192}
193
194/// Public Key Info
195///
196/// Contains public information about the TSS key, including shared public key, commitments to
197/// secret shares and etc.
198#[derive(Clone, Debug)]
199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
200#[cfg_attr(feature = "serde", serde(bound = ""))]
201#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
202pub struct DirtyKeyInfo<E: Curve> {
203    // NOTE: on changing any of `serde` attributes, remember to change
204    // `crate::serde_fix` as well!
205    //
206    /// Guard that ensures curve consistency for deseraization
207    #[cfg_attr(feature = "udigest", udigest(as = utils::encoding::CurveName))]
208    pub curve: CurveName<E>,
209    /// Public key corresponding to shared secret key. Corresponds to _X_ in paper.
210    #[cfg_attr(feature = "serde", serde(with = "As::<generic_ec::serde::Compact>"))]
211    pub shared_public_key: NonZero<Point<E>>,
212    /// Public shares of all signers sharing the key
213    ///
214    /// `public_shares[i]` corresponds to public share (or public commitment) of $\ith$ party.
215    #[cfg_attr(
216        feature = "serde",
217        serde(with = "As::<Vec<generic_ec::serde::Compact>>")
218    )]
219    pub public_shares: Vec<NonZero<Point<E>>>,
220    /// Verifiable secret sharing setup, present if key was generated using VSS scheme
221    #[cfg_attr(
222        feature = "serde",
223        serde(default, skip_serializing_if = "Option::is_none")
224    )]
225    pub vss_setup: Option<VssSetup<E>>,
226    /// Chain code associated with the key, if HD wallets support was enabled
227    #[cfg(feature = "hd-wallet")]
228    #[cfg_attr(
229        feature = "serde",
230        serde(default),
231        serde(skip_serializing_if = "Option::is_none"),
232        serde(with = "As::<Option<utils::HexOrBin>>")
233    )]
234    #[cfg_attr(feature = "udigest", udigest(as = Option<udigest::Bytes>))]
235    pub chain_code: Option<hd_wallet::ChainCode>,
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
240#[cfg_attr(feature = "serde", serde(bound = ""))]
241#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
242/// Secret sharing setup of a key
243pub struct VssSetup<E: Curve> {
244    /// Threshold parameter
245    ///
246    /// Specifies how many signers are required to perform signing
247    pub min_signers: u16,
248    /// Key shares indexes
249    ///
250    /// `I[i]` corresponds to key share index of a $\ith$ signer
251    #[cfg_attr(
252        feature = "serde",
253        serde(with = "As::<Vec<generic_ec::serde::PreferCompact>>")
254    )]
255    pub I: Vec<NonZero<Scalar<E>>>,
256}
257
258impl<E: Curve> Validate for DirtyCoreKeyShare<E> {
259    type Error = InvalidCoreShare;
260
261    fn is_valid(&self) -> Result<(), Self::Error> {
262        let party_public_share = self
263            .public_shares
264            .get(usize::from(self.i))
265            .ok_or(InvalidShareReason::PartyIndexOutOfBounds)?;
266        if *party_public_share != Point::generator() * &self.x {
267            return Err(InvalidShareReason::PartySecretShareDoesntMatchPublicShare.into());
268        }
269
270        self.key_info.is_valid()?;
271
272        Ok(())
273    }
274}
275
276impl<E: Curve> ValidateFromParts<(u16, DirtyKeyInfo<E>, NonZero<SecretScalar<E>>)>
277    for DirtyCoreKeyShare<E>
278{
279    fn validate_parts(
280        (i, key_info, x): &(u16, DirtyKeyInfo<E>, NonZero<SecretScalar<E>>),
281    ) -> Result<(), Self::Error> {
282        let party_public_share = key_info
283            .public_shares
284            .get(usize::from(*i))
285            .ok_or(InvalidShareReason::PartyIndexOutOfBounds)?;
286        if *party_public_share != Point::generator() * x {
287            return Err(InvalidShareReason::PartySecretShareDoesntMatchPublicShare.into());
288        }
289
290        Ok(())
291    }
292
293    fn from_parts((i, key_info, x): (u16, DirtyKeyInfo<E>, NonZero<SecretScalar<E>>)) -> Self {
294        Self { i, key_info, x }
295    }
296}
297
298impl<E: Curve> Validate for DirtyKeyInfo<E> {
299    type Error = InvalidCoreShare;
300
301    fn is_valid(&self) -> Result<(), Self::Error> {
302        match &self.vss_setup {
303            Some(vss_setup) => {
304                validate_vss_key_info(self.shared_public_key, &self.public_shares, vss_setup)
305            }
306            None => validate_non_vss_key_info(self.shared_public_key, &self.public_shares),
307        }
308    }
309}
310
311#[allow(clippy::nonminimal_bool)]
312fn validate_vss_key_info<E: Curve>(
313    shared_public_key: NonZero<Point<E>>,
314    public_shares: &[NonZero<Point<E>>],
315    vss_setup: &VssSetup<E>,
316) -> Result<(), InvalidCoreShare> {
317    let n: u16 = public_shares
318        .len()
319        .try_into()
320        .map_err(|_| InvalidShareReason::NOverflowsU16)?;
321    if n < 2 {
322        return Err(InvalidShareReason::TooFewParties.into());
323    }
324
325    let t = vss_setup.min_signers;
326    if !(2 <= t) {
327        return Err(InvalidShareReason::ThresholdTooSmall.into());
328    }
329    if !(t <= n) {
330        return Err(InvalidShareReason::ThresholdTooLarge.into());
331    }
332    if vss_setup.I.len() != usize::from(n) {
333        return Err(InvalidShareReason::ILen.into());
334    }
335
336    // Now we need to check that public key shares indeed form a public key.
337    // We do that in two steps:
338    // 1. Take `t` first public key shares, derive a public key and compare
339    //    with public key specified in key share
340    // 2. Using first `t` public key shares, derive other `n-t` public shares
341    //    and compare with the ones specified in the key share
342
343    let first_t_shares = &public_shares[0..usize::from(t)];
344    let indexes = &vss_setup.I[0..usize::from(t)];
345    let interpolation = |x: Scalar<E>| {
346        let lagrange_coefficients = (0..usize::from(t))
347            .map(|j| lagrange_coefficient(x, j, indexes))
348            .collect::<Option<Vec<_>>>()
349            .ok_or(InvalidShareReason::INotPairwiseDistinct)?;
350        Ok::<_, InvalidCoreShare>(Scalar::multiscalar_mul(
351            lagrange_coefficients.into_iter().zip(first_t_shares),
352        ))
353    };
354    let reconstructed_pk = interpolation(Scalar::zero())?;
355    if reconstructed_pk != shared_public_key {
356        return Err(InvalidShareReason::SharesDontMatchPublicKey.into());
357    }
358
359    for (&j, public_share_j) in vss_setup.I.iter().zip(public_shares).skip(t.into()) {
360        if interpolation(j.into())? != *public_share_j {
361            return Err(InvalidShareReason::SharesDontMatchPublicKey.into());
362        }
363    }
364
365    Ok(())
366}
367
368fn validate_non_vss_key_info<E: Curve>(
369    shared_public_key: NonZero<Point<E>>,
370    public_shares: &[NonZero<Point<E>>],
371) -> Result<(), InvalidCoreShare> {
372    let n: u16 = public_shares
373        .len()
374        .try_into()
375        .map_err(|_| InvalidShareReason::NOverflowsU16)?;
376    if n < 2 {
377        return Err(InvalidShareReason::TooFewParties.into());
378    }
379    if shared_public_key != public_shares.iter().sum::<Point<E>>() {
380        return Err(InvalidShareReason::SharesDontMatchPublicKey.into());
381    }
382    Ok(())
383}
384
385impl<E: Curve> DirtyKeyInfo<E> {
386    /// Returns share preimage associated with j-th signer
387    ///
388    /// * For additive shares, share preimage is defined as `j+1`
389    /// * For VSS-shares, share preimage is scalar $I_j$ such that $x_j = F(I_j)$ where
390    ///   $F(x)$ is polynomial co-shared by the signers and $x_j$ is secret share of j-th
391    ///   signer
392    ///
393    /// Note: if you have no idea what it is, probably you don't need it.
394    pub fn share_preimage(&self, j: u16) -> Option<NonZero<Scalar<E>>> {
395        if let Some(vss_setup) = self.vss_setup.as_ref() {
396            vss_setup.I.get(usize::from(j)).copied()
397        } else if usize::from(j) < self.public_shares.len() {
398            #[allow(clippy::expect_used)]
399            Some(
400                NonZero::from_scalar(Scalar::one() + Scalar::from(j))
401                    .expect("1 + i_u16 is guaranteed to be nonzero"),
402            )
403        } else {
404            None
405        }
406    }
407}
408
409#[cfg(feature = "hd-wallet")]
410impl<E: Curve> DirtyKeyInfo<E> {
411    /// Checks whether the key is HD-capable
412    pub fn is_hd_wallet(&self) -> bool {
413        self.chain_code.is_some()
414    }
415
416    /// Returns extended public key, if HD support was enabled
417    pub fn extended_public_key(&self) -> Option<hd_wallet::ExtendedPublicKey<E>> {
418        Some(hd_wallet::ExtendedPublicKey {
419            public_key: self.shared_public_key.into_inner(),
420            chain_code: self.chain_code?,
421        })
422    }
423
424    /// Derives child public key, if it's HD key, using [`HdWallet`](hd_wallet::HdWallet) algorithm
425    pub fn derive_child_public_key<Hd: hd_wallet::HdWallet<E>, ChildIndex>(
426        &self,
427        derivation_path: impl IntoIterator<Item = ChildIndex>,
428    ) -> Result<
429        hd_wallet::ExtendedPublicKey<E>,
430        HdError<<ChildIndex as TryInto<hd_wallet::NonHardenedIndex>>::Error>,
431    >
432    where
433        hd_wallet::NonHardenedIndex: TryFrom<ChildIndex>,
434    {
435        let epub = self.extended_public_key().ok_or(HdError::DisabledHd)?;
436        Hd::try_derive_child_public_key_with_path(
437            &epub,
438            derivation_path.into_iter().map(|index| index.try_into()),
439        )
440        .map_err(HdError::InvalidPath)
441    }
442}
443
444#[cfg(feature = "hd-wallet")]
445impl<E: Curve> DirtyCoreKeyShare<E> {
446    /// Checks whether the key is HD-capable
447    pub fn is_hd_wallet(&self) -> bool {
448        (**self).is_hd_wallet()
449    }
450
451    /// Returns extended public key, if HD support was enabled
452    pub fn extended_public_key(&self) -> Option<hd_wallet::ExtendedPublicKey<E>> {
453        (**self).extended_public_key()
454    }
455
456    /// Derives child public key, if it's HD key
457    pub fn derive_child_public_key<Hd: hd_wallet::HdWallet<E>, ChildIndex>(
458        &self,
459        derivation_path: impl IntoIterator<Item = ChildIndex>,
460    ) -> Result<
461        hd_wallet::ExtendedPublicKey<E>,
462        HdError<<ChildIndex as TryInto<hd_wallet::NonHardenedIndex>>::Error>,
463    >
464    where
465        hd_wallet::NonHardenedIndex: TryFrom<ChildIndex>,
466    {
467        (**self).derive_child_public_key::<Hd, _>(derivation_path)
468    }
469}
470
471impl<E: Curve> CoreKeyShare<E> {
472    /// Returns amount of key co-holders
473    pub fn n(&self) -> u16 {
474        #[allow(clippy::expect_used)]
475        self.public_shares
476            .len()
477            .try_into()
478            .expect("valid key share is guaranteed to have amount of signers fitting into u16")
479    }
480
481    /// Returns threshold
482    ///
483    /// Threshold is an amount of signers required to cooperate in order to sign a message
484    /// and/or generate presignature
485    pub fn min_signers(&self) -> u16 {
486        self.vss_setup
487            .as_ref()
488            .map(|s| s.min_signers)
489            .unwrap_or_else(|| self.n())
490    }
491
492    /// Returns public key shared by signers
493    pub fn shared_public_key(&self) -> NonZero<Point<E>> {
494        self.shared_public_key
495    }
496}
497
498impl<E: Curve> ops::Deref for DirtyCoreKeyShare<E> {
499    type Target = DirtyKeyInfo<E>;
500    fn deref(&self) -> &Self::Target {
501        &self.key_info
502    }
503}
504impl<E: Curve> AsRef<DirtyKeyInfo<E>> for DirtyCoreKeyShare<E> {
505    fn as_ref(&self) -> &DirtyKeyInfo<E> {
506        &self.key_info
507    }
508}
509impl<E: Curve> AsRef<CoreKeyShare<E>> for CoreKeyShare<E> {
510    fn as_ref(&self) -> &CoreKeyShare<E> {
511        self
512    }
513}
514
515/// Error indicating that key share is not valid
516#[derive(Debug, displaydoc::Display)]
517#[cfg_attr(feature = "std", derive(thiserror::Error))]
518#[displaydoc("invalid core key share")]
519pub struct InvalidCoreShare(#[cfg_attr(feature = "std", source)] InvalidShareReason);
520
521#[derive(Debug, displaydoc::Display)]
522#[cfg_attr(feature = "std", derive(thiserror::Error))]
523enum InvalidShareReason {
524    #[displaydoc("`n` overflows u16")]
525    NOverflowsU16,
526    #[displaydoc("amount of parties `n` is less than 2: n < 2")]
527    TooFewParties,
528    #[displaydoc("party secret share doesn't match its public share: public_shares[i] != G x")]
529    PartyIndexOutOfBounds,
530    #[displaydoc("party secret share doesn't match its public share: public_shares[i] != G x")]
531    PartySecretShareDoesntMatchPublicShare,
532    #[displaydoc(
533        "list of public shares doesn't match shared public key: \
534        `public_shares.sum() != shared_public_key`"
535    )]
536    SharesDontMatchPublicKey,
537    #[displaydoc("threshold value is too small (can't be less than 2)")]
538    ThresholdTooSmall,
539    #[displaydoc("threshold valud cannot exceed amount of signers")]
540    ThresholdTooLarge,
541    #[displaydoc("mismatched length of I: I.len() != n")]
542    ILen,
543    #[displaydoc("indexes of shares in I are not pairwise distinct")]
544    INotPairwiseDistinct,
545}
546
547impl From<InvalidShareReason> for InvalidCoreShare {
548    fn from(err: InvalidShareReason) -> Self {
549        Self(err)
550    }
551}
552
553/// Error related to HD key derivation
554#[derive(Debug, displaydoc::Display)]
555#[cfg_attr(feature = "std", derive(thiserror::Error))]
556pub enum HdError<E> {
557    /// HD derivation is disabled for the key
558    DisabledHd,
559    /// derivation path is not valid
560    InvalidPath(#[cfg_attr(feature = "std", source)] E),
561}
562
563impl<T> From<ValidateError<T, InvalidCoreShare>> for InvalidCoreShare {
564    fn from(err: ValidateError<T, InvalidCoreShare>) -> Self {
565        err.into_error()
566    }
567}
568
569/// Reconstructs a secret key from set of at least
570/// [`min_signers`](CoreKeyShare::min_signers) key shares
571///
572/// Requires at least [`min_signers`](CoreKeyShare::min_signers) distinct key
573/// shares. Returns error if input is invalid.
574///
575/// Note that, normally, secret key is not supposed to be reconstructed, and key
576/// shares should never be at one place. This basically defeats purpose of MPC and
577/// creates single point of failure/trust.
578#[cfg(feature = "spof")]
579pub fn reconstruct_secret_key<E: Curve>(
580    key_shares: &[impl AsRef<CoreKeyShare<E>>],
581) -> Result<SecretScalar<E>, ReconstructError> {
582    if key_shares.is_empty() {
583        return Err(ReconstructErrorReason::NoKeyShares.into());
584    }
585
586    let t = key_shares[0].as_ref().min_signers();
587    let pk = key_shares[0].as_ref().shared_public_key;
588    let vss = &key_shares[0].as_ref().vss_setup;
589    let X = &key_shares[0].as_ref().public_shares;
590
591    if key_shares[1..].iter().any(|s| {
592        t != s.as_ref().min_signers()
593            || pk != s.as_ref().shared_public_key
594            || *vss != s.as_ref().vss_setup
595            || *X != s.as_ref().public_shares
596    }) {
597        return Err(ReconstructErrorReason::DifferentKeyShares.into());
598    }
599
600    if key_shares.len() < usize::from(t) {
601        return Err(ReconstructErrorReason::TooFewKeyShares {
602            len: key_shares.len(),
603            t,
604        }
605        .into());
606    }
607
608    if let Some(VssSetup { I, .. }) = vss {
609        let S = key_shares.iter().map(|s| s.as_ref().i).collect::<Vec<_>>();
610        let I = crate::utils::subset(&S, I).ok_or(ReconstructErrorReason::Subset)?;
611        let lagrange_coefficients =
612            (0..).map(|j| generic_ec_zkp::polynomial::lagrange_coefficient_at_zero(j, &I));
613        let mut sk = lagrange_coefficients
614            .zip(key_shares)
615            .try_fold(Scalar::zero(), |acc, (lambda_j, key_share_j)| {
616                Some(acc + lambda_j? * &key_share_j.as_ref().x)
617            })
618            .ok_or(ReconstructErrorReason::Interpolation)?;
619        Ok(SecretScalar::new(&mut sk))
620    } else {
621        let mut sk = key_shares
622            .iter()
623            .map(|s| &s.as_ref().x)
624            .fold(Scalar::zero(), |acc, x_j| acc + x_j);
625        Ok(SecretScalar::new(&mut sk))
626    }
627}
628
629/// Error indicating that [key reconstruction](reconstruct_secret_key) failed
630#[cfg(feature = "spof")]
631#[derive(Debug, displaydoc::Display)]
632#[cfg_attr(feature = "std", derive(thiserror::Error))]
633#[displaydoc("key reconstruction failed")]
634pub struct ReconstructError(#[cfg_attr(feature = "std", source)] ReconstructErrorReason);
635
636#[cfg(feature = "spof")]
637#[derive(Debug, displaydoc::Display)]
638#[cfg_attr(feature = "std", derive(thiserror::Error))]
639enum ReconstructErrorReason {
640    #[displaydoc("no key shares provided")]
641    NoKeyShares,
642    #[displaydoc(
643        "provided key shares doesn't seem to share \
644        the same key or belong to the same generation"
645    )]
646    DifferentKeyShares,
647    #[displaydoc(
648        "expected at least `t={t}` key shares, but {len} \
649        key shares were provided"
650    )]
651    TooFewKeyShares { len: usize, t: u16 },
652    #[displaydoc("subset function returned error (seems like a bug)")]
653    Subset,
654    #[displaydoc("interpolation failed (seems like a bug)")]
655    Interpolation,
656}
657
658#[cfg(feature = "spof")]
659impl From<ReconstructErrorReason> for ReconstructError {
660    fn from(err: ReconstructErrorReason) -> Self {
661        Self(err)
662    }
663}