#![allow(non_snake_case)]
#![deny(missing_docs, clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![forbid(unused_crate_dependencies)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use core::ops;
use generic_ec::{serde::CurveName, Curve, NonZero, Point, Scalar, SecretScalar};
use generic_ec_zkp::polynomial::lagrange_coefficient;
#[cfg(feature = "spof")]
pub mod trusted_dealer;
mod utils;
mod valid;
pub use self::valid::{Valid, Validate, ValidateError, ValidateFromParts};
pub type CoreKeyShare<E> = Valid<DirtyCoreKeyShare<E>>;
pub type KeyInfo<E> = Valid<DirtyKeyInfo<E>>;
#[cfg(feature = "serde")]
use serde_with::As;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = ""))]
pub struct DirtyCoreKeyShare<E: Curve> {
pub i: u16,
#[cfg_attr(feature = "serde", serde(flatten))]
pub key_info: DirtyKeyInfo<E>,
#[cfg_attr(feature = "serde", serde(with = "As::<generic_ec::serde::Compact>"))]
pub x: SecretScalar<E>,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = ""))]
#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
pub struct DirtyKeyInfo<E: Curve> {
#[cfg_attr(feature = "udigest", udigest(with = utils::encoding::curve_name))]
pub curve: CurveName<E>,
#[cfg_attr(feature = "serde", serde(with = "As::<generic_ec::serde::Compact>"))]
pub shared_public_key: Point<E>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Vec<generic_ec::serde::Compact>>")
)]
pub public_shares: Vec<Point<E>>,
pub vss_setup: Option<VssSetup<E>>,
#[cfg(feature = "hd-wallets")]
#[cfg_attr(
feature = "serde",
serde(default),
serde(with = "As::<Option<utils::HexOrBin>>")
)]
#[cfg_attr(feature = "udigest", udigest(with = utils::encoding::maybe_bytes))]
pub chain_code: Option<slip_10::ChainCode>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = ""))]
#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
pub struct VssSetup<E: Curve> {
pub min_signers: u16,
pub I: Vec<NonZero<Scalar<E>>>,
}
impl<E: Curve> Validate for DirtyCoreKeyShare<E> {
type Error = InvalidCoreShare;
fn is_valid(&self) -> Result<(), Self::Error> {
let party_public_share = self
.public_shares
.get(usize::from(self.i))
.ok_or(InvalidShareReason::PartyIndexOutOfBounds)?;
if *party_public_share != Point::generator() * &self.x {
return Err(InvalidShareReason::PartySecretShareDoesntMatchPublicShare.into());
}
self.key_info.is_valid()?;
Ok(())
}
}
impl<E: Curve> ValidateFromParts<(u16, DirtyKeyInfo<E>, SecretScalar<E>)> for DirtyCoreKeyShare<E> {
fn validate_parts(
(i, key_info, x): &(u16, DirtyKeyInfo<E>, SecretScalar<E>),
) -> Result<(), Self::Error> {
let party_public_share = key_info
.public_shares
.get(usize::from(*i))
.ok_or(InvalidShareReason::PartyIndexOutOfBounds)?;
if *party_public_share != Point::generator() * x {
return Err(InvalidShareReason::PartySecretShareDoesntMatchPublicShare.into());
}
Ok(())
}
fn from_parts((i, key_info, x): (u16, DirtyKeyInfo<E>, SecretScalar<E>)) -> Self {
Self { i, key_info, x }
}
}
impl<E: Curve> Validate for DirtyKeyInfo<E> {
type Error = InvalidCoreShare;
fn is_valid(&self) -> Result<(), Self::Error> {
match &self.vss_setup {
Some(vss_setup) => {
validate_vss_key_info(self.shared_public_key, &self.public_shares, vss_setup)
}
None => validate_non_vss_key_info(self.shared_public_key, &self.public_shares),
}
}
}
#[allow(clippy::nonminimal_bool)]
fn validate_vss_key_info<E: Curve>(
shared_public_key: Point<E>,
public_shares: &[Point<E>],
vss_setup: &VssSetup<E>,
) -> Result<(), InvalidCoreShare> {
let n: u16 = public_shares
.len()
.try_into()
.map_err(|_| InvalidShareReason::NOverflowsU16)?;
if n < 2 {
return Err(InvalidShareReason::TooFewParties.into());
}
let t = vss_setup.min_signers;
if !(2 <= t) {
return Err(InvalidShareReason::ThresholdTooSmall.into());
}
if !(t <= n) {
return Err(InvalidShareReason::ThresholdTooLarge.into());
}
if vss_setup.I.len() != usize::from(n) {
return Err(InvalidShareReason::ILen.into());
}
let first_t_shares = &public_shares[0..usize::from(t)];
let indexes = &vss_setup.I[0..usize::from(t)];
let interpolation = |x: Scalar<E>| {
let lagrange_coefficients =
(0..usize::from(t)).map(|j| lagrange_coefficient(x, j, indexes));
lagrange_coefficients
.zip(first_t_shares)
.try_fold(Point::zero(), |acc, (lambda_j, X_j)| {
Some(acc + lambda_j? * X_j)
})
.ok_or(InvalidShareReason::INotPairwiseDistinct)
};
let reconstructed_pk = interpolation(Scalar::zero())?;
if reconstructed_pk != shared_public_key {
return Err(InvalidShareReason::SharesDontMatchPublicKey.into());
}
for (&j, public_share_j) in vss_setup.I.iter().zip(public_shares).skip(t.into()) {
if interpolation(j.into())? != *public_share_j {
return Err(InvalidShareReason::SharesDontMatchPublicKey.into());
}
}
Ok(())
}
fn validate_non_vss_key_info<E: Curve>(
shared_public_key: Point<E>,
public_shares: &[Point<E>],
) -> Result<(), InvalidCoreShare> {
let n: u16 = public_shares
.len()
.try_into()
.map_err(|_| InvalidShareReason::NOverflowsU16)?;
if n < 2 {
return Err(InvalidShareReason::TooFewParties.into());
}
if shared_public_key != public_shares.iter().sum::<Point<E>>() {
return Err(InvalidShareReason::SharesDontMatchPublicKey.into());
}
Ok(())
}
impl<E: Curve> DirtyKeyInfo<E> {
pub fn share_preimage(&self, j: u16) -> Option<NonZero<Scalar<E>>> {
if let Some(vss_setup) = self.vss_setup.as_ref() {
vss_setup.I.get(usize::from(j)).copied()
} else if usize::from(j) < self.public_shares.len() {
#[allow(clippy::expect_used)]
Some(
NonZero::from_scalar(Scalar::one() + Scalar::from(j))
.expect("1 + i_u16 is guaranteed to be nonzero"),
)
} else {
None
}
}
}
#[cfg(feature = "hd-wallets")]
impl<E: Curve> DirtyCoreKeyShare<E> {
pub fn is_hd_wallet(&self) -> bool {
self.chain_code.is_some()
}
pub fn extended_public_key(&self) -> Option<slip_10::ExtendedPublicKey<E>> {
Some(slip_10::ExtendedPublicKey {
public_key: self.shared_public_key,
chain_code: self.chain_code?,
})
}
pub fn derive_child_public_key<ChildIndex>(
&self,
derivation_path: impl IntoIterator<Item = ChildIndex>,
) -> Result<
slip_10::ExtendedPublicKey<E>,
HdError<<ChildIndex as TryInto<slip_10::NonHardenedIndex>>::Error>,
>
where
slip_10::NonHardenedIndex: TryFrom<ChildIndex>,
{
let epub = self.extended_public_key().ok_or(HdError::DisabledHd)?;
slip_10::try_derive_child_public_key_with_path(
&epub,
derivation_path.into_iter().map(|index| index.try_into()),
)
.map_err(HdError::InvalidPath)
}
}
impl<E: Curve> CoreKeyShare<E> {
pub fn n(&self) -> u16 {
#[allow(clippy::expect_used)]
self.public_shares
.len()
.try_into()
.expect("valid key share is guaranteed to have amount of signers fitting into u16")
}
pub fn min_signers(&self) -> u16 {
self.vss_setup
.as_ref()
.map(|s| s.min_signers)
.unwrap_or_else(|| self.n())
}
pub fn shared_public_key(&self) -> Point<E> {
self.shared_public_key
}
}
impl<E: Curve> ops::Deref for DirtyCoreKeyShare<E> {
type Target = DirtyKeyInfo<E>;
fn deref(&self) -> &Self::Target {
&self.key_info
}
}
impl<E: Curve> AsRef<DirtyKeyInfo<E>> for DirtyCoreKeyShare<E> {
fn as_ref(&self) -> &DirtyKeyInfo<E> {
&self.key_info
}
}
impl<E: Curve> AsRef<CoreKeyShare<E>> for CoreKeyShare<E> {
fn as_ref(&self) -> &CoreKeyShare<E> {
self
}
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct InvalidCoreShare(#[from] InvalidShareReason);
#[derive(Debug, thiserror::Error)]
enum InvalidShareReason {
#[error("`n` overflows u16")]
NOverflowsU16,
#[error("amount of parties `n` is less than 2: n < 2")]
TooFewParties,
#[error("party index `i` out of bounds: i >= n")]
PartyIndexOutOfBounds,
#[error("party secret share doesn't match its public share: public_shares[i] != G x")]
PartySecretShareDoesntMatchPublicShare,
#[error("list of public shares doesn't match shared public key: public_shares.sum() != shared_public_key")]
SharesDontMatchPublicKey,
#[error("threshold value is too small (can't be less than 2)")]
ThresholdTooSmall,
#[error("threshold valud cannot exceed amount of signers")]
ThresholdTooLarge,
#[error("mismatched length of I: I.len() != n")]
ILen,
#[error("indexes of shares in I are not pairwise distinct")]
INotPairwiseDistinct,
}
#[derive(Debug, thiserror::Error)]
pub enum HdError<E> {
#[error("HD derivation is disabled for the key")]
DisabledHd,
#[error("derivation path is not valid")]
InvalidPath(#[source] E),
}
impl<T> From<ValidateError<T, InvalidCoreShare>> for InvalidCoreShare {
fn from(err: ValidateError<T, InvalidCoreShare>) -> Self {
err.into_error()
}
}
#[cfg(feature = "spof")]
pub fn reconstruct_secret_key<E: Curve>(
key_shares: &[impl AsRef<CoreKeyShare<E>>],
) -> Result<SecretScalar<E>, ReconstructError> {
if key_shares.is_empty() {
return Err(ReconstructErrorReason::NoKeyShares.into());
}
let t = key_shares[0].as_ref().min_signers();
let pk = key_shares[0].as_ref().shared_public_key;
let vss = &key_shares[0].as_ref().vss_setup;
let X = &key_shares[0].as_ref().public_shares;
if key_shares[1..].iter().any(|s| {
t != s.as_ref().min_signers()
|| pk != s.as_ref().shared_public_key
|| *vss != s.as_ref().vss_setup
|| *X != s.as_ref().public_shares
}) {
return Err(ReconstructErrorReason::DifferentKeyShares.into());
}
if key_shares.len() < usize::from(t) {
return Err(ReconstructErrorReason::TooFewKeyShares {
len: key_shares.len(),
t,
}
.into());
}
if let Some(VssSetup { I, .. }) = vss {
let S = key_shares.iter().map(|s| s.as_ref().i).collect::<Vec<_>>();
let I = crate::utils::subset(&S, I).ok_or(ReconstructErrorReason::Subset)?;
let lagrange_coefficients =
(0..).map(|j| generic_ec_zkp::polynomial::lagrange_coefficient(Scalar::zero(), j, &I));
let mut sk = lagrange_coefficients
.zip(key_shares)
.try_fold(Scalar::zero(), |acc, (lambda_j, key_share_j)| {
Some(acc + lambda_j? * &key_share_j.as_ref().x)
})
.ok_or(ReconstructErrorReason::Interpolation)?;
Ok(SecretScalar::new(&mut sk))
} else {
let mut sk = key_shares
.iter()
.map(|s| &s.as_ref().x)
.fold(Scalar::zero(), |acc, x_j| acc + x_j);
Ok(SecretScalar::new(&mut sk))
}
}
#[cfg(feature = "spof")]
#[derive(Debug, thiserror::Error)]
#[error("secret key reconstruction error")]
pub struct ReconstructError(
#[source]
#[from]
ReconstructErrorReason,
);
#[cfg(feature = "spof")]
#[derive(Debug, thiserror::Error)]
enum ReconstructErrorReason {
#[error("no key shares provided")]
NoKeyShares,
#[error(
"provided key shares doesn't seem to share the same key or belong to the same generation"
)]
DifferentKeyShares,
#[error("expected at least `t={t}` key shares, but {len} key shares were provided")]
TooFewKeyShares { len: usize, t: u16 },
#[error("subset function returned error (seems like a bug)")]
Subset,
#[error("interpolation failed (seems like a bug)")]
Interpolation,
}