Skip to content

Commit

Permalink
[❄] Make verification shares first class
Browse files Browse the repository at this point in the history
signature share verification was unnecessarily slow because you get to
regenerate their whole verification share each time. Now you can cache
it.

Also verification shares are only for EvenY keys. We now have a concept
of "share image" which is more general I'm not entirely sure I like but
I'll leave it in for now.
  • Loading branch information
LLFourn committed Aug 8, 2024
1 parent 151c6d6 commit 87173fa
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 71 deletions.
60 changes: 40 additions & 20 deletions schnorr_fun/src/frost/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
//! # let public_poly2 = poly::scalar::to_point_poly(&secret_poly2);
//! # let public_poly3 = poly::scalar::to_point_poly(&secret_poly3);
//!
//! // Party indicies can be any non-zero scalar
//! // Party indices can be any non-zero scalar
//! let my_index = s!(1).public();
//! let party_index2 = s!(2).public();
//! let party_index3 = s!(3).public();
Expand Down Expand Up @@ -86,7 +86,7 @@
//! // received their secret shares correctly and have the same view of the protocol
//! // (e.g same keygen_id). If they all give the OK then we're ready to use the key and do some signing!
//! // With signing we'll have at least one party be the "coordinator" (steps marked with 🐙)
//! // In this example we'll be the coordiantor (but it doesn't have to be on eof the signing parties)
//! // In this example we'll be the coordinator (but it doesn't have to be on eof the signing parties)
//! let xonly_shared_key = shared_key.into_xonly(); // this is the key signatures will be valid under
//! let xonly_my_secret_share = my_secret_share.into_xonly();
//! # let xonly_secret_share3 = secret_share3.into_xonly();
Expand Down Expand Up @@ -169,7 +169,7 @@
//! let mut poly_rng = derive_nonce_rng! {
//! // use synthetic nonces that add system randomness in
//! nonce_gen => nonce_gen,
//! // Use your static secret key to add further protectoin
//! // Use your static secret key to add further protection
//! secret => static_secret_key,
//! // session id should be unique for each key generation session
//! public => ["frost_key_session_1053"],
Expand Down Expand Up @@ -221,8 +221,8 @@ use secp256kfun::{
/// It is used in interpolation and computation of the shared secret.
///
/// This index can be any non-zero [`Scalar`], but must be unique between parties.
/// In most cases it will make sense to use simple indicies `s!(1), s!(2), ...` for smaller backups.
/// Other applications may desire to use indicies corresponding to pre-existing keys or identifiers.
/// In most cases it will make sense to use simple indices `s!(1), s!(2), ...` for smaller backups.
/// Other applications may desire to use indices corresponding to pre-existing keys or identifiers.
pub type PartyIndex = Scalar<Public, NonZero>;

/// The FROST context.
Expand Down Expand Up @@ -269,7 +269,7 @@ impl<H, NG> Frost<H, NG> {
&self.nonce_gen
}

/// Create our secret shares to be shared with other participants using pre-existing indicies
/// Create our secret shares to be shared with other participants using pre-existing indices
///
/// Each secret share needs to be securely communicated to the intended participant.
///
Expand Down Expand Up @@ -386,9 +386,9 @@ impl core::fmt::Display for FinishKeyGenError {
impl std::error::Error for FinishKeyGenError {}

impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen> Frost<H, NG> {
/// Convienence method to generate secret shares and proof-of-possession to be shared with other
/// Convenience method to generate secret shares and proof-of-possession to be shared with other
/// participants. Each secret share needs to be securely communicated to the intended
/// participant but the proof of possession (shnorr signature) can be publically shared with
/// participant but the proof of possession (schnorr signature) can be publically shared with
/// everyone.
pub fn create_shares_and_pop(
&self,
Expand Down Expand Up @@ -438,7 +438,7 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen> Frost<H, NG> {
/// must not reuse the session id, the resulting rng or anything derived from that rng again.
///
/// 💡 Before using this function with a deterministic rng write a short justification as to why
/// you beleive your session id will be unique per signing attempt. Perhaps include it as a
/// you believe your session id will be unique per signing attempt. Perhaps include it as a
/// comment next to the call. Note **it must be unique even across signing attempts for the same
/// or different messages**.
///
Expand Down Expand Up @@ -537,7 +537,7 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen> Frost<H, NG> {
}

impl<H: Digest<OutputSize = U32> + Clone, NG> Frost<H, NG> {
/// Generate an id for the key generation by hashing the party indicies and their point
/// Generate an id for the key generation by hashing the party indices and their point
/// polynomials
pub fn keygen_id(&self, keygen: &KeyGen) -> [u8; 32] {
let mut keygen_hash = self.keygen_id_hash.clone();
Expand Down Expand Up @@ -650,7 +650,7 @@ impl<H: Digest<OutputSize = U32> + Clone, NG> Frost<H, NG> {
Ok(keygen.frost_poly)
}

/// Combine all receieved shares into your long-lived secret share.
/// Combine all received shares into your long-lived secret share.
///
/// The `secret_shares` includes your own share as well as shares from each of the other
/// parties. The `secret_shares` are validated to match the expected result by evaluating their
Expand Down Expand Up @@ -773,6 +773,7 @@ impl<H: Digest<OutputSize = U32> + Clone, NG> Frost<H, NG> {
final_nonce,
challenge,
nonces,
public_key: shared_key.public_key(),
}
}

Expand Down Expand Up @@ -802,7 +803,7 @@ impl<H: Digest<OutputSize = U32> + Clone, NG> Frost<H, NG> {
///
/// ## Panics
///
/// Panics if the shah
/// Panics if the `secret_share` was not part of the signing session
pub fn sign(
&self,
session: &PartySignSession,
Expand All @@ -825,27 +826,42 @@ impl<H: Digest<OutputSize = U32> + Clone, NG> Frost<H, NG> {
s!(r1 + (r2 * b) + lambda * x * c).public()
}

/// Verify a partial signature for a participant at `index` (from zero).
/// Verify a partial signature for a participant for a particular session.
///
/// The `verification_share` is usually derived from either [`SharedKey::verification_share`] or
/// [`PairedSecretShare::verification_share`].
///
/// ## Return Value
///
/// Returns `true` if signature share is valid.
pub fn verify_signature_share(
&self,
shared_key: &SharedKey<EvenY>,
verification_share: VerificationShare<impl PointType>,
session: &CoordinatorSignSession,
index: PartyIndex,
signature_share: Scalar<Public, Zero>,
) -> Result<(), SignatureShareInvalid> {
let X = verification_share.share_image;
let index = verification_share.index;

// We need to know the verification share was generated against the session's key for
// further validity to have any meaning.
if verification_share.public_key != session.public_key() {
return Err(SignatureShareInvalid { index });
}

let s = signature_share;
let lambda = poly::eval_basis_poly_at_0(index, session.nonces.keys().cloned());
let lambda =
poly::eval_basis_poly_at_0(verification_share.index, session.nonces.keys().cloned());
let c = &session.challenge;
let b = &session.binding_coeff;
let X = shared_key.verification_share(index);
debug_assert!(
session.parties().contains(&index),
"the party is not part of the session"
);
let [R1, R2] = session
.nonces
.get(&index)
.expect("verifying party index that is not part of frost signing coalition")
.ok_or(SignatureShareInvalid { index })?
.0;
let valid = g!(R1 + b * R2 + (c * lambda) * X - s * G).is_zero();
if valid {
Expand All @@ -872,8 +888,12 @@ impl<H: Digest<OutputSize = U32> + Clone, NG> Frost<H, NG> {
});
}
for (party_index, signature_share) in &signature_shares {
self.verify_signature_share(shared_key, session, *party_index, *signature_share)
.map_err(VerifySignatureSharesError::Invalid)?;
self.verify_signature_share(
shared_key.verification_share(*party_index),
session,
*signature_share,
)
.map_err(VerifySignatureSharesError::Invalid)?;
}

let signature = self
Expand Down
16 changes: 12 additions & 4 deletions schnorr_fun/src/frost/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ use secp256kfun::prelude::*;
serde(crate = "crate::fun::serde")
)]
pub struct CoordinatorSignSession {
pub(crate) public_key: Point<EvenY>,
pub(crate) binding_coeff: Scalar<Public>,
pub(crate) agg_binonce: binonce::Nonce<Zero>,
pub(crate) final_nonce: Point<EvenY>,
pub(crate) challenge: Scalar<Public, Zero>,

pub(crate) agg_binonce: binonce::Nonce<Zero>,
pub(crate) nonces: BTreeMap<PartyIndex, binonce::Nonce>,
}

Expand All @@ -44,6 +46,11 @@ impl CoordinatorSignSession {
pub fn final_nonce(&self) -> Point<EvenY> {
self.final_nonce
}

/// The public key this session was started under
pub fn public_key(&self) -> Point<EvenY> {
self.public_key
}
}

/// The session that is used to sign a message.
Expand All @@ -64,11 +71,12 @@ impl CoordinatorSignSession {
)]
pub struct PartySignSession {
pub(crate) public_key: Point<EvenY>,
pub(crate) parties: BTreeSet<Scalar<Public>>,
pub(crate) challenge: Scalar<Public, Zero>,
pub(crate) binonce_needs_negation: bool,
pub(crate) binding_coeff: Scalar<Public>,
pub(crate) final_nonce: Point<EvenY>,
pub(crate) challenge: Scalar<Public, Zero>,

pub(crate) parties: BTreeSet<Scalar<Public>>,
pub(crate) binonce_needs_negation: bool,
}

impl PartySignSession {
Expand Down
46 changes: 39 additions & 7 deletions schnorr_fun/src/frost/share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use secp256kfun::{poly, prelude::*};
pub struct SecretShare {
/// The scalar index for this secret share, usually this is a small number but it can take any
/// value (other than 0).
pub index: Scalar<Public>,
pub index: PartyIndex,
/// The secret scalar which is the output of the polynomial evaluated at `index`
pub share: Scalar<Secret, Zero>,
}
Expand All @@ -68,11 +68,6 @@ impl SecretShare {
poly::scalar::interpolate_and_eval_poly_at_0(&index_and_secret[..])
}

/// The verification share for this secret share.
pub fn verification_share(&self) -> Point<NonNormal, Public, Zero> {
g!(self.share * G)
}

/// Encodes the secret share to 64 bytes. The first 32 is the index and the second 32 is the
/// secret.
pub fn to_bytes(&self) -> [u8; 64] {
Expand All @@ -90,6 +85,11 @@ impl SecretShare {
share: Scalar::from_slice(&bytes[32..])?,
})
}

/// Get the image of the secret share.
pub fn share_image(&self) -> Point<NonNormal, Public, Zero> {
g!(self.share * G)
}
}

secp256kfun::impl_fromstr_deserialize! {
Expand Down Expand Up @@ -243,7 +243,7 @@ impl<Z: ZeroChoice, T: PointType> PairedSecretShare<T, Z> {
}
}

impl PairedSecretShare<Normal, NonZero> {
impl PairedSecretShare<Normal> {
/// Create an XOnly secert share where the paired image is always an `EvenY` point.
#[must_use]
pub fn into_xonly(mut self) -> PairedSecretShare<EvenY> {
Expand All @@ -257,6 +257,38 @@ impl PairedSecretShare<Normal, NonZero> {
}
}

impl PairedSecretShare<EvenY> {
/// Get the verification for the inner secret share.
pub fn verification_share(&self) -> VerificationShare<NonNormal> {
VerificationShare {
index: self.index(),
share_image: self.secret_share.share_image(),
public_key: self.public_key,
}
}
}

/// This is the public image of a [`SecretShare`]. You can't sign with it but you can verify
/// signature shares created by the secret share.
///
/// A `VerificationShare` is the same as a [`share_image`] except it's generated against an `EvenY`
/// key that can actually have signatures verified against it.
///
/// A `VerificationShare` is not designed to be persisted. The verification share will only be able
/// to verify signatures against the key that it was generated from. Tweaking a key with
/// `homomorphic_add` etc will invalidate the verification share.
///
/// [`share_image`]: SecretShare::share_image
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VerificationShare<T: PointType> {
/// The index of the share in the secret sharing
pub index: PartyIndex,
/// The image of the secret share
pub share_image: Point<T, Public, Zero>,
/// The public key that this is a share of
pub public_key: Point<EvenY>,
}

#[cfg(feature = "share_backup")]
mod share_backup {
use super::*;
Expand Down
Loading

0 comments on commit 87173fa

Please sign in to comment.