diff --git a/Cargo.toml b/Cargo.toml index a064e52..e1100fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,8 @@ 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.6.0", optional = true } +frost-rerandomized = { git = "https://github.com/ZcashFoundation/frost.git", rev = "a1834dccd046d852e368c565474e78af77b5d9a8", features=["test-impl"], optional = true } [dependencies.zeroize] version = "1" @@ -50,7 +51,8 @@ proptest = "1.0" rand = "0.8" rand_chacha = "0.3" serde_json = "1.0" -frost-rerandomized = { version = "0.6.0", features=["test-impl"] } +# frost-rerandomized = { version = "0.6.0", features=["test-impl"] } +frost-rerandomized = { git = "https://github.com/ZcashFoundation/frost.git", rev = "a1834dccd046d852e368c565474e78af77b5d9a8", features=["test-impl"] } num-bigint = "0.4.3" num-traits = "0.2.15" @@ -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]] diff --git a/src/frost/redjubjub.rs b/src/frost/redjubjub.rs index 51a10cd..9e5ae31 100644 --- a/src/frost/redjubjub.rs +++ b/src/frost/redjubjub.rs @@ -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}; @@ -198,8 +201,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( + key: &SigningKey, + max_signers: u16, + min_signers: u16, + identifiers: IdentifierList, + rng: &mut R, + ) -> Result<(HashMap, 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 +226,12 @@ pub mod keys { /// .into(), which under the hood also performs validation. pub type SecretShare = frost::keys::SecretShare; + /// A secret scalar value representing a signer's share of the group secret. + pub type SigningShare = frost::keys::SigningShare; + + /// A public group element that represents a single signer's public verification share. + pub type VerifyingShare = frost::keys::VerifyingShare; + /// A FROST(Jubjub, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using /// a DKG. /// @@ -221,7 +246,22 @@ pub mod keys { /// Used for verification purposes before publishing a signature. pub type PublicKeyPackage = frost::keys::PublicKeyPackage; + /// 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; + pub mod dkg; + pub mod repairable; } /// FROST(Jubjub, BLAKE2b-512) Round 1 functionality and types. @@ -242,6 +282,9 @@ pub mod round1 { /// SigningCommitment can be used for exactly *one* signature. pub type SigningCommitments = frost::round1::SigningCommitments; + /// A commitment to a signing nonce share. + pub type NonceCommitment = frost::round1::NonceCommitment; + /// Performed once by each participant selected for the signing operation. /// /// Generates the signing nonces and commitments to be used in the signing @@ -269,9 +312,8 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare; - /// Generated by the coordinator of the signing operation and distributed to - /// each signing party - pub type SigningPackage = frost::SigningPackage; + /// A randomizer. A random scalar which is used to randomize the key. + pub type Randomizer = frost_rerandomized::Randomizer; /// Performed once by each participant selected for the signing operation. /// @@ -285,20 +327,18 @@ pub mod round2 { signing_package: &SigningPackage, signer_nonces: &round1::SigningNonces, key_package: &keys::KeyPackage, - randomizer_point: &<::Group as Group>::Element, + randomizer: Randomizer, ) -> Result { - 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; +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; + /// Verifies each FROST(Jubjub, BLAKE2b-512) participant's signature share, and if all are valid, /// aggregates the shares into a signature to publish. /// @@ -315,10 +355,10 @@ pub type Signature = frost_rerandomized::frost_core::Signature; /// 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, pubkeys: &keys::PublicKeyPackage, - randomized_params: &RandomizedParams, + randomized_params: &RandomizedParams, ) -> Result { frost_rerandomized::aggregate( signing_package, diff --git a/src/frost/redjubjub/keys/repairable.rs b/src/frost/redjubjub/keys/repairable.rs new file mode 100644 index 0000000..bd92a7a --- /dev/null +++ b/src/frost/redjubjub/keys/repairable.rs @@ -0,0 +1,55 @@ +//! Repairable Threshold Scheme +//! +//! Implements the Repairable Threshold Scheme (RTS) from . +//! 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( + helpers: &[Identifier], + share_i: &SecretShare, + rng: &mut R, + participant: Identifier, +) -> Result, 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::(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) +} diff --git a/src/frost/redpallas.rs b/src/frost/redpallas.rs index 3dcb46e..0b62f57 100644 --- a/src/frost/redpallas.rs +++ b/src/frost/redpallas.rs @@ -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 { @@ -200,8 +205,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( + key: &SigningKey, + max_signers: u16, + min_signers: u16, + identifiers: IdentifierList, + rng: &mut R, + ) -> Result<(HashMap, 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 +230,12 @@ pub mod keys { /// .into(), which under the hood also performs validation. pub type SecretShare = frost::keys::SecretShare

; + /// A secret scalar value representing a signer's share of the group secret. + pub type SigningShare = frost::keys::SigningShare

; + + /// A public group element that represents a single signer's public verification share. + pub type VerifyingShare = frost::keys::VerifyingShare

; + /// A FROST(Pallas, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using /// a DKG. /// @@ -223,7 +250,65 @@ pub mod keys { /// Used for verification purposes before publishing a signature. pub type PublicKeyPackage = frost::keys::PublicKeyPackage

; + /// 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

; + + /// 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) + } else { + self + } + } + } + pub mod dkg; + pub mod repairable; } /// FROST(Pallas, BLAKE2b-512) Round 1 functionality and types. @@ -244,6 +329,9 @@ pub mod round1 { /// SigningCommitment can be used for exactly *one* signature. pub type SigningCommitments = frost::round1::SigningCommitments

; + /// A commitment to a signing nonce share. + pub type NonceCommitment = frost::round1::NonceCommitment

; + /// Performed once by each participant selected for the signing operation. /// /// Generates the signing nonces and commitments to be used in the signing @@ -271,9 +359,8 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare

; - /// Generated by the coordinator of the signing operation and distributed to - /// each signing party - pub type SigningPackage = frost::SigningPackage

; + /// A randomizer. A random scalar which is used to randomize the key. + pub type Randomizer = frost_rerandomized::Randomizer

; /// Performed once by each participant selected for the signing operation. /// @@ -287,20 +374,18 @@ pub mod round2 { signing_package: &SigningPackage, signer_nonces: &round1::SigningNonces, key_package: &keys::KeyPackage, - randomizer_point: &<

::Group as Group>::Element, + randomizer: Randomizer, ) -> Result { - 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

; +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams

; + /// Verifies each FROST(Pallas, BLAKE2b-512) participant's signature share, and if all are valid, /// aggregates the shares into a signature to publish. /// @@ -317,10 +402,10 @@ pub type Signature = frost_rerandomized::frost_core::Signature

; /// 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, pubkeys: &keys::PublicKeyPackage, - randomized_params: &RandomizedParams

, + randomized_params: &RandomizedParams, ) -> Result { frost_rerandomized::aggregate( signing_package, diff --git a/src/frost/redpallas/keys/repairable.rs b/src/frost/redpallas/keys/repairable.rs new file mode 100644 index 0000000..15d05db --- /dev/null +++ b/src/frost/redpallas/keys/repairable.rs @@ -0,0 +1,55 @@ +//! Repairable Threshold Scheme +//! +//! Implements the Repairable Threshold Scheme (RTS) from . +//! 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( + helpers: &[Identifier], + share_i: &SecretShare, + rng: &mut R, + participant: Identifier, +) -> Result, 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::(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) +}