From 408540fb868f21e6a35a46637de4b623b36c6a32 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 7 Nov 2023 11:57:21 -0300 Subject: [PATCH] frost-core: split part of lib.rs into traits.rs and serialization.rs (#569) --- frost-core/CHANGELOG.md | 2 + frost-core/src/identifier.rs | 2 +- frost-core/src/keys.rs | 7 +- frost-core/src/keys/dkg.rs | 4 +- frost-core/src/lib.rs | 454 +++----------------------------- frost-core/src/round1.rs | 7 +- frost-core/src/round2.rs | 2 +- frost-core/src/serialization.rs | 190 +++++++++++++ frost-core/src/traits.rs | 231 ++++++++++++++++ frost-core/src/verifying_key.rs | 2 +- frost-rerandomized/src/lib.rs | 2 +- 11 files changed, 468 insertions(+), 435 deletions(-) create mode 100644 frost-core/src/serialization.rs create mode 100644 frost-core/src/traits.rs diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 5ab954a..aa15fc1 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -9,6 +9,8 @@ Entries are listed in reverse chronological order. * The `frost-core::frost` module contents were merged into `frost-core`, thus eliminating the `frost` module. You can adapt any calling code with e.g. changing `use frost_core::frost::*` to `use frost-core::*`. +* `Challenge`, `BindingFactor`, `BindingFactorList` and `GroupCommitment` + are no longer public (you can use them with the `internals` feature). * Both serde serialization and the default byte-oriented serialization now include a version field (a u8) at the beginning which is always 0 for now. The ciphersuite ID field was moved from the last field to the second field, after diff --git a/frost-core/src/identifier.rs b/frost-core/src/identifier.rs index 961baf3..5525197 100644 --- a/frost-core/src/identifier.rs +++ b/frost-core/src/identifier.rs @@ -8,7 +8,7 @@ use std::{ use crate::{Ciphersuite, Error, Field, FieldError, Group, Scalar}; #[cfg(feature = "serde")] -use crate::ScalarSerialization; +use crate::serialization::ScalarSerialization; /// A FROST participant identifier. /// diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 4425f92..8542e7a 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -17,12 +17,13 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::{DefaultIsZeroes, Zeroize}; use crate::{ - Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Identifier, Scalar, Serialize, - SigningKey, VerifyingKey, + serialization::{Deserialize, Serialize}, + Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, SigningKey, + VerifyingKey, }; #[cfg(feature = "serde")] -use crate::{ElementSerialization, ScalarSerialization}; +use crate::serialization::{ElementSerialization, ScalarSerialization}; use super::compute_lagrange_coefficient; diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 8c4ea78..09aca6c 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -50,7 +50,7 @@ pub mod round1 { use derive_getters::Getters; use zeroize::Zeroize; - use crate::{Deserialize, Serialize}; + use crate::serialization::{Deserialize, Serialize}; use super::*; @@ -167,7 +167,7 @@ pub mod round2 { use derive_getters::Getters; use zeroize::Zeroize; - use crate::{Deserialize, Serialize}; + use crate::serialization::{Deserialize, Serialize}; use super::*; diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 0ceaa25..2e4e81c 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -15,7 +15,6 @@ use std::{ default::Default, fmt::{self, Debug}, marker::PhantomData, - ops::{Add, Mul, Sub}, }; use derive_getters::Getters; @@ -24,10 +23,6 @@ use hex::FromHex; use rand_core::{CryptoRng, RngCore}; use zeroize::Zeroize; -// Re-export serde -#[cfg(feature = "serde")] -pub use serde; - pub mod batch; #[cfg(any(test, feature = "test-impl"))] pub mod benches; @@ -37,339 +32,37 @@ pub mod keys; pub mod round1; pub mod round2; mod scalar_mul; +// We'd like to make this conditionally pub but the attribute below does +// not work yet (https://github.com/rust-lang/rust/issues/54727) +// #[cfg_attr(feature = "internals", visibility::make(pub))] +pub mod serialization; mod signature; mod signing_key; #[cfg(any(test, feature = "test-impl"))] pub mod tests; +mod traits; mod verifying_key; -pub use self::identifier::Identifier; -use crate::scalar_mul::VartimeMultiscalarMul; pub use error::{Error, FieldError, GroupError}; +pub use identifier::Identifier; +use scalar_mul::VartimeMultiscalarMul; +// Re-export serde +#[cfg(feature = "serde")] +pub use serde; pub use signature::Signature; pub use signing_key::SigningKey; +pub use traits::{Ciphersuite, Element, Field, Group, Scalar}; pub use verifying_key::VerifyingKey; -/// A prime order finite field GF(q) over which all scalar values for our prime order group can be -/// multiplied are defined. -/// -/// This trait does not have to be implemented for a finite field scalar itself, it can be a -/// pass-through, implemented for a type just for the ciphersuite, and calls through to another -/// implementation underneath, so that this trait does not have to be implemented for types you -/// don't own. -pub trait Field: Copy + Clone { - /// An element of the scalar field GF(p). - /// The Eq/PartialEq implementation MUST be constant-time. - type Scalar: Add - + Copy - + Clone - + Eq - + Mul - + PartialEq - + Sub; - - /// A unique byte array buf of fixed length N. - type Serialization: AsRef<[u8]> + Debug + TryFrom>; - - /// Returns the zero element of the field, the additive identity. - fn zero() -> Self::Scalar; - - /// Returns the one element of the field, the multiplicative identity. - fn one() -> Self::Scalar; - - /// Computes the multiplicative inverse of an element of the scalar field, failing if the - /// element is zero. - fn invert(scalar: &Self::Scalar) -> Result; - - /// Generate a random scalar from the entire space [0, l-1] - /// - /// - fn random(rng: &mut R) -> Self::Scalar; - - /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of - /// fixed length Ne. - /// - /// - fn serialize(scalar: &Self::Scalar) -> Self::Serialization; - - /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of - /// fixed length Ne, in little-endian order. - /// - /// This is used internally. - fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization; - - /// A member function of a [`Field`] that attempts to map a byte array `buf` to a [`Scalar`]. - /// - /// Fails if the input is not a valid byte representation of an [`Scalar`] of the - /// [`Field`]. This function can raise an [`Error`] if deserialization fails. - /// - /// - fn deserialize(buf: &Self::Serialization) -> Result; -} - -/// An element of the [`Ciphersuite`] `C`'s [`Group`]'s scalar [`Field`]. -pub type Scalar = <<::Group as Group>::Field as Field>::Scalar; - -#[cfg(feature = "serde")] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -/// Helper struct to serialize a Scalar. -pub(crate) struct ScalarSerialization( - pub <<::Group as Group>::Field as Field>::Serialization, -); - -#[cfg(feature = "serde")] -impl serde::Serialize for ScalarSerialization -where - C: Ciphersuite, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serdect::array::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de, C> serde::Deserialize<'de> for ScalarSerialization -where - C: Ciphersuite, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - // Get size from the size of the zero scalar - let zero = <::Field as Field>::zero(); - let len = <::Field as Field>::serialize(&zero) - .as_ref() - .len(); - - let mut bytes = vec![0u8; len]; - serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; - let array = bytes - .try_into() - .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - Ok(Self(array)) - } -} - -/// A prime-order group (or subgroup) that provides everything we need to create and verify Schnorr -/// signatures. -/// -/// This trait does not have to be implemented for the curve/element/point itself, it can be a -/// pass-through, implemented for a type just for the ciphersuite, and calls through to another -/// implementation underneath, so that this trait does not have to be implemented for types you -/// don't own. -pub trait Group: Copy + Clone + PartialEq { - /// A prime order finite field GF(q) over which all scalar values for our prime order group can - /// be multiplied are defined. - type Field: Field; - - /// An element of our group that we will be computing over. - type Element: Add - + Copy - + Clone - + Eq - + Mul<::Scalar, Output = Self::Element> - + PartialEq - + Sub; - - /// A unique byte array buf of fixed length N. - /// - /// Little-endian! - type Serialization: AsRef<[u8]> + Debug + TryFrom>; - - /// The order of the the quotient group when the prime order subgroup divides the order of the - /// full curve group. - /// - /// If using a prime order elliptic curve, the cofactor should be 1 in the scalar field. - fn cofactor() -> ::Scalar; - - /// Additive [identity] of the prime order group. - /// - /// [identity]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.2 - fn identity() -> Self::Element; - - /// The fixed generator element of the prime order group. - /// - /// The 'base' of ['ScalarBaseMult()'] from the spec. - /// - /// [`ScalarBaseMult()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.5 - fn generator() -> Self::Element; - - /// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of - /// fixed length Ne. - /// - /// - fn serialize(element: &Self::Element) -> Self::Serialization; - - /// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`]. - /// - /// Fails if the input is not a valid byte representation of an [`Element`] of the - /// [`Group`]. This function can raise an [`Error`] if deserialization fails or if the - /// resulting [`Element`] is the identity element of the group - /// - /// - fn deserialize(buf: &Self::Serialization) -> Result; -} - -/// An element of the [`Ciphersuite`] `C`'s [`Group`]. -pub type Element = <::Group as Group>::Element; - -#[cfg(feature = "serde")] -pub(crate) struct ElementSerialization( - <::Group as Group>::Serialization, -); - -#[cfg(feature = "serde")] -impl serde::Serialize for ElementSerialization -where - C: Ciphersuite, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serdect::array::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de, C> serde::Deserialize<'de> for ElementSerialization -where - C: Ciphersuite, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - // Get size from the size of the generator - let generator = ::generator(); - let len = ::serialize(&generator).as_ref().len(); - - let mut bytes = vec![0u8; len]; - serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; - let array = bytes - .try_into() - .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - Ok(Self(array)) - } -} - -/// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash -/// function. -/// -/// [FROST ciphersuite]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-ciphersuites -pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { - /// The ciphersuite ID string. It should be equal to the contextString in - /// the spec. For new ciphersuites, this should be a string that identifies - /// the ciphersuite; it's recommended to use a similar format to the - /// ciphersuites in the FROST spec, e.g. "FROST-RISTRETTO255-SHA512-v1". - const ID: &'static str; - - /// The prime order group (or subgroup) that this ciphersuite operates over. - type Group: Group; - - /// A unique byte array of fixed length. - type HashOutput: AsRef<[u8]>; - - /// A unique byte array of fixed length that is the `Group::ElementSerialization` + - /// `Group::ScalarSerialization` - type SignatureSerialization: AsRef<[u8]> + TryFrom>; - - /// [H1] for a FROST ciphersuite. - /// - /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. - /// - /// [H1]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function - fn H1(m: &[u8]) -> <::Field as Field>::Scalar; - - /// [H2] for a FROST ciphersuite. - /// - /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. - /// - /// [H2]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function - fn H2(m: &[u8]) -> <::Field as Field>::Scalar; - - /// [H3] for a FROST ciphersuite. - /// - /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. - /// - /// [H3]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function - fn H3(m: &[u8]) -> <::Field as Field>::Scalar; - - /// [H4] for a FROST ciphersuite. - /// - /// Usually an an alias for the ciphersuite hash function _H_ with domain separation applied. - /// - /// [H4]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function - fn H4(m: &[u8]) -> Self::HashOutput; - - /// [H5] for a FROST ciphersuite. - /// - /// Usually an an alias for the ciphersuite hash function _H_ with domain separation applied. - /// - /// [H5]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash - fn H5(m: &[u8]) -> Self::HashOutput; - - /// Hash function for a FROST ciphersuite, used for the DKG. - /// - /// The DKG it not part of the specification, thus this is optional. - /// It can return None if DKG is not supported by the Ciphersuite. This is - /// the default implementation. - /// - /// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field. - fn HDKG(_m: &[u8]) -> Option<<::Field as Field>::Scalar> { - None - } - - /// Hash function for a FROST ciphersuite, used for deriving identifiers from strings. - /// - /// This feature is not part of the specification and is just a convenient - /// way of creating identifiers. Therefore it can return None if this is not supported by the - /// Ciphersuite. This is the default implementation. - /// - /// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field. - fn HID(_m: &[u8]) -> Option<<::Field as Field>::Scalar> { - None - } - - /// Verify a signature for this ciphersuite. 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. - fn verify_signature( - msg: &[u8], - signature: &Signature, - public_key: &VerifyingKey, - ) -> Result<(), Error> { - let c = crate::challenge::(&signature.R, public_key, msg); - - public_key.verify_prehashed(c, signature) - } -} - -// The short 4-byte ID. Derived as the CRC-32 of the UTF-8 -// encoded ID in big endian format. -const fn short_id() -> [u8; 4] -where - C: Ciphersuite, -{ - const_crc32::crc32(C::ID.as_bytes()).to_be_bytes() -} - /// A type refinement for the scalar field element representing the per-message _[challenge]_. /// /// [challenge]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-challenge-computa #[derive(Clone)] -pub struct Challenge(pub(crate) <::Field as Field>::Scalar); +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +pub(crate) struct Challenge( + pub(crate) <::Field as Field>::Scalar, +); impl Challenge where @@ -447,17 +140,17 @@ struct Header { /// Format version #[cfg_attr( feature = "serde", - serde(deserialize_with = "crate::version_deserialize::<_>") + serde(deserialize_with = "crate::serialization::version_deserialize::<_>") )] version: u8, /// Ciphersuite ID #[cfg_attr( feature = "serde", - serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + serde(serialize_with = "crate::serialization::ciphersuite_serialize::<_, C>") )] #[cfg_attr( feature = "serde", - serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + serde(deserialize_with = "crate::serialization::ciphersuite_deserialize::<_, C>") )] ciphersuite: (), #[serde(skip)] @@ -477,99 +170,6 @@ where } } -/// Serialize a placeholder ciphersuite field with the ciphersuite ID string. -#[cfg(feature = "serde")] -pub(crate) fn ciphersuite_serialize(_: &(), s: S) -> Result -where - S: serde::Serializer, - C: Ciphersuite, -{ - use serde::Serialize; - - if s.is_human_readable() { - C::ID.serialize(s) - } else { - serde::Serialize::serialize(&short_id::(), s) - } -} - -/// Deserialize a placeholder ciphersuite field, checking if it's the ciphersuite ID string. -#[cfg(feature = "serde")] -pub(crate) fn ciphersuite_deserialize<'de, D, C>(deserializer: D) -> Result<(), D::Error> -where - D: serde::Deserializer<'de>, - C: Ciphersuite, -{ - if deserializer.is_human_readable() { - let s: &str = serde::de::Deserialize::deserialize(deserializer)?; - if s != C::ID { - Err(serde::de::Error::custom("wrong ciphersuite")) - } else { - Ok(()) - } - } else { - let buffer: [u8; 4] = serde::de::Deserialize::deserialize(deserializer)?; - if buffer != short_id::() { - Err(serde::de::Error::custom("wrong ciphersuite")) - } else { - Ok(()) - } - } -} - -/// Deserialize a version. For now, since there is a single version 0, -/// simply validate if it's 0. -#[cfg(feature = "serde")] -pub(crate) fn version_deserialize<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let version: u8 = serde::de::Deserialize::deserialize(deserializer)?; - if version != 0 { - Err(serde::de::Error::custom( - "wrong format version, only 0 supported", - )) - } else { - Ok(version) - } -} - -// Default byte-oriented serialization for structs that need to be communicated. -// -// Note that we still manually implement these methods in each applicable type, -// instead of making these traits `pub` and asking users to import the traits. -// The reason is that ciphersuite traits would need to re-export these traits, -// parametrized with the ciphersuite, but trait aliases are not currently -// supported: - -#[cfg(feature = "serialization")] -trait Serialize { - /// Serialize the struct into a Vec. - fn serialize(&self) -> Result, Error>; -} - -#[cfg(feature = "serialization")] -trait Deserialize { - /// Deserialize the struct from a slice of bytes. - fn deserialize(bytes: &[u8]) -> Result> - where - Self: std::marker::Sized; -} - -#[cfg(feature = "serialization")] -impl Serialize for T { - fn serialize(&self) -> Result, Error> { - postcard::to_stdvec(self).map_err(|_| Error::SerializationError) - } -} - -#[cfg(feature = "serialization")] -impl serde::Deserialize<'de>, C: Ciphersuite> Deserialize for T { - fn deserialize(bytes: &[u8]) -> Result> { - postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError) - } -} - /// The binding factor, also known as _rho_ (ρ) /// /// Ensures each signature share is strongly bound to a signing set, specific set @@ -577,7 +177,9 @@ impl serde::Deserialize<'de>, C: Ciphersuite> Deserialize for T { /// /// #[derive(Clone, PartialEq, Eq)] -pub struct BindingFactor(Scalar); +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +pub(crate) struct BindingFactor(Scalar); impl BindingFactor where @@ -611,7 +213,9 @@ where /// A list of binding factors and their associated identifiers. #[derive(Clone)] -pub struct BindingFactorList(BTreeMap, BindingFactor>); +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +pub(crate) struct BindingFactorList(BTreeMap, BindingFactor>); impl BindingFactorList where @@ -847,19 +451,21 @@ where { /// Serialize the struct into a Vec. pub fn serialize(&self) -> Result, Error> { - Serialize::serialize(&self) + serialization::Serialize::serialize(&self) } /// Deserialize the struct from a slice of bytes. pub fn deserialize(bytes: &[u8]) -> Result> { - Deserialize::deserialize(bytes) + serialization::Deserialize::deserialize(bytes) } } /// The product of all signers' individual commitments, published as part of the /// final signature. #[derive(Clone, PartialEq, Eq)] -pub struct GroupCommitment(pub(crate) Element); +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +pub(crate) struct GroupCommitment(pub(crate) Element); impl GroupCommitment where diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index a4febb0..eb4abb3 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -13,10 +13,13 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::Zeroize; use crate as frost; -use crate::{Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Scalar, Serialize}; +use crate::{ + serialization::{Deserialize, Serialize}, + Ciphersuite, Element, Error, Field, Group, Header, Scalar, +}; #[cfg(feature = "serde")] -use crate::ElementSerialization; +use crate::serialization::ElementSerialization; use super::{keys::SigningShare, Identifier}; diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 0449ac9..509a6e0 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -8,7 +8,7 @@ use crate::{ }; #[cfg(feature = "serde")] -use crate::ScalarSerialization; +use crate::serialization::ScalarSerialization; // Used to help encoding a SignatureShare. Since it has a Scalar it can't // be directly encoded with serde, so we use this struct to wrap the scalar. diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs new file mode 100644 index 0000000..422d056 --- /dev/null +++ b/frost-core/src/serialization.rs @@ -0,0 +1,190 @@ +//! Serialization support. + +use crate::{Ciphersuite, Error, Field, Group}; + +#[cfg(feature = "serde")] +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +/// Helper struct to serialize a Scalar. +pub(crate) struct ScalarSerialization( + pub <<::Group as Group>::Field as Field>::Serialization, +); + +#[cfg(feature = "serde")] +impl serde::Serialize for ScalarSerialization +where + C: Ciphersuite, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serdect::array::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> serde::Deserialize<'de> for ScalarSerialization +where + C: Ciphersuite, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Get size from the size of the zero scalar + let zero = <::Field as Field>::zero(); + let len = <::Field as Field>::serialize(&zero) + .as_ref() + .len(); + + let mut bytes = vec![0u8; len]; + serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; + let array = bytes + .try_into() + .map_err(|_| serde::de::Error::custom("invalid byte length"))?; + Ok(Self(array)) + } +} + +#[cfg(feature = "serde")] +pub(crate) struct ElementSerialization( + pub(crate) <::Group as Group>::Serialization, +); + +#[cfg(feature = "serde")] +impl serde::Serialize for ElementSerialization +where + C: Ciphersuite, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serdect::array::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> serde::Deserialize<'de> for ElementSerialization +where + C: Ciphersuite, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Get size from the size of the generator + let generator = ::generator(); + let len = ::serialize(&generator).as_ref().len(); + + let mut bytes = vec![0u8; len]; + serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; + let array = bytes + .try_into() + .map_err(|_| serde::de::Error::custom("invalid byte length"))?; + Ok(Self(array)) + } +} + +// The short 4-byte ID. Derived as the CRC-32 of the UTF-8 +// encoded ID in big endian format. +const fn short_id() -> [u8; 4] +where + C: Ciphersuite, +{ + const_crc32::crc32(C::ID.as_bytes()).to_be_bytes() +} + +/// Serialize a placeholder ciphersuite field with the ciphersuite ID string. +#[cfg(feature = "serde")] +pub(crate) fn ciphersuite_serialize(_: &(), s: S) -> Result +where + S: serde::Serializer, + C: Ciphersuite, +{ + use serde::Serialize; + + if s.is_human_readable() { + C::ID.serialize(s) + } else { + serde::Serialize::serialize(&short_id::(), s) + } +} + +/// Deserialize a placeholder ciphersuite field, checking if it's the ciphersuite ID string. +#[cfg(feature = "serde")] +pub(crate) fn ciphersuite_deserialize<'de, D, C>(deserializer: D) -> Result<(), D::Error> +where + D: serde::Deserializer<'de>, + C: Ciphersuite, +{ + if deserializer.is_human_readable() { + let s: &str = serde::de::Deserialize::deserialize(deserializer)?; + if s != C::ID { + Err(serde::de::Error::custom("wrong ciphersuite")) + } else { + Ok(()) + } + } else { + let buffer: [u8; 4] = serde::de::Deserialize::deserialize(deserializer)?; + if buffer != short_id::() { + Err(serde::de::Error::custom("wrong ciphersuite")) + } else { + Ok(()) + } + } +} + +/// Deserialize a version. For now, since there is a single version 0, +/// simply validate if it's 0. +#[cfg(feature = "serde")] +pub(crate) fn version_deserialize<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let version: u8 = serde::de::Deserialize::deserialize(deserializer)?; + if version != 0 { + Err(serde::de::Error::custom( + "wrong format version, only 0 supported", + )) + } else { + Ok(version) + } +} + +// Default byte-oriented serialization for structs that need to be communicated. +// +// Note that we still manually implement these methods in each applicable type, +// instead of making these traits `pub` and asking users to import the traits. +// The reason is that ciphersuite traits would need to re-export these traits, +// parametrized with the ciphersuite, but trait aliases are not currently +// supported: + +#[cfg(feature = "serialization")] +pub(crate) trait Serialize { + /// Serialize the struct into a Vec. + fn serialize(&self) -> Result, Error>; +} + +#[cfg(feature = "serialization")] +pub(crate) trait Deserialize { + /// Deserialize the struct from a slice of bytes. + fn deserialize(bytes: &[u8]) -> Result> + where + Self: std::marker::Sized; +} + +#[cfg(feature = "serialization")] +impl Serialize for T { + fn serialize(&self) -> Result, Error> { + postcard::to_stdvec(self).map_err(|_| Error::SerializationError) + } +} + +#[cfg(feature = "serialization")] +impl serde::Deserialize<'de>, C: Ciphersuite> Deserialize for T { + fn deserialize(bytes: &[u8]) -> Result> { + postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError) + } +} diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs new file mode 100644 index 0000000..817caf6 --- /dev/null +++ b/frost-core/src/traits.rs @@ -0,0 +1,231 @@ +//! Traits used to abstract Ciphersuites. + +use std::{ + fmt::Debug, + ops::{Add, Mul, Sub}, +}; + +use rand_core::{CryptoRng, RngCore}; + +use crate::{Error, FieldError, GroupError, Signature, VerifyingKey}; + +/// A prime order finite field GF(q) over which all scalar values for our prime order group can be +/// multiplied are defined. +/// +/// This trait does not have to be implemented for a finite field scalar itself, it can be a +/// pass-through, implemented for a type just for the ciphersuite, and calls through to another +/// implementation underneath, so that this trait does not have to be implemented for types you +/// don't own. +pub trait Field: Copy + Clone { + /// An element of the scalar field GF(p). + /// The Eq/PartialEq implementation MUST be constant-time. + type Scalar: Add + + Copy + + Clone + + Eq + + Mul + + PartialEq + + Sub; + + /// A unique byte array buf of fixed length N. + type Serialization: AsRef<[u8]> + Debug + TryFrom>; + + /// Returns the zero element of the field, the additive identity. + fn zero() -> Self::Scalar; + + /// Returns the one element of the field, the multiplicative identity. + fn one() -> Self::Scalar; + + /// Computes the multiplicative inverse of an element of the scalar field, failing if the + /// element is zero. + fn invert(scalar: &Self::Scalar) -> Result; + + /// Generate a random scalar from the entire space [0, l-1] + /// + /// + fn random(rng: &mut R) -> Self::Scalar; + + /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of + /// fixed length Ne. + /// + /// + fn serialize(scalar: &Self::Scalar) -> Self::Serialization; + + /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of + /// fixed length Ne, in little-endian order. + /// + /// This is used internally. + fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization; + + /// A member function of a [`Field`] that attempts to map a byte array `buf` to a [`Scalar`]. + /// + /// Fails if the input is not a valid byte representation of an [`Scalar`] of the + /// [`Field`]. This function can raise an [`Error`] if deserialization fails. + /// + /// + fn deserialize(buf: &Self::Serialization) -> Result; +} + +/// An element of the [`Ciphersuite`] `C`'s [`Group`]'s scalar [`Field`]. +pub type Scalar = <<::Group as Group>::Field as Field>::Scalar; + +/// A prime-order group (or subgroup) that provides everything we need to create and verify Schnorr +/// signatures. +/// +/// This trait does not have to be implemented for the curve/element/point itself, it can be a +/// pass-through, implemented for a type just for the ciphersuite, and calls through to another +/// implementation underneath, so that this trait does not have to be implemented for types you +/// don't own. +pub trait Group: Copy + Clone + PartialEq { + /// A prime order finite field GF(q) over which all scalar values for our prime order group can + /// be multiplied are defined. + type Field: Field; + + /// An element of our group that we will be computing over. + type Element: Add + + Copy + + Clone + + Eq + + Mul<::Scalar, Output = Self::Element> + + PartialEq + + Sub; + + /// A unique byte array buf of fixed length N. + /// + /// Little-endian! + type Serialization: AsRef<[u8]> + Debug + TryFrom>; + + /// The order of the the quotient group when the prime order subgroup divides the order of the + /// full curve group. + /// + /// If using a prime order elliptic curve, the cofactor should be 1 in the scalar field. + fn cofactor() -> ::Scalar; + + /// Additive [identity] of the prime order group. + /// + /// [identity]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.2 + fn identity() -> Self::Element; + + /// The fixed generator element of the prime order group. + /// + /// The 'base' of ['ScalarBaseMult()'] from the spec. + /// + /// [`ScalarBaseMult()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.5 + fn generator() -> Self::Element; + + /// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of + /// fixed length Ne. + /// + /// + fn serialize(element: &Self::Element) -> Self::Serialization; + + /// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`]. + /// + /// Fails if the input is not a valid byte representation of an [`Element`] of the + /// [`Group`]. This function can raise an [`Error`] if deserialization fails or if the + /// resulting [`Element`] is the identity element of the group + /// + /// + fn deserialize(buf: &Self::Serialization) -> Result; +} + +/// An element of the [`Ciphersuite`] `C`'s [`Group`]. +pub type Element = <::Group as Group>::Element; + +/// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash +/// function. +/// +/// [FROST ciphersuite]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-ciphersuites +pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { + /// The ciphersuite ID string. It should be equal to the contextString in + /// the spec. For new ciphersuites, this should be a string that identifies + /// the ciphersuite; it's recommended to use a similar format to the + /// ciphersuites in the FROST spec, e.g. "FROST-RISTRETTO255-SHA512-v1". + const ID: &'static str; + + /// The prime order group (or subgroup) that this ciphersuite operates over. + type Group: Group; + + /// A unique byte array of fixed length. + type HashOutput: AsRef<[u8]>; + + /// A unique byte array of fixed length that is the `Group::ElementSerialization` + + /// `Group::ScalarSerialization` + type SignatureSerialization: AsRef<[u8]> + TryFrom>; + + /// [H1] for a FROST ciphersuite. + /// + /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. + /// + /// [H1]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + fn H1(m: &[u8]) -> <::Field as Field>::Scalar; + + /// [H2] for a FROST ciphersuite. + /// + /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. + /// + /// [H2]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + fn H2(m: &[u8]) -> <::Field as Field>::Scalar; + + /// [H3] for a FROST ciphersuite. + /// + /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. + /// + /// [H3]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + fn H3(m: &[u8]) -> <::Field as Field>::Scalar; + + /// [H4] for a FROST ciphersuite. + /// + /// Usually an an alias for the ciphersuite hash function _H_ with domain separation applied. + /// + /// [H4]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + fn H4(m: &[u8]) -> Self::HashOutput; + + /// [H5] for a FROST ciphersuite. + /// + /// Usually an an alias for the ciphersuite hash function _H_ with domain separation applied. + /// + /// [H5]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash + fn H5(m: &[u8]) -> Self::HashOutput; + + /// Hash function for a FROST ciphersuite, used for the DKG. + /// + /// The DKG it not part of the specification, thus this is optional. + /// It can return None if DKG is not supported by the Ciphersuite. This is + /// the default implementation. + /// + /// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field. + fn HDKG(_m: &[u8]) -> Option<<::Field as Field>::Scalar> { + None + } + + /// Hash function for a FROST ciphersuite, used for deriving identifiers from strings. + /// + /// This feature is not part of the specification and is just a convenient + /// way of creating identifiers. Therefore it can return None if this is not supported by the + /// Ciphersuite. This is the default implementation. + /// + /// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field. + fn HID(_m: &[u8]) -> Option<<::Field as Field>::Scalar> { + None + } + + /// Verify a signature for this ciphersuite. 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. + fn verify_signature( + msg: &[u8], + signature: &Signature, + public_key: &VerifyingKey, + ) -> Result<(), Error> { + let c = crate::challenge::(&signature.R, public_key, msg); + + public_key.verify_prehashed(c, signature) + } +} diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index 572aa35..519020b 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -6,7 +6,7 @@ use hex::FromHex; use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature}; #[cfg(feature = "serde")] -use crate::ElementSerialization; +use crate::serialization::ElementSerialization; /// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`]. #[derive(Copy, Clone, PartialEq, Eq)] diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 6d41db1..f4ff81b 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -28,7 +28,7 @@ use frost_core::{ #[cfg(feature = "serde")] use frost_core::serde; #[cfg(feature = "serde")] -use frost_core::ScalarSerialization; +use frost_core::serialization::ScalarSerialization; // When pulled into `reddsa`, that has its own sibling `rand_core` import. // For the time being, we do not re-export this `rand_core`.