frost-core: split part of lib.rs into traits.rs and serialization.rs (#569)

This commit is contained in:
Conrado Gouvea 2023-11-07 11:57:21 -03:00 committed by GitHub
parent a0df08e30a
commit 408540fb86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 468 additions and 435 deletions

View File

@ -9,6 +9,8 @@ Entries are listed in reverse chronological order.
* The `frost-core::frost` module contents were merged into `frost-core`, thus * 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. eliminating the `frost` module. You can adapt any calling code with e.g.
changing `use frost_core::frost::*` to `use frost-core::*`. 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 * 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 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 ciphersuite ID field was moved from the last field to the second field, after

View File

@ -8,7 +8,7 @@ use std::{
use crate::{Ciphersuite, Error, Field, FieldError, Group, Scalar}; use crate::{Ciphersuite, Error, Field, FieldError, Group, Scalar};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::ScalarSerialization; use crate::serialization::ScalarSerialization;
/// A FROST participant identifier. /// A FROST participant identifier.
/// ///

View File

@ -17,12 +17,13 @@ use rand_core::{CryptoRng, RngCore};
use zeroize::{DefaultIsZeroes, Zeroize}; use zeroize::{DefaultIsZeroes, Zeroize};
use crate::{ use crate::{
Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Identifier, Scalar, Serialize, serialization::{Deserialize, Serialize},
SigningKey, VerifyingKey, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, SigningKey,
VerifyingKey,
}; };
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::{ElementSerialization, ScalarSerialization}; use crate::serialization::{ElementSerialization, ScalarSerialization};
use super::compute_lagrange_coefficient; use super::compute_lagrange_coefficient;

View File

@ -50,7 +50,7 @@ pub mod round1 {
use derive_getters::Getters; use derive_getters::Getters;
use zeroize::Zeroize; use zeroize::Zeroize;
use crate::{Deserialize, Serialize}; use crate::serialization::{Deserialize, Serialize};
use super::*; use super::*;
@ -167,7 +167,7 @@ pub mod round2 {
use derive_getters::Getters; use derive_getters::Getters;
use zeroize::Zeroize; use zeroize::Zeroize;
use crate::{Deserialize, Serialize}; use crate::serialization::{Deserialize, Serialize};
use super::*; use super::*;

View File

@ -15,7 +15,6 @@ use std::{
default::Default, default::Default,
fmt::{self, Debug}, fmt::{self, Debug},
marker::PhantomData, marker::PhantomData,
ops::{Add, Mul, Sub},
}; };
use derive_getters::Getters; use derive_getters::Getters;
@ -24,10 +23,6 @@ use hex::FromHex;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroize; use zeroize::Zeroize;
// Re-export serde
#[cfg(feature = "serde")]
pub use serde;
pub mod batch; pub mod batch;
#[cfg(any(test, feature = "test-impl"))] #[cfg(any(test, feature = "test-impl"))]
pub mod benches; pub mod benches;
@ -37,339 +32,37 @@ pub mod keys;
pub mod round1; pub mod round1;
pub mod round2; pub mod round2;
mod scalar_mul; 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 signature;
mod signing_key; mod signing_key;
#[cfg(any(test, feature = "test-impl"))] #[cfg(any(test, feature = "test-impl"))]
pub mod tests; pub mod tests;
mod traits;
mod verifying_key; mod verifying_key;
pub use self::identifier::Identifier;
use crate::scalar_mul::VartimeMultiscalarMul;
pub use error::{Error, FieldError, GroupError}; 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 signature::Signature;
pub use signing_key::SigningKey; pub use signing_key::SigningKey;
pub use traits::{Ciphersuite, Element, Field, Group, Scalar};
pub use verifying_key::VerifyingKey; 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<Output = Self::Scalar>
+ Copy
+ Clone
+ Eq
+ Mul<Output = Self::Scalar>
+ PartialEq
+ Sub<Output = Self::Scalar>;
/// A unique byte array buf of fixed length N.
type Serialization: AsRef<[u8]> + Debug + TryFrom<Vec<u8>>;
/// 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<Self::Scalar, FieldError>;
/// Generate a random scalar from the entire space [0, l-1]
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.3>
fn random<R: RngCore + CryptoRng>(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.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.8>
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.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.9>
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError>;
}
/// An element of the [`Ciphersuite`] `C`'s [`Group`]'s scalar [`Field`].
pub type Scalar<C> = <<<C as Ciphersuite>::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<C: Ciphersuite>(
pub <<<C as Ciphersuite>::Group as Group>::Field as Field>::Serialization,
);
#[cfg(feature = "serde")]
impl<C> serde::Serialize for ScalarSerialization<C>
where
C: Ciphersuite,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<C>
where
C: Ciphersuite,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Get size from the size of the zero scalar
let zero = <<C::Group as Group>::Field as Field>::zero();
let len = <<C::Group as Group>::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<Output = Self::Element>
+ Copy
+ Clone
+ Eq
+ Mul<<Self::Field as Field>::Scalar, Output = Self::Element>
+ PartialEq
+ Sub<Output = Self::Element>;
/// A unique byte array buf of fixed length N.
///
/// Little-endian!
type Serialization: AsRef<[u8]> + Debug + TryFrom<Vec<u8>>;
/// 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() -> <Self::Field as Field>::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.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.6>
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
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.7>
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError>;
}
/// An element of the [`Ciphersuite`] `C`'s [`Group`].
pub type Element<C> = <<C as Ciphersuite>::Group as Group>::Element;
#[cfg(feature = "serde")]
pub(crate) struct ElementSerialization<C: Ciphersuite>(
<<C as Ciphersuite>::Group as Group>::Serialization,
);
#[cfg(feature = "serde")]
impl<C> serde::Serialize for ElementSerialization<C>
where
C: Ciphersuite,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<C>
where
C: Ciphersuite,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Get size from the size of the generator
let generator = <C::Group>::generator();
let len = <C::Group>::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<Vec<u8>>;
/// [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]) -> <<Self::Group as Group>::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]) -> <<Self::Group as Group>::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]) -> <<Self::Group as Group>::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<<<Self::Group as Group>::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<<<Self::Group as Group>::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<Self>,
public_key: &VerifyingKey<Self>,
) -> Result<(), Error<Self>> {
let c = crate::challenge::<Self>(&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<C>() -> [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]_. /// 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 /// [challenge]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-challenge-computa
#[derive(Clone)] #[derive(Clone)]
pub struct Challenge<C: Ciphersuite>(pub(crate) <<C::Group as Group>::Field as Field>::Scalar); #[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct Challenge<C: Ciphersuite>(
pub(crate) <<C::Group as Group>::Field as Field>::Scalar,
);
impl<C> Challenge<C> impl<C> Challenge<C>
where where
@ -447,17 +140,17 @@ struct Header<C: Ciphersuite> {
/// Format version /// Format version
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
serde(deserialize_with = "crate::version_deserialize::<_>") serde(deserialize_with = "crate::serialization::version_deserialize::<_>")
)] )]
version: u8, version: u8,
/// Ciphersuite ID /// Ciphersuite ID
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") serde(serialize_with = "crate::serialization::ciphersuite_serialize::<_, C>")
)] )]
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") serde(deserialize_with = "crate::serialization::ciphersuite_deserialize::<_, C>")
)] )]
ciphersuite: (), ciphersuite: (),
#[serde(skip)] #[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, C>(_: &(), s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
C: Ciphersuite,
{
use serde::Serialize;
if s.is_human_readable() {
C::ID.serialize(s)
} else {
serde::Serialize::serialize(&short_id::<C>(), 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::<C>() {
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<u8, D::Error>
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: <https://github.com/rust-lang/rust/issues/41517>
#[cfg(feature = "serialization")]
trait Serialize<C: Ciphersuite> {
/// Serialize the struct into a Vec.
fn serialize(&self) -> Result<Vec<u8>, Error<C>>;
}
#[cfg(feature = "serialization")]
trait Deserialize<C: Ciphersuite> {
/// Deserialize the struct from a slice of bytes.
fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>>
where
Self: std::marker::Sized;
}
#[cfg(feature = "serialization")]
impl<T: serde::Serialize, C: Ciphersuite> Serialize<C> for T {
fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
postcard::to_stdvec(self).map_err(|_| Error::SerializationError)
}
}
#[cfg(feature = "serialization")]
impl<T: for<'de> serde::Deserialize<'de>, C: Ciphersuite> Deserialize<C> for T {
fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError)
}
}
/// The binding factor, also known as _rho_ (ρ) /// The binding factor, also known as _rho_ (ρ)
/// ///
/// Ensures each signature share is strongly bound to a signing set, specific set /// Ensures each signature share is strongly bound to a signing set, specific set
@ -577,7 +177,9 @@ impl<T: for<'de> serde::Deserialize<'de>, C: Ciphersuite> Deserialize<C> for T {
/// ///
/// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md> /// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md>
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct BindingFactor<C: Ciphersuite>(Scalar<C>); #[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct BindingFactor<C: Ciphersuite>(Scalar<C>);
impl<C> BindingFactor<C> impl<C> BindingFactor<C>
where where
@ -611,7 +213,9 @@ where
/// A list of binding factors and their associated identifiers. /// A list of binding factors and their associated identifiers.
#[derive(Clone)] #[derive(Clone)]
pub struct BindingFactorList<C: Ciphersuite>(BTreeMap<Identifier<C>, BindingFactor<C>>); #[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct BindingFactorList<C: Ciphersuite>(BTreeMap<Identifier<C>, BindingFactor<C>>);
impl<C> BindingFactorList<C> impl<C> BindingFactorList<C>
where where
@ -847,19 +451,21 @@ where
{ {
/// Serialize the struct into a Vec. /// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> { pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Serialize::serialize(&self) serialization::Serialize::serialize(&self)
} }
/// Deserialize the struct from a slice of bytes. /// Deserialize the struct from a slice of bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> { pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
Deserialize::deserialize(bytes) serialization::Deserialize::deserialize(bytes)
} }
} }
/// The product of all signers' individual commitments, published as part of the /// The product of all signers' individual commitments, published as part of the
/// final signature. /// final signature.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>); #[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);
impl<C> GroupCommitment<C> impl<C> GroupCommitment<C>
where where

View File

@ -13,10 +13,13 @@ use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroize; use zeroize::Zeroize;
use crate as frost; 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")] #[cfg(feature = "serde")]
use crate::ElementSerialization; use crate::serialization::ElementSerialization;
use super::{keys::SigningShare, Identifier}; use super::{keys::SigningShare, Identifier};

View File

@ -8,7 +8,7 @@ use crate::{
}; };
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::ScalarSerialization; use crate::serialization::ScalarSerialization;
// Used to help encoding a SignatureShare. Since it has a Scalar<C> it can't // Used to help encoding a SignatureShare. Since it has a Scalar<C> it can't
// be directly encoded with serde, so we use this struct to wrap the scalar. // be directly encoded with serde, so we use this struct to wrap the scalar.

View File

@ -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<C: Ciphersuite>(
pub <<<C as Ciphersuite>::Group as Group>::Field as Field>::Serialization,
);
#[cfg(feature = "serde")]
impl<C> serde::Serialize for ScalarSerialization<C>
where
C: Ciphersuite,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<C>
where
C: Ciphersuite,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Get size from the size of the zero scalar
let zero = <<C::Group as Group>::Field as Field>::zero();
let len = <<C::Group as Group>::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<C: Ciphersuite>(
pub(crate) <<C as Ciphersuite>::Group as Group>::Serialization,
);
#[cfg(feature = "serde")]
impl<C> serde::Serialize for ElementSerialization<C>
where
C: Ciphersuite,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<C>
where
C: Ciphersuite,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Get size from the size of the generator
let generator = <C::Group>::generator();
let len = <C::Group>::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<C>() -> [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, C>(_: &(), s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
C: Ciphersuite,
{
use serde::Serialize;
if s.is_human_readable() {
C::ID.serialize(s)
} else {
serde::Serialize::serialize(&short_id::<C>(), 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::<C>() {
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<u8, D::Error>
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: <https://github.com/rust-lang/rust/issues/41517>
#[cfg(feature = "serialization")]
pub(crate) trait Serialize<C: Ciphersuite> {
/// Serialize the struct into a Vec.
fn serialize(&self) -> Result<Vec<u8>, Error<C>>;
}
#[cfg(feature = "serialization")]
pub(crate) trait Deserialize<C: Ciphersuite> {
/// Deserialize the struct from a slice of bytes.
fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>>
where
Self: std::marker::Sized;
}
#[cfg(feature = "serialization")]
impl<T: serde::Serialize, C: Ciphersuite> Serialize<C> for T {
fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
postcard::to_stdvec(self).map_err(|_| Error::SerializationError)
}
}
#[cfg(feature = "serialization")]
impl<T: for<'de> serde::Deserialize<'de>, C: Ciphersuite> Deserialize<C> for T {
fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError)
}
}

231
frost-core/src/traits.rs Normal file
View File

@ -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<Output = Self::Scalar>
+ Copy
+ Clone
+ Eq
+ Mul<Output = Self::Scalar>
+ PartialEq
+ Sub<Output = Self::Scalar>;
/// A unique byte array buf of fixed length N.
type Serialization: AsRef<[u8]> + Debug + TryFrom<Vec<u8>>;
/// 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<Self::Scalar, FieldError>;
/// Generate a random scalar from the entire space [0, l-1]
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.3>
fn random<R: RngCore + CryptoRng>(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.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.8>
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.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.9>
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError>;
}
/// An element of the [`Ciphersuite`] `C`'s [`Group`]'s scalar [`Field`].
pub type Scalar<C> = <<<C as Ciphersuite>::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<Output = Self::Element>
+ Copy
+ Clone
+ Eq
+ Mul<<Self::Field as Field>::Scalar, Output = Self::Element>
+ PartialEq
+ Sub<Output = Self::Element>;
/// A unique byte array buf of fixed length N.
///
/// Little-endian!
type Serialization: AsRef<[u8]> + Debug + TryFrom<Vec<u8>>;
/// 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() -> <Self::Field as Field>::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.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.6>
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
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.7>
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError>;
}
/// An element of the [`Ciphersuite`] `C`'s [`Group`].
pub type Element<C> = <<C as Ciphersuite>::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<Vec<u8>>;
/// [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]) -> <<Self::Group as Group>::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]) -> <<Self::Group as Group>::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]) -> <<Self::Group as Group>::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<<<Self::Group as Group>::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<<<Self::Group as Group>::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<Self>,
public_key: &VerifyingKey<Self>,
) -> Result<(), Error<Self>> {
let c = crate::challenge::<Self>(&signature.R, public_key, msg);
public_key.verify_prehashed(c, signature)
}
}

View File

@ -6,7 +6,7 @@ use hex::FromHex;
use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature}; use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::ElementSerialization; use crate::serialization::ElementSerialization;
/// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`]. /// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`].
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]

View File

@ -28,7 +28,7 @@ use frost_core::{
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use frost_core::serde; use frost_core::serde;
#[cfg(feature = "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. // When pulled into `reddsa`, that has its own sibling `rand_core` import.
// For the time being, we do not re-export this `rand_core`. // For the time being, we do not re-export this `rand_core`.