From 4cc535c0c9322a9ed787ee5ef39cfbe07d8a02f6 Mon Sep 17 00:00:00 2001 From: conduition Date: Wed, 2 Oct 2024 17:43:45 +0000 Subject: [PATCH 01/13] modify frost-core traits to enable taproot compatibility This commit contains changes to the frost-core crate which allow ciphersuites to better customize how signatures are computed. This will enable taproot support without requiring major changes to existing frost ciphersuites. Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com --- frost-core/src/batch.rs | 20 ++- frost-core/src/keys.rs | 10 ++ frost-core/src/keys/dkg.rs | 31 +++- frost-core/src/lib.rs | 122 +++++++++---- frost-core/src/round1.rs | 19 ++ frost-core/src/round2.rs | 22 ++- frost-core/src/signature.rs | 41 +---- frost-core/src/signing_key.rs | 26 ++- frost-core/src/traits.rs | 224 +++++++++++++++++++++++- frost-core/src/verifying_key.rs | 35 +++- frost-ed25519/src/lib.rs | 10 ++ frost-ed25519/tests/serde_tests.rs | 20 ++- frost-ed448/src/lib.rs | 10 ++ frost-ed448/tests/serde_tests.rs | 20 ++- frost-p256/src/lib.rs | 10 ++ frost-p256/tests/serde_tests.rs | 20 ++- frost-ristretto255/src/lib.rs | 10 ++ frost-ristretto255/tests/serde_tests.rs | 20 ++- frost-secp256k1/src/lib.rs | 10 ++ frost-secp256k1/tests/serde_tests.rs | 20 ++- 20 files changed, 560 insertions(+), 140 deletions(-) diff --git a/frost-core/src/batch.rs b/frost-core/src/batch.rs index 3aa1e806..fb5ada0e 100644 --- a/frost-core/src/batch.rs +++ b/frost-core/src/batch.rs @@ -20,6 +20,7 @@ use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *}; pub struct Item { vk: VerifyingKey, sig: Signature, + sig_params: C::SigningParameters, c: Challenge, } @@ -33,10 +34,15 @@ where where M: AsRef<[u8]>, { - // Compute c now to avoid dependency on the msg lifetime. - let c = crate::challenge(&sig.R, &vk, msg.as_ref())?; - - Ok(Self { vk, sig, c }) + let sig_target = SigningTarget::from_message(msg); + let c = ::challenge(&sig.R, &vk, &sig_target)?; + + Ok(Self { + vk, + sig, + sig_params: sig_target.sig_params, + c, + }) } } @@ -52,7 +58,8 @@ where /// requires borrowing the message data, the `Item` type is unlinked /// from the lifetime of the message. pub fn verify_single(self) -> Result<(), Error> { - self.vk.verify_prehashed(self.c, &self.sig) + self.vk + .verify_prehashed(self.c, &self.sig, &self.sig_params) } } @@ -121,6 +128,7 @@ where for item in self.signatures.iter() { let z = item.sig.z; let R = item.sig.R; + let vk = ::effective_pubkey_element(&item.vk, &item.sig_params); let blind = <::Field>::random(&mut rng); @@ -131,7 +139,7 @@ where Rs.push(R); VK_coeffs.push(<::Field>::zero() + (blind * item.c.0)); - VKs.push(item.vk.to_element()); + VKs.push(vk); } let scalars = core::iter::once(&P_coeff_acc) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 9112e108..f9cf0f08 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -119,6 +119,11 @@ where pub(crate) fn from_coefficients(coefficients: &[Scalar], peer: Identifier) -> Self { Self::new(evaluate_polynomial(peer, coefficients)) } + + /// Returns negated SigningShare + pub fn negate(&mut self) { + self.0 .0 = <::Field>::negate(&self.to_scalar()); + } } impl Debug for SigningShare @@ -630,6 +635,11 @@ where min_signers, } } + + /// Negate `SigningShare`. + pub fn negate_signing_share(&mut self) { + self.signing_share.negate(); + } } #[cfg(feature = "serialization")] diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index aa3a56ae..77e29d41 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -38,7 +38,7 @@ use rand_core::{CryptoRng, RngCore}; use crate::{ Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature, - SigningKey, VerifyingKey, + SigningKey, }; #[cfg(feature = "serialization")] @@ -322,7 +322,7 @@ pub fn part1( /// Generates the challenge for the proof of knowledge to a secret for the DKG. fn challenge( identifier: Identifier, - verifying_key: &VerifyingKey, + verifying_key: &Element, R: &Element, ) -> Result, Error> where @@ -331,7 +331,7 @@ where let mut preimage = vec![]; preimage.extend_from_slice(identifier.serialize().as_ref()); - preimage.extend_from_slice(::serialize(&verifying_key.to_element())?.as_ref()); + preimage.extend_from_slice(::serialize(&verifying_key)?.as_ref()); preimage.extend_from_slice(::serialize(R)?.as_ref()); Ok(Challenge( @@ -354,13 +354,23 @@ pub(crate) fn compute_proof_of_knowledge // > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k, // > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being // > a context string to prevent replay attacks. - let k = <::Field>::random(&mut rng); - let R_i = ::generator() * k; - let c_i = challenge::(identifier, &commitment.verifying_key()?, &R_i)?; + let mut k = <::Field>::random(&mut rng); + let mut R_i = ::generator() * k; + k = ::effective_nonce_secret(k, &R_i); + R_i = ::effective_nonce_element(R_i); + + let verifying_key = commitment.verifying_key()?; + let sig_params = Default::default(); + + let phi_ell0 = ::effective_pubkey_element(&verifying_key, &sig_params); + + let c_i = challenge::(identifier, &phi_ell0, &R_i)?; let a_i0 = *coefficients .first() .expect("coefficients must have at least one element"); - let mu_i = k + a_i0 * c_i.0; + let a_i0_effective = ::effective_secret_key(a_i0, &verifying_key, &sig_params); + + let mu_i = k + a_i0_effective * c_i.0; Ok(Signature { R: R_i, z: mu_i }) } @@ -380,9 +390,12 @@ pub(crate) fn verify_proof_of_knowledge( let ell = identifier; let R_ell = proof_of_knowledge.R; let mu_ell = proof_of_knowledge.z; - let phi_ell0 = commitment.verifying_key()?; + + let verifying_key = commitment.verifying_key()?; + let phi_ell0 = ::effective_pubkey_element(&verifying_key, &Default::default()); let c_ell = challenge::(ell, &phi_ell0, &R_ell)?; - if R_ell != ::generator() * mu_ell - phi_ell0.to_element() * c_ell.0 { + + if R_ell != ::generator() * mu_ell - phi_ell0 * c_ell.0 { return Err(Error::InvalidProofOfKnowledge { culprit: ell }); } Ok(()) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index fbecfe07..434a3816 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -57,18 +57,14 @@ use scalar_mul::VartimeMultiscalarMul; pub use serde; pub use signature::Signature; pub use signing_key::SigningKey; -pub use traits::{Ciphersuite, Element, Field, Group, Scalar}; +pub use traits::{Ciphersuite, Element, Field, Group, Scalar, SigningParameters}; pub use verifying_key::VerifyingKey; /// A type refinement for the scalar field element representing the per-message _[challenge]_. /// /// [challenge]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-challenge-computa #[derive(Copy, Clone)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct Challenge( - pub(crate) <::Field as Field>::Scalar, -); +pub struct Challenge(pub(crate) <::Field as Field>::Scalar); impl Challenge where @@ -192,9 +188,7 @@ where /// /// #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct BindingFactor(Scalar); +pub struct BindingFactor(Scalar); impl BindingFactor where @@ -357,6 +351,54 @@ fn derive_interpolating_value( ) } +/// The data which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] +#[derive(Clone, Debug, PartialEq, Eq, Getters)] +pub struct SigningTarget { + #[cfg_attr( + feature = "serde", + serde( + serialize_with = "serdect::slice::serialize_hex_lower_or_bin", + deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" + ) + )] + message: Vec, + + #[cfg_attr(feature = "serde", serde(default))] + sig_params: C::SigningParameters, +} + +impl SigningTarget { + /// Construct a signing target from a message and additional signing parameters. + pub fn new, P: Into>( + message: T, + sig_params: P, + ) -> SigningTarget { + SigningTarget { + message: message.as_ref().to_vec(), + sig_params: sig_params.into(), + } + } + + /// Constructs a signing target from an arbitrary message. + /// This populates [the `sig_params` field][SigningTarget::sig_params] with + /// the [`Default`] instance of the [`Ciphersuite::SigningParameters`]. + pub fn from_message>(message: T) -> SigningTarget { + SigningTarget { + message: message.as_ref().to_vec(), + sig_params: C::SigningParameters::default(), + } + } +} + +impl> From for SigningTarget { + fn from(message: T) -> Self { + Self::from_message(message) + } +} + /// Generated by the coordinator of the signing operation and distributed to /// each signing party #[derive(Clone, Debug, PartialEq, Eq, Getters)] @@ -370,18 +412,9 @@ pub struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. signing_commitments: BTreeMap, round1::SigningCommitments>, - /// Message which each participant will sign. - /// - /// Each signer should perform protocol-specific verification on the - /// message. - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - ) - )] - message: Vec, + /// The message and parameters which each participant will use to sign. + /// Each signer should perform protocol-specific verification on the signing target. + sig_target: SigningTarget, } impl SigningPackage @@ -391,14 +424,19 @@ where /// Create a new `SigningPackage` /// /// The `signing_commitments` are sorted by participant `identifier`. + /// + /// The `sig_target` can be any bytes-like type that implements `AsRef<[u8]>`. + /// Some ciphersuites like `frost-secp256k1-tr` allow customization of the signing + /// process by embedding additional parameters into a [`SigningTarget`], but this + /// is optional and not required by most ciphersuites. pub fn new( signing_commitments: BTreeMap, round1::SigningCommitments>, - message: &[u8], + sig_target: impl Into>, ) -> SigningPackage { SigningPackage { header: Header::default(), signing_commitments, - message: message.to_vec(), + sig_target: sig_target.into(), } } @@ -410,6 +448,11 @@ where self.signing_commitments.get(identifier).copied() } + /// Returns the message to be signed. + pub fn message(&self) -> &[u8] { + &self.sig_target.message + } + /// Compute the preimages to H1 to compute the per-signer binding factors // We separate this out into its own method so it can be tested #[cfg_attr(feature = "internals", visibility::make(pub))] @@ -430,7 +473,7 @@ where // The message is hashed with H4 to force the variable-length message // into a fixed-length byte string, same for hashing the variable-sized // (between runs of the protocol) set of group commitments, but with H5. - binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref()); + binding_factor_input_prefix.extend_from_slice(C::H4(self.message()).as_ref()); binding_factor_input_prefix.extend_from_slice( C::H5(&round1::encode_group_commitments(self.signing_commitments())?[..]).as_ref(), ); @@ -469,9 +512,7 @@ where /// The product of all signers' individual commitments, published as part of the /// final signature. #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct GroupCommitment(pub(crate) Element); +pub struct GroupCommitment(pub(crate) Element); impl GroupCommitment where @@ -482,6 +523,12 @@ where pub fn to_element(self) -> ::Element { self.0 } + + /// Check if group commitment is odd + #[cfg(feature = "internals")] + pub fn y_is_odd(&self) -> bool { + ::y_is_odd(&self.0) + } } /// Generates the group commitment which is published as part of the joint @@ -589,6 +636,7 @@ where compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?; // Compute the group commitment from signing commitments produced in round one. let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + let R = ::effective_nonce_element(group_commitment.0); // The aggregation of the signature shares by summing them up, resulting in // a plain Schnorr signature. @@ -602,15 +650,13 @@ where z = z + signature_share.to_scalar(); } - let signature = Signature { - R: group_commitment.0, - z, - }; + let signature: Signature = + ::aggregate_sig_finalize(z, R, &pubkeys.verifying_key, &signing_package.sig_target)?; // Verify the aggregate signature let verification_result = pubkeys .verifying_key - .verify(signing_package.message(), &signature); + .verify(signing_package.sig_target.clone(), &signature); // Only if the verification of the aggregate signature failed; verify each share to find the cheater. // This approach is more efficient since we don't need to verify all shares @@ -619,6 +665,7 @@ where if verification_result.is_err() { detect_cheater( group_commitment, + &R, pubkeys, signing_package, signature_shares, @@ -631,21 +678,21 @@ where Ok(signature) } - /// Optional cheater detection feature /// Each share is verified to find the cheater fn detect_cheater( group_commitment: GroupCommitment, + effective_group_commitment: &Element, pubkeys: &keys::PublicKeyPackage, signing_package: &SigningPackage, signature_shares: &BTreeMap, round2::SignatureShare>, binding_factor_list: &BindingFactorList, ) -> Result<(), Error> { // Compute the per-message challenge. - let challenge = crate::challenge::( - &group_commitment.0, + let challenge = ::challenge( + effective_group_commitment, &pubkeys.verifying_key, - signing_package.message().as_slice(), + &signing_package.sig_target, )?; // Verify the signature shares. @@ -677,6 +724,9 @@ fn detect_cheater( signer_pubkey, lambda_i, &challenge, + &group_commitment, + &pubkeys.verifying_key, + &signing_package.sig_target.sig_params, )?; } diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 27e1c229..ce5e28a5 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -64,6 +64,11 @@ where self.0 .0 } + /// Negate `Nonce`. + pub fn negate(&mut self) { + self.0 .0 = <::Field>::negate(&self.to_scalar()); + } + /// Generates a nonce from the given random bytes. /// This function allows testing and MUST NOT be made public. pub(crate) fn nonce_generate_from_random_bytes( @@ -281,6 +286,12 @@ where pub fn deserialize(bytes: &[u8]) -> Result> { Deserialize::deserialize(bytes) } + + /// Negate `SigningNonces`. + pub fn negate_nonces(&mut self) { + self.binding.negate(); + self.hiding.negate(); + } } /// Published by each participant in the first round of the signing protocol. @@ -358,6 +369,14 @@ where #[derive(Clone, Copy, PartialEq)] pub struct GroupCommitmentShare(pub(super) Element); +impl GroupCommitmentShare { + /// Return the underlying element. + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn to_element(self) -> Element { + self.0 + } +} + /// Encode the list of group signing commitments. /// /// Implements [`encode_group_commitment_list()`] from the spec. diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 7b0fd450..06cfec29 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -4,7 +4,7 @@ use core::fmt::{self, Debug}; use crate as frost; use crate::{ - challenge, Challenge, Ciphersuite, Error, Field, Group, {round1, *}, + Challenge, Ciphersuite, Error, Field, Group, {round1, *}, }; /// A participant's signature share, which the coordinator will aggregate with all other signer's @@ -62,6 +62,7 @@ where #[cfg(any(feature = "cheater-detection", feature = "internals"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + #[allow(clippy::too_many_arguments)] pub(crate) fn verify( &self, identifier: Identifier, @@ -69,9 +70,16 @@ where verifying_share: &frost::keys::VerifyingShare, lambda_i: Scalar, challenge: &Challenge, + group_commitment: &frost::GroupCommitment, + verifying_key: &frost::VerifyingKey, + sig_params: &C::SigningParameters, ) -> Result<(), Error> { + let commitment_share = + ::effective_commitment_share(group_commitment_share.clone(), &group_commitment); + let vsh = ::effective_verifying_share(&verifying_share, &verifying_key, &sig_params); + if (::generator() * self.to_scalar()) - != (group_commitment_share.0 + (verifying_share.to_element() * challenge.0 * lambda_i)) + != (commitment_share + (vsh * challenge.0 * lambda_i)) { return Err(Error::InvalidSignatureShare { culprit: identifier, @@ -96,7 +104,7 @@ where /// Compute the signature share for a signing operation. #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -fn compute_signature_share( +pub(super) fn compute_signature_share( signer_nonces: &round1::SigningNonces, binding_factor: BindingFactor, lambda_i: <<::Group as Group>::Field as Field>::Scalar, @@ -158,19 +166,21 @@ pub fn sign( let lambda_i = frost::derive_interpolating_value(key_package.identifier(), signing_package)?; // Compute the per-message challenge. - let challenge = challenge::( + let challenge = ::challenge( &group_commitment.0, &key_package.verifying_key, - signing_package.message.as_slice(), + &signing_package.sig_target, )?; // Compute the Schnorr signature share. - let signature_share = compute_signature_share( + let signature_share = ::compute_signature_share( signer_nonces, binding_factor, + group_commitment, lambda_i, key_package, challenge, + &signing_package.sig_target.sig_params, ); Ok(signature_share) diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 7150b085..2d085493 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -31,49 +31,12 @@ where /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. pub fn deserialize(bytes: &[u8]) -> Result> { - // To compute the expected length of the encoded point, encode the generator - // and get its length. Note that we can't use the identity because it can be encoded - // shorter in some cases (e.g. P-256, which uses SEC1 encoding). - let generator = ::generator(); - let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); - let R_bytes_len = R_bytes.len(); - - let one = <::Field as Field>::zero(); - let mut z_bytes = - Vec::from(<::Field as Field>::serialize(&one).as_ref()); - let z_bytes_len = z_bytes.len(); - - if bytes.len() != R_bytes_len + z_bytes_len { - return Err(Error::MalformedSignature); - } - - R_bytes[..].copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); - - let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; - - // 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 - .get(R_bytes_len..R_bytes_len + z_bytes_len) - .ok_or(Error::MalformedSignature)?, - ); - - let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; - - Ok(Self { - R: ::deserialize(R_serialization)?, - z: <::Field>::deserialize(z_serialization)?, - }) + C::deserialize_signature(bytes) } /// Converts this signature to its byte serialization. pub fn serialize(&self) -> Result, Error> { - let mut bytes = Vec::::new(); - - bytes.extend(::serialize(&self.R)?.as_ref()); - bytes.extend(<::Field>::serialize(&self.z).as_ref()); - - Ok(bytes) + ::serialize_signature(self) } } diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 4ba306ed..c562c057 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -5,8 +5,8 @@ use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; use crate::{ - random_nonzero, serialization::SerializableScalar, Ciphersuite, Error, Field, Group, Scalar, - Signature, VerifyingKey, + random_nonzero, serialization::SerializableScalar, Challenge, Ciphersuite, Error, Field, Group, + Scalar, Signature, SigningTarget, VerifyingKey, }; /// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`]. @@ -40,17 +40,25 @@ where } /// Create a signature `msg` using this `SigningKey`. - pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { - let k = random_nonzero::(&mut rng); + pub fn sign( + &self, + mut rng: R, + sig_target: impl Into>, + ) -> Signature { + let sig_target = sig_target.into(); - let R = ::generator() * k; + let public = VerifyingKey::::from(*self); + let secret = ::effective_secret_key(self.scalar, &public, &sig_target.sig_params); - // Generate Schnorr challenge - let c = crate::challenge::(&R, &VerifyingKey::::from(*self), msg).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); + let mut k = random_nonzero::(&mut rng); + let mut R = ::generator() * k; + k = ::effective_nonce_secret(k, &R); + R = ::effective_nonce_element(R); - let z = k + (c.0 * self.scalar); + // Generate Schnorr challenge + let c: Challenge = ::challenge(&R, &public, &sig_target).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); - Signature { R, z } + ::single_sig_finalize(k, R, secret, &c, &public, &sig_target.sig_params) } /// Creates a SigningKey from a scalar. Returns an error if the scalar is zero. diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 3e0d9365..5e21a2f0 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -8,7 +8,12 @@ use core::{ use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; -use crate::{Error, FieldError, GroupError, Signature, VerifyingKey}; +use crate::{ + challenge, + keys::{KeyPackage, VerifyingShare}, + round1, round2, BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError, + Signature, SigningTarget, VerifyingKey, +}; /// A prime order finite field GF(q) over which all scalar values for our prime order group can be /// multiplied are defined. @@ -41,6 +46,11 @@ pub trait Field: Copy + Clone { /// element is zero. fn invert(scalar: &Self::Scalar) -> Result; + /// Computes the negation of the element of the scalar field + fn negate(_scalar: &Self::Scalar) -> Self::Scalar { + panic!("Not implemented"); + } + /// Generate a random scalar from the entire space [0, l-1] /// /// @@ -114,6 +124,11 @@ pub trait Group: Copy + Clone + PartialEq { /// [`ScalarBaseMult()`]: https://datatracker.ietf.org/doc/html/rfc9591#section-3.1-4.10 fn generator() -> Self::Element; + /// Check if element is odd + fn y_is_odd(_element: &Self::Element) -> bool { + panic!("Not implemented"); + } + /// A member function of a group _G_ that maps an [`Element`] to a unique /// byte array buf of fixed length Ne. This function raises an error if the /// element is the identity element of the group. @@ -134,6 +149,25 @@ pub trait Group: Copy + Clone + PartialEq { /// An element of the [`Ciphersuite`] `C`'s [`Group`]. pub type Element = <::Group as Group>::Element; +/// This is a marker trait for types which are passed in to modify the signing logic of a [`Ciphersuite`]. +/// +/// If the `serde` feature is enabled, any type implementing this trait must also implement +/// [`serde::Serialize`] and [`serde::Deserialize`]. +#[cfg(feature = "serde")] +pub trait SigningParameters: + Clone + Debug + Eq + PartialEq + Default + serde::Serialize + for<'d> serde::Deserialize<'d> +{ +} + +/// This is a marker trait for types which are passed in to modify the signing logic of a [`Ciphersuite`]. +/// +/// If the `serde` feature is enabled, any type implementing this trait must also implement +/// [`serde::Serialize`] and [`serde::Deserialize`]. +#[cfg(not(feature = "serde"))] +pub trait SigningParameters: Clone + Debug + Eq + PartialEq + Default {} + +impl SigningParameters for () {} + /// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash /// function. /// @@ -156,6 +190,10 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// `Group::ScalarSerialization` type SignatureSerialization: AsRef<[u8]> + TryFrom>; + /// Additional parameters which should be provided to the ciphersuite's signing code + /// to produce an effective signature. Most ciphersuites will just set this to `()`. + type SigningParameters: SigningParameters; + /// [H1] for a FROST ciphersuite. /// /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. @@ -223,12 +261,190 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// (see [`crate::batch::Verifier`]) also uses the default implementation regardless whether a /// tailored implementation was provided. fn verify_signature( - msg: &[u8], + sig_target: &SigningTarget, signature: &Signature, public_key: &VerifyingKey, ) -> Result<(), Error> { - let c = crate::challenge::(&signature.R, public_key, msg)?; + let c = ::challenge(&signature.R, public_key, sig_target)?; - public_key.verify_prehashed(c, signature) + public_key.verify_prehashed(c, signature, &sig_target.sig_params) + } + + /// 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. + /// + /// This is the only invocation of the H2 hash function from the [RFC]. + /// + /// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#name-signature-challenge-computa + /// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-3.2 + fn challenge( + R: &Element, + verifying_key: &VerifyingKey, + sig_target: &SigningTarget, + ) -> Result, Error> { + challenge(R, verifying_key, &sig_target.message) + } + + /// Finalize an aggregated group signature. This is used by frost-sepc256k1-tr + /// to ensure the signature is valid under BIP340. + fn aggregate_sig_finalize( + z: <::Field as Field>::Scalar, + R: Element, + _verifying_key: &VerifyingKey, + _sig_target: &SigningTarget, + ) -> Result, Error> { + Ok(Signature { R, z }) + } + + /// Finalize and output a single-signer Schnorr signature. + fn single_sig_finalize( + k: <::Field as Field>::Scalar, + R: Element, + secret: <::Field as Field>::Scalar, + challenge: &Challenge, + _verifying_key: &VerifyingKey, + _sig_params: &Self::SigningParameters, + ) -> Signature { + let z = k + (challenge.0 * secret); + Signature { R, z } + } + + /// Converts a signature to its [`Ciphersuite::SignatureSerialization`] in bytes. + /// + /// The default implementation serializes a signature by serializing its `R` point and + /// `z` component independently, and then concatenating them. + fn serialize_signature(signature: &Signature) -> Result, Error> { + let mut bytes = vec![]; + bytes.extend(::serialize(&signature.R)?.as_ref()); + bytes.extend(<::Field>::serialize(&signature.z).as_ref()); + Ok(bytes) + } + + /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. + /// + /// The default implementation assumes the serialization is a serialized `R` point + /// followed by a serialized `z` component with no padding or extra fields. + fn deserialize_signature(bytes: &[u8]) -> Result, Error> { + // To compute the expected length of the encoded point, encode the generator + // and get its length. Note that we can't use the identity because it can be encoded + // shorter in some cases (e.g. P-256, which uses SEC1 encoding). + let generator = ::generator(); + let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); + let R_bytes_len = R_bytes.len(); + + let one = <::Field as Field>::zero(); + let mut z_bytes = + Vec::from(<::Field as Field>::serialize(&one).as_ref()); + let z_bytes_len = z_bytes.len(); + + if bytes.len() != R_bytes_len + z_bytes_len { + return Err(Error::MalformedSignature); + } + + R_bytes[..].copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); + + let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; + + // 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 + .get(R_bytes_len..R_bytes_len + z_bytes_len) + .ok_or(Error::MalformedSignature)?, + ); + + let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; + + Ok(Signature { + R: ::deserialize(R_serialization)?, + z: <::Field>::deserialize(z_serialization)?, + }) + } + + /// Compute the signature share for a particular signer on a given challenge. + fn compute_signature_share( + signer_nonces: &round1::SigningNonces, + binding_factor: BindingFactor, + _group_commitment: GroupCommitment, + lambda_i: <::Field as Field>::Scalar, + key_package: &KeyPackage, + challenge: Challenge, + _sig_params: &Self::SigningParameters, + ) -> round2::SignatureShare { + round2::compute_signature_share( + signer_nonces, + binding_factor, + lambda_i, + key_package, + challenge, + ) + } + + /// Compute the effective group element which should be used for signature operations + /// for the given verifying key. + /// + /// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes. + fn effective_pubkey_element( + verifying_key: &VerifyingKey, + _sig_params: &Self::SigningParameters, + ) -> ::Element { + verifying_key.to_element() + } + + /// Compute the effective nonce element which should be used for signature operations. + /// + /// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity. + fn effective_nonce_element( + R: ::Element, + ) -> ::Element { + R + } + + /// Compute the effective secret key which should be used for signature operations + /// for the given verifying key. + /// + /// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes. + fn effective_secret_key( + secret: <::Field as Field>::Scalar, + _public: &VerifyingKey, + _sig_params: &Self::SigningParameters, + ) -> <::Field as Field>::Scalar { + secret + } + + /// Compute the effective nonce secret which should be used for signature operations. + /// + /// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity. + fn effective_nonce_secret( + nonce: <::Field as Field>::Scalar, + _R: &Element, + ) -> <::Field as Field>::Scalar { + nonce + } + + /// Compute the effective nonce commitment share which should be used for + /// FROST signing. + /// + /// In frost-sepc256k1-tr, this negates the commitment share if the group's final + /// commitment has an odd parity. + fn effective_commitment_share( + group_commitment_share: round1::GroupCommitmentShare, + _group_commitment: &GroupCommitment, + ) -> ::Element { + group_commitment_share.to_element() + } + + /// Compute the effective verifying share which should be used for FROST + /// partial signature verification. + /// + /// In frost-sepc256k1-tr, this negates the verifying share if the group's final + /// verifying key has an odd parity. + fn effective_verifying_share( + verifying_share: &VerifyingShare, + _verifying_key: &VerifyingKey, + _sig_params: &Self::SigningParameters, + ) -> ::Element { + verifying_share.to_element() } } diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index c3a1cfc5..b472d484 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -5,7 +5,10 @@ use alloc::{string::ToString, vec::Vec}; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; -use crate::{serialization::SerializableElement, Challenge, Ciphersuite, Error, Group, Signature}; +use crate::{ + serialization::SerializableElement, Challenge, Ciphersuite, Error, Group, Signature, + SigningTarget, +}; /// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`]. #[derive(Copy, Clone, PartialEq, Eq)] @@ -39,6 +42,18 @@ where self.element.0 } + /// Return the effective verifying key given the specific signing parameters + /// to be verified against. For most ciphersuites, this simply returns the + /// same verifying key unchanged. + pub fn effective_key(self, sig_params: &C::SigningParameters) -> Self { + VerifyingKey::new(::effective_pubkey_element(&self, sig_params)) + } + + /// Check if VerifyingKey is odd + pub fn y_is_odd(&self) -> bool { + ::y_is_odd(&self.to_element()) + } + /// Deserialize from bytes pub fn deserialize(bytes: &[u8]) -> Result, Error> { Ok(Self::new(SerializableElement::deserialize(bytes)?.0)) @@ -55,14 +70,18 @@ where &self, challenge: Challenge, signature: &Signature, + sig_params: &C::SigningParameters, ) -> Result<(), Error> { // Verify check is h * ( - z * B + R + c * A) == 0 // h * ( z * B - c * A - R) == 0 // // where h is the cofactor + let R = signature.R; + let vk = C::effective_pubkey_element(&self, sig_params); + let zB = C::Group::generator() * signature.z; - let cA = self.element.0 * challenge.0; - let check = (zB - cA - signature.R) * C::Group::cofactor(); + let cA = vk * challenge.0; + let check = (zB - cA - R) * C::Group::cofactor(); if check == C::Group::identity() { Ok(()) @@ -71,9 +90,13 @@ where } } - /// Verify a purported `signature` over `msg` made by this verification key. - pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { - C::verify_signature(msg, signature, self) + /// Verify a purported `signature` over `sig_target` made by this verification key. + pub fn verify( + &self, + sig_target: impl Into>, + signature: &Signature, + ) -> Result<(), Error> { + C::verify_signature(&sig_target.into(), signature, self) } /// Computes the group public key given the group commitment. diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 1e33b2d9..42a25933 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -163,6 +163,14 @@ const CONTEXT_STRING: &str = "FROST-ED25519-SHA512-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Ed25519Sha512; +/// The ciphersuite-specific signing parameters which are fed into +/// signing code to ensure correctly compliant signatures are computed. +pub type SigningParameters = (); + +/// The message target which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +pub type SigningTarget = frost_core::SigningTarget; + impl Ciphersuite for Ed25519Sha512 { const ID: &'static str = CONTEXT_STRING; @@ -172,6 +180,8 @@ impl Ciphersuite for Ed25519Sha512 { type SignatureSerialization = [u8; 64]; + type SigningParameters = (); + /// H1 for FROST(Ed25519, SHA-512) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.2 diff --git a/frost-ed25519/tests/serde_tests.rs b/frost-ed25519/tests/serde_tests.rs index 9f722797..1a2a6a4f 100644 --- a/frost-ed25519/tests/serde_tests.rs +++ b/frost-ed25519/tests/serde_tests.rs @@ -112,7 +112,9 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -133,7 +135,9 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -153,7 +157,9 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -172,7 +178,9 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -192,7 +200,9 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "message": "68656c6c6f20776f726c64", + "sig_target": { + "message": "68656c6c6f20776f726c64" + }, "extra": 1 } "#; diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 4ceb707e..f4f5bbd2 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -157,6 +157,14 @@ const CONTEXT_STRING: &str = "FROST-ED448-SHAKE256-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Ed448Shake256; +/// The ciphersuite-specific signing parameters which are fed into +/// signing code to ensure correctly compliant signatures are computed. +pub type SigningParameters = (); + +/// The message target which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +pub type SigningTarget = frost_core::SigningTarget; + impl Ciphersuite for Ed448Shake256 { const ID: &'static str = CONTEXT_STRING; @@ -166,6 +174,8 @@ impl Ciphersuite for Ed448Shake256 { type SignatureSerialization = [u8; 114]; + type SigningParameters = (); + /// H1 for FROST(Ed448, SHAKE256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.2 diff --git a/frost-ed448/tests/serde_tests.rs b/frost-ed448/tests/serde_tests.rs index 3b5c667a..71de30c2 100644 --- a/frost-ed448/tests/serde_tests.rs +++ b/frost-ed448/tests/serde_tests.rs @@ -112,7 +112,9 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -133,7 +135,9 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -153,7 +157,9 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -172,7 +178,9 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -192,7 +200,9 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "message": "68656c6c6f20776f726c64", + "sig_target": { + "message": "68656c6c6f20776f726c64" + }, "extra": 1 } "#; diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 3e798f4b..b8b61b87 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -175,6 +175,14 @@ const CONTEXT_STRING: &str = "FROST-P256-SHA256-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct P256Sha256; +/// The ciphersuite-specific signing parameters which are fed into +/// signing code to ensure correctly compliant signatures are computed. +pub type SigningParameters = (); + +/// The message target which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +pub type SigningTarget = frost_core::SigningTarget; + impl Ciphersuite for P256Sha256 { const ID: &'static str = CONTEXT_STRING; @@ -184,6 +192,8 @@ impl Ciphersuite for P256Sha256 { type SignatureSerialization = [u8; 65]; + type SigningParameters = (); + /// H1 for FROST(P-256, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.2 diff --git a/frost-p256/tests/serde_tests.rs b/frost-p256/tests/serde_tests.rs index c1475814..23e7507f 100644 --- a/frost-p256/tests/serde_tests.rs +++ b/frost-p256/tests/serde_tests.rs @@ -112,7 +112,9 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -133,7 +135,9 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -153,7 +157,9 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -172,7 +178,9 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -192,7 +200,9 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "message": "68656c6c6f20776f726c64", + "sig_target": { + "message": "68656c6c6f20776f726c64" + }, "extra": 1 } "#; diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 0929c228..3785d827 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -149,6 +149,14 @@ const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Ristretto255Sha512; +/// The ciphersuite-specific signing parameters which are fed into +/// signing code to ensure correctly compliant signatures are computed. +pub type SigningParameters = (); + +/// The message target which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +pub type SigningTarget = frost_core::SigningTarget; + impl Ciphersuite for Ristretto255Sha512 { const ID: &'static str = CONTEXT_STRING; @@ -158,6 +166,8 @@ impl Ciphersuite for Ristretto255Sha512 { type SignatureSerialization = [u8; 64]; + type SigningParameters = (); + /// H1 for FROST(ristretto255, SHA-512) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.2 diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs index faf1769a..1bc680e4 100644 --- a/frost-ristretto255/tests/serde_tests.rs +++ b/frost-ristretto255/tests/serde_tests.rs @@ -112,7 +112,9 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -133,7 +135,9 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -153,7 +157,9 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -172,7 +178,9 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -192,7 +200,9 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "message": "68656c6c6f20776f726c64", + "sig_target": { + "message": "68656c6c6f20776f726c64" + }, "extra": 1 } "#; diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 4d25266b..5489b48d 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -175,6 +175,14 @@ const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Secp256K1Sha256; +/// The ciphersuite-specific signing parameters which are fed into +/// signing code to ensure correctly compliant signatures are computed. +pub type SigningParameters = (); + +/// The message target which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +pub type SigningTarget = frost_core::SigningTarget; + impl Ciphersuite for Secp256K1Sha256 { const ID: &'static str = CONTEXT_STRING; @@ -184,6 +192,8 @@ impl Ciphersuite for Secp256K1Sha256 { type SignatureSerialization = [u8; 65]; + type SigningParameters = (); + /// H1 for FROST(secp256k1, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.2 diff --git a/frost-secp256k1/tests/serde_tests.rs b/frost-secp256k1/tests/serde_tests.rs index 82a0735d..a092d930 100644 --- a/frost-secp256k1/tests/serde_tests.rs +++ b/frost-secp256k1/tests/serde_tests.rs @@ -112,7 +112,9 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -133,7 +135,9 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -153,7 +157,9 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -172,7 +178,9 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "message": "68656c6c6f20776f726c64" + "sig_target": { + "message": "68656c6c6f20776f726c64" + } }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -192,7 +200,9 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "message": "68656c6c6f20776f726c64", + "sig_target": { + "message": "68656c6c6f20776f726c64" + }, "extra": 1 } "#; From f9264387e471fa90d70d0c3a68e992d3443e5497 Mon Sep 17 00:00:00 2001 From: conduition Date: Wed, 2 Oct 2024 19:23:52 +0000 Subject: [PATCH 02/13] add frost-secp256k1-tr crate and ciphersuite Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com --- Cargo.toml | 1 + frost-core/src/signature.rs | 3 +- frost-secp256k1-tr/Cargo.toml | 67 ++ frost-secp256k1-tr/README.md | 121 ++++ frost-secp256k1-tr/dkg.md | 168 +++++ frost-secp256k1-tr/src/keys/dkg.rs | 103 +++ frost-secp256k1-tr/src/keys/refresh.rs | 35 + frost-secp256k1-tr/src/keys/repairable.rs | 101 +++ frost-secp256k1-tr/src/lib.rs | 755 ++++++++++++++++++++++ 9 files changed, 1353 insertions(+), 1 deletion(-) create mode 100644 frost-secp256k1-tr/Cargo.toml create mode 100644 frost-secp256k1-tr/README.md create mode 100644 frost-secp256k1-tr/dkg.md create mode 100644 frost-secp256k1-tr/src/keys/dkg.rs create mode 100644 frost-secp256k1-tr/src/keys/refresh.rs create mode 100644 frost-secp256k1-tr/src/keys/repairable.rs create mode 100644 frost-secp256k1-tr/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index cbf054ff..69771765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "frost-p256", "frost-ristretto255", "frost-secp256k1", + "frost-secp256k1-tr", "frost-rerandomized", "gencode" ] diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 2d085493..f03d1e45 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -1,11 +1,12 @@ //! Schnorr signatures over prime order groups (or subgroups) use alloc::{string::ToString, vec::Vec}; +use derive_getters::Getters; use crate::{Ciphersuite, Element, Error, Field, Group, Scalar}; /// A Schnorr signature over some prime order group (or subgroup). -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Getters)] pub struct Signature { /// The commitment `R` to the signature nonce. pub(crate) R: Element, diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml new file mode 100644 index 00000000..ac0f26ae --- /dev/null +++ b/frost-secp256k1-tr/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "frost-secp256k1-tr" +edition = "2021" +# When releasing to crates.io: +# - Update CHANGELOG.md +# - Create git tag. +version = "2.0.0-rc.0" +authors = [ + "Deirdre Connolly ", + "Chelsea Komlo ", + "Conrado Gouvea " +] +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/ZcashFoundation/frost" +categories = ["cryptography"] +keywords = ["cryptography", "crypto", "threshold", "signature"] +description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot." + +[package.metadata.docs.rs] +features = ["serde"] +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +document-features = "0.2.7" +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } +serde = { version = "1.0.160", features = ["derive"], optional = true } +rand_core = "0.6" +sha2 = { version = "0.10.2", default-features = false } + +[dev-dependencies] +criterion = "0.5" +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +insta = { version = "1.31.0", features = ["yaml"] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +lazy_static = "1.4" +proptest = "1.0" +rand = "0.8" +rand_chacha = "0.3" +serde_json = "1.0" + +[features] +nightly = [] +default = ["serialization", "cheater-detection", "std"] +#! ## Features +## Enable standard library support. +std = ["frost-core/std"] +## Enable `serde` support for types that need to be communicated. You +## can use `serde` to serialize structs with any encoder that supports +## `serde` (e.g. JSON with `serde_json`). +serde = ["frost-core/serde", "dep:serde"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] + +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false diff --git a/frost-secp256k1-tr/README.md b/frost-secp256k1-tr/README.md new file mode 100644 index 00000000..9022e25c --- /dev/null +++ b/frost-secp256k1-tr/README.md @@ -0,0 +1,121 @@ +An implementation of Schnorr signatures on the secp256k1 curve for both single and threshold numbers +of signers (FROST). + +## Example: key generation with trusted dealer and FROST signing + +Creating a key with a trusted dealer and splitting into shares; then signing a message +and aggregating the signature. Note that the example just simulates a distributed +scenario in a single thread and it abstracts away any communication between peers. + + +```rust +# // ANCHOR: tkg_gen +use frost_secp256k1_tr as frost; +use rand::thread_rng; +use std::collections::BTreeMap; + +let mut rng = thread_rng(); +let max_signers = 5; +let min_signers = 3; +let (shares, pubkey_package) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, +)?; +# // ANCHOR_END: tkg_gen + +// Verifies the secret shares from the dealer and store them in a BTreeMap. +// In practice, the KeyPackages must be sent to its respective participants +// through a confidential and authenticated channel. +let mut key_packages: BTreeMap<_, _> = BTreeMap::new(); + +for (identifier, secret_share) in shares { + # // ANCHOR: tkg_verify + let key_package = frost::keys::KeyPackage::try_from(secret_share)?; + # // ANCHOR_END: tkg_verify + key_packages.insert(identifier, key_package); +} + +let mut nonces_map = BTreeMap::new(); +let mut commitments_map = BTreeMap::new(); + +//////////////////////////////////////////////////////////////////////////// +// Round 1: generating nonces and signing commitments for each participant +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_index in 1..=min_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let key_package = &key_packages[&participant_identifier]; + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _threshold_. + # // ANCHOR: round1_commit + let (nonces, commitments) = frost::round1::commit( + key_package.signing_share(), + &mut rng, + ); + # // ANCHOR_END: round1_commit + // In practice, the nonces must be kept by the participant to use in the + // next round, while the commitment must be sent to the coordinator + // (or to every other participant if there is no coordinator) using + // an authenticated channel. + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); +} + +// 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 = BTreeMap::new(); +# // ANCHOR: round2_package +let message = "message to sign".as_bytes(); +# // In practice, the SigningPackage must be sent to all participants +# // involved in the current signing (at least min_signers participants), +# // using an authenticate channel (and confidential if the message is secret). +let signing_package = frost::SigningPackage::new(commitments_map, message); +# // ANCHOR_END: round2_package + +//////////////////////////////////////////////////////////////////////////// +// Round 2: each participant generates their signature share +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_identifier in nonces_map.keys() { + let key_package = &key_packages[participant_identifier]; + + let nonces = &nonces_map[participant_identifier]; + + // Each participant generates their signature share. + # // ANCHOR: round2_sign + let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?; + # // ANCHOR_END: round2_sign + + // In practice, the signature share must be sent to the Coordinator + // using an authenticated channel. + signature_shares.insert(*participant_identifier, signature_share); +} + +//////////////////////////////////////////////////////////////////////////// +// Aggregation: collects the signing shares from all participants, +// generates the final signature. +//////////////////////////////////////////////////////////////////////////// + +// Aggregate (also verifies the signature shares) +# // ANCHOR: aggregate +let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; +# // ANCHOR_END: aggregate + + +// Check that the threshold signature can be verified by the group public +// key (the verification key). +# // ANCHOR: verify +let is_signature_valid = pubkey_package + .verifying_key() + .verify(message, &group_signature) + .is_ok(); +# // ANCHOR_END: verify +assert!(is_signature_valid); + +# Ok::<(), frost::Error>(()) +``` diff --git a/frost-secp256k1-tr/dkg.md b/frost-secp256k1-tr/dkg.md new file mode 100644 index 00000000..217cbc99 --- /dev/null +++ b/frost-secp256k1-tr/dkg.md @@ -0,0 +1,168 @@ +# Distributed Key Generation (DKG) + +The DKG module supports generating FROST key shares in a distributed manner, +without a trusted dealer. + +Before starting, each participant needs an unique identifier, which can be built from +a `u16`. The process in which these identifiers are allocated is up to the application. + +The distributed key generation process has 3 parts, with 2 communication rounds +between them, in which each participant needs to send a "package" to every other +participant. In the first round, each participant sends the same package +(a [`round1::Package`]) to every other. In the second round, each receiver gets +their own package (a [`round2::Package`]). + +Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] +that MUST be kept secret. Between part 2 and 3, each participant needs to hold +onto a [`round2::SecretPackage`]. + +After the third part, each participant will get a [`KeyPackage`] with their +long-term secret share that must be kept secret, and a [`PublicKeyPackage`] +that is public (and will be the same between all participants). With those +they can proceed to sign messages with FROST. + + +## Example + +```rust +# // ANCHOR: dkg_import +use rand::thread_rng; +use std::collections::BTreeMap; + +use frost_secp256k1_tr as frost; + +let mut rng = thread_rng(); + +let max_signers = 5; +let min_signers = 3; +# // ANCHOR_END: dkg_import + +//////////////////////////////////////////////////////////////////////////// +// Key generation, Round 1 +//////////////////////////////////////////////////////////////////////////// + +// Keep track of each participant's round 1 secret package. +// In practice each participant will keep its copy; no one +// will have all the participant's packages. +let mut round1_secret_packages = BTreeMap::new(); + +// Keep track of all round 1 packages sent to the given participant. +// This is used to simulate the broadcast; in practice the packages +// will be sent through some communication channel. +let mut received_round1_packages = BTreeMap::new(); + +// For each participant, perform the first part of the DKG protocol. +// In practice, each participant will perform this on their own environments. +for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + # // ANCHOR: dkg_part1 + let (round1_secret_package, round1_package) = frost::keys::dkg::part1( + participant_identifier, + max_signers, + min_signers, + &mut rng, + )?; + # // ANCHOR_END: dkg_part1 + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round1_secret_packages.insert(participant_identifier, round1_secret_package); + + // "Send" the round 1 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + for receiver_participant_index in 1..=max_signers { + if receiver_participant_index == participant_index { + continue; + } + let receiver_participant_identifier: frost::Identifier = receiver_participant_index + .try_into() + .expect("should be nonzero"); + received_round1_packages + .entry(receiver_participant_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round1_package.clone()); + } +} + +//////////////////////////////////////////////////////////////////////////// +// Key generation, Round 2 +//////////////////////////////////////////////////////////////////////////// + +// Keep track of each participant's round 2 secret package. +// In practice each participant will keep its copy; no one +// will have all the participant's packages. +let mut round2_secret_packages = BTreeMap::new(); + +// Keep track of all round 2 packages sent to the given participant. +// This is used to simulate the broadcast; in practice the packages +// will be sent through some communication channel. +let mut received_round2_packages = BTreeMap::new(); + +// For each participant, perform the second part of the DKG protocol. +// In practice, each participant will perform this on their own environments. +for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let round1_secret_package = round1_secret_packages + .remove(&participant_identifier) + .unwrap(); + let round1_packages = &received_round1_packages[&participant_identifier]; + # // ANCHOR: dkg_part2 + let (round2_secret_package, round2_packages) = + frost::keys::dkg::part2(round1_secret_package, round1_packages)?; + # // ANCHOR_END: dkg_part2 + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round2_secret_packages.insert(participant_identifier, round2_secret_package); + + // "Send" the round 2 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + // Note that, in contrast to the previous part, here each other participant + // gets its own specific package. + for (receiver_identifier, round2_package) in round2_packages { + received_round2_packages + .entry(receiver_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round2_package); + } +} + +//////////////////////////////////////////////////////////////////////////// +// Key generation, final computation +//////////////////////////////////////////////////////////////////////////// + +// Keep track of each participant's long-lived key package. +// In practice each participant will keep its copy; no one +// will have all the participant's packages. +let mut key_packages = BTreeMap::new(); + +// Keep track of each participant's public key package. +// In practice, if there is a Coordinator, only they need to store the set. +// If there is not, then all candidates must store their own sets. +// All participants will have the same exact public key package. +let mut pubkey_packages = BTreeMap::new(); + +// For each participant, perform the third part of the DKG protocol. +// In practice, each participant will perform this on their own environments. +for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let round2_secret_package = &round2_secret_packages[&participant_identifier]; + let round1_packages = &received_round1_packages[&participant_identifier]; + let round2_packages = &received_round2_packages[&participant_identifier]; + # // ANCHOR: dkg_part3 + let (key_package, pubkey_package) = frost::keys::dkg::part3( + round2_secret_package, + round1_packages, + round2_packages, + )?; + # // ANCHOR_END: dkg_part3 + key_packages.insert(participant_identifier, key_package); + pubkey_packages.insert(participant_identifier, pubkey_package); +} + +// With its own key package and the pubkey package, each participant can now proceed +// to sign with FROST. +# Ok::<(), frost::Error>(()) +``` diff --git a/frost-secp256k1-tr/src/keys/dkg.rs b/frost-secp256k1-tr/src/keys/dkg.rs new file mode 100644 index 00000000..9ea40b26 --- /dev/null +++ b/frost-secp256k1-tr/src/keys/dkg.rs @@ -0,0 +1,103 @@ +#![doc = include_str!("../../dkg.md")] +use super::*; + +/// DKG Round 1 structures. +pub mod round1 { + use super::*; + + /// The secret package that must be kept in memory by the participant + /// between the first and second parts of the DKG protocol (round 1). + /// + /// # Security + /// + /// This package MUST NOT be sent to other participants! + pub type SecretPackage = frost::keys::dkg::round1::SecretPackage; + + /// The package that must be broadcast by each participant to all other participants + /// between the first and second parts of the DKG protocol (round 1). + pub type Package = frost::keys::dkg::round1::Package; +} + +/// DKG Round 2 structures. +pub mod round2 { + use super::*; + + /// The secret package that must be kept in memory by the participant + /// between the second and third parts of the DKG protocol (round 2). + /// + /// # Security + /// + /// This package MUST NOT be sent to other participants! + pub type SecretPackage = frost::keys::dkg::round2::SecretPackage; + + /// A package that must be sent by each participant to some other participants + /// in Round 2 of the DKG protocol. Note that there is one specific package + /// for each specific recipient, in contrast to Round 1. + /// + /// # Security + /// + /// The package must be sent on an *confidential* and *authenticated* channel. + pub type Package = frost::keys::dkg::round2::Package; +} + +/// Performs the first part of the distributed key generation protocol +/// for the given participant. +/// +/// It returns the [`round1::SecretPackage`] that must be kept in memory +/// by the participant for the other steps, and the [`round1::Package`] that +/// must be sent to each other participant in the DKG run. +pub fn part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) +} + +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. +/// +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. +pub fn part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::dkg::part2(secret_package, round1_packages) +} + +/// Performs the third and final part of the distributed key generation protocol +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`KeyPackage`] that has the long-lived key share for the +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. +pub fn part3( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) +} diff --git a/frost-secp256k1-tr/src/keys/refresh.rs b/frost-secp256k1-tr/src/keys/refresh.rs new file mode 100644 index 00000000..c270fc20 --- /dev/null +++ b/frost-secp256k1-tr/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs new file mode 100644 index 00000000..88bce01d --- /dev/null +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -0,0 +1,101 @@ +//! Repairable Threshold Scheme +//! +//! Implements the Repairable Threshold Scheme (RTS) from . +//! The RTS is used to help a signer (participant) repair their lost share. This is achieved +//! using a subset of the other signers know here as `helpers`. + +use alloc::collections::BTreeMap; + +// This is imported separately to make `gencode` work. +// (if it were below, the position of the import would vary between ciphersuites +// after `cargo fmt`) +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{Error, Secp256K1Sha256}; + +use super::{SecretShare, VerifiableSecretSharingCommitment}; + +/// Step 1 of RTS. +/// +/// Generates the "delta" values from `helper_i` to help `participant` recover their share +/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` +/// is the share of `helper_i`. +/// +/// Returns a BTreeMap mapping which value should be sent to which participant. +pub fn repair_share_step_1( + helpers: &[Identifier], + share_i: &SecretShare, + rng: &mut R, + participant: Identifier, +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +} + +/// Step 2 of RTS. +/// +/// Generates the `sigma` values from all `deltas` received from `helpers` +/// to help `participant` recover their share. +/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. +/// +/// Returns a scalar +pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { + frost::keys::repairable::repair_share_step_2::(deltas_j) +} + +/// Step 3 of RTS +/// +/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` +/// is made up of the `identifier`and `commitment` of the `participant` as well as the +/// `value` which is the `SigningShare`. +pub fn repair_share_step_3( + sigmas: &[Scalar], + identifier: Identifier, + commitment: &VerifiableSecretSharingCommitment, +) -> SecretShare { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) +} + +#[cfg(test)] +mod tests { + + use lazy_static::lazy_static; + use rand::thread_rng; + use serde_json::Value; + + use crate::Secp256K1Sha256; + + lazy_static! { + pub static ref REPAIR_SHARE: Value = + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) + .unwrap(); + } + + #[test] + fn check_repair_share_step_1() { + let rng = thread_rng(); + + frost_core::tests::repairable::check_repair_share_step_1::(rng); + } + + #[test] + fn check_repair_share_step_2() { + frost_core::tests::repairable::check_repair_share_step_2::(&REPAIR_SHARE); + } + + #[test] + fn check_repair_share_step_3() { + let rng = thread_rng(); + frost_core::tests::repairable::check_repair_share_step_3::( + rng, + &REPAIR_SHARE, + ); + } + + #[test] + fn check_repair_share_step_1_fails_with_invalid_min_signers() { + let rng = thread_rng(); + frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + Secp256K1Sha256, + _, + >(rng); + } +} diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs new file mode 100644 index 00000000..e73333c5 --- /dev/null +++ b/frost-secp256k1-tr/src/lib.rs @@ -0,0 +1,755 @@ +#![allow(non_snake_case)] +#![deny(missing_docs)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc = document_features::document_features!()] + +extern crate alloc; + +use alloc::borrow::ToOwned; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +use frost_rerandomized::RandomizedCiphersuite; +use k256::{ + elliptic_curve::{ + bigint::U256, + group::prime::PrimeCurveAffine, + hash2curve::{hash_to_field, ExpandMsgXmd}, + point::AffineCoordinates, + sec1::{FromEncodedPoint, ToEncodedPoint}, + Field as FFField, PrimeField, ScalarPrimitive, + }, + AffinePoint, ProjectivePoint, Scalar, +}; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha256}; + +use frost_core as frost; + +// TODO +// #[cfg(test)] +// mod tests; + +// Re-exports in our public API +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{ + Challenge, Ciphersuite, Element, Field, FieldError, Group, GroupCommitment, GroupError, +}; +pub use rand_core; + +/// An error. +pub type Error = frost_core::Error; + +/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite scalar field. +#[derive(Clone, Copy)] +pub struct Secp256K1ScalarField; + +impl Field for Secp256K1ScalarField { + 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 { + // [`Scalar`]'s Eq/PartialEq does a constant-time comparison + if *scalar == ::zero() { + Err(FieldError::InvalidZeroScalar) + } else { + Ok(scalar.invert().unwrap()) + } + } + + fn negate(scalar: &Self::Scalar) -> Self::Scalar { + -scalar + } + + fn random(rng: &mut R) -> Self::Scalar { + Scalar::random(rng) + } + + fn serialize(scalar: &Self::Scalar) -> Self::Serialization { + scalar.to_bytes().into() + } + + fn deserialize(buf: &Self::Serialization) -> Result { + let field_bytes: &k256::FieldBytes = buf.into(); + match Scalar::from_repr(*field_bytes).into() { + Some(s) => Ok(s), + None => Err(FieldError::MalformedScalar), + } + } + + fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization { + let mut array = Self::serialize(scalar); + array.reverse(); + array + } +} + +/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite group. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Secp256K1Group; + +impl Group for Secp256K1Group { + type Field = Secp256K1ScalarField; + + type Element = ProjectivePoint; + + /// [SEC 1][1] serialization of a compressed point in secp256k1 takes 33 bytes + /// (1-byte prefix and 32 bytes for the coordinate). + /// + /// Note that, in the SEC 1 spec, the identity is encoded as a single null byte; + /// but here we pad with zeroes. This is acceptable as the identity _should_ never + /// be serialized in FROST, else we error. + /// + /// [1]: https://secg.org/sec1-v2.pdf + type Serialization = [u8; 33]; + + fn cofactor() -> ::Scalar { + Scalar::ONE + } + + fn identity() -> Self::Element { + ProjectivePoint::IDENTITY + } + + fn generator() -> Self::Element { + ProjectivePoint::GENERATOR + } + + fn y_is_odd(element: &Self::Element) -> bool { + element.to_affine().y_is_odd().into() + } + + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } + let mut fixed_serialized = [0; 33]; + let serialized_point = element.to_affine().to_encoded_point(true); + let serialized = serialized_point.as_bytes(); + fixed_serialized.copy_from_slice(serialized); + Ok(fixed_serialized) + } + + fn deserialize(buf: &Self::Serialization) -> Result { + let encoded_point = + k256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; + + match Option::::from(AffinePoint::from_encoded_point(&encoded_point)) { + Some(point) => { + if point.is_identity().into() { + // This is actually impossible since the identity is encoded a a single byte + // which will never happen since we receive a 33-byte buffer. + // We leave the check for consistency. + Err(GroupError::InvalidIdentityElement) + } else { + Ok(ProjectivePoint::from(point)) + } + } + None => Err(GroupError::MalformedElement), + } + } +} + +fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for i in inputs { + h.update(i); + } + let mut output = [0u8; 32]; + output.copy_from_slice(h.finalize().as_slice()); + output +} + +fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { + let mut u = [Secp256K1ScalarField::zero()]; + hash_to_field::, Scalar>(&[msg], &[domain], &mut u) + .expect("should never return error according to error cases described in ExpandMsgXmd"); + u[0] +} + +/// Context string from the ciphersuite in the [spec]. +/// +/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-1 +const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-TR-v1"; + +/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Secp256K1Sha256; + +/// Digest the hasher to a Scalar +fn hasher_to_scalar(hasher: Sha256) -> Scalar { + let sp = ScalarPrimitive::new(U256::from_be_slice(&hasher.finalize())).unwrap(); + Scalar::from(&sp) +} + +/// Create a BIP340 compliant tagged hash +fn tagged_hash(tag: &str) -> Sha256 { + let mut hasher = Sha256::new(); + let mut tag_hasher = Sha256::new(); + tag_hasher.update(tag.as_bytes()); + let tag_hash = tag_hasher.finalize(); + hasher.update(tag_hash); + hasher.update(tag_hash); + hasher +} + +/// Create a BIP341 compliant taproot tweak +fn tweak>( + public_key: &<::Group as Group>::Element, + merkle_root: Option, +) -> Scalar { + match merkle_root { + None => Secp256K1ScalarField::zero(), + Some(root) => { + let mut hasher = tagged_hash("TapTweak"); + hasher.update(public_key.to_affine().x()); + hasher.update(root.as_ref()); + hasher_to_scalar(hasher) + } + } +} + +/// Create a BIP341 compliant tweaked public key +fn tweaked_public_key>( + public_key: &VerifyingKey, + merkle_root: Option, +) -> <::Group as Group>::Element { + let mut pk = public_key.to_element(); + if pk.to_affine().y_is_odd().into() { + pk = -pk; + } + ProjectivePoint::GENERATOR * tweak(&pk, merkle_root) + pk +} + +/// The message target which the group's signature should commit to. Includes +/// a message byte vector, and a set of ciphersuite-specific parameters. +pub type SigningTarget = frost_core::SigningTarget; + +/// The ciphersuite-specific signing parameters which are fed into +/// signing code to ensure correctly compliant signatures are computed. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SigningParameters { + /// The tapscript merkle tree root which must be committed to and agreed upon + /// in advance by all participants in the signing round. + /// + /// If set to `None` (the default), then no taproot tweak will be committed to in the signature. + /// Best practice suggested by BIP341 is to commit to an empty merkle root in cases + /// where no tapscript tweak is needed, i.e. by supplying `&[0; u8]` as the merkle root. + /// This prevents hiding of taproot commitments inside a linearly aggregated key. + /// + /// However, for FROST, this is not strictly required as the group key cannot be + /// poisoned as long as the DKG procedure is conducted correctly. + /// Thus, the [`Default`] trait implementation of taproot `SigningParameters` + /// sets `tapscript_merkle_root` to `None`. + /// + /// If 3rd party observers outside the FROST group must be able to verify there + /// is no hidden script-spending path embedded in the FROST group's taproot output key, + /// then you should set `tapscript_merkle_root` to `Some(vec![])`, which proves + /// the tapscript commitment for the tweaked output key is unspendable. + pub tapscript_merkle_root: Option>, +} + +impl frost_core::SigningParameters for SigningParameters {} + +impl Ciphersuite for Secp256K1Sha256 { + const ID: &'static str = CONTEXT_STRING; + + type Group = Secp256K1Group; + + type HashOutput = [u8; 32]; + + type SignatureSerialization = [u8; 64]; + + type SigningParameters = SigningParameters; + + /// H1 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.1 + fn H1(m: &[u8]) -> <::Field as Field>::Scalar { + hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) + } + + /// H2 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.2 + fn H2(m: &[u8]) -> <::Field as Field>::Scalar { + let mut hasher = tagged_hash("BIP0340/challenge"); + hasher.update(m); + hasher_to_scalar(hasher) + } + + /// H3 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.3 + fn H3(m: &[u8]) -> <::Field as Field>::Scalar { + hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) + } + + /// H4 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.4 + fn H4(m: &[u8]) -> Self::HashOutput { + hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) + } + + /// H5 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.5 + fn H5(m: &[u8]) -> Self::HashOutput { + hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) + } + + /// HDKG for FROST(secp256k1, SHA-256) + fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "dkg").as_bytes(), + m, + )) + } + + /// HID for FROST(secp256k1, SHA-256) + fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "id").as_bytes(), + m, + )) + } + + /// Generates the challenge as is required for Schnorr signatures. + fn challenge( + R: &Element, + verifying_key: &VerifyingKey, + sig_target: &SigningTarget, + ) -> Result, Error> { + let mut preimage = vec![]; + let tweaked_pk = tweaked_public_key( + &verifying_key, + sig_target.sig_params().tapscript_merkle_root.as_ref(), + ); + preimage.extend_from_slice(&R.to_affine().x()); + preimage.extend_from_slice(&tweaked_pk.to_affine().x()); + preimage.extend_from_slice(sig_target.message().as_ref()); + Ok(Challenge::from_scalar(S::H2(&preimage[..]))) + } + + /// Finalizes the signature by negating it depending on whether + /// the group [`VerifyingKey`] is even or odd parity. + fn aggregate_sig_finalize( + z_raw: <::Field as Field>::Scalar, + R: Element, + verifying_key: &VerifyingKey, + sig_target: &SigningTarget, + ) -> Result { + let challenge = Self::challenge(&R, verifying_key, &sig_target)?; + + let t = tweak( + &verifying_key.to_element(), + sig_target.sig_params().tapscript_merkle_root.as_ref(), + ); + let tc = t * challenge.clone().to_scalar(); + let tweaked_pubkey = tweaked_public_key( + verifying_key, + sig_target.sig_params().tapscript_merkle_root.as_ref(), + ); + let z_tweaked = if tweaked_pubkey.to_affine().y_is_odd().into() { + z_raw - tc + } else { + z_raw + tc + }; + Ok(Signature::new(R, z_tweaked)) + } + + /// Finalize a single-signer BIP340 Schnorr signature. + fn single_sig_finalize( + k: <::Field as Field>::Scalar, + R: Element, + secret: <::Field as Field>::Scalar, + challenge: &Challenge, + verifying_key: &VerifyingKey, + sig_params: &SigningParameters, + ) -> Signature { + let tweaked_pubkey = + tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref()); + let c = challenge.clone().to_scalar(); + let z = if tweaked_pubkey.to_affine().y_is_odd().into() { + k - (c * secret) + } else { + k + (c * secret) + }; + Signature::new(R, z) + } + + /// Serialize a signature in compact BIP340 format, with an x-only R point. + fn serialize_signature(signature: &Signature) -> Result, Error> { + let R_bytes = Self::Group::serialize(signature.R())?; + let z_bytes = ::Field::serialize(signature.z()); + + let mut bytes = vec![0u8; 64]; + bytes[..32].copy_from_slice(&R_bytes[1..]); + bytes[32..].copy_from_slice(&z_bytes); + Ok(bytes) + } + + /// Deserialize a signature in compact BIP340 format, with an x-only R point. + fn deserialize_signature(bytes: &[u8]) -> Result { + if bytes.len() != 64 { + return Err(Error::MalformedSignature); + } + + let mut R_bytes = [0u8; 33]; + R_bytes[0] = 0x02; // taproot signatures always have an even R point + R_bytes[1..].copy_from_slice(&bytes[..32]); + + let mut z_bytes = [0u8; 32]; + z_bytes.copy_from_slice(&bytes[32..]); + + let R = Self::Group::deserialize(&R_bytes)?; + let z = ::Field::deserialize(&z_bytes)?; + + Ok(Signature::new(R, z)) + } + + /// Compute a signature share, negating if required by BIP340. + fn compute_signature_share( + signer_nonces: &round1::SigningNonces, + binding_factor: frost::BindingFactor, + group_commitment: GroupCommitment, + lambda_i: <::Field as Field>::Scalar, + key_package: &frost::keys::KeyPackage, + challenge: Challenge, + sig_params: &SigningParameters, + ) -> round2::SignatureShare { + let mut sn = signer_nonces.clone(); + if group_commitment.y_is_odd() { + sn.negate_nonces(); + } + + let mut kp = key_package.clone(); + let public_key = key_package.verifying_key(); + let pubkey_is_odd: bool = public_key.y_is_odd(); + let tweaked_pubkey_is_odd: bool = + tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref()) + .to_affine() + .y_is_odd() + .into(); + if pubkey_is_odd != tweaked_pubkey_is_odd { + kp.negate_signing_share(); + } + + frost::round2::compute_signature_share(&sn, binding_factor, lambda_i, &kp, challenge) + } + + /// Computes the effective pubkey point by tweaking the verifying key with a + /// provably unspendable taproot tweak. + fn effective_pubkey_element( + public_key: &VerifyingKey, + sig_params: &SigningParameters, + ) -> ::Element { + let tweaked_pubkey = + tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref()); + if Self::Group::y_is_odd(&tweaked_pubkey) { + -tweaked_pubkey + } else { + tweaked_pubkey + } + } + + /// Ensures the nonce has an even Y coordinate. + fn effective_nonce_element( + R: ::Element, + ) -> ::Element { + if Self::Group::y_is_odd(&R) { + -R + } else { + R + } + } + + /// Ensures the secret key is negated if the public key has odd parity. + fn effective_secret_key( + secret: <::Field as Field>::Scalar, + public_key: &VerifyingKey, + sig_params: &SigningParameters, + ) -> <::Field as Field>::Scalar { + let t = tweak( + &public_key.to_element(), + sig_params.tapscript_merkle_root.as_ref(), + ); + if Self::Group::y_is_odd(&public_key.to_element()) { + -secret + t + } else { + secret + t + } + } + + /// Ensures the nonce secret is negated if the public nonce point has odd parity. + fn effective_nonce_secret( + nonce: <::Field as Field>::Scalar, + R: &Element, + ) -> <::Field as Field>::Scalar { + if R.to_affine().y_is_odd().into() { + -nonce + } else { + nonce + } + } + + /// Ensures the commitment share is negated if the group's commitment has odd parity. + fn effective_commitment_share( + group_commitment_share: frost::round1::GroupCommitmentShare, + group_commitment: &GroupCommitment, + ) -> Element { + if group_commitment + .clone() + .to_element() + .to_affine() + .y_is_odd() + .into() + { + -group_commitment_share.to_element() + } else { + group_commitment_share.to_element() + } + } + + /// Calculate a verifying share compatible with taproot, depending on the parity + /// of the tweaked vs untweaked verifying key. + fn effective_verifying_share( + verifying_share: &keys::VerifyingShare, + verifying_key: &VerifyingKey, + sig_params: &SigningParameters, + ) -> ::Element { + let pubkey_is_odd: bool = verifying_key.to_element().to_affine().y_is_odd().into(); + let tweaked_pubkey_is_odd: bool = + tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref()) + .to_affine() + .y_is_odd() + .into(); + + let vs = verifying_share.to_element(); + if pubkey_is_odd != tweaked_pubkey_is_odd { + -vs + } else { + vs + } + } +} + +impl RandomizedCiphersuite for Secp256K1Sha256 { + fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), + m, + )) + } +} + +type S = Secp256K1Sha256; + +/// A FROST(secp256k1, SHA-256) participant identifier. +pub type Identifier = frost::Identifier; + +/// FROST(secp256k1, SHA-256) keys, key generation, key shares. +pub mod keys { + use super::*; + use std::collections::BTreeMap; + + /// The identifier list to use when generating key shares. + pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, S>; + + /// Allows all participants' keys to be generated using a central, trusted + /// dealer. + pub fn generate_with_dealer( + max_signers: u16, + min_signers: u16, + identifiers: IdentifierList, + mut rng: RNG, + ) -> Result<(BTreeMap, PublicKeyPackage), Error> { + frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng) + } + + /// Splits an existing key into FROST shares. + /// + /// This is identical to [`generate_with_dealer`] but receives an existing key + /// instead of generating a fresh one. This is useful in scenarios where + /// the key needs to be generated externally or must be derived from e.g. a + /// seed phrase. + pub fn split( + secret: &SigningKey, + max_signers: u16, + min_signers: u16, + identifiers: IdentifierList, + rng: &mut R, + ) -> Result<(BTreeMap, PublicKeyPackage), Error> { + frost::keys::split(secret, max_signers, min_signers, identifiers, rng) + } + + /// Recompute the secret from t-of-n secret shares using Lagrange interpolation. + /// + /// This can be used if for some reason the original key must be restored; e.g. + /// if threshold signing is not required anymore. + /// + /// This is NOT required to sign with FROST; the whole point of FROST is being + /// able to generate signatures only using the shares, without having to + /// reconstruct the original key. + /// + /// The caller is responsible for providing at least `min_signers` shares; + /// if less than that is provided, a different key will be returned. + pub fn reconstruct(secret_shares: &[KeyPackage]) -> Result { + frost::keys::reconstruct(secret_shares) + } + + /// Secret and public key material generated by a dealer performing + /// [`generate_with_dealer`]. + /// + /// # Security + /// + /// To derive a FROST(secp256k1, SHA-256) keypair, the receiver of the [`SecretShare`] *must* call + /// .into(), which under the hood also performs validation. + pub type SecretShare = frost::keys::SecretShare; + + /// A secret scalar value representing a signer's share of the group secret. + pub type SigningShare = frost::keys::SigningShare; + + /// A public group element that represents a single signer's public verification share. + pub type VerifyingShare = frost::keys::VerifyingShare; + + /// A FROST(secp256k1, SHA-256) keypair, which can be generated either by a trusted dealer or using + /// a DKG. + /// + /// When using a central dealer, [`SecretShare`]s are distributed to + /// participants, who then perform verification, before deriving + /// [`KeyPackage`]s, which they store to later use during signing. + pub type KeyPackage = frost::keys::KeyPackage; + + /// 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 type PublicKeyPackage = frost::keys::PublicKeyPackage; + + /// Contains the commitments to the coefficients for our secret polynomial _f_, + /// used to generate participants' key shares. + /// + /// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which + /// themselves are scalars) for a secret polynomial f, where f is used to + /// generate each ith participant's key share f(i). Participants use this set of + /// commitments to perform verifiable secret sharing. + /// + /// Note that participants MUST be assured that they have the *same* + /// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using + /// some agreed-upon public location for publication, where each participant can + /// ensure that they received the correct (and same) value. + pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; + + pub mod dkg; + pub mod repairable; +} + +/// FROST(secp256k1, SHA-256) Round 1 functionality and types. +pub mod round1 { + use crate::keys::SigningShare; + + use super::*; + + /// Comprised of FROST(secp256k1, SHA-256) 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. + pub type SigningNonces = frost::round1::SigningNonces; + + /// 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. + pub type SigningCommitments = frost::round1::SigningCommitments; + + /// A commitment to a signing nonce share. + pub type NonceCommitment = frost::round1::NonceCommitment; + + /// Performed once by each participant selected for the signing operation. + /// + /// Generates the signing nonces and commitments to be used in the signing + /// operation. + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) + where + RNG: CryptoRng + RngCore, + { + frost::round1::commit::(secret, rng) + } +} + +/// Generated by the coordinator of the signing operation and distributed to +/// each signing party. +pub type SigningPackage = frost::SigningPackage; + +/// FROST(secp256k1, SHA-256) Round 2 functionality and types, for signature share generation. +pub mod round2 { + use super::*; + + /// A FROST(secp256k1, SHA-256) participant's signature share, which the Coordinator will aggregate with all other signer's + /// shares into the joint signature. + pub type SignatureShare = frost::round2::SignatureShare; + + /// Performed once by each participant selected for the signing operation. + /// + /// 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. + pub fn sign( + signing_package: &SigningPackage, + signer_nonces: &round1::SigningNonces, + key_package: &keys::KeyPackage, + ) -> Result { + frost::round2::sign(signing_package, signer_nonces, key_package) + } +} + +/// A Schnorr signature on FROST(secp256k1, SHA-256). +pub type Signature = frost_core::Signature; + +/// Verifies each FROST(secp256k1, SHA-256) 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 Schnorr +/// 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, + signature_shares: &BTreeMap, + pubkeys: &keys::PublicKeyPackage, +) -> Result { + frost::aggregate(signing_package, signature_shares, pubkeys) +} + +/// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256). +pub type SigningKey = frost_core::SigningKey; + +/// A valid verifying key for Schnorr signatures on FROST(secp256k1, SHA-256). +pub type VerifyingKey = frost_core::VerifyingKey; From 11f43eb19f9e6e1b79cc5f997c098ea818f487f8 Mon Sep 17 00:00:00 2001 From: conduition Date: Wed, 2 Oct 2024 20:37:03 +0000 Subject: [PATCH 03/13] test coverage for taproot crate Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com --- frost-core/src/tests/ciphersuite_generic.rs | 40 +- frost-core/src/tests/refresh.rs | 13 +- frost-ed25519/tests/helpers/samples.json | 3 +- frost-ed25519/tests/helpers/samples.rs | 11 +- frost-ed25519/tests/integration_tests.rs | 12 +- frost-ed25519/tests/interoperability_tests.rs | 10 +- frost-ed25519/tests/recreation_tests.rs | 4 +- frost-ed448/tests/helpers/samples.json | 3 +- frost-ed448/tests/helpers/samples.rs | 11 +- frost-ed448/tests/integration_tests.rs | 12 +- frost-ed448/tests/recreation_tests.rs | 4 +- frost-p256/tests/helpers/samples.json | 3 +- frost-p256/tests/helpers/samples.rs | 11 +- frost-p256/tests/integration_tests.rs | 11 +- frost-p256/tests/recreation_tests.rs | 4 +- frost-ristretto255/tests/helpers/samples.json | 3 +- frost-ristretto255/tests/helpers/samples.rs | 11 +- frost-ristretto255/tests/integration_tests.rs | 12 +- frost-ristretto255/tests/recreation_tests.rs | 4 +- frost-secp256k1-tr/benches/bench.rs | 19 + frost-secp256k1-tr/src/lib.rs | 5 +- frost-secp256k1-tr/src/tests.rs | 5 + frost-secp256k1-tr/src/tests/batch.rs | 24 + .../src/tests/coefficient_commitment.rs | 46 ++ frost-secp256k1-tr/src/tests/deserialize.rs | 38 ++ frost-secp256k1-tr/src/tests/proptests.rs | 33 + .../src/tests/vss_commitment.rs | 38 ++ .../tests/common_traits_tests.rs | 74 ++ .../tests/helpers/elements.json | 5 + frost-secp256k1-tr/tests/helpers/mod.rs | 5 + .../tests/helpers/repair-share.json | 15 + frost-secp256k1-tr/tests/helpers/samples.json | 7 + frost-secp256k1-tr/tests/helpers/samples.rs | 128 ++++ .../tests/helpers/vectors-big-identifier.json | 77 +++ frost-secp256k1-tr/tests/helpers/vectors.json | 77 +++ .../tests/helpers/vectors_dkg.json | 51 ++ frost-secp256k1-tr/tests/integration_tests.rs | 363 ++++++++++ frost-secp256k1-tr/tests/recreation_tests.rs | 133 ++++ .../tests/rerandomized_tests.rs | 10 + frost-secp256k1-tr/tests/serde_tests.rs | 642 ++++++++++++++++++ .../tests/serialization_tests.rs | 105 +++ ...ck_key_package_postcard_serialization.snap | 5 + ...ic_key_package_postcard_serialization.snap | 5 + ...round1_package_postcard_serialization.snap | 5 + ...round2_package_postcard_serialization.snap | 5 + ...k_secret_share_postcard_serialization.snap | 5 + ...ignature_share_postcard_serialization.snap | 5 + ...ng_commitments_postcard_serialization.snap | 5 + ...signing_nonces_postcard_serialization.snap | 5 + ...igning_package_postcard_serialization.snap | 5 + frost-secp256k1-tr/tests/tweaking_tests.rs | 89 +++ frost-secp256k1/tests/helpers/samples.json | 3 +- frost-secp256k1/tests/helpers/samples.rs | 11 +- frost-secp256k1/tests/integration_tests.rs | 12 +- frost-secp256k1/tests/recreation_tests.rs | 4 +- gencode/src/main.rs | 29 +- 56 files changed, 2180 insertions(+), 95 deletions(-) create mode 100644 frost-secp256k1-tr/benches/bench.rs create mode 100644 frost-secp256k1-tr/src/tests.rs create mode 100644 frost-secp256k1-tr/src/tests/batch.rs create mode 100644 frost-secp256k1-tr/src/tests/coefficient_commitment.rs create mode 100644 frost-secp256k1-tr/src/tests/deserialize.rs create mode 100644 frost-secp256k1-tr/src/tests/proptests.rs create mode 100644 frost-secp256k1-tr/src/tests/vss_commitment.rs create mode 100644 frost-secp256k1-tr/tests/common_traits_tests.rs create mode 100644 frost-secp256k1-tr/tests/helpers/elements.json create mode 100644 frost-secp256k1-tr/tests/helpers/mod.rs create mode 100644 frost-secp256k1-tr/tests/helpers/repair-share.json create mode 100644 frost-secp256k1-tr/tests/helpers/samples.json create mode 100644 frost-secp256k1-tr/tests/helpers/samples.rs create mode 100644 frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json create mode 100644 frost-secp256k1-tr/tests/helpers/vectors.json create mode 100644 frost-secp256k1-tr/tests/helpers/vectors_dkg.json create mode 100644 frost-secp256k1-tr/tests/integration_tests.rs create mode 100644 frost-secp256k1-tr/tests/recreation_tests.rs create mode 100644 frost-secp256k1-tr/tests/rerandomized_tests.rs create mode 100644 frost-secp256k1-tr/tests/serde_tests.rs create mode 100644 frost-secp256k1-tr/tests/serialization_tests.rs create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/tweaking_tests.rs diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 4528e9b7..f5332273 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -7,9 +7,8 @@ use crate as frost; use crate::round2::SignatureShare; use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, - VerifyingKey, + SigningTarget, VerifyingKey, }; -use alloc::borrow::ToOwned; use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; @@ -103,7 +102,8 @@ pub fn check_share_generation_fails_with_invalid_signers( mut rng: R, -) -> (Vec, Signature, VerifyingKey) { + signing_target: SigningTarget, +) -> (SigningTarget, Signature, VerifyingKey) { //////////////////////////////////////////////////////////////////////////// // Key generation //////////////////////////////////////////////////////////////////////////// @@ -147,10 +147,11 @@ pub fn check_sign_with_dealer( .collect(), &mut rng, pubkeys.clone(), + signing_target.clone(), ); assert_eq!(r, Err(Error::InvalidSignature)); - check_sign(min_signers, key_packages, rng, pubkeys).unwrap() + check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap() } /// Test FROST signing with trusted dealer fails with invalid numbers of signers. @@ -195,7 +196,8 @@ pub fn check_sign( key_packages: BTreeMap, frost::keys::KeyPackage>, mut rng: R, pubkey_package: PublicKeyPackage, -) -> Result<(Vec, Signature, VerifyingKey), Error> { + signing_target: SigningTarget, +) -> Result<(SigningTarget, Signature, VerifyingKey), Error> { let mut nonces_map: BTreeMap, frost::round1::SigningNonces> = BTreeMap::new(); let mut commitments_map: BTreeMap, frost::round1::SigningCommitments> = @@ -223,8 +225,7 @@ pub fn check_sign( // - decide what message to sign // - take one (unused) commitment per signing participant let mut signature_shares = BTreeMap::new(); - let message = "message to sign".as_bytes(); - let signing_package = SigningPackage::new(commitments_map, message); + let signing_package = frost::SigningPackage::new(commitments_map, signing_target.clone()); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -266,11 +267,18 @@ pub fn check_sign( // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; + // Check that the effective verifying key can be verified against the raw message, + // without exposing the SigningParameters. + pubkey_package + .verifying_key + .effective_key(signing_target.sig_params()) + .verify(signing_target.message(), &group_signature)?; + // Check that the threshold signature can be verified by the group public // key (the verification key). pubkey_package .verifying_key - .verify(message, &group_signature)?; + .verify(signing_target.clone(), &group_signature)?; // Check that the threshold signature can be verified by the group public // key (the verification key) from KeyPackage.verifying_key @@ -279,11 +287,11 @@ pub fn check_sign( key_package .verifying_key - .verify(message, &group_signature)?; + .verify(signing_target.clone(), &group_signature)?; } Ok(( - message.to_owned(), + signing_target, group_signature, pubkey_package.verifying_key, )) @@ -303,7 +311,7 @@ fn check_sign_errors( .find(|&&id| id != key_package.identifier) .unwrap(); commitments.remove(&id); - let signing_package = frost::SigningPackage::new(commitments, signing_package.message()); + let signing_package = frost::SigningPackage::new(commitments, signing_package.sig_target); let r = frost::round2::sign(&signing_package, &signing_nonces, &key_package); assert_eq!(r, Err(Error::IncorrectNumberOfCommitments)); @@ -376,7 +384,8 @@ fn check_aggregate_invalid_share_identifier_for_verifying_shares( mut rng: R, -) -> (Vec, Signature, VerifyingKey) + signing_target: SigningTarget, +) -> (SigningTarget, Signature, VerifyingKey) where C::Group: core::cmp::PartialEq, { @@ -533,7 +542,7 @@ where let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap()); // Proceed with the signing test. - check_sign(min_signers, key_packages, rng, pubkeys).unwrap() + check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap() } /// Check that calling dkg::part3() with distinct sets of participants fail. @@ -577,7 +586,8 @@ fn check_part3_different_participants( /// Identifiers. pub fn check_sign_with_dealer_and_identifiers( mut rng: R, -) -> (Vec, Signature, VerifyingKey) { + signing_target: SigningTarget, +) -> (SigningTarget, Signature, VerifyingKey) { // Check error cases first // Check repeated identifiers @@ -643,7 +653,7 @@ pub fn check_sign_with_dealer_and_identifiers( diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 29330b90..940b0f53 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -9,7 +9,7 @@ use crate::keys::refresh::{compute_refreshing_shares, refresh_share}; use crate::{self as frost}; use crate::{ keys::{KeyPackage, PublicKeyPackage, SecretShare}, - Ciphersuite, Error, Identifier, + Ciphersuite, Error, Identifier, SigningTarget, }; use super::ciphersuite_generic::check_sign; @@ -81,7 +81,16 @@ pub fn check_refresh_shares_with_dealer( for (k, v) in new_shares { key_packages.insert(k, v.unwrap()); } - check_sign(MIN_SIGNERS, key_packages, rng, new_pub_key_package).unwrap(); + + let signing_target = SigningTarget::from_message(b"hello world"); + check_sign( + MIN_SIGNERS, + key_packages, + rng, + new_pub_key_package, + signing_target, + ) + .unwrap(); } /// We want to check that shares are refreshed with valid signers diff --git a/frost-ed25519/tests/helpers/samples.json b/frost-ed25519/tests/helpers/samples.json index 3402fbe7..a61e0c4b 100644 --- a/frost-ed25519/tests/helpers/samples.json +++ b/frost-ed25519/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "proof_of_knowledge": "5866666666666666666666666666666666666666666666666666666666666666498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "element1": "5866666666666666666666666666666666666666666666666666666666666666", "element2": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "scalar1": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a" -} \ No newline at end of file +} diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index 6f22aed3..4d785e5c 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 6c564788..320b8707 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -12,7 +12,10 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + b"message".into(), + ); } #[test] @@ -184,7 +187,10 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + b"message".into(), + ); } #[test] @@ -336,7 +342,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed25519Sha512, _, - >(rng); + >(rng, b"message".into()); } #[test] diff --git a/frost-ed25519/tests/interoperability_tests.rs b/frost-ed25519/tests/interoperability_tests.rs index 9c27193f..10c7032b 100644 --- a/frost-ed25519/tests/interoperability_tests.rs +++ b/frost-ed25519/tests/interoperability_tests.rs @@ -12,12 +12,13 @@ fn check_interoperability_in_sign_with_dkg() { // and the interoperability check. A smaller number of iterations is used // because DKG takes longer and otherwise the test would be too slow. for _ in 0..32 { - let (msg, group_signature, group_pubkey) = + let (target, group_signature, group_pubkey) = frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( rng.clone(), + b"message".into(), ); - helpers::verify_signature(&msg, group_signature, group_pubkey); + helpers::verify_signature(target.message(), group_signature, group_pubkey); } } @@ -28,13 +29,14 @@ fn check_interoperability_in_sign_with_dealer() { // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. for _ in 0..256 { - let (msg, group_signature, group_pubkey) = + let (target, group_signature, group_pubkey) = frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( rng.clone(), + b"message".into(), ); // Check that the threshold signature can be verified by the `ed25519_dalek` crate // public key (interoperability test) - helpers::verify_signature(&msg, group_signature, group_pubkey); + helpers::verify_signature(target.message(), group_signature, group_pubkey); } } diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 0b1d44f1..3bdb64ba 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let message = signing_package.message(); + let sig_target = signing_package.sig_target(); - let new_signing_package = SigningPackage::new(commitments.clone(), message); + let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); assert!(signing_package == new_signing_package); } diff --git a/frost-ed448/tests/helpers/samples.json b/frost-ed448/tests/helpers/samples.json index 36e86286..f93c3e9c 100644 --- a/frost-ed448/tests/helpers/samples.json +++ b/frost-ed448/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "proof_of_knowledge": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69004d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "element1": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "element2": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "scalar1": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00" -} \ No newline at end of file +} diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index ec556736..afb26d50 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 70061503..eaf4336b 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -12,7 +12,10 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + b"message".into(), + ); } #[test] @@ -184,7 +187,10 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + b"message".into(), + ); } #[test] @@ -336,7 +342,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed448Shake256, _, - >(rng); + >(rng, b"message".into()); } #[test] diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index e51f8b6e..0f0c14b4 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let message = signing_package.message(); + let sig_target = signing_package.sig_target(); - let new_signing_package = SigningPackage::new(commitments.clone(), message); + let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); assert!(signing_package == new_signing_package); } diff --git a/frost-p256/tests/helpers/samples.json b/frost-p256/tests/helpers/samples.json index 928e355c..3fe4c698 100644 --- a/frost-p256/tests/helpers/samples.json +++ b/frost-p256/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "proof_of_knowledge": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "element1": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "element2": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "scalar1": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1" -} \ No newline at end of file +} diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index 340bc763..432af475 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 8d44312d..c1c9347e 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -12,7 +12,10 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + b"message".into(), + ); } #[test] @@ -184,7 +187,10 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + b"message".into(), + ); } #[test] @@ -333,6 +339,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::( rng, + b"message".into(), ); } diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index a24e57f5..0a96090f 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let message = signing_package.message(); + let sig_target = signing_package.sig_target(); - let new_signing_package = SigningPackage::new(commitments.clone(), message); + let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); assert!(signing_package == new_signing_package); } diff --git a/frost-ristretto255/tests/helpers/samples.json b/frost-ristretto255/tests/helpers/samples.json index bb80d1b8..1fff1337 100644 --- a/frost-ristretto255/tests/helpers/samples.json +++ b/frost-ristretto255/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "element1": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "element2": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "scalar1": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a" -} \ No newline at end of file +} diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index f598c53f..da9e9919 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index af536ac3..307d5762 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -12,7 +12,10 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + b"message".into(), + ); } #[test] @@ -185,7 +188,10 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + b"message".into(), + ); } #[test] @@ -337,7 +343,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ristretto255Sha512, _, - >(rng); + >(rng, b"message".into()); } #[test] diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index a8ed937c..ab06c93a 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let message = signing_package.message(); + let sig_target = signing_package.sig_target(); - let new_signing_package = SigningPackage::new(commitments.clone(), message); + let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); assert!(signing_package == new_signing_package); } diff --git a/frost-secp256k1-tr/benches/bench.rs b/frost-secp256k1-tr/benches/bench.rs new file mode 100644 index 00000000..3d51c3f1 --- /dev/null +++ b/frost-secp256k1-tr/benches/bench.rs @@ -0,0 +1,19 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; + +use frost_secp256k1_tr::*; + +fn bench_secp256k1_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); +} + +fn bench_secp256k1_sign(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); +} + +criterion_group!(benches, bench_secp256k1_batch_verify, bench_secp256k1_sign); +criterion_main!(benches); diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index e73333c5..7cf3b1ea 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -28,9 +28,8 @@ use sha2::{Digest, Sha256}; use frost_core as frost; -// TODO -// #[cfg(test)] -// mod tests; +#[cfg(test)] +mod tests; // Re-exports in our public API #[cfg(feature = "serde")] diff --git a/frost-secp256k1-tr/src/tests.rs b/frost-secp256k1-tr/src/tests.rs new file mode 100644 index 00000000..15a3e184 --- /dev/null +++ b/frost-secp256k1-tr/src/tests.rs @@ -0,0 +1,5 @@ +mod batch; +mod coefficient_commitment; +mod deserialize; +mod proptests; +mod vss_commitment; diff --git a/frost-secp256k1-tr/src/tests/batch.rs b/frost-secp256k1-tr/src/tests/batch.rs new file mode 100644 index 00000000..b87d22a9 --- /dev/null +++ b/frost-secp256k1-tr/src/tests/batch.rs @@ -0,0 +1,24 @@ +use rand::thread_rng; + +use crate::*; + +#[test] +fn check_batch_verify() { + let rng = thread_rng(); + + frost_core::tests::batch::batch_verify::(rng); +} + +#[test] +fn check_bad_batch_verify() { + let rng = thread_rng(); + + frost_core::tests::batch::bad_batch_verify::(rng); +} + +#[test] +fn empty_batch_verify() { + let rng = thread_rng(); + + frost_core::tests::batch::empty_batch_verify::(rng); +} diff --git a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs new file mode 100644 index 00000000..d1b6c22c --- /dev/null +++ b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs @@ -0,0 +1,46 @@ +use lazy_static::lazy_static; +use rand::thread_rng; +use serde_json::Value; + +use crate::*; + +// Tests for serialization and deserialization of CoefficientCommitment + +lazy_static! { + pub static ref ELEMENTS: Value = + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); +} + +#[test] +fn check_serialization_of_coefficient_commitment() { + let rng = thread_rng(); + frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< + Secp256K1Sha256, + _, + >(rng); +} + +#[test] +fn check_create_coefficient_commitment() { + let rng = thread_rng(); + frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< + Secp256K1Sha256, + _, + >(rng); +} +#[test] +fn check_create_coefficient_commitment_error() { + frost_core::tests::coefficient_commitment::check_create_coefficient_commitment_error::< + Secp256K1Sha256, + >(&ELEMENTS); +} + +#[test] +fn check_get_value_of_coefficient_commitment() { + let rng = thread_rng(); + + frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< + Secp256K1Sha256, + _, + >(rng); +} diff --git a/frost-secp256k1-tr/src/tests/deserialize.rs b/frost-secp256k1-tr/src/tests/deserialize.rs new file mode 100644 index 00000000..a744832b --- /dev/null +++ b/frost-secp256k1-tr/src/tests/deserialize.rs @@ -0,0 +1,38 @@ +use crate::*; + +#[test] +fn check_deserialize_non_canonical() { + let mut encoded_generator = ::Group::serialize( + &::Group::generator(), + ) + .unwrap(); + + let r = ::Group::deserialize(&encoded_generator); + assert!(r.is_ok()); + + // The first byte should be 0x02 or 0x03. Set other value to + // create a non-canonical encoding. + encoded_generator[0] = 0xFF; + let r = ::Group::deserialize(&encoded_generator); + assert_eq!(r, Err(GroupError::MalformedElement)); + + // Besides the first byte, it is still possible to get non-canonical encodings. + // This is x = p + 2 which is non-canonical and maps to a valid prime-order point. + let encoded_point = + hex::decode("02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc31") + .unwrap() + .try_into() + .unwrap(); + let r = ::Group::deserialize(&encoded_point); + assert_eq!(r, Err(GroupError::MalformedElement)); +} + +#[test] +fn check_deserialize_identity() { + // The identity is actually encoded as a single byte; but the API does not + // allow us to change that. Try to send something similar. + let encoded_identity = [0u8; 33]; + + let r = ::Group::deserialize(&encoded_identity); + assert_eq!(r, Err(GroupError::MalformedElement)); +} diff --git a/frost-secp256k1-tr/src/tests/proptests.rs b/frost-secp256k1-tr/src/tests/proptests.rs new file mode 100644 index 00000000..dd598569 --- /dev/null +++ b/frost-secp256k1-tr/src/tests/proptests.rs @@ -0,0 +1,33 @@ +use crate::*; +use frost_core::tests::proptests::{tweak_strategy, SignatureCase}; +use proptest::prelude::*; + +use rand_chacha::ChaChaRng; +use rand_core::SeedableRng; + +proptest! { + + #[test] + fn tweak_signature( + tweaks in prop::collection::vec(tweak_strategy(), (0,5)), + rng_seed in prop::array::uniform32(any::()), + ) { + // Use a deterministic RNG so that test failures can be reproduced. + // Seeding with 64 bits of entropy is INSECURE and this code should + // not be copied outside of this test! + let rng = ChaChaRng::from_seed(rng_seed); + + // Create a test case for each signature type. + let msg = b"test message for proptests"; + let mut sig = SignatureCase::::new(rng, msg.to_vec()); + + // Apply tweaks to each case. + for t in &tweaks { + sig.apply_tweak(t); + } + + assert!(sig.check()); + } + + +} diff --git a/frost-secp256k1-tr/src/tests/vss_commitment.rs b/frost-secp256k1-tr/src/tests/vss_commitment.rs new file mode 100644 index 00000000..1a09195a --- /dev/null +++ b/frost-secp256k1-tr/src/tests/vss_commitment.rs @@ -0,0 +1,38 @@ +use lazy_static::lazy_static; +use rand::thread_rng; +use serde_json::Value; + +use crate::*; + +// Tests for serialization and deserialization VerifiableSecretSharingCommitment + +lazy_static! { + pub static ref ELEMENTS: Value = + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); +} + +#[test] +fn check_serialize_vss_commitment() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); +} + +#[test] +fn check_deserialize_vss_commitment() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); +} + +#[test] +fn check_deserialize_vss_commitment_error() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( + rng, &ELEMENTS, + ); +} + +#[test] +fn check_compute_public_key_package() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); +} diff --git a/frost-secp256k1-tr/tests/common_traits_tests.rs b/frost-secp256k1-tr/tests/common_traits_tests.rs new file mode 100644 index 00000000..81b97a95 --- /dev/null +++ b/frost-secp256k1-tr/tests/common_traits_tests.rs @@ -0,0 +1,74 @@ +#![cfg(feature = "serde")] + +mod helpers; + +use frost_secp256k1_tr::SigningKey; +use helpers::samples; +use rand::thread_rng; + +#[allow(clippy::unnecessary_literal_unwrap)] +fn check_common_traits_for_type(v: T) { + // Make sure can be debug-printed. This also catches if the Debug does not + // have an endless recursion (a popular mistake). + println!("{:?}", v); + // Test Clone and Eq + assert_eq!(v, v.clone()); + // Make sure it can be unwrapped in a Result (which requires Debug). + let e: Result = Ok(v.clone()); + assert_eq!(v, e.unwrap()); +} + +#[test] +fn check_signing_key_common_traits() { + let mut rng = thread_rng(); + let signing_key = SigningKey::new(&mut rng); + check_common_traits_for_type(signing_key); +} + +#[test] +fn check_signing_commitments_common_traits() { + let commitments = samples::signing_commitments(); + check_common_traits_for_type(commitments); +} + +#[test] +fn check_signing_package_common_traits() { + let signing_package = samples::signing_package(); + check_common_traits_for_type(signing_package); +} + +#[test] +fn check_signature_share_common_traits() { + let signature_share = samples::signature_share(); + check_common_traits_for_type(signature_share); +} + +#[test] +fn check_secret_share_common_traits() { + let secret_share = samples::secret_share(); + check_common_traits_for_type(secret_share); +} + +#[test] +fn check_key_package_common_traits() { + let key_package = samples::key_package(); + check_common_traits_for_type(key_package); +} + +#[test] +fn check_public_key_package_common_traits() { + let public_key_package = samples::public_key_package(); + check_common_traits_for_type(public_key_package); +} + +#[test] +fn check_round1_package_common_traits() { + let round1_package = samples::round1_package(); + check_common_traits_for_type(round1_package); +} + +#[test] +fn check_round2_package_common_traits() { + let round2_package = samples::round2_package(); + check_common_traits_for_type(round2_package); +} diff --git a/frost-secp256k1-tr/tests/helpers/elements.json b/frost-secp256k1-tr/tests/helpers/elements.json new file mode 100644 index 00000000..e8cd4082 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/elements.json @@ -0,0 +1,5 @@ +{ + "elements": { + "invalid_element": "123456afdf4a7f88885ab26b20d18edb7d4d9589812a6cf1a5a1a09d3808dae5d8" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/mod.rs b/frost-secp256k1-tr/tests/helpers/mod.rs new file mode 100644 index 00000000..2f415966 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/mod.rs @@ -0,0 +1,5 @@ +// Required since each integration test is compiled as a separated crate, +// and each one uses only part of the module. +#![allow(dead_code)] + +pub mod samples; diff --git a/frost-secp256k1-tr/tests/helpers/repair-share.json b/frost-secp256k1-tr/tests/helpers/repair-share.json new file mode 100644 index 00000000..f4db9bc8 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/repair-share.json @@ -0,0 +1,15 @@ +{ + "scalar_generation": { + "random_scalar_1": "1847f6c4a85096e5dbc9e200c9691c5164f8e276d32d4a54ebaf4275474a1403", + "random_scalar_2": "eac5595269d108812eaa865bf62c703a2c128a61fa3bd4dc837b9314bc515204", + "random_scalar_3": "5b3b6084e41c273a39a8d9bbbd87fbcd626c07030142bf78c6c91247bf175700", + "random_scalar_sum": "5e48b09bf63dc6a1441d42187d1d885a38c896f51f633e6e76218944f27c7bc6" + }, + "sigma_generation": { + "sigma_1": "ec3aa83140065181d75b746bfd6bbbbaf212bdfbb3a91670f924d1ca899cbc0c", + "sigma_2": "5dd288d659e0a2dd3ef7523a9cc4f80f4a7f919e9980005c7fbec0961d3fb500", + "sigma_3": "3e62e7461db9ca1ed2f1549a8114bbc87fa9242ce0012ed3f9ac9dcf23f4c30a", + "sigma_4": "684c44e7aba416a1982a8db8ec2a3095f5cc6a3f958a4716b69ae76524dd7200", + "sigma_sum": "f0bc5d356344d51f816ea8fa076fa029f7590120136bec7c6958b9081f7864d5" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/samples.json b/frost-secp256k1-tr/tests/helpers/samples.json new file mode 100644 index 00000000..1cc174a9 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/samples.json @@ -0,0 +1,7 @@ +{ + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "proof_of_knowledge": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "element1": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "element2": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + "scalar1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" +} diff --git a/frost-secp256k1-tr/tests/helpers/samples.rs b/frost-secp256k1-tr/tests/helpers/samples.rs new file mode 100644 index 00000000..839f3f57 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/samples.rs @@ -0,0 +1,128 @@ +//! Generate sample, fixed instances of structs for testing. + +use std::collections::BTreeMap; + +use frost_core::{round1::Nonce, Ciphersuite, Element, Group, Scalar}; +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, SigningShare, VerifiableSecretSharingCommitment, + VerifyingShare, + }, + round1::{NonceCommitment, SigningCommitments, SigningNonces}, + round2::SignatureShare, + Field, Signature, SigningPackage, VerifyingKey, +}; + +type C = frost_secp256k1_tr::Secp256K1Sha256; + +fn element1() -> Element { + ::Group::generator() +} + +fn element2() -> Element { + element1() + element1() +} + +fn scalar1() -> Scalar { + let one = <::Group as Group>::Field::one(); + let three = one + one + one; + // To return a fixed non-small number, get the inverse of 3 + <::Group as Group>::Field::invert(&three) + .expect("nonzero elements have inverses") +} + +/// Generate a sample SigningCommitments. +pub fn signing_nonces() -> SigningNonces { + let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); + + SigningNonces::from_nonces(hiding_nonce, binding_nonce) +} + +/// Generate a sample SigningCommitments. +pub fn signing_commitments() -> SigningCommitments { + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); + + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) +} + +/// Generate a sample SigningPackage. +pub fn signing_package() -> SigningPackage { + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); + let message = "hello world".as_bytes(); + + SigningPackage::new(commitments, message) +} + +/// Generate a sample SignatureShare. +pub fn signature_share() -> SignatureShare { + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() +} + +/// Generate a sample SecretShare. +pub fn secret_share() -> SecretShare { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let vss_commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + SecretShare::new(identifier, signing_share, vss_commitment) +} + +/// Generate a sample KeyPackage. +pub fn key_package() -> KeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + + KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) +} + +/// Generate a sample PublicKeyPackage. +pub fn public_key_package() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key) +} + +/// Generate a sample round1::Package. +pub fn round1_package() -> round1::Package { + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let vss_commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::Package::new(vss_commitment, signature) +} + +/// Generate a sample round2::Package. +pub fn round2_package() -> round2::Package { + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + + round2::Package::new(signing_share) +} diff --git a/frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json b/frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json new file mode 100644 index 00000000..2fedbec2 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json @@ -0,0 +1,77 @@ +{ + "config": { + "MAX_PARTICIPANTS": "3", + "NUM_PARTICIPANTS": "2", + "MIN_PARTICIPANTS": "2", + "name": "FROST(secp256k1, SHA-256)", + "group": "secp256k1", + "hash": "SHA-256" + }, + "inputs": { + "participant_list": [ + 1, + 3 + ], + "group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", + "verifying_key_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", + "message": "74657374", + "share_polynomial_coefficients": [ + "fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579" + ], + "participant_shares": [ + { + "identifier": 1, + "participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c" + }, + { + "identifier": 2, + "participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984" + }, + { + "identifier": 3, + "participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" + } + ] + }, + "round_one_outputs": { + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "bda8e748e599187762cff956f03dc6ea13fc8e04491a0427b7e6e78600f41c52", + "binding_nonce_randomness": "2ca682429bf05df435b9927b8edb1d748278f3e42fa11ef358e49bbf4a1b780d", + "hiding_nonce": "58cd30723da418156fe9b71870a118e0bbc3d0353ba7c760f9bbc8d60c3dab29", + "binding_nonce": "c22289cc43b82ed938d4b2288efb7381c405fb59f5d43bddc543d98838c60b19", + "hiding_nonce_commitment": "024e34ab3a7ad6b4563dbfe97e9f1206b3378cceb2502491ed0fb709765e1e5ba8", + "binding_nonce_commitment": "03d4b1f3a61dc67e64dfb4abfccabb712f1f6914a6ec9b67749d171370453192cb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "55a3e44879db6daf00c81eb28e828869560c0901f347baff524f1c91a6669604" + }, + { + "identifier": 3, + "hiding_nonce_randomness": "70818dd5170672c4a4285fd593d4f222417f941f3118e1244955e7a1098a35d8", + "binding_nonce_randomness": "74ca2da071ed4a2a6cad5087d6758b48a558ab5861c61117fee05757e4b1309e", + "hiding_nonce": "a4109db0a5db30fac8cd1f4e272ff02e08258928f067d82c63d97279b114514a", + "binding_nonce": "ce3837bd963f0d81002279f7bb9eefceac64435f638885c2beae6f1dd881fd9e", + "hiding_nonce_commitment": "02d768658a1b94225645401a1512b803657770c7a21bf9ccccccfa09930a44951b", + "binding_nonce_commitment": "034570a4e5217ee8770a28401185f50b4fce4d3f3933a3af9df7ab39b42381d0eb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "444c1cc7cfe48f4577cce65488f337a9cc8c33dea4cfa986eb590bd8e2b1fa2d" + } + ] + }, + "round_two_outputs": { + "outputs": [ + { + "identifier": 1, + "sig_share": "2ffc305d1694fd84108b84d98306a1af807c6ad9bc3a2d8e448a09643202a15b" + }, + { + "identifier": 3, + "sig_share": "a8c392566ea29e852b4080a028bf5547166c87e703e4fb7136d4ebef65f99b3f" + } + ] + }, + "final_output": { + "sig": "0c776a9516a77808b70a31e74f1464814a6fcf897fb3a6bd84c7a9a9a7a5bcb8d8bfc2b385379c093bcc0579abc5f6f696e8f2c0c01f28ff7b5ef55397fc3c9a" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/vectors.json b/frost-secp256k1-tr/tests/helpers/vectors.json new file mode 100644 index 00000000..2fedbec2 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/vectors.json @@ -0,0 +1,77 @@ +{ + "config": { + "MAX_PARTICIPANTS": "3", + "NUM_PARTICIPANTS": "2", + "MIN_PARTICIPANTS": "2", + "name": "FROST(secp256k1, SHA-256)", + "group": "secp256k1", + "hash": "SHA-256" + }, + "inputs": { + "participant_list": [ + 1, + 3 + ], + "group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", + "verifying_key_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", + "message": "74657374", + "share_polynomial_coefficients": [ + "fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579" + ], + "participant_shares": [ + { + "identifier": 1, + "participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c" + }, + { + "identifier": 2, + "participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984" + }, + { + "identifier": 3, + "participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" + } + ] + }, + "round_one_outputs": { + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "bda8e748e599187762cff956f03dc6ea13fc8e04491a0427b7e6e78600f41c52", + "binding_nonce_randomness": "2ca682429bf05df435b9927b8edb1d748278f3e42fa11ef358e49bbf4a1b780d", + "hiding_nonce": "58cd30723da418156fe9b71870a118e0bbc3d0353ba7c760f9bbc8d60c3dab29", + "binding_nonce": "c22289cc43b82ed938d4b2288efb7381c405fb59f5d43bddc543d98838c60b19", + "hiding_nonce_commitment": "024e34ab3a7ad6b4563dbfe97e9f1206b3378cceb2502491ed0fb709765e1e5ba8", + "binding_nonce_commitment": "03d4b1f3a61dc67e64dfb4abfccabb712f1f6914a6ec9b67749d171370453192cb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "55a3e44879db6daf00c81eb28e828869560c0901f347baff524f1c91a6669604" + }, + { + "identifier": 3, + "hiding_nonce_randomness": "70818dd5170672c4a4285fd593d4f222417f941f3118e1244955e7a1098a35d8", + "binding_nonce_randomness": "74ca2da071ed4a2a6cad5087d6758b48a558ab5861c61117fee05757e4b1309e", + "hiding_nonce": "a4109db0a5db30fac8cd1f4e272ff02e08258928f067d82c63d97279b114514a", + "binding_nonce": "ce3837bd963f0d81002279f7bb9eefceac64435f638885c2beae6f1dd881fd9e", + "hiding_nonce_commitment": "02d768658a1b94225645401a1512b803657770c7a21bf9ccccccfa09930a44951b", + "binding_nonce_commitment": "034570a4e5217ee8770a28401185f50b4fce4d3f3933a3af9df7ab39b42381d0eb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "444c1cc7cfe48f4577cce65488f337a9cc8c33dea4cfa986eb590bd8e2b1fa2d" + } + ] + }, + "round_two_outputs": { + "outputs": [ + { + "identifier": 1, + "sig_share": "2ffc305d1694fd84108b84d98306a1af807c6ad9bc3a2d8e448a09643202a15b" + }, + { + "identifier": 3, + "sig_share": "a8c392566ea29e852b4080a028bf5547166c87e703e4fb7136d4ebef65f99b3f" + } + ] + }, + "final_output": { + "sig": "0c776a9516a77808b70a31e74f1464814a6fcf897fb3a6bd84c7a9a9a7a5bcb8d8bfc2b385379c093bcc0579abc5f6f696e8f2c0c01f28ff7b5ef55397fc3c9a" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json new file mode 100644 index 00000000..ff497e9a --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json @@ -0,0 +1,51 @@ +{ + "config": { + "MAX_PARTICIPANTS": 3, + "MIN_PARTICIPANTS": 2, + "name": "FROST(secp256k1, SHA-256)", + "group": "secp256k1", + "hash": "SHA-256" + }, + "inputs": { + "verifying_key": "034a48daffc43b47b42695611942c481aecffb9137686ad0b3e0ab8e1f1dab0293", + "1": { + "identifier": 1, + "signing_key": "68e3f6904c6043973515a36bf7801a71597da35733f21305d75a5234f06e4529", + "coefficient": "25d2d840a3e2718a431ec69e14ee8a015b000d43c7a9868060f01d5aa52a19d1", + "vss_commitments": ["03e7ba4acb164d2bd5eba4f47b3a788109ddb3f88f1181792424fa332123a25ea8", "037495e920a1f032916193aa80ea97a4c3a611dec9ab47ccc969deb664f5f88bbe"], + "proof_of_knowledge": "6689a8d414eb4961308e21f8caa1045236efded4f3de9209dc07547e88be3b42e192de9bed27fb78a7a4d4e35a0422f11f52631b8e66d69e609398eaff2770b8", + "signing_shares": { + "2": "1dd3cb3e2370e6af22917415f0ad584514807b58b3cc40d2230a26e115f02771", + "3": "dd25ee86acd01f996618aa0d1153f5e8fbc929a8e8a18b8f0a15f91d087217e2" + }, + "verifying_share": "03e2eada7cdb20ec24babb687eb633580c977148c70254700f1ad4a931316dc6d9", + "signing_share": "89b08895c083bb6a00de882d0e6ff2a20a1878b5e8c0c5aba5983100e3c45d0c" + }, + "2": { + "identifier": 2, + "signing_key": "2619be8223b23e0453ddc630a4d164e81f7d8a9e07af33c4d4d02190df8bec13", + "coefficient": "f7ba0cbbffbea8aaceb3ade54bdbf35bafb1cda15b65ad490e0c63dd069a7c9f", + "vss_commitments": ["03ef10370a008cd95e179dc51e2cb7828f30b72d254e5166484f927c84ab326582", "022ce0dac0db217ba326fbbe3e6132d45e2a4bfa0a0c3790d91eacce9a1c2d6a10"], + "proof_of_knowledge": "19df66bda7724ccfa6a5ea76aac9cc167880d55717fe6887b89aeea94408cc9ce47b65a55f9d00479e9d3ea2c7402e81803e2e724d45d70c2cb93e3b0deb5f78", + "signing_shares": { + "1": "b489a711942526abbb5330a8215d2e740f7dbddec3452006993a8cea3ac278cb", + "3": "20255dc07b1fb78bdf90bd85fd2389c988c8250faee11826656a09142fa9fc97" + }, + "verifying_share": "0312705f7560a146760034ebdd103277e184ce81d2ba6a67a43f0b3f39410cc396", + "signing_share": "ea3cdccc32746d918c2910295b0a03dfa1c94f01d20f860c6fe8c22fb6c0d831" + }, + "3": { + "identifier": 3, + "signing_key": "9a267f4cde8087a6eca0969425846209b41b515b73195ebbeeef8a991103f1ec", + "coefficient": "42ff6f39ce4f97f279781378ebcf93df47add84d75882cd31b266e83f76e25f6", + "vss_commitments": ["02da186c3863c5600b471a2799cb6f15ae4d8315a2f225c177798880e75ac820a0", "03e6a36e7fa4b117c1aa428886672e3a35d926bb4c585a9b07d8ee9a3387420067"], + "proof_of_knowledge": "6e115d9e63fd15d432b380ccf1ec4ed03340fcf96caeae8985aedb5f905b1a65dc422ffe5878988fbbc55454857736c7755d9c8f5ee6822c8833ea21d54dba36", + "signing_shares": { + "1": "da5c7f5238079835fe71f746364bb8756a7dcb228aeea686fa2aaa44dfec929c", + "2": "0d47e4b622ee3804bff8cfe088653efefe865cce0c065aecbf7e318182b89e2d" + }, + "verifying_share": "036607b45621ce6840d999980c9c74d69a15fa5a246e852ee2ca6924ce65fa299b", + "signing_share": "4ac93102a4651fb917739825a7a4151e7ecb48670c15a6317a66f4d1b9871215" + } + } +} diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs new file mode 100644 index 00000000..ddbb6540 --- /dev/null +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -0,0 +1,363 @@ +use frost_secp256k1_tr::*; +use lazy_static::lazy_static; +use rand::thread_rng; +use serde_json::Value; + +#[test] +fn check_zero_key_fails() { + frost_core::tests::ciphersuite_generic::check_zero_key_fails::(); +} + +#[test] +fn check_sign_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + b"message".into(), + ); +} + +#[test] +fn check_dkg_part1_fails_with_invalid_signers_min_signers() { + let rng = thread_rng(); + + let min_signers = 1; + let max_signers = 3; + let error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_dkg_part1_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 2; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_dkg_part1_fails_with_invalid_signers_max_signers() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_rts() { + let rng = thread_rng(); + + frost_core::tests::repairable::check_rts::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Secp256K1Sha256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_sign_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + b"message".into(), + ); +} + +#[test] +fn check_sign_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + + let min_signers = 1; + let max_signers = 3; + let error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 2; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_sign_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +/// This is testing that Shamir's secret sharing to compute and arbitrary +/// value is working. +#[test] +fn check_share_generation_secp256k1_tr_sha256() { + let rng = thread_rng(); + frost_core::tests::ciphersuite_generic::check_share_generation::(rng); +} + +#[test] +fn check_share_generation_fails_with_invalid_min_signers() { + let rng = thread_rng(); + + let min_signers = 0; + let max_signers = 3; + let error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_share_generation_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 2; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_share_generation_fails_with_invalid_max_signers() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 0; + let error = Error::InvalidMaxSigners; + + frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(min_signers, max_signers, error, rng); +} + +lazy_static! { + pub static ref VECTORS: Value = + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON"); + pub static ref VECTORS_BIG_IDENTIFIER: Value = + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON"); + pub static ref VECTORS_DKG: Value = + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON"); +} + +#[test] +fn check_sign_with_test_vectors() { + frost_core::tests::vectors::check_sign_with_test_vectors::(&VECTORS); +} + +#[test] +fn check_sign_with_test_vectors_dkg() { + frost_core::tests::vectors_dkg::check_dkg_keygen::(&VECTORS_DKG); +} + +#[test] +fn check_sign_with_test_vectors_with_big_identifiers() { + frost_core::tests::vectors::check_sign_with_test_vectors::( + &VECTORS_BIG_IDENTIFIER, + ); +} + +#[test] +fn check_error_culprit() { + frost_core::tests::ciphersuite_generic::check_error_culprit::(); +} + +#[test] +fn check_identifier_derivation() { + frost_core::tests::ciphersuite_generic::check_identifier_derivation::(); +} + +// Explicit test which is used in a documentation snippet +#[test] +#[allow(unused_variables)] +fn check_identifier_generation() -> Result<(), Error> { + // ANCHOR: dkg_identifier + let participant_identifier = Identifier::try_from(7u16)?; + let participant_identifier = Identifier::derive("alice@example.com".as_bytes())?; + // ANCHOR_END: dkg_identifier + Ok(()) +} + +#[test] +fn check_sign_with_dealer_and_identifiers() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< + Secp256K1Sha256, + _, + >(rng, b"message".into()); +} + +#[test] +fn check_sign_with_missing_identifier() { + let rng = thread_rng(); + frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( + rng, + ); +} + +#[test] +fn check_sign_with_incorrect_commitments() { + let rng = thread_rng(); + frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< + Secp256K1Sha256, + _, + >(rng); +} diff --git a/frost-secp256k1-tr/tests/recreation_tests.rs b/frost-secp256k1-tr/tests/recreation_tests.rs new file mode 100644 index 00000000..6b3ce890 --- /dev/null +++ b/frost-secp256k1-tr/tests/recreation_tests.rs @@ -0,0 +1,133 @@ +//! Test for recreating packages from their components, which shows that they +//! can be serialized and deserialized as the user wishes. + +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, + }, + round1::{SigningCommitments, SigningNonces}, + round2::SignatureShare, + SigningPackage, +}; + +mod helpers; + +use helpers::samples; + +/// Check if SigningNonces can be recreated. +#[test] +fn check_signing_nonces_recreation() { + let nonces = samples::signing_nonces(); + let hiding = nonces.hiding(); + let binding = nonces.binding(); + let new_nonces = SigningNonces::from_nonces(*hiding, *binding); + assert!(nonces == new_nonces); +} + +/// Check if SigningCommitments can be recreated. +#[test] +fn check_signing_commitments_recreation() { + let commitments = samples::signing_commitments(); + let hiding = commitments.hiding(); + let binding = commitments.binding(); + let new_commitments = SigningCommitments::new(*hiding, *binding); + assert!(commitments == new_commitments); +} + +/// Check if SigningPackage can be recreated. +#[test] +fn check_signing_package_recreation() { + let signing_package = samples::signing_package(); + + let commitments = signing_package.signing_commitments(); + let sig_target = signing_package.sig_target(); + + let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + assert!(signing_package == new_signing_package); +} + +/// Check if SignatureShare can be recreated. +#[test] +fn check_signature_share_recreation() { + let signature_share = samples::signature_share(); + + let encoded = signature_share.serialize(); + + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); + assert!(signature_share == new_signature_share); +} + +/// Check if SecretShare can be recreated. +#[test] +fn check_secret_share_recreation() { + let secret_share = samples::secret_share(); + + let identifier = secret_share.identifier(); + let value = secret_share.signing_share(); + let commitment = secret_share.commitment(); + + let new_secret_share = SecretShare::new(*identifier, *value, commitment.clone()); + + assert!(secret_share == new_secret_share); +} + +/// Check if KeyPackage can be recreated. +#[test] +fn check_key_package_recreation() { + let key_package = samples::key_package(); + + let identifier = key_package.identifier(); + let signing_share = key_package.signing_share(); + let verifying_share = key_package.verifying_share(); + let verifying_key = key_package.verifying_key(); + let min_signers = key_package.min_signers(); + + let new_key_package = KeyPackage::new( + *identifier, + *signing_share, + *verifying_share, + *verifying_key, + *min_signers, + ); + + assert!(key_package == new_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_recreation() { + let public_key_package = samples::public_key_package(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + + let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if round1::Package can be recreated. +#[test] +fn check_round1_package_recreation() { + let round1_package = samples::round1_package(); + + let vss_commitment = round1_package.commitment(); + let signature = round1_package.proof_of_knowledge(); + + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); + + assert!(round1_package == new_round1_package); +} + +/// Check if round2::Package can be recreated. +#[test] +fn check_round2_package_recreation() { + let round2_package = samples::round2_package(); + + let signing_share = round2_package.signing_share(); + + let new_round2_package = round2::Package::new(*signing_share); + + assert!(round2_package == new_round2_package); +} diff --git a/frost-secp256k1-tr/tests/rerandomized_tests.rs b/frost-secp256k1-tr/tests/rerandomized_tests.rs new file mode 100644 index 00000000..79bb5aa3 --- /dev/null +++ b/frost-secp256k1-tr/tests/rerandomized_tests.rs @@ -0,0 +1,10 @@ +use frost_secp256k1_tr::Secp256K1Sha256; +use rand::thread_rng; + +#[test] +fn check_randomized_sign_with_dealer() { + let rng = thread_rng(); + + let (_msg, _group_signature, _group_pubkey) = + frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); +} diff --git a/frost-secp256k1-tr/tests/serde_tests.rs b/frost-secp256k1-tr/tests/serde_tests.rs new file mode 100644 index 00000000..fb8ce73b --- /dev/null +++ b/frost-secp256k1-tr/tests/serde_tests.rs @@ -0,0 +1,642 @@ +#![cfg(feature = "serde")] + +mod helpers; + +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, + }, + round1::SigningCommitments, + round2::SignatureShare, + SigningPackage, +}; + +use helpers::samples; + +#[test] +fn check_signing_commitments_serialization() { + let commitments = samples::signing_commitments(); + + let json = serde_json::to_string_pretty(&commitments).unwrap(); + println!("{}", json); + + let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); + assert!(commitments == decoded_commitments); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + let decoded_commitments: SigningCommitments = serde_json::from_str(json).unwrap(); + assert!(commitments == decoded_commitments); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Wrong ciphersuite + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST(Wrong, SHA-512)" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "0000000000000000000000000000000000000000000000000000000000000000", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST(Ed25519, SHA-512)" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_signing_package_serialization() { + let signing_package = samples::signing_package(); + + let json = serde_json::to_string_pretty(&signing_package).unwrap(); + println!("{}", json); + + let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); + assert!(signing_package == decoded_signing_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "sig_target": { + "message": "68656c6c6f20776f726c64" + } + }"#; + let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); + assert!(signing_package == decoded_signing_package); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "0000000000000000000000000000000000000000000000000000000000000000": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "sig_target": { + "message": "68656c6c6f20776f726c64" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "sig_target": { + "message": "68656c6c6f20776f726c64" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "sig_target": { + "message": "68656c6c6f20776f726c64" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "sig_target": { + "message": "68656c6c6f20776f726c64" + }, + "extra": 1 + } + "#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_signature_share_serialization() { + let signature_share = samples::signature_share(); + + let json = serde_json::to_string_pretty(&signature_share).unwrap(); + println!("{}", json); + + let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); + assert!(signature_share == decoded_signature_share); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); + assert!(signature_share == decoded_commitments); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_secret_share_serialization() { + let secret_share = samples::secret_share(); + + let json = serde_json::to_string_pretty(&secret_share).unwrap(); + println!("{}", json); + + let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); + assert!(secret_share == decoded_secret_share); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + let decoded_secret_share: SecretShare = serde_json::from_str(json).unwrap(); + assert!(secret_share == decoded_secret_share); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + "extra": 1, + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_key_package_serialization() { + let key_package = samples::key_package(); + + let json = serde_json::to_string_pretty(&key_package).unwrap(); + println!("{}", json); + + let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); + assert!(key_package == decoded_key_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 + }"#; + let decoded_key_package: KeyPackage = serde_json::from_str(json).unwrap(); + assert!(key_package == decoded_key_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "extra_field": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid version + let invalid_json = r#"{ + "header": { + "version": 1, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "public": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "group_public": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_public_key_package_serialization() { + let public_key_package = samples::public_key_package(); + + let json = serde_json::to_string_pretty(&public_key_package).unwrap(); + println!("{}", json); + + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); + assert!(public_key_package == decoded_public_key_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package == decoded_public_key_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "0000000000000000000000000000000000000000000000000000000000000000": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_round1_package_serialization() { + let round1_package = samples::round1_package(); + + let json = serde_json::to_string_pretty(&round1_package).unwrap(); + println!("{}", json); + + let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); + assert!(round1_package == decoded_round1_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ], + "proof_of_knowledge": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + let decoded_round1_package: round1::Package = serde_json::from_str(json).unwrap(); + assert!(round1_package == decoded_round1_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ], + "foo": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ], + "proof_of_knowledge": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_round2_package_serialization() { + let round2_package = samples::round2_package(); + + let json = serde_json::to_string_pretty(&round2_package).unwrap(); + println!("{}", json); + + let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); + assert!(round2_package == decoded_round2_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + let decoded_round2_package: round2::Package = serde_json::from_str(json).unwrap(); + assert!(round2_package == decoded_round2_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} diff --git a/frost-secp256k1-tr/tests/serialization_tests.rs b/frost-secp256k1-tr/tests/serialization_tests.rs new file mode 100644 index 00000000..8f93cfb5 --- /dev/null +++ b/frost-secp256k1-tr/tests/serialization_tests.rs @@ -0,0 +1,105 @@ +#![cfg(feature = "serialization")] + +mod helpers; + +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, + }, + round1::{SigningCommitments, SigningNonces}, + round2::SignatureShare, + SigningPackage, +}; + +use helpers::samples; +use insta::assert_snapshot; + +#[test] +fn check_signing_nonces_postcard_serialization() { + let nonces = samples::signing_nonces(); + let bytes: Vec<_> = nonces.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!(nonces, SigningNonces::deserialize(&bytes).unwrap()); +} + +#[test] +fn check_signing_commitments_postcard_serialization() { + let commitments = samples::signing_commitments(); + let bytes: Vec<_> = commitments.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + commitments, + SigningCommitments::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_signing_package_postcard_serialization() { + let signing_package = samples::signing_package(); + let bytes: Vec<_> = signing_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signing_package, + SigningPackage::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_signature_share_postcard_serialization() { + let signature_share = samples::signature_share(); + let bytes = signature_share.serialize(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); +} +#[test] +fn check_secret_share_postcard_serialization() { + let secret_share = samples::secret_share(); + let bytes: Vec<_> = secret_share.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!(secret_share, SecretShare::deserialize(&bytes).unwrap()); +} + +#[test] +fn check_key_package_postcard_serialization() { + let key_package = samples::key_package(); + let bytes: Vec<_> = key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!(key_package, KeyPackage::deserialize(&bytes).unwrap()); +} + +#[test] +fn check_public_key_package_postcard_serialization() { + let public_key_package = samples::public_key_package(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_round1_package_postcard_serialization() { + let round1_package = samples::round1_package(); + let bytes: Vec<_> = round1_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_package, + round1::Package::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_round2_package_postcard_serialization() { + let round2_package = samples::round2_package(); + let bytes: Vec<_> = round2_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_package, + round2::Package::deserialize(&bytes).unwrap() + ); +} diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap new file mode 100644 index 00000000..ca169f4c --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b810279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap new file mode 100644 index 00000000..5600403f --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab301000000000000000000000000000000000000000000000000000000000000002a0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap new file mode 100644 index 00000000..9099b14e --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817984079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap new file mode 100644 index 00000000..218294fb --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap new file mode 100644 index 00000000..82e3585a --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap new file mode 100644 index 00000000..aa7a5030 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(bytes)" +--- +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap new file mode 100644 index 00000000..66962d3c --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap new file mode 100644 index 00000000..537b8e38 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b8100230f8ab3034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap new file mode 100644 index 00000000..cc62ef11 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab301000000000000000000000000000000000000000000000000000000000000002a00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee50b68656c6c6f20776f726c6400 diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs new file mode 100644 index 00000000..ee31e487 --- /dev/null +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -0,0 +1,89 @@ +use frost_secp256k1_tr::*; +use rand::thread_rng; + +#[test] +fn check_tweaked_signing_key() { + let signing_key = SigningKey::deserialize(&[0xAA; 32]).unwrap(); + let untweaked_verifying_key = VerifyingKey::from(signing_key); + + let mut rng = rand::thread_rng(); + let message = b"message"; + + let untweaked_signature = signing_key.sign(&mut rng, &message); + + untweaked_verifying_key + .verify(&message, &untweaked_signature) + .expect("untweaked signature should be valid under untweaked verifying key"); + + let signing_target = SigningTarget::new( + &message, + SigningParameters { + tapscript_merkle_root: Some(vec![]), + }, + ); + + let tweaked_signature = signing_key.sign(&mut rng, signing_target.clone()); + + untweaked_verifying_key + .verify(&message, &tweaked_signature) + .expect_err("tweaked signature should not be valid under untweaked verifying key"); + + let tweaked_verifying_key = untweaked_verifying_key.effective_key(signing_target.sig_params()); + tweaked_verifying_key + .verify(&message, &tweaked_signature) + .expect("tweaked signature should be valid under tweaked verifying key"); + + untweaked_verifying_key + .verify(signing_target.clone(), &tweaked_signature) + .expect( + "tweaked signature should be valid under untweaked verifying key\ + when signing params are provided", + ); +} + +#[test] +fn check_tweaked_sign_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + SigningTarget::new( + b"message", + SigningParameters { + tapscript_merkle_root: Some(vec![]), + }, + ), + ); +} +#[test] +fn check_tweaked_sign_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + SigningTarget::new( + b"message", + SigningParameters { + tapscript_merkle_root: Some(vec![]), + }, + ), + ); +} + +#[test] +fn check_tweaked_sign_with_dealer_and_identifiers() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< + Secp256K1Sha256, + _, + >( + rng, + SigningTarget::new( + b"message", + SigningParameters { + tapscript_merkle_root: Some(vec![]), + }, + ), + ); +} diff --git a/frost-secp256k1/tests/helpers/samples.json b/frost-secp256k1/tests/helpers/samples.json index 210c6f27..54f6e1e1 100644 --- a/frost-secp256k1/tests/helpers/samples.json +++ b/frost-secp256k1/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "proof_of_knowledge": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "element1": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "element2": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "scalar1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" -} \ No newline at end of file +} diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index 3afe78f9..11b84085 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 9581384b..4cdd2294 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -12,7 +12,10 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng, + b"message".into(), + ); } #[test] @@ -184,7 +187,10 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng, + b"message".into(), + ); } #[test] @@ -336,7 +342,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Secp256K1Sha256, _, - >(rng); + >(rng, b"message".into()); } #[test] diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index bb2f8315..1158385b 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let message = signing_package.message(); + let sig_target = signing_package.sig_target(); - let new_signing_package = SigningPackage::new(commitments.clone(), message); + let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); assert!(signing_package == new_signing_package); } diff --git a/gencode/src/main.rs b/gencode/src/main.rs index eda52d4e..8a91e576 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -227,7 +227,13 @@ fn main() -> ExitCode { &std::fs::read_to_string(format!("{original_folder}/tests/helpers/samples.json")).unwrap(), ) .unwrap(); - for key in &["identifier", "element1", "element2", "scalar1"] { + for key in &[ + "identifier", + "proof_of_knowledge", + "element1", + "element2", + "scalar1", + ] { original_strings.push(samples[key].as_str().unwrap().to_owned()); } let original_strings: Vec<&str> = original_strings.iter().map(|s| s.as_ref()).collect(); @@ -290,6 +296,19 @@ fn main() -> ExitCode { "", ], ), + ( + "frost-secp256k1-tr", + &[ + "Secp256K1Sha256", + "secp256k1 curve", + "Secp256K1", + "FROST(secp256k1, SHA-256)", + "FROST-secp256k1-SHA256-TR-v1", + "secp256k1_tr_sha256", + "secp256k1_tr", + "", + ], + ), ] { // Some test use "sample" values. To make these tests work for another ciphersuites, // these values must be replaced. To make it cleaner, the strings are @@ -300,7 +319,13 @@ fn main() -> ExitCode { &std::fs::read_to_string(format!("{folder}/tests/helpers/samples.json")).unwrap(), ) .unwrap(); - for key in &["identifier", "element1", "element2", "scalar1"] { + for key in &[ + "identifier", + "proof_of_knowledge", + "element1", + "element2", + "scalar1", + ] { replacement_strings.push(samples[key].as_str().unwrap().to_owned()); } let replacement_strings: Vec<&str> = From d69a59d62f2d6e1ae81ad3034188535119e2e7d3 Mon Sep 17 00:00:00 2001 From: conduition Date: Fri, 4 Oct 2024 19:50:12 +0000 Subject: [PATCH 04/13] clippy fixes --- frost-core/src/keys/dkg.rs | 2 +- frost-core/src/round2.rs | 4 ++-- frost-core/src/verifying_key.rs | 2 +- frost-secp256k1-tr/src/lib.rs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 77e29d41..29c906ec 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -331,7 +331,7 @@ where let mut preimage = vec![]; preimage.extend_from_slice(identifier.serialize().as_ref()); - preimage.extend_from_slice(::serialize(&verifying_key)?.as_ref()); + preimage.extend_from_slice(::serialize(verifying_key)?.as_ref()); preimage.extend_from_slice(::serialize(R)?.as_ref()); Ok(Challenge( diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 06cfec29..00f9911d 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -75,8 +75,8 @@ where sig_params: &C::SigningParameters, ) -> Result<(), Error> { let commitment_share = - ::effective_commitment_share(group_commitment_share.clone(), &group_commitment); - let vsh = ::effective_verifying_share(&verifying_share, &verifying_key, &sig_params); + ::effective_commitment_share(*group_commitment_share, group_commitment); + let vsh = ::effective_verifying_share(verifying_share, verifying_key, sig_params); if (::generator() * self.to_scalar()) != (commitment_share + (vsh * challenge.0 * lambda_i)) diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index b472d484..ec5c9f69 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -77,7 +77,7 @@ where // // where h is the cofactor let R = signature.R; - let vk = C::effective_pubkey_element(&self, sig_params); + let vk = C::effective_pubkey_element(self, sig_params); let zB = C::Group::generator() * signature.z; let cA = vk * challenge.0; diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 7cf3b1ea..8d345fbe 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -335,7 +335,7 @@ impl Ciphersuite for Secp256K1Sha256 { ) -> Result, Error> { let mut preimage = vec![]; let tweaked_pk = tweaked_public_key( - &verifying_key, + verifying_key, sig_target.sig_params().tapscript_merkle_root.as_ref(), ); preimage.extend_from_slice(&R.to_affine().x()); @@ -352,13 +352,13 @@ impl Ciphersuite for Secp256K1Sha256 { verifying_key: &VerifyingKey, sig_target: &SigningTarget, ) -> Result { - let challenge = Self::challenge(&R, verifying_key, &sig_target)?; + let challenge = Self::challenge(&R, verifying_key, sig_target)?; let t = tweak( &verifying_key.to_element(), sig_target.sig_params().tapscript_merkle_root.as_ref(), ); - let tc = t * challenge.clone().to_scalar(); + let tc = t * challenge.to_scalar(); let tweaked_pubkey = tweaked_public_key( verifying_key, sig_target.sig_params().tapscript_merkle_root.as_ref(), @@ -382,7 +382,7 @@ impl Ciphersuite for Secp256K1Sha256 { ) -> Signature { let tweaked_pubkey = tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref()); - let c = challenge.clone().to_scalar(); + let c = challenge.to_scalar(); let z = if tweaked_pubkey.to_affine().y_is_odd().into() { k - (c * secret) } else { From 802de7a5c09faba9e5f3b7347bbed1b52a8d1dff Mon Sep 17 00:00:00 2001 From: conduition Date: Sat, 5 Oct 2024 19:31:53 +0000 Subject: [PATCH 05/13] tweak DKG output to avoid rogue taproot tweaks --- frost-core/src/keys/dkg.rs | 14 +++---- frost-core/src/traits.rs | 37 +++++++++++++++-- frost-secp256k1-tr/src/lib.rs | 41 +++++++++++++++++++ .../tests/helpers/vectors_dkg.json | 14 +++---- 4 files changed, 87 insertions(+), 19 deletions(-) diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 29c906ec..712a516a 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -562,16 +562,12 @@ pub fn part3( &round2_secret_package.commitment, ))) .collect(); - let public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?; - let key_package = KeyPackage { - header: Header::default(), - identifier: round2_secret_package.identifier, + C::dkg_output_finalize( + round2_secret_package.identifier, + commitments, signing_share, verifying_share, - verifying_key: public_key_package.verifying_key, - min_signers: round2_secret_package.min_signers, - }; - - Ok((key_package, public_key_package)) + round2_secret_package.min_signers, + ) } diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 5e21a2f0..73e52c66 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -5,14 +5,17 @@ use core::{ ops::{Add, Mul, Sub}, }; -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; use crate::{ challenge, - keys::{KeyPackage, VerifyingShare}, + keys::{ + KeyPackage, PublicKeyPackage, SigningShare, VerifiableSecretSharingCommitment, + VerifyingShare, + }, round1, round2, BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError, - Signature, SigningTarget, VerifyingKey, + Header, Identifier, Signature, SigningTarget, VerifyingKey, }; /// A prime order finite field GF(q) over which all scalar values for our prime order group can be @@ -447,4 +450,32 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { ) -> ::Element { verifying_share.to_element() } + + /// Construct the key packages from the output of a successful DKG execution. + /// The signing share and verifying share have already been verified and the + /// identifier belongs to our signer. + /// + /// In frost-sepc256k1-tr, this adds a hash-based tweak to the group key + /// to prevent peers from inserting rogue tapscript tweaks into the group's + /// joint public key. + fn dkg_output_finalize( + identifier: Identifier, + commitments: BTreeMap, &VerifiableSecretSharingCommitment>, + signing_share: SigningShare, + verifying_share: VerifyingShare, + min_signers: u16, + ) -> Result<(KeyPackage, PublicKeyPackage), Error> { + let public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?; + + let key_package = KeyPackage { + header: Header::default(), + identifier, + signing_share, + verifying_share, + verifying_key: public_key_package.verifying_key, + min_signers, + }; + + Ok((key_package, public_key_package)) + } } diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 8d345fbe..a34680b5 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -545,6 +545,47 @@ impl Ciphersuite for Secp256K1Sha256 { vs } } + + /// We add an unusable taproot tweak to the group key computed by a DKG run, + /// to prevent peers from inserting rogue tapscript tweaks into the group's + /// joint public key. + fn dkg_output_finalize( + identifier: Identifier, + commitments: BTreeMap, + signing_share: keys::SigningShare, + verifying_share: keys::VerifyingShare, + min_signers: u16, + ) -> Result<(keys::KeyPackage, keys::PublicKeyPackage), Error> { + let untweaked_public_key_package = + keys::PublicKeyPackage::from_dkg_commitments(&commitments)?; + + let untweaked_vk = untweaked_public_key_package.verifying_key().to_element(); + let t = tweak(&untweaked_vk, Some(vec![])); // unspendable script path + let tG = ProjectivePoint::GENERATOR * t; + + let tweaked_verifying_shares: BTreeMap = + untweaked_public_key_package + .verifying_shares() + .clone() + .into_iter() + .map(|(id, share)| (id, keys::VerifyingShare::new(share.to_element() + tG))) + .collect(); + + let tweaked_verifying_key = VerifyingKey::new(untweaked_vk + tG); + + let key_package = keys::KeyPackage::new( + identifier, + keys::SigningShare::new(signing_share.to_scalar() + t), + keys::VerifyingShare::new(verifying_share.to_element() + tG), + tweaked_verifying_key, + min_signers, + ); + + let public_key_package = + keys::PublicKeyPackage::new(tweaked_verifying_shares, tweaked_verifying_key); + + Ok((key_package, public_key_package)) + } } impl RandomizedCiphersuite for Secp256K1Sha256 { diff --git a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json index ff497e9a..048f620e 100644 --- a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json +++ b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json @@ -7,7 +7,7 @@ "hash": "SHA-256" }, "inputs": { - "verifying_key": "034a48daffc43b47b42695611942c481aecffb9137686ad0b3e0ab8e1f1dab0293", + "verifying_key": "02409611d2fd36025b75caa15f1d70f6d5cfea9cc4254d29580075fdf832d934db", "1": { "identifier": 1, "signing_key": "68e3f6904c6043973515a36bf7801a71597da35733f21305d75a5234f06e4529", @@ -18,8 +18,8 @@ "2": "1dd3cb3e2370e6af22917415f0ad584514807b58b3cc40d2230a26e115f02771", "3": "dd25ee86acd01f996618aa0d1153f5e8fbc929a8e8a18b8f0a15f91d087217e2" }, - "verifying_share": "03e2eada7cdb20ec24babb687eb633580c977148c70254700f1ad4a931316dc6d9", - "signing_share": "89b08895c083bb6a00de882d0e6ff2a20a1878b5e8c0c5aba5983100e3c45d0c" + "verifying_share": "03487e21b8658bffc7903fa2f6435bdae7a417990a27698c273a14f72bece665f3", + "signing_share": "051f56860fd1225af141494c573ce33da026e1c5f8c5099b73c9ad28675a8389" }, "2": { "identifier": 2, @@ -31,8 +31,8 @@ "1": "b489a711942526abbb5330a8215d2e740f7dbddec3452006993a8cea3ac278cb", "3": "20255dc07b1fb78bdf90bd85fd2389c988c8250faee11826656a09142fa9fc97" }, - "verifying_share": "0312705f7560a146760034ebdd103277e184ce81d2ba6a67a43f0b3f39410cc396", - "signing_share": "ea3cdccc32746d918c2910295b0a03dfa1c94f01d20f860c6fe8c22fb6c0d831" + "verifying_share": "02aab83a9c57284041ae6add2d5e2ea43d715c6607e03c4c22ca5aae10086076f4", + "signing_share": "65abaabc81c1d4827c8bd148a3d6f47b37d7b811e213c9fc3e1a3e573a56feae" }, "3": { "identifier": 3, @@ -44,8 +44,8 @@ "1": "da5c7f5238079835fe71f746364bb8756a7dcb228aeea686fa2aaa44dfec929c", "2": "0d47e4b622ee3804bff8cfe088653efefe865cce0c065aecbf7e318182b89e2d" }, - "verifying_share": "036607b45621ce6840d999980c9c74d69a15fa5a246e852ee2ca6924ce65fa299b", - "signing_share": "4ac93102a4651fb917739825a7a4151e7ecb48670c15a6317a66f4d1b9871215" + "verifying_share": "031f1571625e54d9bb0e58e228abe5430460ab9d414bced42250fd09526476290b", + "signing_share": "c637fef2f3b286aa07d65944f07105b8cf888e5dcb628a5d086acf860d5379d3" } } } From 404ba69862562c502f3d23e8b46acfaea07092e4 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 18 Oct 2024 18:56:13 -0300 Subject: [PATCH 06/13] add interoperability tests --- frost-secp256k1-tr/Cargo.toml | 1 + frost-secp256k1-tr/tests/helpers/mod.rs | 19 ++++++++ .../tests/interoperability_tests.rs | 43 +++++++++++++++++++ frost-secp256k1-tr/tests/tweaking_tests.rs | 7 +++ 4 files changed, 70 insertions(+) create mode 100644 frost-secp256k1-tr/tests/interoperability_tests.rs diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index ac0f26ae..99d93687 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -40,6 +40,7 @@ lazy_static = "1.4" proptest = "1.0" rand = "0.8" rand_chacha = "0.3" +secp256k1 = "0.30.0" serde_json = "1.0" [features] diff --git a/frost-secp256k1-tr/tests/helpers/mod.rs b/frost-secp256k1-tr/tests/helpers/mod.rs index 2f415966..6b899101 100644 --- a/frost-secp256k1-tr/tests/helpers/mod.rs +++ b/frost-secp256k1-tr/tests/helpers/mod.rs @@ -2,4 +2,23 @@ // and each one uses only part of the module. #![allow(dead_code)] +use frost_secp256k1_tr::Secp256K1Sha256; +use secp256k1::Secp256k1; + pub mod samples; + +pub fn verify_signature( + msg: &[u8], + group_signature: frost_core::Signature, + group_pubkey: frost_core::VerifyingKey, +) { + let secp = Secp256k1::new(); + let sig = secp256k1::schnorr::Signature::from_byte_array( + group_signature.serialize().unwrap().try_into().unwrap(), + ); + let pubkey = secp256k1::XOnlyPublicKey::from_byte_array( + &group_pubkey.serialize().unwrap()[1..33].try_into().unwrap(), + ) + .unwrap(); + secp.verify_schnorr(&sig, msg, &pubkey).unwrap(); +} diff --git a/frost-secp256k1-tr/tests/interoperability_tests.rs b/frost-secp256k1-tr/tests/interoperability_tests.rs new file mode 100644 index 00000000..165d7b6c --- /dev/null +++ b/frost-secp256k1-tr/tests/interoperability_tests.rs @@ -0,0 +1,43 @@ +use frost_secp256k1_tr::*; + +use crate::Secp256K1Sha256; +use rand::thread_rng; + +mod helpers; + +#[test] +fn check_interoperability_in_sign_with_dkg() { + let rng = thread_rng(); + + // Test with multiple keys/signatures to better exercise the key generation + // and the interoperability check. A smaller number of iterations is used + // because DKG takes longer and otherwise the test would be too slow. + for _ in 0..32 { + let (target, group_signature, group_pubkey) = + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng.clone(), + b"message".into(), + ); + + helpers::verify_signature(target.message(), group_signature, group_pubkey); + } +} + +#[test] +fn check_interoperability_in_sign_with_dealer() { + let rng = thread_rng(); + + // Test with multiple keys/signatures to better exercise the key generation + // and the interoperability check. + for _ in 0..256 { + let (target, group_signature, group_pubkey) = + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng.clone(), + b"message".into(), + ); + + // Check that the threshold signature can be verified by the `ed25519_dalek` crate + // public key (interoperability test) + helpers::verify_signature(target.message(), group_signature, group_pubkey); + } +} diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs index ee31e487..7ae33ad5 100644 --- a/frost-secp256k1-tr/tests/tweaking_tests.rs +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -1,5 +1,8 @@ use frost_secp256k1_tr::*; use rand::thread_rng; +use secp256k1::Secp256k1; + +mod helpers; #[test] fn check_tweaked_signing_key() { @@ -11,6 +14,8 @@ fn check_tweaked_signing_key() { let untweaked_signature = signing_key.sign(&mut rng, &message); + helpers::verify_signature(message, untweaked_signature, untweaked_verifying_key); + untweaked_verifying_key .verify(&message, &untweaked_signature) .expect("untweaked signature should be valid under untweaked verifying key"); @@ -39,6 +44,8 @@ fn check_tweaked_signing_key() { "tweaked signature should be valid under untweaked verifying key\ when signing params are provided", ); + + helpers::verify_signature(message, tweaked_signature, tweaked_verifying_key); } #[test] From 8e22bc7dc23893cb5d8e73e1d93da65b47b4400a Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 18 Oct 2024 21:19:51 -0300 Subject: [PATCH 07/13] cleanup taproot implementation to minimize impact in frost_core --- frost-core/src/batch.rs | 16 +- frost-core/src/keys.rs | 12 +- frost-core/src/keys/dkg.rs | 44 +- frost-core/src/lib.rs | 133 ++-- frost-core/src/round1.rs | 23 +- frost-core/src/round2.rs | 32 +- frost-core/src/signature.rs | 50 ++ frost-core/src/signing_key.rs | 27 +- frost-core/src/tests/ciphersuite_generic.rs | 39 +- frost-core/src/tests/refresh.rs | 13 +- frost-core/src/traits.rs | 393 +++++------ frost-core/src/verifying_key.rs | 37 +- frost-ed25519/src/lib.rs | 10 +- frost-ed25519/tests/integration_tests.rs | 12 +- frost-ed25519/tests/interoperability_tests.rs | 10 +- frost-ed25519/tests/recreation_tests.rs | 4 +- frost-ed25519/tests/serde_tests.rs | 20 +- frost-ed448/src/lib.rs | 10 +- frost-ed448/tests/integration_tests.rs | 12 +- frost-ed448/tests/recreation_tests.rs | 4 +- frost-ed448/tests/serde_tests.rs | 20 +- frost-p256/src/lib.rs | 10 +- frost-p256/tests/integration_tests.rs | 11 +- frost-p256/tests/recreation_tests.rs | 4 +- frost-p256/tests/serde_tests.rs | 20 +- frost-ristretto255/src/lib.rs | 10 +- frost-ristretto255/tests/integration_tests.rs | 12 +- frost-ristretto255/tests/recreation_tests.rs | 4 +- frost-ristretto255/tests/serde_tests.rs | 20 +- frost-secp256k1-tr/README.md | 2 +- frost-secp256k1-tr/benches/bench.rs | 4 +- frost-secp256k1-tr/src/keys/repairable.rs | 16 +- frost-secp256k1-tr/src/lib.rs | 661 +++++++++++------- frost-secp256k1-tr/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 8 +- frost-secp256k1-tr/src/tests/deserialize.rs | 12 +- frost-secp256k1-tr/src/tests/proptests.rs | 2 +- .../src/tests/vss_commitment.rs | 12 +- frost-secp256k1-tr/tests/helpers/mod.rs | 6 +- frost-secp256k1-tr/tests/helpers/samples.rs | 2 +- frost-secp256k1-tr/tests/integration_tests.rs | 88 ++- .../tests/interoperability_tests.rs | 28 +- frost-secp256k1-tr/tests/recreation_tests.rs | 4 +- .../tests/rerandomized_tests.rs | 4 +- frost-secp256k1-tr/tests/serde_tests.rs | 20 +- ...igning_package_postcard_serialization.snap | 2 +- frost-secp256k1-tr/tests/tweaking_tests.rs | 163 ++--- frost-secp256k1/src/lib.rs | 10 +- frost-secp256k1/tests/integration_tests.rs | 12 +- frost-secp256k1/tests/recreation_tests.rs | 4 +- frost-secp256k1/tests/serde_tests.rs | 20 +- gencode/src/main.rs | 23 +- 52 files changed, 1010 insertions(+), 1111 deletions(-) diff --git a/frost-core/src/batch.rs b/frost-core/src/batch.rs index fb5ada0e..a30a109e 100644 --- a/frost-core/src/batch.rs +++ b/frost-core/src/batch.rs @@ -20,7 +20,6 @@ use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *}; pub struct Item { vk: VerifyingKey, sig: Signature, - sig_params: C::SigningParameters, c: Challenge, } @@ -34,13 +33,12 @@ where where M: AsRef<[u8]>, { - let sig_target = SigningTarget::from_message(msg); - let c = ::challenge(&sig.R, &vk, &sig_target)?; + let (msg, sig, vk) = ::pre_verify(msg.as_ref(), &sig, &vk)?; + let c = ::challenge(&sig.R, &vk, &msg)?; Ok(Self { - vk, - sig, - sig_params: sig_target.sig_params, + vk: *vk, + sig: *sig, c, }) } @@ -58,8 +56,7 @@ where /// requires borrowing the message data, the `Item` type is unlinked /// from the lifetime of the message. pub fn verify_single(self) -> Result<(), Error> { - self.vk - .verify_prehashed(self.c, &self.sig, &self.sig_params) + self.vk.verify_prehashed(self.c, &self.sig) } } @@ -128,7 +125,6 @@ where for item in self.signatures.iter() { let z = item.sig.z; let R = item.sig.R; - let vk = ::effective_pubkey_element(&item.vk, &item.sig_params); let blind = <::Field>::random(&mut rng); @@ -139,7 +135,7 @@ where Rs.push(R); VK_coeffs.push(<::Field>::zero() + (blind * item.c.0)); - VKs.push(vk); + VKs.push(item.vk.to_element()); } let scalars = core::iter::once(&P_coeff_acc) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index f9cf0f08..45a023d3 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -119,11 +119,6 @@ where pub(crate) fn from_coefficients(coefficients: &[Scalar], peer: Identifier) -> Self { Self::new(evaluate_polynomial(peer, coefficients)) } - - /// Returns negated SigningShare - pub fn negate(&mut self) { - self.0 .0 = <::Field>::negate(&self.to_scalar()); - } } impl Debug for SigningShare @@ -333,7 +328,7 @@ where } /// Returns VerifiableSecretSharingCommitment from a iterator of serialized - /// CoefficientCommitments (e.g. a Vec>). + /// CoefficientCommitments (e.g. a `Vec>`). pub fn deserialize(serialized_coefficient_commitments: I) -> Result> where I: IntoIterator, @@ -635,11 +630,6 @@ where min_signers, } } - - /// Negate `SigningShare`. - pub fn negate_signing_share(&mut self) { - self.signing_share.negate(); - } } #[cfg(feature = "serialization")] diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 712a516a..cd58e8f5 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -38,7 +38,7 @@ use rand_core::{CryptoRng, RngCore}; use crate::{ Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature, - SigningKey, + SigningKey, VerifyingKey, }; #[cfg(feature = "serialization")] @@ -322,7 +322,7 @@ pub fn part1( /// Generates the challenge for the proof of knowledge to a secret for the DKG. fn challenge( identifier: Identifier, - verifying_key: &Element, + verifying_key: &VerifyingKey, R: &Element, ) -> Result, Error> where @@ -331,7 +331,7 @@ where let mut preimage = vec![]; preimage.extend_from_slice(identifier.serialize().as_ref()); - preimage.extend_from_slice(::serialize(verifying_key)?.as_ref()); + preimage.extend_from_slice(::serialize(&verifying_key.to_element())?.as_ref()); preimage.extend_from_slice(::serialize(R)?.as_ref()); Ok(Challenge( @@ -354,23 +354,12 @@ pub(crate) fn compute_proof_of_knowledge // > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k, // > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being // > a context string to prevent replay attacks. - let mut k = <::Field>::random(&mut rng); - let mut R_i = ::generator() * k; - k = ::effective_nonce_secret(k, &R_i); - R_i = ::effective_nonce_element(R_i); - - let verifying_key = commitment.verifying_key()?; - let sig_params = Default::default(); - - let phi_ell0 = ::effective_pubkey_element(&verifying_key, &sig_params); - - let c_i = challenge::(identifier, &phi_ell0, &R_i)?; + let (k, R_i) = ::generate_nonce(&mut rng); + let c_i = challenge::(identifier, &commitment.verifying_key()?, &R_i)?; let a_i0 = *coefficients .first() .expect("coefficients must have at least one element"); - let a_i0_effective = ::effective_secret_key(a_i0, &verifying_key, &sig_params); - - let mu_i = k + a_i0_effective * c_i.0; + let mu_i = k + a_i0 * c_i.0; Ok(Signature { R: R_i, z: mu_i }) } @@ -390,12 +379,9 @@ pub(crate) fn verify_proof_of_knowledge( let ell = identifier; let R_ell = proof_of_knowledge.R; let mu_ell = proof_of_knowledge.z; - - let verifying_key = commitment.verifying_key()?; - let phi_ell0 = ::effective_pubkey_element(&verifying_key, &Default::default()); + let phi_ell0 = commitment.verifying_key()?; let c_ell = challenge::(ell, &phi_ell0, &R_ell)?; - - if R_ell != ::generator() * mu_ell - phi_ell0 * c_ell.0 { + if R_ell != ::generator() * mu_ell - phi_ell0.to_element() * c_ell.0 { return Err(Error::InvalidProofOfKnowledge { culprit: ell }); } Ok(()) @@ -562,12 +548,16 @@ pub fn part3( &round2_secret_package.commitment, ))) .collect(); + let public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?; - C::dkg_output_finalize( - round2_secret_package.identifier, - commitments, + let key_package = KeyPackage { + header: Header::default(), + identifier: round2_secret_package.identifier, signing_share, verifying_share, - round2_secret_package.min_signers, - ) + verifying_key: public_key_package.verifying_key, + min_signers: round2_secret_package.min_signers, + }; + + C::post_dkg(key_package, public_key_package) } diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 434a3816..e373e27a 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -57,7 +57,7 @@ use scalar_mul::VartimeMultiscalarMul; pub use serde; pub use signature::Signature; pub use signing_key::SigningKey; -pub use traits::{Ciphersuite, Element, Field, Group, Scalar, SigningParameters}; +pub use traits::{Ciphersuite, Context, Element, Field, Group, Scalar}; pub use verifying_key::VerifyingKey; /// A type refinement for the scalar field element representing the per-message _[challenge]_. @@ -134,6 +134,8 @@ where /// Generates a random nonzero scalar. /// /// It assumes that the Scalar Eq/PartialEq implementation is constant-time. +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn random_nonzero(rng: &mut R) -> Scalar { loop { let scalar = <::Field>::random(rng); @@ -351,54 +353,6 @@ fn derive_interpolating_value( ) } -/// The data which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[derive(Clone, Debug, PartialEq, Eq, Getters)] -pub struct SigningTarget { - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - ) - )] - message: Vec, - - #[cfg_attr(feature = "serde", serde(default))] - sig_params: C::SigningParameters, -} - -impl SigningTarget { - /// Construct a signing target from a message and additional signing parameters. - pub fn new, P: Into>( - message: T, - sig_params: P, - ) -> SigningTarget { - SigningTarget { - message: message.as_ref().to_vec(), - sig_params: sig_params.into(), - } - } - - /// Constructs a signing target from an arbitrary message. - /// This populates [the `sig_params` field][SigningTarget::sig_params] with - /// the [`Default`] instance of the [`Ciphersuite::SigningParameters`]. - pub fn from_message>(message: T) -> SigningTarget { - SigningTarget { - message: message.as_ref().to_vec(), - sig_params: C::SigningParameters::default(), - } - } -} - -impl> From for SigningTarget { - fn from(message: T) -> Self { - Self::from_message(message) - } -} - /// Generated by the coordinator of the signing operation and distributed to /// each signing party #[derive(Clone, Debug, PartialEq, Eq, Getters)] @@ -412,9 +366,18 @@ pub struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. signing_commitments: BTreeMap, round1::SigningCommitments>, - /// The message and parameters which each participant will use to sign. - /// Each signer should perform protocol-specific verification on the signing target. - sig_target: SigningTarget, + /// Message which each participant will sign. + /// + /// Each signer should perform protocol-specific verification on the + /// message. + #[cfg_attr( + feature = "serde", + serde( + serialize_with = "serdect::slice::serialize_hex_lower_or_bin", + deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" + ) + )] + message: Vec, } impl SigningPackage @@ -424,19 +387,14 @@ where /// Create a new `SigningPackage` /// /// The `signing_commitments` are sorted by participant `identifier`. - /// - /// The `sig_target` can be any bytes-like type that implements `AsRef<[u8]>`. - /// Some ciphersuites like `frost-secp256k1-tr` allow customization of the signing - /// process by embedding additional parameters into a [`SigningTarget`], but this - /// is optional and not required by most ciphersuites. pub fn new( signing_commitments: BTreeMap, round1::SigningCommitments>, - sig_target: impl Into>, + message: &[u8], ) -> SigningPackage { SigningPackage { header: Header::default(), signing_commitments, - sig_target: sig_target.into(), + message: message.to_vec(), } } @@ -448,11 +406,6 @@ where self.signing_commitments.get(identifier).copied() } - /// Returns the message to be signed. - pub fn message(&self) -> &[u8] { - &self.sig_target.message - } - /// Compute the preimages to H1 to compute the per-signer binding factors // We separate this out into its own method so it can be tested #[cfg_attr(feature = "internals", visibility::make(pub))] @@ -473,7 +426,7 @@ where // The message is hashed with H4 to force the variable-length message // into a fixed-length byte string, same for hashing the variable-sized // (between runs of the protocol) set of group commitments, but with H5. - binding_factor_input_prefix.extend_from_slice(C::H4(self.message()).as_ref()); + binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref()); binding_factor_input_prefix.extend_from_slice( C::H5(&round1::encode_group_commitments(self.signing_commitments())?[..]).as_ref(), ); @@ -524,10 +477,10 @@ where self.0 } - /// Check if group commitment is odd + /// Return the underlying element. #[cfg(feature = "internals")] - pub fn y_is_odd(&self) -> bool { - ::y_is_odd(&self.0) + pub fn from_element(element: Element) -> Self { + Self(element) } } @@ -630,13 +583,18 @@ where return Err(Error::UnknownIdentifier); } + let mut ctx = C::Context::default(); + + let (signing_package, signature_shares, pubkeys) = + ::pre_aggregate(&mut ctx, signing_package, signature_shares, pubkeys)?; + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?; + compute_binding_factor_list(&signing_package, &pubkeys.verifying_key, &[])?; // Compute the group commitment from signing commitments produced in round one. - let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; - let R = ::effective_nonce_element(group_commitment.0); + let group_commitment = + ::compute_group_commitment(&mut ctx, &signing_package, &binding_factor_list)?; // The aggregation of the signature shares by summing them up, resulting in // a plain Schnorr signature. @@ -650,13 +608,15 @@ where z = z + signature_share.to_scalar(); } - let signature: Signature = - ::aggregate_sig_finalize(z, R, &pubkeys.verifying_key, &signing_package.sig_target)?; + let signature = Signature { + R: group_commitment.0, + z, + }; // Verify the aggregate signature let verification_result = pubkeys .verifying_key - .verify(signing_package.sig_target.clone(), &signature); + .verify(signing_package.message(), &signature); // Only if the verification of the aggregate signature failed; verify each share to find the cheater. // This approach is more efficient since we don't need to verify all shares @@ -664,11 +624,11 @@ where #[cfg(feature = "cheater-detection")] if verification_result.is_err() { detect_cheater( - group_commitment, - &R, - pubkeys, - signing_package, - signature_shares, + &mut ctx, + &group_commitment, + &pubkeys, + &signing_package, + &signature_shares, &binding_factor_list, )?; } @@ -681,8 +641,8 @@ where /// Optional cheater detection feature /// Each share is verified to find the cheater fn detect_cheater( - group_commitment: GroupCommitment, - effective_group_commitment: &Element, + ctx: &mut C::Context, + group_commitment: &GroupCommitment, pubkeys: &keys::PublicKeyPackage, signing_package: &SigningPackage, signature_shares: &BTreeMap, round2::SignatureShare>, @@ -690,9 +650,9 @@ fn detect_cheater( ) -> Result<(), Error> { // Compute the per-message challenge. let challenge = ::challenge( - effective_group_commitment, + &group_commitment.0, &pubkeys.verifying_key, - &signing_package.sig_target, + signing_package.message(), )?; // Verify the signature shares. @@ -718,15 +678,14 @@ fn detect_cheater( .to_group_commitment_share(binding_factor); // Compute relation values to verify this signature share. - signature_share.verify( + ::verify_share( + ctx, + signature_share, *signature_share_identifier, &R_share, signer_pubkey, lambda_i, &challenge, - &group_commitment, - &pubkeys.verifying_key, - &signing_package.sig_target.sig_params, )?; } diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index ce5e28a5..694043b2 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -54,21 +54,22 @@ where Self::nonce_generate_from_random_bytes(secret, random_bytes) } + /// Create a nonce from a scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] fn from_scalar(scalar: <<::Group as Group>::Field as Field>::Scalar) -> Self { Self(SerializableScalar(scalar)) } + /// Convert a nonce into a scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn to_scalar( self, ) -> <<::Group as Group>::Field as Field>::Scalar { self.0 .0 } - /// Negate `Nonce`. - pub fn negate(&mut self) { - self.0 .0 = <::Field>::negate(&self.to_scalar()); - } - /// Generates a nonce from the given random bytes. /// This function allows testing and MUST NOT be made public. pub(crate) fn nonce_generate_from_random_bytes( @@ -286,12 +287,6 @@ where pub fn deserialize(bytes: &[u8]) -> Result> { Deserialize::deserialize(bytes) } - - /// Negate `SigningNonces`. - pub fn negate_nonces(&mut self) { - self.binding.negate(); - self.hiding.negate(); - } } /// Published by each participant in the first round of the signing protocol. @@ -370,6 +365,12 @@ where pub struct GroupCommitmentShare(pub(super) Element); impl GroupCommitmentShare { + /// Create from an element. + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn from_element(element: Element) -> Self { + Self(element) + } + /// Return the underlying element. #[cfg_attr(feature = "internals", visibility::make(pub))] pub(crate) fn to_element(self) -> Element { diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 00f9911d..012cb641 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -62,7 +62,6 @@ where #[cfg(any(feature = "cheater-detection", feature = "internals"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] - #[allow(clippy::too_many_arguments)] pub(crate) fn verify( &self, identifier: Identifier, @@ -70,16 +69,10 @@ where verifying_share: &frost::keys::VerifyingShare, lambda_i: Scalar, challenge: &Challenge, - group_commitment: &frost::GroupCommitment, - verifying_key: &frost::VerifyingKey, - sig_params: &C::SigningParameters, ) -> Result<(), Error> { - let commitment_share = - ::effective_commitment_share(*group_commitment_share, group_commitment); - let vsh = ::effective_verifying_share(verifying_share, verifying_key, sig_params); - if (::generator() * self.to_scalar()) - != (commitment_share + (vsh * challenge.0 * lambda_i)) + != (group_commitment_share.to_element() + + (verifying_share.to_element() * challenge.0 * lambda_i)) { return Err(Error::InvalidSignatureShare { culprit: identifier, @@ -150,37 +143,42 @@ pub fn sign( return Err(Error::IncorrectCommitment); } + let mut ctx = C::Context::default(); + + let (signing_package, signer_nonces, key_package) = + ::pre_sign(&mut ctx, signing_package, signer_nonces, key_package)?; + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, &key_package.verifying_key, &[])?; + compute_binding_factor_list(&signing_package, &key_package.verifying_key, &[])?; let binding_factor: frost::BindingFactor = binding_factor_list .get(&key_package.identifier) .ok_or(Error::UnknownIdentifier)? .clone(); // Compute the group commitment from signing commitments produced in round one. - let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + let group_commitment = + ::compute_group_commitment(&mut ctx, &signing_package, &binding_factor_list)?; // Compute Lagrange coefficient. - let lambda_i = frost::derive_interpolating_value(key_package.identifier(), signing_package)?; + let lambda_i = frost::derive_interpolating_value(key_package.identifier(), &signing_package)?; // Compute the per-message challenge. let challenge = ::challenge( &group_commitment.0, &key_package.verifying_key, - &signing_package.sig_target, + signing_package.message(), )?; // Compute the Schnorr signature share. let signature_share = ::compute_signature_share( - signer_nonces, + &mut ctx, + &signer_nonces, binding_factor, - group_commitment, lambda_i, - key_package, + &key_package, challenge, - &signing_package.sig_target.sig_params, ); Ok(signature_share) diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index f03d1e45..91d3ade9 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -30,11 +30,61 @@ where Self { R, z } } + /// Converts default-encoded bytes as + /// [`Ciphersuite::SignatureSerialization`] into a `Signature`. + #[cfg(feature = "internals")] + pub fn default_deserialize(bytes: &[u8]) -> Result> { + // To compute the expected length of the encoded point, encode the generator + // and get its length. Note that we can't use the identity because it can be encoded + // shorter in some cases (e.g. P-256, which uses SEC1 encoding). + let generator = ::generator(); + let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); + let R_bytes_len = R_bytes.len(); + + let one = <::Field as Field>::zero(); + let mut z_bytes = + Vec::from(<::Field as Field>::serialize(&one).as_ref()); + let z_bytes_len = z_bytes.len(); + + if bytes.len() != R_bytes_len + z_bytes_len { + return Err(Error::MalformedSignature); + } + + R_bytes[..].copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); + + let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; + + // 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 + .get(R_bytes_len..R_bytes_len + z_bytes_len) + .ok_or(Error::MalformedSignature)?, + ); + + let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; + + Ok(Self { + R: ::deserialize(R_serialization)?, + z: <::Field>::deserialize(z_serialization)?, + }) + } + /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. pub fn deserialize(bytes: &[u8]) -> Result> { C::deserialize_signature(bytes) } + /// Converts this signature to its default byte serialization. + #[cfg(feature = "internals")] + pub fn default_serialize(&self) -> Result, Error> { + let mut bytes = Vec::::new(); + + bytes.extend(::serialize(&self.R)?.as_ref()); + bytes.extend(<::Field>::serialize(&self.z).as_ref()); + + Ok(bytes) + } + /// Converts this signature to its byte serialization. pub fn serialize(&self) -> Result, Error> { ::serialize_signature(self) diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index c562c057..93a096c3 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -6,7 +6,7 @@ use rand_core::{CryptoRng, RngCore}; use crate::{ random_nonzero, serialization::SerializableScalar, Challenge, Ciphersuite, Error, Field, Group, - Scalar, Signature, SigningTarget, VerifyingKey, + Scalar, Signature, VerifyingKey, }; /// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`]. @@ -40,25 +40,24 @@ where } /// Create a signature `msg` using this `SigningKey`. - pub fn sign( - &self, - mut rng: R, - sig_target: impl Into>, - ) -> Signature { - let sig_target = sig_target.into(); + pub fn sign(&self, rng: R, message: &[u8]) -> Signature { + ::single_sign(self, rng, message) + } + /// Create a signature `msg` using this `SigningKey` using the default + /// signing. + #[cfg(feature = "internals")] + pub fn default_sign(&self, mut rng: R, message: &[u8]) -> Signature { let public = VerifyingKey::::from(*self); - let secret = ::effective_secret_key(self.scalar, &public, &sig_target.sig_params); - let mut k = random_nonzero::(&mut rng); - let mut R = ::generator() * k; - k = ::effective_nonce_secret(k, &R); - R = ::effective_nonce_element(R); + let (k, R) = ::generate_nonce(&mut rng); // Generate Schnorr challenge - let c: Challenge = ::challenge(&R, &public, &sig_target).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); + let c: Challenge = ::challenge(&R, &public, message).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); + + let z = k + (c.0 * self.scalar); - ::single_sig_finalize(k, R, secret, &c, &public, &sig_target.sig_params) + Signature { R, z } } /// Creates a SigningKey from a scalar. Returns an error if the scalar is zero. diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index f5332273..7f164310 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -7,7 +7,7 @@ use crate as frost; use crate::round2::SignatureShare; use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, - SigningTarget, VerifyingKey, + VerifyingKey, }; use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; @@ -102,8 +102,7 @@ pub fn check_share_generation_fails_with_invalid_signers( mut rng: R, - signing_target: SigningTarget, -) -> (SigningTarget, Signature, VerifyingKey) { +) -> (Vec, Signature, VerifyingKey) { //////////////////////////////////////////////////////////////////////////// // Key generation //////////////////////////////////////////////////////////////////////////// @@ -147,11 +146,10 @@ pub fn check_sign_with_dealer( .collect(), &mut rng, pubkeys.clone(), - signing_target.clone(), ); assert_eq!(r, Err(Error::InvalidSignature)); - check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap() + check_sign(min_signers, key_packages, rng, pubkeys).unwrap() } /// Test FROST signing with trusted dealer fails with invalid numbers of signers. @@ -196,8 +194,7 @@ pub fn check_sign( key_packages: BTreeMap, frost::keys::KeyPackage>, mut rng: R, pubkey_package: PublicKeyPackage, - signing_target: SigningTarget, -) -> Result<(SigningTarget, Signature, VerifyingKey), Error> { +) -> Result<(Vec, Signature, VerifyingKey), Error> { let mut nonces_map: BTreeMap, frost::round1::SigningNonces> = BTreeMap::new(); let mut commitments_map: BTreeMap, frost::round1::SigningCommitments> = @@ -225,7 +222,8 @@ pub fn check_sign( // - decide what message to sign // - take one (unused) commitment per signing participant let mut signature_shares = BTreeMap::new(); - let signing_package = frost::SigningPackage::new(commitments_map, signing_target.clone()); + let message = "message to sign".as_bytes(); + let signing_package = SigningPackage::new(commitments_map, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -267,18 +265,11 @@ pub fn check_sign( // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; - // Check that the effective verifying key can be verified against the raw message, - // without exposing the SigningParameters. - pubkey_package - .verifying_key - .effective_key(signing_target.sig_params()) - .verify(signing_target.message(), &group_signature)?; - // Check that the threshold signature can be verified by the group public // key (the verification key). pubkey_package .verifying_key - .verify(signing_target.clone(), &group_signature)?; + .verify(message, &group_signature)?; // Check that the threshold signature can be verified by the group public // key (the verification key) from KeyPackage.verifying_key @@ -287,11 +278,11 @@ pub fn check_sign( key_package .verifying_key - .verify(signing_target.clone(), &group_signature)?; + .verify(message, &group_signature)?; } Ok(( - signing_target, + message.to_owned(), group_signature, pubkey_package.verifying_key, )) @@ -311,7 +302,7 @@ fn check_sign_errors( .find(|&&id| id != key_package.identifier) .unwrap(); commitments.remove(&id); - let signing_package = frost::SigningPackage::new(commitments, signing_package.sig_target); + let signing_package = frost::SigningPackage::new(commitments, signing_package.message()); let r = frost::round2::sign(&signing_package, &signing_nonces, &key_package); assert_eq!(r, Err(Error::IncorrectNumberOfCommitments)); @@ -384,8 +375,7 @@ fn check_aggregate_invalid_share_identifier_for_verifying_shares( mut rng: R, - signing_target: SigningTarget, -) -> (SigningTarget, Signature, VerifyingKey) +) -> (Vec, Signature, VerifyingKey) where C::Group: core::cmp::PartialEq, { @@ -542,7 +532,7 @@ where let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap()); // Proceed with the signing test. - check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap() + check_sign(min_signers, key_packages, rng, pubkeys).unwrap() } /// Check that calling dkg::part3() with distinct sets of participants fail. @@ -586,8 +576,7 @@ fn check_part3_different_participants( /// Identifiers. pub fn check_sign_with_dealer_and_identifiers( mut rng: R, - signing_target: SigningTarget, -) -> (SigningTarget, Signature, VerifyingKey) { +) -> (Vec, Signature, VerifyingKey) { // Check error cases first // Check repeated identifiers @@ -653,7 +642,7 @@ pub fn check_sign_with_dealer_and_identifiers( diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 940b0f53..29330b90 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -9,7 +9,7 @@ use crate::keys::refresh::{compute_refreshing_shares, refresh_share}; use crate::{self as frost}; use crate::{ keys::{KeyPackage, PublicKeyPackage, SecretShare}, - Ciphersuite, Error, Identifier, SigningTarget, + Ciphersuite, Error, Identifier, }; use super::ciphersuite_generic::check_sign; @@ -81,16 +81,7 @@ pub fn check_refresh_shares_with_dealer( for (k, v) in new_shares { key_packages.insert(k, v.unwrap()); } - - let signing_target = SigningTarget::from_message(b"hello world"); - check_sign( - MIN_SIGNERS, - key_packages, - rng, - new_pub_key_package, - signing_target, - ) - .unwrap(); + check_sign(MIN_SIGNERS, key_packages, rng, new_pub_key_package).unwrap(); } /// We want to check that shares are refreshed with valid signers diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 73e52c66..2cc23ea9 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -4,18 +4,19 @@ use core::{ fmt::Debug, ops::{Add, Mul, Sub}, }; +use std::borrow::Cow; use alloc::{collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; use crate::{ - challenge, - keys::{ - KeyPackage, PublicKeyPackage, SigningShare, VerifiableSecretSharingCommitment, - VerifyingShare, - }, - round1, round2, BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError, - Header, Identifier, Signature, SigningTarget, VerifyingKey, + challenge, compute_group_commitment, + keys::{KeyPackage, PublicKeyPackage, VerifyingShare}, + random_nonzero, + round1::{self}, + round2::{self, SignatureShare}, + BindingFactor, BindingFactorList, Challenge, Error, FieldError, GroupCommitment, GroupError, + Identifier, Signature, SigningKey, SigningPackage, VerifyingKey, }; /// A prime order finite field GF(q) over which all scalar values for our prime order group can be @@ -49,11 +50,6 @@ pub trait Field: Copy + Clone { /// element is zero. fn invert(scalar: &Self::Scalar) -> Result; - /// Computes the negation of the element of the scalar field - fn negate(_scalar: &Self::Scalar) -> Self::Scalar { - panic!("Not implemented"); - } - /// Generate a random scalar from the entire space [0, l-1] /// /// @@ -127,11 +123,6 @@ pub trait Group: Copy + Clone + PartialEq { /// [`ScalarBaseMult()`]: https://datatracker.ietf.org/doc/html/rfc9591#section-3.1-4.10 fn generator() -> Self::Element; - /// Check if element is odd - fn y_is_odd(_element: &Self::Element) -> bool { - panic!("Not implemented"); - } - /// A member function of a group _G_ that maps an [`Element`] to a unique /// byte array buf of fixed length Ne. This function raises an error if the /// element is the identity element of the group. @@ -152,24 +143,15 @@ pub trait Group: Copy + Clone + PartialEq { /// An element of the [`Ciphersuite`] `C`'s [`Group`]. pub type Element = <::Group as Group>::Element; -/// This is a marker trait for types which are passed in to modify the signing logic of a [`Ciphersuite`]. +/// A context which can be used by Ciphersuite implementations to pass context +/// between methods when using the overriding methods of the Ciphersuite trait. /// -/// If the `serde` feature is enabled, any type implementing this trait must also implement -/// [`serde::Serialize`] and [`serde::Deserialize`]. -#[cfg(feature = "serde")] -pub trait SigningParameters: - Clone + Debug + Eq + PartialEq + Default + serde::Serialize + for<'d> serde::Deserialize<'d> -{ -} - -/// This is a marker trait for types which are passed in to modify the signing logic of a [`Ciphersuite`]. -/// -/// If the `serde` feature is enabled, any type implementing this trait must also implement -/// [`serde::Serialize`] and [`serde::Deserialize`]. -#[cfg(not(feature = "serde"))] -pub trait SigningParameters: Clone + Debug + Eq + PartialEq + Default {} +/// This trait is implemented for the unit type `()` which is useful for +/// Ciphersuites implementations that don't care about the Context. +pub trait Context: Default {} -impl SigningParameters for () {} +/// Implements Context for the unit type. +impl Context for () {} /// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash /// function. @@ -193,9 +175,8 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// `Group::ScalarSerialization` type SignatureSerialization: AsRef<[u8]> + TryFrom>; - /// Additional parameters which should be provided to the ciphersuite's signing code - /// to produce an effective signature. Most ciphersuites will just set this to `()`. - type SigningParameters: SigningParameters; + /// Optional context. Most ciphersuites will just set this to `()`. + type Context: Context; /// [H1] for a FROST ciphersuite. /// @@ -254,126 +235,160 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { None } - /// Verify a signature for this ciphersuite. The default implementation uses the "cofactored" - /// equation (it multiplies by the cofactor returned by [`Group::cofactor()`]). + // The following are optional methods that allow customizing steps of the + // protocol if required. + + /// Optional. Do regular (non-FROST) signing with a [`SigningKey`]. Called + /// by [`SigningKey::sign()`]. This is not used by FROST. Can be overriden + /// if required which is useful if FROST signing has been changed by the + /// other Ciphersuite trait methods and regular signing should be changed + /// accordingly to match. + fn single_sign( + signing_key: &SigningKey, + rng: R, + message: &[u8], + ) -> Signature { + signing_key.default_sign(rng, message) + } + + /// Optional. Verify a signature for this ciphersuite. Called by + /// [`VerifyingKey::verify()`]. The default implementation uses the + /// "cofactored" equation (it multiplies by the cofactor returned by + /// [`Group::cofactor()`]). /// /// # Cryptographic Safety /// - /// You may override this to provide a tailored implementation, but if the ciphersuite defines it, - /// it must also multiply by the cofactor to comply with the RFC. Note that batch verification - /// (see [`crate::batch::Verifier`]) also uses the default implementation regardless whether a - /// tailored implementation was provided. + /// You may override this to provide a tailored implementation, but if the + /// ciphersuite defines it, it must also multiply by the cofactor to comply + /// with the RFC. Note that batch verification (see + /// [`crate::batch::Verifier`]) also uses the default implementation + /// regardless whether a tailored implementation was provided. fn verify_signature( - sig_target: &SigningTarget, + message: &[u8], signature: &Signature, public_key: &VerifyingKey, ) -> Result<(), Error> { - let c = ::challenge(&signature.R, public_key, sig_target)?; + let (message, signature, public_key) = ::pre_verify(message, signature, public_key)?; - public_key.verify_prehashed(c, signature, &sig_target.sig_params) + let c = ::challenge(&signature.R, &public_key, &message)?; + + public_key.verify_prehashed(c, &signature) } - /// 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. - /// - /// This is the only invocation of the H2 hash function from the [RFC]. - /// - /// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#name-signature-challenge-computa - /// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-3.2 - fn challenge( - R: &Element, - verifying_key: &VerifyingKey, - sig_target: &SigningTarget, - ) -> Result, Error> { - challenge(R, verifying_key, &sig_target.message) + /// Optional. Pre-process [`round2::sign()`] inputs. The default + /// implementation returns them as-is. [`Cow`] is used so implementations + /// can choose to return the same passed reference or a modified clone. + #[allow(clippy::type_complexity)] + fn pre_sign<'a>( + _ctx: &mut Self::Context, + signing_package: &'a SigningPackage, + signer_nonces: &'a round1::SigningNonces, + key_package: &'a KeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, round1::SigningNonces>, + Cow<'a, KeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signer_nonces), + Cow::Borrowed(key_package), + )) } - /// Finalize an aggregated group signature. This is used by frost-sepc256k1-tr - /// to ensure the signature is valid under BIP340. - fn aggregate_sig_finalize( - z: <::Field as Field>::Scalar, - R: Element, - _verifying_key: &VerifyingKey, - _sig_target: &SigningTarget, - ) -> Result, Error> { - Ok(Signature { R, z }) + /// Optional. Pre-process [`crate::aggregate()`] inputs. The default implementation + /// returns them as-is. [`Cow`] is used so implementations can choose to + /// return the same passed reference or a modified clone. + #[allow(clippy::type_complexity)] + fn pre_aggregate<'a>( + _ctx: &mut Self::Context, + signing_package: &'a SigningPackage, + signature_shares: &'a BTreeMap, round2::SignatureShare>, + public_key_package: &'a PublicKeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, BTreeMap, round2::SignatureShare>>, + Cow<'a, PublicKeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signature_shares), + Cow::Borrowed(public_key_package), + )) } - /// Finalize and output a single-signer Schnorr signature. - fn single_sig_finalize( - k: <::Field as Field>::Scalar, - R: Element, - secret: <::Field as Field>::Scalar, - challenge: &Challenge, - _verifying_key: &VerifyingKey, - _sig_params: &Self::SigningParameters, - ) -> Signature { - let z = k + (challenge.0 * secret); - Signature { R, z } + /// Optional. Pre-process [`VerifyingKey::verify()`] inputs. The default + /// implementation returns them as-is. [`Cow`] is used so implementations + /// can choose to return the same passed reference or a modified clone. + #[allow(clippy::type_complexity)] + fn pre_verify<'a>( + msg: &'a [u8], + signature: &'a Signature, + public_key: &'a VerifyingKey, + ) -> Result< + ( + Cow<'a, [u8]>, + Cow<'a, Signature>, + Cow<'a, VerifyingKey>, + ), + Error, + > { + Ok(( + Cow::Borrowed(msg), + Cow::Borrowed(signature), + Cow::Borrowed(public_key), + )) } - /// Converts a signature to its [`Ciphersuite::SignatureSerialization`] in bytes. - /// - /// The default implementation serializes a signature by serializing its `R` point and - /// `z` component independently, and then concatenating them. - fn serialize_signature(signature: &Signature) -> Result, Error> { - let mut bytes = vec![]; - bytes.extend(::serialize(&signature.R)?.as_ref()); - bytes.extend(<::Field>::serialize(&signature.z).as_ref()); - Ok(bytes) + /// Optional. Generate a nonce and a commitment to it. Used by + /// [`SigningKey`] for regular (non-FROST) signing and internally by the DKG + /// to generate proof-of-knowledge signatures. + fn generate_nonce( + rng: &mut R, + ) -> ( + <::Field as Field>::Scalar, + ::Element, + ) { + let k = random_nonzero::(rng); + let R = ::generator() * k; + (k, R) } - /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. - /// - /// The default implementation assumes the serialization is a serialized `R` point - /// followed by a serialized `z` component with no padding or extra fields. - fn deserialize_signature(bytes: &[u8]) -> Result, Error> { - // To compute the expected length of the encoded point, encode the generator - // and get its length. Note that we can't use the identity because it can be encoded - // shorter in some cases (e.g. P-256, which uses SEC1 encoding). - let generator = ::generator(); - let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); - let R_bytes_len = R_bytes.len(); - - let one = <::Field as Field>::zero(); - let mut z_bytes = - Vec::from(<::Field as Field>::serialize(&one).as_ref()); - let z_bytes_len = z_bytes.len(); - - if bytes.len() != R_bytes_len + z_bytes_len { - return Err(Error::MalformedSignature); - } - - R_bytes[..].copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); - - let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; - - // 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 - .get(R_bytes_len..R_bytes_len + z_bytes_len) - .ok_or(Error::MalformedSignature)?, - ); - - let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; - - Ok(Signature { - R: ::deserialize(R_serialization)?, - z: <::Field>::deserialize(z_serialization)?, - }) + /// Optional. Compute the group commitment. Called by [`round2::sign()`] and + /// [`crate::aggregate()`]. + fn compute_group_commitment( + _context: &mut Self::Context, + signing_package: &SigningPackage, + binding_factor_list: &BindingFactorList, + ) -> Result, Error> { + compute_group_commitment(signing_package, binding_factor_list) + } + + /// Optional. Generates the challenge as is required for Schnorr signatures. + /// Called by [`round2::sign()`] and [`crate::aggregate()`]. + fn challenge( + R: &Element, + verifying_key: &VerifyingKey, + message: &[u8], + ) -> Result, Error> { + challenge(R, verifying_key, message) } - /// Compute the signature share for a particular signer on a given challenge. + /// Optional. Compute the signature share for a particular signer on a given + /// challenge. Called by [`round2::sign()`]. fn compute_signature_share( + _ctx: &mut Self::Context, signer_nonces: &round1::SigningNonces, binding_factor: BindingFactor, - _group_commitment: GroupCommitment, lambda_i: <::Field as Field>::Scalar, key_package: &KeyPackage, challenge: Challenge, - _sig_params: &Self::SigningParameters, ) -> round2::SignatureShare { round2::compute_signature_share( signer_nonces, @@ -384,98 +399,50 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { ) } - /// Compute the effective group element which should be used for signature operations - /// for the given verifying key. - /// - /// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes. - fn effective_pubkey_element( - verifying_key: &VerifyingKey, - _sig_params: &Self::SigningParameters, - ) -> ::Element { - verifying_key.to_element() - } - - /// Compute the effective nonce element which should be used for signature operations. - /// - /// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity. - fn effective_nonce_element( - R: ::Element, - ) -> ::Element { - R - } - - /// Compute the effective secret key which should be used for signature operations - /// for the given verifying key. - /// - /// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes. - fn effective_secret_key( - secret: <::Field as Field>::Scalar, - _public: &VerifyingKey, - _sig_params: &Self::SigningParameters, - ) -> <::Field as Field>::Scalar { - secret - } - - /// Compute the effective nonce secret which should be used for signature operations. - /// - /// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity. - fn effective_nonce_secret( - nonce: <::Field as Field>::Scalar, - _R: &Element, - ) -> <::Field as Field>::Scalar { - nonce + /// Optional. Verify a signing share. Called by [`crate::aggregate()`] if + /// cheater detection is enabled. + fn verify_share( + _ctx: &mut Self::Context, + signature_share: &SignatureShare, + identifier: Identifier, + group_commitment_share: &round1::GroupCommitmentShare, + verifying_share: &VerifyingShare, + lambda_i: Scalar, + challenge: &Challenge, + ) -> Result<(), Error> { + signature_share.verify( + identifier, + group_commitment_share, + verifying_share, + lambda_i, + challenge, + ) } - /// Compute the effective nonce commitment share which should be used for - /// FROST signing. + /// Optional. Converts a signature to its + /// [`Ciphersuite::SignatureSerialization`] in bytes. /// - /// In frost-sepc256k1-tr, this negates the commitment share if the group's final - /// commitment has an odd parity. - fn effective_commitment_share( - group_commitment_share: round1::GroupCommitmentShare, - _group_commitment: &GroupCommitment, - ) -> ::Element { - group_commitment_share.to_element() + /// The default implementation serializes a signature by serializing its `R` + /// point and `z` component independently, and then concatenating them. + fn serialize_signature(signature: &Signature) -> Result, Error> { + signature.default_serialize() } - /// Compute the effective verifying share which should be used for FROST - /// partial signature verification. + /// Optional. Converts bytes as [`Ciphersuite::SignatureSerialization`] into + /// a `Signature`. /// - /// In frost-sepc256k1-tr, this negates the verifying share if the group's final - /// verifying key has an odd parity. - fn effective_verifying_share( - verifying_share: &VerifyingShare, - _verifying_key: &VerifyingKey, - _sig_params: &Self::SigningParameters, - ) -> ::Element { - verifying_share.to_element() + /// The default implementation assumes the serialization is a serialized `R` + /// point followed by a serialized `z` component with no padding or extra + /// fields. + fn deserialize_signature(bytes: &[u8]) -> Result, Error> { + Signature::::default_deserialize(bytes) } - /// Construct the key packages from the output of a successful DKG execution. - /// The signing share and verifying share have already been verified and the - /// identifier belongs to our signer. - /// - /// In frost-sepc256k1-tr, this adds a hash-based tweak to the group key - /// to prevent peers from inserting rogue tapscript tweaks into the group's - /// joint public key. - fn dkg_output_finalize( - identifier: Identifier, - commitments: BTreeMap, &VerifiableSecretSharingCommitment>, - signing_share: SigningShare, - verifying_share: VerifyingShare, - min_signers: u16, + /// Post-process the output of the DKG for a given participant. + fn post_dkg( + key_package: KeyPackage, + public_key_package: PublicKeyPackage, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { - let public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?; - - let key_package = KeyPackage { - header: Header::default(), - identifier, - signing_share, - verifying_share, - verifying_key: public_key_package.verifying_key, - min_signers, - }; - Ok((key_package, public_key_package)) } } diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index ec5c9f69..24f05401 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -5,10 +5,7 @@ use alloc::{string::ToString, vec::Vec}; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; -use crate::{ - serialization::SerializableElement, Challenge, Ciphersuite, Error, Group, Signature, - SigningTarget, -}; +use crate::{serialization::SerializableElement, Challenge, Ciphersuite, Error, Group, Signature}; /// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`]. #[derive(Copy, Clone, PartialEq, Eq)] @@ -42,18 +39,6 @@ where self.element.0 } - /// Return the effective verifying key given the specific signing parameters - /// to be verified against. For most ciphersuites, this simply returns the - /// same verifying key unchanged. - pub fn effective_key(self, sig_params: &C::SigningParameters) -> Self { - VerifyingKey::new(::effective_pubkey_element(&self, sig_params)) - } - - /// Check if VerifyingKey is odd - pub fn y_is_odd(&self) -> bool { - ::y_is_odd(&self.to_element()) - } - /// Deserialize from bytes pub fn deserialize(bytes: &[u8]) -> Result, Error> { Ok(Self::new(SerializableElement::deserialize(bytes)?.0)) @@ -66,22 +51,20 @@ where /// Verify a purported `signature` with a pre-hashed [`Challenge`] made by this verification /// key. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn verify_prehashed( &self, challenge: Challenge, signature: &Signature, - sig_params: &C::SigningParameters, ) -> Result<(), Error> { // Verify check is h * ( - z * B + R + c * A) == 0 // h * ( z * B - c * A - R) == 0 // // where h is the cofactor - let R = signature.R; - let vk = C::effective_pubkey_element(self, sig_params); - let zB = C::Group::generator() * signature.z; - let cA = vk * challenge.0; - let check = (zB - cA - R) * C::Group::cofactor(); + let cA = self.element.0 * challenge.0; + let check = (zB - cA - signature.R) * C::Group::cofactor(); if check == C::Group::identity() { Ok(()) @@ -90,13 +73,9 @@ where } } - /// Verify a purported `signature` over `sig_target` made by this verification key. - pub fn verify( - &self, - sig_target: impl Into>, - signature: &Signature, - ) -> Result<(), Error> { - C::verify_signature(&sig_target.into(), signature, self) + /// Verify a purported `signature` over `msg` made by this verification key. + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + C::verify_signature(msg, signature, self) } /// Computes the group public key given the group commitment. diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 42a25933..d3477d74 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -163,14 +163,6 @@ const CONTEXT_STRING: &str = "FROST-ED25519-SHA512-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Ed25519Sha512; -/// The ciphersuite-specific signing parameters which are fed into -/// signing code to ensure correctly compliant signatures are computed. -pub type SigningParameters = (); - -/// The message target which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -pub type SigningTarget = frost_core::SigningTarget; - impl Ciphersuite for Ed25519Sha512 { const ID: &'static str = CONTEXT_STRING; @@ -180,7 +172,7 @@ impl Ciphersuite for Ed25519Sha512 { type SignatureSerialization = [u8; 64]; - type SigningParameters = (); + type Context = (); /// H1 for FROST(Ed25519, SHA-512) /// diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 320b8707..6c564788 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -12,10 +12,7 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] @@ -187,10 +184,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] @@ -342,7 +336,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed25519Sha512, _, - >(rng, b"message".into()); + >(rng); } #[test] diff --git a/frost-ed25519/tests/interoperability_tests.rs b/frost-ed25519/tests/interoperability_tests.rs index 10c7032b..9c27193f 100644 --- a/frost-ed25519/tests/interoperability_tests.rs +++ b/frost-ed25519/tests/interoperability_tests.rs @@ -12,13 +12,12 @@ fn check_interoperability_in_sign_with_dkg() { // and the interoperability check. A smaller number of iterations is used // because DKG takes longer and otherwise the test would be too slow. for _ in 0..32 { - let (target, group_signature, group_pubkey) = + let (msg, group_signature, group_pubkey) = frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( rng.clone(), - b"message".into(), ); - helpers::verify_signature(target.message(), group_signature, group_pubkey); + helpers::verify_signature(&msg, group_signature, group_pubkey); } } @@ -29,14 +28,13 @@ fn check_interoperability_in_sign_with_dealer() { // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. for _ in 0..256 { - let (target, group_signature, group_pubkey) = + let (msg, group_signature, group_pubkey) = frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( rng.clone(), - b"message".into(), ); // Check that the threshold signature can be verified by the `ed25519_dalek` crate // public key (interoperability test) - helpers::verify_signature(target.message(), group_signature, group_pubkey); + helpers::verify_signature(&msg, group_signature, group_pubkey); } } diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 3bdb64ba..0b1d44f1 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let sig_target = signing_package.sig_target(); + let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } diff --git a/frost-ed25519/tests/serde_tests.rs b/frost-ed25519/tests/serde_tests.rs index 1a2a6a4f..9f722797 100644 --- a/frost-ed25519/tests/serde_tests.rs +++ b/frost-ed25519/tests/serde_tests.rs @@ -112,9 +112,7 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -135,9 +133,7 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -157,9 +153,7 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -178,9 +172,7 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -200,9 +192,7 @@ fn check_signing_package_serialization() { "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - }, + "message": "68656c6c6f20776f726c64", "extra": 1 } "#; diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index f4f5bbd2..2db75121 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -157,14 +157,6 @@ const CONTEXT_STRING: &str = "FROST-ED448-SHAKE256-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Ed448Shake256; -/// The ciphersuite-specific signing parameters which are fed into -/// signing code to ensure correctly compliant signatures are computed. -pub type SigningParameters = (); - -/// The message target which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -pub type SigningTarget = frost_core::SigningTarget; - impl Ciphersuite for Ed448Shake256 { const ID: &'static str = CONTEXT_STRING; @@ -174,7 +166,7 @@ impl Ciphersuite for Ed448Shake256 { type SignatureSerialization = [u8; 114]; - type SigningParameters = (); + type Context = (); /// H1 for FROST(Ed448, SHAKE256) /// diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index eaf4336b..70061503 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -12,10 +12,7 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] @@ -187,10 +184,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] @@ -342,7 +336,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed448Shake256, _, - >(rng, b"message".into()); + >(rng); } #[test] diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index 0f0c14b4..e51f8b6e 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let sig_target = signing_package.sig_target(); + let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } diff --git a/frost-ed448/tests/serde_tests.rs b/frost-ed448/tests/serde_tests.rs index 71de30c2..3b5c667a 100644 --- a/frost-ed448/tests/serde_tests.rs +++ b/frost-ed448/tests/serde_tests.rs @@ -112,9 +112,7 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -135,9 +133,7 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -157,9 +153,7 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -178,9 +172,7 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -200,9 +192,7 @@ fn check_signing_package_serialization() { "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - }, + "message": "68656c6c6f20776f726c64", "extra": 1 } "#; diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index b8b61b87..ba4a462c 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -175,14 +175,6 @@ const CONTEXT_STRING: &str = "FROST-P256-SHA256-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct P256Sha256; -/// The ciphersuite-specific signing parameters which are fed into -/// signing code to ensure correctly compliant signatures are computed. -pub type SigningParameters = (); - -/// The message target which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -pub type SigningTarget = frost_core::SigningTarget; - impl Ciphersuite for P256Sha256 { const ID: &'static str = CONTEXT_STRING; @@ -192,7 +184,7 @@ impl Ciphersuite for P256Sha256 { type SignatureSerialization = [u8; 65]; - type SigningParameters = (); + type Context = (); /// H1 for FROST(P-256, SHA-256) /// diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index c1c9347e..8d44312d 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -12,10 +12,7 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] @@ -187,10 +184,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] @@ -339,7 +333,6 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::( rng, - b"message".into(), ); } diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index 0a96090f..a24e57f5 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let sig_target = signing_package.sig_target(); + let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } diff --git a/frost-p256/tests/serde_tests.rs b/frost-p256/tests/serde_tests.rs index 23e7507f..c1475814 100644 --- a/frost-p256/tests/serde_tests.rs +++ b/frost-p256/tests/serde_tests.rs @@ -112,9 +112,7 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -135,9 +133,7 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -157,9 +153,7 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -178,9 +172,7 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -200,9 +192,7 @@ fn check_signing_package_serialization() { "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - }, + "message": "68656c6c6f20776f726c64", "extra": 1 } "#; diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 3785d827..b627442b 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -149,14 +149,6 @@ const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Ristretto255Sha512; -/// The ciphersuite-specific signing parameters which are fed into -/// signing code to ensure correctly compliant signatures are computed. -pub type SigningParameters = (); - -/// The message target which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -pub type SigningTarget = frost_core::SigningTarget; - impl Ciphersuite for Ristretto255Sha512 { const ID: &'static str = CONTEXT_STRING; @@ -166,7 +158,7 @@ impl Ciphersuite for Ristretto255Sha512 { type SignatureSerialization = [u8; 64]; - type SigningParameters = (); + type Context = (); /// H1 for FROST(ristretto255, SHA-512) /// diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index 307d5762..af536ac3 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -12,10 +12,7 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] @@ -188,10 +185,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] @@ -343,7 +337,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ristretto255Sha512, _, - >(rng, b"message".into()); + >(rng); } #[test] diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index ab06c93a..a8ed937c 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let sig_target = signing_package.sig_target(); + let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs index 1bc680e4..faf1769a 100644 --- a/frost-ristretto255/tests/serde_tests.rs +++ b/frost-ristretto255/tests/serde_tests.rs @@ -112,9 +112,7 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -135,9 +133,7 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -157,9 +153,7 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -178,9 +172,7 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -200,9 +192,7 @@ fn check_signing_package_serialization() { "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - }, + "message": "68656c6c6f20776f726c64", "extra": 1 } "#; diff --git a/frost-secp256k1-tr/README.md b/frost-secp256k1-tr/README.md index 9022e25c..f4d2205f 100644 --- a/frost-secp256k1-tr/README.md +++ b/frost-secp256k1-tr/README.md @@ -1,4 +1,4 @@ -An implementation of Schnorr signatures on the secp256k1 curve for both single and threshold numbers +An implementation of Schnorr signatures on the secp256k1 curve (Taproot) for both single and threshold numbers of signers (FROST). ## Example: key generation with trusted dealer and FROST signing diff --git a/frost-secp256k1-tr/benches/bench.rs b/frost-secp256k1-tr/benches/bench.rs index 3d51c3f1..e9097bdd 100644 --- a/frost-secp256k1-tr/benches/bench.rs +++ b/frost-secp256k1-tr/benches/bench.rs @@ -6,13 +6,13 @@ use frost_secp256k1_tr::*; fn bench_secp256k1_batch_verify(c: &mut Criterion) { let mut rng = thread_rng(); - frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); + frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); } fn bench_secp256k1_sign(c: &mut Criterion) { let mut rng = thread_rng(); - frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); + frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); } criterion_group!(benches, bench_secp256k1_batch_verify, bench_secp256k1_sign); diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs index 88bce01d..9b538030 100644 --- a/frost-secp256k1-tr/src/keys/repairable.rs +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -10,7 +10,7 @@ use alloc::collections::BTreeMap; // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; -use crate::{Error, Secp256K1Sha256}; +use crate::{Error, Secp256K1Sha256TR}; use super::{SecretShare, VerifiableSecretSharingCommitment}; @@ -38,7 +38,7 @@ pub fn repair_share_step_1( /// /// Returns a scalar pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) + frost::keys::repairable::repair_share_step_2::(deltas_j) } /// Step 3 of RTS @@ -61,7 +61,7 @@ mod tests { use rand::thread_rng; use serde_json::Value; - use crate::Secp256K1Sha256; + use crate::Secp256K1Sha256TR; lazy_static! { pub static ref REPAIR_SHARE: Value = @@ -73,18 +73,20 @@ mod tests { fn check_repair_share_step_1() { let rng = thread_rng(); - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_step_1::(rng); } #[test] fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::(&REPAIR_SHARE); + frost_core::tests::repairable::check_repair_share_step_2::( + &REPAIR_SHARE, + ); } #[test] fn check_repair_share_step_3() { let rng = thread_rng(); - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, ); @@ -94,7 +96,7 @@ mod tests { fn check_repair_share_step_1_fails_with_invalid_min_signers() { let rng = thread_rng(); frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(rng); } diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index a34680b5..91486c23 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; +use alloc::borrow::Cow; use alloc::borrow::ToOwned; use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -26,7 +27,10 @@ use k256::{ use rand_core::{CryptoRng, RngCore}; use sha2::{Digest, Sha256}; -use frost_core as frost; +use frost_core::{self as frost, compute_group_commitment, random_nonzero}; + +use keys::EvenY; +use keys::Tweak; #[cfg(test)] mod tests; @@ -40,7 +44,7 @@ pub use frost_core::{ pub use rand_core; /// An error. -pub type Error = frost_core::Error; +pub type Error = frost_core::Error; /// An implementation of the FROST(secp256k1, SHA-256) ciphersuite scalar field. #[derive(Clone, Copy)] @@ -68,10 +72,6 @@ impl Field for Secp256K1ScalarField { } } - fn negate(scalar: &Self::Scalar) -> Self::Scalar { - -scalar - } - fn random(rng: &mut R) -> Self::Scalar { Scalar::random(rng) } @@ -126,10 +126,6 @@ impl Group for Secp256K1Group { ProjectivePoint::GENERATOR } - fn y_is_odd(element: &Self::Element) -> bool { - element.to_affine().y_is_odd().into() - } - fn serialize(element: &Self::Element) -> Result { if *element == Self::identity() { return Err(GroupError::InvalidIdentityElement); @@ -185,7 +181,7 @@ const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-TR-v1"; /// An implementation of the FROST(secp256k1, SHA-256) ciphersuite. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Secp256K1Sha256; +pub struct Secp256K1Sha256TR; /// Digest the hasher to a Scalar fn hasher_to_scalar(hasher: Sha256) -> Scalar { @@ -206,7 +202,7 @@ fn tagged_hash(tag: &str) -> Sha256 { /// Create a BIP341 compliant taproot tweak fn tweak>( - public_key: &<::Group as Group>::Element, + public_key: &<::Group as Group>::Element, merkle_root: Option, ) -> Scalar { match merkle_root { @@ -220,50 +216,31 @@ fn tweak>( } } -/// Create a BIP341 compliant tweaked public key -fn tweaked_public_key>( - public_key: &VerifyingKey, - merkle_root: Option, -) -> <::Group as Group>::Element { - let mut pk = public_key.to_element(); - if pk.to_affine().y_is_odd().into() { - pk = -pk; - } - ProjectivePoint::GENERATOR * tweak(&pk, merkle_root) + pk +// Negate a Nonce +fn negate_nonce(nonce: &frost_core::round1::Nonce) -> frost_core::round1::Nonce { + frost_core::round1::Nonce::::from_scalar(-nonce.to_scalar()) } -/// The message target which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -pub type SigningTarget = frost_core::SigningTarget; - -/// The ciphersuite-specific signing parameters which are fed into -/// signing code to ensure correctly compliant signatures are computed. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct SigningParameters { - /// The tapscript merkle tree root which must be committed to and agreed upon - /// in advance by all participants in the signing round. - /// - /// If set to `None` (the default), then no taproot tweak will be committed to in the signature. - /// Best practice suggested by BIP341 is to commit to an empty merkle root in cases - /// where no tapscript tweak is needed, i.e. by supplying `&[0; u8]` as the merkle root. - /// This prevents hiding of taproot commitments inside a linearly aggregated key. - /// - /// However, for FROST, this is not strictly required as the group key cannot be - /// poisoned as long as the DKG procedure is conducted correctly. - /// Thus, the [`Default`] trait implementation of taproot `SigningParameters` - /// sets `tapscript_merkle_root` to `None`. - /// - /// If 3rd party observers outside the FROST group must be able to verify there - /// is no hidden script-spending path embedded in the FROST group's taproot output key, - /// then you should set `tapscript_merkle_root` to `Some(vec![])`, which proves - /// the tapscript commitment for the tweaked output key is unspendable. - pub tapscript_merkle_root: Option>, +// Negate a SigningNonces +fn negate_nonces(signing_nonces: &round1::SigningNonces) -> round1::SigningNonces { + // TODO: this recomputes commitments which is expensive, and not needed. + // Create an `internals` SigningNonces::from_nonces_and_commitments or + // something similar. + round1::SigningNonces::from_nonces( + negate_nonce(signing_nonces.hiding()), + negate_nonce(signing_nonces.binding()), + ) } -impl frost_core::SigningParameters for SigningParameters {} +/// TODO +#[derive(Default)] +pub struct Context { + is_group_commitment_even: bool, +} + +impl frost_core::Context for Context {} -impl Ciphersuite for Secp256K1Sha256 { +impl Ciphersuite for Secp256K1Sha256TR { const ID: &'static str = CONTEXT_STRING; type Group = Secp256K1Group; @@ -272,7 +249,7 @@ impl Ciphersuite for Secp256K1Sha256 { type SignatureSerialization = [u8; 64]; - type SigningParameters = SigningParameters; + type Context = Context; /// H1 for FROST(secp256k1, SHA-256) /// @@ -327,68 +304,176 @@ impl Ciphersuite for Secp256K1Sha256 { )) } - /// Generates the challenge as is required for Schnorr signatures. + // Sign, negating the key if required by BIP-340. + fn single_sign( + signing_key: &SigningKey, + rng: R, + message: &[u8], + ) -> Signature { + let signing_key = signing_key.into_even_y(None); + signing_key.default_sign(rng, message) + } + + // Preprocess sign inputs, negating the keys in the KeyPackage if required + // by BIP-340. + fn pre_sign<'a>( + _ctx: &mut Self::Context, + signing_package: &'a SigningPackage, + signer_nonces: &'a round1::SigningNonces, + key_package: &'a keys::KeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, round1::SigningNonces>, + Cow<'a, keys::KeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signer_nonces), + Cow::Owned(key_package.clone().into_even_y(None)), + )) + } + + // Preprocess sign inputs, negating the keys in the PublicKeyPackage if + // required by BIP-340. + fn pre_aggregate<'a>( + _ctx: &mut Self::Context, + signing_package: &'a SigningPackage, + signature_shares: &'a BTreeMap, + public_key_package: &'a keys::PublicKeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, BTreeMap>, + Cow<'a, keys::PublicKeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signature_shares), + Cow::Owned(public_key_package.clone().into_even_y(None)), + )) + } + + // Preprocess verify inputs, negating the VerifyingKey if required by + // BIP-340. + fn pre_verify<'a>( + message: &'a [u8], + signature: &'a Signature, + public_key: &'a VerifyingKey, + ) -> Result<(Cow<'a, [u8]>, Cow<'a, Signature>, Cow<'a, VerifyingKey>), Error> { + let public_key = public_key.into_even_y(None); + Ok(( + Cow::Borrowed(message), + Cow::Borrowed(signature), + Cow::Owned(public_key), + )) + } + + // Generate a nonce, negating it if required by BIP-340. + fn generate_nonce( + rng: &mut R, + ) -> ( + <::Field as Field>::Scalar, + ::Element, + ) { + let k = random_nonzero::(rng); + let R = ::generator() * k; + if R.to_affine().y_is_odd().into() { + (-k, -R) + } else { + (k, R) + } + } + + // Compute the group commitment, negating if required by BIP-340. Note that + // only at this point it is possible to check if negation will be required + // or not. If it is, it will require negating the participant's nonces (when + // signing) or their group commitment share (when aggregating). This is + // signaled by setting the `is_group_commitment_even` flag in the Context. + fn compute_group_commitment( + ctx: &mut Self::Context, + signing_package: &SigningPackage, + binding_factor_list: &frost_core::BindingFactorList, + ) -> Result, Error> { + let group_commitment = compute_group_commitment(signing_package, binding_factor_list)?; + ctx.is_group_commitment_even = + (!group_commitment.clone().to_element().to_affine().y_is_odd()).into(); + let group_commitment = if !ctx.is_group_commitment_even { + GroupCommitment::::from_element(-group_commitment.to_element()) + } else { + group_commitment + }; + + Ok(group_commitment) + } + + // Compute the challenge. Per BIP-340, only the X coordinate of R and + // verifying_key are hashed, unlike vanilla FROST. fn challenge( R: &Element, verifying_key: &VerifyingKey, - sig_target: &SigningTarget, + message: &[u8], ) -> Result, Error> { let mut preimage = vec![]; - let tweaked_pk = tweaked_public_key( - verifying_key, - sig_target.sig_params().tapscript_merkle_root.as_ref(), - ); preimage.extend_from_slice(&R.to_affine().x()); - preimage.extend_from_slice(&tweaked_pk.to_affine().x()); - preimage.extend_from_slice(sig_target.message().as_ref()); + preimage.extend_from_slice(&verifying_key.to_element().to_affine().x()); + preimage.extend_from_slice(message); Ok(Challenge::from_scalar(S::H2(&preimage[..]))) } - /// Finalizes the signature by negating it depending on whether - /// the group [`VerifyingKey`] is even or odd parity. - fn aggregate_sig_finalize( - z_raw: <::Field as Field>::Scalar, - R: Element, - verifying_key: &VerifyingKey, - sig_target: &SigningTarget, - ) -> Result { - let challenge = Self::challenge(&R, verifying_key, sig_target)?; - - let t = tweak( - &verifying_key.to_element(), - sig_target.sig_params().tapscript_merkle_root.as_ref(), - ); - let tc = t * challenge.to_scalar(); - let tweaked_pubkey = tweaked_public_key( - verifying_key, - sig_target.sig_params().tapscript_merkle_root.as_ref(), - ); - let z_tweaked = if tweaked_pubkey.to_affine().y_is_odd().into() { - z_raw - tc + /// Compute a signature share, negating the nonces if required by BIP-340. + fn compute_signature_share( + ctx: &mut Self::Context, + signer_nonces: &round1::SigningNonces, + binding_factor: frost::BindingFactor, + lambda_i: <::Field as Field>::Scalar, + key_package: &frost::keys::KeyPackage, + challenge: Challenge, + ) -> round2::SignatureShare { + let signer_nonces = if !ctx.is_group_commitment_even { + negate_nonces(signer_nonces) } else { - z_raw + tc + signer_nonces.clone() }; - Ok(Signature::new(R, z_tweaked)) + + frost::round2::compute_signature_share( + &signer_nonces, + binding_factor, + lambda_i, + key_package, + challenge, + ) } - /// Finalize a single-signer BIP340 Schnorr signature. - fn single_sig_finalize( - k: <::Field as Field>::Scalar, - R: Element, - secret: <::Field as Field>::Scalar, + /// Verify a signature share, negating the group commitment share if + /// required by BIP-340. + fn verify_share( + ctx: &mut Self::Context, + signature_share: &frost_core::round2::SignatureShare, + identifier: Identifier, + group_commitment_share: &frost_core::round1::GroupCommitmentShare, + verifying_share: &frost_core::keys::VerifyingShare, + lambda_i: Scalar, challenge: &Challenge, - verifying_key: &VerifyingKey, - sig_params: &SigningParameters, - ) -> Signature { - let tweaked_pubkey = - tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref()); - let c = challenge.to_scalar(); - let z = if tweaked_pubkey.to_affine().y_is_odd().into() { - k - (c * secret) + ) -> Result<(), Error> { + let group_commitment_share = if !ctx.is_group_commitment_even { + frost_core::round1::GroupCommitmentShare::from_element( + -group_commitment_share.to_element(), + ) } else { - k + (c * secret) + *group_commitment_share }; - Signature::new(R, z) + signature_share.verify( + identifier, + &group_commitment_share, + verifying_share, + lambda_i, + challenge, + ) } /// Serialize a signature in compact BIP340 format, with an x-only R point. @@ -421,174 +506,27 @@ impl Ciphersuite for Secp256K1Sha256 { Ok(Signature::new(R, z)) } - /// Compute a signature share, negating if required by BIP340. - fn compute_signature_share( - signer_nonces: &round1::SigningNonces, - binding_factor: frost::BindingFactor, - group_commitment: GroupCommitment, - lambda_i: <::Field as Field>::Scalar, - key_package: &frost::keys::KeyPackage, - challenge: Challenge, - sig_params: &SigningParameters, - ) -> round2::SignatureShare { - let mut sn = signer_nonces.clone(); - if group_commitment.y_is_odd() { - sn.negate_nonces(); - } - - let mut kp = key_package.clone(); - let public_key = key_package.verifying_key(); - let pubkey_is_odd: bool = public_key.y_is_odd(); - let tweaked_pubkey_is_odd: bool = - tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref()) - .to_affine() - .y_is_odd() - .into(); - if pubkey_is_odd != tweaked_pubkey_is_odd { - kp.negate_signing_share(); - } - - frost::round2::compute_signature_share(&sn, binding_factor, lambda_i, &kp, challenge) - } - - /// Computes the effective pubkey point by tweaking the verifying key with a - /// provably unspendable taproot tweak. - fn effective_pubkey_element( - public_key: &VerifyingKey, - sig_params: &SigningParameters, - ) -> ::Element { - let tweaked_pubkey = - tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref()); - if Self::Group::y_is_odd(&tweaked_pubkey) { - -tweaked_pubkey - } else { - tweaked_pubkey - } - } - - /// Ensures the nonce has an even Y coordinate. - fn effective_nonce_element( - R: ::Element, - ) -> ::Element { - if Self::Group::y_is_odd(&R) { - -R - } else { - R - } - } - - /// Ensures the secret key is negated if the public key has odd parity. - fn effective_secret_key( - secret: <::Field as Field>::Scalar, - public_key: &VerifyingKey, - sig_params: &SigningParameters, - ) -> <::Field as Field>::Scalar { - let t = tweak( - &public_key.to_element(), - sig_params.tapscript_merkle_root.as_ref(), - ); - if Self::Group::y_is_odd(&public_key.to_element()) { - -secret + t - } else { - secret + t - } - } - - /// Ensures the nonce secret is negated if the public nonce point has odd parity. - fn effective_nonce_secret( - nonce: <::Field as Field>::Scalar, - R: &Element, - ) -> <::Field as Field>::Scalar { - if R.to_affine().y_is_odd().into() { - -nonce - } else { - nonce - } - } - - /// Ensures the commitment share is negated if the group's commitment has odd parity. - fn effective_commitment_share( - group_commitment_share: frost::round1::GroupCommitmentShare, - group_commitment: &GroupCommitment, - ) -> Element { - if group_commitment - .clone() - .to_element() - .to_affine() - .y_is_odd() - .into() - { - -group_commitment_share.to_element() - } else { - group_commitment_share.to_element() - } - } - - /// Calculate a verifying share compatible with taproot, depending on the parity - /// of the tweaked vs untweaked verifying key. - fn effective_verifying_share( - verifying_share: &keys::VerifyingShare, - verifying_key: &VerifyingKey, - sig_params: &SigningParameters, - ) -> ::Element { - let pubkey_is_odd: bool = verifying_key.to_element().to_affine().y_is_odd().into(); - let tweaked_pubkey_is_odd: bool = - tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref()) - .to_affine() - .y_is_odd() - .into(); - - let vs = verifying_share.to_element(); - if pubkey_is_odd != tweaked_pubkey_is_odd { - -vs - } else { - vs - } - } - - /// We add an unusable taproot tweak to the group key computed by a DKG run, - /// to prevent peers from inserting rogue tapscript tweaks into the group's - /// joint public key. - fn dkg_output_finalize( - identifier: Identifier, - commitments: BTreeMap, - signing_share: keys::SigningShare, - verifying_share: keys::VerifyingShare, - min_signers: u16, + /// Post-process the DKG output. We add an unusable taproot tweak to the + /// group key computed by a DKG run, to prevent peers from inserting rogue + /// tapscript tweaks into the group's joint public key. + fn post_dkg( + key_package: keys::KeyPackage, + public_key_package: keys::PublicKeyPackage, ) -> Result<(keys::KeyPackage, keys::PublicKeyPackage), Error> { - let untweaked_public_key_package = - keys::PublicKeyPackage::from_dkg_commitments(&commitments)?; - - let untweaked_vk = untweaked_public_key_package.verifying_key().to_element(); - let t = tweak(&untweaked_vk, Some(vec![])); // unspendable script path - let tG = ProjectivePoint::GENERATOR * t; - - let tweaked_verifying_shares: BTreeMap = - untweaked_public_key_package - .verifying_shares() - .clone() - .into_iter() - .map(|(id, share)| (id, keys::VerifyingShare::new(share.to_element() + tG))) - .collect(); - - let tweaked_verifying_key = VerifyingKey::new(untweaked_vk + tG); - - let key_package = keys::KeyPackage::new( - identifier, - keys::SigningShare::new(signing_share.to_scalar() + t), - keys::VerifyingShare::new(verifying_share.to_element() + tG), - tweaked_verifying_key, - min_signers, - ); - - let public_key_package = - keys::PublicKeyPackage::new(tweaked_verifying_shares, tweaked_verifying_key); - - Ok((key_package, public_key_package)) + // From BIP-341: + // > If the spending conditions do not require a script path, the output + // > key should commit to an unspendable script path instead of having + // > no script path. This can be achieved by computing the output key + // > point as Q = P + int(hashTapTweak(bytes(P)))G. + let merkle_root = key_package.verifying_key().to_element().to_affine().x(); + Ok(( + key_package.tweak(Some(merkle_root)), + public_key_package.tweak(Some(merkle_root)), + )) } } -impl RandomizedCiphersuite for Secp256K1Sha256 { +impl RandomizedCiphersuite for Secp256K1Sha256TR { fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { Some(hash_to_scalar( (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), @@ -597,7 +535,7 @@ impl RandomizedCiphersuite for Secp256K1Sha256 { } } -type S = Secp256K1Sha256; +type S = Secp256K1Sha256TR; /// A FROST(secp256k1, SHA-256) participant identifier. pub type Identifier = frost::Identifier; @@ -695,6 +633,167 @@ pub mod keys { /// ensure that they received the correct (and same) value. pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; + /// Trait for ensuring the group public key has an even Y coordinate. + /// + /// In BIP-320, public keys are encoded with only the X coordinate, which + /// means that two Y coordinates are possible. The specification says that + /// the coordinate which is even must be used. Alternatively, something + /// equivalent can be accomplished by simply converting any existing + /// (non-encoded) public key to have an even Y coordinate. + /// + /// This trait is used to enable this procedure, by changing the private and + /// public keys to ensure that the public key has a even Y coordinate. This + /// is done by simply negating both keys if Y is even (in a field, negating + /// is equivalent to computing p - x where p is the prime modulus. Since p + /// is odd, if x is odd then the result will be even). Fortunately this + /// works even after Shamir secret sharing, in the individual signing and + /// verifying shares, since it's linear. + pub trait EvenY { + /// Return if the given type has a group public key with an even Y + /// coordinate. + fn has_even_y(&self) -> bool; + + /// Convert the given type to make sure the group public key has an even + /// Y coordinate. `is_even` can be specified if evenness was already + /// determined beforehand. + fn into_even_y(self, is_even: Option) -> Self; + } + + impl EvenY for PublicKeyPackage { + fn has_even_y(&self) -> bool { + let verifying_key = self.verifying_key(); + (!verifying_key.to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + // Negate verifying key + let verifying_key = VerifyingKey::new(-self.verifying_key().to_element()); + // Recreate verifying share map with negated VerifyingShares + // values. + let verifying_shares: BTreeMap<_, _> = self + .verifying_shares() + .iter() + .map(|(i, vs)| { + let vs = VerifyingShare::new(-vs.to_element()); + (*i, vs) + }) + .collect(); + PublicKeyPackage::new(verifying_shares, verifying_key) + } else { + self + } + } + } + + impl EvenY for KeyPackage { + fn has_even_y(&self) -> bool { + let verifying_key = self.verifying_key(); + (!verifying_key.to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + // Negate all components + let verifying_key = VerifyingKey::new(-self.verifying_key().to_element()); + let signing_share = SigningShare::new(-self.signing_share().to_scalar()); + let verifying_share = VerifyingShare::new(-self.verifying_share().to_element()); + KeyPackage::new( + *self.identifier(), + signing_share, + verifying_share, + verifying_key, + *self.min_signers(), + ) + } else { + self + } + } + } + + impl EvenY for VerifyingKey { + fn has_even_y(&self) -> bool { + (!self.to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + VerifyingKey::new(-self.to_element()) + } else { + self + } + } + } + + impl EvenY for SigningKey { + fn has_even_y(&self) -> bool { + (!Into::::into(self) + .to_element() + .to_affine() + .y_is_odd()) + .into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + SigningKey::from_scalar(-self.to_scalar()) + .expect("the original SigningKey must be nonzero") + } else { + self + } + } + } + + /// Trait for tweaking a key component following BIP-341 + pub trait Tweak: EvenY { + /// Convert the given type to add a tweak. + fn tweak>(self, merkle_root: Option) -> Self; + } + + impl Tweak for PublicKeyPackage { + fn tweak>(self, merkle_root: Option) -> Self { + let t = tweak(&self.verifying_key().to_element(), merkle_root); + let tp = ProjectivePoint::GENERATOR * t; + let public_key_package = self.into_even_y(None); + let verifying_key = + VerifyingKey::new(public_key_package.verifying_key().to_element() + tp); + // Recreate verifying share map with negated VerifyingShares + // values. + let verifying_shares: BTreeMap<_, _> = public_key_package + .verifying_shares() + .iter() + .map(|(i, vs)| { + let vs = VerifyingShare::new(vs.to_element() + tp); + (*i, vs) + }) + .collect(); + PublicKeyPackage::new(verifying_shares, verifying_key) + } + } + + impl Tweak for KeyPackage { + fn tweak>(self, merkle_root: Option) -> Self { + let t = tweak(&self.verifying_key().to_element(), merkle_root); + let tp = ProjectivePoint::GENERATOR * t; + let key_package = self.into_even_y(None); + let verifying_key = VerifyingKey::new(key_package.verifying_key().to_element() + tp); + let signing_share = SigningShare::new(key_package.signing_share().to_scalar() + t); + let verifying_share = + VerifyingShare::new(key_package.verifying_share().to_element() + tp); + KeyPackage::new( + *key_package.identifier(), + signing_share, + verifying_share, + verifying_key, + *key_package.min_signers(), + ) + } + } + pub mod dkg; pub mod repairable; } @@ -739,6 +838,8 @@ pub type SigningPackage = frost::SigningPackage; /// FROST(secp256k1, SHA-256) Round 2 functionality and types, for signature share generation. pub mod round2 { + use keys::Tweak; + use super::*; /// A FROST(secp256k1, SHA-256) participant's signature share, which the Coordinator will aggregate with all other signer's @@ -760,6 +861,21 @@ pub mod round2 { ) -> Result { frost::round2::sign(signing_package, signer_nonces, key_package) } + + /// Same as [`sign()`], but using a Taproot tweak as specified in BIP-341. + pub fn sign_with_tweak( + signing_package: &SigningPackage, + signer_nonces: &round1::SigningNonces, + key_package: &keys::KeyPackage, + merkle_root: Option<&[u8]>, + ) -> Result { + if merkle_root.is_some() { + let key_package = key_package.clone().tweak(merkle_root); + frost::round2::sign(signing_package, signer_nonces, &key_package) + } else { + frost::round2::sign(signing_package, signer_nonces, key_package) + } + } } /// A Schnorr signature on FROST(secp256k1, SHA-256). @@ -783,9 +899,24 @@ pub type Signature = frost_core::Signature; pub fn aggregate( signing_package: &SigningPackage, signature_shares: &BTreeMap, - pubkeys: &keys::PublicKeyPackage, + public_key_package: &keys::PublicKeyPackage, ) -> Result { - frost::aggregate(signing_package, signature_shares, pubkeys) + frost::aggregate(signing_package, signature_shares, public_key_package) +} + +/// Same as [`aggregate()`], but using a Taproot tweak as specified in BIP-341. +pub fn aggregate_with_tweak( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + public_key_package: &keys::PublicKeyPackage, + merkle_root: Option<&[u8]>, +) -> Result { + if merkle_root.is_some() { + let public_key_package = public_key_package.clone().tweak(merkle_root); + frost::aggregate(signing_package, signature_shares, &public_key_package) + } else { + frost::aggregate(signing_package, signature_shares, public_key_package) + } } /// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256). diff --git a/frost-secp256k1-tr/src/tests/batch.rs b/frost-secp256k1-tr/src/tests/batch.rs index b87d22a9..f88793a3 100644 --- a/frost-secp256k1-tr/src/tests/batch.rs +++ b/frost-secp256k1-tr/src/tests/batch.rs @@ -6,19 +6,19 @@ use crate::*; fn check_batch_verify() { let rng = thread_rng(); - frost_core::tests::batch::batch_verify::(rng); + frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { let rng = thread_rng(); - frost_core::tests::batch::bad_batch_verify::(rng); + frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { let rng = thread_rng(); - frost_core::tests::batch::empty_batch_verify::(rng); + frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs index d1b6c22c..a63259c2 100644 --- a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs +++ b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs @@ -15,7 +15,7 @@ lazy_static! { fn check_serialization_of_coefficient_commitment() { let rng = thread_rng(); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(rng); } @@ -24,14 +24,14 @@ fn check_serialization_of_coefficient_commitment() { fn check_create_coefficient_commitment() { let rng = thread_rng(); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(rng); } #[test] fn check_create_coefficient_commitment_error() { frost_core::tests::coefficient_commitment::check_create_coefficient_commitment_error::< - Secp256K1Sha256, + Secp256K1Sha256TR, >(&ELEMENTS); } @@ -40,7 +40,7 @@ fn check_get_value_of_coefficient_commitment() { let rng = thread_rng(); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(rng); } diff --git a/frost-secp256k1-tr/src/tests/deserialize.rs b/frost-secp256k1-tr/src/tests/deserialize.rs index a744832b..7d4c630b 100644 --- a/frost-secp256k1-tr/src/tests/deserialize.rs +++ b/frost-secp256k1-tr/src/tests/deserialize.rs @@ -2,18 +2,18 @@ use crate::*; #[test] fn check_deserialize_non_canonical() { - let mut encoded_generator = ::Group::serialize( - &::Group::generator(), + let mut encoded_generator = ::Group::serialize( + &::Group::generator(), ) .unwrap(); - let r = ::Group::deserialize(&encoded_generator); + let r = ::Group::deserialize(&encoded_generator); assert!(r.is_ok()); // The first byte should be 0x02 or 0x03. Set other value to // create a non-canonical encoding. encoded_generator[0] = 0xFF; - let r = ::Group::deserialize(&encoded_generator); + let r = ::Group::deserialize(&encoded_generator); assert_eq!(r, Err(GroupError::MalformedElement)); // Besides the first byte, it is still possible to get non-canonical encodings. @@ -23,7 +23,7 @@ fn check_deserialize_non_canonical() { .unwrap() .try_into() .unwrap(); - let r = ::Group::deserialize(&encoded_point); + let r = ::Group::deserialize(&encoded_point); assert_eq!(r, Err(GroupError::MalformedElement)); } @@ -33,6 +33,6 @@ fn check_deserialize_identity() { // allow us to change that. Try to send something similar. let encoded_identity = [0u8; 33]; - let r = ::Group::deserialize(&encoded_identity); + let r = ::Group::deserialize(&encoded_identity); assert_eq!(r, Err(GroupError::MalformedElement)); } diff --git a/frost-secp256k1-tr/src/tests/proptests.rs b/frost-secp256k1-tr/src/tests/proptests.rs index dd598569..cad88c33 100644 --- a/frost-secp256k1-tr/src/tests/proptests.rs +++ b/frost-secp256k1-tr/src/tests/proptests.rs @@ -19,7 +19,7 @@ proptest! { // Create a test case for each signature type. let msg = b"test message for proptests"; - let mut sig = SignatureCase::::new(rng, msg.to_vec()); + let mut sig = SignatureCase::::new(rng, msg.to_vec()); // Apply tweaks to each case. for t in &tweaks { diff --git a/frost-secp256k1-tr/src/tests/vss_commitment.rs b/frost-secp256k1-tr/src/tests/vss_commitment.rs index 1a09195a..80fb1ca7 100644 --- a/frost-secp256k1-tr/src/tests/vss_commitment.rs +++ b/frost-secp256k1-tr/src/tests/vss_commitment.rs @@ -14,19 +14,21 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { let rng = thread_rng(); - frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); + frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { let rng = thread_rng(); - frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); + frost_core::tests::vss_commitment::check_deserialize_vss_commitment::( + rng, + ); } #[test] fn check_deserialize_vss_commitment_error() { let rng = thread_rng(); - frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( + frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); } @@ -34,5 +36,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { let rng = thread_rng(); - frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); + frost_core::tests::vss_commitment::check_compute_public_key_package::( + rng, + ); } diff --git a/frost-secp256k1-tr/tests/helpers/mod.rs b/frost-secp256k1-tr/tests/helpers/mod.rs index 6b899101..0de6147c 100644 --- a/frost-secp256k1-tr/tests/helpers/mod.rs +++ b/frost-secp256k1-tr/tests/helpers/mod.rs @@ -2,15 +2,15 @@ // and each one uses only part of the module. #![allow(dead_code)] -use frost_secp256k1_tr::Secp256K1Sha256; +use frost_secp256k1_tr::Secp256K1Sha256TR; use secp256k1::Secp256k1; pub mod samples; pub fn verify_signature( msg: &[u8], - group_signature: frost_core::Signature, - group_pubkey: frost_core::VerifyingKey, + group_signature: &frost_core::Signature, + group_pubkey: &frost_core::VerifyingKey, ) { let secp = Secp256k1::new(); let sig = secp256k1::schnorr::Signature::from_byte_array( diff --git a/frost-secp256k1-tr/tests/helpers/samples.rs b/frost-secp256k1-tr/tests/helpers/samples.rs index 839f3f57..11ef99e3 100644 --- a/frost-secp256k1-tr/tests/helpers/samples.rs +++ b/frost-secp256k1-tr/tests/helpers/samples.rs @@ -14,7 +14,7 @@ use frost_secp256k1_tr::{ Field, Signature, SigningPackage, VerifyingKey, }; -type C = frost_secp256k1_tr::Secp256K1Sha256; +type C = frost_secp256k1_tr::Secp256K1Sha256TR; fn element1() -> Element { ::Group::generator() diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index ddbb6540..9187285b 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -5,17 +5,14 @@ use serde_json::Value; #[test] fn check_zero_key_fails() { - frost_core::tests::ciphersuite_generic::check_zero_key_fails::(); + frost_core::tests::ciphersuite_generic::check_zero_key_fails::(); } #[test] fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] @@ -27,7 +24,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { let error = Error::InvalidMinSigners; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -38,10 +35,10 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { let min_signers = 3; let max_signers = 2; - let error: frost_core::Error = Error::InvalidMinSigners; + let error: frost_core::Error = Error::InvalidMinSigners; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -55,7 +52,7 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { let error = Error::InvalidMaxSigners; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -64,23 +61,24 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { fn check_rts() { let rng = thread_rng(); - frost_core::tests::repairable::check_rts::(rng); + frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { let rng = thread_rng(); - frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { let rng = thread_rng(); - frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( - rng, - ); + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< + Secp256K1Sha256TR, + _, + >(rng); } #[test] @@ -88,7 +86,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { let rng = thread_rng(); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(rng); } @@ -107,7 +105,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { let error = Error::InvalidMinSigners; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(max_signers, min_signers, &identifiers, error, rng); } @@ -123,10 +121,10 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s ]; let min_signers = 3; let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(max_signers, min_signers, &identifiers, error, rng); } @@ -142,10 +140,10 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { ]; let min_signers = 6; let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; + let error: frost_core::Error = Error::InvalidMinSigners; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(max_signers, min_signers, &identifiers, error, rng); } @@ -159,7 +157,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let error = Error::InvalidMaxSigners; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(max_signers, min_signers, &identifiers, error, rng); } @@ -178,7 +176,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(max_signers, min_signers, &identifiers, error, rng); } @@ -187,10 +185,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] @@ -202,7 +197,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { let error = Error::InvalidMinSigners; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -213,10 +208,10 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { let min_signers = 3; let max_signers = 2; - let error: frost_core::Error = Error::InvalidMinSigners; + let error: frost_core::Error = Error::InvalidMinSigners; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -230,7 +225,7 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { let error = Error::InvalidMaxSigners; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -240,7 +235,7 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_share_generation_secp256k1_tr_sha256() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_share_generation::(rng); + frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] @@ -252,7 +247,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { let error = Error::InvalidMinSigners; frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -263,10 +258,10 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { let min_signers = 3; let max_signers = 2; - let error: frost_core::Error = Error::InvalidMinSigners; + let error: frost_core::Error = Error::InvalidMinSigners; frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -280,7 +275,7 @@ fn check_share_generation_fails_with_invalid_max_signers() { let error = Error::InvalidMaxSigners; frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(min_signers, max_signers, error, rng); } @@ -299,29 +294,29 @@ lazy_static! { #[test] fn check_sign_with_test_vectors() { - frost_core::tests::vectors::check_sign_with_test_vectors::(&VECTORS); + frost_core::tests::vectors::check_sign_with_test_vectors::(&VECTORS); } #[test] fn check_sign_with_test_vectors_dkg() { - frost_core::tests::vectors_dkg::check_dkg_keygen::(&VECTORS_DKG); + frost_core::tests::vectors_dkg::check_dkg_keygen::(&VECTORS_DKG); } #[test] fn check_sign_with_test_vectors_with_big_identifiers() { - frost_core::tests::vectors::check_sign_with_test_vectors::( + frost_core::tests::vectors::check_sign_with_test_vectors::( &VECTORS_BIG_IDENTIFIER, ); } #[test] fn check_error_culprit() { - frost_core::tests::ciphersuite_generic::check_error_culprit::(); + frost_core::tests::ciphersuite_generic::check_error_culprit::(); } #[test] fn check_identifier_derivation() { - frost_core::tests::ciphersuite_generic::check_identifier_derivation::(); + frost_core::tests::ciphersuite_generic::check_identifier_derivation::(); } // Explicit test which is used in a documentation snippet @@ -340,24 +335,25 @@ fn check_sign_with_dealer_and_identifiers() { let rng = thread_rng(); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, - >(rng, b"message".into()); + >(rng); } #[test] fn check_sign_with_missing_identifier() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( - rng, - ); + frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::< + Secp256K1Sha256TR, + _, + >(rng); } #[test] fn check_sign_with_incorrect_commitments() { let rng = thread_rng(); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< - Secp256K1Sha256, + Secp256K1Sha256TR, _, >(rng); } diff --git a/frost-secp256k1-tr/tests/interoperability_tests.rs b/frost-secp256k1-tr/tests/interoperability_tests.rs index 165d7b6c..b2e3f9a0 100644 --- a/frost-secp256k1-tr/tests/interoperability_tests.rs +++ b/frost-secp256k1-tr/tests/interoperability_tests.rs @@ -1,10 +1,22 @@ use frost_secp256k1_tr::*; -use crate::Secp256K1Sha256; +use crate::Secp256K1Sha256TR; use rand::thread_rng; mod helpers; +#[test] +fn check_interoperability_in_regular_sign() { + let mut rng = thread_rng(); + + for _ in 0..256 { + let signing_key = SigningKey::new(&mut rng); + let verifying_key = signing_key.into(); + let signature = signing_key.sign(&mut rng, b"message"); + helpers::verify_signature(b"message", &signature, &verifying_key); + } +} + #[test] fn check_interoperability_in_sign_with_dkg() { let rng = thread_rng(); @@ -13,13 +25,12 @@ fn check_interoperability_in_sign_with_dkg() { // and the interoperability check. A smaller number of iterations is used // because DKG takes longer and otherwise the test would be too slow. for _ in 0..32 { - let (target, group_signature, group_pubkey) = - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + let (message, group_signature, group_pubkey) = + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( rng.clone(), - b"message".into(), ); - helpers::verify_signature(target.message(), group_signature, group_pubkey); + helpers::verify_signature(&message, &group_signature, &group_pubkey); } } @@ -30,14 +41,13 @@ fn check_interoperability_in_sign_with_dealer() { // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. for _ in 0..256 { - let (target, group_signature, group_pubkey) = - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + let (message, group_signature, group_pubkey) = + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( rng.clone(), - b"message".into(), ); // Check that the threshold signature can be verified by the `ed25519_dalek` crate // public key (interoperability test) - helpers::verify_signature(target.message(), group_signature, group_pubkey); + helpers::verify_signature(&message, &group_signature, &group_pubkey); } } diff --git a/frost-secp256k1-tr/tests/recreation_tests.rs b/frost-secp256k1-tr/tests/recreation_tests.rs index 6b3ce890..477de37e 100644 --- a/frost-secp256k1-tr/tests/recreation_tests.rs +++ b/frost-secp256k1-tr/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let sig_target = signing_package.sig_target(); + let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } diff --git a/frost-secp256k1-tr/tests/rerandomized_tests.rs b/frost-secp256k1-tr/tests/rerandomized_tests.rs index 79bb5aa3..67e14313 100644 --- a/frost-secp256k1-tr/tests/rerandomized_tests.rs +++ b/frost-secp256k1-tr/tests/rerandomized_tests.rs @@ -1,4 +1,4 @@ -use frost_secp256k1_tr::Secp256K1Sha256; +use frost_secp256k1_tr::Secp256K1Sha256TR; use rand::thread_rng; #[test] @@ -6,5 +6,5 @@ fn check_randomized_sign_with_dealer() { let rng = thread_rng(); let (_msg, _group_signature, _group_pubkey) = - frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); + frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); } diff --git a/frost-secp256k1-tr/tests/serde_tests.rs b/frost-secp256k1-tr/tests/serde_tests.rs index fb8ce73b..62a70e70 100644 --- a/frost-secp256k1-tr/tests/serde_tests.rs +++ b/frost-secp256k1-tr/tests/serde_tests.rs @@ -112,9 +112,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -135,9 +133,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -157,9 +153,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -178,9 +172,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -200,9 +192,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - }, + "message": "68656c6c6f20776f726c64", "extra": 1 } "#; diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap index cc62ef11..b398e5e1 100644 --- a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap @@ -2,4 +2,4 @@ source: frost-secp256k1-tr/tests/serialization_tests.rs expression: "hex::encode(&bytes)" --- -00230f8ab301000000000000000000000000000000000000000000000000000000000000002a00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee50b68656c6c6f20776f726c6400 +00230f8ab301000000000000000000000000000000000000000000000000000000000000002a00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee50b68656c6c6f20776f726c64 diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs index 7ae33ad5..f95a32b7 100644 --- a/frost-secp256k1-tr/tests/tweaking_tests.rs +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -1,96 +1,79 @@ +use std::{error::Error, vec}; + use frost_secp256k1_tr::*; -use rand::thread_rng; -use secp256k1::Secp256k1; +use keys::Tweak; mod helpers; #[test] -fn check_tweaked_signing_key() { - let signing_key = SigningKey::deserialize(&[0xAA; 32]).unwrap(); - let untweaked_verifying_key = VerifyingKey::from(signing_key); - - let mut rng = rand::thread_rng(); - let message = b"message"; - - let untweaked_signature = signing_key.sign(&mut rng, &message); - - helpers::verify_signature(message, untweaked_signature, untweaked_verifying_key); - - untweaked_verifying_key - .verify(&message, &untweaked_signature) - .expect("untweaked signature should be valid under untweaked verifying key"); - - let signing_target = SigningTarget::new( - &message, - SigningParameters { - tapscript_merkle_root: Some(vec![]), - }, - ); - - let tweaked_signature = signing_key.sign(&mut rng, signing_target.clone()); - - untweaked_verifying_key - .verify(&message, &tweaked_signature) - .expect_err("tweaked signature should not be valid under untweaked verifying key"); - - let tweaked_verifying_key = untweaked_verifying_key.effective_key(signing_target.sig_params()); - tweaked_verifying_key - .verify(&message, &tweaked_signature) - .expect("tweaked signature should be valid under tweaked verifying key"); - - untweaked_verifying_key - .verify(signing_target.clone(), &tweaked_signature) - .expect( - "tweaked signature should be valid under untweaked verifying key\ - when signing params are provided", - ); - - helpers::verify_signature(message, tweaked_signature, tweaked_verifying_key); -} - -#[test] -fn check_tweaked_sign_with_dkg() { - let rng = thread_rng(); - - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - SigningTarget::new( - b"message", - SigningParameters { - tapscript_merkle_root: Some(vec![]), - }, - ), - ); -} -#[test] -fn check_tweaked_sign_with_dealer() { - let rng = thread_rng(); - - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - SigningTarget::new( - b"message", - SigningParameters { - tapscript_merkle_root: Some(vec![]), - }, - ), - ); -} - -#[test] -fn check_tweaked_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); - - frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< - Secp256K1Sha256, - _, - >( - rng, - SigningTarget::new( - b"message", - SigningParameters { - tapscript_merkle_root: Some(vec![]), - }, - ), - ); +fn check_tweaked_sign_with_dealer() -> Result<(), Box> { + use frost_secp256k1_tr as frost; + use rand::thread_rng; + use std::collections::BTreeMap; + + let merkle_root: Vec = vec![]; + + let mut rng = thread_rng(); + let max_signers = 5; + let min_signers = 3; + let (shares, pubkey_package) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + )?; + let mut key_packages: BTreeMap<_, _> = BTreeMap::new(); + for (identifier, secret_share) in shares { + let key_package = frost::keys::KeyPackage::try_from(secret_share)?; + key_packages.insert(identifier, key_package); + } + + let mut nonces_map = BTreeMap::new(); + let mut commitments_map = BTreeMap::new(); + + for participant_index in 1..=min_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let key_package = &key_packages[&participant_identifier]; + let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), &mut rng); + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); + } + + let mut signature_shares = BTreeMap::new(); + let message = "message to sign".as_bytes(); + let signing_package = frost::SigningPackage::new(commitments_map, message); + + for participant_identifier in nonces_map.keys() { + let key_package = &key_packages[participant_identifier]; + let nonces = &nonces_map[participant_identifier]; + let signature_share = frost::round2::sign_with_tweak( + &signing_package, + nonces, + key_package, + Some(&merkle_root), + )?; + signature_shares.insert(*participant_identifier, signature_share); + } + + let group_signature = frost::aggregate_with_tweak( + &signing_package, + &signature_shares, + &pubkey_package, + Some(&merkle_root), + )?; + + pubkey_package + .verifying_key() + .verify(message, &group_signature) + .expect_err("signature should not be valid for untweaked pubkey_package"); + + let pubkey_package = pubkey_package.tweak(Some(&merkle_root)); + pubkey_package + .verifying_key() + .verify(message, &group_signature) + .expect("signature should be valid for tweaked pubkey_package"); + + helpers::verify_signature(message, &group_signature, pubkey_package.verifying_key()); + + Ok(()) } diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 5489b48d..2f432e4a 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -175,14 +175,6 @@ const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v1"; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Secp256K1Sha256; -/// The ciphersuite-specific signing parameters which are fed into -/// signing code to ensure correctly compliant signatures are computed. -pub type SigningParameters = (); - -/// The message target which the group's signature should commit to. Includes -/// a message byte vector, and a set of ciphersuite-specific parameters. -pub type SigningTarget = frost_core::SigningTarget; - impl Ciphersuite for Secp256K1Sha256 { const ID: &'static str = CONTEXT_STRING; @@ -192,7 +184,7 @@ impl Ciphersuite for Secp256K1Sha256 { type SignatureSerialization = [u8; 65]; - type SigningParameters = (); + type Context = (); /// H1 for FROST(secp256k1, SHA-256) /// diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 4cdd2294..9581384b 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -12,10 +12,7 @@ fn check_zero_key_fails() { fn check_sign_with_dkg() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] @@ -187,10 +184,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { fn check_sign_with_dealer() { let rng = thread_rng(); - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng, - b"message".into(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] @@ -342,7 +336,7 @@ fn check_sign_with_dealer_and_identifiers() { frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Secp256K1Sha256, _, - >(rng, b"message".into()); + >(rng); } #[test] diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index 1158385b..bb2f8315 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -41,9 +41,9 @@ fn check_signing_package_recreation() { let signing_package = samples::signing_package(); let commitments = signing_package.signing_commitments(); - let sig_target = signing_package.sig_target(); + let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone()); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } diff --git a/frost-secp256k1/tests/serde_tests.rs b/frost-secp256k1/tests/serde_tests.rs index a092d930..82a0735d 100644 --- a/frost-secp256k1/tests/serde_tests.rs +++ b/frost-secp256k1/tests/serde_tests.rs @@ -112,9 +112,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -135,9 +133,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -157,9 +153,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -178,9 +172,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - } + "message": "68656c6c6f20776f726c64" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -200,9 +192,7 @@ fn check_signing_package_serialization() { "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" } }, - "sig_target": { - "message": "68656c6c6f20776f726c64" - }, + "message": "68656c6c6f20776f726c64", "extra": 1 } "#; diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 8a91e576..8a420fca 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -121,17 +121,14 @@ fn write_docs( // To be able to replace the documentation properly, start from the end, which // will keep the string positions consistent for (old_name, _, old_start, old_end) in old_docs.iter().rev() { - let new_doc = docs - .get(old_name) - .unwrap_or_else(|| { - panic!( - "documentation for {} is not available in base file", - old_name - ) - }) - .1 - .clone(); - + let new_doc = docs.get(old_name).map(|v| v.1.clone()); + let Some(new_doc) = new_doc else { + eprintln!( + "WARNING: documentation for {} is not available in base file. This can mean it's a specific type for the ciphersuite, or that there is a bug in gencode", + old_name + ); + continue; + }; // Replaces ciphersuite-references in documentation let mut new_doc = new_doc.to_string(); for (old_n, new_n) in zip(original_suite_strings.iter(), new_suite_strings.iter()) { @@ -299,8 +296,8 @@ fn main() -> ExitCode { ( "frost-secp256k1-tr", &[ - "Secp256K1Sha256", - "secp256k1 curve", + "Secp256K1Sha256TR", + "secp256k1 curve (Taproot)", "Secp256K1", "FROST(secp256k1, SHA-256)", "FROST-secp256k1-SHA256-TR-v1", From d90169e452bb2c9bc4b379809dda60074336f803 Mon Sep 17 00:00:00 2001 From: conduition Date: Sun, 27 Oct 2024 22:35:09 +0000 Subject: [PATCH 08/13] Update PoK test vector to use nonce which generates an even-parity point Uses r = e99ae2676eab512a3572c7b7655d633642a717250af57a7e0ccd5f9618b69f3f --- frost-secp256k1-tr/tests/helpers/vectors_dkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json index 048f620e..5df7b6e3 100644 --- a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json +++ b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json @@ -26,7 +26,7 @@ "signing_key": "2619be8223b23e0453ddc630a4d164e81f7d8a9e07af33c4d4d02190df8bec13", "coefficient": "f7ba0cbbffbea8aaceb3ade54bdbf35bafb1cda15b65ad490e0c63dd069a7c9f", "vss_commitments": ["03ef10370a008cd95e179dc51e2cb7828f30b72d254e5166484f927c84ab326582", "022ce0dac0db217ba326fbbe3e6132d45e2a4bfa0a0c3790d91eacce9a1c2d6a10"], - "proof_of_knowledge": "19df66bda7724ccfa6a5ea76aac9cc167880d55717fe6887b89aeea94408cc9ce47b65a55f9d00479e9d3ea2c7402e81803e2e724d45d70c2cb93e3b0deb5f78", + "proof_of_knowledge": "a319dd51cf64b3896c22f54154812d4ae76cfa95f46f53ef69241fd702456fef32da76cc93d3a541ca495b723e793ee90c32440da5f314e2e58a2dc30550314a", "signing_shares": { "1": "b489a711942526abbb5330a8215d2e740f7dbddec3452006993a8cea3ac278cb", "3": "20255dc07b1fb78bdf90bd85fd2389c988c8250faee11826656a09142fa9fc97" From f9237f5bc2f58b385b56f10164103bdcf4890a4d Mon Sep 17 00:00:00 2001 From: conduition Date: Sun, 27 Oct 2024 22:38:33 +0000 Subject: [PATCH 09/13] BIP341 key package tweaks shouldn't cause key negation --- frost-secp256k1-tr/src/lib.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 91486c23..97820770 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -518,10 +518,10 @@ impl Ciphersuite for Secp256K1Sha256TR { // > key should commit to an unspendable script path instead of having // > no script path. This can be achieved by computing the output key // > point as Q = P + int(hashTapTweak(bytes(P)))G. - let merkle_root = key_package.verifying_key().to_element().to_affine().x(); + let merkle_root = [0u8; 0]; Ok(( - key_package.tweak(Some(merkle_root)), - public_key_package.tweak(Some(merkle_root)), + key_package.tweak(Some(&merkle_root)), + public_key_package.tweak(Some(&merkle_root)), )) } } @@ -749,7 +749,7 @@ pub mod keys { } /// Trait for tweaking a key component following BIP-341 - pub trait Tweak: EvenY { + pub trait Tweak { /// Convert the given type to add a tweak. fn tweak>(self, merkle_root: Option) -> Self; } @@ -758,12 +758,10 @@ pub mod keys { fn tweak>(self, merkle_root: Option) -> Self { let t = tweak(&self.verifying_key().to_element(), merkle_root); let tp = ProjectivePoint::GENERATOR * t; - let public_key_package = self.into_even_y(None); - let verifying_key = - VerifyingKey::new(public_key_package.verifying_key().to_element() + tp); + let verifying_key = VerifyingKey::new(self.verifying_key().to_element() + tp); // Recreate verifying share map with negated VerifyingShares // values. - let verifying_shares: BTreeMap<_, _> = public_key_package + let verifying_shares: BTreeMap<_, _> = self .verifying_shares() .iter() .map(|(i, vs)| { @@ -779,17 +777,15 @@ pub mod keys { fn tweak>(self, merkle_root: Option) -> Self { let t = tweak(&self.verifying_key().to_element(), merkle_root); let tp = ProjectivePoint::GENERATOR * t; - let key_package = self.into_even_y(None); - let verifying_key = VerifyingKey::new(key_package.verifying_key().to_element() + tp); - let signing_share = SigningShare::new(key_package.signing_share().to_scalar() + t); - let verifying_share = - VerifyingShare::new(key_package.verifying_share().to_element() + tp); + let verifying_key = VerifyingKey::new(self.verifying_key().to_element() + tp); + let signing_share = SigningShare::new(self.signing_share().to_scalar() + t); + let verifying_share = VerifyingShare::new(self.verifying_share().to_element() + tp); KeyPackage::new( - *key_package.identifier(), + *self.identifier(), signing_share, verifying_share, verifying_key, - *key_package.min_signers(), + *self.min_signers(), ) } } From ebb9f27c9817cccc3e022d29bc7c8473bf884923 Mon Sep 17 00:00:00 2001 From: conduition Date: Sun, 3 Nov 2024 05:41:58 +0000 Subject: [PATCH 10/13] prune the Context type, instead negate signature.R before verifying With a couple of small adjustments to the code, we can remove the need for this extra associated type on the Ciphersuite crate. Accepting signature with odd-parity nonce values is OK, because BIP340 discard the nonce parity bit anyway. --- frost-core/src/lib.rs | 13 ++---- frost-core/src/round2.rs | 9 ++-- frost-core/src/traits.rs | 35 +++------------- frost-ed25519/src/lib.rs | 2 - frost-ed448/src/lib.rs | 2 - frost-p256/src/lib.rs | 2 - frost-ristretto255/src/lib.rs | 2 - frost-secp256k1-tr/src/lib.rs | 79 +++++++++++++++++------------------ frost-secp256k1/src/lib.rs | 2 - 9 files changed, 50 insertions(+), 96 deletions(-) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index e373e27a..82053d9f 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -57,7 +57,7 @@ use scalar_mul::VartimeMultiscalarMul; pub use serde; pub use signature::Signature; pub use signing_key::SigningKey; -pub use traits::{Ciphersuite, Context, Element, Field, Group, Scalar}; +pub use traits::{Ciphersuite, Element, Field, Group, Scalar}; pub use verifying_key::VerifyingKey; /// A type refinement for the scalar field element representing the per-message _[challenge]_. @@ -583,18 +583,15 @@ where return Err(Error::UnknownIdentifier); } - let mut ctx = C::Context::default(); - let (signing_package, signature_shares, pubkeys) = - ::pre_aggregate(&mut ctx, signing_package, signature_shares, pubkeys)?; + ::pre_aggregate(signing_package, signature_shares, pubkeys)?; // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = compute_binding_factor_list(&signing_package, &pubkeys.verifying_key, &[])?; // Compute the group commitment from signing commitments produced in round one. - let group_commitment = - ::compute_group_commitment(&mut ctx, &signing_package, &binding_factor_list)?; + let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // The aggregation of the signature shares by summing them up, resulting in // a plain Schnorr signature. @@ -624,7 +621,6 @@ where #[cfg(feature = "cheater-detection")] if verification_result.is_err() { detect_cheater( - &mut ctx, &group_commitment, &pubkeys, &signing_package, @@ -641,7 +637,6 @@ where /// Optional cheater detection feature /// Each share is verified to find the cheater fn detect_cheater( - ctx: &mut C::Context, group_commitment: &GroupCommitment, pubkeys: &keys::PublicKeyPackage, signing_package: &SigningPackage, @@ -679,7 +674,7 @@ fn detect_cheater( // Compute relation values to verify this signature share. ::verify_share( - ctx, + group_commitment, signature_share, *signature_share_identifier, &R_share, diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 012cb641..3d863996 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -143,10 +143,8 @@ pub fn sign( return Err(Error::IncorrectCommitment); } - let mut ctx = C::Context::default(); - let (signing_package, signer_nonces, key_package) = - ::pre_sign(&mut ctx, signing_package, signer_nonces, key_package)?; + ::pre_sign(signing_package, signer_nonces, key_package)?; // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. @@ -158,8 +156,7 @@ pub fn sign( .clone(); // Compute the group commitment from signing commitments produced in round one. - let group_commitment = - ::compute_group_commitment(&mut ctx, &signing_package, &binding_factor_list)?; + let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // Compute Lagrange coefficient. let lambda_i = frost::derive_interpolating_value(key_package.identifier(), &signing_package)?; @@ -173,7 +170,7 @@ pub fn sign( // Compute the Schnorr signature share. let signature_share = ::compute_signature_share( - &mut ctx, + &group_commitment, &signer_nonces, binding_factor, lambda_i, diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 2cc23ea9..34912ce3 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -10,13 +10,13 @@ use alloc::{collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; use crate::{ - challenge, compute_group_commitment, + challenge, keys::{KeyPackage, PublicKeyPackage, VerifyingShare}, random_nonzero, round1::{self}, round2::{self, SignatureShare}, - BindingFactor, BindingFactorList, Challenge, Error, FieldError, GroupCommitment, GroupError, - Identifier, Signature, SigningKey, SigningPackage, VerifyingKey, + BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError, Identifier, + Signature, SigningKey, SigningPackage, VerifyingKey, }; /// A prime order finite field GF(q) over which all scalar values for our prime order group can be @@ -143,16 +143,6 @@ pub trait Group: Copy + Clone + PartialEq { /// An element of the [`Ciphersuite`] `C`'s [`Group`]. pub type Element = <::Group as Group>::Element; -/// A context which can be used by Ciphersuite implementations to pass context -/// between methods when using the overriding methods of the Ciphersuite trait. -/// -/// This trait is implemented for the unit type `()` which is useful for -/// Ciphersuites implementations that don't care about the Context. -pub trait Context: Default {} - -/// Implements Context for the unit type. -impl Context for () {} - /// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash /// function. /// @@ -175,9 +165,6 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// `Group::ScalarSerialization` type SignatureSerialization: AsRef<[u8]> + TryFrom>; - /// Optional context. Most ciphersuites will just set this to `()`. - type Context: Context; - /// [H1] for a FROST ciphersuite. /// /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. @@ -280,7 +267,6 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// can choose to return the same passed reference or a modified clone. #[allow(clippy::type_complexity)] fn pre_sign<'a>( - _ctx: &mut Self::Context, signing_package: &'a SigningPackage, signer_nonces: &'a round1::SigningNonces, key_package: &'a KeyPackage, @@ -304,7 +290,6 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// return the same passed reference or a modified clone. #[allow(clippy::type_complexity)] fn pre_aggregate<'a>( - _ctx: &mut Self::Context, signing_package: &'a SigningPackage, signature_shares: &'a BTreeMap, round2::SignatureShare>, public_key_package: &'a PublicKeyPackage, @@ -360,16 +345,6 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { (k, R) } - /// Optional. Compute the group commitment. Called by [`round2::sign()`] and - /// [`crate::aggregate()`]. - fn compute_group_commitment( - _context: &mut Self::Context, - signing_package: &SigningPackage, - binding_factor_list: &BindingFactorList, - ) -> Result, Error> { - compute_group_commitment(signing_package, binding_factor_list) - } - /// Optional. Generates the challenge as is required for Schnorr signatures. /// Called by [`round2::sign()`] and [`crate::aggregate()`]. fn challenge( @@ -383,7 +358,7 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// Optional. Compute the signature share for a particular signer on a given /// challenge. Called by [`round2::sign()`]. fn compute_signature_share( - _ctx: &mut Self::Context, + _group_commitment: &GroupCommitment, signer_nonces: &round1::SigningNonces, binding_factor: BindingFactor, lambda_i: <::Field as Field>::Scalar, @@ -402,7 +377,7 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// Optional. Verify a signing share. Called by [`crate::aggregate()`] if /// cheater detection is enabled. fn verify_share( - _ctx: &mut Self::Context, + _group_commitment: &GroupCommitment, signature_share: &SignatureShare, identifier: Identifier, group_commitment_share: &round1::GroupCommitmentShare, diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index d3477d74..1e33b2d9 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -172,8 +172,6 @@ impl Ciphersuite for Ed25519Sha512 { type SignatureSerialization = [u8; 64]; - type Context = (); - /// H1 for FROST(Ed25519, SHA-512) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.2 diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 2db75121..4ceb707e 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -166,8 +166,6 @@ impl Ciphersuite for Ed448Shake256 { type SignatureSerialization = [u8; 114]; - type Context = (); - /// H1 for FROST(Ed448, SHAKE256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.2 diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index ba4a462c..3e798f4b 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -184,8 +184,6 @@ impl Ciphersuite for P256Sha256 { type SignatureSerialization = [u8; 65]; - type Context = (); - /// H1 for FROST(P-256, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.2 diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index b627442b..0929c228 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -158,8 +158,6 @@ impl Ciphersuite for Ristretto255Sha512 { type SignatureSerialization = [u8; 64]; - type Context = (); - /// H1 for FROST(ristretto255, SHA-512) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.2 diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 97820770..d90f5769 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -27,7 +27,7 @@ use k256::{ use rand_core::{CryptoRng, RngCore}; use sha2::{Digest, Sha256}; -use frost_core::{self as frost, compute_group_commitment, random_nonzero}; +use frost_core::{self as frost, random_nonzero}; use keys::EvenY; use keys::Tweak; @@ -232,14 +232,6 @@ fn negate_nonces(signing_nonces: &round1::SigningNonces) -> round1::SigningNonce ) } -/// TODO -#[derive(Default)] -pub struct Context { - is_group_commitment_even: bool, -} - -impl frost_core::Context for Context {} - impl Ciphersuite for Secp256K1Sha256TR { const ID: &'static str = CONTEXT_STRING; @@ -249,8 +241,6 @@ impl Ciphersuite for Secp256K1Sha256TR { type SignatureSerialization = [u8; 64]; - type Context = Context; - /// H1 for FROST(secp256k1, SHA-256) /// /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.1 @@ -317,7 +307,6 @@ impl Ciphersuite for Secp256K1Sha256TR { // Preprocess sign inputs, negating the keys in the KeyPackage if required // by BIP-340. fn pre_sign<'a>( - _ctx: &mut Self::Context, signing_package: &'a SigningPackage, signer_nonces: &'a round1::SigningNonces, key_package: &'a keys::KeyPackage, @@ -339,7 +328,6 @@ impl Ciphersuite for Secp256K1Sha256TR { // Preprocess sign inputs, negating the keys in the PublicKeyPackage if // required by BIP-340. fn pre_aggregate<'a>( - _ctx: &mut Self::Context, signing_package: &'a SigningPackage, signature_shares: &'a BTreeMap, public_key_package: &'a keys::PublicKeyPackage, @@ -358,7 +346,7 @@ impl Ciphersuite for Secp256K1Sha256TR { )) } - // Preprocess verify inputs, negating the VerifyingKey if required by + // Preprocess verify inputs, negating the VerifyingKey and `signature.R` if required by // BIP-340. fn pre_verify<'a>( message: &'a [u8], @@ -366,9 +354,10 @@ impl Ciphersuite for Secp256K1Sha256TR { public_key: &'a VerifyingKey, ) -> Result<(Cow<'a, [u8]>, Cow<'a, Signature>, Cow<'a, VerifyingKey>), Error> { let public_key = public_key.into_even_y(None); + let signature = signature.into_even_y(None); Ok(( Cow::Borrowed(message), - Cow::Borrowed(signature), + Cow::Owned(signature), Cow::Owned(public_key), )) } @@ -389,28 +378,6 @@ impl Ciphersuite for Secp256K1Sha256TR { } } - // Compute the group commitment, negating if required by BIP-340. Note that - // only at this point it is possible to check if negation will be required - // or not. If it is, it will require negating the participant's nonces (when - // signing) or their group commitment share (when aggregating). This is - // signaled by setting the `is_group_commitment_even` flag in the Context. - fn compute_group_commitment( - ctx: &mut Self::Context, - signing_package: &SigningPackage, - binding_factor_list: &frost_core::BindingFactorList, - ) -> Result, Error> { - let group_commitment = compute_group_commitment(signing_package, binding_factor_list)?; - ctx.is_group_commitment_even = - (!group_commitment.clone().to_element().to_affine().y_is_odd()).into(); - let group_commitment = if !ctx.is_group_commitment_even { - GroupCommitment::::from_element(-group_commitment.to_element()) - } else { - group_commitment - }; - - Ok(group_commitment) - } - // Compute the challenge. Per BIP-340, only the X coordinate of R and // verifying_key are hashed, unlike vanilla FROST. fn challenge( @@ -427,14 +394,14 @@ impl Ciphersuite for Secp256K1Sha256TR { /// Compute a signature share, negating the nonces if required by BIP-340. fn compute_signature_share( - ctx: &mut Self::Context, + group_commitment: &GroupCommitment, signer_nonces: &round1::SigningNonces, binding_factor: frost::BindingFactor, lambda_i: <::Field as Field>::Scalar, key_package: &frost::keys::KeyPackage, challenge: Challenge, ) -> round2::SignatureShare { - let signer_nonces = if !ctx.is_group_commitment_even { + let signer_nonces = if !group_commitment.has_even_y() { negate_nonces(signer_nonces) } else { signer_nonces.clone() @@ -452,7 +419,7 @@ impl Ciphersuite for Secp256K1Sha256TR { /// Verify a signature share, negating the group commitment share if /// required by BIP-340. fn verify_share( - ctx: &mut Self::Context, + group_commitment: &GroupCommitment, signature_share: &frost_core::round2::SignatureShare, identifier: Identifier, group_commitment_share: &frost_core::round1::GroupCommitmentShare, @@ -460,7 +427,7 @@ impl Ciphersuite for Secp256K1Sha256TR { lambda_i: Scalar, challenge: &Challenge, ) -> Result<(), Error> { - let group_commitment_share = if !ctx.is_group_commitment_even { + let group_commitment_share = if !group_commitment.has_even_y() { frost_core::round1::GroupCommitmentShare::from_element( -group_commitment_share.to_element(), ) @@ -728,6 +695,36 @@ pub mod keys { } } + impl EvenY for GroupCommitment { + fn has_even_y(&self) -> bool { + (!self.clone().to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + Self::from_element(-self.to_element()) + } else { + self + } + } + } + + impl EvenY for Signature { + fn has_even_y(&self) -> bool { + (!self.R().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + Self::new(-*self.R(), *self.z()) + } else { + self + } + } + } + impl EvenY for SigningKey { fn has_even_y(&self) -> bool { (!Into::::into(self) diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 2f432e4a..4d25266b 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -184,8 +184,6 @@ impl Ciphersuite for Secp256K1Sha256 { type SignatureSerialization = [u8; 65]; - type Context = (); - /// H1 for FROST(secp256k1, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.2 From 5a66946db8e22b153248869df6cc6de26422ce34 Mon Sep 17 00:00:00 2001 From: conduition Date: Fri, 8 Nov 2024 22:13:02 +0000 Subject: [PATCH 11/13] proper TapTweak point-addition operates on even internal key representation Thanks to @conradoplg for spotting this. The internal key is supposed to be represented as an even-parity point when adding the TapTweak point t*G. I added a regression test to ensure the tweaked verifying key and its parity match the BIP341 spec. --- frost-secp256k1-tr/src/lib.rs | 20 +++-- .../tests/helpers/vectors_dkg.json | 20 ++--- frost-secp256k1-tr/tests/tweaking_tests.rs | 80 +++++++++++++++++-- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index d90f5769..56aa52ca 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -746,7 +746,7 @@ pub mod keys { } /// Trait for tweaking a key component following BIP-341 - pub trait Tweak { + pub trait Tweak: EvenY { /// Convert the given type to add a tweak. fn tweak>(self, merkle_root: Option) -> Self; } @@ -755,10 +755,12 @@ pub mod keys { fn tweak>(self, merkle_root: Option) -> Self { let t = tweak(&self.verifying_key().to_element(), merkle_root); let tp = ProjectivePoint::GENERATOR * t; - let verifying_key = VerifyingKey::new(self.verifying_key().to_element() + tp); + let public_key_package = self.into_even_y(None); + let verifying_key = + VerifyingKey::new(public_key_package.verifying_key().to_element() + tp); // Recreate verifying share map with negated VerifyingShares // values. - let verifying_shares: BTreeMap<_, _> = self + let verifying_shares: BTreeMap<_, _> = public_key_package .verifying_shares() .iter() .map(|(i, vs)| { @@ -774,15 +776,17 @@ pub mod keys { fn tweak>(self, merkle_root: Option) -> Self { let t = tweak(&self.verifying_key().to_element(), merkle_root); let tp = ProjectivePoint::GENERATOR * t; - let verifying_key = VerifyingKey::new(self.verifying_key().to_element() + tp); - let signing_share = SigningShare::new(self.signing_share().to_scalar() + t); - let verifying_share = VerifyingShare::new(self.verifying_share().to_element() + tp); + let key_package = self.into_even_y(None); + let verifying_key = VerifyingKey::new(key_package.verifying_key().to_element() + tp); + let signing_share = SigningShare::new(key_package.signing_share().to_scalar() + t); + let verifying_share = + VerifyingShare::new(key_package.verifying_share().to_element() + tp); KeyPackage::new( - *self.identifier(), + *key_package.identifier(), signing_share, verifying_share, verifying_key, - *self.min_signers(), + *key_package.min_signers(), ) } } diff --git a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json index 5df7b6e3..9fcbc839 100644 --- a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json +++ b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json @@ -7,7 +7,7 @@ "hash": "SHA-256" }, "inputs": { - "verifying_key": "02409611d2fd36025b75caa15f1d70f6d5cfea9cc4254d29580075fdf832d934db", + "verifying_key": "03849089de77b56bd35fcbfc70bf38e73448131090acc75d538a5cea63cc3dcefe", "1": { "identifier": 1, "signing_key": "68e3f6904c6043973515a36bf7801a71597da35733f21305d75a5234f06e4529", @@ -18,8 +18,8 @@ "2": "1dd3cb3e2370e6af22917415f0ad584514807b58b3cc40d2230a26e115f02771", "3": "dd25ee86acd01f996618aa0d1153f5e8fbc929a8e8a18b8f0a15f91d087217e2" }, - "verifying_share": "03487e21b8658bffc7903fa2f6435bdae7a417990a27698c273a14f72bece665f3", - "signing_share": "051f56860fd1225af141494c573ce33da026e1c5f8c5099b73c9ad28675a8389" + "verifying_share": "02a8bf413b5d7af0e692fba967540cde8009f161a4d721f8c88649c1933bbb7531", + "signing_share": "f1be455a8ec9ab86ef8438f23a5cfdf70153aa2785d4bebba83e0840403e4bf3" }, "2": { "identifier": 2, @@ -27,12 +27,7 @@ "coefficient": "f7ba0cbbffbea8aaceb3ade54bdbf35bafb1cda15b65ad490e0c63dd069a7c9f", "vss_commitments": ["03ef10370a008cd95e179dc51e2cb7828f30b72d254e5166484f927c84ab326582", "022ce0dac0db217ba326fbbe3e6132d45e2a4bfa0a0c3790d91eacce9a1c2d6a10"], "proof_of_knowledge": "a319dd51cf64b3896c22f54154812d4ae76cfa95f46f53ef69241fd702456fef32da76cc93d3a541ca495b723e793ee90c32440da5f314e2e58a2dc30550314a", - "signing_shares": { - "1": "b489a711942526abbb5330a8215d2e740f7dbddec3452006993a8cea3ac278cb", - "3": "20255dc07b1fb78bdf90bd85fd2389c988c8250faee11826656a09142fa9fc97" - }, - "verifying_share": "02aab83a9c57284041ae6add2d5e2ea43d715c6607e03c4c22ca5aae10086076f4", - "signing_share": "65abaabc81c1d4827c8bd148a3d6f47b37d7b811e213c9fc3e1a3e573a56feae" + "verifying_share": "029ecb3a4db28a82e7b8d600d42711b02790dde3f063f0ecec6f812c1c5d7dcefc" }, "3": { "identifier": 3, @@ -40,12 +35,7 @@ "coefficient": "42ff6f39ce4f97f279781378ebcf93df47add84d75882cd31b266e83f76e25f6", "vss_commitments": ["02da186c3863c5600b471a2799cb6f15ae4d8315a2f225c177798880e75ac820a0", "03e6a36e7fa4b117c1aa428886672e3a35d926bb4c585a9b07d8ee9a3387420067"], "proof_of_knowledge": "6e115d9e63fd15d432b380ccf1ec4ed03340fcf96caeae8985aedb5f905b1a65dc422ffe5878988fbbc55454857736c7755d9c8f5ee6822c8833ea21d54dba36", - "signing_shares": { - "1": "da5c7f5238079835fe71f746364bb8756a7dcb228aeea686fa2aaa44dfec929c", - "2": "0d47e4b622ee3804bff8cfe088653efefe865cce0c065aecbf7e318182b89e2d" - }, - "verifying_share": "031f1571625e54d9bb0e58e228abe5430460ab9d414bced42250fd09526476290b", - "signing_share": "c637fef2f3b286aa07d65944f07105b8cf888e5dcb628a5d086acf860d5379d3" + "verifying_share": "02c98b3c2e9f4bde4cf90dc9c7be639e5adda6ea09fc605239880a22cb836f7145" } } } diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs index f95a32b7..83b7a032 100644 --- a/frost-secp256k1-tr/tests/tweaking_tests.rs +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -1,7 +1,11 @@ use std::{error::Error, vec}; -use frost_secp256k1_tr::*; +use k256::elliptic_curve::point::AffineCoordinates; +use k256::ProjectivePoint; use keys::Tweak; +use sha2::{Digest, Sha256}; + +use frost_secp256k1_tr::*; mod helpers; @@ -11,7 +15,7 @@ fn check_tweaked_sign_with_dealer() -> Result<(), Box> { use rand::thread_rng; use std::collections::BTreeMap; - let merkle_root: Vec = vec![]; + let merkle_root: Vec = vec![12; 32]; let mut rng = thread_rng(); let max_signers = 5; @@ -67,13 +71,79 @@ fn check_tweaked_sign_with_dealer() -> Result<(), Box> { .verify(message, &group_signature) .expect_err("signature should not be valid for untweaked pubkey_package"); - let pubkey_package = pubkey_package.tweak(Some(&merkle_root)); - pubkey_package + let pubkey_package_tweaked = pubkey_package.clone().tweak(Some(&merkle_root)); + pubkey_package_tweaked .verifying_key() .verify(message, &group_signature) .expect("signature should be valid for tweaked pubkey_package"); - helpers::verify_signature(message, &group_signature, pubkey_package.verifying_key()); + helpers::verify_signature( + message, + &group_signature, + pubkey_package_tweaked.verifying_key(), + ); + + // Confirm the internal (untweaked) group key can be provided to access + // script spending paths under the output (tweaked) group key. + let (expected_parity, expected_tr_output_pubkey) = taproot_tweak_pubkey( + pubkey_package + .verifying_key() + .to_element() + .to_affine() + .x() + .into(), + &merkle_root, + ); + + let tr_output_point = pubkey_package_tweaked + .verifying_key() + .to_element() + .to_affine(); + + let tr_output_pubkey: [u8; 32] = tr_output_point.x().into(); + let tr_output_parity: bool = tr_output_point.y_is_odd().into(); + + assert_eq!( + tr_output_pubkey, expected_tr_output_pubkey, + "taproot output pubkey does not match" + ); + + assert_eq!( + tr_output_parity, expected_parity, + "taproot output pubkey parity bit does not match" + ); Ok(()) } + +/// Emulates the BIP341 helper function: +/// +/// def taproot_tweak_pubkey(pubkey, h): +/// t = int_from_bytes(tagged_hash("TapTweak", pubkey + h)) +/// if t >= SECP256K1_ORDER: +/// raise ValueError +/// P = lift_x(int_from_bytes(pubkey)) +/// if P is None: +/// raise ValueError +/// Q = point_add(P, point_mul(G, t)) +/// return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) +/// +fn taproot_tweak_pubkey(pubkey: [u8; 32], merkle_root: &[u8]) -> (bool, [u8; 32]) { + let prefix = Sha256::digest(b"TapTweak"); + let tweak_hash = Sha256::new() + .chain_update(&prefix) + .chain_update(&prefix) + .chain_update(&pubkey) + .chain_update(merkle_root) + .finalize(); + let t = k256::Scalar::from( + k256::elliptic_curve::ScalarPrimitive::new(k256::U256::from_be_slice(&tweak_hash)).unwrap(), + ); + + let mut pubkey_even_bytes = [0x02; 33]; + pubkey_even_bytes[1..].copy_from_slice(&pubkey); + let pubkey_even = Secp256K1Group::deserialize(&pubkey_even_bytes).unwrap(); + + let tr_output_key = (pubkey_even + ProjectivePoint::GENERATOR * t).to_affine(); + (tr_output_key.y_is_odd().into(), tr_output_key.x().into()) +} From aa4b5fbd5f0e4fea5455570218b951bda5c908ce Mon Sep 17 00:00:00 2001 From: conduition Date: Fri, 8 Nov 2024 22:22:26 +0000 Subject: [PATCH 12/13] clippy test fixes --- frost-secp256k1-tr/tests/tweaking_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs index 83b7a032..3fc74aef 100644 --- a/frost-secp256k1-tr/tests/tweaking_tests.rs +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -131,9 +131,9 @@ fn check_tweaked_sign_with_dealer() -> Result<(), Box> { fn taproot_tweak_pubkey(pubkey: [u8; 32], merkle_root: &[u8]) -> (bool, [u8; 32]) { let prefix = Sha256::digest(b"TapTweak"); let tweak_hash = Sha256::new() - .chain_update(&prefix) - .chain_update(&prefix) - .chain_update(&pubkey) + .chain_update(prefix) + .chain_update(prefix) + .chain_update(pubkey) .chain_update(merkle_root) .finalize(); let t = k256::Scalar::from( From 59d1da299c7c04afc2be9c1a5f8a63a1b6cb35b9 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Mon, 11 Nov 2024 19:10:43 -0300 Subject: [PATCH 13/13] fix no-std issues and warnings --- frost-core/src/lib.rs | 2 ++ frost-core/src/traits.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 70534fa5..7d78e2df 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -636,8 +636,10 @@ where Ok(signature) } + /// Optional cheater detection feature /// Each share is verified to find the cheater +#[cfg(feature = "cheater-detection")] fn detect_cheater( group_commitment: &GroupCommitment, pubkeys: &keys::PublicKeyPackage, diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index a9c8ca05..4e3f959d 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -4,9 +4,8 @@ use core::{ fmt::Debug, ops::{Add, Mul, Sub}, }; -use std::borrow::Cow; -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; use crate::{