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:
parent
b1bbad7bac
commit
ac524000e1
|
@ -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,4 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
*~
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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]]
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
1.65.0
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue