diff --git a/Cargo.toml b/Cargo.toml index 5c47ea22..565b6d1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ digest = "0.9" jubjub = "0.6" rand_core = "0.6" serde = { version = "1", optional = true, features = ["derive"] } +serde_json = "1.0" thiserror = "1.0" zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } diff --git a/frost-msg-spec/msg_spec.html b/frost-msg-spec/msg_spec.html new file mode 100644 index 00000000..2de4c8bb --- /dev/null +++ b/frost-msg-spec/msg_spec.html @@ -0,0 +1,659 @@ + + + + + + + +FROST Messages & Data Serialization + + + + + + + +
+

FROST Messages & Data Serialization

+
+

Table of Contents

+ +
+ +
+

1 Overview

+
+

+The following document describes the byte-level structure of messages sent in +FROST frost. Each message consists of a header of fixed size followed by +the actual payload of the message. +

+
+
+ +
+

2 Headers

+
+

+All messages have the following header: +

+ + + + +++ ++ ++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BytesField NameData Type
1Message Typeu8
1Versionu8
2Sender IDu16
2Receiver IDu16
+ +

+The Message Type and Version fields specify the payload that follows after +the header. The sender is uniquely identified by the Sender ID 1 field +and the receiver is uniquely identified by the Receiver ID 1 field. +

+
+
+ +
+

3 Payload types

+
+

+Messages in FROST are split into three general domains. The following sections +describe each domain and its messages. +

+
+ +
+

3.1 Key Generation with DKG

+
+

+These messages are sent during the Distributed Key Generation (DKG). +

+
+ +
+

3.1.1 Round One

+
+

+Broadcast the public commitment vector \(\vec{C} = \langle \phi_0, \ldots, +\phi_{t-1} \rangle\) and the proof of knowledge \(\sigma = (R, \mu)\).
+

+ +

+Header:
+Message Type = 1
+Version = 1
+

+ +

+Payload: +

+ + + +++ ++ ++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BytesDescriptionData Type
2Length t of the commitment vectoru16
512 ⋅ tIndividual commitments φj[AffinePoint; t]
256The value \(R\)Scalar
256The value μScalar
+
+
+ +
+

3.1.2 Round Two

+
+

+Broadcast the secret shares \(f(l)\).
+

+ +

+Header:
+Message Type = 2
+Version = 1
+

+ +

+Payload: +

+ + + +++ ++ ++ + + + + + + + + + + + + + + +
BytesDescriptionData Type
256Secret share \(f(l)\)Scalar
+
+
+
+ + +
+

3.2 Key Generation with Dealer

+
+

+…
+

+ +

+Header:
+Message Type = 3
+Version = 1
+

+ +

+Payload: +

+ + + +++ ++ ++ + + + + + + + + + + + + + + +
BytesDescriptionData Type
+
+
+ +
+

3.3 Signing

+
+

+… +

+
+ +
+

3.3.1 Round One

+
+

+Share signing commitments.
+

+ +

+Header:
+Message Type = 4
+Version = 1
+

+ +

+Payload: +

+ + + +++ ++ ++ + + + + + + + + + + + + + + + + + + + + +
BytesDescriptionData Type
2Number of signing commitments nu16
1024 ⋅ nSigning commitments[SigningCommitment; n]
+
+
+ +
+

3.3.2 Round Two

+
+

+…
+

+ +

+Header:
+Message Type = 5
+Version = 1
+

+ +

+Payload: +

+ + + +++ ++ ++ + + + + + + + + + + + + + + +
BytesDescriptionData Type
+
+
+
+
+ +
+

4 Data Types

+
+
+
+

4.1 AffinePoint

+
+ +
+

4.2 Scalar

+
+ +
+

4.3 SigningCommitment

+
+

+\bibliographystyle{plain} +\bibliography{refs} +

+
+
+
+
+

Footnotes:

+
+ +
1

+TODO: Consider other data types such as u32 or u64. +

+ + +
+
+
+

Created: 2021-04-15 Thu 21:57

+

Validate

+
+ + diff --git a/frost-msg-spec/msg_spec.org b/frost-msg-spec/msg_spec.org new file mode 100644 index 00000000..885929bd --- /dev/null +++ b/frost-msg-spec/msg_spec.org @@ -0,0 +1,143 @@ +#+OPTIONS: author:nil + +#+latex_header: \hypersetup{colorlinks=true,linkcolor=blue} + +#+TITLE: FROST Messages & Data Serialization + +* Overview +The following document describes the byte-level structure of messages sent in +FROST cite:frost. Each message consists of a header of fixed size followed by +the actual payload of the message. + +* Headers +All messages have the following header: + +| Bytes | Field Name | Data Type | +|-------+----------------+-----------| +| 1 | =Message Type= | =u8= | +| 1 | =Version= | =u8= | +| 2 | =Sender ID= | =u16= | +| 2 | =Receiver ID= | =u16= | + +The =Message Type= and =Version= fields specify the payload that follows after +the header. The sender is uniquely identified by the =Sender ID= [fn:size] field +and the receiver is uniquely identified by the =Receiver ID= [fn:size] field. + +[fn:size] TODO: Consider other data types such as =u32= or =u64=. + +* Payload types +Messages in FROST are split into three general domains. The following sections +describe each domain and its messages. + +** Key Generation with DKG +These messages are sent during the Distributed Key Generation (DKG). + +*** Round One +Broadcast the public commitment vector $\vec{C} = \langle \phi_0, \ldots, +\phi_{t-1} \rangle$ and the proof of knowledge $\sigma = (R, \mu)$. \\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Header*:\\ +=Message Type = 1=\\ +=Version = 1=\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Payload*: +| Bytes | Description | Data Type | +|---------------+-------------------------------------+--------------------| +| 2 | Length =t= of the commitment vector | =u16= | +| 512 \cdot =t= | Individual commitments \phi_j | =[AffinePoint; t]= | +| 256 | The value $R$ | =Scalar= | +| 256 | The value \mu | =Scalar= | + +*** Round Two +Broadcast the secret shares $f(l)$.\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Header*:\\ +=Message Type = 2=\\ +=Version = 1=\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Payload*: +| Bytes | Description | Data Type | +|-------+-----------------------+-----------| +| 256 | Secret share $f(l)$ | =Scalar= | + + +** Key Generation with Dealer +...\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Header*:\\ +=Message Type = 3=\\ +=Version = 1=\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Payload*: +| Bytes | Description | Data Type | +|-------+-------------+-----------| +| ... | ... | ... | + +** Signing +... + +*** Round One +Share signing commitments.\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Header*:\\ +=Message Type = 4=\\ +=Version = 1=\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Payload*: +| Bytes | Description | Data Type | +|----------------+-----------------------------------+--------------------------| +| 2 | Number of signing commitments =n= | =u16= | +| 1024 \cdot =n= | Signing commitments | =[SigningCommitment; n]= | + +*** Round Two +...\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Header*:\\ +=Message Type = 5=\\ +=Version = 1=\\ + +#+BEGIN_EXPORT latex +\noindent +#+END_EXPORT +*Payload*: +| Bytes | Description | Data Type | +|-------+-------------+-----------| +| ... | ... | ... | + +* Data Types +** =AffinePoint= + +** =Scalar= + +** =SigningCommitment= + + +\bibliographystyle{plain} +\bibliography{refs} diff --git a/frost-msg-spec/msg_spec.pdf b/frost-msg-spec/msg_spec.pdf new file mode 100644 index 00000000..f2885ea9 Binary files /dev/null and b/frost-msg-spec/msg_spec.pdf differ diff --git a/frost-msg-spec/msg_spec.tex b/frost-msg-spec/msg_spec.tex new file mode 100644 index 00000000..708c7308 --- /dev/null +++ b/frost-msg-spec/msg_spec.tex @@ -0,0 +1,186 @@ +% Created 2021-04-15 Thu 21:55 +% Intended LaTeX compiler: pdflatex +\documentclass[11pt]{article} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{graphicx} +\usepackage{grffile} +\usepackage{longtable} +\usepackage{wrapfig} +\usepackage{rotating} +\usepackage[normalem]{ulem} +\usepackage{amsmath} +\usepackage{textcomp} +\usepackage{amssymb} +\usepackage{capt-of} +\usepackage{hyperref} +\hypersetup{colorlinks=true,linkcolor=blue} +\date{\today} +\title{FROST Messages \& Data Serialization} +\hypersetup{ + pdfauthor={m}, + pdftitle={FROST Messages \& Data Serialization}, + pdfkeywords={}, + pdfsubject={}, + pdfcreator={Emacs 27.1 (Org mode 9.4.4)}, + pdflang={English}} +\begin{document} + +\maketitle +\tableofcontents + + +\section{Overview} +\label{sec:org912bf34} +The following document describes the byte-level structure of messages sent in +FROST \cite{frost}. Each message consists of a header of fixed size followed by +the actual payload of the message. + +\section{Headers} +\label{sec:org12fae35} +All messages have the following header: + +\begin{center} +\begin{tabular}{rll} +Bytes & Field Name & Data Type\\ +\hline +1 & \texttt{Message Type} & \texttt{u8}\\ +1 & \texttt{Version} & \texttt{u8}\\ +2 & \texttt{Sender ID} & \texttt{u16}\\ +2 & \texttt{Receiver ID} & \texttt{u16}\\ +\end{tabular} +\end{center} + +The \texttt{Message Type} and \texttt{Version} fields specify the payload that follows after +the header. The sender is uniquely identified by the \texttt{Sender ID} \footnote{TODO: Consider other data types such as \texttt{u32} or \texttt{u64}.\label{org68e5745}} field +and the receiver is uniquely identified by the \texttt{Receiver ID} \textsuperscript{\ref{org68e5745}} field. + +\section{Payload types} +\label{sec:org09c501d} +Messages in FROST are split into three general domains. The following sections +describe each domain and its messages. + +\subsection{Key Generation with DKG} +\label{sec:org9fbd07e} +These messages are sent during the Distributed Key Generation (DKG). + +\subsubsection{Round One} +\label{sec:orgac6c9e9} +Broadcast the public commitment vector \(\vec{C} = \langle \phi_0, \ldots, +\phi_{t-1} \rangle\) and the proof of knowledge \(\sigma = (R, \mu)\). \\ + +\noindent +\textbf{Header}:\\ +\texttt{Message Type = 1}\\ +\texttt{Version = 1}\\ + +\noindent +\textbf{Payload}: +\begin{center} +\begin{tabular}{rll} +Bytes & Description & Data Type\\ +\hline +2 & Length \texttt{t} of the commitment vector & \texttt{u16}\\ +512 \(\cdot\) \texttt{t} & Individual commitments \(\phi\)\textsubscript{j} & \texttt{[AffinePoint; t]}\\ +256 & The value \(R\) & \texttt{Scalar}\\ +256 & The value \(\mu\) & \texttt{Scalar}\\ +\end{tabular} +\end{center} + +\subsubsection{Round Two} +\label{sec:orgf834e96} +Broadcast the secret shares \(f(l)\).\\ + +\noindent +\textbf{Header}:\\ +\texttt{Message Type = 2}\\ +\texttt{Version = 1}\\ + +\noindent +\textbf{Payload}: +\begin{center} +\begin{tabular}{rll} +Bytes & Description & Data Type\\ +\hline +256 & Secret share \(f(l)\) & \texttt{Scalar}\\ +\end{tabular} +\end{center} + + +\subsection{Key Generation with Dealer} +\label{sec:org145cb98} +\ldots{}\\ + +\noindent +\textbf{Header}:\\ +\texttt{Message Type = 3}\\ +\texttt{Version = 1}\\ + +\noindent +\textbf{Payload}: +\begin{center} +\begin{tabular}{lll} +Bytes & Description & Data Type\\ +\hline +\ldots{} & \ldots{} & \ldots{}\\ +\end{tabular} +\end{center} + +\subsection{Signing} +\label{sec:org4282a73} +\ldots{} + +\subsubsection{Round One} +\label{sec:org09bc462} +Share signing commitments.\\ + +\noindent +\textbf{Header}:\\ +\texttt{Message Type = 4}\\ +\texttt{Version = 1}\\ + +\noindent +\textbf{Payload}: +\begin{center} +\begin{tabular}{lll} +Bytes & Description & Data Type\\ +\hline +2 & Number of signing commitments \texttt{n} & \texttt{u16}\\ +1024 \(\cdot\) \texttt{n} & Signing commitments & \texttt{[SigningCommitment; n]}\\ +\end{tabular} +\end{center} + +\subsubsection{Round Two} +\label{sec:org1f878e5} +\ldots{}\\ + +\noindent +\textbf{Header}:\\ +\texttt{Message Type = 5}\\ +\texttt{Version = 1}\\ + +\noindent +\textbf{Payload}: +\begin{center} +\begin{tabular}{lll} +Bytes & Description & Data Type\\ +\hline +\ldots{} & \ldots{} & \ldots{}\\ +\end{tabular} +\end{center} + +\section{Data Types} +\label{sec:org4546f1a} +\subsection{\texttt{AffinePoint}} +\label{sec:org5e766fe} + +\subsection{\texttt{Scalar}} +\label{sec:orgac14fc4} + +\subsection{\texttt{SigningCommitment}} +\label{sec:orge2d2bee} + + +\bibliographystyle{plain} +\bibliography{refs} +\end{document} \ No newline at end of file diff --git a/frost-msg-spec/refs.bib b/frost-msg-spec/refs.bib new file mode 100644 index 00000000..38a6dba1 --- /dev/null +++ b/frost-msg-spec/refs.bib @@ -0,0 +1,7 @@ +@misc{frost, + author = {Chelsea Komlo and Ian Goldberg}, + title = {FROST: Flexible Round-Optimized Schnorr Threshold Signatures}, + howpublished = {Cryptology ePrint Archive, Report 2020/852}, + year = {2020}, + note = {\url{https://eprint.iacr.org/2020/852}}, +} \ No newline at end of file diff --git a/src/frost.rs b/src/frost.rs index 28a44700..7508abbd 100644 --- a/src/frost.rs +++ b/src/frost.rs @@ -32,27 +32,102 @@ use zeroize::DefaultIsZeroes; use crate::private::Sealed; use crate::{HStar, Signature, SpendAuth, VerificationKey}; +/// A serializable & deserializable Scalar in RedJubJub. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "[u8; 32]"))] +#[cfg_attr(feature = "serde", serde(into = "[u8; 32]"))] +pub struct SerializableScalar(jubjub::Scalar); + +impl From for [u8; 32] { + fn from(ss: SerializableScalar) -> [u8; 32] { + ss.0.to_bytes() + } +} + +impl TryFrom<[u8; 32]> for SerializableScalar { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + // This checks that the encoding is canonical. + let maybe_point = jubjub::Scalar::from_bytes(&bytes); + if maybe_point.is_some().into() { + let scalar: jubjub::Scalar = maybe_point.unwrap().into(); + Ok(SerializableScalar(scalar)) + } else { + Err("Invalid bytes.") + } + } +} + +impl From for SerializableScalar { + fn from(source: jubjub::Scalar) -> SerializableScalar { + SerializableScalar(source) + } +} /// A secret scalar value representing a single signer's secret key. -#[derive(Clone, Copy, Default)] -pub struct Secret(Scalar); +#[derive(Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Secret(SerializableScalar); // Zeroizes `Secret` to be the `Default` value on drop (when it goes out of // scope). Luckily the derived `Default` includes the `Default` impl of // jubjub::Fr/Scalar, which is four 0u64's under the hood. impl DefaultIsZeroes for Secret {} -impl From for Secret { - fn from(source: Scalar) -> Secret { +impl From for Secret { + fn from(source: SerializableScalar) -> Secret { Secret(source) } } +/// A serializable & deserializable ExtendedPoint in RedJubJub. +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "[u8; 32]"))] +#[cfg_attr(feature = "serde", serde(into = "[u8; 32]"))] +pub struct SerializablePoint(jubjub::ExtendedPoint); + +impl From for [u8; 32] { + fn from(sp: SerializablePoint) -> [u8; 32] { + jubjub::AffinePoint::from(sp.0).to_bytes() + } +} + +impl TryFrom<[u8; 32]> for SerializablePoint { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + // XXX-jubjub: this should not use CtOption + // XXX-jubjub: this takes ownership of bytes, while Fr doesn't. + // This checks that the encoding is canonical... + let maybe_point = jubjub::AffinePoint::from_bytes(bytes); + if maybe_point.is_some().into() { + let point: jubjub::ExtendedPoint = maybe_point.unwrap().into(); + if ::from(point.is_small_order()) == false { + Ok(SerializablePoint(point)) + } else { + Err("Invalid bytes.") + } + } else { + Err("Invalid bytes.") + } + } +} + +impl From for SerializablePoint { + fn from(source: jubjub::ExtendedPoint) -> SerializablePoint { + SerializablePoint(source) + } +} + /// A public group element that represents a single signer's public key. #[derive(Copy, Clone, Debug, PartialEq)] -pub struct Public(jubjub::ExtendedPoint); +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Public(SerializablePoint); -impl From for Public { - fn from(source: jubjub::ExtendedPoint) -> Public { +impl From for Public { + fn from(source: SerializablePoint) -> Public { Public(source) } } @@ -60,7 +135,8 @@ impl From for Public { /// A share generated by performing a (t-out-of-n) secret sharing scheme where /// n is the total number of shares and t is the threshold required to /// reconstruct the secret; in this case we use Shamir's secret sharing. -#[derive(Clone)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Share { receiver_index: u8, value: Secret, @@ -72,8 +148,9 @@ pub struct Share { /// /// This is a (public) commitment to one coefficient of a secret polynomial used /// for performing verifiable secret sharing for a Shamir secret share. -#[derive(Clone)] -struct Commitment(jubjub::ExtendedPoint); +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct Commitment(SerializablePoint); /// Contains the commitments to the coefficients for our secret polynomial _f_, /// used to generate participants' key shares. @@ -87,7 +164,8 @@ struct Commitment(jubjub::ExtendedPoint); /// [`ShareCommitment`], either by performing pairwise comparison, or by using /// some agreed-upon public location for publication, where each participant can /// ensure that they received the correct (and same) value. -#[derive(Clone)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ShareCommitment(Vec); /// The product of all signers' individual commitments, published as part of the @@ -99,6 +177,8 @@ pub struct GroupCommitment(jubjub::ExtendedPoint); /// /// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call /// .into(), which under the hood also performs validation. +#[derive(PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SharePackage { /// Denotes the participant index each share is owned by. We implicitly /// restrict the number of participants to 255. @@ -176,14 +256,14 @@ pub fn keygen_with_dealer( let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes); - let secret = Secret(Scalar::from_bytes_wide(&bytes)); - let group_public = VerificationKey::from(&secret.0); + let secret = Secret(SerializableScalar(Scalar::from_bytes_wide(&bytes))); + let group_public = VerificationKey::from(&secret.0 .0); let shares = generate_shares(&secret, num_signers, threshold, rng)?; let mut sharepackages: Vec = Vec::with_capacity(num_signers as usize); let mut signer_pubkeys: HashMap = HashMap::with_capacity(num_signers as usize); for share in shares { - let signer_public = Public(SpendAuth::basepoint() * share.value.0); + let signer_public = Public(SerializablePoint(SpendAuth::basepoint() * share.value.0 .0)); sharepackages.push(SharePackage { index: share.receiver_index, share: share.clone(), @@ -210,13 +290,13 @@ pub fn keygen_with_dealer( /// ensure that they have the same view as all other participants of the /// commitment! fn verify_share(share: &Share) -> Result<(), &'static str> { - let f_result = SpendAuth::basepoint() * share.value.0; + let f_result = SpendAuth::basepoint() * share.value.0 .0; let x = Scalar::from(share.receiver_index as u64); let (_, result) = share.commitment.0.iter().fold( (Scalar::one(), jubjub::ExtendedPoint::identity()), - |(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i), + |(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 .0 * x_to_the_i), ); if !(f_result == result) { @@ -275,12 +355,14 @@ fn generate_shares( // Verifiable secret sharing, to make sure that participants can ensure their secret is consistent // with every other participant's. - commitment - .0 - .push(Commitment(SpendAuth::basepoint() * secret.0)); + commitment.0.push(Commitment(SerializablePoint( + SpendAuth::basepoint() * secret.0 .0, + ))); for c in &coefficients { - commitment.0.push(Commitment(SpendAuth::basepoint() * c)); + commitment + .0 + .push(Commitment(SerializablePoint(SpendAuth::basepoint() * c))); } // Evaluate the polynomial with `secret` as the constant term @@ -295,11 +377,11 @@ fn generate_shares( value += &coefficients[i as usize]; value *= scalar_index; } - value += secret.0; + value += secret.0 .0; shares.push(Share { receiver_index: index, - value: Secret(value), + value: Secret(SerializableScalar(value)), commitment: commitment.clone(), }); } @@ -413,7 +495,7 @@ impl SignatureShare { challenge: Scalar, ) -> Result<(), &'static str> { if (SpendAuth::basepoint() * self.signature) - != (commitment + pubkey.0 * challenge * lambda_i) + != (commitment + pubkey.0 .0 * challenge * lambda_i) { return Err("Invalid signature share"); } @@ -579,7 +661,7 @@ pub fn sign( // The Schnorr signature share let signature: Scalar = participant_nonces.hiding + (participant_nonces.binding * participant_rho_i) - + (lambda_i * share_package.share.value.0 * challenge); + + (lambda_i * share_package.share.value.0 .0 * challenge); Ok(SignatureShare { index: share_package.index, @@ -682,7 +764,7 @@ mod tests { let mut secret = Scalar::zero(); for i in 0..numshares { - secret += lagrange_coeffs[i] * shares[i].value.0; + secret += lagrange_coeffs[i] * shares[i].value.0 .0; } Ok(secret) @@ -696,9 +778,9 @@ mod tests { let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes); - let secret = Secret(Scalar::from_bytes_wide(&bytes)); + let secret = Secret(SerializableScalar(Scalar::from_bytes_wide(&bytes))); - let _ = SpendAuth::basepoint() * secret.0; + let _ = SpendAuth::basepoint() * secret.0 .0; let shares = generate_shares(&secret, 5, 3, rng).unwrap(); @@ -706,6 +788,6 @@ mod tests { assert_eq!(verify_share(&share), Ok(())); } - assert_eq!(reconstruct_secret(shares).unwrap(), secret.0) + assert_eq!(reconstruct_secret(shares).unwrap(), secret.0 .0) } } diff --git a/src/verification_key.rs b/src/verification_key.rs index 80d4e523..813e5820 100644 --- a/src/verification_key.rs +++ b/src/verification_key.rs @@ -66,7 +66,7 @@ impl Hash for VerificationKeyBytes { /// /// 1. The check that the bytes are a canonical encoding of a verification key; /// 2. The check that the verification key is not a point of small order. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] #[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] diff --git a/tests/frost.rs b/tests/frost.rs index 678d743c..29ff9eed 100644 --- a/tests/frost.rs +++ b/tests/frost.rs @@ -54,9 +54,26 @@ fn check_sign_with_dealer() { // Check that the threshold signature can be verified by the group public // key (aka verification key). assert!(pubkeys - .group_public - .verify(&message, &group_signature) - .is_ok()); + .group_public + .verify(&message, &group_signature) + .is_ok()); // TODO: also check that the SharePackage.group_public also verifies the group signature. } + +#[test] +fn check_serde() { + // Generate initial data. + let mut rng = thread_rng(); + let numsigners = 3; + let threshold = 2; + let (shares, _pubkeys) = frost::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap(); + + // Check the serialization & deserialization of share packages. + for share in shares { + let ser_share_package = serde_json::to_string(&share).unwrap(); + let deser_share_package: frost::SharePackage = + serde_json::from_str(&ser_share_package).unwrap(); + assert!(share == deser_share_package); + } +}