Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spark modified frost implemenation #1

Open
wants to merge 6 commits into
base: secp256k1-tr-base
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,3 +697,58 @@ where

Ok(signature)
}

/// signature aggreation
pub fn aggregate_spark<C>(
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
verifying_key: &VerifyingKey<C>,
) -> Result<Signature<C>, Error<C>>
where
C: Ciphersuite,
{
// Check if signing_package.signing_commitments and signature_shares have
// the same set of identifiers, and if they are all in pubkeys.verifying_shares.
if signing_package.signing_commitments().len() != signature_shares.len() {
return Err(Error::UnknownIdentifier);
}

// if !signing_package.signing_commitments().keys().all(|id| {
// #[cfg(feature = "cheater-detection")]
// return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id);
// #[cfg(not(feature = "cheater-detection"))]
// return signature_shares.contains_key(id);
// }) {
// return Err(Error::UnknownIdentifier);
// }

// Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
// binding factor.
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, &verifying_key, &[]);
// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;

// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
//
// Implements [`aggregate`] from the spec.
//
// [`aggregate`]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation
let mut z = <<C::Group as Group>::Field>::zero();

for signature_share in signature_shares.values() {
z = z + signature_share.share;
}

let R = <C>::effective_nonce_element(group_commitment.0);

let signature: Signature<C> =
<C>::aggregate_sig_finalize(z, R, &verifying_key, &signing_package.sig_target);

// Verify the aggregate signature
let verification_result = verifying_key.verify(signing_package.sig_target.clone(), &signature);
verification_result?;

Ok(signature)
}
74 changes: 74 additions & 0 deletions frost-core/src/round2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,80 @@ pub fn sign<C: Ciphersuite>(
group_commitment,
lambda_i,
key_package,
key_package.verifying_key(),
challenge,
&signing_package.sig_target.sig_params,
);

Ok(signature_share)
}

/// Performed once by each participant selected for the signing operation.
pub fn sign_spark<C: Ciphersuite>(
signing_package: &SigningPackage<C>,
signer_nonces: &round1::SigningNonces<C>,
key_package: &frost::keys::KeyPackage<C>,
inner_coef_set_x: &BTreeSet<Identifier<C>>,
outer_coef_set_x: Option<&BTreeSet<Identifier<C>>>,
outer_signer_id: Option<&Identifier<C>>,
verifying_key: &VerifyingKey<C>,
) -> Result<SignatureShare<C>, Error<C>> {
if signing_package.signing_commitments().len() < key_package.min_signers as usize {
return Err(Error::IncorrectNumberOfCommitments);
}

// Validate the signer's commitment is present in the signing package
let commitment = signing_package
.signing_commitments
.get(&key_package.identifier)
.ok_or(Error::MissingCommitment)?;

// Validate if the signer's commitment exists
if &signer_nonces.commitments != commitment {
return Err(Error::IncorrectCommitment);
}

// Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
// binding factor.
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, &verifying_key, &[]);
let binding_factor: frost::BindingFactor<C> = binding_factor_list
.get(&key_package.identifier)
.ok_or(Error::UnknownIdentifier)?
.clone();

// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;

// Compute Lagrange coefficient.
let inner_lambda_i =
frost::compute_lagrange_coefficient(inner_coef_set_x, None, *key_package.identifier())?;

let outer_lambda_i = match (outer_coef_set_x, outer_signer_id) {
(Some(outer_coef_set_x), Some(outer_signer_id)) => {
// If the user's key is a SSS shard, we need to compute the outer lambda_i
frost::compute_lagrange_coefficient(outer_coef_set_x, None, *outer_signer_id)?
}
// Otherwise, we use additive approach for user's key and SO's keys.
_ => <<C::Group as Group>::Field>::one(),
};
Comment on lines +285 to +292
Copy link
Collaborator

@conduition conduition Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see your approach here is to assume we have two distinct sets of signer IDs, the inner and the outer sets. This should work, but presently the code assumes the two sets don't intersect.

Let's say we set outer_coef_set_x = {1, 2} and inner_coef_set_x = {1, 2, 3, 4, 5, ...}. Signer 1 is the SE and signer 2 is the user. The signing_package stores all commitments, inner and outer, in a BTreeMap<Identifier<C>, round1::SigningCommitments<C>>.

If the inner signer (SO) with identifier 2 participates in the signing session, their commitment will overwrite the user's commitment, or vise-versa. Both commitments can't exist in the BTree at once.

My first thought is to adjust the structure of the SigningPackage or SigningParameters to include the outer signers' commitments as an extra field, rather than lumping them in with the inner signers' commitments.

How do you think that we should handle this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I should include this script on how to use this to make this more clear
https://gist.github.com/zhenlu/6183156fd6151bc82dedca1f6888c24e

So my approach is that each signer have 2 identifiers here, inner_id and outer_id. The SigningCommitments here is only using the inner_id, so the inner_id should be different for each signers. However, the outer_id is only used for calculate the Lagrange coefficient for the second round, so it's ok to intersect with inner.

In the above script, I have the inner_ids for the group as 1, 2, ..., n, and assume user's inner_id as 100 (Note this is only a hack for now, assuming we have less than 100 participants in the group.) And always set group's outer_id to 1, user's outer_id to 2 (I hardcoded this since our use case only has two participants in this round).

I think your approach might work better since we don't need these extra parameters if we include the outer signers' commitments as an extra field, (probably still need the outer signer's identifier to be able to calculate Lagrange coefficient for outer signers). My approach here is just to make the minimum change needed to test this works.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, as long as we're aware of that limitation and can guard against it 👍


let lambda_i = inner_lambda_i * outer_lambda_i;
// Compute the per-message challenge.
let challenge = <C>::challenge(
&group_commitment.0,
&verifying_key,
&signing_package.sig_target,
);

// Compute the Schnorr signature share.
let signature_share = <C>::compute_signature_share(
signer_nonces,
binding_factor,
group_commitment,
lambda_i,
key_package,
verifying_key,
challenge,
&signing_package.sig_target.sig_params,
);
Expand Down
1 change: 1 addition & 0 deletions frost-core/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
_group_commitment: GroupCommitment<Self>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &KeyPackage<Self>,
_verifying_key: &VerifyingKey<Self>,
challenge: Challenge<Self>,
_sig_params: &Self::SigningParameters,
) -> round2::SignatureShare<Self> {
Expand Down
33 changes: 32 additions & 1 deletion frost-secp256k1-tr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ impl Ciphersuite for Secp256K1Sha256 {
group_commitment: GroupCommitment<S>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &frost::keys::KeyPackage<S>,
verifying_key: &VerifyingKey,
challenge: Challenge<S>,
sig_params: &SigningParameters,
) -> round2::SignatureShare {
Expand All @@ -438,7 +439,7 @@ impl Ciphersuite for Secp256K1Sha256 {
}

let mut kp = key_package.clone();
let public_key = key_package.verifying_key();
let public_key = verifying_key;
let pubkey_is_odd: bool = public_key.y_is_odd();
let tweaked_pubkey_is_odd: bool =
tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref())
Expand Down Expand Up @@ -720,6 +721,27 @@ pub mod round2 {
) -> Result<SignatureShare, Error> {
frost::round2::sign(signing_package, signer_nonces, key_package)
}

/// Spark custom signing operation.
pub fn sign_spark(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
inner_coef_set_x: &std::collections::BTreeSet<Identifier>,
outer_coef_set_x: Option<&std::collections::BTreeSet<Identifier>>,
outer_signer_id: Option<&Identifier>,
verifiying_key: &VerifyingKey,
) -> Result<SignatureShare, Error> {
frost::round2::sign_spark(
signing_package,
signer_nonces,
key_package,
inner_coef_set_x,
outer_coef_set_x,
outer_signer_id,
verifiying_key,
)
}
}

/// A Schnorr signature on FROST(secp256k1, SHA-256).
Expand Down Expand Up @@ -748,6 +770,15 @@ pub fn aggregate(
frost::aggregate(signing_package, signature_shares, pubkeys)
}

/// Spark
pub fn aggregate_spark(
signing_package: &SigningPackage,
signature_shares: &BTreeMap<Identifier, round2::SignatureShare>,
verifying_key: &VerifyingKey,
) -> Result<Signature, Error> {
frost::aggregate_spark(signing_package, signature_shares, verifying_key)
}

/// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256).
pub type SigningKey = frost_core::SigningKey<S>;

Expand Down
30 changes: 30 additions & 0 deletions frost-secp256k1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,27 @@ pub mod round2 {
) -> Result<SignatureShare, Error> {
frost::round2::sign(signing_package, signer_nonces, key_package)
}

/// Spark custom signing operation.
pub fn sign_spark(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
inner_coef_set_x: &std::collections::BTreeSet<Identifier>,
outer_coef_set_x: Option<&std::collections::BTreeSet<Identifier>>,
outer_signer_id: Option<&Identifier>,
verifiying_key: &VerifyingKey,
) -> Result<SignatureShare, Error> {
frost::round2::sign_spark(
signing_package,
signer_nonces,
key_package,
inner_coef_set_x,
outer_coef_set_x,
outer_signer_id,
verifiying_key,
)
}
}

/// A Schnorr signature on FROST(secp256k1, SHA-256).
Expand Down Expand Up @@ -446,6 +467,15 @@ pub fn aggregate(
frost::aggregate(signing_package, signature_shares, pubkeys)
}

/// Spark
pub fn aggregate_spark(
signing_package: &SigningPackage,
signature_shares: &BTreeMap<Identifier, round2::SignatureShare>,
verifying_key: &VerifyingKey,
) -> Result<Signature, Error> {
frost::aggregate_spark(signing_package, signature_shares, verifying_key)
}

/// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256).
pub type SigningKey = frost_core::SigningKey<S>;

Expand Down
Loading