Skip to content

Commit

Permalink
Fixes needed for frost DKG / threshold signatures to verify onchain. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
drewstone authored Feb 14, 2024
1 parent 7874bea commit a2d9ba7
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 58 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pallets/dkg/frost/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ serde = { workspace = true }
serdect = { workspace = true, features = ["alloc"] }
sp-std = { workspace = true }
subtle = { workspace = true }

postcard = { version = "1.0.0", default-features = false, features = ["alloc"] }
hex = { workspace = true, features = ["alloc"] }
rand_core = { workspace = true, optional = true }
debugless-unwrap = "0.0.4"
Expand Down
9 changes: 9 additions & 0 deletions pallets/dkg/frost/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ pub enum Error {
/// An error related to a Group (usually an elliptic curve or constructed from one) or one of
/// its Elements.
Group(GroupError),
/// Serialization error
SerializationError,
/// Deserialization error
DeserializationError,
IdentifierDerivationNotSupported,
/// An error related to a Malformed Signature.
MalformedSignature,
/// An error related to an invalid signature verification
Expand All @@ -50,6 +55,10 @@ impl Debug for Error {
Error::InvalidSignature => write!(f, "Invalid Signature error"),
Error::MalformedVerifyingKey => write!(f, "Malformed VerifyingKey"),
Error::MalformedSigningKey => write!(f, "Malformed SigningKey"),
Error::SerializationError => write!(f, "Serialization error"),
Error::DeserializationError => write!(f, "Deserialization error"),
Error::IdentifierDerivationNotSupported =>
write!(f, "Identifier derivation not supported"),
}
}
}
Expand Down
190 changes: 190 additions & 0 deletions pallets/dkg/frost/src/identifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use core::{
fmt::Debug,
hash::{Hash, Hasher},
};

use crate::{
error::{Error, FieldError},
serialization::ScalarSerialization,
traits::{Ciphersuite, Field, Group, Scalar},
util::scalar_is_valid,
};

#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(bound = "C: Ciphersuite")]
#[serde(try_from = "ScalarSerialization<C>")]
#[serde(into = "ScalarSerialization<C>")]
pub struct Identifier<C: Ciphersuite>(Scalar<C>);

impl<C> Identifier<C>
where
C: Ciphersuite,
{
/// Create a new Identifier from a scalar. For internal use only.
fn new(scalar: Scalar<C>) -> Result<Self, Error> {
if scalar == <<C::Group as Group>::Field>::zero() {
Err(FieldError::InvalidZeroScalar.into())
} else {
Ok(Self(scalar))
}
}

/// Derive an Identifier from an arbitrary byte string.
///
/// This feature is not part of the specification and is just a convenient
/// way of creating identifiers.
///
/// Each possible byte string will map to an uniformly random identifier.
/// Returns an error if the ciphersuite does not support identifier derivation,
/// or if the mapped identifier is zero (which is unpredictable, but should happen
/// with negligible probability).
pub fn derive(s: &[u8]) -> Result<Self, Error> {
let scalar = C::HID(s).ok_or(Error::IdentifierDerivationNotSupported)?;
Self::new(scalar)
}

/// Serialize the identifier using the ciphersuite encoding.
pub fn serialize(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
<<C::Group as Group>::Field>::serialize(&self.0)
}

/// Deserialize an Identifier from a serialized buffer.
/// Returns an error if it attempts to deserialize zero.
pub fn deserialize(
buf: &<<C::Group as Group>::Field as Field>::Serialization,
) -> Result<Self, Error> {
let scalar = <<C::Group as Group>::Field>::deserialize(buf)?;
Self::new(scalar)
}

/// Check if the identifier is valid aka not zero
pub fn is_valid(&self) -> bool {
scalar_is_valid::<C>(&self.0)
}
}

impl<C> TryFrom<ScalarSerialization<C>> for Identifier<C>
where
C: Ciphersuite,
{
type Error = Error;

fn try_from(value: ScalarSerialization<C>) -> Result<Self, Self::Error> {
Self::deserialize(&value.0)
}
}

impl<C> From<Identifier<C>> for ScalarSerialization<C>
where
C: Ciphersuite,
{
fn from(value: Identifier<C>) -> Self {
Self(value.serialize())
}
}

impl<C> Eq for Identifier<C> where C: Ciphersuite {}

impl<C> Debug for Identifier<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_tuple("Identifier")
.field(&hex::encode(<<C::Group as Group>::Field>::serialize(&self.0).as_ref()))
.finish()
}
}

#[allow(clippy::derived_hash_with_manual_eq)]
impl<C> Hash for Identifier<C>
where
C: Ciphersuite,
{
fn hash<H: Hasher>(&self, state: &mut H) {
<<C::Group as Group>::Field>::serialize(&self.0).as_ref().hash(state)
}
}

impl<C> Ord for Identifier<C>
where
C: Ciphersuite,
{
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
let serialized_self = <<C::Group as Group>::Field>::little_endian_serialize(&self.0);
let serialized_other = <<C::Group as Group>::Field>::little_endian_serialize(&other.0);
// The default cmp uses lexicographic order; so we need the elements in big endian
serialized_self
.as_ref()
.iter()
.rev()
.cmp(serialized_other.as_ref().iter().rev())
}
}

impl<C> PartialOrd for Identifier<C>
where
C: Ciphersuite,
{
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl<C> core::ops::Mul<Scalar<C>> for Identifier<C>
where
C: Ciphersuite,
{
type Output = Scalar<C>;

fn mul(self, scalar: Scalar<C>) -> Scalar<C> {
self.0 * scalar
}
}

impl<C> core::ops::MulAssign<Identifier<C>> for Scalar<C>
where
C: Ciphersuite,
{
fn mul_assign(&mut self, identifier: Identifier<C>) {
*self = *self * identifier.0
}
}

impl<C> core::ops::Sub for Identifier<C>
where
C: Ciphersuite,
{
type Output = Self;

fn sub(self, rhs: Identifier<C>) -> Self::Output {
Self(self.0 - rhs.0)
}
}

impl<C> TryFrom<u16> for Identifier<C>
where
C: Ciphersuite,
{
type Error = Error;

fn try_from(n: u16) -> Result<Identifier<C>, Self::Error> {
if n == 0 {
Err(FieldError::InvalidZeroScalar.into())
} else {
// Classic left-to-right double-and-add algorithm that skips the first bit 1 (since
// identifiers are never zero, there is always a bit 1), thus `sum` starts with 1 too.
let one = <<C::Group as Group>::Field>::one();
let mut sum = <<C::Group as Group>::Field>::one();

let bits = (n.to_be_bytes().len() as u32) * 8;
for i in (0..(bits - n.leading_zeros() - 1)).rev() {
sum = sum + sum;
if n & (1 << i) != 0 {
sum = sum + one;
}
}
Ok(Self(sum))
}
}
}
148 changes: 148 additions & 0 deletions pallets/dkg/frost/src/keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use core::fmt::Debug;

use crate::{
error::Error,
identifier::Identifier,
serialization::{Deserialize, ElementSerialization, Serialize},
traits::{Ciphersuite, Element, Group},
util::element_is_valid,
verifying_key::VerifyingKey,
Header,
};
use alloc::collections::BTreeMap;
use sp_std::vec::Vec;

/// A public group element that represents a single signer's public verification share.
#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound = "C: Ciphersuite")]
#[serde(try_from = "ElementSerialization<C>")]
#[serde(into = "ElementSerialization<C>")]
pub struct VerifyingShare<C>(pub(super) Element<C>)
where
C: Ciphersuite;

impl<C> VerifyingShare<C>
where
C: Ciphersuite,
{
/// Create a new [`VerifyingShare`] from a element.
pub fn new(element: Element<C>) -> Self {
Self(element)
}

/// Get the inner element.
#[cfg(feature = "internals")]
pub fn to_element(&self) -> Element<C> {
self.0
}

/// Deserialize from bytes
pub fn deserialize(bytes: <C::Group as Group>::Serialization) -> Result<Self, Error> {
<C::Group as Group>::deserialize(&bytes)
.map(|element| Self(element))
.map_err(|e| e.into())
}

/// Serialize to bytes
pub fn serialize(&self) -> <C::Group as Group>::Serialization {
<C::Group as Group>::serialize(&self.0)
}

/// Verifies that a verifying share is valid aka not zero or the base point
pub fn is_valid(&self) -> bool {
element_is_valid::<C>(&self.0)
}
}

impl<C> Debug for VerifyingShare<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_tuple("VerifyingShare").field(&hex::encode(self.serialize())).finish()
}
}

impl<C> TryFrom<ElementSerialization<C>> for VerifyingShare<C>
where
C: Ciphersuite,
{
type Error = Error;

fn try_from(value: ElementSerialization<C>) -> Result<Self, Self::Error> {
Self::deserialize(value.0)
}
}

impl<C> From<VerifyingShare<C>> for ElementSerialization<C>
where
C: Ciphersuite,
{
fn from(value: VerifyingShare<C>) -> Self {
Self(value.serialize())
}
}

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound = "C: Ciphersuite")]
#[serde(deny_unknown_fields)]
pub struct PublicKeyPackage<C: Ciphersuite> {
/// Serialization header
pub header: Header<C>,
/// The verifying shares for all participants. Used to validate signature
/// shares they generate.
pub verifying_shares: BTreeMap<Identifier<C>, VerifyingShare<C>>,
/// The joint public key for the entire group.
pub verifying_key: VerifyingKey<C>,
}

impl<C> PublicKeyPackage<C>
where
C: Ciphersuite,
{
/// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
Serialize::serialize(&self)
}

/// Deserialize the struct from a slice of bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
Deserialize::deserialize(bytes)
}
}

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

#[cfg(feature = "serialization")]
pub(crate) trait Deserialize<C: Ciphersuite> {
/// Deserialize the struct from a slice of bytes.
fn deserialize(bytes: &[u8]) -> Result<Self, Error>
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> {
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> {
postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError)
}
}
Loading

0 comments on commit a2d9ba7

Please sign in to comment.