frost: incorporate recent fixes; add into_positive_y() to redpallas (#68)

* expose dkg module

* fix dkg compiling issues

* incorporate frost repo fixes; add into_positive_y() for RedPallas

* don't use all features in MSRV test

* remove unneeded frost-rerandomized import in dev-dependencies

* bump frost-rerandomized rev

* update to frost-rerandomized 0.7.0

* commit lockfile; update CI test to match
This commit is contained in:
Conrado Gouvea 2023-10-03 17:12:01 -03:00 committed by GitHub
parent b1bbad7bac
commit ac524000e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1878 additions and 120 deletions

View File

@ -4,46 +4,30 @@ on: [push]
jobs:
test_msrv:
name: test on MSRV
name: build on MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.3
- uses: actions-rs/toolchain@v1.0.7
with:
# When toolchain is not specified, it uses rust-toolchain, which is the MSRV
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: test
args: --all-features
- uses: actions/checkout@v4.0.0
- uses: dtolnay/rust-toolchain@1.65.0
# Don't use --all-features because `frost` has a higher MSRV and it's non-default.
# Also don't run tests because some dev-dependencies have higher MSRVs.
- run: cargo build
test_nightly:
name: test on nightly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.3
# Because we use nightly features for building docs,
# using --all-features will fail without nightly toolchain.
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: nightly
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: test
args: --all-features
- uses: actions/checkout@v4.0.0
- uses: dtolnay/rust-toolchain@nightly
# Update dependencies since we commit the lockfile
- run: cargo update --verbose
- run: cargo test --all-features
build_no_std:
name: build with no_std
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.3
- uses: actions-rs/toolchain@v1.0.7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
override: true
# This does not support std, so we use to test if no_std works
target: thumbv6m-none-eabi
- uses: actions-rs/cargo@v1.0.3
with:
command: build
# Disables std feature
args: --no-default-features --target thumbv6m-none-eabi
targets: thumbv6m-none-eabi
- run: cargo build --no-default-features --target thumbv6m-none-eabi

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
*~

1488
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,8 @@ edition = "2021"
rust-version = "1.65"
# When releasing to crates.io:
# - Update CHANGELOG.md
# - Double check if the MSRV above (rust-version field) is equal to the version
# used in main.yml `test_msrv`
# - Create git tag.
version = "0.5.1"
authors = [
@ -33,7 +35,7 @@ pasta_curves = { version = "0.5", default-features = false }
rand_core = { version = "0.6", default-features = false }
serde = { version = "1", optional = true, features = ["derive"] }
thiserror = { version = "1.0", optional = true }
frost-rerandomized = { version = "0.6.0", optional = true }
frost-rerandomized = { version = "0.7.0", optional = true }
[dependencies.zeroize]
version = "1"
@ -50,9 +52,9 @@ proptest = "1.0"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"
frost-rerandomized = { version = "0.6.0", features=["test-impl"] }
num-bigint = "0.4.3"
num-traits = "0.2.15"
frost-rerandomized = { version = "0.7.0", features = ["test-impl"] }
# `alloc` is only used in test code
[dev-dependencies.pasta_curves]
@ -66,6 +68,7 @@ std = ["blake2b_simd/std", "thiserror", "zeroize", "alloc",
alloc = ["hex"]
nightly = []
frost = ["std", "frost-rerandomized"]
serde = ["dep:serde", "frost-rerandomized?/serde"]
default = ["std"]
[[bench]]

View File

@ -1 +0,0 @@
1.65.0

View File

@ -8,10 +8,13 @@ use group::GroupEncoding;
#[cfg(feature = "alloc")]
use group::{ff::Field as FFField, ff::PrimeField};
use frost_rerandomized::{
frost_core::{frost, Ciphersuite, Field, FieldError, Group, GroupError},
RandomizedParams,
// Re-exports in our public API
#[cfg(feature = "serde")]
pub use frost_rerandomized::frost_core::serde;
pub use frost_rerandomized::frost_core::{
frost, Ciphersuite, Field, FieldError, Group, GroupError,
};
pub use rand_core;
use rand_core::{CryptoRng, RngCore};
@ -170,6 +173,15 @@ impl Ciphersuite for JubjubBlake2b512 {
.finalize(),
)
}
/// HID for FROST(Jubjub, BLAKE2b-512)
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(
HStar::<sapling::SpendAuth>::new(b"FROST_RedJubjubI")
.update(m)
.finalize(),
)
}
}
// Shorthand alias for the ciphersuite
@ -198,8 +210,24 @@ pub mod keys {
frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng)
}
/// Splits an existing key into FROST shares.
///
/// This is identical to [`generate_with_dealer`] but receives an existing key
/// instead of generating a fresh one. This is useful in scenarios where
/// the key needs to be generated externally or must be derived from e.g. a
/// seed phrase.
pub fn split<R: RngCore + CryptoRng>(
key: &SigningKey,
max_signers: u16,
min_signers: u16,
identifiers: IdentifierList,
rng: &mut R,
) -> Result<(HashMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
frost::keys::split(key, max_signers, min_signers, identifiers, rng)
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
/// [`generate_with_dealer`].
///
/// # Security
///
@ -207,6 +235,12 @@ pub mod keys {
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<J>;
/// A secret scalar value representing a signer's share of the group secret.
pub type SigningShare = frost::keys::SigningShare<J>;
/// A public group element that represents a single signer's public verification share.
pub type VerifyingShare = frost::keys::VerifyingShare<J>;
/// A FROST(Jubjub, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
@ -220,6 +254,23 @@ pub mod keys {
///
/// Used for verification purposes before publishing a signature.
pub type PublicKeyPackage = frost::keys::PublicKeyPackage<J>;
/// 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.
pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment<J>;
pub mod dkg;
pub mod repairable;
}
/// FROST(Jubjub, BLAKE2b-512) Round 1 functionality and types.
@ -240,6 +291,9 @@ pub mod round1 {
/// SigningCommitment can be used for exactly *one* signature.
pub type SigningCommitments = frost::round1::SigningCommitments<J>;
/// A commitment to a signing nonce share.
pub type NonceCommitment = frost::round1::NonceCommitment<J>;
/// Performed once by each participant selected for the signing operation.
///
/// Generates the signing nonces and commitments to be used in the signing
@ -267,9 +321,8 @@ pub mod round2 {
/// shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<J>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<J>;
/// A randomizer. A random scalar which is used to randomize the key.
pub type Randomizer = frost_rerandomized::Randomizer<J>;
/// Performed once by each participant selected for the signing operation.
///
@ -283,20 +336,18 @@ pub mod round2 {
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
randomizer_point: &<<J as Ciphersuite>::Group as Group>::Element,
randomizer: Randomizer,
) -> Result<SignatureShare, Error> {
frost_rerandomized::sign(
signing_package,
signer_nonces,
key_package,
randomizer_point,
)
frost_rerandomized::sign(signing_package, signer_nonces, key_package, randomizer)
}
}
/// A Schnorr signature on FROST(Jubjub, BLAKE2b-512).
pub type Signature = frost_rerandomized::frost_core::Signature<J>;
/// Randomized parameters for a signing instance of randomized FROST.
pub type RandomizedParams = frost_rerandomized::RandomizedParams<J>;
/// Verifies each FROST(Jubjub, BLAKE2b-512) participant's signature share, and if all are valid,
/// aggregates the shares into a signature to publish.
///
@ -313,10 +364,10 @@ pub type Signature = frost_rerandomized::frost_core::Signature<J>;
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.
pub fn aggregate(
signing_package: &round2::SigningPackage,
signing_package: &SigningPackage,
signature_shares: &HashMap<Identifier, round2::SignatureShare>,
pubkeys: &keys::PublicKeyPackage,
randomized_params: &RandomizedParams<J>,
randomized_params: &RandomizedParams,
) -> Result<Signature, Error> {
frost_rerandomized::aggregate(
signing_package,

View File

@ -3,7 +3,7 @@
The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.
Before starting, each participant needs a unique identifier, which can be built from
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
@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use reddsa::frost::redjubjub as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// 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.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// 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 (secret_package, round1_package) = frost::keys::dkg::part1(
# // 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, secret_package);
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 HashMap; in practice this will be
@ -76,8 +80,8 @@ for participant_index in 1..=max_signers {
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(Vec::new)
.push(round1_package.clone());
.or_insert_with(HashMap::new)
.insert(participant_identifier, round1_package.clone());
}
}
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// 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_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
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.
@ -115,11 +121,11 @@ for participant_index in 1..=max_signers {
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for round2_package in round2_packages {
for (receiver_identifier, round2_package) in round2_packages {
received_round2_packages
.entry(round2_package.receiver_identifier)
.or_insert_with(Vec::new)
.push(round2_package);
.entry(receiver_identifier)
.or_insert_with(HashMap::new)
.insert(participant_identifier, round2_package);
}
}
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// 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 (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
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_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -64,8 +64,8 @@ pub fn part1<R: RngCore + CryptoRng>(
/// must be sent to other participants.
pub fn part2(
secret_package: round1::SecretPackage,
round1_packages: &[round1::Package],
) -> Result<(round2::SecretPackage, Vec<round2::Package>), Error> {
round1_packages: &HashMap<Identifier, round1::Package>,
) -> Result<(round2::SecretPackage, HashMap<Identifier, round2::Package>), Error> {
frost::keys::dkg::part2(secret_package, round1_packages)
}
@ -80,8 +80,8 @@ pub fn part2(
/// signatures.
pub fn part3(
round2_secret_package: &round2::SecretPackage,
round1_packages: &[round1::Package],
round2_packages: &[round2::Package],
round1_packages: &HashMap<Identifier, round1::Package>,
round2_packages: &HashMap<Identifier, round2::Package>,
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages)
}

View File

@ -0,0 +1,55 @@
//! 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 std::collections::HashMap;
use jubjub::Scalar;
use crate::frost::redjubjub::{
frost, Ciphersuite, CryptoRng, Error, Identifier, JubjubBlake2b512, RngCore,
};
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 HashMap 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<HashMap<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::<JubjubBlake2b512>(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)
}

View File

@ -9,10 +9,13 @@ use group::GroupEncoding;
use group::{ff::Field as FFField, ff::PrimeField, Group as FFGroup};
use pasta_curves::pallas;
use frost_rerandomized::{
frost_core::{frost, Ciphersuite, Field, FieldError, Group, GroupError},
RandomizedParams,
// Re-exports in our public API
#[cfg(feature = "serde")]
pub use frost_rerandomized::frost_core::serde;
pub use frost_rerandomized::frost_core::{
frost, Ciphersuite, Field, FieldError, Group, GroupError,
};
pub use rand_core;
use rand_core::{CryptoRng, RngCore};
@ -116,6 +119,8 @@ impl Group for PallasGroup {
/// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "self::serde"))]
pub struct PallasBlake2b512;
impl Ciphersuite for PallasBlake2b512 {
@ -172,6 +177,15 @@ impl Ciphersuite for PallasBlake2b512 {
.finalize(),
)
}
/// HID for FROST(Pallas, BLAKE2b-512)
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(
HStar::<orchard::SpendAuth>::new(b"FROST_RedPallasI")
.update(m)
.finalize(),
)
}
}
// Shorthand alias for the ciphersuite
@ -200,8 +214,24 @@ pub mod keys {
frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng)
}
/// Splits an existing key into FROST shares.
///
/// This is identical to [`generate_with_dealer`] but receives an existing key
/// instead of generating a fresh one. This is useful in scenarios where
/// the key needs to be generated externally or must be derived from e.g. a
/// seed phrase.
pub fn split<R: RngCore + CryptoRng>(
key: &SigningKey,
max_signers: u16,
min_signers: u16,
identifiers: IdentifierList,
rng: &mut R,
) -> Result<(HashMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
frost::keys::split(key, max_signers, min_signers, identifiers, rng)
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
/// [`generate_with_dealer`].
///
/// # Security
///
@ -209,6 +239,12 @@ pub mod keys {
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<P>;
/// A secret scalar value representing a signer's share of the group secret.
pub type SigningShare = frost::keys::SigningShare<P>;
/// A public group element that represents a single signer's public verification share.
pub type VerifyingShare = frost::keys::VerifyingShare<P>;
/// A FROST(Pallas, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
@ -222,6 +258,72 @@ pub mod keys {
///
/// Used for verification purposes before publishing a signature.
pub type PublicKeyPackage = frost::keys::PublicKeyPackage<P>;
/// 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.
pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment<P>;
/// Trait for ensuring the group public key has a positive Y coordinate.
pub trait PositiveY {
/// Convert the given package to make sure the group public key has
/// a positive Y coordinate.
fn into_positive_y(self) -> Self;
}
impl PositiveY for PublicKeyPackage {
fn into_positive_y(self) -> Self {
let pubkey = self.group_public();
let pubkey_serialized = pubkey.serialize();
if pubkey_serialized[31] & 0x80 != 0 {
let pubkey = VerifyingKey::new(-pubkey.to_element());
let signer_pubkeys: HashMap<_, _> = self
.signer_pubkeys()
.iter()
.map(|(i, vs)| {
let vs = VerifyingShare::new(-vs.to_element());
(*i, vs)
})
.collect();
PublicKeyPackage::new(signer_pubkeys, pubkey)
} else {
self
}
}
}
impl PositiveY for KeyPackage {
fn into_positive_y(self) -> Self {
let pubkey = self.group_public();
let pubkey_serialized = pubkey.serialize();
if pubkey_serialized[31] & 0x80 != 0 {
let pubkey = VerifyingKey::new(-pubkey.to_element());
let signing_share = SigningShare::new(-self.secret_share().to_scalar());
let verifying_share = VerifyingShare::new(-self.public().to_element());
KeyPackage::new(
*self.identifier(),
signing_share,
verifying_share,
pubkey,
*self.min_signers(),
)
} else {
self
}
}
}
pub mod dkg;
pub mod repairable;
}
/// FROST(Pallas, BLAKE2b-512) Round 1 functionality and types.
@ -242,6 +344,9 @@ pub mod round1 {
/// SigningCommitment can be used for exactly *one* signature.
pub type SigningCommitments = frost::round1::SigningCommitments<P>;
/// A commitment to a signing nonce share.
pub type NonceCommitment = frost::round1::NonceCommitment<P>;
/// Performed once by each participant selected for the signing operation.
///
/// Generates the signing nonces and commitments to be used in the signing
@ -269,9 +374,8 @@ pub mod round2 {
/// shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<P>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<P>;
/// A randomizer. A random scalar which is used to randomize the key.
pub type Randomizer = frost_rerandomized::Randomizer<P>;
/// Performed once by each participant selected for the signing operation.
///
@ -285,20 +389,18 @@ pub mod round2 {
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
randomizer_point: &<<P as Ciphersuite>::Group as Group>::Element,
randomizer: Randomizer,
) -> Result<SignatureShare, Error> {
frost_rerandomized::sign(
signing_package,
signer_nonces,
key_package,
randomizer_point,
)
frost_rerandomized::sign(signing_package, signer_nonces, key_package, randomizer)
}
}
/// A Schnorr signature on FROST(Pallas, BLAKE2b-512).
pub type Signature = frost_rerandomized::frost_core::Signature<P>;
/// Randomized parameters for a signing instance of randomized FROST.
pub type RandomizedParams = frost_rerandomized::RandomizedParams<P>;
/// Verifies each FROST(Pallas, BLAKE2b-512) participant's signature share, and if all are valid,
/// aggregates the shares into a signature to publish.
///
@ -315,10 +417,10 @@ pub type Signature = frost_rerandomized::frost_core::Signature<P>;
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.
pub fn aggregate(
signing_package: &round2::SigningPackage,
signing_package: &SigningPackage,
signature_shares: &HashMap<Identifier, round2::SignatureShare>,
pubkeys: &keys::PublicKeyPackage,
randomized_params: &RandomizedParams<P>,
randomized_params: &RandomizedParams,
) -> Result<Signature, Error> {
frost_rerandomized::aggregate(
signing_package,

View File

@ -3,7 +3,7 @@
The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.
Before starting, each participant needs a unique identifier, which can be built from
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
@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use reddsa::frost::redpallas as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// 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.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// 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 (secret_package, round1_package) = frost::keys::dkg::part1(
# // 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, secret_package);
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 HashMap; in practice this will be
@ -76,8 +80,8 @@ for participant_index in 1..=max_signers {
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(Vec::new)
.push(round1_package.clone());
.or_insert_with(HashMap::new)
.insert(participant_identifier, round1_package.clone());
}
}
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// 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_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
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.
@ -115,11 +121,11 @@ for participant_index in 1..=max_signers {
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for round2_package in round2_packages {
for (receiver_identifier, round2_package) in round2_packages {
received_round2_packages
.entry(round2_package.receiver_identifier)
.or_insert_with(Vec::new)
.push(round2_package);
.entry(receiver_identifier)
.or_insert_with(HashMap::new)
.insert(participant_identifier, round2_package);
}
}
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// 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 (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
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_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -64,8 +64,8 @@ pub fn part1<R: RngCore + CryptoRng>(
/// must be sent to other participants.
pub fn part2(
secret_package: round1::SecretPackage,
round1_packages: &[round1::Package],
) -> Result<(round2::SecretPackage, Vec<round2::Package>), Error> {
round1_packages: &HashMap<Identifier, round1::Package>,
) -> Result<(round2::SecretPackage, HashMap<Identifier, round2::Package>), Error> {
frost::keys::dkg::part2(secret_package, round1_packages)
}
@ -80,8 +80,8 @@ pub fn part2(
/// signatures.
pub fn part3(
round2_secret_package: &round2::SecretPackage,
round1_packages: &[round1::Package],
round2_packages: &[round2::Package],
round1_packages: &HashMap<Identifier, round1::Package>,
round2_packages: &HashMap<Identifier, round2::Package>,
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages)
}

View File

@ -0,0 +1,55 @@
//! 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 std::collections::HashMap;
use pasta_curves::pallas::Scalar;
use crate::frost::redpallas::{
frost, Ciphersuite, CryptoRng, Error, Identifier, PallasBlake2b512, RngCore,
};
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 HashMap 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<HashMap<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::<PallasBlake2b512>(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)
}