From 47121537e8541bc83e580b52c7ad628bce474397 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 23 Jun 2023 06:58:22 -0300 Subject: [PATCH] Support serde (#398) * Implement serde for network messages. * Make sure marker type implements serde. * add serde support to all required structs * use serdect * gate under serde feature * ci: add build with default features job * add serde tests and required changes/fixes * add support for encoding ciphersuite ID --------- Co-authored-by: David Craven --- .github/workflows/main.yml | 16 +- frost-core/CHANGELOG.md | 6 + frost-core/Cargo.toml | 3 + frost-core/src/benches.rs | 2 +- frost-core/src/frost.rs | 28 +- frost-core/src/frost/identifier.rs | 31 +- frost-core/src/frost/keys.rs | 174 +++++- frost-core/src/frost/keys/dkg.rs | 71 ++- frost-core/src/frost/keys/repairable.rs | 1 + frost-core/src/frost/round1.rs | 59 +- frost-core/src/frost/round2.rs | 50 ++ frost-core/src/lib.rs | 102 +++ frost-core/src/signature.rs | 36 ++ frost-core/src/tests/ciphersuite_generic.rs | 7 +- frost-core/src/tests/repairable.rs | 8 +- frost-core/src/tests/vectors.rs | 36 +- frost-core/src/verifying_key.rs | 30 +- frost-ed25519/README.md | 2 +- frost-ed25519/src/lib.rs | 2 + frost-ed448/README.md | 2 +- frost-ed448/src/lib.rs | 2 + frost-p256/README.md | 2 +- frost-p256/src/lib.rs | 2 + frost-rerandomized/src/tests.rs | 2 +- frost-ristretto255/Cargo.toml | 2 + frost-ristretto255/README.md | 2 +- frost-ristretto255/src/lib.rs | 7 + frost-ristretto255/tests/serde_tests.rs | 651 ++++++++++++++++++++ frost-secp256k1/Cargo.toml | 1 + frost-secp256k1/README.md | 2 +- frost-secp256k1/src/lib.rs | 5 +- 31 files changed, 1289 insertions(+), 55 deletions(-) create mode 100644 frost-ristretto255/tests/serde_tests.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 43b31885..17901b68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,14 +10,26 @@ on: jobs: + build_default: + name: build with default features + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3.5.3 + - uses: actions-rs/toolchain@v1.0.7 + with: + toolchain: beta + override: true + - uses: actions-rs/cargo@v1.0.3 + with: + command: build + test_beta: name: test on beta runs-on: ubuntu-latest steps: - uses: actions/checkout@v3.5.3 - # Because we use nightly features for building docs, - # using --all-features will fail without nightly toolchain. - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index c76ee829..aba0ed67 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -7,6 +7,12 @@ Entries are listed in reverse chronological order. ## 0.5.0 * add SigningShare type to ciphersuite libraries +* most structs now have a private field which mean that they can no longer be + instantiated directly. `new()` methods have been added to them. +* change `SigningPackage::new()` to take `&[u8]P instead of `Vec` +* expose `NonceCommitment` and `SignatureResponse` in ciphersuite crates +* add `serde` support under `serde` feature to allow encoding structs which + need to be communicated between participants. ## Released diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index dc61ff32..69bc508a 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -22,6 +22,8 @@ debugless-unwrap = "0.0.4" digest = "0.10" hex = { version = "0.4.3", features = ["serde"] } rand_core = "0.6" +serde = { version = "1.0.160", features = ["derive"], optional = true } +serdect = { version = "0.2.0", optional = true } thiserror = "1.0" visibility = "0.0.1" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } @@ -46,6 +48,7 @@ sha2 = "0.10.2" nightly = [] default = [] internals = [] +serde = ["dep:serde", "dep:serdect"] # Exposes ciphersuite-generic tests for other crates to use test-impl = ["proptest", "proptest-derive", "serde_json", "criterion"] diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index 8eb6a023..4a4da460 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -146,7 +146,7 @@ pub fn bench_sign( let message = "message to sign".as_bytes(); let comms = commitments.clone().into_values().collect(); - let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + let signing_package = frost::SigningPackage::new(comms, message); group.bench_with_input( BenchmarkId::new("Round 2", min_signers), diff --git a/frost-core/src/frost.rs b/frost-core/src/frost.rs index cd39ba8c..b570a738 100644 --- a/frost-core/src/frost.rs +++ b/frost-core/src/frost.rs @@ -183,7 +183,9 @@ fn derive_interpolating_value( /// Generated by the coordinator of the signing operation and distributed to /// each signing party -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. @@ -192,26 +194,44 @@ pub struct SigningPackage { /// /// Each signer should perform protocol-specific verification on the /// message. + #[cfg_attr( + feature = "serde", + serde( + serialize_with = "serdect::slice::serialize_hex_lower_or_bin", + deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" + ) + )] message: Vec, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + ciphersuite: (), } impl SigningPackage where C: Ciphersuite, { - /// Create a new `SigingPackage` + /// Create a new `SigningPackage` /// /// The `signing_commitments` are sorted by participant `identifier`. pub fn new( signing_commitments: Vec>, - message: Vec, + message: &[u8], ) -> SigningPackage { SigningPackage { signing_commitments: signing_commitments .into_iter() .map(|s| (s.identifier, s)) .collect(), - message, + message: message.to_vec(), + ciphersuite: (), } } diff --git a/frost-core/src/frost/identifier.rs b/frost-core/src/frost/identifier.rs index 4b16eb5e..51dff025 100644 --- a/frost-core/src/frost/identifier.rs +++ b/frost-core/src/frost/identifier.rs @@ -7,12 +7,18 @@ use std::{ use crate::{Ciphersuite, Error, Field, FieldError, Group, Scalar}; +#[cfg(feature = "serde")] +use crate::ScalarSerialization; + /// A FROST participant identifier. /// /// The identifier is a field element in the scalar field that the secret polynomial is defined /// over, corresponding to some x-coordinate for a polynomial f(x) = y. MUST NOT be zero in the /// field, as f(0) = the shared secret. #[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] pub struct Identifier(Scalar); impl Identifier @@ -20,8 +26,7 @@ where C: Ciphersuite, { /// Serialize the identifier using the ciphersuite encoding. - #[cfg_attr(feature = "internals", visibility::make(pub))] - pub(crate) fn serialize(&self) -> <::Field as Field>::Serialization { + pub fn serialize(&self) -> <::Field as Field>::Serialization { <::Field>::serialize(&self.0) } @@ -39,6 +44,28 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for Identifier +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ScalarSerialization) -> Result { + Self::deserialize(&value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ScalarSerialization +where + C: Ciphersuite, +{ + fn from(value: Identifier) -> Self { + Self(value.serialize()) + } +} + impl Eq for Identifier where C: Ciphersuite {} impl Debug for Identifier diff --git a/frost-core/src/frost/keys.rs b/frost-core/src/frost/keys.rs index ebf92989..3d128fef 100644 --- a/frost-core/src/frost/keys.rs +++ b/frost-core/src/frost/keys.rs @@ -19,6 +19,9 @@ use crate::{ frost::Identifier, Ciphersuite, Element, Error, Field, Group, Scalar, SigningKey, VerifyingKey, }; +#[cfg(feature = "serde")] +use crate::{ElementSerialization, ScalarSerialization}; + pub mod dkg; pub mod repairable; @@ -34,6 +37,9 @@ pub(crate) fn generate_coefficients( /// A secret scalar value representing a signer's share of the group secret. #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] pub struct SigningShare(pub(crate) Scalar); impl SigningShare @@ -94,8 +100,33 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for SigningShare +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ScalarSerialization) -> Result { + Self::from_bytes(value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ScalarSerialization +where + C: Ciphersuite, +{ + fn from(value: SigningShare) -> Self { + Self(value.to_bytes()) + } +} + /// A public group element that represents a single signer's public verification share. #[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] pub struct VerifyingShare(pub(super) Element) where C: Ciphersuite; @@ -137,11 +168,36 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for VerifyingShare +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ElementSerialization) -> Result { + Self::from_bytes(value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ElementSerialization +where + C: Ciphersuite, +{ + fn from(value: VerifyingShare) -> Self { + Self(value.to_bytes()) + } +} + /// A [`Group::Element`] newtype that is a commitment to one coefficient of our secret polynomial. /// /// 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, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] pub struct CoefficientCommitment(pub(crate) Element); impl CoefficientCommitment @@ -166,6 +222,28 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for CoefficientCommitment +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ElementSerialization) -> Result { + Self::deserialize(value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ElementSerialization +where + C: Ciphersuite, +{ + fn from(value: CoefficientCommitment) -> Self { + Self(value.serialize()) + } +} + /// Contains the commitments to the coefficients for our secret polynomial _f_, /// used to generate participants' key shares. /// @@ -178,7 +256,8 @@ where /// [`VerifiableSecretSharingCommitment`], 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, PartialEq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct VerifiableSecretSharingCommitment( pub(crate) Vec>, ); @@ -219,7 +298,9 @@ where /// /// To derive a FROST keypair, the receiver of the [`SecretShare`] *must* call /// .into(), which under the hood also performs validation. -#[derive(Clone, Zeroize)] +#[derive(Clone, Zeroize, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SecretShare { /// The participant identifier of this [`SecretShare`]. #[zeroize(skip)] @@ -229,12 +310,36 @@ pub struct SecretShare { #[zeroize(skip)] /// The commitments to be distributed among signers. pub commitment: VerifiableSecretSharingCommitment, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + ciphersuite: (), } impl SecretShare where C: Ciphersuite, { + /// Create a new [`SecretShare`] instance. + pub fn new( + identifier: Identifier, + value: SigningShare, + commitment: VerifiableSecretSharingCommitment, + ) -> Self { + SecretShare { + identifier, + value, + commitment, + ciphersuite: (), + } + } + /// Gets the inner [`SigningShare`] value. pub fn secret(&self) -> &SigningShare { &self.value @@ -332,6 +437,7 @@ pub fn split( PublicKeyPackage { signer_pubkeys, group_public, + ciphersuite: (), }, )) } @@ -379,7 +485,9 @@ fn evaluate_vss( /// When using a central dealer, [`SecretShare`]s are distributed to /// participants, who then perform verification, before deriving /// [`KeyPackage`]s, which they store to later use during signing. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct KeyPackage { /// Denotes the participant identifier each secret share key package is owned by. pub identifier: Identifier, @@ -389,12 +497,38 @@ pub struct KeyPackage { pub public: VerifyingShare, /// The public signing key that represents the entire group. pub group_public: VerifyingKey, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + ciphersuite: (), } impl KeyPackage where C: Ciphersuite, { + /// Create a new [`KeyPackage`] instance. + pub fn new( + identifier: Identifier, + secret_share: SigningShare, + public: VerifyingShare, + group_public: VerifyingKey, + ) -> Self { + Self { + identifier, + secret_share, + public, + group_public, + ciphersuite: (), + } + } + /// Gets the participant identifier associated with this [`KeyPackage`]. pub fn identifier(&self) -> &Identifier { &self.identifier @@ -438,6 +572,7 @@ where secret_share: secret_share.value, public, group_public, + ciphersuite: (), }) } } @@ -446,6 +581,9 @@ where /// group public key. /// /// Used for verification purposes before publishing a signature. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct PublicKeyPackage { /// When performing signing, the coordinator must ensure that they have the /// correct view of participants' public keys to perform verification before @@ -454,6 +592,33 @@ pub struct PublicKeyPackage { pub signer_pubkeys: HashMap, VerifyingShare>, /// The joint public key for the entire group. pub group_public: VerifyingKey, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + ciphersuite: (), +} + +impl PublicKeyPackage +where + C: Ciphersuite, +{ + /// Create a new [`PublicKeyPackage`] instance. + pub fn new( + signer_pubkeys: HashMap, VerifyingShare>, + group_public: VerifyingKey, + ) -> Self { + Self { + signer_pubkeys, + group_public, + ciphersuite: (), + } + } } /// Generate a secret polynomial to use in secret sharing, for the given @@ -538,6 +703,7 @@ pub(crate) fn generate_secret_shares( identifier: id, value: SigningShare(value), commitment: commitment.clone(), + ciphersuite: (), }); } diff --git a/frost-core/src/frost/keys/dkg.rs b/frost-core/src/frost/keys/dkg.rs index 5754692c..668401b0 100644 --- a/frost-core/src/frost/keys/dkg.rs +++ b/frost-core/src/frost/keys/dkg.rs @@ -51,7 +51,9 @@ pub mod round1 { /// The package that must be broadcast by each participant to all other participants /// between the first and second parts of the DKG protocol (round 1). - #[derive(Clone)] + #[derive(Clone, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Package { /// The identifier of the participant who is sending the package (i). pub sender_identifier: Identifier, @@ -59,6 +61,35 @@ pub mod round1 { pub commitment: VerifiableSecretSharingCommitment, /// The proof of knowledge of the temporary secret (σ_i = (R_i, μ_i)) pub proof_of_knowledge: Signature, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + pub(super) ciphersuite: (), + } + + impl Package + where + C: Ciphersuite, + { + /// Create a new [`Package`] instance. + pub fn new( + sender_identifier: Identifier, + commitment: VerifiableSecretSharingCommitment, + proof_of_knowledge: Signature, + ) -> Self { + Self { + sender_identifier, + commitment, + proof_of_knowledge, + ciphersuite: (), + } + } } /// The secret package that must be kept in memory by the participant @@ -92,7 +123,9 @@ pub mod round2 { /// # Security /// /// The package must be sent on an *confidential* and *authenticated* channel. - #[derive(Clone)] + #[derive(Clone, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Package { /// The identifier of the participant that generated the package (i). pub sender_identifier: Identifier, @@ -100,6 +133,35 @@ pub mod round2 { pub receiver_identifier: Identifier, /// The secret share being sent. pub secret_share: SigningShare, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + pub(super) ciphersuite: (), + } + + impl Package + where + C: Ciphersuite, + { + /// Create a new [`Package`] instance. + pub fn new( + sender_identifier: Identifier, + receiver_identifier: Identifier, + secret_share: SigningShare, + ) -> Self { + Self { + sender_identifier, + receiver_identifier, + secret_share, + ciphersuite: (), + } + } } /// The secret package that must be kept in memory by the participant @@ -168,6 +230,7 @@ pub fn part1( sender_identifier: identifier, commitment, proof_of_knowledge: Signature { R: R_i, z: mu_i }, + ciphersuite: (), }; Ok((secret_package, package)) @@ -235,6 +298,7 @@ pub fn part2( sender_identifier: secret_package.identifier, receiver_identifier: ell, secret_share: SigningShare(value), + ciphersuite: (), }); } let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients); @@ -346,6 +410,7 @@ pub fn part3( identifier: round2_secret_package.identifier, value: f_ell_i, commitment: commitment.clone(), + ciphersuite: (), }; // Verify the share. We don't need the result. @@ -390,10 +455,12 @@ pub fn part3( secret_share: signing_share, public: verifying_key, group_public, + ciphersuite: (), }; let public_key_package = PublicKeyPackage { signer_pubkeys: all_verifying_keys, group_public, + ciphersuite: (), }; Ok((key_package, public_key_package)) diff --git a/frost-core/src/frost/keys/repairable.rs b/frost-core/src/frost/keys/repairable.rs index 4732c9c3..fb8a9c2b 100644 --- a/frost-core/src/frost/keys/repairable.rs +++ b/frost-core/src/frost/keys/repairable.rs @@ -129,5 +129,6 @@ pub fn repair_share_step_3( identifier, value: SigningShare(share), commitment: commitment.clone(), + ciphersuite: (), } } diff --git a/frost-core/src/frost/round1.rs b/frost-core/src/frost/round1.rs index 5703339a..a0e89115 100644 --- a/frost-core/src/frost/round1.rs +++ b/frost-core/src/frost/round1.rs @@ -10,6 +10,9 @@ use zeroize::Zeroize; use crate::{frost, Ciphersuite, Element, Error, Field, Group, Scalar}; +#[cfg(feature = "serde")] +use crate::ElementSerialization; + use super::{keys::SigningShare, Identifier}; /// A scalar that is a signing nonce. @@ -106,7 +109,10 @@ where } /// A Ristretto point that is a commitment to a signing nonce share. -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] pub struct NonceCommitment(pub(super) Element); impl NonceCommitment @@ -126,6 +132,28 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for NonceCommitment +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ElementSerialization) -> Result { + Self::from_bytes(value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ElementSerialization +where + C: Ciphersuite, +{ + fn from(value: NonceCommitment) -> Self { + Self(value.to_bytes()) + } +} + impl Debug for NonceCommitment where C: Ciphersuite, @@ -219,7 +247,9 @@ where /// /// This step can be batched if desired by the implementation. Each /// SigningCommitment can be used for exactly *one* signature. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SigningCommitments { /// The participant identifier. pub identifier: Identifier, @@ -227,12 +257,36 @@ pub struct SigningCommitments { pub hiding: NonceCommitment, /// Commitment to the binding [`Nonce`]. pub binding: NonceCommitment, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + ciphersuite: (), } impl SigningCommitments where C: Ciphersuite, { + /// Create new SigningCommitments + pub fn new( + identifier: Identifier, + hiding: NonceCommitment, + binding: NonceCommitment, + ) -> Self { + Self { + identifier, + hiding, + binding, + ciphersuite: (), + } + } + /// Computes the [signature commitment share] from these round one signing commitments. /// /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#name-signature-share-verificatio @@ -264,6 +318,7 @@ where identifier, hiding: nonces.hiding.clone().into(), binding: nonces.binding.clone().into(), + ciphersuite: (), } } } diff --git a/frost-core/src/frost/round2.rs b/frost-core/src/frost/round2.rs index 87ac1a4c..55c24ee0 100644 --- a/frost-core/src/frost/round2.rs +++ b/frost-core/src/frost/round2.rs @@ -8,8 +8,14 @@ use crate::{ Challenge, Ciphersuite, Error, Field, Group, }; +#[cfg(feature = "serde")] +use crate::ScalarSerialization; + /// A representation of a single signature share used in FROST structures and messages. #[derive(Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] pub struct SignatureResponse { /// The scalar contribution to the group signature. pub z_share: Scalar, @@ -34,6 +40,28 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for SignatureResponse +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ScalarSerialization) -> Result { + Self::from_bytes(value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ScalarSerialization +where + C: Ciphersuite, +{ + fn from(value: SignatureResponse) -> Self { + Self(value.to_bytes()) + } +} + impl Debug for SignatureResponse where C: Ciphersuite, @@ -60,17 +88,38 @@ where /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. #[derive(Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SignatureShare { /// Represents the participant identifier. pub identifier: Identifier, /// This participant's signature over the message. pub signature: SignatureResponse, + /// Ciphersuite ID for serialization + #[cfg_attr( + feature = "serde", + serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") + )] + ciphersuite: (), } impl SignatureShare where C: Ciphersuite, { + /// Create a new [`SignatureShare`]. + pub fn new(identifier: Identifier, signature: SignatureResponse) -> Self { + Self { + identifier, + signature, + ciphersuite: (), + } + } + /// Gets the participant identifier associated with this [`SignatureShare`]. pub fn identifier(&self) -> &Identifier { &self.identifier @@ -129,6 +178,7 @@ fn compute_signature_share( SignatureShare:: { identifier: *key_package.identifier(), signature: SignatureResponse:: { z_share }, + ciphersuite: (), } } diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 8d4c4f00..7529b01e 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -13,6 +13,10 @@ use std::{ use rand_core::{CryptoRng, RngCore}; +// Re-export serde +#[cfg(feature = "serde")] +pub use serde; + pub mod batch; #[cfg(any(test, feature = "test-impl"))] pub mod benches; @@ -91,6 +95,41 @@ pub trait Field: Copy + Clone { /// An element of the [`Ciphersuite`] `C`'s [`Group`]'s scalar [`Field`]. pub type Scalar = <<::Group as Group>::Field as Field>::Scalar; +#[cfg(feature = "serde")] +pub(crate) struct ScalarSerialization( + <<::Group as Group>::Field as Field>::Serialization, +); + +#[cfg(feature = "serde")] +impl serde::Serialize for ScalarSerialization +where + C: Ciphersuite, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serdect::slice::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> serde::Deserialize<'de> for ScalarSerialization +where + C: Ciphersuite, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes = serdect::slice::deserialize_hex_or_bin_vec(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. /// @@ -154,11 +193,49 @@ pub trait Group: Copy + Clone + PartialEq { /// An element of the [`Ciphersuite`] `C`'s [`Group`]. pub type Element = <::Group as Group>::Element; +#[cfg(feature = "serde")] +pub(crate) struct ElementSerialization( + <::Group as Group>::Serialization, +); + +#[cfg(feature = "serde")] +impl serde::Serialize for ElementSerialization +where + C: Ciphersuite, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serdect::slice::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> serde::Deserialize<'de> for ElementSerialization +where + C: Ciphersuite, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes = serdect::slice::deserialize_hex_or_bin_vec(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-11.html#name-ciphersuites pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { + /// The ciphersuite ID string + const ID: &'static str; + /// The prime order group (or subgroup) that this ciphersuite operates over. type Group: Group; @@ -308,3 +385,28 @@ pub(crate) fn random_nonzero(rng: &mut R } } } + +/// Serialize a placeholder ciphersuite field with the ciphersuite ID string. +#[cfg(feature = "serde")] +pub(crate) fn ciphersuite_serialize(_: &(), s: S) -> Result +where + S: serde::Serializer, + C: Ciphersuite, +{ + s.serialize_str(C::ID) +} + +/// 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, +{ + let s: &str = serde::de::Deserialize::deserialize(deserializer)?; + if s != C::ID { + Err(serde::de::Error::custom("wrong ciphersuite")) + } else { + Ok(()) + } +} diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 32a55855..905814ab 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -71,6 +71,42 @@ where } } +#[cfg(feature = "serde")] +impl serde::Serialize for Signature +where + C: Ciphersuite, + C::Group: Group, + ::Field: Field, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serdect::slice::serialize_hex_lower_or_bin(&self.to_bytes().as_ref(), serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> serde::Deserialize<'de> for Signature +where + C: Ciphersuite, + C::Group: Group, + ::Field: Field, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?; + let array = bytes + .try_into() + .map_err(|_| serde::de::Error::custom("invalid byte length"))?; + let identifier = Signature::from_bytes(array) + .map_err(|err| serde::de::Error::custom(format!("{err}")))?; + Ok(identifier) + } +} + impl std::fmt::Debug for Signature { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("Signature") diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index ba1f9ac7..bf4d6629 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -114,7 +114,7 @@ fn check_sign( let mut signature_shares = Vec::new(); let message = "message to sign".as_bytes(); let comms = commitments_map.clone().into_values().collect(); - let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + let signing_package = frost::SigningPackage::new(comms, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -315,10 +315,7 @@ where assert!(verifying_keys_for_participant.signer_pubkeys == verifying_keys); } - let pubkeys = frost::keys::PublicKeyPackage { - signer_pubkeys: verifying_keys, - group_public: group_public.unwrap(), - }; + let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, group_public.unwrap()); // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys) diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index 0aecbb60..45c93d7e 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -180,11 +180,11 @@ pub fn check_repair_share_step_3( let actual_sigma: <<::Group as Group>::Field as Field>::Scalar = generate_scalar_from_byte_string::(sigmas["sigma_sum"].as_str().unwrap()); - let actual: SecretShare = SecretShare { - identifier: Identifier::try_from(2).unwrap(), - value: SigningShare(actual_sigma), + let actual: SecretShare = SecretShare::new( + Identifier::try_from(2).unwrap(), + SigningShare(actual_sigma), commitment, - }; + ); assert!(actual.value == expected.value); } diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index 9bde1ab3..3e6be17b 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -66,12 +66,12 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors { - identifier: u16::from_str(i).unwrap().try_into().unwrap(), - secret_share: secret, - public: signer_public, + let key_package = KeyPackage::::new( + u16::from_str(i).unwrap().try_into().unwrap(), + secret, + signer_public, group_public, - }; + ); key_packages.insert(*key_package.identifier(), key_package); } @@ -109,15 +109,12 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors { + let signing_commitments = SigningCommitments::::new( identifier, - hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap()) + NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap()).unwrap(), + NonceCommitment::from_hex(signer["binding_nonce_commitment"].as_str().unwrap()) .unwrap(), - binding: NonceCommitment::from_hex( - signer["binding_nonce_commitment"].as_str().unwrap(), - ) - .unwrap(), - }; + ); signer_commitments.insert(identifier, signing_commitments); @@ -148,12 +145,12 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors { - identifier: u16::from_str(i).unwrap().try_into().unwrap(), - signature: SignatureResponse { + let signature_share = SignatureShare::::new( + u16::from_str(i).unwrap().try_into().unwrap(), + SignatureResponse { z_share: <::Field>::deserialize(&sig_share).unwrap(), }, - }; + ); signature_shares.insert( u16::from_str(i).unwrap().try_into().unwrap(), @@ -275,7 +272,7 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { let signer_commitments_vec = signer_commitments.into_values().collect(); - let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes); + let signing_package = frost::SigningPackage::new(signer_commitments_vec, &message_bytes); for (identifier, input) in signing_package.binding_factor_preimages(&[]).iter() { assert_eq!(*input, binding_factor_inputs[identifier]); @@ -310,10 +307,7 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { .map(|(i, key_package)| (i, *key_package.public())) .collect(); - let pubkey_package = frost::keys::PublicKeyPackage { - signer_pubkeys, - group_public, - }; + let pubkey_package = frost::keys::PublicKeyPackage::new(signer_pubkeys, group_public); //////////////////////////////////////////////////////////////////////////// // Aggregation: collects the signing shares from all participants, diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index 583dd357..3cc00fb9 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -5,8 +5,14 @@ use hex::FromHex; use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature}; +#[cfg(feature = "serde")] +use crate::ElementSerialization; + /// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`]. -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] pub struct VerifyingKey where C: Ciphersuite, @@ -105,6 +111,28 @@ where } } +#[cfg(feature = "serde")] +impl TryFrom> for VerifyingKey +where + C: Ciphersuite, +{ + type Error = Error; + + fn try_from(value: ElementSerialization) -> Result { + Self::from_bytes(value.0) + } +} + +#[cfg(feature = "serde")] +impl From> for ElementSerialization +where + C: Ciphersuite, +{ + fn from(value: VerifyingKey) -> Self { + Self(value.to_bytes()) + } +} + // impl From> for ::ElementSerialization { // fn from(pk: VerifyingKey) -> ::ElementSerialization { // pk.bytes.bytes diff --git a/frost-ed25519/README.md b/frost-ed25519/README.md index 8ced05e4..7b5a343a 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -70,7 +70,7 @@ let message = "message to sign".as_bytes(); # // In practice, the SigningPackage must be sent to all participants # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec()); +let signing_package = frost::SigningPackage::new(commitments_received, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 703da346..5e650098 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -150,6 +150,8 @@ const CONTEXT_STRING: &str = "FROST-ED25519-SHA512-v11"; pub struct Ed25519Sha512; impl Ciphersuite for Ed25519Sha512 { + const ID: &'static str = "FROST(Ed25519, SHA-512)"; + type Group = Ed25519Group; type HashOutput = [u8; 64]; diff --git a/frost-ed448/README.md b/frost-ed448/README.md index fde4bfe9..08b160bd 100644 --- a/frost-ed448/README.md +++ b/frost-ed448/README.md @@ -70,7 +70,7 @@ let message = "message to sign".as_bytes(); # // In practice, the SigningPackage must be sent to all participants # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec()); +let signing_package = frost::SigningPackage::new(commitments_received, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index a227f9f9..fd54f528 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -145,6 +145,8 @@ const CONTEXT_STRING: &str = "FROST-ED448-SHAKE256-v11"; pub struct Ed448Shake256; impl Ciphersuite for Ed448Shake256 { + const ID: &'static str = "FROST(Ed448, SHAKE256)"; + type Group = Ed448Group; type HashOutput = [u8; 114]; diff --git a/frost-p256/README.md b/frost-p256/README.md index b33e4b84..4937eba5 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -70,7 +70,7 @@ let message = "message to sign".as_bytes(); # // In practice, the SigningPackage must be sent to all participants # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec()); +let signing_package = frost::SigningPackage::new(commitments_received, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index b458513f..eaafd6f2 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -170,6 +170,8 @@ const CONTEXT_STRING: &str = "FROST-P256-SHA256-v11"; pub struct P256Sha256; impl Ciphersuite for P256Sha256 { + const ID: &'static str = "FROST(P-256, SHA-256)"; + type Group = P256Group; type HashOutput = [u8; 32]; diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index 452f3b1d..4aa2ce07 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -61,7 +61,7 @@ pub fn check_randomized_sign_with_dealer let mut signature_shares: Vec> = Vec::new(); let message = "message to sign".as_bytes(); let comms = commitments.clone().into_values().collect(); - let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + let signing_package = frost::SigningPackage::new(comms, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index d8ae867f..dd81cd47 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -28,6 +28,7 @@ bincode = "1" criterion = { version = "0.5", features = ["html_reports"] } ed25519-dalek = "1.0.1" ed25519-zebra = "4.0.0" +hex = "0.4.3" lazy_static = "1.4" proptest = "1.0" proptest-derive = "0.3" @@ -38,6 +39,7 @@ serde_json = "1.0" [features] nightly = [] default = [] +serde = ["frost-core/serde"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index c7d0625b..14df0557 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -70,7 +70,7 @@ let message = "message to sign".as_bytes(); # // In practice, the SigningPackage must be sent to all participants # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec()); +let signing_package = frost::SigningPackage::new(commitments_received, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 8468adfe..98ee68c6 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -13,6 +13,9 @@ use sha2::{Digest, Sha512}; use frost_core::frost; +#[cfg(feature = "serde")] +use frost_core::serde; + #[cfg(test)] mod tests; @@ -136,9 +139,13 @@ const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512-v11"; /// An implementation of the FROST(ristretto255, SHA-512) ciphersuite. #[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "self::serde"))] pub struct Ristretto255Sha512; impl Ciphersuite for Ristretto255Sha512 { + const ID: &'static str = "FROST(ristretto255, SHA-512)"; + type Group = RistrettoGroup; type HashOutput = [u8; 64]; diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs new file mode 100644 index 00000000..d63321e0 --- /dev/null +++ b/frost-ristretto255/tests/serde_tests.rs @@ -0,0 +1,651 @@ +#![cfg(feature = "serde")] + +use std::collections::HashMap; + +use frost_core::{ + frost::keys::{SigningShare, VerifyingShare}, + Ciphersuite, Group, +}; +use frost_ristretto255::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, VerifiableSecretSharingCommitment, + }, + round1::{NonceCommitment, SigningCommitments}, + round2::{SignatureResponse, SignatureShare}, + Signature, SigningPackage, VerifyingKey, +}; + +type C = frost_ristretto255::Ristretto255Sha512; + +fn build_sample_signing_commitments() -> SigningCommitments { + let element1 = ::Group::generator(); + let element2 = element1 + element1; + let serialized_element1 = ::Group::serialize(&element1); + let serialized_element2 = ::Group::serialize(&element2); + let hiding_nonce_commitment = NonceCommitment::from_bytes(serialized_element1).unwrap(); + let binding_nonce_commitment = NonceCommitment::from_bytes(serialized_element2).unwrap(); + let identifier = 42u16.try_into().unwrap(); + + SigningCommitments::new( + identifier, + hiding_nonce_commitment, + binding_nonce_commitment, + ) +} + +#[test] +fn check_signing_commitments_serialization() { + let commitments = build_sample_signing_commitments(); + + let json = serde_json::to_string_pretty(&commitments).unwrap(); + println!("{}", json); + + let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); + assert!(commitments == decoded_commitments); + + let json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_commitments: SigningCommitments = serde_json::from_str(json).unwrap(); + assert!(commitments == decoded_commitments); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Wrong ciphersuite + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(Ed25519, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "foo": "0000000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "foo": "0000000000000000000000000000000000000000000000000000000000000000", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_signing_package_serialization() { + let commitments = build_sample_signing_commitments(); + let message = "hello world".as_bytes(); + + let signing_package = SigningPackage::new(vec![commitments], message); + + let json = serde_json::to_string_pretty(&signing_package).unwrap(); + println!("{}", json); + + let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); + assert!(signing_package == decoded_signing_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + let json = r#"{ + "signing_commitments": { + "2a00000000000000000000000000000000000000000000000000000000000000": { + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(ristretto255, SHA-512)" + } + }, + "message": "68656c6c6f20776f726c64", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); + assert!(signing_package == decoded_signing_package); + + // Invalid identifier + let invalid_json = r#"{ + "signing_commitments": { + "0000000000000000000000000000000000000000000000000000000000000000": { + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(ristretto255, SHA-512)" + } + }, + "message": "68656c6c6f20776f726c64", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "signing_commitments": { + "2a00000000000000000000000000000000000000000000000000000000000000": { + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "foo": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(ristretto255, SHA-512)" + } + }, + "message": "68656c6c6f20776f726c64", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "signing_commitments": { + "2a00000000000000000000000000000000000000000000000000000000000000": { + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(ristretto255, SHA-512)" + } + }, + "message": "68656c6c6f20776f726c64", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "signing_commitments": { + "2a00000000000000000000000000000000000000000000000000000000000000": { + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", + "ciphersuite": "FROST(ristretto255, SHA-512)" + } + }, + "message": "68656c6c6f20776f726c64", + "extra": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + } + "#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_signature_share_serialization() { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = + hex::decode("a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f") + .unwrap() + .try_into() + .unwrap(); + let signature_response = SignatureResponse::from_bytes(serialized_scalar).unwrap(); + + let signature_share = SignatureShare::new(identifier, signature_response); + + let json = serde_json::to_string_pretty(&signature_share).unwrap(); + println!("{}", json); + + let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); + assert!(signature_share == decoded_signature_share); + + let json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "signature": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); + assert!(signature_share == decoded_commitments); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "signature": "e660b88149e1dd06d7cace3c5ee32a71b4b718e2719583630ba916579fe8320d", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "foo": "e660b88149e1dd06d7cace3c5ee32a71b4b718e2719583630ba916579fe8320d", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000",, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "signature": "e660b88149e1dd06d7cace3c5ee32a71b4b718e2719583630ba916579fe8320d", + "extra": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_secret_share_serialization() { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = + hex::decode("a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f") + .unwrap() + .try_into() + .unwrap(); + let serialized_element = + hex::decode("e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76") + .unwrap() + .try_into() + .unwrap(); + let signing_share = SigningShare::from_bytes(serialized_scalar).unwrap(); + let vss_commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + let secret_share = SecretShare::new(identifier, signing_share, vss_commitment); + + let json = serde_json::to_string_pretty(&secret_share).unwrap(); + println!("{}", json); + + let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); + assert!(secret_share == decoded_secret_share); + + let json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "value": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_secret_share: SecretShare = serde_json::from_str(json).unwrap(); + assert!(secret_share == decoded_secret_share); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "value": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "foo": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "value": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "extra": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_key_package_serialization() { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = + hex::decode("a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f") + .unwrap() + .try_into() + .unwrap(); + let serialized_element = + hex::decode("e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76") + .unwrap() + .try_into() + .unwrap(); + let signing_share = SigningShare::from_bytes(serialized_scalar).unwrap(); + let verifying_share = VerifyingShare::from_bytes(serialized_element).unwrap(); + let verifying_key = VerifyingKey::from_bytes(serialized_element).unwrap(); + + let key_package = KeyPackage::new(identifier, signing_share, verifying_share, verifying_key); + + let json = serde_json::to_string_pretty(&key_package).unwrap(); + println!("{}", json); + + let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); + assert!(key_package == decoded_key_package); + + let json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_key_package: KeyPackage = serde_json::from_str(json).unwrap(); + assert!(key_package == decoded_key_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "foo": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "extra_field": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_public_key_package_serialization() { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = + hex::decode("e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76") + .unwrap() + .try_into() + .unwrap(); + let verifying_share = VerifyingShare::from_bytes(serialized_element).unwrap(); + let verifying_key = VerifyingKey::from_bytes(serialized_element).unwrap(); + + let public_key_package = PublicKeyPackage::new( + HashMap::from([(identifier, verifying_share)]), + verifying_key, + ); + + let json = serde_json::to_string_pretty(&public_key_package).unwrap(); + println!("{}", json); + + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); + assert!(public_key_package == decoded_public_key_package); + + let json = r#"{ + "signer_pubkeys": { + "2a00000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }, + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package == decoded_public_key_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "signer_pubkeys": { + "0000000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }, + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "signer_pubkeys": { + "2a00000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }, + "foo": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "signer_pubkeys": { + "2a00000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "signer_pubkeys": { + "2a00000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }, + "group_public": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "extra": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_round1_package_serialization() { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar: [u8; 32] = + hex::decode("a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f") + .unwrap() + .try_into() + .unwrap(); + let serialized_element: [u8; 32] = + hex::decode("e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76") + .unwrap() + .try_into() + .unwrap(); + let serialized_signature: [u8; 64] = serialized_element + .iter() + .chain(serialized_scalar.iter()) + .cloned() + .collect::>() + .try_into() + .unwrap(); + let vss_commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let signature = Signature::from_bytes(serialized_signature).unwrap(); + + let round1_package = round1::Package::new(identifier, vss_commitment, signature); + + let json = serde_json::to_string_pretty(&round1_package).unwrap(); + println!("{}", json); + + let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); + assert!(round1_package == decoded_round1_package); + + let json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_round1_package: round1::Package = serde_json::from_str(json).unwrap(); + assert!(round1_package == decoded_round1_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "foo": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "commitment": [ + "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + ], + "proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "extra": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_round2_package_serialization() { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar: [u8; 32] = + hex::decode("a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f") + .unwrap() + .try_into() + .unwrap(); + let signing_share = SigningShare::from_bytes(serialized_scalar).unwrap(); + + let round2_package = round2::Package::new(identifier, identifier, signing_share); + + let json = serde_json::to_string_pretty(&round2_package).unwrap(); + println!("{}", json); + + let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); + assert!(round2_package == decoded_round2_package); + + let json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + let decoded_round2_package: round2::Package = serde_json::from_str(json).unwrap(); + assert!(round2_package == decoded_round2_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "foo": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "secret_share": "a0bdb9f6bf7b44c092dc285e66ee0484bce85c2d83babe03442510ab37603b0f", + "extra": 1, + "ciphersuite": "FROST(ristretto255, SHA-512)" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 0937ac8a..5b38a91a 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -24,6 +24,7 @@ features = ["nightly"] frost-core = { path = "../frost-core", version = "0.4.0", features = ["test-impl"] } k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"] } rand_core = "0.6" +serde = { version = "1.0.160", features = ["derive"] } sha2 = "0.10.2" [dev-dependencies] diff --git a/frost-secp256k1/README.md b/frost-secp256k1/README.md index ebc79da5..a0d58aad 100644 --- a/frost-secp256k1/README.md +++ b/frost-secp256k1/README.md @@ -70,7 +70,7 @@ let message = "message to sign".as_bytes(); # // In practice, the SigningPackage must be sent to all participants # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec()); +let signing_package = frost::SigningPackage::new(commitments_received, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 388f0836..1ef9eb1e 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -12,6 +12,7 @@ use k256::{ AffinePoint, ProjectivePoint, Scalar, }; use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use frost_core::frost; @@ -167,10 +168,12 @@ fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v11"; /// An implementation of the FROST(secp256k1, SHA-256) ciphersuite. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct Secp256K1Sha256; impl Ciphersuite for Secp256K1Sha256 { + const ID: &'static str = "FROST(secp256k1, SHA-256)"; + type Group = Secp256K1Group; type HashOutput = [u8; 32];