diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 9525530..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index bcb3afa..9a40d6f 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -16,7 +16,7 @@ on: jobs: coverage: - name: Coverage (+nightly) + name: Coverage runs-on: ubuntu-latest env: CARGO_INCREMENTAL: 0 @@ -29,7 +29,7 @@ jobs: - uses: actions-rs/toolchain@v1.0.7 with: - toolchain: nightly + toolchain: stable override: true profile: minimal components: llvm-tools-preview @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov --lcov --no-run --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v3.1.0 diff --git a/codecov.yml b/codecov.yml index 8dc6498..419999f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -23,7 +23,7 @@ parsers: # This turns off the extra comment; the coverage %'s are still # reported on the main PR page as check results -comment: false +# comment: false github_checks: annotations: false diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 52f015d..22373ab 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -34,8 +34,6 @@ where R_bytes[..].copy_from_slice(&bytes.as_ref()[0..R_bytes_len]); - println!("{:?}", R_bytes); - let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; let mut z_bytes = @@ -43,9 +41,8 @@ where let z_bytes_len = z_bytes.len(); - z_bytes[..].copy_from_slice(&bytes.as_ref()[R_bytes_len..z_bytes_len]); - - println!("{:?}", z_bytes); + // We extract the exact length of bytes we expect, not just the remaining bytes with `bytes[R_bytes_len..]` + z_bytes[..].copy_from_slice(&bytes.as_ref()[R_bytes_len..R_bytes_len + z_bytes_len]); let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 2c41635..2378b5d 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # - Update CHANGELOG.md # - Create git tag. version = "0.1.0" -authors = [ "Deirdre Connolly ", "Chelsea Komlo ", "Henry de Valence "] +authors = ["Deirdre Connolly ", "Chelsea Komlo "] readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/ZcashFoundation/frost" @@ -20,7 +20,8 @@ features = ["nightly"] [dependencies] byteorder = "1.4" curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] } -# digest = "0.9" +digest = "0.9" +frost-core = { path = "../frost-core" } hex = { version = "0.4.3", features = ["serde"] } rand_core = "0.6" serde = { version = "1", optional = true, features = ["derive"] } diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index 3c6aaf4..b1b53a0 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -1,18 +1,12 @@ An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers of signers (FROST). -In addition to the `Signature`, `SigningKey`, `VerificationKey` types, the library also provides -`VerificationKeyBytes`, a [refinement] of a `[u8; 32]` indicating that bytes represent an encoding -of a verification key. This allows the `VerificationKey` type to cache verification checks related to -the verification key encoding. - ## Examples Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the signature: ```rust -# use std::convert::TryFrom; use rand::thread_rng; use frost_ristretto255::*; @@ -22,25 +16,17 @@ let msg = b"Hello!"; let sk = SigningKey::new(thread_rng()); let sig = sk.sign(thread_rng(), msg); -// Types can be converted to raw byte arrays using From/Into -let sig_bytes: [u8; 64] = sig.into(); -let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); +// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes` +let sig_bytes = sig.to_bytes(); +let pk_bytes = VerifyingKey::from(&sk).to_bytes(); // Deserialize and verify the signature. -let sig: Signature = sig_bytes.into(); +let sig = Signature::from_bytes(sig_bytes)?; assert!( - VerificationKey::try_from(pk_bytes) + VerifyingKey::from_bytes(pk_bytes) .and_then(|pk| pk.verify(msg, &sig)) .is_ok() ); +# Ok::<(), Error>(()) ``` - -## docs - -```shell,no_run -cargo doc --features "nightly" --open -``` - -[redjubjub]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa -[refinement]: https://en.wikipedia.org/wiki/Refinement_type diff --git a/frost-ristretto255/src/batch.rs b/frost-ristretto255/src/batch.rs deleted file mode 100644 index 534819f..0000000 --- a/frost-ristretto255/src/batch.rs +++ /dev/null @@ -1,168 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of frost-ristretto255. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -//! Performs batch Schnorr signature verification on the Ristretto group. -//! -//! Batch verification asks whether *all* signatures in some set are valid, -//! rather than asking whether *each* of them is valid. This allows sharing -//! computations among all signature verifications, performing less work overall -//! at the cost of higher latency (the entire batch must complete), complexity -//! of caller code (which must assemble a batch of signatures across -//! work-items), and loss of the ability to easily pinpoint failing signatures. - -use std::convert::TryFrom; - -use curve25519_dalek::{ - ristretto::{CompressedRistretto, RistrettoPoint}, - scalar::Scalar, - traits::{Identity, VartimeMultiscalarMul}, -}; -use rand_core::{CryptoRng, RngCore}; - -use crate::*; - -/// A batch verification item. -/// -/// This struct exists to allow batch processing to be decoupled from the -/// lifetime of the message. This is useful when using the batch verification -/// API in an async context. -#[derive(Clone, Debug)] -pub struct Item { - vk_bytes: VerificationKeyBytes, - sig: Signature, - c: Scalar, -} - -impl<'msg, M: AsRef<[u8]>> From<(VerificationKeyBytes, Signature, &'msg M)> for Item { - fn from((vk_bytes, sig, msg): (VerificationKeyBytes, Signature, &'msg M)) -> Self { - // Compute c now to avoid dependency on the msg lifetime. - - let c = crate::generate_challenge(&sig.R_bytes, &vk_bytes.bytes, msg.as_ref()); - - Self { vk_bytes, sig, c } - } -} - -impl Item { - /// Perform non-batched verification of this `Item`. - /// - /// This is useful (in combination with `Item::clone`) for implementing - /// fallback logic when batch verification fails. In contrast to - /// [`VerificationKey::verify`](crate::VerificationKey::verify), which - /// requires borrowing the message data, the `Item` type is unlinked - /// from the lifetime of the message. - #[allow(non_snake_case)] - pub fn verify_single(self) -> Result<(), Error> { - VerificationKey::try_from(self.vk_bytes) - .and_then(|vk| vk.verify_prehashed(&self.sig, self.c)) - } -} - -#[derive(Default)] -/// A batch verification context. -pub struct Verifier { - /// Signature data queued for verification. - signatures: Vec, -} - -impl Verifier { - /// Construct a new batch verifier. - pub fn new() -> Verifier { - Verifier::default() - } - - /// Queue an Item for verification. - pub fn queue>(&mut self, item: I) { - self.signatures.push(item.into()); - } - - /// Perform batch verification, returning `Ok(())` if all signatures were - /// valid and `Err` otherwise. - /// - /// The batch verification equation is: - /// - /// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i + [z_i * c_i]VK_i) = 0_G - /// - /// which we split out into: - /// - /// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) = - /// 0_G - /// - /// so that we can use multiscalar multiplication speedups. - /// - /// where for each signature i, - /// - VK_i is the verification key; - /// - R_i is the signature's R value; - /// - s_i is the signature's s value; - /// - c_i is the hash of the message and other data; - /// - z_i is a random 128-bit Scalar; - /// - h_G is the cofactor of the group; - /// - P_G is the generator of the subgroup; - /// - /// As follows elliptic curve scalar multiplication convention, - /// scalar variables are lowercase and group point variables - /// are uppercase. This does not exactly match the RedDSA - /// notation in the [protocol specification §B.1][ps]. - /// - /// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify - #[allow(non_snake_case)] - pub fn verify(self, mut rng: R) -> Result<(), Error> { - let n = self.signatures.len(); - - let mut VK_coeffs = Vec::with_capacity(n); - let mut VKs = Vec::with_capacity(n); - let mut R_coeffs = Vec::with_capacity(self.signatures.len()); - let mut Rs = Vec::with_capacity(self.signatures.len()); - let mut P_coeff_acc = Scalar::zero(); - - for item in self.signatures.iter() { - let (z_bytes, R_bytes, c) = (item.sig.z_bytes, item.sig.R_bytes, item.c); - - let s = Scalar::from_bytes_mod_order(z_bytes); - - let R = { - match CompressedRistretto::from_slice(&R_bytes).decompress() { - Some(point) => point, - None => return Err(Error::InvalidSignature), - } - }; - - let VK = VerificationKey::try_from(item.vk_bytes.bytes)?.point; - - let z = Scalar::random(&mut rng); - - let P_coeff = z * s; - P_coeff_acc -= P_coeff; - - R_coeffs.push(z); - Rs.push(R); - - VK_coeffs.push(Scalar::zero() + (z * c)); - VKs.push(VK); - } - - use std::iter::once; - - let scalars = once(&P_coeff_acc) - .chain(VK_coeffs.iter()) - .chain(R_coeffs.iter()); - - let basepoints = [curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT]; - let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter()); - - let check = RistrettoPoint::vartime_multiscalar_mul(scalars, points); - - if check == RistrettoPoint::identity() { - Ok(()) - } else { - Err(Error::InvalidSignature) - } - } -} diff --git a/frost-ristretto255/src/error.rs b/frost-ristretto255/src/error.rs deleted file mode 100644 index e130f45..0000000 --- a/frost-ristretto255/src/error.rs +++ /dev/null @@ -1,25 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redjubjub. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -use thiserror::Error; - -/// An error related to RedJubJub signatures. -#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] -pub enum Error { - /// The encoding of a signing key was malformed. - #[error("Malformed signing key encoding.")] - MalformedSigningKey, - /// The encoding of a verification key was malformed. - #[error("Malformed verification key encoding.")] - MalformedVerificationKey, - /// Signature verification failed. - #[error("Invalid signature.")] - InvalidSignature, -} diff --git a/frost-ristretto255/src/frost.rs b/frost-ristretto255/src/frost.rs deleted file mode 100644 index 4138f93..0000000 --- a/frost-ristretto255/src/frost.rs +++ /dev/null @@ -1,311 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of frost-ristretto255. -// Copyright (c) 2020-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Chelsea H. Komlo -// - Deirdre Connolly -// - isis agora lovecruft - -//! An implementation of FROST (Flexible Round-Optimized Schnorr Threshold) -//! signatures. -//! -//! If you are interested in deploying FROST, please do not hesitate to consult the FROST authors. -//! -//! This implementation currently only supports key generation using a central -//! dealer. In the future, we will add support for key generation via a DKG, -//! as specified in the FROST paper. -//! -//! Internally, keygen_with_dealer generates keys using Verifiable Secret -//! Sharing, where shares are generated using Shamir Secret Sharing. - -use std::{collections::HashMap, convert::TryFrom, fmt, fmt::Debug}; - -use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar, traits::Identity}; - -use hex::FromHex; - -pub mod keys; -pub mod round1; -pub mod round2; - -#[cfg(test)] -mod tests; - -use crate::{generate_challenge, Signature, H1, H3}; - -/// The binding factor, also known as _rho_ (ρ) -/// -/// Ensures each signature share is strongly bound to a signing set, specific set -/// of commitments, and a specific message. -/// -/// -#[derive(Clone, Debug, PartialEq)] -struct Rho(Scalar); - -impl From<&SigningPackage> for Rho { - // [`compute_binding_factor`] in the spec - // - // [`compute_binding_factor`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.4 - fn from(signing_package: &SigningPackage) -> Rho { - let preimage = signing_package.rho_preimage(); - - let binding_factor = H1(&preimage[..]); - - Rho(Scalar::from_bytes_mod_order_wide(&binding_factor)) - } -} - -impl FromHex for Rho { - type Error = &'static str; - - fn from_hex>(hex: T) -> Result { - let mut bytes = [0u8; 32]; - - match hex::decode_to_slice(hex, &mut bytes[..]) { - Ok(()) => Self::try_from(bytes), - Err(_) => Err("invalid hex"), - } - } -} - -impl TryFrom<[u8; 32]> for Rho { - type Error = &'static str; - - fn try_from(source: [u8; 32]) -> Result { - match Scalar::from_canonical_bytes(source) { - Some(scalar) => Ok(Self(scalar)), - None => Err("scalar was not canonically encoded"), - } - } -} - -/// Generates the lagrange coefficient for the i'th participant. -fn derive_lagrange_coeff( - signer_index: u16, - signing_package: &SigningPackage, -) -> Result { - let signer_index_scalar = Scalar::from(signer_index as u16); - - if signer_index_scalar == Scalar::zero() { - return Err("Invalid parameters"); - } - - if signing_package - .signing_commitments() - .iter() - .any(|commitment| Scalar::from(commitment.index as u16) == Scalar::zero()) - { - return Err("Invalid parameters"); - } - - let mut num = Scalar::one(); - let mut den = Scalar::one(); - - // Ala the sorting of B, just always sort by index in ascending order - // - // https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding - for commitment in signing_package.signing_commitments() { - if commitment.index == signer_index { - continue; - } - - let commitment_index_scalar = Scalar::from(commitment.index as u16); - num *= commitment_index_scalar; - den *= commitment_index_scalar - signer_index_scalar; - } - - if den == Scalar::zero() { - return Err("Duplicate shares provided"); - } - - // TODO: handle this unwrap better like other CtOption's - let lagrange_coeff = num * den.invert(); - - Ok(lagrange_coeff) -} - -/// Generated by the coordinator of the signing operation and distributed to -/// each signing party -#[derive(Debug)] -pub struct SigningPackage { - /// The set of commitments participants published in the first round of the - /// protocol. - signing_commitments: HashMap, - /// Message which each participant will sign. - /// - /// Each signer should perform protocol-specific verification on the - /// message. - message: Vec, -} - -impl SigningPackage { - /// Create a new `SigingPackage` - /// - /// The `signing_commitments` are sorted by participant `index`. - pub fn new( - mut signing_commitments: Vec, - message: Vec, - ) -> SigningPackage { - signing_commitments.sort_by_key(|a| a.index); - - SigningPackage { - signing_commitments: signing_commitments - .into_iter() - .map(|s| (s.index, s)) - .collect(), - message, - } - } - - /// Get a signing commitment by its participant index. - pub fn signing_commitment(&self, index: &u16) -> round1::SigningCommitments { - self.signing_commitments[index] - } - - /// Get the signing commitments, sorted by the participant indices - pub fn signing_commitments(&self) -> Vec { - let mut signing_commitments: Vec = - self.signing_commitments.values().cloned().collect(); - signing_commitments.sort_by_key(|a| a.index); - signing_commitments - } - - /// Get the message to be signed - pub fn message(&self) -> &Vec { - &self.message - } - - /// Compute the preimage to H3 to compute rho - // We separate this out into its own method so it can be tested - pub(super) fn rho_preimage(&self) -> Vec { - let mut preimage = vec![]; - - preimage - .extend_from_slice(&round1::encode_group_commitments(self.signing_commitments())[..]); - preimage.extend_from_slice(&H3(self.message.as_slice())); - - preimage - } -} - -/// The product of all signers' individual commitments, published as part of the -/// final signature. -#[derive(PartialEq)] -pub struct GroupCommitment(pub(super) RistrettoPoint); - -impl Debug for GroupCommitment { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("GroupCommitment") - .field(&hex::encode(self.0.compress().to_bytes())) - .finish() - } -} - -impl TryFrom<&SigningPackage> for GroupCommitment { - type Error = &'static str; - - /// Generates the group commitment which is published as part of the joint - /// Schnorr signature. - /// - /// Implements [`compute_group_commitment`] from the spec. - /// - /// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.4 - fn try_from(signing_package: &SigningPackage) -> Result { - let rho: Rho = signing_package.into(); - - let identity = RistrettoPoint::identity(); - let mut accumulator = identity; - - // Ala the sorting of B, just always sort by index in ascending order - // - // https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding - for commitment in signing_package.signing_commitments() { - // The following check prevents a party from accidentally revealing their share. - // Note that the '&&' operator would be sufficient. - if identity == commitment.binding.0 || identity == commitment.hiding.0 { - return Err("Commitment equals the identity."); - } - - accumulator += commitment.hiding.0 + (commitment.binding.0 * rho.0) - } - - Ok(GroupCommitment(accumulator)) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Aggregation -//////////////////////////////////////////////////////////////////////////////// - -/// Verifies each participant's signature share, and if all are valid, -/// aggregates the shares into a signature to publish. -/// -/// Resulting signature is compatible with verification of a plain SpendAuth -/// signature. -/// -/// This operation is performed by a coordinator that can communicate with all -/// the signing participants before publishing the final signature. The -/// coordinator can be one of the participants or a semi-trusted third party -/// (who is trusted to not perform denial of service attacks, but does not learn -/// any secret information). Note that because the coordinator is trusted to -/// report misbehaving parties in order to avoid publishing an invalid -/// signature, if the coordinator themselves is a signer and misbehaves, they -/// 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: &SigningPackage, - signing_shares: &[round2::SignatureShare], - pubkeys: &keys::PublicKeyPackage, -) -> Result { - // Encodes the signing commitment list produced in round one as part of generating [`Rho`], the - // binding factor. - let rho: Rho = signing_package.into(); - - // Compute the group commitment from signing commitments produced in round one. - let group_commitment = GroupCommitment::try_from(signing_package)?; - - // Compute the per-message challenge. - let challenge = generate_challenge( - &group_commitment.0.compress().to_bytes(), - &pubkeys.group_public.bytes.bytes, - signing_package.message().as_slice(), - ); - - // Verify the signature shares. - for signing_share in signing_shares { - // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, - // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. - let signer_pubkey = pubkeys.signer_pubkeys.get(&signing_share.index).unwrap(); - - // Compute Lagrange coefficient. - let lambda_i = derive_lagrange_coeff(signing_share.index, signing_package)?; - - // Compute the commitment share. - let R_share = signing_package - .signing_commitment(&signing_share.index) - .to_group_commitment_share(&rho); - - // Compute relation values to verify this signing share. - signing_share.verify(R_share, signer_pubkey, lambda_i, challenge)?; - } - - // The aggregation of the signature shares by summing them up, resulting in - // a plain Schnorr signature. - // - // Implements [`frost_aggregate`] from the spec. - // - // [`frost_aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.3-4 - let mut z = Scalar::zero(); - - for signature_share in signing_shares { - z += signature_share.signature.z_share; - } - - Ok(Signature { - R_bytes: group_commitment.0.compress().to_bytes(), - z_bytes: z.to_bytes(), - }) -} diff --git a/frost-ristretto255/src/frost/keys.rs b/frost-ristretto255/src/frost/keys.rs deleted file mode 100644 index 51d6598..0000000 --- a/frost-ristretto255/src/frost/keys.rs +++ /dev/null @@ -1,348 +0,0 @@ -//! FROST keys, keygen, key shares - -use std::{collections::HashMap, convert::TryFrom, fmt::Debug}; - -use curve25519_dalek::{ - constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar, - traits::Identity, -}; -use hex::FromHex; -use rand_core::{CryptoRng, RngCore}; -use zeroize::DefaultIsZeroes; - -use crate::VerificationKey; - -/// A secret scalar value representing a single signer's secret key. -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct Secret(pub(super) Scalar); - -impl Secret { - /// Generates a new uniformly random secret value using the provided RNG. - pub fn random(rng: &mut R) -> Self - where - R: CryptoRng + RngCore, - { - Self(Scalar::random(rng)) - } -} - -// Zeroizes `Secret` to be the `Default` value on drop (when it goes out of scope). Luckily the -// derived `Default` includes the `Default` impl of Scalar, which is four 0u64's under the hood. -impl DefaultIsZeroes for Secret {} - -impl From for Secret { - fn from(source: Scalar) -> Secret { - Secret(source) - } -} - -impl FromHex for Secret { - type Error = &'static str; - - fn from_hex>(hex: T) -> Result { - let mut bytes = [0u8; 32]; - - match hex::decode_to_slice(hex, &mut bytes[..]) { - Ok(()) => Secret::try_from(bytes), - Err(_) => Err("invalid hex"), - } - } -} - -impl TryFrom<[u8; 32]> for Secret { - type Error = &'static str; - - fn try_from(source: [u8; 32]) -> Result { - match Scalar::from_canonical_bytes(source) { - Some(scalar) => Ok(Secret(scalar)), - None => Err("scalar was not canonically encoded"), - } - } -} - -/// A public group element that represents a single signer's public key. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Public(pub(super) RistrettoPoint); - -impl From for Public { - fn from(source: RistrettoPoint) -> Public { - Public(source) - } -} - -impl From for Public { - fn from(secret: Secret) -> Public { - Public(RISTRETTO_BASEPOINT_POINT * secret.0) - } -} - -/// A Ristretto point that is a commitment to one coefficient of our secret -/// polynomial. -/// -/// This is a (public) commitment to one coefficient of a secret polynomial used -/// for performing verifiable secret sharing for a Shamir secret share. -#[derive(Clone, Copy, Debug, PartialEq)] -pub(super) struct CoefficientCommitment(pub(super) RistrettoPoint); - -/// 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. -#[derive(Clone)] -pub struct VerifiableSecretSharingCommitment(pub(super) Vec); - -/// A secret share generated by performing a (t-out-of-n) secret sharing scheme. -/// -/// `n` is the total number of shares and `t` is the threshold required to reconstruct the secret; -/// in this case we use Shamir's secret sharing. -#[derive(Clone)] -pub struct SecretShare { - pub(super) index: u16, - /// Secret Key. - pub(super) value: Secret, - /// The commitments to be distributed among signers. - pub(super) commitment: VerifiableSecretSharingCommitment, -} - -impl SecretShare { - /// Verifies that a secret share is consistent with a verifiable secret sharing commitment. - /// - /// This ensures that this participant's share has been generated using the same - /// mechanism as all other signing participants. Note that participants *MUST* - /// ensure that they have the same view as all other participants of the - /// commitment! - /// - /// An implementation of `vss_verify()` from the [spec]. - /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#appendix-B.2-4 - pub fn verify(&self) -> Result<(), &'static str> { - let f_result = RISTRETTO_BASEPOINT_POINT * self.value.0; - - let x = Scalar::from(self.index as u16); - - let (_, result) = self.commitment.0.iter().fold( - (Scalar::one(), RistrettoPoint::identity()), - |(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i), - ); - - if !(f_result == result) { - return Err("SecretShare is invalid."); - } - - Ok(()) - } -} - -/// Secret and public key material generated by a dealer performing -/// [`keygen_with_dealer`]. -/// -/// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call -/// .into(), which under the hood also performs validation. -#[derive(Clone)] -pub struct SharePackage { - /// Denotes the participant index each share is owned by. - pub index: u16, - /// This participant's secret share. - pub(super) secret_share: SecretShare, - /// This participant's public key. - pub public: Public, - /// The public signing key that represents the entire group. - pub group_public: VerificationKey, -} - -/// Allows all participants' keys to be generated using a central, trusted -/// dealer. -/// -/// Under the hood, this performs verifiable secret sharing, which itself uses -/// Shamir secret sharing, from which each share becomes a participant's secret -/// key. The output from this function is a set of shares along with one single -/// commitment that participants use to verify the integrity of the share. The -/// number of signers is limited to 255. -/// -/// Implements [`trusted_dealer_keygen`] from the spec. -/// -/// [`trusted_dealer_keygen`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B -pub fn keygen_with_dealer( - num_signers: u8, - threshold: u8, - mut rng: R, -) -> Result<(Vec, PublicKeyPackage), &'static str> { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - - let secret = Secret::random(&mut rng); - let group_public = VerificationKey::from(&secret.0); - let secret_shares = generate_secret_shares(&secret, num_signers, threshold, rng)?; - let mut share_packages: Vec = Vec::with_capacity(num_signers as usize); - let mut signer_pubkeys: HashMap = HashMap::with_capacity(num_signers as usize); - - for secret_share in secret_shares { - let signer_public = secret_share.value.into(); - - share_packages.push(SharePackage { - index: secret_share.index, - secret_share: secret_share.clone(), - public: signer_public, - group_public, - }); - - signer_pubkeys.insert(secret_share.index, signer_public); - } - - Ok(( - share_packages, - PublicKeyPackage { - signer_pubkeys, - group_public, - }, - )) -} - -/// A FROST keypair, which can be generated either by a trusted dealer or using -/// a DKG. -/// -/// When using a central dealer, [`SharePackage`]s are distributed to -/// participants, who then perform verification, before deriving -/// [`KeyPackage`]s, which they store to later use during signing. -#[derive(Copy, Clone, Debug)] -pub struct KeyPackage { - /// Denotes the participant index each secret share key package is owned by. - pub index: u16, - /// This participant's secret share. - pub(super) secret_share: Secret, - /// This participant's public key. - pub public: Public, - /// The public signing key that represents the entire group. - pub group_public: VerificationKey, -} - -impl TryFrom for KeyPackage { - type Error = &'static str; - - /// Tries to verify a share and construct a [`KeyPackage`] from it. - /// - /// When participants receive a [`SharePackage`] from the dealer, they - /// *MUST* verify the integrity of the share before continuing on to - /// transform it into a signing/verification keypair. Here, we assume that - /// every participant has the same view of the commitment issued by the - /// dealer, but implementations *MUST* make sure that all participants have - /// a consistent view of this commitment in practice. - fn try_from(share_package: SharePackage) -> Result { - share_package.secret_share.verify()?; - - Ok(KeyPackage { - index: share_package.index, - secret_share: share_package.secret_share.value, - public: share_package.public, - group_public: share_package.group_public, - }) - } -} - -/// Public data that contains all the signers' public keys as well as the -/// group public key. -/// -/// Used for verification purposes before publishing a signature. -pub struct PublicKeyPackage { - /// When performing signing, the coordinator must ensure that they have the - /// correct view of participants' public keys to perform verification before - /// publishing a signature. `signer_pubkeys` represents all signers for a - /// signing operation. - pub(super) signer_pubkeys: HashMap, - /// The joint public key for the entire group. - pub group_public: VerificationKey, -} - -/// Creates secret shares for a given secret. -/// -/// This function accepts a secret from which shares are generated. While in -/// FROST this secret should always be generated randomly, we allow this secret -/// to be specified for this internal function for testability. -/// -/// Internally, [`generate_secret_shares`] performs verifiable secret sharing, which -/// generates shares via Shamir Secret Sharing, and then generates public -/// commitments to those shares. -/// -/// More specifically, [`generate_secret_shares`]: -/// - Randomly samples of coefficients [a, b, c], this represents a secret -/// polynomial f -/// - For each participant i, their secret share is f(i) -/// - The commitment to the secret polynomial f is [g^a, g^b, g^c] -/// -/// Implements [`secret_key_shard`] from the spec. -/// -/// [`secret_key_shard`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B.1 -pub fn generate_secret_shares( - secret: &Secret, - numshares: u8, - threshold: u8, - mut rng: R, -) -> Result, &'static str> { - if threshold < 2 { - return Err("Threshold cannot be less than 2"); - } - - if numshares < 2 { - return Err("Number of shares cannot be less than the minimum threshold 2"); - } - - if threshold > numshares { - return Err("Threshold cannot exceed numshares"); - } - - let numcoeffs = threshold - 1; - - let mut coefficients: Vec = Vec::with_capacity(threshold as usize); - - let mut secret_shares: Vec = Vec::with_capacity(numshares as usize); - - let mut commitment: VerifiableSecretSharingCommitment = - VerifiableSecretSharingCommitment(Vec::with_capacity(threshold as usize)); - - for _ in 0..numcoeffs { - coefficients.push(Scalar::random(&mut rng)); - } - - // Verifiable secret sharing, to make sure that participants can ensure their - // secret is consistent with every other participant's. - commitment - .0 - .push(CoefficientCommitment(RISTRETTO_BASEPOINT_POINT * secret.0)); - - for c in &coefficients { - commitment - .0 - .push(CoefficientCommitment(RISTRETTO_BASEPOINT_POINT * c)); - } - - // Evaluate the polynomial with `secret` as the constant term - // and `coeffs` as the other coefficients at the point x=share_index, - // using Horner's method. - for index in 1..=numshares { - let scalar_index = Scalar::from(index as u16); - let mut value = Scalar::zero(); - - // Polynomial evaluation, for this index - for i in (0..numcoeffs).rev() { - value += &coefficients[i as usize]; - value *= scalar_index; - } - value += secret.0; - - secret_shares.push(SecretShare { - index: index as u16, - value: Secret(value), - commitment: commitment.clone(), - }); - } - - Ok(secret_shares) -} diff --git a/frost-ristretto255/src/frost/round1.rs b/frost-ristretto255/src/frost/round1.rs deleted file mode 100644 index a2ef675..0000000 --- a/frost-ristretto255/src/frost/round1.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! FROST Round 1 functionality and types - -use std::{ - convert::TryFrom, - fmt::{self, Debug}, -}; - -use curve25519_dalek::{ - constants::RISTRETTO_BASEPOINT_POINT, - ristretto::{CompressedRistretto, RistrettoPoint}, - scalar::Scalar, -}; -use hex::FromHex; -use rand_core::{CryptoRng, RngCore}; -use zeroize::DefaultIsZeroes; - -use crate::frost; - -/// A scalar used in Ristretto that is a signing nonce. -#[derive(Clone, Copy, Default, PartialEq)] -pub(super) struct Nonce(pub(super) Scalar); - -impl Nonce { - /// Generates a new uniformly random signing nonce. - /// - /// Each participant generates signing nonces before performing a signing - /// operation. - /// - /// An implementation of `RandomNonzeroScalar()` from the [spec]. - /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.4 - pub fn random(rng: &mut R) -> Self - where - R: CryptoRng + RngCore, - { - // The values of 'hiding' and 'binding' nonces must be non-zero so that commitments are - // not the identity. - loop { - let scalar = Scalar::random(rng); - if scalar != Scalar::zero() { - return Self(scalar); - } - } - } -} - -impl AsRef for Nonce { - fn as_ref(&self) -> &Scalar { - &self.0 - } -} - -impl Debug for Nonce { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("Nonce") - .field(&hex::encode(self.0.to_bytes())) - .finish() - } -} - -// Zeroizes `Secret` to be the `Default` value on drop (when it goes out of scope). Luckily the -// derived `Default` includes the `Default` impl of Scalar, which is four 0u64's under the hood. -impl DefaultIsZeroes for Nonce {} - -impl FromHex for Nonce { - type Error = &'static str; - - fn from_hex>(hex: T) -> Result { - let mut bytes = [0u8; 32]; - - match hex::decode_to_slice(hex, &mut bytes[..]) { - Ok(()) => Self::try_from(bytes), - Err(_) => Err("invalid hex"), - } - } -} - -impl TryFrom<[u8; 32]> for Nonce { - type Error = &'static str; - - fn try_from(source: [u8; 32]) -> Result { - match Scalar::from_canonical_bytes(source) { - Some(scalar) if scalar != Scalar::zero() => Ok(Self(scalar)), - None => Err("ristretto scalar not canonical byte representation"), - _ => Err("invalid nonce value"), - } - } -} - -/// A Ristretto point that is a commitment to a signing nonce share. -#[derive(Clone, Copy, Debug, PartialEq)] -pub(super) struct NonceCommitment(pub(super) RistrettoPoint); - -impl From for NonceCommitment { - fn from(nonce: Nonce) -> Self { - Self(RISTRETTO_BASEPOINT_POINT * nonce.0) - } -} - -impl FromHex for NonceCommitment { - type Error = &'static str; - - fn from_hex>(hex: T) -> Result { - let mut bytes = [0u8; 32]; - - match hex::decode_to_slice(hex, &mut bytes[..]) { - Ok(()) => Self::try_from(bytes), - Err(_) => Err("invalid hex"), - } - } -} - -impl TryFrom<[u8; 32]> for NonceCommitment { - type Error = &'static str; - - fn try_from(source: [u8; 32]) -> Result { - match CompressedRistretto::from_slice(&source[..]).decompress() { - Some(point) => Ok(Self(point)), - None => Err("ristretto point was not canonically encoded"), - } - } -} - -/// Comprised of hiding and binding nonces. -/// -/// Note that [`SigningNonces`] must be used *only once* for a signing -/// operation; re-using nonces will result in leakage of a signer's long-lived -/// signing key. -#[derive(Clone, Copy, Default, Debug)] -pub struct SigningNonces { - pub(super) hiding: Nonce, - pub(super) binding: Nonce, -} - -// Zeroizes `SigningNonces` to be the `Default` value on drop (when it goes out of scope). Luckily -// the derived `Default` includes the `Default` impl of the `curve25519_dalek::scalar::Scalar`s, -// which is 32 0u8's under the hood. -impl DefaultIsZeroes for SigningNonces {} - -impl SigningNonces { - /// Generates a new signing nonce. - /// - /// Each participant generates signing nonces before performing a signing - /// operation. - pub fn new(rng: &mut R) -> Self - where - R: CryptoRng + RngCore, - { - // The values of 'hiding' and 'binding' must be non-zero so that commitments are - // not the identity. - let hiding = Nonce::random(rng); - let binding = Nonce::random(rng); - - Self { hiding, binding } - } -} - -/// Published by each participant in the first round of the signing protocol. -/// -/// This step can be batched if desired by the implementation. Each -/// SigningCommitment can be used for exactly *one* signature. -#[derive(Copy, Clone, Debug)] -pub struct SigningCommitments { - /// The participant index - pub(super) index: u16, - /// The hiding point. - pub(super) hiding: NonceCommitment, - /// The binding point. - pub(super) binding: NonceCommitment, -} - -impl SigningCommitments { - /// Computes the [signature commitment share] from these round one signing commitments. - /// - /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#name-signature-share-verificatio - pub(super) fn to_group_commitment_share( - self, - binding_factor: &frost::Rho, - ) -> GroupCommitmentShare { - GroupCommitmentShare(self.hiding.0 + (self.binding.0 * binding_factor.0)) - } -} - -impl From<(u16, &SigningNonces)> for SigningCommitments { - fn from((index, nonces): (u16, &SigningNonces)) -> Self { - Self { - index, - hiding: nonces.hiding.into(), - binding: nonces.binding.into(), - } - } -} - -/// One signer's share of the group commitment, derived from their individual signing commitments -/// and the binding factor _rho_. -#[derive(Clone, Copy, Default, PartialEq)] -pub struct GroupCommitmentShare(pub(super) RistrettoPoint); - -/// Encode the list of group signing commitments. -/// -/// Implements [`encode_group_commitment_list()`] from the spec. -/// -/// Inputs: -/// - commitment_list = [(j, D_j, E_j), ...], a list of commitments issued by each signer, -/// where each element in the list indicates the signer index and their -/// two commitment Element values. B MUST be sorted in ascending order -/// by signer index. -/// -/// Outputs: -/// - A byte string containing the serialized representation of B. -/// -/// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.3 -pub(super) fn encode_group_commitments(signing_commitments: Vec) -> Vec { - // B MUST be sorted in ascending order by signer index. - // - // https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding - let mut sorted_signing_commitments = signing_commitments; - sorted_signing_commitments.sort_by_key(|a| a.index); - - let mut bytes = vec![]; - - for item in sorted_signing_commitments { - bytes.extend_from_slice(&item.index.to_be_bytes()[..]); - bytes.extend_from_slice(&item.hiding.0.compress().to_bytes()[..]); - bytes.extend_from_slice(&item.binding.0.compress().to_bytes()[..]); - } - - bytes -} - -/// Done once by each participant, to generate _their_ nonces and commitments -/// that are then used during signing. -/// -/// When performing signing using two rounds, num_nonces would equal 1, to -/// perform the first round. Batching entails generating more than one -/// nonce/commitment pair at a time. Nonces should be stored in secret storage -/// for later use, whereas the commitments are published. -/// -/// The number of nonces is limited to 255. This limit can be increased if it -/// turns out to be too conservative. -// TODO: Make sure the above is a correct statement, fix if needed in: -// https://github.com/ZcashFoundation/redjubjub/issues/111 -pub fn preprocess( - num_nonces: u8, - participant_index: u16, - rng: &mut R, -) -> (Vec, Vec) -where - R: CryptoRng + RngCore, -{ - let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); - let mut signing_commitments: Vec = Vec::with_capacity(num_nonces as usize); - - for _ in 0..num_nonces { - let nonces = SigningNonces::new(rng); - signing_commitments.push(SigningCommitments::from((participant_index, &nonces))); - signing_nonces.push(nonces); - } - - (signing_nonces, signing_commitments) -} diff --git a/frost-ristretto255/src/frost/round2.rs b/frost-ristretto255/src/frost/round2.rs deleted file mode 100644 index 263f69b..0000000 --- a/frost-ristretto255/src/frost/round2.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! FROST Round 2 functionality and types, for signature share generation - -use std::fmt::{self, Debug}; - -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar}; - -use zeroize::DefaultIsZeroes; - -use crate::{ - frost::{self, round1, *}, - generate_challenge, -}; - -/// A representation of a single signature share used in FROST structures and messages. - -#[derive(Clone, Copy, Default, PartialEq)] -pub struct SignatureResponse { - pub(super) z_share: Scalar, -} - -impl Debug for SignatureResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("SignatureResponse") - .field("z_share", &hex::encode(self.z_share.to_bytes())) - .finish() - } -} - -impl From for [u8; 32] { - fn from(sig: SignatureResponse) -> [u8; 32] { - sig.z_share.to_bytes() - } -} - -/// A participant's signature share, which the coordinator will aggregate with all other signer's -/// shares into the joint signature. -#[derive(Clone, Copy, Default, PartialEq)] -pub struct SignatureShare { - /// Represents the participant index. - pub(super) index: u16, - /// This participant's signature over the message. - pub(super) signature: SignatureResponse, -} - -impl Debug for SignatureShare { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("SignatureShare") - .field("index", &self.index) - .field("signature", &self.signature) - .finish() - } -} - -// Zeroizes `SignatureShare` to be the `Default` value on drop (when it goes out -// of scope). Luckily the derived `Default` includes the `Default` impl of -// Scalar, which is four 0u64's under the hood, and u32, which is -// 0u32. -impl DefaultIsZeroes for SignatureShare {} - -impl SignatureShare { - /// Tests if a signature share issued by a participant is valid before - /// aggregating it into a final joint signature to publish. - /// - /// This is the final step of [`verify_signature_share`] from the spec. - /// - /// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.3 - pub fn verify( - &self, - group_commitment_share: round1::GroupCommitmentShare, - public_key: &frost::keys::Public, - lambda_i: Scalar, - challenge: Scalar, - ) -> Result<(), &'static str> { - if (RISTRETTO_BASEPOINT_POINT * self.signature.z_share) - != (group_commitment_share.0 + (public_key.0 * challenge * lambda_i)) - { - return Err("Invalid signature share"); - } - - Ok(()) - } -} - -/// Performed once by each participant selected for the signing operation. -/// -/// Implements [`sign`] from the spec. -/// -/// Receives the message to be signed and a set of signing commitments and a set -/// of randomizing commitments to be used in that signing operation, including -/// that for this participant. -/// -/// Assumes the participant has already determined which nonce corresponds with -/// the commitment that was assigned by the coordinator in the SigningPackage. -/// -/// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.2 -pub fn sign( - signing_package: &SigningPackage, - signer_nonces: &round1::SigningNonces, - key_package: &frost::keys::KeyPackage, -) -> Result { - // Encodes the signing commitment list produced in round one as part of generating [`Rho`], the - // binding factor. - let rho: frost::Rho = signing_package.into(); - - // Compute the group commitment from signing commitments produced in round one. - let group_commitment = GroupCommitment::try_from(signing_package)?; - - // Compute Lagrange coefficient. - let lambda_i = frost::derive_lagrange_coeff(key_package.index, signing_package)?; - - // Compute the per-message challenge. - let challenge = generate_challenge( - &group_commitment.0.compress().to_bytes(), - &key_package.group_public.bytes.bytes, - signing_package.message.as_slice(), - ); - - // Compute the Schnorr signature share. - let z_share: Scalar = signer_nonces.hiding.0 - + (signer_nonces.binding.0 * rho.0) - + (lambda_i * key_package.secret_share.0 * challenge); - - let signature_share = SignatureShare { - index: key_package.index, - signature: SignatureResponse { z_share }, - }; - - Ok(signature_share) -} diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index ebf57c8..2f77d5e 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -1,99 +1,259 @@ -// -*- mode: rust; -*- -// -// This file is part of redjubjub. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - #![allow(non_snake_case)] #![deny(missing_docs)] #![doc = include_str!("../README.md")] -use curve25519_dalek::scalar::Scalar; +use curve25519_dalek::{ + constants::{BASEPOINT_ORDER, RISTRETTO_BASEPOINT_POINT}, + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::Identity, +}; +use rand_core::{CryptoRng, RngCore}; use sha2::{digest::Update, Digest, Sha512}; -pub mod batch; -mod error; -pub mod frost; -// mod messages; -pub(crate) mod signature; -mod signing_key; -mod verification_key; +use frost_core::{frost, Ciphersuite, Field, Group}; -pub use error::Error; -pub use signature::Signature; -pub use signing_key::SigningKey; -pub use verification_key::{VerificationKey, VerificationKeyBytes}; +#[cfg(test)] +mod tests; + +pub use frost_core::Error; + +#[derive(Clone, Copy)] +/// An implementation of the FROST ciphersuite scalar field. +pub struct RistrettoScalarField; + +impl Field for RistrettoScalarField { + type Scalar = Scalar; + + type Serialization = [u8; 32]; + + fn zero() -> Self::Scalar { + Scalar::zero() + } + + fn one() -> Self::Scalar { + Scalar::one() + } + + fn invert(scalar: &Self::Scalar) -> Result { + // [`curve25519_dalek::scalar::Scalar`]'s Eq/PartialEq does a constant-time comparison using + // `ConstantTimeEq` + if *scalar == ::zero() { + Err(Error::InvalidZeroScalar) + } else { + Ok(scalar.invert()) + } + } + + fn random(rng: &mut R) -> Self::Scalar { + Scalar::random(rng) + } + + fn random_nonzero(rng: &mut R) -> Self::Scalar { + loop { + let scalar = Scalar::random(rng); + + // This impl of `Eq` calls to `ConstantTimeEq` under the hood + if scalar != Scalar::zero() { + return scalar; + } + } + } + + fn serialize(scalar: &Self::Scalar) -> Self::Serialization { + scalar.to_bytes() + } + + fn deserialize(buf: &Self::Serialization) -> Result { + match Scalar::from_canonical_bytes(*buf) { + Some(s) => Ok(s), + None => Err(Error::MalformedScalar), + } + } +} + +#[derive(Clone, Copy, PartialEq)] +/// An implementation of the FROST ciphersuite group. +pub struct RistrettoGroup; + +impl Group for RistrettoGroup { + type Field = RistrettoScalarField; + + type Element = RistrettoPoint; + + type Serialization = [u8; 32]; + + fn order() -> ::Scalar { + BASEPOINT_ORDER + } + + fn cofactor() -> ::Scalar { + Scalar::one() + } + + fn identity() -> Self::Element { + RistrettoPoint::identity() + } + + fn generator() -> Self::Element { + RISTRETTO_BASEPOINT_POINT + } + + fn serialize(element: &Self::Element) -> Self::Serialization { + element.compress().to_bytes() + } + + fn deserialize(buf: &Self::Serialization) -> Result { + match CompressedRistretto::from_slice(buf.as_ref()).decompress() { + Some(point) => Ok(point), + None => Err(Error::MalformedElement), + } + } +} /// Context string 'FROST-RISTRETTO255-SHA512' from the ciphersuite in the [spec] /// -/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-01.txt +/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.txt const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512"; -/// H1 for FROST(ristretto255, SHA-512) -/// -/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash -pub(crate) fn H1(m: &[u8]) -> [u8; 64] { - let h = Sha512::new() - .chain(CONTEXT_STRING.as_bytes()) - .chain("rho") - .chain(m); +#[derive(Clone, Copy, PartialEq)] +/// An implementation of the FROST ciphersuite Ristretto255-SHA512. +pub struct Ristretto255Sha512; - let mut output = [0u8; 64]; - output.copy_from_slice(h.finalize().as_slice()); - output +impl Ciphersuite for Ristretto255Sha512 { + type Group = RistrettoGroup; + + type HashOutput = [u8; 64]; + + type SignatureSerialization = [u8; 64]; + + /// H1 for FROST(ristretto255, SHA-512) + /// + /// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash + fn H1(m: &[u8]) -> <::Field as Field>::Scalar { + let h = Sha512::new() + .chain(CONTEXT_STRING.as_bytes()) + .chain("rho") + .chain(m); + + let mut output = [0u8; 64]; + output.copy_from_slice(h.finalize().as_slice()); + <::Field as Field>::Scalar::from_bytes_mod_order_wide(&output) + } + + /// H2 for FROST(ristretto255, SHA-512) + /// + /// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash + fn H2(m: &[u8]) -> <::Field as Field>::Scalar { + let h = Sha512::new() + .chain(CONTEXT_STRING.as_bytes()) + .chain("chal") + .chain(m); + + let mut output = [0u8; 64]; + output.copy_from_slice(h.finalize().as_slice()); + <::Field as Field>::Scalar::from_bytes_mod_order_wide(&output) + } + + /// H3 for FROST(ristretto255, SHA-512) + /// + /// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash + fn H3(m: &[u8]) -> Self::HashOutput { + let h = Sha512::new() + .chain(CONTEXT_STRING.as_bytes()) + .chain("digest") + .chain(m); + + let mut output = [0u8; 64]; + output.copy_from_slice(h.finalize().as_slice()); + output + } } -/// H2 for FROST(ristretto255, SHA-512) -/// -/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash -pub(crate) fn H2(m: &[u8]) -> [u8; 64] { - let h = Sha512::new() - .chain(CONTEXT_STRING.as_bytes()) - .chain("chal") - .chain(m); +type R = Ristretto255Sha512; - let mut output = [0u8; 64]; - output.copy_from_slice(h.finalize().as_slice()); - output +/// +pub mod keys { + use super::*; + + /// + pub fn keygen_with_dealer( + num_signers: u8, + threshold: u8, + mut rng: RNG, + ) -> Result<(Vec, PublicKeyPackage), &'static str> { + frost::keys::keygen_with_dealer(num_signers, threshold, &mut rng) + } + + /// + pub type SharePackage = frost::keys::SharePackage; + + /// + pub type KeyPackage = frost::keys::KeyPackage; + + /// + pub type PublicKeyPackage = frost::keys::PublicKeyPackage; } -/// H3 for FROST(ristretto255, SHA-512) /// -/// Yes, this is just an alias for SHA-512. -/// -/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash -pub(crate) fn H3(m: &[u8]) -> [u8; 64] { - let h = Sha512::new() - .chain(CONTEXT_STRING.as_bytes()) - .chain("digest") - .chain(m); +pub mod round1 { + use super::*; + /// + pub type SigningNonces = frost::round1::SigningNonces; - let mut output = [0u8; 64]; - output.copy_from_slice(h.finalize().as_slice()); - output + /// + pub type SigningCommitments = frost::round1::SigningCommitments; + + /// + pub fn preprocess( + num_nonces: u8, + participant_index: u16, + rng: &mut RNG, + ) -> (Vec, Vec) + where + RNG: CryptoRng + RngCore, + { + frost::round1::preprocess::(num_nonces, participant_index, rng) + } } -/// Generates the challenge as is required for Schnorr signatures. /// -/// Deals in bytes, so that [FROST] and singleton signing and verification can use it with different -/// types. +pub type SigningPackage = frost::SigningPackage; + /// -/// This is the only invocation of the H2 hash function from the [RFC]. -/// -/// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.6 -/// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-3.2 -fn generate_challenge(R_bytes: &[u8; 32], pubkey_bytes: &[u8; 32], msg: &[u8]) -> Scalar { - let mut preimage = vec![]; +pub mod round2 { + use super::*; - preimage.extend_from_slice(R_bytes); - preimage.extend_from_slice(pubkey_bytes); - preimage.extend_from_slice(msg); + /// + pub type SignatureShare = frost::round2::SignatureShare; - let challenge_wide = H2(&preimage[..]); + /// + pub type SigningPackage = frost::SigningPackage; - Scalar::from_bytes_mod_order_wide(&challenge_wide) + /// + pub fn sign( + signing_package: &SigningPackage, + signer_nonces: &round1::SigningNonces, + key_package: &keys::KeyPackage, + ) -> Result { + frost::round2::sign(&signing_package, signer_nonces, key_package) + } } + +/// +pub type Signature = frost_core::Signature; + +/// +pub fn aggregate( + signing_package: &round2::SigningPackage, + signature_shares: &[round2::SignatureShare], + pubkeys: &keys::PublicKeyPackage, +) -> Result { + frost::aggregate(&signing_package, &signature_shares[..], &pubkeys) +} + +/// +pub type SigningKey = frost_core::SigningKey; + +/// +pub type VerifyingKey = frost_core::VerifyingKey; diff --git a/frost-ristretto255/src/messages.rs b/frost-ristretto255/src/messages.rs deleted file mode 100644 index bf56fe0..0000000 --- a/frost-ristretto255/src/messages.rs +++ /dev/null @@ -1,296 +0,0 @@ -//! The FROST communication messages specified in [RFC-001] -//! -//! [RFC-001]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md - -use std::collections::BTreeMap; - -use curve25519_dalek::ristretto::CompressedRistretto; -#[cfg(test)] -use proptest_derive::Arbitrary; -use serde::{Deserialize, Serialize}; - -#[cfg(test)] -mod arbitrary; -mod constants; -mod serialize; -#[cfg(test)] -mod tests; -mod validate; - -use crate::{frost, signature, verification_key}; - -/// Define our own `Secret` type instead of using [`frost::Secret`]. -/// -/// The serialization design specifies that `Secret` is a -/// [`curve25519_dalek::scalar::Scalar`] that uses: "a 32-byte little-endian -/// canonical representation". -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct Secret([u8; 32]); - -/// Define our own `Commitment` type instead of using [`frost::Commitment`]. -/// -/// The serialization design specifies that `Commitment` is an -/// [`RistrettoPoint`] that uses: "a 32-byte little-endian canonical -/// representation". -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Copy)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct Commitment([u8; 32]); - -impl From for Commitment { - fn from(value: frost::CoefficientCommitment) -> Commitment { - Commitment(value.0.compress().to_bytes()) - } -} - -impl From for Commitment { - fn from(value: frost::NonceCommitment) -> Commitment { - Commitment(value.0.compress().to_bytes()) - } -} - -/// Define our own `GroupCommitment` type instead of using -/// [`frost::GroupCommitment`]. -/// -/// The serialization design specifies that `GroupCommitment` is an -/// [`RistrettoPoint`] that uses: "a 32-byte little-endian canonical -/// representation". -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct GroupCommitment([u8; 32]); - -/// Define our own `SignatureResponse` type instead of using -/// [`frost::SignatureResponse`]. -/// -/// The serialization design specifies that `SignatureResponse` is a -/// [`jubjub::Scalar`] that uses: "a 32-byte little-endian canonical -/// representation". -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct SignatureResponse([u8; 64]); - -impl From for SignatureResponse { - fn from(value: signature::Signature) -> SignatureResponse { - SignatureResponse(value.into()) - } -} - -impl From for GroupCommitment { - fn from(value: signature::Signature) -> GroupCommitment { - GroupCommitment(value.R_bytes) - } -} - -/// Define our own `VerificationKey` type instead of using -/// [`verification_key::VerificationKey`]. -/// -/// The serialization design specifies that `VerificationKey` is a -/// [`verification_key::VerificationKeyBytes`] that uses: "a 32-byte -/// little-endian canonical representation". -#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct VerificationKey([u8; 32]); - -impl From for VerificationKey { - fn from(value: verification_key::VerificationKey) -> VerificationKey { - VerificationKey(<[u8; 32]>::from(value)) - } -} - -/// The data required to serialize a frost message. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct Message { - header: Header, - payload: Payload, -} - -/// The data required to serialize the common header fields for every message. -/// -/// Note: the `msg_type` is derived from the `payload` enum variant. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)] -pub struct Header { - version: MsgVersion, - sender: ParticipantId, - receiver: ParticipantId, -} - -/// The data required to serialize the payload for a message. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub enum Payload { - SharePackage(SharePackage), - SigningCommitments(SigningCommitments), - SigningPackage(SigningPackage), - SignatureShare(SignatureShare), - AggregateSignature(AggregateSignature), -} - -/// The numeric values used to identify each [`Payload`] variant during -/// serialization. -// TODO: spec says `#[repr(u8)]` but it is incompatible with `bincode` -// manual serialization and deserialization is needed. -#[repr(u32)] -#[non_exhaustive] -#[derive(Serialize, Deserialize, Debug, PartialEq)] -enum MsgType { - SharePackage, - SigningCommitments, - SigningPackage, - SignatureShare, - AggregateSignature, -} - -/// The numeric values used to identify the protocol version during -/// serialization. -#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)] -pub struct MsgVersion(u8); - -/// The numeric values used to identify each participant during serialization. -/// -/// In the `frost` module, participant ID `0` should be invalid. -/// But in serialization, we want participants to be indexed from `0..n`, -/// where `n` is the number of participants. -/// This helps us look up their shares and commitments in serialized arrays. -/// So in serialization, we assign the dealer and aggregator the highest IDs, -/// and mark those IDs as invalid for signers. -/// -/// "When performing Shamir secret sharing, a polynomial `f(x)` is used to -/// generate each party’s share of the secret. The actual secret is `f(0)` and -/// the party with ID `i` will be given a share with value `f(i)`. -/// Since a DKG may be implemented in the future, we recommend that the ID `0` -/// be declared invalid." https://raw.githubusercontent.com/ZcashFoundation/redjubjub/main/zcash-frost-audit-report-20210323.pdf#d -#[derive(PartialEq, Eq, Hash, PartialOrd, Debug, Copy, Clone, Ord)] -pub enum ParticipantId { - /// A serialized participant ID for a signer. - /// - /// Must be less than or equal to [`constants::MAX_SIGNER_PARTICIPANT_ID`]. - Signer(u16), - /// The fixed participant ID for the dealer as defined in - /// [`constants::DEALER_PARTICIPANT_ID`]. - Dealer, - /// The fixed participant ID for the aggregator as defined in - /// [`constants::AGGREGATOR_PARTICIPANT_ID`]. - Aggregator, -} - -impl From for u16 { - fn from(value: ParticipantId) -> u16 { - match value { - // An id of `0` is invalid in frost. - ParticipantId::Signer(id) => id + 1, - ParticipantId::Dealer => constants::DEALER_PARTICIPANT_ID, - ParticipantId::Aggregator => constants::AGGREGATOR_PARTICIPANT_ID, - } - } -} - -/// The data required to serialize [`frost::SharePackage`]. -/// -/// The dealer sends this message to each signer for this round. -/// With this, the signer should be able to build a [`SharePackage`] and use -/// the [`frost::sign()`] function. -/// -/// Note: [`frost::SharePackage::public`] can be calculated from -/// [`SharePackage::secret_share`]. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct SharePackage { - /// The public signing key that represents the entire group: - /// [`frost::SharePackage::group_public`]. - group_public: VerificationKey, - /// This participant's secret key share: [`frost::SharePackage::share`]. - secret_share: Secret, - /// The commitments to the coefficients for our secret polynomial _f_, - /// used to generate participants' key shares. Participants use these to - /// perform verifiable secret sharing. - /// Share packages that contain duplicate or missing [`ParticipantId`]s are - /// invalid. [`ParticipantId`]s must be serialized in ascending numeric - /// order. - share_commitment: BTreeMap, -} - -/// The data required to serialize [`frost::SigningCommitments`]. -/// -/// Each signer must send this message to the aggregator. -/// A signing commitment from the first round of the signing protocol. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct SigningCommitments { - /// The hiding point: [`frost::SigningCommitments::hiding`] - hiding: Commitment, - /// The binding point: [`frost::SigningCommitments::binding`] - binding: Commitment, -} - -/// The data required to serialize [`frost::SigningPackage`]. -/// -/// The aggregator decides what message is going to be signed and -/// sends it to each signer with all the commitments collected. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct SigningPackage { - /// The collected commitments for each signer as a hashmap of - /// unique participant identifiers: - /// [`frost::SigningPackage::signing_commitments`] - /// - /// Signing packages that contain duplicate or missing [`ParticipantId`]s - /// are invalid. - signing_commitments: BTreeMap, - /// The message to be signed: [`frost::SigningPackage::message`]. - /// - /// Each signer should perform protocol-specific verification on the - /// message. - message: Vec, -} - -impl From for frost::SigningPackage { - fn from(value: SigningPackage) -> frost::SigningPackage { - let mut signing_commitments = Vec::new(); - for (participant_id, commitment) in &value.signing_commitments { - let s = frost::SigningCommitments { - index: u16::from(*participant_id), - hiding: frost::NonceCommitment( - CompressedRistretto::from_slice(&commitment.hiding.0) - .decompress() - .unwrap(), - ), - binding: frost::NonceCommitment( - CompressedRistretto::from_slice(&commitment.binding.0) - .decompress() - .unwrap(), - ), - }; - signing_commitments.push(s); - } - - frost::SigningPackage::new(signing_commitments, value.message) - } -} - -/// The data required to serialize [`frost::SignatureShare`]. -/// -/// Each signer sends their signatures to the aggregator who is going to collect -/// them and generate a final spend signature. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct SignatureShare { - /// This participant's signature over the message: - /// [`frost::SignatureShare::signature`] - signature: SignatureResponse, -} - -/// The data required to serialize a successful output from -/// [`frost::aggregate()`]. -/// -/// The final signature is broadcasted by the aggregator to all signers. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct AggregateSignature { - /// The aggregated group commitment: [`signature::Signature::R_bytes`] - /// returned by [`frost::aggregate()`] - group_commitment: GroupCommitment, - /// A plain Schnorr signature created by summing all the signature shares: - /// [`signature::Signature::z_bytes`] returned by [`frost::aggregate()`] - schnorr_signature: SignatureResponse, -} diff --git a/frost-ristretto255/src/messages/arbitrary.rs b/frost-ristretto255/src/messages/arbitrary.rs deleted file mode 100644 index ccca060..0000000 --- a/frost-ristretto255/src/messages/arbitrary.rs +++ /dev/null @@ -1,55 +0,0 @@ -use proptest::{ - arbitrary::{any, Arbitrary}, - prelude::*, -}; - -use super::*; - -impl Arbitrary for Header { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - ( - any::(), - any::(), - any::(), - ) - .prop_filter( - "Sender and receiver participant IDs can not be the same", - |(_, sender, receiver)| sender != receiver, - ) - .prop_map(|(version, sender, receiver)| Header { - version, - sender, - receiver, - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -impl Arbitrary for MsgVersion { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - Just(constants::BASIC_FROST_SERIALIZATION).boxed() - } - - type Strategy = BoxedStrategy; -} - -impl Arbitrary for ParticipantId { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - prop_oneof![ - (u16::MIN..=constants::MAX_SIGNER_PARTICIPANT_ID).prop_map(ParticipantId::Signer), - Just(ParticipantId::Dealer), - Just(ParticipantId::Aggregator), - ] - .boxed() - } - - type Strategy = BoxedStrategy; -} diff --git a/frost-ristretto255/src/messages/constants.rs b/frost-ristretto255/src/messages/constants.rs deleted file mode 100644 index f47ae56..0000000 --- a/frost-ristretto255/src/messages/constants.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Definitions of constants. - -use super::MsgVersion; - -/// The first version of FROST messages -pub const BASIC_FROST_SERIALIZATION: MsgVersion = MsgVersion(0); - -/// The fixed participant ID for the dealer. -pub const DEALER_PARTICIPANT_ID: u16 = u16::MAX - 1; - -/// The fixed participant ID for the aggregator. -pub const AGGREGATOR_PARTICIPANT_ID: u16 = u16::MAX; - -/// The maximum `ParticipantId::Signer` in this serialization format. -/// -/// We reserve two participant IDs for the dealer and aggregator. -pub const MAX_SIGNER_PARTICIPANT_ID: u16 = u16::MAX - 2; - -/// The maximum number of signers -/// -/// By protocol the number of signers can'e be more than 255. -pub const MAX_SIGNERS: u8 = 255; - -/// The maximum length of a Zcash message, in bytes. -pub const ZCASH_MAX_PROTOCOL_MESSAGE_LEN: usize = 2 * 1024 * 1024; - -/// The minimum number of signers of any FROST setup. -pub const MIN_SIGNERS: usize = 2; - -/// The minimum number of signers that must sign. -pub const MIN_THRESHOLD: usize = 2; diff --git a/frost-ristretto255/src/messages/serialize.rs b/frost-ristretto255/src/messages/serialize.rs deleted file mode 100644 index 57f392f..0000000 --- a/frost-ristretto255/src/messages/serialize.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Serialization rules specified in [RFC-001#Serialize-Deserialize] -//! -//! We automatically serialize and deserialize using serde derivations where possible. -//! Sometimes we need to implement ourselves, this file holds that code. -//! -//! [RFC-001#Serialize-Deserialize]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md#serializationdeserialization - -use serde::ser::{Serialize, Serializer}; - -use serde::de::{self, Deserialize, Deserializer, Visitor}; - -use super::constants::{ - AGGREGATOR_PARTICIPANT_ID, DEALER_PARTICIPANT_ID, MAX_SIGNER_PARTICIPANT_ID, -}; -use super::*; - -use std::fmt; - -impl Serialize for ParticipantId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - ParticipantId::Signer(id) => { - assert!(id <= MAX_SIGNER_PARTICIPANT_ID); - serializer.serialize_u16(id) - } - ParticipantId::Dealer => serializer.serialize_u16(DEALER_PARTICIPANT_ID), - ParticipantId::Aggregator => serializer.serialize_u16(AGGREGATOR_PARTICIPANT_ID), - } - } -} - -struct ParticipantIdVisitor; - -impl<'de> Visitor<'de> for ParticipantIdVisitor { - type Value = ParticipantId; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str( - format!("an integer between {} and {}", std::u16::MIN, std::u16::MAX).as_str(), - ) - } - - fn visit_u16(self, value: u16) -> Result - where - E: de::Error, - { - // Note: deserialization can't fail, because all values are valid. - if value == DEALER_PARTICIPANT_ID { - Ok(ParticipantId::Dealer) - } else if value == AGGREGATOR_PARTICIPANT_ID { - Ok(ParticipantId::Aggregator) - } else { - Ok(ParticipantId::Signer(value)) - } - } -} - -impl<'de> Deserialize<'de> for ParticipantId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_u16(ParticipantIdVisitor) - } -} diff --git a/frost-ristretto255/src/messages/tests.rs b/frost-ristretto255/src/messages/tests.rs deleted file mode 100644 index f0f320e..0000000 --- a/frost-ristretto255/src/messages/tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod integration; -mod prop; diff --git a/frost-ristretto255/src/messages/tests/integration.rs b/frost-ristretto255/src/messages/tests/integration.rs deleted file mode 100644 index 9505352..0000000 --- a/frost-ristretto255/src/messages/tests/integration.rs +++ /dev/null @@ -1,815 +0,0 @@ -use std::convert::TryFrom; - -use rand::thread_rng; - -use crate::{ - frost, - messages::{ - validate::{MsgErr, Validate}, - *, - }, - verification_key, -}; - -#[test] -fn validate_version() { - // A version number that we expect to be always invalid - const INVALID_VERSION: u8 = u8::MAX; - - let setup = basic_setup(); - - let header = Header { - version: MsgVersion(INVALID_VERSION), - sender: setup.dealer, - receiver: setup.signer1, - }; - - let validate = Validate::validate(&header); - assert_eq!(validate, Err(MsgErr::WrongVersion)); - - let validate = Validate::validate(&Header { - version: constants::BASIC_FROST_SERIALIZATION, - sender: setup.dealer, - receiver: setup.signer1, - }) - .err(); - - assert_eq!(validate, None); -} - -#[test] -fn validate_sender_receiver() { - let setup = basic_setup(); - - let header = Header { - version: constants::BASIC_FROST_SERIALIZATION, - sender: setup.signer1, - receiver: setup.signer1, - }; - - let validate = Validate::validate(&header); - assert_eq!(validate, Err(MsgErr::SameSenderAndReceiver)); -} - -#[test] -fn validate_share_package() { - let setup = basic_setup(); - let (mut shares, pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); - - let header = create_valid_header(setup.signer1, setup.signer2); - - let group_public = VerificationKey::from( - verification_key::VerificationKey::try_from(pubkeys.group_public.bytes).unwrap(), - ); - let secret_share = Secret(shares[0].secret_share.value.0.to_bytes()); - - let participants = vec![setup.signer1, setup.signer2]; - shares.truncate(2); - let share_commitment = generate_share_commitment(&shares, participants); - - let payload = Payload::SharePackage(SharePackage { - group_public, - secret_share, - share_commitment, - }); - let validate_payload = Validate::validate(&payload); - let valid_payload = validate_payload.expect("a valid payload").clone(); - - let message = Message { - header, - payload: valid_payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::SenderMustBeDealer)); - - // change the header - let header = create_valid_header(setup.dealer, setup.aggregator); - - let message = Message { - header, - payload: valid_payload, - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); - - let participants = vec![setup.signer1]; - shares.truncate(1); - let mut share_commitment = generate_share_commitment(&shares, participants); - - // change the payload to have only 1 commitment - let payload = Payload::SharePackage(SharePackage { - group_public, - secret_share, - share_commitment: share_commitment.clone(), - }); - let validate_payload = Validate::validate(&payload); - assert_eq!( - validate_payload, - Err(MsgErr::NotEnoughCommitments(constants::MIN_SIGNERS)) - ); - - // build and use too many commitments - for i in 2..constants::MAX_SIGNERS as u16 + 2 { - share_commitment.insert( - ParticipantId::Signer(i), - share_commitment.clone()[&setup.signer1], - ); - } - let payload = Payload::SharePackage(SharePackage { - group_public, - secret_share, - share_commitment, - }); - let validate_payload = Validate::validate(&payload); - assert_eq!(validate_payload, Err(MsgErr::TooManyCommitments)); -} - -#[test] -fn serialize_share_package() { - let setup = basic_setup(); - - let (mut shares, _pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); - - let header = create_valid_header(setup.dealer, setup.signer1); - - let group_public = VerificationKey::from( - verification_key::VerificationKey::try_from(shares[0].group_public.bytes).unwrap(), - ); - let secret_share = Secret(shares[0].secret_share.value.0.to_bytes()); - - let participants = vec![setup.signer1]; - shares.truncate(1); - let share_commitment = generate_share_commitment(&shares, participants); - - let payload = Payload::SharePackage(SharePackage { - group_public, - secret_share, - share_commitment: share_commitment.clone(), - }); - - let message = Message { - header, - payload: payload.clone(), - }; - - // check general structure and header serialization/deserialization - serialize_message(message, setup.dealer, setup.signer1); - - // check payload serialization/deserialization - let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); - // check the message type is correct - let deserialized_msg_type: MsgType = - bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); - assert_eq!(deserialized_msg_type, MsgType::SharePackage); - - // remove the msg_type from the the payload - payload_serialized_bytes = payload_serialized_bytes[4..payload_serialized_bytes.len()].to_vec(); - - // group_public is 32 bytes - let deserialized_group_public: VerificationKey = - bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); - // secret share is 32 bytes - let deserialized_secret_share: Secret = - bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); - // rest of the message is the map: 32(Commitment) + 8(ParticipantId) + 8(map.len()) - let deserialized_share_commitment: BTreeMap = - bincode::deserialize(&payload_serialized_bytes[64..112]).unwrap(); - - // check the map len - let deserialized_map_len: u64 = - bincode::deserialize(&payload_serialized_bytes[64..72]).unwrap(); - assert_eq!(deserialized_map_len, 1); - - // no leftover bytes - assert_eq!(payload_serialized_bytes.len(), 112); - - assert_eq!(deserialized_group_public, group_public); - assert_eq!(deserialized_secret_share, secret_share); - assert_eq!(deserialized_share_commitment, share_commitment); -} - -#[test] -fn validate_signingcommitments() { - let mut setup = basic_setup(); - - let (_nonce, commitment) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - - let header = create_valid_header(setup.aggregator, setup.signer2); - - let payload = Payload::SigningCommitments(SigningCommitments { - hiding: Commitment::from(commitment[0].hiding), - binding: Commitment::from(commitment[0].binding), - }); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::SenderMustBeSigner)); - - // change the header - let header = create_valid_header(setup.signer1, setup.signer2); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeAggregator)); - - // change the header to be valid - let header = create_valid_header(setup.signer1, setup.aggregator); - - let validate_message = Validate::validate(&Message { header, payload }).err(); - - assert_eq!(validate_message, None); -} - -#[test] -fn serialize_signingcommitments() { - let mut setup = basic_setup(); - - let (_nonce, commitment) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - - let header = create_valid_header(setup.aggregator, setup.signer1); - - let hiding = Commitment::from(commitment[0].hiding); - let binding = Commitment::from(commitment[0].binding); - - let payload = Payload::SigningCommitments(SigningCommitments { hiding, binding }); - - let message = Message { - header, - payload: payload.clone(), - }; - - // check general structure serialization/deserialization - serialize_message(message, setup.aggregator, setup.signer1); - - // check payload serialization/deserialization - let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); - // check the message type is correct - let deserialized_msg_type: MsgType = - bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); - assert_eq!(deserialized_msg_type, MsgType::SigningCommitments); - - // remove the msg_type from the the payload - payload_serialized_bytes = payload_serialized_bytes[4..payload_serialized_bytes.len()].to_vec(); - - // hiding is 32 bytes - let deserialized_hiding: Commitment = - bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); - // binding is 43 bytes kore - let deserialized_binding: Commitment = - bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); - - // no leftover bytes - assert_eq!(payload_serialized_bytes.len(), 64); - - assert_eq!(deserialized_hiding, hiding); - assert_eq!(deserialized_binding, binding); -} - -#[test] -fn validate_signingpackage() { - let mut setup = basic_setup(); - - let (_nonce, commitment1) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - let (_nonce, commitment2) = frost::preprocess(1, u16::from(setup.signer2), &mut setup.rng); - - let header = create_valid_header(setup.signer1, setup.signer2); - - // try with only 1 commitment - let commitments = vec![commitment1[0]]; - let participants = vec![setup.signer1]; - let signing_commitments = create_signing_commitments(commitments, participants); - - let payload = Payload::SigningPackage(SigningPackage { - signing_commitments: signing_commitments.clone(), - message: "hola".as_bytes().to_vec(), - }); - let validate_payload = Validate::validate(&payload); - assert_eq!( - validate_payload, - Err(MsgErr::NotEnoughCommitments(constants::MIN_SIGNERS)) - ); - - // add too many commitments - let mut big_signing_commitments = BTreeMap::::new(); - for i in 0..constants::MAX_SIGNERS as u16 + 1 { - big_signing_commitments.insert( - ParticipantId::Signer(i), - signing_commitments[&setup.signer1].clone(), - ); - } - let payload = Payload::SigningPackage(SigningPackage { - signing_commitments: big_signing_commitments, - message: "hola".as_bytes().to_vec(), - }); - let validate_payload = Validate::validate(&payload); - assert_eq!(validate_payload, Err(MsgErr::TooManyCommitments)); - - // change to 2 commitments - let commitments = vec![commitment1[0], commitment2[0]]; - let participants = vec![setup.signer1, setup.signer2]; - let signing_commitments = create_signing_commitments(commitments, participants); - - let big_message = [0u8; constants::ZCASH_MAX_PROTOCOL_MESSAGE_LEN + 1].to_vec(); - let payload = Payload::SigningPackage(SigningPackage { - signing_commitments: signing_commitments.clone(), - message: big_message, - }); - let validate_payload = Validate::validate(&payload); - assert_eq!(validate_payload, Err(MsgErr::MsgTooBig)); - - let message = Message { - header, - payload: payload.clone(), - }; - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::SenderMustBeAggregator)); - - // change header - let header = create_valid_header(setup.aggregator, setup.dealer); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); - - let header = create_valid_header(setup.aggregator, setup.signer1); - let payload = Payload::SigningPackage(SigningPackage { - signing_commitments, - message: "hola".as_bytes().to_vec(), - }); - - let validate_message = Validate::validate(&Message { header, payload }).err(); - assert_eq!(validate_message, None); -} - -#[test] -fn serialize_signingpackage() { - let mut setup = basic_setup(); - - let (_nonce, commitment1) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - let (_nonce, commitment2) = frost::preprocess(1, u16::from(setup.signer2), &mut setup.rng); - - let header = create_valid_header(setup.aggregator, setup.signer1); - - let commitments = vec![commitment1[0], commitment2[0]]; - let participants = vec![setup.signer1, setup.signer2]; - let signing_commitments = create_signing_commitments(commitments, participants); - - let payload = Payload::SigningPackage(SigningPackage { - signing_commitments: signing_commitments.clone(), - message: "hola".as_bytes().to_vec(), - }); - - let message = Message { - header, - payload: payload.clone(), - }; - - // check general structure serialization/deserialization - serialize_message(message, setup.aggregator, setup.signer1); - - // check payload serialization/deserialization - let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); - - // check the message type is correct - let deserialized_msg_type: MsgType = - bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); - assert_eq!(deserialized_msg_type, MsgType::SigningPackage); - - // remove the msg_type from the the payload - payload_serialized_bytes = payload_serialized_bytes[4..payload_serialized_bytes.len()].to_vec(); - - // check the map len - let deserialized_map_len: u64 = bincode::deserialize(&payload_serialized_bytes[0..8]).unwrap(); - assert_eq!(deserialized_map_len, 2); - - // Each SigningCommitment is 64 bytes and the ParticipantId is 8 bytes. - // This is multiplied by the map len, also include the map len bytes. - let deserialized_signing_commitments: BTreeMap = - bincode::deserialize(&payload_serialized_bytes[0..152]).unwrap(); - - // Message is from the end of the map up to the end of the message. - let deserialized_message: Vec = - bincode::deserialize(&payload_serialized_bytes[152..payload_serialized_bytes.len()]) - .unwrap(); - - // no leftover bytes - assert_eq!(payload_serialized_bytes.len(), 164); - - assert_eq!(deserialized_signing_commitments, signing_commitments); - assert_eq!(deserialized_message, "hola".as_bytes().to_vec()); -} - -#[test] -fn validate_signatureshare() { - let mut setup = basic_setup(); - - // signers and aggregator should have this data from `SharePackage` - let (shares, _pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); - - // create a signing package, this is done in the aggregator side. - // the signers should have this data from `SigningPackage` - let (nonce1, commitment1) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - let (_nonce2, commitment2) = frost::preprocess(1, u16::from(setup.signer2), &mut setup.rng); - let commitments = vec![commitment1[0], commitment2[0]]; - let participants = vec![setup.signer1, setup.signer2]; - let signing_commitments = create_signing_commitments(commitments, participants); - - let signing_package = frost::SigningPackage::from(SigningPackage { - signing_commitments, - message: "hola".as_bytes().to_vec(), - }); - - // here we get started with the `SignatureShare` message. - let signature_share = frost::sign( - &signing_package, - &nonce1[0], - &commitment1[0], - &frost::KeyPackage::try_from(shares[0].clone()).unwrap(), - ) - .unwrap(); - - // this header is invalid - let header = create_valid_header(setup.aggregator, setup.signer1); - - let payload = Payload::SignatureShare(SignatureShare { - signature: signature_share.signature, - }); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::SenderMustBeSigner)); - - // change the header, still invalid. - let header = create_valid_header(setup.signer1, setup.signer2); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeAggregator)); - - // change the header to be valid - let header = create_valid_header(setup.signer1, setup.aggregator); - - let validate_message = Validate::validate(&Message { header, payload }).err(); - - assert_eq!(validate_message, None); -} - -#[test] -fn serialize_signatureshare() { - let mut setup = basic_setup(); - - // signers and aggregator should have this data from `SharePackage` - let (shares, _pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); - - // create a signing package, this is done in the aggregator side. - // the signers should have this data from `SigningPackage` - let (nonce1, commitment1) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - let (_nonce2, commitment2) = frost::preprocess(1, u16::from(setup.signer2), &mut setup.rng); - let commitments = vec![commitment1[0], commitment2[0]]; - let participants = vec![setup.signer1, setup.signer2]; - let signing_commitments = create_signing_commitments(commitments, participants); - - let signing_package = frost::SigningPackage::from(SigningPackage { - signing_commitments, - message: "hola".as_bytes().to_vec(), - }); - - // here we get started with the `SignatureShare` message. - let signature_share = frost::sign( - &signing_package, - &nonce1[0], - &frost::KeyPackage::try_from(shares[0].clone()).unwrap(), - ) - .unwrap(); - - // valid header - let header = create_valid_header(setup.signer1, setup.aggregator); - - let signature = SignatureResponse(signature_share.signature.0.to_bytes()); - let payload = Payload::SignatureShare(SignatureShare { signature }); - - let message = Message { - header, - payload: payload.clone(), - }; - - // check general structure serialization/deserialization - serialize_message(message, setup.signer1, setup.aggregator); - - // check payload serialization/deserialization - let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); - - // check the message type is correct - let deserialized_msg_type: MsgType = - bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); - assert_eq!(deserialized_msg_type, MsgType::SignatureShare); - - // remove the msg_type from the the payload - payload_serialized_bytes = payload_serialized_bytes[4..payload_serialized_bytes.len()].to_vec(); - - // signature is 32 bytes - let deserialized_signature: SignatureResponse = - bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); - - // no leftover bytes - assert_eq!(payload_serialized_bytes.len(), 32); - - assert_eq!(deserialized_signature, signature); -} - -#[test] -fn validate_aggregatesignature() { - let (setup, group_signature_res) = full_setup(); - - // this header is invalid - let header = create_valid_header(setup.signer1, setup.aggregator); - - let payload = Payload::AggregateSignature(AggregateSignature { - group_commitment: GroupCommitment::from(group_signature_res), - schnorr_signature: SignatureResponse::from(group_signature_res), - }); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::SenderMustBeAggregator)); - - // change the header, still invalid. - let header = create_valid_header(setup.aggregator, setup.dealer); - - let message = Message { - header, - payload: payload.clone(), - }; - - let validate_message = Validate::validate(&message); - assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); - - // change the header to be valid - let header = create_valid_header(setup.aggregator, setup.signer1); - - let validate_message = Validate::validate(&Message { header, payload }).err(); - - assert_eq!(validate_message, None); -} - -#[test] -fn serialize_aggregatesignature() { - let (setup, group_signature_res) = full_setup(); - - let header = create_valid_header(setup.aggregator, setup.signer1); - - let group_commitment = GroupCommitment::from(group_signature_res); - let schnorr_signature = SignatureResponse::from(group_signature_res); - let payload = Payload::AggregateSignature(AggregateSignature { - group_commitment, - schnorr_signature, - }); - - let message = Message { - header, - payload: payload.clone(), - }; - - // check general structure serialization/deserialization - serialize_message(message, setup.aggregator, setup.signer1); - - // check payload serialization/deserialization - let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); - - // check the message type is correct - let deserialized_msg_type: MsgType = - bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); - assert_eq!(deserialized_msg_type, MsgType::AggregateSignature); - - // remove the msg_type from the the payload - payload_serialized_bytes = payload_serialized_bytes[4..payload_serialized_bytes.len()].to_vec(); - - // group_commitment is 32 bytes - let deserialized_group_commiment: GroupCommitment = - bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); - // schnorr_signature is 32 bytes - let deserialized_schnorr_signature: SignatureResponse = - bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); - - // no leftover bytes - assert_eq!(payload_serialized_bytes.len(), 64); - - assert_eq!(deserialized_group_commiment, group_commitment); - assert_eq!(deserialized_schnorr_signature, schnorr_signature); -} - -#[test] -fn btreemap() { - let mut setup = basic_setup(); - let mut map = BTreeMap::new(); - - let (_nonce, commitment) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng); - - let commitments = vec![commitment[0]]; - let participants = vec![setup.signer1]; - let signing_commitments = create_signing_commitments(commitments, participants); - - map.insert(ParticipantId::Signer(1), &signing_commitments); - map.insert(ParticipantId::Signer(2), &signing_commitments); - map.insert(ParticipantId::Signer(0), &signing_commitments); - - // Check the ascending order - let mut map_iter = map.iter(); - let (key, _) = map_iter.next().unwrap(); - assert_eq!(*key, ParticipantId::Signer(0)); - let (key, _) = map_iter.next().unwrap(); - assert_eq!(*key, ParticipantId::Signer(1)); - let (key, _) = map_iter.next().unwrap(); - assert_eq!(*key, ParticipantId::Signer(2)); - - // Add a repeated key - map.insert(ParticipantId::Signer(1), &signing_commitments); - // BTreeMap is not increasing - assert_eq!(map.len(), 3); -} - -// utility functions - -fn create_valid_header(sender: ParticipantId, receiver: ParticipantId) -> Header { - *Validate::validate(&Header { - version: constants::BASIC_FROST_SERIALIZATION, - sender, - receiver, - }) - .expect("always a valid header") -} - -fn serialize_header( - header_serialized_bytes: Vec, - sender: ParticipantId, - receiver: ParticipantId, -) { - let deserialized_version: MsgVersion = - bincode::deserialize(&header_serialized_bytes[0..1]).unwrap(); - let deserialized_sender: ParticipantId = - bincode::deserialize(&header_serialized_bytes[1..9]).unwrap(); - let deserialized_receiver: ParticipantId = - bincode::deserialize(&header_serialized_bytes[9..17]).unwrap(); - assert_eq!(deserialized_version, constants::BASIC_FROST_SERIALIZATION); - assert_eq!(deserialized_sender, sender); - assert_eq!(deserialized_receiver, receiver); -} - -fn serialize_message(message: Message, sender: ParticipantId, receiver: ParticipantId) { - let serialized_bytes = bincode::serialize(&message).unwrap(); - let deserialized_bytes: Message = bincode::deserialize(&serialized_bytes).unwrap(); - assert_eq!(message, deserialized_bytes); - - let serialized_json = serde_json::to_string(&message).unwrap(); - let deserialized_json: Message = serde_json::from_str(serialized_json.as_str()).unwrap(); - assert_eq!(message, deserialized_json); - - let header_serialized_bytes = bincode::serialize(&message.header).unwrap(); - serialize_header(header_serialized_bytes, sender, receiver); - - // make sure the message fields are in the right order - let message_serialized_bytes = bincode::serialize(&message).unwrap(); - let deserialized_header: Header = - bincode::deserialize(&message_serialized_bytes[0..17]).unwrap(); - let deserialized_payload: Payload = - bincode::deserialize(&message_serialized_bytes[17..message_serialized_bytes.len()]) - .unwrap(); - assert_eq!(deserialized_header, message.header); - assert_eq!(deserialized_payload, message.payload); -} - -struct Setup { - rng: rand::rngs::ThreadRng, - num_signers: u8, - threshold: u8, - dealer: ParticipantId, - aggregator: ParticipantId, - signer1: ParticipantId, - signer2: ParticipantId, -} - -fn basic_setup() -> Setup { - Setup { - rng: thread_rng(), - num_signers: 3, - threshold: 2, - dealer: ParticipantId::Dealer, - aggregator: ParticipantId::Aggregator, - signer1: ParticipantId::Signer(0), - signer2: ParticipantId::Signer(1), - } -} - -fn full_setup() -> (Setup, signature::Signature) { - let mut setup = basic_setup(); - - // aggregator creates the shares and pubkeys for this round - let (shares, pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); - - let mut nonces: std::collections::HashMap> = - std::collections::HashMap::with_capacity(setup.threshold as usize); - let mut commitments: Vec = - Vec::with_capacity(setup.threshold as usize); - - // aggregator generates nonces and signing commitments for each participant. - for participant_index in 1..=setup.threshold { - let (nonce, commitment) = frost::preprocess(1, participant_index as u16, &mut setup.rng); - nonces.insert(participant_index as u16, nonce); - commitments.push(commitment[0]); - } - - // aggregator generates a signing package - let mut signature_shares: Vec = - Vec::with_capacity(setup.threshold as usize); - let message = "message to sign".as_bytes().to_vec(); - let signing_package = frost::SigningPackage::new(commitments, message); - - // each participant generates their signature share - for (participant_index, nonce) in nonces { - let share_package = shares - .iter() - .find(|share| participant_index == share.index) - .unwrap(); - - let nonce_to_use = nonce[0]; - let signature_share = frost::sign( - &signing_package, - &nonce_to_use, - &frost::KeyPackage::try_from(share_package.clone()).unwrap(), - ) - .unwrap(); - - signature_shares.push(signature_share); - } - - // aggregator generate the final signature - let final_signature = - frost::aggregate(&signing_package, &signature_shares[..], &pubkeys).unwrap(); - (setup, final_signature) -} - -fn generate_share_commitment( - shares: &Vec, - participants: Vec, -) -> BTreeMap { - assert_eq!(shares.len(), participants.len()); - participants - .into_iter() - .zip(shares) - .map(|(participant_id, share)| { - ( - participant_id, - Commitment::from(share.secret_share.commitment.0[0]), - ) - }) - .collect() -} - -fn create_signing_commitments( - commitments: Vec, - participants: Vec, -) -> BTreeMap { - assert_eq!(commitments.len(), participants.len()); - participants - .into_iter() - .zip(commitments) - .map(|(participant_id, commitment)| { - let signing_commitment = SigningCommitments { - hiding: Commitment::from(commitment.hiding), - binding: Commitment::from(commitment.binding), - }; - (participant_id, signing_commitment) - }) - .collect() -} diff --git a/frost-ristretto255/src/messages/tests/prop.rs b/frost-ristretto255/src/messages/tests/prop.rs deleted file mode 100644 index e307a92..0000000 --- a/frost-ristretto255/src/messages/tests/prop.rs +++ /dev/null @@ -1,15 +0,0 @@ -use proptest::prelude::*; - -use crate::messages::*; - -proptest! { - #[test] - fn serialize_message( - message in any::(), - ) { - let serialized = bincode::serialize(&message).unwrap(); - let deserialized: Message = bincode::deserialize(serialized.as_slice()).unwrap(); - - prop_assert_eq!(message, deserialized); - } -} diff --git a/frost-ristretto255/src/messages/validate.rs b/frost-ristretto255/src/messages/validate.rs deleted file mode 100644 index 5d1d9ce..0000000 --- a/frost-ristretto255/src/messages/validate.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Validation rules specified in [RFC-001#rules] -//! -//! [RFC-001#rules]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md#rules - -use super::constants::{ - BASIC_FROST_SERIALIZATION, MAX_SIGNERS, MIN_SIGNERS, MIN_THRESHOLD, - ZCASH_MAX_PROTOCOL_MESSAGE_LEN, -}; -use super::*; - -use thiserror::Error; - -pub trait Validate { - fn validate(&self) -> Result<&Self, MsgErr>; -} - -impl Validate for Message { - fn validate(&self) -> Result<&Self, MsgErr> { - match self.payload { - Payload::SharePackage(_) => { - if self.header.sender != ParticipantId::Dealer { - return Err(MsgErr::SenderMustBeDealer); - } - if !matches!(self.header.receiver, ParticipantId::Signer(_)) { - return Err(MsgErr::ReceiverMustBeSigner); - } - } - Payload::SigningCommitments(_) => { - if !matches!(self.header.sender, ParticipantId::Signer(_)) { - return Err(MsgErr::SenderMustBeSigner); - } - if self.header.receiver != ParticipantId::Aggregator { - return Err(MsgErr::ReceiverMustBeAggregator); - } - } - Payload::SigningPackage(_) => { - if self.header.sender != ParticipantId::Aggregator { - return Err(MsgErr::SenderMustBeAggregator); - } - if !matches!(self.header.receiver, ParticipantId::Signer(_)) { - return Err(MsgErr::ReceiverMustBeSigner); - } - } - Payload::SignatureShare(_) => { - if !matches!(self.header.sender, ParticipantId::Signer(_)) { - return Err(MsgErr::SenderMustBeSigner); - } - if self.header.receiver != ParticipantId::Aggregator { - return Err(MsgErr::ReceiverMustBeAggregator); - } - } - Payload::AggregateSignature(_) => { - if self.header.sender != ParticipantId::Aggregator { - return Err(MsgErr::SenderMustBeAggregator); - } - if !matches!(self.header.receiver, ParticipantId::Signer(_)) { - return Err(MsgErr::ReceiverMustBeSigner); - } - } - } - self.header.validate()?; - self.payload.validate()?; - Ok(self) - } -} - -impl Validate for Header { - fn validate(&self) -> Result<&Self, MsgErr> { - // Validate the message version. - // By now we only have 1 valid version so we compare against that. - if self.version != BASIC_FROST_SERIALIZATION { - return Err(MsgErr::WrongVersion); - } - - // Make sure the sender and the receiver are not the same. - if self.sender == self.receiver { - return Err(MsgErr::SameSenderAndReceiver); - } - Ok(self) - } -} - -impl Validate for Payload { - fn validate(&self) -> Result<&Self, MsgErr> { - match self { - Payload::SharePackage(share_package) => { - if share_package.share_commitment.len() < MIN_SIGNERS { - return Err(MsgErr::NotEnoughCommitments(MIN_SIGNERS)); - } - - if share_package.share_commitment.len() > MAX_SIGNERS.into() { - return Err(MsgErr::TooManyCommitments); - } - } - Payload::SigningCommitments(_) => {} - Payload::SigningPackage(signing_package) => { - if signing_package.message.len() > ZCASH_MAX_PROTOCOL_MESSAGE_LEN { - return Err(MsgErr::MsgTooBig); - } - - if signing_package.signing_commitments.len() < MIN_THRESHOLD { - return Err(MsgErr::NotEnoughCommitments(MIN_THRESHOLD)); - } - - if signing_package.signing_commitments.len() > MAX_SIGNERS.into() { - return Err(MsgErr::TooManyCommitments); - } - } - Payload::SignatureShare(_) => {} - Payload::AggregateSignature(_) => {} - } - - Ok(self) - } -} - -/// The error a message can produce if it fails validation. -#[derive(Error, Debug, PartialEq)] -pub enum MsgErr { - #[error("wrong version number")] - WrongVersion, - #[error("sender and receiver are the same")] - SameSenderAndReceiver, - #[error("the sender of this message must be the dealer")] - SenderMustBeDealer, - #[error("the receiver of this message must be a signer")] - ReceiverMustBeSigner, - #[error("the sender of this message must be a signer")] - SenderMustBeSigner, - #[error("the receiver of this message must be the aggregator")] - ReceiverMustBeAggregator, - #[error("the sender of this message must be the aggregator")] - SenderMustBeAggregator, - #[error("the number of signers must be at least {0}")] - NotEnoughCommitments(usize), - #[error("the number of signers can't be more than {}", MAX_SIGNERS)] - TooManyCommitments, - #[error( - "the message field can't be bigger than {}", - ZCASH_MAX_PROTOCOL_MESSAGE_LEN - )] - MsgTooBig, -} diff --git a/frost-ristretto255/src/signature.rs b/frost-ristretto255/src/signature.rs deleted file mode 100644 index fa1b4f5..0000000 --- a/frost-ristretto255/src/signature.rs +++ /dev/null @@ -1,60 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of frost-ristretto. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Henry de Valence -// - Deirdre Connolly - -//! Schnorr signatures on the Ristretto group - -/// A Schnorr signature on the Ristretto group. -#[derive(Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Signature { - pub(crate) R_bytes: [u8; 32], - pub(crate) z_bytes: [u8; 32], -} - -impl std::fmt::Debug for Signature { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("Signature") - .field("R", &hex::encode(self.R_bytes)) - .field("z", &hex::encode(self.z_bytes)) - .finish() - } -} - -impl From<[u8; 64]> for Signature { - fn from(bytes: [u8; 64]) -> Signature { - let mut R_bytes = [0; 32]; - R_bytes.copy_from_slice(&bytes[0..32]); - let mut z_bytes = [0; 32]; - z_bytes.copy_from_slice(&bytes[32..64]); - Signature { R_bytes, z_bytes } - } -} - -impl From for [u8; 64] { - fn from(sig: Signature) -> [u8; 64] { - let mut bytes = [0; 64]; - bytes[0..32].copy_from_slice(&sig.R_bytes[..]); - bytes[32..64].copy_from_slice(&sig.z_bytes[..]); - bytes - } -} - -impl hex::FromHex for Signature { - type Error = &'static str; - - fn from_hex>(hex: T) -> Result { - let mut bytes = [0u8; 64]; - - match hex::decode_to_slice(hex, &mut bytes[..]) { - Ok(()) => Ok(Self::from(bytes)), - Err(_) => Err("invalid hex"), - } - } -} diff --git a/frost-ristretto255/src/signing_key.rs b/frost-ristretto255/src/signing_key.rs deleted file mode 100644 index dd9f831..0000000 --- a/frost-ristretto255/src/signing_key.rs +++ /dev/null @@ -1,115 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redjubjub. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -use std::convert::{TryFrom, TryInto}; - -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar}; -use rand_core::{CryptoRng, RngCore}; -use sha2::{Digest, Sha512}; - -use crate::{Error, Signature, VerificationKey}; - -/// A signing key for a Schnorr signature on the Ristretto group. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(try_from = "SerdeHelper"))] -#[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] -pub struct SigningKey { - sk: Scalar, - pk: VerificationKey, -} - -impl<'a> From<&'a SigningKey> for VerificationKey { - fn from(sk: &'a SigningKey) -> VerificationKey { - sk.pk - } -} - -impl From for [u8; 32] { - fn from(sk: SigningKey) -> [u8; 32] { - sk.sk.to_bytes() - } -} - -impl TryFrom<[u8; 32]> for SigningKey { - type Error = Error; - - fn try_from(bytes: [u8; 32]) -> Result { - match Scalar::from_canonical_bytes(bytes) { - Some(sk) => { - let pk = VerificationKey::from(&sk); - Ok(SigningKey { sk, pk }) - } - None => Err(Error::MalformedSigningKey), - } - } -} - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -struct SerdeHelper([u8; 32]); - -impl TryFrom for SigningKey { - type Error = Error; - - fn try_from(helper: SerdeHelper) -> Result { - helper.0.try_into() - } -} - -impl From for SerdeHelper { - fn from(sk: SigningKey) -> Self { - Self(sk.into()) - } -} - -impl SigningKey { - /// Generate a new signing key. - pub fn new(mut rng: R) -> SigningKey { - let sk = { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - Scalar::from_bytes_mod_order_wide(&bytes) - }; - let pk = VerificationKey::from(&sk); - SigningKey { sk, pk } - } - - /// Create a signature `msg` using this `SigningKey`. - // Similar to signature::Signer but without boxed errors. - pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { - // Choose a byte sequence uniformly at random of length - // (\ell_H + 128)/8 bytes. For RedJubjub this is (512 + 128)/8 = 80. - let random_bytes = { - let mut bytes = [0; 80]; - rng.fill_bytes(&mut bytes); - bytes - }; - - let mut hasher = Sha512::new(); - hasher.update(&random_bytes[..]); - hasher.update(&self.pk.bytes.bytes[..]); - hasher.update(msg); - - let mut hash_bytes = [0u8; 64]; - hash_bytes.copy_from_slice(hasher.finalize().as_slice()); - - let nonce = Scalar::from_bytes_mod_order_wide(&hash_bytes); - - // XXX: does this need `RistrettoPoint::from_uniform_bytes()` ? - let R_bytes = (RISTRETTO_BASEPOINT_POINT * nonce).compress().to_bytes(); - - // Generate Schnorr challenge - let c = crate::generate_challenge(&R_bytes, &self.pk.bytes.bytes, msg); - - let z_bytes = (nonce + (c * self.sk)).to_bytes(); - - Signature { R_bytes, z_bytes } - } -} diff --git a/frost-ristretto255/src/frost/tests.rs b/frost-ristretto255/src/tests.rs similarity index 64% rename from frost-ristretto255/src/frost/tests.rs rename to frost-ristretto255/src/tests.rs index 9d2179c..21e98e5 100644 --- a/frost-ristretto255/src/frost/tests.rs +++ b/frost-ristretto255/src/tests.rs @@ -1,59 +1,18 @@ -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar}; - use rand::thread_rng; -use crate::frost::{self, *}; +use crate::*; mod vectors; use vectors::*; -fn reconstruct_secret( - secret_shares: Vec, -) -> Result { - let numshares = secret_shares.len(); - - if numshares < 1 { - return Err("No secret_shares provided"); - } - - let mut lagrange_coeffs: Vec = Vec::with_capacity(numshares as usize); - - for i in 0..numshares { - let mut num = Scalar::one(); - let mut den = Scalar::one(); - for j in 0..numshares { - if j == i { - continue; - } - num *= Scalar::from(secret_shares[j].index as u64); - den *= Scalar::from(secret_shares[j].index as u64) - - Scalar::from(secret_shares[i].index as u64); - } - if den == Scalar::zero() { - return Err("Duplicate shares provided"); - } - lagrange_coeffs.push(num * den.invert()); - } - - let mut secret = Scalar::zero(); - - for i in 0..numshares { - secret += lagrange_coeffs[i] * secret_shares[i].value.0; - } - - Ok(secret) -} - /// This is testing that Shamir's secret sharing to compute and arbitrary /// value is working. #[test] -fn check_share_generation() { +fn check_share_generation_ristretto255_sha512() { let mut rng = thread_rng(); - let secret = frost::keys::Secret::random(&mut rng); - - let _ = RISTRETTO_BASEPOINT_POINT * secret.0; + let secret = frost::keys::Secret::::random(&mut rng); let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap(); @@ -61,7 +20,10 @@ fn check_share_generation() { assert_eq!(secret_share.verify(), Ok(())); } - assert_eq!(reconstruct_secret(secret_shares).unwrap(), secret.0) + assert_eq!( + frost::keys::reconstruct_secret::(secret_shares).unwrap(), + secret + ) } #[test] @@ -76,15 +38,20 @@ fn check_sign_with_test_vectors() { group_binding_factor_input, group_binding_factor, signature_shares, - signature, + signature_bytes, ) = parse_test_vectors(); + type R = Ristretto255Sha512; + //////////////////////////////////////////////////////////////////////////// // Key generation //////////////////////////////////////////////////////////////////////////// for key_package in key_packages.values() { - assert_eq!(key_package.public, key_package.secret_share.into()); + assert_eq!( + *key_package.public(), + frost::keys::Public::from(*key_package.secret_share()) + ); } ///////////////////////////////////////////////////////////////////////////// @@ -97,13 +64,13 @@ fn check_sign_with_test_vectors() { let nonce_commitments = signer_commitments.get(&i).unwrap(); assert_eq!( - frost::round1::NonceCommitment::from(nonces.hiding), - nonce_commitments.hiding + &frost::round1::NonceCommitment::from(nonces.hiding()), + nonce_commitments.hiding() ); assert_eq!( - frost::round1::NonceCommitment::from(nonces.binding), - nonce_commitments.binding + &frost::round1::NonceCommitment::from(nonces.binding()), + nonce_commitments.binding() ); } @@ -112,7 +79,6 @@ fn check_sign_with_test_vectors() { ///////////////////////////////////////////////////////////////////////////// let signer_commitments_vec = signer_commitments - .clone() .into_iter() .map(|(_, signing_commitments)| signing_commitments) .collect(); @@ -121,30 +87,30 @@ fn check_sign_with_test_vectors() { assert_eq!(signing_package.rho_preimage(), group_binding_factor_input); - let rho: Rho = (&signing_package).into(); + let rho: frost::Rho = (&signing_package).into(); assert_eq!(rho, group_binding_factor); - let mut our_signature_shares: Vec = Vec::new(); + let mut our_signature_shares: Vec> = Vec::new(); // Each participant generates their signature share for index in signer_nonces.keys() { - let key_package = key_packages[index]; - let nonces = signer_nonces[index]; + let key_package = &key_packages[index]; + let nonces = &signer_nonces[index]; // Each participant generates their signature share. - let signature_share = frost::round2::sign(&signing_package, &nonces, &key_package).unwrap(); + let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap(); our_signature_shares.push(signature_share); } for sig_share in our_signature_shares.clone() { - assert_eq!(sig_share, signature_shares[&sig_share.index]); + assert_eq!(sig_share, signature_shares[sig_share.index()]); } let signer_pubkeys = key_packages .into_iter() - .map(|(i, key_package)| (i, key_package.public)) + .map(|(i, key_package)| (i, *key_package.public())) .collect(); let pubkey_package = frost::keys::PublicKeyPackage { @@ -163,7 +129,7 @@ fn check_sign_with_test_vectors() { &signature_shares .values() .cloned() - .collect::>(), + .collect::>>(), &pubkey_package, ); @@ -172,7 +138,7 @@ fn check_sign_with_test_vectors() { // Check that the generated signature matches the test vector signature let group_signature = group_signature_result.unwrap(); - assert_eq!(group_signature, signature); + assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes); // Aggregate the FROST signature from our signature shares let group_signature_result = @@ -183,5 +149,5 @@ fn check_sign_with_test_vectors() { // Check that the generated signature matches the test vector signature let group_signature = group_signature_result.unwrap(); - assert_eq!(group_signature, signature); + assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes); } diff --git a/frost-ristretto255/src/frost/tests/vectors.json b/frost-ristretto255/src/tests/vectors.json similarity index 100% rename from frost-ristretto255/src/frost/tests/vectors.json rename to frost-ristretto255/src/tests/vectors.json diff --git a/frost-ristretto255/src/frost/tests/vectors.rs b/frost-ristretto255/src/tests/vectors.rs similarity index 63% rename from frost-ristretto255/src/frost/tests/vectors.rs rename to frost-ristretto255/src/tests/vectors.rs index 186c22d..e242134 100644 --- a/frost-ristretto255/src/frost/tests/vectors.rs +++ b/frost-ristretto255/src/tests/vectors.rs @@ -1,16 +1,17 @@ use std::{collections::HashMap, str::FromStr}; use curve25519_dalek::scalar::Scalar; - -use hex; +use hex::{self, FromHex}; use lazy_static::lazy_static; use serde_json::Value; -use crate::{ +use frost_core::{ frost::{keys::*, round1::*, round2::*, *}, - Signature, VerificationKey, + VerifyingKey, }; +use crate::Ristretto255Sha512; + lazy_static! { pub static ref RISTRETTO255_SHA512: Value = serde_json::from_str(include_str!("vectors.json").trim()) @@ -18,24 +19,27 @@ lazy_static! { } #[allow(clippy::type_complexity)] -pub(super) fn parse_test_vectors() -> ( - VerificationKey, - HashMap, +#[allow(dead_code)] +pub(crate) fn parse_test_vectors() -> ( + VerifyingKey, + HashMap>, &'static str, Vec, - HashMap, - HashMap, + HashMap>, + HashMap>, Vec, - Rho, - HashMap, - Signature, + Rho, + HashMap>, + Vec, // Signature, ) { + type R = Ristretto255Sha512; + let inputs = &RISTRETTO255_SHA512["inputs"]; let message = inputs["message"].as_str().unwrap(); let message_bytes = hex::decode(message).unwrap(); - let mut key_packages: HashMap = HashMap::new(); + let mut key_packages: HashMap> = HashMap::new(); let possible_signers = RISTRETTO255_SHA512["inputs"]["signers"] .as_object() @@ -43,20 +47,20 @@ pub(super) fn parse_test_vectors() -> ( .iter(); let group_public = - VerificationKey::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap(); + VerifyingKey::::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap(); for (i, secret_share) in possible_signers { - let secret = Secret::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap(); + let secret = Secret::::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap(); let signer_public = secret.into(); - let key_package = KeyPackage { + let key_package = KeyPackage:: { index: u16::from_str(i).unwrap(), secret_share: secret, public: signer_public, group_public, }; - key_packages.insert(key_package.index, key_package); + key_packages.insert(*key_package.index(), key_package); } // Round one outputs @@ -71,22 +75,22 @@ pub(super) fn parse_test_vectors() -> ( .unwrap(); let group_binding_factor = - Rho::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap(); + Rho::::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap(); - let mut signer_nonces: HashMap = HashMap::new(); - let mut signer_commitments: HashMap = HashMap::new(); + let mut signer_nonces: HashMap> = HashMap::new(); + let mut signer_commitments: HashMap> = HashMap::new(); for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() { let index = u16::from_str(i).unwrap(); - let signing_nonces = SigningNonces { - hiding: Nonce::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(), - binding: Nonce::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(), + let signing_nonces = SigningNonces:: { + hiding: Nonce::::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(), + binding: Nonce::::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(), }; signer_nonces.insert(index, signing_nonces); - let signing_commitments = SigningCommitments { + let signing_commitments = SigningCommitments:: { index, hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap()) .unwrap(), @@ -103,10 +107,10 @@ pub(super) fn parse_test_vectors() -> ( let round_two_outputs = &RISTRETTO255_SHA512["round_two_outputs"]; - let mut signature_shares: HashMap = HashMap::new(); + let mut signature_shares: HashMap> = HashMap::new(); for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() { - let signature_share = SignatureShare { + let signature_share = SignatureShare:: { index: u16::from_str(i).unwrap(), signature: SignatureResponse { z_share: Scalar::from_canonical_bytes( @@ -126,7 +130,7 @@ pub(super) fn parse_test_vectors() -> ( let final_output = &RISTRETTO255_SHA512["final_output"]; - let signature = Signature::from_hex(final_output["sig"].as_str().unwrap()).unwrap(); + let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap(); ( group_public, @@ -138,6 +142,6 @@ pub(super) fn parse_test_vectors() -> ( group_binding_factor_input, group_binding_factor, signature_shares, - signature, + signature_bytes, ) } diff --git a/frost-ristretto255/src/verification_key.rs b/frost-ristretto255/src/verification_key.rs deleted file mode 100644 index 4ae5008..0000000 --- a/frost-ristretto255/src/verification_key.rs +++ /dev/null @@ -1,160 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redjubjub. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -use std::convert::{TryFrom, TryInto}; - -use curve25519_dalek::{ - ristretto::{CompressedRistretto, RistrettoPoint}, - scalar::Scalar, - traits::Identity, -}; -use hex::FromHex; - -use crate::{Error, Signature}; - -/// A refinement type for `[u8; 32]` indicating that the bytes represent an -/// encoding of a verification key for Schnorr signatures over the Ristretto -/// group. -/// -/// This is useful for representing a compressed verification key; the -/// [`VerificationKey`] type in this library holds other decompressed state -/// used in signature verification. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct VerificationKeyBytes { - pub(crate) bytes: [u8; 32], -} - -impl From<[u8; 32]> for VerificationKeyBytes { - fn from(bytes: [u8; 32]) -> VerificationKeyBytes { - VerificationKeyBytes { bytes } - } -} - -impl From for [u8; 32] { - fn from(refined: VerificationKeyBytes) -> [u8; 32] { - refined.bytes - } -} - -/// A valid verification key for Schnorr signatures over the Ristretto group. -/// -/// This type holds decompressed state used in signature verification; if the -/// verification key may not be used immediately, it is probably better to use -/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. -/// -/// ## Consensus properties -/// -/// The `TryFrom` conversion performs the following Zcash -/// consensus rule checks: -/// -/// 1. The check that the bytes are a canonical encoding of a verification key; -/// 2. The check that the verification key is not a point of small order. -#[derive(PartialEq, Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] -#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] -pub struct VerificationKey { - pub(crate) point: RistrettoPoint, - pub(crate) bytes: VerificationKeyBytes, -} - -impl From for VerificationKeyBytes { - fn from(pk: VerificationKey) -> VerificationKeyBytes { - pk.bytes - } -} - -impl From for [u8; 32] { - fn from(pk: VerificationKey) -> [u8; 32] { - pk.bytes.bytes - } -} - -impl FromHex for VerificationKey { - type Error = Error; - - fn from_hex>(hex: T) -> Result { - let mut bytes = [0u8; 32]; - - match hex::decode_to_slice(hex, &mut bytes[..]) { - Ok(()) => Self::try_from(bytes), - Err(_) => Err(Error::MalformedVerificationKey), - } - } -} - -impl TryFrom for VerificationKey { - type Error = Error; - - fn try_from(bytes: VerificationKeyBytes) -> Result { - // This checks that the encoding is canonical... - match CompressedRistretto::from_slice(&bytes.bytes).decompress() { - Some(point) => Ok(VerificationKey { point, bytes }), - None => Err(Error::MalformedVerificationKey), - } - } -} - -impl TryFrom<[u8; 32]> for VerificationKey { - type Error = Error; - - fn try_from(bytes: [u8; 32]) -> Result { - VerificationKeyBytes::from(bytes).try_into() - } -} - -impl VerificationKey { - pub(crate) fn from(s: &Scalar) -> VerificationKey { - let point = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT * s; - let bytes = VerificationKeyBytes { - bytes: point.compress().to_bytes(), - }; - VerificationKey { bytes, point } - } - - /// Verify a purported `signature` over `msg` made by this verification key. - // This is similar to impl signature::Verifier but without boxed errors - pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { - self.verify_prehashed( - signature, - crate::generate_challenge(&signature.R_bytes, &self.bytes.bytes, msg), - ) - } - - /// Verify a purported `signature` with a prehashed challenge. - #[allow(non_snake_case)] - pub(crate) fn verify_prehashed(&self, signature: &Signature, c: Scalar) -> Result<(), Error> { - let R = match CompressedRistretto::from_slice(&signature.R_bytes).decompress() { - Some(point) => point, - None => return Err(Error::InvalidSignature), - }; - - let s = match Scalar::from_canonical_bytes(signature.z_bytes) { - Some(s) => s, - None => return Err(Error::InvalidSignature), - }; - - // XXX rewrite as normal double scalar mul - // Verify check is h * ( - s * B + R + c * A) == 0 - // h * ( s * B - c * A - R) == 0 - // - // For Ristretto, h = 1. - let sB = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT * s; - let cA = self.point * c; - let check = sB - cA - R; - - if check == RistrettoPoint::identity() { - Ok(()) - } else { - Err(Error::InvalidSignature) - } - } -} diff --git a/frost-ristretto255/tests/batch.rs b/frost-ristretto255/tests/batch.rs deleted file mode 100644 index 7cdbb7c..0000000 --- a/frost-ristretto255/tests/batch.rs +++ /dev/null @@ -1,58 +0,0 @@ -use rand::thread_rng; - -use frost_ristretto255::*; - -#[test] -fn batch_verify() { - let mut rng = thread_rng(); - let mut batch = batch::Verifier::new(); - for _ in 0..32 { - let sk = SigningKey::new(&mut rng); - let vk = VerificationKey::from(&sk); - let msg = b"BatchVerifyTest"; - let sig = sk.sign(&mut rng, &msg[..]); - batch.queue((vk.into(), sig, msg)); - } - assert!(batch.verify(rng).is_ok()); -} - -#[test] -fn bad_batch_verify() { - let mut rng = thread_rng(); - let bad_index = 4; // must be even - let mut batch = batch::Verifier::new(); - let mut items = Vec::new(); - for i in 0..32 { - let item: batch::Item = match i % 2 { - 0 => { - let sk = SigningKey::new(&mut rng); - let vk = VerificationKey::from(&sk); - let msg = b"BatchVerifyTest"; - let sig = if i != bad_index { - sk.sign(&mut rng, &msg[..]) - } else { - sk.sign(&mut rng, b"bad") - }; - (vk.into(), sig, msg).into() - } - 1 => { - let sk = SigningKey::new(&mut rng); - let vk = VerificationKey::from(&sk); - let msg = b"BatchVerifyTest"; - let sig = sk.sign(&mut rng, &msg[..]); - (vk.into(), sig, msg).into() - } - _ => unreachable!(), - }; - items.push(item.clone()); - batch.queue(item); - } - assert!(batch.verify(rng).is_err()); - for (i, item) in items.drain(..).enumerate() { - if i != bad_index { - assert!(item.verify_single().is_ok()); - } else { - assert!(item.verify_single().is_err()); - } - } -} diff --git a/frost-ristretto255/tests/bincode.rs b/frost-ristretto255/tests/bincode.rs.bck similarity index 100% rename from frost-ristretto255/tests/bincode.rs rename to frost-ristretto255/tests/bincode.rs.bck diff --git a/frost-ristretto255/tests/frost.rs b/frost-ristretto255/tests/frost.rs index c3d2e25..7c0b465 100644 --- a/frost-ristretto255/tests/frost.rs +++ b/frost-ristretto255/tests/frost.rs @@ -1,25 +1,28 @@ use std::{collections::HashMap, convert::TryFrom}; +use frost_ristretto255::*; use rand::thread_rng; -use frost_ristretto255::frost; - #[test] fn check_sign_with_dealer() { let mut rng = thread_rng(); + + //////////////////////////////////////////////////////////////////////////// + // Key generation + //////////////////////////////////////////////////////////////////////////// + let numsigners = 5; let threshold = 3; - let (shares, pubkeys) = - frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap(); + let (shares, pubkeys) = keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap(); // Verifies the secret shares from the dealer - let key_packages: Vec = shares + let key_packages: Vec = shares .into_iter() - .map(|share| frost::keys::KeyPackage::try_from(share).unwrap()) + .map(|share| keys::KeyPackage::try_from(share).unwrap()) .collect(); - let mut nonces: HashMap> = HashMap::new(); - let mut commitments: HashMap> = HashMap::new(); + let mut nonces: HashMap> = HashMap::new(); + let mut commitments: HashMap> = HashMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -28,7 +31,7 @@ fn check_sign_with_dealer() { for participant_index in 1..(threshold + 1) { // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. - let (nonce, commitment) = frost::round1::preprocess(1, participant_index as u16, &mut rng); + let (nonce, commitment) = round1::preprocess(1, participant_index as u16, &mut rng); nonces.insert(participant_index as u16, nonce); commitments.insert(participant_index as u16, commitment); } @@ -36,10 +39,10 @@ fn check_sign_with_dealer() { // This is what the signature aggregator / coordinator needs to do: // - decide what message to sign // - take one (unused) commitment per signing participant - let mut signature_shares: Vec = Vec::new(); + let mut signature_shares: Vec = Vec::new(); let message = "message to sign".as_bytes(); let comms = commitments.clone().into_values().flatten().collect(); - let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + let signing_package = SigningPackage::new(comms, message.to_vec()); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -51,11 +54,10 @@ fn check_sign_with_dealer() { .find(|key_package| *participant_index == key_package.index) .unwrap(); - let nonces_to_use = nonces.get(participant_index).unwrap()[0]; + let nonces_to_use = &nonces.get(participant_index).unwrap()[0]; // Each participant generates their signature share. - let signature_share = - frost::round2::sign(&signing_package, &nonces_to_use, key_package).unwrap(); + let signature_share = round2::sign(&signing_package, nonces_to_use, key_package).unwrap(); signature_shares.push(signature_share); } @@ -65,7 +67,7 @@ fn check_sign_with_dealer() { //////////////////////////////////////////////////////////////////////////// // Aggregate (also verifies the signature shares) - let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys); + let group_signature_res = aggregate(&signing_package, &signature_shares[..], &pubkeys); assert!(group_signature_res.is_ok()); diff --git a/frost-ristretto255/tests/proptests.rs b/frost-ristretto255/tests/proptests.rs index a1777c0..2d6c6b1 100644 --- a/frost-ristretto255/tests/proptests.rs +++ b/frost-ristretto255/tests/proptests.rs @@ -1,17 +1,14 @@ -use std::convert::TryFrom; - +use frost_ristretto255::*; use proptest::prelude::*; use rand_core::{CryptoRng, RngCore}; -use frost_ristretto255::*; - /// A signature test-case, containing signature data and expected validity. #[derive(Clone, Debug)] struct SignatureCase { msg: Vec, sig: Signature, - pk_bytes: VerificationKeyBytes, - invalid_pk_bytes: VerificationKeyBytes, + vk: VerifyingKey, + invalid_vk: VerifyingKey, is_valid: bool, } @@ -40,13 +37,13 @@ impl SignatureCase { fn new(mut rng: R, msg: Vec) -> Self { let sk = SigningKey::new(&mut rng); let sig = sk.sign(&mut rng, &msg); - let pk_bytes = VerificationKey::from(&sk).into(); - let invalid_pk_bytes = VerificationKey::from(&SigningKey::new(&mut rng)).into(); + let vk = VerifyingKey::from(&sk); + let invalid_vk = VerifyingKey::from(&SigningKey::new(&mut rng)); Self { msg, sig, - pk_bytes, - invalid_pk_bytes, + vk, + invalid_vk, is_valid: true, } } @@ -55,21 +52,17 @@ impl SignatureCase { fn check(&self) -> bool { // The signature data is stored in (refined) byte types, but do a round trip // conversion to raw bytes to exercise those code paths. - let sig = { - let bytes: [u8; 64] = self.sig.into(); - Signature::from(bytes) - }; - let pk_bytes = { - let bytes: [u8; 32] = self.pk_bytes.into(); - VerificationKeyBytes::from(bytes) + let _sig = { + let bytes = self.sig.to_bytes(); + Signature::from_bytes(bytes) }; - // Check that the verification key is a valid RedJubjub verification key. - let pub_key = VerificationKey::try_from(pk_bytes) + // Check that the verification key is a valid key. + let _pub_key = VerifyingKey::from_bytes(self.vk.to_bytes()) .expect("The test verification key to be well-formed."); // Check that signature validation has the expected result. - self.is_valid == pub_key.verify(&self.msg, &sig).is_ok() + self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok() } fn apply_tweak(&mut self, tweak: &Tweak) { @@ -82,7 +75,7 @@ impl SignatureCase { } Tweak::ChangePubkey => { // Changing the public key makes the signature invalid. - self.pk_bytes = self.invalid_pk_bytes; + self.vk = self.invalid_vk; self.is_valid = false; } } @@ -102,7 +95,7 @@ use rand_core::SeedableRng; proptest! { - #[test] + #[test] fn tweak_signature( tweaks in prop::collection::vec(tweak_strategy(), (0,5)), rng_seed in prop::array::uniform32(any::()), @@ -114,17 +107,14 @@ proptest! { // Create a test case for each signature type. let msg = b"test message for proptests"; - let mut binding = SignatureCase::new(&mut rng, msg.to_vec()); - let mut spendauth = SignatureCase::new(&mut rng, msg.to_vec()); + let mut sig = SignatureCase::new(&mut rng, msg.to_vec()); // Apply tweaks to each case. for t in &tweaks { - binding.apply_tweak(t); - spendauth.apply_tweak(t); + sig.apply_tweak(t); } - assert!(binding.check()); - assert!(spendauth.check()); + assert!(sig.check()); }