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

merge queue: embarking main (958fde3) and #730 together #775

Closed
wants to merge 15 commits into from
Closed
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add frost-secp256k1-tr crate and ciphersuite
Co-authored by @zebra-lucky and @mimoo

This work sponsored by dlcbtc.com and lightspark.com
conduition committed Oct 4, 2024
commit f9264387e471fa90d70d0c3a68e992d3443e5497
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ members = [
"frost-p256",
"frost-ristretto255",
"frost-secp256k1",
"frost-secp256k1-tr",
"frost-rerandomized",
"gencode"
]
3 changes: 2 additions & 1 deletion frost-core/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Schnorr signatures over prime order groups (or subgroups)

use alloc::{string::ToString, vec::Vec};
use derive_getters::Getters;

use crate::{Ciphersuite, Element, Error, Field, Group, Scalar};

/// A Schnorr signature over some prime order group (or subgroup).
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq, Getters)]
pub struct Signature<C: Ciphersuite> {
/// The commitment `R` to the signature nonce.
pub(crate) R: Element<C>,
67 changes: 67 additions & 0 deletions frost-secp256k1-tr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[package]
name = "frost-secp256k1-tr"
edition = "2021"
# When releasing to crates.io:
# - Update CHANGELOG.md
# - Create git tag.
version = "2.0.0-rc.0"
authors = [
"Deirdre Connolly <durumcrustulum@gmail.com>",
"Chelsea Komlo <me@chelseakomlo.com>",
"Conrado Gouvea <conradoplg@gmail.com>"
]
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ZcashFoundation/frost"
categories = ["cryptography"]
keywords = ["cryptography", "crypto", "threshold", "signature"]
description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot."

[package.metadata.docs.rs]
features = ["serde"]
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
document-features = "0.2.7"
frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false }
frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false }
k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false }
serde = { version = "1.0.160", features = ["derive"], optional = true }
rand_core = "0.6"
sha2 = { version = "0.10.2", default-features = false }

[dev-dependencies]
criterion = "0.5"
frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] }
frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] }
insta = { version = "1.31.0", features = ["yaml"] }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
lazy_static = "1.4"
proptest = "1.0"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"

[features]
nightly = []
default = ["serialization", "cheater-detection", "std"]
#! ## Features
## Enable standard library support.
std = ["frost-core/std"]
## Enable `serde` support for types that need to be communicated. You
## can use `serde` to serialize structs with any encoder that supports
## `serde` (e.g. JSON with `serde_json`).
serde = ["frost-core/serde", "dep:serde"]
## Enable a default serialization format. Enables `serde`.
serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"]
## Enable cheater detection
cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"]

[lib]
# Disables non-criterion benchmark which is not used; prevents errors
# when using criterion-specific flags
bench = false

[[bench]]
name = "bench"
harness = false
121 changes: 121 additions & 0 deletions frost-secp256k1-tr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
An implementation of Schnorr signatures on the secp256k1 curve for both single and threshold numbers
of signers (FROST).

## Example: key generation with trusted dealer and FROST signing

Creating a key with a trusted dealer and splitting into shares; then signing a message
and aggregating the signature. Note that the example just simulates a distributed
scenario in a single thread and it abstracts away any communication between peers.


```rust
# // ANCHOR: tkg_gen
use frost_secp256k1_tr as frost;
use rand::thread_rng;
use std::collections::BTreeMap;

let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(
max_signers,
min_signers,
frost::keys::IdentifierList::Default,
&mut rng,
)?;
# // ANCHOR_END: tkg_gen

// Verifies the secret shares from the dealer and store them in a BTreeMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: BTreeMap<_, _> = BTreeMap::new();

for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}

let mut nonces_map = BTreeMap::new();
let mut commitments_map = BTreeMap::new();

////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////

// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..=min_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
key_package.signing_share(),
&mut rng,
);
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}

// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = BTreeMap::new();
# // ANCHOR: round2_package
let message = "message to sign".as_bytes();
# // In practice, the SigningPackage must be sent to all participants
# // involved in the current signing (at least min_signers participants),
# // using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_map, message);
# // ANCHOR_END: round2_package

////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////

// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];

let nonces = &nonces_map[participant_identifier];

// Each participant generates their signature share.
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign

// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
signature_shares.insert(*participant_identifier, signature_share);
}

////////////////////////////////////////////////////////////////////////////
// Aggregation: collects the signing shares from all participants,
// generates the final signature.
////////////////////////////////////////////////////////////////////////////

// Aggregate (also verifies the signature shares)
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
# // ANCHOR_END: aggregate


// Check that the threshold signature can be verified by the group public
// key (the verification key).
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.verifying_key()
.verify(message, &group_signature)
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);

# Ok::<(), frost::Error>(())
```
168 changes: 168 additions & 0 deletions frost-secp256k1-tr/dkg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Distributed Key Generation (DKG)

The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.

Before starting, each participant needs an unique identifier, which can be built from
a `u16`. The process in which these identifiers are allocated is up to the application.

The distributed key generation process has 3 parts, with 2 communication rounds
between them, in which each participant needs to send a "package" to every other
participant. In the first round, each participant sends the same package
(a [`round1::Package`]) to every other. In the second round, each receiver gets
their own package (a [`round2::Package`]).

Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`]
that MUST be kept secret. Between part 2 and 3, each participant needs to hold
onto a [`round2::SecretPackage`].

After the third part, each participant will get a [`KeyPackage`] with their
long-term secret share that must be kept secret, and a [`PublicKeyPackage`]
that is public (and will be the same between all participants). With those
they can proceed to sign messages with FROST.


## Example

```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::BTreeMap;

use frost_secp256k1_tr as frost;

let mut rng = thread_rng();

let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import

////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////

// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round1_secret_packages = BTreeMap::new();

// Keep track of all round 1 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round1_packages = BTreeMap::new();

// For each participant, perform the first part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1

// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, round1_secret_package);

// "Send" the round 1 package to all other participants. In this
// test this is simulated using a BTreeMap; in practice this will be
// sent through some communication channel.
for receiver_participant_index in 1..=max_signers {
if receiver_participant_index == participant_index {
continue;
}
let receiver_participant_identifier: frost::Identifier = receiver_participant_index
.try_into()
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(BTreeMap::new)
.insert(participant_identifier, round1_package.clone());
}
}

////////////////////////////////////////////////////////////////////////////
// Key generation, Round 2
////////////////////////////////////////////////////////////////////////////

// Keep track of each participant's round 2 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round2_secret_packages = BTreeMap::new();

// Keep track of all round 2 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round2_packages = BTreeMap::new();

// For each participant, perform the second part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2

// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round2_secret_packages.insert(participant_identifier, round2_secret_package);

// "Send" the round 2 package to all other participants. In this
// test this is simulated using a BTreeMap; in practice this will be
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for (receiver_identifier, round2_package) in round2_packages {
received_round2_packages
.entry(receiver_identifier)
.or_insert_with(BTreeMap::new)
.insert(participant_identifier, round2_package);
}
}

////////////////////////////////////////////////////////////////////////////
// Key generation, final computation
////////////////////////////////////////////////////////////////////////////

// Keep track of each participant's long-lived key package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut key_packages = BTreeMap::new();

// Keep track of each participant's public key package.
// In practice, if there is a Coordinator, only they need to store the set.
// If there is not, then all candidates must store their own sets.
// All participants will have the same exact public key package.
let mut pubkey_packages = BTreeMap::new();

// For each participant, perform the third part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package);
}

// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```
103 changes: 103 additions & 0 deletions frost-secp256k1-tr/src/keys/dkg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#![doc = include_str!("../../dkg.md")]
use super::*;

/// DKG Round 1 structures.
pub mod round1 {
use super::*;

/// The secret package that must be kept in memory by the participant
/// between the first and second parts of the DKG protocol (round 1).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type SecretPackage = frost::keys::dkg::round1::SecretPackage<S>;

/// The package that must be broadcast by each participant to all other participants
/// between the first and second parts of the DKG protocol (round 1).
pub type Package = frost::keys::dkg::round1::Package<S>;
}

/// DKG Round 2 structures.
pub mod round2 {
use super::*;

/// The secret package that must be kept in memory by the participant
/// between the second and third parts of the DKG protocol (round 2).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type SecretPackage = frost::keys::dkg::round2::SecretPackage<S>;

/// A package that must be sent by each participant to some other participants
/// in Round 2 of the DKG protocol. Note that there is one specific package
/// for each specific recipient, in contrast to Round 1.
///
/// # Security
///
/// The package must be sent on an *confidential* and *authenticated* channel.
pub type Package = frost::keys::dkg::round2::Package<S>;
}

/// Performs the first part of the distributed key generation protocol
/// for the given participant.
///
/// It returns the [`round1::SecretPackage`] that must be kept in memory
/// by the participant for the other steps, and the [`round1::Package`] that
/// must be sent to each other participant in the DKG run.
pub fn part1<R: RngCore + CryptoRng>(
identifier: Identifier,
max_signers: u16,
min_signers: u16,
mut rng: R,
) -> Result<(round1::SecretPackage, round1::Package), Error> {
frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng)
}

/// Performs the second part of the distributed key generation protocol for the
/// participant holding the given [`round1::SecretPackage`], given the received
/// [`round1::Package`]s received from the other participants.
///
/// `round1_packages` maps the identifier of each other participant to the
/// [`round1::Package`] they sent to the current participant (the owner of
/// `secret_package`). These identifiers must come from whatever mapping the
/// coordinator has between communication channels and participants, i.e. they
/// must have assurance that the [`round1::Package`] came from the participant
/// with that identifier.
///
/// It returns the [`round2::SecretPackage`] that must be kept in memory by the
/// participant for the final step, and the map of [`round2::Package`]s that
/// must be sent to each other participant who has the given identifier in the
/// map key.
pub fn part2(
secret_package: round1::SecretPackage,
round1_packages: &BTreeMap<Identifier, round1::Package>,
) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> {
frost::keys::dkg::part2(secret_package, round1_packages)
}

/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`round2::SecretPackage`], given the
/// received [`round1::Package`]s and [`round2::Package`]s received from the
/// other participants.
///
/// `round1_packages` must be the same used in [`part2()`].
///
/// `round2_packages` maps the identifier of each other participant to the
/// [`round2::Package`] they sent to the current participant (the owner of
/// `secret_package`). These identifiers must come from whatever mapping the
/// coordinator has between communication channels and participants, i.e. they
/// must have assurance that the [`round2::Package`] came from the participant
/// with that identifier.
///
/// It returns the [`KeyPackage`] that has the long-lived key share for the
/// participant, and the [`PublicKeyPackage`]s that has public information about
/// all participants; both of which are required to compute FROST signatures.
pub fn part3(
round2_secret_package: &round2::SecretPackage,
round1_packages: &BTreeMap<Identifier, round1::Package>,
round2_packages: &BTreeMap<Identifier, round2::Package>,
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages)
}
35 changes: 35 additions & 0 deletions frost-secp256k1-tr/src/keys/refresh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Refresh Shares
//!
//! Implements the functionality to refresh a share. This requires the participation
//! of all the remaining signers. This can be done using a Trusted Dealer or
//! DKG (not yet implemented)
use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore};
use alloc::vec::Vec;

use super::{KeyPackage, PublicKeyPackage, SecretShare};

/// Refreshes shares using a trusted dealer
pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>(
old_pub_key_package: PublicKeyPackage,
max_signers: u16,
min_signers: u16,
identifiers: &[Identifier],
mut rng: &mut R,
) -> Result<(Vec<SecretShare>, PublicKeyPackage), Error> {
frost::keys::refresh::compute_refreshing_shares(
old_pub_key_package,
max_signers,
min_signers,
identifiers,
&mut rng,
)
}

/// Each participant refreshed their shares
pub fn refresh_share<C: Ciphersuite>(
zero_share: SecretShare,
current_share: &KeyPackage,
) -> Result<KeyPackage, Error> {
frost::keys::refresh::refresh_share(zero_share, current_share)
}
101 changes: 101 additions & 0 deletions frost-secp256k1-tr/src/keys/repairable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Repairable Threshold Scheme
//!
//! Implements the Repairable Threshold Scheme (RTS) from <https://eprint.iacr.org/2017/1155>.
//! The RTS is used to help a signer (participant) repair their lost share. This is achieved
//! using a subset of the other signers know here as `helpers`.
use alloc::collections::BTreeMap;

// This is imported separately to make `gencode` work.
// (if it were below, the position of the import would vary between ciphersuites
// after `cargo fmt`)
use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar};
use crate::{Error, Secp256K1Sha256};

use super::{SecretShare, VerifiableSecretSharingCommitment};

/// Step 1 of RTS.
///
/// Generates the "delta" values from `helper_i` to help `participant` recover their share
/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i`
/// is the share of `helper_i`.
///
/// Returns a BTreeMap mapping which value should be sent to which participant.
pub fn repair_share_step_1<C: Ciphersuite, R: RngCore + CryptoRng>(
helpers: &[Identifier],
share_i: &SecretShare,
rng: &mut R,
participant: Identifier,
) -> Result<BTreeMap<Identifier, Scalar>, Error> {
frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant)
}

/// Step 2 of RTS.
///
/// Generates the `sigma` values from all `deltas` received from `helpers`
/// to help `participant` recover their share.
/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`.
///
/// Returns a scalar
pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar {
frost::keys::repairable::repair_share_step_2::<Secp256K1Sha256>(deltas_j)
}

/// Step 3 of RTS
///
/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare`
/// is made up of the `identifier`and `commitment` of the `participant` as well as the
/// `value` which is the `SigningShare`.
pub fn repair_share_step_3(
sigmas: &[Scalar],
identifier: Identifier,
commitment: &VerifiableSecretSharingCommitment,
) -> SecretShare {
frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment)
}

#[cfg(test)]
mod tests {

use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;

use crate::Secp256K1Sha256;

lazy_static! {
pub static ref REPAIR_SHARE: Value =
serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim())
.unwrap();
}

#[test]
fn check_repair_share_step_1() {
let rng = thread_rng();

frost_core::tests::repairable::check_repair_share_step_1::<Secp256K1Sha256, _>(rng);
}

#[test]
fn check_repair_share_step_2() {
frost_core::tests::repairable::check_repair_share_step_2::<Secp256K1Sha256>(&REPAIR_SHARE);
}

#[test]
fn check_repair_share_step_3() {
let rng = thread_rng();
frost_core::tests::repairable::check_repair_share_step_3::<Secp256K1Sha256, _>(
rng,
&REPAIR_SHARE,
);
}

#[test]
fn check_repair_share_step_1_fails_with_invalid_min_signers() {
let rng = thread_rng();
frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::<
Secp256K1Sha256,
_,
>(rng);
}
}
755 changes: 755 additions & 0 deletions frost-secp256k1-tr/src/lib.rs

Large diffs are not rendered by default.