Skip to content

Commit f926438

Browse files
committed
add frost-secp256k1-tr crate and ciphersuite
Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com
1 parent 4cc535c commit f926438

File tree

9 files changed

+1353
-1
lines changed

9 files changed

+1353
-1
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"frost-p256",
88
"frost-ristretto255",
99
"frost-secp256k1",
10+
"frost-secp256k1-tr",
1011
"frost-rerandomized",
1112
"gencode"
1213
]

frost-core/src/signature.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! Schnorr signatures over prime order groups (or subgroups)
22
33
use alloc::{string::ToString, vec::Vec};
4+
use derive_getters::Getters;
45

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

78
/// A Schnorr signature over some prime order group (or subgroup).
8-
#[derive(Copy, Clone, Eq, PartialEq)]
9+
#[derive(Copy, Clone, Eq, PartialEq, Getters)]
910
pub struct Signature<C: Ciphersuite> {
1011
/// The commitment `R` to the signature nonce.
1112
pub(crate) R: Element<C>,

frost-secp256k1-tr/Cargo.toml

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[package]
2+
name = "frost-secp256k1-tr"
3+
edition = "2021"
4+
# When releasing to crates.io:
5+
# - Update CHANGELOG.md
6+
# - Create git tag.
7+
version = "2.0.0-rc.0"
8+
authors = [
9+
"Deirdre Connolly <[email protected]>",
10+
"Chelsea Komlo <[email protected]>",
11+
"Conrado Gouvea <[email protected]>"
12+
]
13+
readme = "README.md"
14+
license = "MIT OR Apache-2.0"
15+
repository = "https://github.com/ZcashFoundation/frost"
16+
categories = ["cryptography"]
17+
keywords = ["cryptography", "crypto", "threshold", "signature"]
18+
description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot."
19+
20+
[package.metadata.docs.rs]
21+
features = ["serde"]
22+
rustdoc-args = ["--cfg", "docsrs"]
23+
24+
[dependencies]
25+
document-features = "0.2.7"
26+
frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false }
27+
frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false }
28+
k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false }
29+
serde = { version = "1.0.160", features = ["derive"], optional = true }
30+
rand_core = "0.6"
31+
sha2 = { version = "0.10.2", default-features = false }
32+
33+
[dev-dependencies]
34+
criterion = "0.5"
35+
frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] }
36+
frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] }
37+
insta = { version = "1.31.0", features = ["yaml"] }
38+
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
39+
lazy_static = "1.4"
40+
proptest = "1.0"
41+
rand = "0.8"
42+
rand_chacha = "0.3"
43+
serde_json = "1.0"
44+
45+
[features]
46+
nightly = []
47+
default = ["serialization", "cheater-detection", "std"]
48+
#! ## Features
49+
## Enable standard library support.
50+
std = ["frost-core/std"]
51+
## Enable `serde` support for types that need to be communicated. You
52+
## can use `serde` to serialize structs with any encoder that supports
53+
## `serde` (e.g. JSON with `serde_json`).
54+
serde = ["frost-core/serde", "dep:serde"]
55+
## Enable a default serialization format. Enables `serde`.
56+
serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"]
57+
## Enable cheater detection
58+
cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"]
59+
60+
[lib]
61+
# Disables non-criterion benchmark which is not used; prevents errors
62+
# when using criterion-specific flags
63+
bench = false
64+
65+
[[bench]]
66+
name = "bench"
67+
harness = false

frost-secp256k1-tr/README.md

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
An implementation of Schnorr signatures on the secp256k1 curve for both single and threshold numbers
2+
of signers (FROST).
3+
4+
## Example: key generation with trusted dealer and FROST signing
5+
6+
Creating a key with a trusted dealer and splitting into shares; then signing a message
7+
and aggregating the signature. Note that the example just simulates a distributed
8+
scenario in a single thread and it abstracts away any communication between peers.
9+
10+
11+
```rust
12+
# // ANCHOR: tkg_gen
13+
use frost_secp256k1_tr as frost;
14+
use rand::thread_rng;
15+
use std::collections::BTreeMap;
16+
17+
let mut rng = thread_rng();
18+
let max_signers = 5;
19+
let min_signers = 3;
20+
let (shares, pubkey_package) = frost::keys::generate_with_dealer(
21+
max_signers,
22+
min_signers,
23+
frost::keys::IdentifierList::Default,
24+
&mut rng,
25+
)?;
26+
# // ANCHOR_END: tkg_gen
27+
28+
// Verifies the secret shares from the dealer and store them in a BTreeMap.
29+
// In practice, the KeyPackages must be sent to its respective participants
30+
// through a confidential and authenticated channel.
31+
let mut key_packages: BTreeMap<_, _> = BTreeMap::new();
32+
33+
for (identifier, secret_share) in shares {
34+
# // ANCHOR: tkg_verify
35+
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
36+
# // ANCHOR_END: tkg_verify
37+
key_packages.insert(identifier, key_package);
38+
}
39+
40+
let mut nonces_map = BTreeMap::new();
41+
let mut commitments_map = BTreeMap::new();
42+
43+
////////////////////////////////////////////////////////////////////////////
44+
// Round 1: generating nonces and signing commitments for each participant
45+
////////////////////////////////////////////////////////////////////////////
46+
47+
// In practice, each iteration of this loop will be executed by its respective participant.
48+
for participant_index in 1..=min_signers {
49+
let participant_identifier = participant_index.try_into().expect("should be nonzero");
50+
let key_package = &key_packages[&participant_identifier];
51+
// Generate one (1) nonce and one SigningCommitments instance for each
52+
// participant, up to _threshold_.
53+
# // ANCHOR: round1_commit
54+
let (nonces, commitments) = frost::round1::commit(
55+
key_package.signing_share(),
56+
&mut rng,
57+
);
58+
# // ANCHOR_END: round1_commit
59+
// In practice, the nonces must be kept by the participant to use in the
60+
// next round, while the commitment must be sent to the coordinator
61+
// (or to every other participant if there is no coordinator) using
62+
// an authenticated channel.
63+
nonces_map.insert(participant_identifier, nonces);
64+
commitments_map.insert(participant_identifier, commitments);
65+
}
66+
67+
// This is what the signature aggregator / coordinator needs to do:
68+
// - decide what message to sign
69+
// - take one (unused) commitment per signing participant
70+
let mut signature_shares = BTreeMap::new();
71+
# // ANCHOR: round2_package
72+
let message = "message to sign".as_bytes();
73+
# // In practice, the SigningPackage must be sent to all participants
74+
# // involved in the current signing (at least min_signers participants),
75+
# // using an authenticate channel (and confidential if the message is secret).
76+
let signing_package = frost::SigningPackage::new(commitments_map, message);
77+
# // ANCHOR_END: round2_package
78+
79+
////////////////////////////////////////////////////////////////////////////
80+
// Round 2: each participant generates their signature share
81+
////////////////////////////////////////////////////////////////////////////
82+
83+
// In practice, each iteration of this loop will be executed by its respective participant.
84+
for participant_identifier in nonces_map.keys() {
85+
let key_package = &key_packages[participant_identifier];
86+
87+
let nonces = &nonces_map[participant_identifier];
88+
89+
// Each participant generates their signature share.
90+
# // ANCHOR: round2_sign
91+
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
92+
# // ANCHOR_END: round2_sign
93+
94+
// In practice, the signature share must be sent to the Coordinator
95+
// using an authenticated channel.
96+
signature_shares.insert(*participant_identifier, signature_share);
97+
}
98+
99+
////////////////////////////////////////////////////////////////////////////
100+
// Aggregation: collects the signing shares from all participants,
101+
// generates the final signature.
102+
////////////////////////////////////////////////////////////////////////////
103+
104+
// Aggregate (also verifies the signature shares)
105+
# // ANCHOR: aggregate
106+
let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
107+
# // ANCHOR_END: aggregate
108+
109+
110+
// Check that the threshold signature can be verified by the group public
111+
// key (the verification key).
112+
# // ANCHOR: verify
113+
let is_signature_valid = pubkey_package
114+
.verifying_key()
115+
.verify(message, &group_signature)
116+
.is_ok();
117+
# // ANCHOR_END: verify
118+
assert!(is_signature_valid);
119+
120+
# Ok::<(), frost::Error>(())
121+
```

frost-secp256k1-tr/dkg.md

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Distributed Key Generation (DKG)
2+
3+
The DKG module supports generating FROST key shares in a distributed manner,
4+
without a trusted dealer.
5+
6+
Before starting, each participant needs an unique identifier, which can be built from
7+
a `u16`. The process in which these identifiers are allocated is up to the application.
8+
9+
The distributed key generation process has 3 parts, with 2 communication rounds
10+
between them, in which each participant needs to send a "package" to every other
11+
participant. In the first round, each participant sends the same package
12+
(a [`round1::Package`]) to every other. In the second round, each receiver gets
13+
their own package (a [`round2::Package`]).
14+
15+
Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`]
16+
that MUST be kept secret. Between part 2 and 3, each participant needs to hold
17+
onto a [`round2::SecretPackage`].
18+
19+
After the third part, each participant will get a [`KeyPackage`] with their
20+
long-term secret share that must be kept secret, and a [`PublicKeyPackage`]
21+
that is public (and will be the same between all participants). With those
22+
they can proceed to sign messages with FROST.
23+
24+
25+
## Example
26+
27+
```rust
28+
# // ANCHOR: dkg_import
29+
use rand::thread_rng;
30+
use std::collections::BTreeMap;
31+
32+
use frost_secp256k1_tr as frost;
33+
34+
let mut rng = thread_rng();
35+
36+
let max_signers = 5;
37+
let min_signers = 3;
38+
# // ANCHOR_END: dkg_import
39+
40+
////////////////////////////////////////////////////////////////////////////
41+
// Key generation, Round 1
42+
////////////////////////////////////////////////////////////////////////////
43+
44+
// Keep track of each participant's round 1 secret package.
45+
// In practice each participant will keep its copy; no one
46+
// will have all the participant's packages.
47+
let mut round1_secret_packages = BTreeMap::new();
48+
49+
// Keep track of all round 1 packages sent to the given participant.
50+
// This is used to simulate the broadcast; in practice the packages
51+
// will be sent through some communication channel.
52+
let mut received_round1_packages = BTreeMap::new();
53+
54+
// For each participant, perform the first part of the DKG protocol.
55+
// In practice, each participant will perform this on their own environments.
56+
for participant_index in 1..=max_signers {
57+
let participant_identifier = participant_index.try_into().expect("should be nonzero");
58+
# // ANCHOR: dkg_part1
59+
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
60+
participant_identifier,
61+
max_signers,
62+
min_signers,
63+
&mut rng,
64+
)?;
65+
# // ANCHOR_END: dkg_part1
66+
67+
// Store the participant's secret package for later use.
68+
// In practice each participant will store it in their own environment.
69+
round1_secret_packages.insert(participant_identifier, round1_secret_package);
70+
71+
// "Send" the round 1 package to all other participants. In this
72+
// test this is simulated using a BTreeMap; in practice this will be
73+
// sent through some communication channel.
74+
for receiver_participant_index in 1..=max_signers {
75+
if receiver_participant_index == participant_index {
76+
continue;
77+
}
78+
let receiver_participant_identifier: frost::Identifier = receiver_participant_index
79+
.try_into()
80+
.expect("should be nonzero");
81+
received_round1_packages
82+
.entry(receiver_participant_identifier)
83+
.or_insert_with(BTreeMap::new)
84+
.insert(participant_identifier, round1_package.clone());
85+
}
86+
}
87+
88+
////////////////////////////////////////////////////////////////////////////
89+
// Key generation, Round 2
90+
////////////////////////////////////////////////////////////////////////////
91+
92+
// Keep track of each participant's round 2 secret package.
93+
// In practice each participant will keep its copy; no one
94+
// will have all the participant's packages.
95+
let mut round2_secret_packages = BTreeMap::new();
96+
97+
// Keep track of all round 2 packages sent to the given participant.
98+
// This is used to simulate the broadcast; in practice the packages
99+
// will be sent through some communication channel.
100+
let mut received_round2_packages = BTreeMap::new();
101+
102+
// For each participant, perform the second part of the DKG protocol.
103+
// In practice, each participant will perform this on their own environments.
104+
for participant_index in 1..=max_signers {
105+
let participant_identifier = participant_index.try_into().expect("should be nonzero");
106+
let round1_secret_package = round1_secret_packages
107+
.remove(&participant_identifier)
108+
.unwrap();
109+
let round1_packages = &received_round1_packages[&participant_identifier];
110+
# // ANCHOR: dkg_part2
111+
let (round2_secret_package, round2_packages) =
112+
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
113+
# // ANCHOR_END: dkg_part2
114+
115+
// Store the participant's secret package for later use.
116+
// In practice each participant will store it in their own environment.
117+
round2_secret_packages.insert(participant_identifier, round2_secret_package);
118+
119+
// "Send" the round 2 package to all other participants. In this
120+
// test this is simulated using a BTreeMap; in practice this will be
121+
// sent through some communication channel.
122+
// Note that, in contrast to the previous part, here each other participant
123+
// gets its own specific package.
124+
for (receiver_identifier, round2_package) in round2_packages {
125+
received_round2_packages
126+
.entry(receiver_identifier)
127+
.or_insert_with(BTreeMap::new)
128+
.insert(participant_identifier, round2_package);
129+
}
130+
}
131+
132+
////////////////////////////////////////////////////////////////////////////
133+
// Key generation, final computation
134+
////////////////////////////////////////////////////////////////////////////
135+
136+
// Keep track of each participant's long-lived key package.
137+
// In practice each participant will keep its copy; no one
138+
// will have all the participant's packages.
139+
let mut key_packages = BTreeMap::new();
140+
141+
// Keep track of each participant's public key package.
142+
// In practice, if there is a Coordinator, only they need to store the set.
143+
// If there is not, then all candidates must store their own sets.
144+
// All participants will have the same exact public key package.
145+
let mut pubkey_packages = BTreeMap::new();
146+
147+
// For each participant, perform the third part of the DKG protocol.
148+
// In practice, each participant will perform this on their own environments.
149+
for participant_index in 1..=max_signers {
150+
let participant_identifier = participant_index.try_into().expect("should be nonzero");
151+
let round2_secret_package = &round2_secret_packages[&participant_identifier];
152+
let round1_packages = &received_round1_packages[&participant_identifier];
153+
let round2_packages = &received_round2_packages[&participant_identifier];
154+
# // ANCHOR: dkg_part3
155+
let (key_package, pubkey_package) = frost::keys::dkg::part3(
156+
round2_secret_package,
157+
round1_packages,
158+
round2_packages,
159+
)?;
160+
# // ANCHOR_END: dkg_part3
161+
key_packages.insert(participant_identifier, key_package);
162+
pubkey_packages.insert(participant_identifier, pubkey_package);
163+
}
164+
165+
// With its own key package and the pubkey package, each participant can now proceed
166+
// to sign with FROST.
167+
# Ok::<(), frost::Error>(())
168+
```

0 commit comments

Comments
 (0)