frost/frost-ristretto255/src/frost/keys.rs

349 lines
12 KiB
Rust

//! FROST keys, keygen, key shares
use std::{collections::HashMap, convert::TryFrom, fmt::Debug};
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar,
traits::Identity,
};
use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
use zeroize::DefaultIsZeroes;
use crate::VerificationKey;
/// A secret scalar value representing a single signer's secret key.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Secret(pub(super) Scalar);
impl Secret {
/// Generates a new uniformly random secret value using the provided RNG.
pub fn random<R>(rng: &mut R) -> Self
where
R: CryptoRng + RngCore,
{
Self(Scalar::random(rng))
}
}
// Zeroizes `Secret` to be the `Default` value on drop (when it goes out of scope). Luckily the
// derived `Default` includes the `Default` impl of Scalar, which is four 0u64's under the hood.
impl DefaultIsZeroes for Secret {}
impl From<Scalar> for Secret {
fn from(source: Scalar) -> Secret {
Secret(source)
}
}
impl FromHex for Secret {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Secret::try_from(bytes),
Err(_) => Err("invalid hex"),
}
}
}
impl TryFrom<[u8; 32]> for Secret {
type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match Scalar::from_canonical_bytes(source) {
Some(scalar) => Ok(Secret(scalar)),
None => Err("scalar was not canonically encoded"),
}
}
}
/// A public group element that represents a single signer's public key.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Public(pub(super) RistrettoPoint);
impl From<RistrettoPoint> for Public {
fn from(source: RistrettoPoint) -> Public {
Public(source)
}
}
impl From<Secret> for Public {
fn from(secret: Secret) -> Public {
Public(RISTRETTO_BASEPOINT_POINT * secret.0)
}
}
/// A Ristretto point that is a commitment to one coefficient of our secret
/// polynomial.
///
/// This is a (public) commitment to one coefficient of a secret polynomial used
/// for performing verifiable secret sharing for a Shamir secret share.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct CoefficientCommitment(pub(super) RistrettoPoint);
/// Contains the commitments to the coefficients for our secret polynomial _f_,
/// used to generate participants' key shares.
///
/// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which
/// themselves are scalars) for a secret polynomial f, where f is used to
/// generate each ith participant's key share f(i). Participants use this set of
/// commitments to perform verifiable secret sharing.
///
/// Note that participants MUST be assured that they have the *same*
/// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using
/// some agreed-upon public location for publication, where each participant can
/// ensure that they received the correct (and same) value.
#[derive(Clone)]
pub struct VerifiableSecretSharingCommitment(pub(super) Vec<CoefficientCommitment>);
/// A secret share generated by performing a (t-out-of-n) secret sharing scheme.
///
/// `n` is the total number of shares and `t` is the threshold required to reconstruct the secret;
/// in this case we use Shamir's secret sharing.
#[derive(Clone)]
pub struct SecretShare {
pub(super) index: u16,
/// Secret Key.
pub(super) value: Secret,
/// The commitments to be distributed among signers.
pub(super) commitment: VerifiableSecretSharingCommitment,
}
impl SecretShare {
/// Verifies that a secret share is consistent with a verifiable secret sharing commitment.
///
/// This ensures that this participant's share has been generated using the same
/// mechanism as all other signing participants. Note that participants *MUST*
/// ensure that they have the same view as all other participants of the
/// commitment!
///
/// An implementation of `vss_verify()` from the [spec].
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#appendix-B.2-4
pub fn verify(&self) -> Result<(), &'static str> {
let f_result = RISTRETTO_BASEPOINT_POINT * self.value.0;
let x = Scalar::from(self.index as u16);
let (_, result) = self.commitment.0.iter().fold(
(Scalar::one(), RistrettoPoint::identity()),
|(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i),
);
if !(f_result == result) {
return Err("SecretShare is invalid.");
}
Ok(())
}
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
///
/// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call
/// .into(), which under the hood also performs validation.
#[derive(Clone)]
pub struct SharePackage {
/// Denotes the participant index each share is owned by.
pub index: u16,
/// This participant's secret share.
pub(super) secret_share: SecretShare,
/// This participant's public key.
pub public: Public,
/// The public signing key that represents the entire group.
pub group_public: VerificationKey,
}
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
///
/// Under the hood, this performs verifiable secret sharing, which itself uses
/// Shamir secret sharing, from which each share becomes a participant's secret
/// key. The output from this function is a set of shares along with one single
/// commitment that participants use to verify the integrity of the share. The
/// number of signers is limited to 255.
///
/// Implements [`trusted_dealer_keygen`] from the spec.
///
/// [`trusted_dealer_keygen`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B
pub fn keygen_with_dealer<R: RngCore + CryptoRng>(
num_signers: u8,
threshold: u8,
mut rng: R,
) -> Result<(Vec<SharePackage>, PublicKeyPackage), &'static str> {
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
let secret = Secret::random(&mut rng);
let group_public = VerificationKey::from(&secret.0);
let secret_shares = generate_secret_shares(&secret, num_signers, threshold, rng)?;
let mut share_packages: Vec<SharePackage> = Vec::with_capacity(num_signers as usize);
let mut signer_pubkeys: HashMap<u16, Public> = HashMap::with_capacity(num_signers as usize);
for secret_share in secret_shares {
let signer_public = secret_share.value.into();
share_packages.push(SharePackage {
index: secret_share.index,
secret_share: secret_share.clone(),
public: signer_public,
group_public,
});
signer_pubkeys.insert(secret_share.index, signer_public);
}
Ok((
share_packages,
PublicKeyPackage {
signer_pubkeys,
group_public,
},
))
}
/// A FROST keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
/// When using a central dealer, [`SharePackage`]s are distributed to
/// participants, who then perform verification, before deriving
/// [`KeyPackage`]s, which they store to later use during signing.
#[derive(Copy, Clone, Debug)]
pub struct KeyPackage {
/// Denotes the participant index each secret share key package is owned by.
pub index: u16,
/// This participant's secret share.
pub(super) secret_share: Secret,
/// This participant's public key.
pub public: Public,
/// The public signing key that represents the entire group.
pub group_public: VerificationKey,
}
impl TryFrom<SharePackage> for KeyPackage {
type Error = &'static str;
/// Tries to verify a share and construct a [`KeyPackage`] from it.
///
/// When participants receive a [`SharePackage`] from the dealer, they
/// *MUST* verify the integrity of the share before continuing on to
/// transform it into a signing/verification keypair. Here, we assume that
/// every participant has the same view of the commitment issued by the
/// dealer, but implementations *MUST* make sure that all participants have
/// a consistent view of this commitment in practice.
fn try_from(share_package: SharePackage) -> Result<Self, &'static str> {
share_package.secret_share.verify()?;
Ok(KeyPackage {
index: share_package.index,
secret_share: share_package.secret_share.value,
public: share_package.public,
group_public: share_package.group_public,
})
}
}
/// Public data that contains all the signers' public keys as well as the
/// group public key.
///
/// Used for verification purposes before publishing a signature.
pub struct PublicKeyPackage {
/// When performing signing, the coordinator must ensure that they have the
/// correct view of participants' public keys to perform verification before
/// publishing a signature. `signer_pubkeys` represents all signers for a
/// signing operation.
pub(super) signer_pubkeys: HashMap<u16, Public>,
/// The joint public key for the entire group.
pub group_public: VerificationKey,
}
/// Creates secret shares for a given secret.
///
/// This function accepts a secret from which shares are generated. While in
/// FROST this secret should always be generated randomly, we allow this secret
/// to be specified for this internal function for testability.
///
/// Internally, [`generate_secret_shares`] performs verifiable secret sharing, which
/// generates shares via Shamir Secret Sharing, and then generates public
/// commitments to those shares.
///
/// More specifically, [`generate_secret_shares`]:
/// - Randomly samples of coefficients [a, b, c], this represents a secret
/// polynomial f
/// - For each participant i, their secret share is f(i)
/// - The commitment to the secret polynomial f is [g^a, g^b, g^c]
///
/// Implements [`secret_key_shard`] from the spec.
///
/// [`secret_key_shard`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B.1
pub fn generate_secret_shares<R: RngCore + CryptoRng>(
secret: &Secret,
numshares: u8,
threshold: u8,
mut rng: R,
) -> Result<Vec<SecretShare>, &'static str> {
if threshold < 2 {
return Err("Threshold cannot be less than 2");
}
if numshares < 2 {
return Err("Number of shares cannot be less than the minimum threshold 2");
}
if threshold > numshares {
return Err("Threshold cannot exceed numshares");
}
let numcoeffs = threshold - 1;
let mut coefficients: Vec<Scalar> = Vec::with_capacity(threshold as usize);
let mut secret_shares: Vec<SecretShare> = Vec::with_capacity(numshares as usize);
let mut commitment: VerifiableSecretSharingCommitment =
VerifiableSecretSharingCommitment(Vec::with_capacity(threshold as usize));
for _ in 0..numcoeffs {
coefficients.push(Scalar::random(&mut rng));
}
// Verifiable secret sharing, to make sure that participants can ensure their
// secret is consistent with every other participant's.
commitment
.0
.push(CoefficientCommitment(RISTRETTO_BASEPOINT_POINT * secret.0));
for c in &coefficients {
commitment
.0
.push(CoefficientCommitment(RISTRETTO_BASEPOINT_POINT * c));
}
// Evaluate the polynomial with `secret` as the constant term
// and `coeffs` as the other coefficients at the point x=share_index,
// using Horner's method.
for index in 1..=numshares {
let scalar_index = Scalar::from(index as u16);
let mut value = Scalar::zero();
// Polynomial evaluation, for this index
for i in (0..numcoeffs).rev() {
value += &coefficients[i as usize];
value *= scalar_index;
}
value += secret.0;
secret_shares.push(SecretShare {
index: index as u16,
value: Secret(value),
commitment: commitment.clone(),
});
}
Ok(secret_shares)
}