1#![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
40pub type CoreKeyShare<E> = Valid<DirtyCoreKeyShare<E>>;
56pub type KeyInfo<E> = Valid<DirtyKeyInfo<E>>;
70
71#[cfg(feature = "serde")]
72use serde_with::As;
73
74#[derive(Clone)]
119pub struct DirtyCoreKeyShare<E: Curve> {
120 pub i: u16,
122 pub key_info: DirtyKeyInfo<E>,
124 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 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 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#[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 #[cfg_attr(feature = "udigest", udigest(as = utils::encoding::CurveName))]
208 pub curve: CurveName<E>,
209 #[cfg_attr(feature = "serde", serde(with = "As::<generic_ec::serde::Compact>"))]
211 pub shared_public_key: NonZero<Point<E>>,
212 #[cfg_attr(
216 feature = "serde",
217 serde(with = "As::<Vec<generic_ec::serde::Compact>>")
218 )]
219 pub public_shares: Vec<NonZero<Point<E>>>,
220 #[cfg_attr(
222 feature = "serde",
223 serde(default, skip_serializing_if = "Option::is_none")
224 )]
225 pub vss_setup: Option<VssSetup<E>>,
226 #[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))]
242pub struct VssSetup<E: Curve> {
244 pub min_signers: u16,
248 #[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 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 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 pub fn is_hd_wallet(&self) -> bool {
413 self.chain_code.is_some()
414 }
415
416 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 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 pub fn is_hd_wallet(&self) -> bool {
448 (**self).is_hd_wallet()
449 }
450
451 pub fn extended_public_key(&self) -> Option<hd_wallet::ExtendedPublicKey<E>> {
453 (**self).extended_public_key()
454 }
455
456 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 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 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 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#[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#[derive(Debug, displaydoc::Display)]
555#[cfg_attr(feature = "std", derive(thiserror::Error))]
556pub enum HdError<E> {
557 DisabledHd,
559 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#[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#[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}