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
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

View File

@ -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.
///

View File

@ -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;

View File

@ -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::*;

View File

@ -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<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]_.
///
/// [challenge]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-challenge-computa
#[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>
where
@ -447,17 +140,17 @@ struct Header<C: Ciphersuite> {
/// 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, 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_ (ρ)
///
/// 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>
#[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>
where
@ -611,7 +213,9 @@ where
/// A list of binding factors and their associated identifiers.
#[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>
where
@ -847,19 +451,21 @@ where
{
/// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Serialize::serialize(&self)
serialization::Serialize::serialize(&self)
}
/// Deserialize the struct from a slice of bytes.
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
/// final signature.
#[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>
where

View File

@ -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};

View File

@ -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<C> it can't
// 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};
#[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)]

View File

@ -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`.