add frost-secp256k1-tr crate (BIP340/BIP341)

This commit is contained in:
zebra-lucky 2023-11-20 20:21:36 +02:00
parent d048057a21
commit b380fd589b
46 changed files with 4156 additions and 32 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ Cargo.lock
*~
**/.DS_Store
.vscode/*
*.swp

View File

@ -7,6 +7,7 @@ members = [
"frost-p256",
"frost-ristretto255",
"frost-secp256k1",
"frost-secp256k1-tr",
"frost-rerandomized",
"gencode"
]

View File

@ -32,7 +32,7 @@ where
{
fn from((vk, sig, msg): (VerifyingKey<C>, Signature<C>, &'msg M)) -> Self {
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::challenge(&sig.R, &vk, msg.as_ref());
let c = <C>::challenge(&sig.R, &vk, msg.as_ref());
Self { vk, sig, c }
}
@ -118,7 +118,12 @@ where
for item in self.signatures.iter() {
let z = item.sig.z;
let R = item.sig.R;
let mut R = item.sig.R;
let mut vk = item.vk.element;
if <C>::is_need_tweaking() {
R = <C>::tweaked_R(&item.sig.R);
vk = <C>::tweaked_public_key(&item.vk.element);
}
let blind = <<C::Group as Group>::Field>::random(&mut rng);
@ -129,7 +134,7 @@ where
Rs.push(R);
VK_coeffs.push(<<C::Group as Group>::Field>::zero() + (blind * item.c.0));
VKs.push(item.vk.element);
VKs.push(vk);
}
let scalars = once(&P_coeff_acc)

View File

@ -120,6 +120,11 @@ where
pub(crate) fn from_coefficients(coefficients: &[Scalar<C>], peer: Identifier<C>) -> Self {
Self(evaluate_polynomial(peer, coefficients))
}
/// Returns negated SigningShare
pub fn negate(&mut self) {
self.0 = <<C::Group as Group>::Field>::negate(&self.0);
}
}
impl<C> Debug for SigningShare<C>
@ -686,6 +691,11 @@ where
min_signers,
}
}
/// Negate `SigningShare`.
pub fn negate_signing_share(&mut self) {
self.signing_share.negate();
}
}
#[cfg(feature = "serialization")]

View File

@ -69,7 +69,6 @@ where
C: Ciphersuite,
{
/// Creates a challenge from a scalar.
#[cfg(feature = "internals")]
pub fn from_scalar(
scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
) -> Self {
@ -77,7 +76,6 @@ where
}
/// Return the underlying scalar.
#[cfg(feature = "internals")]
pub fn to_scalar(self) -> <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
self.0
}
@ -465,6 +463,11 @@ where
pub fn to_element(self) -> <C::Group as Group>::Element {
self.0
}
/// Check if group commitment is odd
pub fn y_is_odd(&self) -> bool {
<C::Group as Group>::y_is_odd(&self.0)
}
}
/// Generates the group commitment which is published as part of the joint
@ -585,6 +588,15 @@ where
z = z + signature_share.share;
}
if <C>::is_need_tweaking() {
let challenge = <C>::challenge(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);
z = <C>::aggregate_tweak_z(z, &challenge, &pubkeys.verifying_key.element);
}
let signature = Signature {
R: group_commitment.0,
z,
@ -601,7 +613,7 @@ where
#[cfg(feature = "cheater-detection")]
if let Err(err) = verification_result {
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
let challenge = <C>::challenge(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
@ -636,6 +648,8 @@ where
signer_pubkey,
lambda_i,
&challenge,
&group_commitment,
&pubkeys.verifying_key,
)?;
}

View File

@ -54,6 +54,11 @@ where
Self::nonce_generate_from_random_bytes(secret, random_bytes)
}
/// Negate `Nonce`.
pub fn negate(&mut self) {
self.0 = <<C::Group as Group>::Field>::negate(&self.0);
}
/// Generates a nonce from the given random bytes.
/// This function allows testing and MUST NOT be made public.
pub(crate) fn nonce_generate_from_random_bytes(
@ -317,6 +322,12 @@ where
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
Deserialize::deserialize(bytes)
}
/// Negate `SigningShare`.
pub fn negate_nonces(&mut self) {
self.binding.negate();
self.hiding.negate();
}
}
/// Published by each participant in the first round of the signing protocol.

View File

@ -4,7 +4,7 @@ use std::fmt::{self, Debug};
use crate as frost;
use crate::{
challenge, Challenge, Ciphersuite, Error, Field, Group, {round1, *},
Challenge, Ciphersuite, Error, Field, Group, {round1, *},
};
#[cfg(feature = "serde")]
@ -90,9 +90,23 @@ where
verifying_share: &frost::keys::VerifyingShare<C>,
lambda_i: Scalar<C>,
challenge: &Challenge<C>,
group_commitment: &frost::GroupCommitment<C>,
verifying_key: &frost::VerifyingKey<C>,
) -> Result<(), Error<C>> {
let mut commitment_share = group_commitment_share.0;
let mut vsh = verifying_share.0;
if <C>::is_need_tweaking() {
commitment_share = <C>::tweaked_group_commitment_share(
&group_commitment_share.0,
&group_commitment.0
);
vsh = <C>::tweaked_verifying_share(
&verifying_share.0,
&verifying_key.element
);
}
if (<C::Group>::generator() * self.share)
!= (group_commitment_share.0 + (verifying_share.0 * challenge.0 * lambda_i))
!= (commitment_share + (vsh * challenge.0 * lambda_i))
{
return Err(Error::InvalidSignatureShare {
culprit: identifier,
@ -150,9 +164,7 @@ where
}
/// Compute the signature share for a signing operation.
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_signature_share<C: Ciphersuite>(
pub fn compute_signature_share<C: Ciphersuite>(
signer_nonces: &round1::SigningNonces<C>,
binding_factor: BindingFactor<C>,
lambda_i: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
@ -214,20 +226,33 @@ pub fn sign<C: Ciphersuite>(
let lambda_i = frost::derive_interpolating_value(key_package.identifier(), signing_package)?;
// Compute the per-message challenge.
let challenge = challenge::<C>(
let challenge = <C>::challenge(
&group_commitment.0,
&key_package.verifying_key,
signing_package.message.as_slice(),
);
// Compute the Schnorr signature share.
let signature_share = compute_signature_share(
signer_nonces,
binding_factor,
lambda_i,
key_package,
challenge,
);
if <C>::is_need_tweaking() {
let signature_share = <C>::compute_tweaked_signature_share(
signer_nonces,
binding_factor,
group_commitment,
lambda_i,
key_package,
challenge,
);
Ok(signature_share)
Ok(signature_share)
} else {
let signature_share = compute_signature_share(
signer_nonces,
binding_factor,
lambda_i,
key_package,
challenge,
);
Ok(signature_share)
}
}

View File

@ -1,11 +1,12 @@
//! Schnorr signatures over prime order groups (or subgroups)
use debugless_unwrap::DebuglessUnwrap;
use derive_getters::Getters;
use crate::{Ciphersuite, Element, Error, Field, Group, Scalar};
/// A Schnorr signature over some prime order group (or subgroup).
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq, Getters)]
pub struct Signature<C: Ciphersuite> {
/// The commitment `R` to the signature nonce.
pub(crate) R: Element<C>,

View File

@ -2,7 +2,7 @@
use rand_core::{CryptoRng, RngCore};
use crate::{random_nonzero, Ciphersuite, Error, Field, Group, Scalar, Signature, VerifyingKey};
use crate::{random_nonzero, Ciphersuite, Error, Field, Group, Scalar, Signature, VerifyingKey, Challenge};
/// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`].
#[derive(Copy, Clone, PartialEq, Eq)]
@ -45,14 +45,21 @@ where
/// Create a signature `msg` using this `SigningKey`.
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<C> {
let k = random_nonzero::<C, R>(&mut rng);
let public = VerifyingKey::<C>::from(*self);
let mut secret = self.scalar;
if <C>::is_need_tweaking() {
secret = <C>::tweaked_secret_key(secret, &public.element);
}
let mut k = random_nonzero::<C, R>(&mut rng);
let R = <C::Group>::generator() * k;
if <C>::is_need_tweaking() {
k = <C>::tweaked_nonce(k, &R);
}
// Generate Schnorr challenge
let c = crate::challenge::<C>(&R, &VerifyingKey::<C>::from(*self), msg);
let c: Challenge<C> = <C>::challenge(&R, &public, msg);
let z = k + (c.0 * self.scalar);
let z = k + (c.0 * secret);
Signature { R, z }
}

View File

@ -7,7 +7,8 @@ use std::{
use rand_core::{CryptoRng, RngCore};
use crate::{Error, FieldError, GroupError, Signature, VerifyingKey};
use crate::{Error, FieldError, GroupError, Signature, VerifyingKey, Challenge,
challenge};
/// A prime order finite field GF(q) over which all scalar values for our prime order group can be
/// multiplied are defined.
@ -40,6 +41,12 @@ pub trait Field: Copy + Clone {
/// element is zero.
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, FieldError>;
/// Computes the negation of the element of the scalar field
#[allow(unused)]
fn negate(scalar: &Self::Scalar) -> Self::Scalar {
panic!("Not implemented");
}
/// Generate a random scalar from the entire space [0, l-1]
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.3>
@ -113,6 +120,12 @@ pub trait Group: Copy + Clone + PartialEq {
/// [`ScalarBaseMult()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.5
fn generator() -> Self::Element;
/// Check if element is odd
#[allow(unused)]
fn y_is_odd(element: &Self::Element) -> bool {
panic!("Not implemented");
}
/// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of
/// fixed length Ne.
///
@ -224,8 +237,108 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
signature: &Signature<Self>,
public_key: &VerifyingKey<Self>,
) -> Result<(), Error<Self>> {
let c = crate::challenge::<Self>(&signature.R, public_key, msg);
let c = <Self>::challenge(&signature.R, public_key, msg);
public_key.verify_prehashed(c, signature)
}
/// Generates the challenge as is required for Schnorr signatures.
///
/// Deals in bytes, so that [FROST] and singleton signing and verification can use it with different
/// types.
///
/// This is the only invocation of the H2 hash function from the [RFC].
///
/// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#name-signature-challenge-computa
/// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-3.2
fn challenge(R: &Element<Self>, verifying_key: &VerifyingKey<Self>, msg: &[u8]) -> Challenge<Self>
{
challenge(R, verifying_key, msg)
}
/// determine tweak is need
fn is_need_tweaking() -> bool {
false
}
/// aggregate tweak z
#[allow(unused)]
fn aggregate_tweak_z(
z: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: &Challenge<Self>,
verifying_key: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar
{
panic!("Not implemented");
}
/// signature_share tweak
#[allow(unused)]
fn compute_tweaked_signature_share(
signer_nonces: &crate::round1::SigningNonces<Self>,
binding_factor: crate::BindingFactor<Self>,
group_commitment: crate::GroupCommitment<Self>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &crate::keys::KeyPackage<Self>,
challenge: Challenge<Self>,
) -> crate::round2::SignatureShare<Self>
{
panic!("Not implemented");
}
/// calculate tweaked public key
#[allow(unused)]
fn tweaked_public_key(
public_key: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
panic!("Not implemented");
}
/// calculate tweaked R
#[allow(unused)]
fn tweaked_R(
public_key: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
panic!("Not implemented");
}
/// tweaked secret
#[allow(unused)]
fn tweaked_secret_key(
secret: <<Self::Group as Group>::Field as Field>::Scalar,
public: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar
{
panic!("Not implemented");
}
/// tweaked nonce
#[allow(unused)]
fn tweaked_nonce(
nonce: <<Self::Group as Group>::Field as Field>::Scalar,
R: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar
{
panic!("Not implemented");
}
/// tweaked group commitment
#[allow(unused)]
fn tweaked_group_commitment_share(
group_commitment_share: &<Self::Group as Group>::Element,
group_commitment: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element
{
panic!("Not implemented");
}
/// tweaked verifying share
#[allow(unused)]
fn tweaked_verifying_share(
verifying_share: &<Self::Group as Group>::Element,
verifying_key: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element
{
panic!("Not implemented");
}
}

View File

@ -1,4 +1,5 @@
use std::fmt::{self, Debug};
use derive_getters::Getters;
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
@ -9,7 +10,7 @@ use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature};
use crate::serialization::ElementSerialization;
/// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`].
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, PartialEq, Eq, Getters)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization<C>"))]
@ -33,11 +34,15 @@ where
}
/// Return the underlying element.
#[cfg(feature = "internals")]
pub fn to_element(self) -> <C::Group as Group>::Element {
self.element
}
/// Check if VerifyingKey is odd
pub fn y_is_odd(&self) -> bool {
<C::Group as Group>::y_is_odd(&self.element)
}
/// Deserialize from bytes
pub fn deserialize(
bytes: <C::Group as Group>::Serialization,
@ -63,9 +68,15 @@ where
// h * ( z * B - c * A - R) == 0
//
// where h is the cofactor
let mut R = signature.R;
let mut vk = self.element;
if <C>::is_need_tweaking() {
R = <C>::tweaked_R(&signature.R);
vk = <C>::tweaked_public_key(&self.element);
}
let zB = C::Group::generator() * signature.z;
let cA = self.element * challenge.0;
let check = (zB - cA - signature.R) * C::Group::cofactor();
let cA = vk * challenge.0;
let check = (zB - cA - R) * C::Group::cofactor();
if check == C::Group::identity() {
Ok(())

View File

@ -0,0 +1,63 @@
[package]
name = "frost-secp256k1-tr"
edition = "2021"
# When releasing to crates.io:
# - Update CHANGELOG.md
# - Create git tag.
version = "1.0.0-rc.0"
authors = [
"Deirdre Connolly <durumcrustulum@gmail.com>",
"Chelsea Komlo <me@chelseakomlo.com>",
"Conrado Gouvea <conradoplg@gmail.com>"
]
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ZcashFoundation/frost"
categories = ["cryptography"]
keywords = ["cryptography", "crypto", "threshold", "signature"]
description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot."
[package.metadata.docs.rs]
features = ["serde"]
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
document-features = "0.2.7"
frost-core = { path = "../frost-core", version = "1.0.0-rc.0" }
frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0-rc.0" }
k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"] }
rand_core = "0.6"
sha2 = "0.10.2"
[dev-dependencies]
criterion = "0.5"
frost-core = { path = "../frost-core", version = "1.0.0-rc.0", features = ["test-impl"] }
frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0-rc.0", features = ["test-impl"] }
insta = { version = "1.31.0", features = ["yaml"] }
hex = "0.4.3"
lazy_static = "1.4"
proptest = "1.0"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"
[features]
nightly = []
default = ["serialization", "cheater-detection"]
serialization = ["serde", "frost-core/serialization"]
#! ## Features
## Enable `serde` support for types that need to be communicated. You
## can use `serde` to serialize structs with any encoder that supports
## `serde` (e.g. JSON with `serde_json`).
serde = ["frost-core/serde"]
## Enable cheater detection
cheater-detection = ["frost-core/cheater-detection"]
[lib]
# Disables non-criterion benchmark which is not used; prevents errors
# when using criterion-specific flags
bench = false
[[bench]]
name = "bench"
harness = false

View File

@ -0,0 +1,121 @@
An implementation of Schnorr signatures on the secp256k1 curve for both single and threshold numbers
of signers (FROST) with support of Taproot (BIP340/BIP341).
## Example: key generation with trusted dealer and FROST signing
Creating a key with a trusted dealer and splitting into shares; then signing a message
and aggregating the signature. Note that the example just simulates a distributed
scenario in a single thread and it abstracts away any communication between peers.
```rust
# // ANCHOR: tkg_gen
use frost_secp256k1_tr as frost;
use rand::thread_rng;
use std::collections::BTreeMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(
max_signers,
min_signers,
frost::keys::IdentifierList::Default,
&mut rng,
)?;
# // ANCHOR_END: tkg_gen
// Verifies the secret shares from the dealer and store them in a BTreeMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: BTreeMap<_, _> = BTreeMap::new();
for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}
let mut nonces_map = BTreeMap::new();
let mut commitments_map = BTreeMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
key_packages[&participant_identifier].signing_share(),
&mut rng,
);
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = BTreeMap::new();
# // ANCHOR: round2_package
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_map, message);
# // ANCHOR_END: round2_package
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
signature_shares.insert(*participant_identifier, signature_share);
}
////////////////////////////////////////////////////////////////////////////
// Aggregation: collects the signing shares from all participants,
// generates the final signature.
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
# // ANCHOR_END: aggregate
// Check that the threshold signature can be verified by the group public
// key (the verification key).
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.verifying_key()
.verify(message, &group_signature)
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);
# Ok::<(), frost::Error>(())
```

View File

@ -0,0 +1,19 @@
use criterion::{criterion_group, criterion_main, Criterion};
use rand::thread_rng;
use frost_secp256k1::*;
fn bench_secp256k1_batch_verify(c: &mut Criterion) {
let mut rng = thread_rng();
frost_core::benches::bench_batch_verify::<Secp256K1Sha256, _>(c, "secp256k1", &mut rng);
}
fn bench_secp256k1_sign(c: &mut Criterion) {
let mut rng = thread_rng();
frost_core::benches::bench_sign::<Secp256K1Sha256, _>(c, "secp256k1", &mut rng);
}
criterion_group!(benches, bench_secp256k1_batch_verify, bench_secp256k1_sign);
criterion_main!(benches);

168
frost-secp256k1-tr/dkg.md Normal file
View File

@ -0,0 +1,168 @@
# Distributed Key Generation (DKG)
The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.
Before starting, each participant needs an unique identifier, which can be built from
a `u16`. The process in which these identifiers are allocated is up to the application.
The distributed key generation process has 3 parts, with 2 communication rounds
between them, in which each participant needs to send a "package" to every other
participant. In the first round, each participant sends the same package
(a [`round1::Package`]) to every other. In the second round, each receiver gets
their own package (a [`round2::Package`]).
Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`]
that MUST be kept secret. Between part 2 and 3, each participant needs to hold
onto a [`round2::SecretPackage`].
After the third part, each participant will get a [`KeyPackage`] with their
long-term secret share that must be kept secret, and a [`PublicKeyPackage`]
that is public (and will be the same between all participants). With those
they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::BTreeMap;
use frost_secp256k1_tr as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round1_secret_packages = BTreeMap::new();
// Keep track of all round 1 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round1_packages = BTreeMap::new();
// For each participant, perform the first part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a BTreeMap; in practice this will be
// sent through some communication channel.
for receiver_participant_index in 1..=max_signers {
if receiver_participant_index == participant_index {
continue;
}
let receiver_participant_identifier: frost::Identifier = receiver_participant_index
.try_into()
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(BTreeMap::new)
.insert(participant_identifier, round1_package.clone());
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 2
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's round 2 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round2_secret_packages = BTreeMap::new();
// Keep track of all round 2 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round2_packages = BTreeMap::new();
// For each participant, perform the second part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round2_secret_packages.insert(participant_identifier, round2_secret_package);
// "Send" the round 2 package to all other participants. In this
// test this is simulated using a BTreeMap; in practice this will be
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for (receiver_identifier, round2_package) in round2_packages {
received_round2_packages
.entry(receiver_identifier)
.or_insert_with(BTreeMap::new)
.insert(participant_identifier, round2_package);
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, final computation
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's long-lived key package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut key_packages = BTreeMap::new();
// Keep track of each participant's public key package.
// In practice, if there is a Coordinator, only they need to store the set.
// If there is not, then all candidates must store their own sets.
// All participants will have the same exact public key package.
let mut pubkey_packages = BTreeMap::new();
// For each participant, perform the third part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```

View File

@ -0,0 +1,87 @@
#![doc = include_str!("../../dkg.md")]
use super::*;
/// DKG Round 1 structures.
pub mod round1 {
use super::*;
/// The secret package that must be kept in memory by the participant
/// between the first and second parts of the DKG protocol (round 1).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type SecretPackage = frost::keys::dkg::round1::SecretPackage<S>;
/// 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).
pub type Package = frost::keys::dkg::round1::Package<S>;
}
/// DKG Round 2 structures.
pub mod round2 {
use super::*;
/// The secret package that must be kept in memory by the participant
/// between the second and third parts of the DKG protocol (round 2).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type SecretPackage = frost::keys::dkg::round2::SecretPackage<S>;
/// A package that must be sent by each participant to some other participants
/// in Round 2 of the DKG protocol. Note that there is one specific package
/// for each specific recipient, in contrast to Round 1.
///
/// # Security
///
/// The package must be sent on an *confidential* and *authenticated* channel.
pub type Package = frost::keys::dkg::round2::Package<S>;
}
/// Performs the first part of the distributed key generation protocol
/// for the given participant.
///
/// It returns the [`round1::SecretPackage`] that must be kept in memory
/// by the participant for the other steps, and the [`round1::Package`] that
/// must be sent to other participants.
pub fn part1<R: RngCore + CryptoRng>(
identifier: Identifier,
max_signers: u16,
min_signers: u16,
mut rng: R,
) -> Result<(round1::SecretPackage, round1::Package), Error> {
frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng)
}
/// Performs the second part of the distributed key generation protocol
/// for the participant holding the given [`round1::SecretPackage`],
/// given the received [`round1::Package`]s received from the other participants.
///
/// It returns the [`round2::SecretPackage`] that must be kept in memory
/// by the participant for the final step, and the [`round2::Package`]s that
/// must be sent to other participants.
pub fn part2(
secret_package: round1::SecretPackage,
round1_packages: &BTreeMap<Identifier, round1::Package>,
) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> {
frost::keys::dkg::part2(secret_package, round1_packages)
}
/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`round2::SecretPackage`],
/// given the received [`round1::Package`]s and [`round2::Package`]s received from
/// the other participants.
///
/// It returns the [`KeyPackage`] that has the long-lived key share for the
/// participant, and the [`PublicKeyPackage`]s that has public information
/// about all participants; both of which are required to compute FROST
/// signatures.
pub fn part3(
round2_secret_package: &round2::SecretPackage,
round1_packages: &BTreeMap<Identifier, round1::Package>,
round2_packages: &BTreeMap<Identifier, round2::Package>,
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages)
}

View File

@ -0,0 +1,101 @@
//! Repairable Threshold Scheme
//!
//! Implements the Repairable Threshold Scheme (RTS) from <https://eprint.iacr.org/2017/1155>.
//! The RTS is used to help a signer (participant) repair their lost share. This is achieved
//! using a subset of the other signers know here as `helpers`.
use std::collections::BTreeMap;
// This is imported separately to make `gencode` work.
// (if it were below, the position of the import would vary between ciphersuites
// after `cargo fmt`)
use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar};
use crate::{Error, Secp256K1Sha256};
use super::{SecretShare, VerifiableSecretSharingCommitment};
/// Step 1 of RTS.
///
/// Generates the "delta" values from `helper_i` to help `participant` recover their share
/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i`
/// is the share of `helper_i`.
///
/// Returns a BTreeMap mapping which value should be sent to which participant.
pub fn repair_share_step_1<C: Ciphersuite, R: RngCore + CryptoRng>(
helpers: &[Identifier],
share_i: &SecretShare,
rng: &mut R,
participant: Identifier,
) -> Result<BTreeMap<Identifier, Scalar>, Error> {
frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant)
}
/// Step 2 of RTS.
///
/// Generates the `sigma` values from all `deltas` received from `helpers`
/// to help `participant` recover their share.
/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`.
///
/// Returns a scalar
pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar {
frost::keys::repairable::repair_share_step_2::<Secp256K1Sha256>(deltas_j)
}
/// Step 3 of RTS
///
/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare`
/// is made up of the `identifier`and `commitment` of the `participant` as well as the
/// `value` which is the `SigningShare`.
pub fn repair_share_step_3(
sigmas: &[Scalar],
identifier: Identifier,
commitment: &VerifiableSecretSharingCommitment,
) -> SecretShare {
frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment)
}
#[cfg(test)]
mod tests {
use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;
use crate::Secp256K1Sha256;
lazy_static! {
pub static ref REPAIR_SHARE: Value =
serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim())
.unwrap();
}
#[test]
fn check_repair_share_step_1() {
let rng = thread_rng();
frost_core::tests::repairable::check_repair_share_step_1::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_repair_share_step_2() {
frost_core::tests::repairable::check_repair_share_step_2::<Secp256K1Sha256>(&REPAIR_SHARE);
}
#[test]
fn check_repair_share_step_3() {
let rng = thread_rng();
frost_core::tests::repairable::check_repair_share_step_3::<Secp256K1Sha256, _>(
rng,
&REPAIR_SHARE,
);
}
#[test]
fn check_repair_share_step_1_fails_with_invalid_min_signers() {
let rng = thread_rng();
frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::<
Secp256K1Sha256,
_,
>(rng);
}
}

View File

@ -0,0 +1,628 @@
#![allow(non_snake_case)]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc = document_features::document_features!()]
use std::collections::BTreeMap;
use frost_rerandomized::RandomizedCiphersuite;
use k256::{
elliptic_curve::{
bigint::{U256},
group::prime::PrimeCurveAffine,
hash2curve::{hash_to_field, ExpandMsgXmd},
sec1::{FromEncodedPoint, ToEncodedPoint},
Field as FFField, PrimeField,
ScalarPrimitive,
point::{AffineCoordinates, DecompactPoint},
},
AffinePoint, ProjectivePoint, Scalar,
};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use frost_core as frost;
#[cfg(test)]
mod tests;
// Re-exports in our public API
pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError,
Element, Challenge};
pub use rand_core;
/// An error.
pub type Error = frost_core::Error<Secp256K1Sha256>;
/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite scalar field.
#[derive(Clone, Copy)]
pub struct Secp256K1ScalarField;
impl Field for Secp256K1ScalarField {
type Scalar = Scalar;
type Serialization = [u8; 32];
fn zero() -> Self::Scalar {
Scalar::ZERO
}
fn one() -> Self::Scalar {
Scalar::ONE
}
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, FieldError> {
// [`Scalar`]'s Eq/PartialEq does a constant-time comparison
if *scalar == <Self as Field>::zero() {
Err(FieldError::InvalidZeroScalar)
} else {
Ok(scalar.invert().unwrap())
}
}
fn negate(scalar: &Self::Scalar) -> Self::Scalar {
-scalar
}
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
}
fn serialize(scalar: &Self::Scalar) -> Self::Serialization {
scalar.to_bytes().into()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError> {
let field_bytes: &k256::FieldBytes = buf.into();
match Scalar::from_repr(*field_bytes).into() {
Some(s) => Ok(s),
None => Err(FieldError::MalformedScalar),
}
}
fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
let mut array = Self::serialize(scalar);
array.reverse();
array
}
}
/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite group.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Secp256K1Group;
impl Group for Secp256K1Group {
type Field = Secp256K1ScalarField;
type Element = ProjectivePoint;
/// [SEC 1][1] serialization of a compressed point in secp256k1 takes 33 bytes
/// (1-byte prefix and 32 bytes for the coordinate).
///
/// Note that, in the SEC 1 spec, the identity is encoded as a single null byte;
/// but here we pad with zeroes. This is acceptable as the identity _should_ never
/// be serialized in FROST, else we error.
///
/// [1]: https://secg.org/sec1-v2.pdf
type Serialization = [u8; 33];
fn cofactor() -> <Self::Field as Field>::Scalar {
Scalar::ONE
}
fn identity() -> Self::Element {
ProjectivePoint::IDENTITY
}
fn generator() -> Self::Element {
ProjectivePoint::GENERATOR
}
fn y_is_odd(element: &Self::Element) -> bool {
element.to_affine().y_is_odd().into()
}
fn serialize(element: &Self::Element) -> Self::Serialization {
let mut fixed_serialized = [0; 33];
let serialized_point = element.to_affine().to_encoded_point(true);
let serialized = serialized_point.as_bytes();
// Sanity check; either it takes all bytes or a single byte (identity).
assert!(serialized.len() == fixed_serialized.len() || serialized.len() == 1);
// Copy to the left of the buffer (i.e. pad the identity with zeroes).
// Note that identity elements shouldn't be serialized in FROST, but we
// do this padding so that this function doesn't have to return an error.
// If this encodes the identity, it will fail when deserializing.
{
let (left, _right) = fixed_serialized.split_at_mut(serialized.len());
left.copy_from_slice(serialized);
}
fixed_serialized
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError> {
let encoded_point =
k256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?;
match Option::<AffinePoint>::from(AffinePoint::from_encoded_point(&encoded_point)) {
Some(point) => {
if point.is_identity().into() {
// This is actually impossible since the identity is encoded a a single byte
// which will never happen since we receive a 33-byte buffer.
// We leave the check for consistency.
Err(GroupError::InvalidIdentityElement)
} else {
Ok(ProjectivePoint::from(point))
}
}
None => Err(GroupError::MalformedElement),
}
}
}
fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] {
let mut h = Sha256::new();
for i in inputs {
h.update(i);
}
let mut output = [0u8; 32];
output.copy_from_slice(h.finalize().as_slice());
output
}
fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar {
let mut u = [Secp256K1ScalarField::zero()];
hash_to_field::<ExpandMsgXmd<Sha256>, Scalar>(&[msg], &[domain], &mut u)
.expect("should never return error according to error cases described in ExpandMsgXmd");
u[0]
}
/// Context string from the ciphersuite in the [spec].
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-1
const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-TR-v1";
/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Secp256K1Sha256;
/// Digest the hasher to a Scalar
pub fn hasher_to_scalar(hasher: Sha256) -> Scalar {
let sp = ScalarPrimitive::new(U256::from_be_slice(&hasher.finalize()))
.unwrap();
Scalar::from(&sp)
}
/// Create a BIP340 compliant tagged hash
pub fn tagged_hash(tag: &str) -> Sha256 {
let mut hasher = Sha256::new();
let mut tag_hasher = Sha256::new();
tag_hasher.update(tag.as_bytes());
let tag_hash = tag_hasher.finalize();
hasher.update(tag_hash);
hasher.update(tag_hash);
hasher
}
/// Create a BIP341 compliant taproot tweak
pub fn tweak(
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
merkle_root: &[u8]
) -> Scalar {
let mut hasher = tagged_hash("TapTweak");
hasher.update(public_key.to_affine().x());
hasher.update(merkle_root);
hasher_to_scalar(hasher)
}
/// Create a BIP341 compliant tweaked public key
pub fn tweaked_public_key(
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
merkle_root: &[u8],
) -> <<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element {
let mut pk = public_key.clone();
if public_key.to_affine().y_is_odd().into() {
pk = -pk;
}
ProjectivePoint::GENERATOR * tweak(&pk, merkle_root) + pk
}
/// Create a BIP341 compliant tweaked secret key
pub fn tweaked_secret_key(
secret: <<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
merkle_root: &[u8],
) -> <<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
let mut secret = secret.clone();
if public_key.to_affine().y_is_odd().into() {
secret = -secret
}
secret + tweak(&public_key, merkle_root)
}
impl Ciphersuite for Secp256K1Sha256 {
const ID: &'static str = CONTEXT_STRING;
type Group = Secp256K1Group;
type HashOutput = [u8; 32];
type SignatureSerialization = [u8; 65];
/// H1 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.1
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m)
}
/// H2 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.2
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
let mut hasher = tagged_hash("BIP0340/challenge");
hasher.update(m);
hasher_to_scalar(hasher)
}
/// H3 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.3
fn H3(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m)
}
/// H4 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.4
fn H4(m: &[u8]) -> Self::HashOutput {
hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m])
}
/// H5 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.5
fn H5(m: &[u8]) -> Self::HashOutput {
hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m])
}
/// HDKG for FROST(secp256k1, SHA-256)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(
(CONTEXT_STRING.to_owned() + "dkg").as_bytes(),
m,
))
}
/// HID for FROST(secp256k1, SHA-256)
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(
(CONTEXT_STRING.to_owned() + "id").as_bytes(),
m,
))
}
/// Generates the challenge as is required for Schnorr signatures.
fn challenge(R: &Element<S>, verifying_key: &VerifyingKey, msg: &[u8]) -> Challenge<S>
{
let mut preimage = vec![];
let tweaked_public_key = tweaked_public_key(&verifying_key.to_element(), &[]);
preimage.extend_from_slice(&R.to_affine().x());
preimage.extend_from_slice(&tweaked_public_key.to_affine().x());
preimage.extend_from_slice(msg);
Challenge::from_scalar(S::H2(&preimage[..]))
}
/// determine tweak is need
fn is_need_tweaking() -> bool {
true
}
/// aggregate tweak z
fn aggregate_tweak_z(
z: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: &Challenge<S>,
verifying_key: &Element<S>,
) -> <<Self::Group as Group>::Field as Field>::Scalar
{
let t = tweak(&verifying_key, &[]);
z + t * challenge.clone().to_scalar()
}
/// compute tweaked signature_share
fn compute_tweaked_signature_share(
signer_nonces: &round1::SigningNonces,
binding_factor: frost::BindingFactor<S>,
group_commitment: frost_core::GroupCommitment<S>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &frost::keys::KeyPackage<S>,
challenge: Challenge<S>,
) -> round2::SignatureShare
{
let mut sn = signer_nonces.clone();
if group_commitment.y_is_odd() {
sn.negate_nonces();
}
let mut kp = key_package.clone();
if key_package.verifying_key().y_is_odd() {
kp.negate_signing_share();
}
frost::round2::compute_signature_share(
&sn,
binding_factor,
lambda_i,
&kp,
challenge,
)
}
/// calculate tweaked public key
fn tweaked_public_key(
public_key: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
tweaked_public_key(public_key, &[])
}
/// calculate tweaked R
fn tweaked_R(
R: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
AffinePoint::decompact(&R.to_affine().x()).unwrap().into()
}
/// tweaked secret
fn tweaked_secret_key(
secret: <<Self::Group as Group>::Field as Field>::Scalar,
public: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar
{
tweaked_secret_key(secret, &public, &[])
}
/// tweaked nonce
fn tweaked_nonce(
nonce: <<Self::Group as Group>::Field as Field>::Scalar,
R: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar
{
if R.to_affine().y_is_odd().into() {
-nonce
} else {
nonce
}
}
fn tweaked_group_commitment_share(
group_commitment_share: &Element<Self>,
group_commitment: &Element<Self>,
) -> Element<Self>
{
if group_commitment.to_affine().y_is_odd().into() {
-group_commitment_share
} else {
*group_commitment_share
}
}
fn tweaked_verifying_share(
verifying_share: &<Self::Group as Group>::Element,
verifying_key: &<Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element
{
let mut vs = verifying_share.clone();
if verifying_key.to_affine().y_is_odd().into() {
vs = -vs;
}
vs
}
}
impl RandomizedCiphersuite for Secp256K1Sha256 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(
(CONTEXT_STRING.to_owned() + "randomizer").as_bytes(),
m,
))
}
}
type S = Secp256K1Sha256;
/// A FROST(secp256k1, SHA-256) participant identifier.
pub type Identifier = frost::Identifier<S>;
/// FROST(secp256k1, SHA-256) keys, key generation, key shares.
pub mod keys {
use super::*;
use std::collections::BTreeMap;
/// The identifier list to use when generating key shares.
pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, S>;
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
pub fn generate_with_dealer<RNG: RngCore + CryptoRng>(
max_signers: u16,
min_signers: u16,
identifiers: IdentifierList,
mut rng: RNG,
) -> Result<(BTreeMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng)
}
/// Splits an existing key into FROST shares.
///
/// This is identical to [`generate_with_dealer`] but receives an existing key
/// instead of generating a fresh one. This is useful in scenarios where
/// the key needs to be generated externally or must be derived from e.g. a
/// seed phrase.
pub fn split<R: RngCore + CryptoRng>(
secret: &SigningKey,
max_signers: u16,
min_signers: u16,
identifiers: IdentifierList,
rng: &mut R,
) -> Result<(BTreeMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
frost::keys::split(secret, max_signers, min_signers, identifiers, rng)
}
/// Recompute the secret from t-of-n secret shares using Lagrange interpolation.
///
/// This can be used if for some reason the original key must be restored; e.g.
/// if threshold signing is not required anymore.
///
/// This is NOT required to sign with FROST; the whole point of FROST is being
/// able to generate signatures only using the shares, without having to
/// reconstruct the original key.
///
/// The caller is responsible for providing at least `min_signers` shares;
/// if less than that is provided, a different key will be returned.
pub fn reconstruct(secret_shares: &[KeyPackage]) -> Result<SigningKey, Error> {
frost::keys::reconstruct(secret_shares)
}
/// Secret and public key material generated by a dealer performing
/// [`generate_with_dealer`].
///
/// # Security
///
/// To derive a FROST(secp256k1, SHA-256) keypair, the receiver of the [`SecretShare`] *must* call
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<S>;
/// A secret scalar value representing a signer's share of the group secret.
pub type SigningShare = frost::keys::SigningShare<S>;
/// A public group element that represents a single signer's public verification share.
pub type VerifyingShare = frost::keys::VerifyingShare<S>;
/// A FROST(secp256k1, SHA-256) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
/// 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.
pub type KeyPackage = frost::keys::KeyPackage<S>;
/// Public data that contains all the signers' public keys as well as the
/// group public key.
///
/// Used for verification purposes before publishing a signature.
pub type PublicKeyPackage = frost::keys::PublicKeyPackage<S>;
/// Contains the commitments to the coefficients for our secret polynomial _f_,
/// used to generate participants' key shares.
///
/// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which
/// themselves are scalars) for a secret polynomial f, where f is used to
/// generate each ith participant's key share f(i). Participants use this set of
/// commitments to perform verifiable secret sharing.
///
/// Note that participants MUST be assured that they have the *same*
/// [`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.
pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment<S>;
pub mod dkg;
pub mod repairable;
}
/// FROST(secp256k1, SHA-256) Round 1 functionality and types.
pub mod round1 {
use crate::keys::SigningShare;
use super::*;
/// Comprised of FROST(secp256k1, SHA-256) hiding and binding nonces.
///
/// Note that [`SigningNonces`] must be used *only once* for a signing
/// operation; re-using nonces will result in leakage of a signer's long-lived
/// signing key.
pub type SigningNonces = frost::round1::SigningNonces<S>;
/// Published by each participant in the first round of the signing protocol.
///
/// This step can be batched if desired by the implementation. Each
/// SigningCommitment can be used for exactly *one* signature.
pub type SigningCommitments = frost::round1::SigningCommitments<S>;
/// A commitment to a signing nonce share.
pub type NonceCommitment = frost::round1::NonceCommitment<S>;
/// Performed once by each participant selected for the signing operation.
///
/// Generates the signing nonces and commitments to be used in the signing
/// operation.
pub fn commit<RNG>(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<S, RNG>(secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<S>;
/// FROST(secp256k1, SHA-256) Round 2 functionality and types, for signature share generation.
pub mod round2 {
use super::*;
/// A FROST(secp256k1, SHA-256) participant's signature share, which the Coordinator will aggregate with all other signer's
/// shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<S>;
/// Performed once by each participant selected for the signing operation.
///
/// Receives the message to be signed and a set of signing commitments and a set
/// of randomizing commitments to be used in that signing operation, including
/// that for this participant.
///
/// Assumes the participant has already determined which nonce corresponds with
/// the commitment that was assigned by the coordinator in the SigningPackage.
pub fn sign(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
) -> Result<SignatureShare, Error> {
frost::round2::sign(signing_package, signer_nonces, key_package)
}
}
/// A Schnorr signature on FROST(secp256k1, SHA-256).
pub type Signature = frost_core::Signature<S>;
/// Verifies each FROST(secp256k1, SHA-256) participant's signature share, and if all are valid,
/// aggregates the shares into a signature to publish.
///
/// Resulting signature is compatible with verification of a plain Schnorr
/// signature.
///
/// This operation is performed by a coordinator that can communicate with all
/// the signing participants before publishing the final signature. The
/// coordinator can be one of the participants or a semi-trusted third party
/// (who is trusted to not perform denial of service attacks, but does not learn
/// any secret information). Note that because the coordinator is trusted to
/// report misbehaving parties in order to avoid publishing an invalid
/// signature, if the coordinator themselves is a signer and misbehaves, they
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.
pub fn aggregate(
signing_package: &SigningPackage,
signature_shares: &BTreeMap<Identifier, round2::SignatureShare>,
pubkeys: &keys::PublicKeyPackage,
) -> Result<Signature, Error> {
frost::aggregate(signing_package, signature_shares, pubkeys)
}
/// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256).
pub type SigningKey = frost_core::SigningKey<S>;
/// A valid verifying key for Schnorr signatures on FROST(secp256k1, SHA-256).
pub type VerifyingKey = frost_core::VerifyingKey<S>;

View File

@ -0,0 +1,5 @@
mod batch;
mod coefficient_commitment;
mod deserialize;
mod proptests;
mod vss_commitment;

View File

@ -0,0 +1,24 @@
use rand::thread_rng;
use crate::*;
#[test]
fn check_batch_verify() {
let rng = thread_rng();
frost_core::tests::batch::batch_verify::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_bad_batch_verify() {
let rng = thread_rng();
frost_core::tests::batch::bad_batch_verify::<Secp256K1Sha256, _>(rng);
}
#[test]
fn empty_batch_verify() {
let rng = thread_rng();
frost_core::tests::batch::empty_batch_verify::<Secp256K1Sha256, _>(rng);
}

View File

@ -0,0 +1,46 @@
use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;
use crate::*;
// Tests for serialization and deserialization of CoefficientCommitment
lazy_static! {
pub static ref ELEMENTS: Value =
serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap();
}
#[test]
fn check_serialization_of_coefficient_commitment() {
let rng = thread_rng();
frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::<
Secp256K1Sha256,
_,
>(rng);
}
#[test]
fn check_create_coefficient_commitment() {
let rng = thread_rng();
frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::<
Secp256K1Sha256,
_,
>(rng);
}
#[test]
fn check_create_coefficient_commitment_error() {
frost_core::tests::coefficient_commitment::check_create_coefficient_commitment_error::<
Secp256K1Sha256,
>(&ELEMENTS);
}
#[test]
fn check_get_value_of_coefficient_commitment() {
let rng = thread_rng();
frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::<
Secp256K1Sha256,
_,
>(rng);
}

View File

@ -0,0 +1,37 @@
use crate::*;
#[test]
fn check_deserialize_non_canonical() {
let mut encoded_generator = <Secp256K1Sha256 as Ciphersuite>::Group::serialize(
&<Secp256K1Sha256 as Ciphersuite>::Group::generator(),
);
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert!(r.is_ok());
// The first byte should be 0x02 or 0x03. Set other value to
// create a non-canonical encoding.
encoded_generator[0] = 0xFF;
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert_eq!(r, Err(GroupError::MalformedElement));
// Besides the first byte, it is still possible to get non-canonical encodings.
// This is x = p + 2 which is non-canonical and maps to a valid prime-order point.
let encoded_point =
hex::decode("02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc31")
.unwrap()
.try_into()
.unwrap();
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::MalformedElement));
}
#[test]
fn check_deserialize_identity() {
// The identity is actually encoded as a single byte; but the API does not
// allow us to change that. Try to send something similar.
let encoded_identity = [0u8; 33];
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::MalformedElement));
}

View File

@ -0,0 +1,33 @@
use crate::*;
use frost_core::tests::proptests::{tweak_strategy, SignatureCase};
use proptest::prelude::*;
use rand_chacha::ChaChaRng;
use rand_core::SeedableRng;
proptest! {
#[test]
fn tweak_signature(
tweaks in prop::collection::vec(tweak_strategy(), (0,5)),
rng_seed in prop::array::uniform32(any::<u8>()),
) {
// Use a deterministic RNG so that test failures can be reproduced.
// Seeding with 64 bits of entropy is INSECURE and this code should
// not be copied outside of this test!
let rng = ChaChaRng::from_seed(rng_seed);
// Create a test case for each signature type.
let msg = b"test message for proptests";
let mut sig = SignatureCase::<Secp256K1Sha256>::new(rng, msg.to_vec());
// Apply tweaks to each case.
for t in &tweaks {
sig.apply_tweak(t);
}
assert!(sig.check());
}
}

View File

@ -0,0 +1,38 @@
use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;
use crate::*;
// Tests for serialization and deserialization VerifiableSecretSharingCommitment
lazy_static! {
pub static ref ELEMENTS: Value =
serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap();
}
#[test]
fn check_serialize_vss_commitment() {
let rng = thread_rng();
frost_core::tests::vss_commitment::check_serialize_vss_commitment::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_deserialize_vss_commitment() {
let rng = thread_rng();
frost_core::tests::vss_commitment::check_deserialize_vss_commitment::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_deserialize_vss_commitment_error() {
let rng = thread_rng();
frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::<Secp256K1Sha256, _>(
rng, &ELEMENTS,
);
}
#[test]
fn check_compute_public_key_package() {
let rng = thread_rng();
frost_core::tests::vss_commitment::check_compute_public_key_package::<Secp256K1Sha256, _>(rng);
}

View File

@ -0,0 +1,74 @@
#![cfg(feature = "serde")]
mod helpers;
use frost_secp256k1_tr::SigningKey;
use helpers::samples;
use rand::thread_rng;
#[allow(clippy::unnecessary_literal_unwrap)]
fn check_common_traits_for_type<T: Clone + Eq + PartialEq + std::fmt::Debug>(v: T) {
// Make sure can be debug-printed. This also catches if the Debug does not
// have an endless recursion (a popular mistake).
println!("{:?}", v);
// Test Clone and Eq
assert_eq!(v, v.clone());
// Make sure it can be unwrapped in a Result (which requires Debug).
let e: Result<T, ()> = Ok(v.clone());
assert_eq!(v, e.unwrap());
}
#[test]
fn check_signing_key_common_traits() {
let mut rng = thread_rng();
let signing_key = SigningKey::new(&mut rng);
check_common_traits_for_type(signing_key);
}
#[test]
fn check_signing_commitments_common_traits() {
let commitments = samples::signing_commitments();
check_common_traits_for_type(commitments);
}
#[test]
fn check_signing_package_common_traits() {
let signing_package = samples::signing_package();
check_common_traits_for_type(signing_package);
}
#[test]
fn check_signature_share_common_traits() {
let signature_share = samples::signature_share();
check_common_traits_for_type(signature_share);
}
#[test]
fn check_secret_share_common_traits() {
let secret_share = samples::secret_share();
check_common_traits_for_type(secret_share);
}
#[test]
fn check_key_package_common_traits() {
let key_package = samples::key_package();
check_common_traits_for_type(key_package);
}
#[test]
fn check_public_key_package_common_traits() {
let public_key_package = samples::public_key_package();
check_common_traits_for_type(public_key_package);
}
#[test]
fn check_round1_package_common_traits() {
let round1_package = samples::round1_package();
check_common_traits_for_type(round1_package);
}
#[test]
fn check_round2_package_common_traits() {
let round2_package = samples::round2_package();
check_common_traits_for_type(round2_package);
}

View File

@ -0,0 +1,5 @@
{
"elements": {
"invalid_element": "123456afdf4a7f88885ab26b20d18edb7d4d9589812a6cf1a5a1a09d3808dae5d8"
}
}

View File

@ -0,0 +1 @@
pub mod samples;

View File

@ -0,0 +1,15 @@
{
"scalar_generation": {
"random_scalar_1": "1847f6c4a85096e5dbc9e200c9691c5164f8e276d32d4a54ebaf4275474a1403",
"random_scalar_2": "eac5595269d108812eaa865bf62c703a2c128a61fa3bd4dc837b9314bc515204",
"random_scalar_3": "5b3b6084e41c273a39a8d9bbbd87fbcd626c07030142bf78c6c91247bf175700",
"random_scalar_sum": "5e48b09bf63dc6a1441d42187d1d885a38c896f51f633e6e76218944f27c7bc6"
},
"sigma_generation": {
"sigma_1": "ec3aa83140065181d75b746bfd6bbbbaf212bdfbb3a91670f924d1ca899cbc0c",
"sigma_2": "5dd288d659e0a2dd3ef7523a9cc4f80f4a7f919e9980005c7fbec0961d3fb500",
"sigma_3": "3e62e7461db9ca1ed2f1549a8114bbc87fa9242ce0012ed3f9ac9dcf23f4c30a",
"sigma_4": "684c44e7aba416a1982a8db8ec2a3095f5cc6a3f958a4716b69ae76524dd7200",
"sigma_sum": "f0bc5d356344d51f816ea8fa076fa029f7590120136bec7c6958b9081f7864d5"
}
}

View File

@ -0,0 +1,6 @@
{
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"element1": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"element2": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
"scalar1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}

View File

@ -0,0 +1,123 @@
//! Generate sample, fixed instances of structs for testing.
use std::collections::BTreeMap;
use frost_core::{Ciphersuite, Element, Group, Scalar};
use frost_secp256k1_tr::{
keys::{
dkg::{round1, round2},
KeyPackage, PublicKeyPackage, SecretShare, SigningShare, VerifiableSecretSharingCommitment,
VerifyingShare,
},
round1::{NonceCommitment, SigningCommitments},
round2::SignatureShare,
Field, Signature, SigningPackage, VerifyingKey,
};
type C = frost_secp256k1_tr::Secp256K1Sha256;
fn element1() -> Element<C> {
<C as Ciphersuite>::Group::generator()
}
fn element2() -> Element<C> {
element1() + element1()
}
fn scalar1() -> Scalar<C> {
let one = <<C as Ciphersuite>::Group as Group>::Field::one();
let three = one + one + one;
// To return a fixed non-small number, get the inverse of 3
<<C as Ciphersuite>::Group as Group>::Field::invert(&three)
.expect("nonzero elements have inverses")
}
/// Generate a sample SigningCommitments.
pub fn signing_commitments() -> SigningCommitments {
let serialized_element1 = <C as Ciphersuite>::Group::serialize(&element1());
let serialized_element2 = <C as Ciphersuite>::Group::serialize(&element2());
let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap();
let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap();
SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment)
}
/// Generate a sample SigningPackage.
pub fn signing_package() -> SigningPackage {
let identifier = 42u16.try_into().unwrap();
let commitments = BTreeMap::from([(identifier, signing_commitments())]);
let message = "hello world".as_bytes();
SigningPackage::new(commitments, message)
}
/// Generate a sample SignatureShare.
pub fn signature_share() -> SignatureShare {
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
SignatureShare::deserialize(serialized_scalar).unwrap()
}
/// Generate a sample SecretShare.
pub fn secret_share() -> SecretShare {
let identifier = 42u16.try_into().unwrap();
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let signing_share = SigningShare::deserialize(serialized_scalar).unwrap();
let vss_commitment =
VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap();
SecretShare::new(identifier, signing_share, vss_commitment)
}
/// Generate a sample KeyPackage.
pub fn key_package() -> KeyPackage {
let identifier = 42u16.try_into().unwrap();
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let signing_share = SigningShare::deserialize(serialized_scalar).unwrap();
let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap();
let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap();
KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2)
}
/// Generate a sample PublicKeyPackage.
pub fn public_key_package() -> PublicKeyPackage {
let identifier = 42u16.try_into().unwrap();
let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap();
let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap();
let verifying_shares = BTreeMap::from([(identifier, verifying_share)]);
PublicKeyPackage::new(verifying_shares, verifying_key)
}
/// Generate a sample round1::Package.
pub fn round1_package() -> round1::Package {
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let serialized_signature = serialized_element
.as_ref()
.iter()
.chain(serialized_scalar.as_ref().iter())
.cloned()
.collect::<Vec<u8>>()
.try_into()
.unwrap();
let vss_commitment =
VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap();
let signature = Signature::deserialize(serialized_signature).unwrap();
round1::Package::new(vss_commitment, signature)
}
/// Generate a sample round2::Package.
pub fn round2_package() -> round2::Package {
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let signing_share = SigningShare::deserialize(serialized_scalar).unwrap();
round2::Package::new(signing_share)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
{
"config": {
"MAX_PARTICIPANTS": "3",
"NUM_PARTICIPANTS": "2",
"MIN_PARTICIPANTS": "2",
"name": "FROST(secp256k1, SHA-256)",
"group": "secp256k1",
"hash": "SHA-256"
},
"inputs": {
"participant_list": [
1,
3
],
"group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114",
"verifying_key_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f",
"message": "74657374",
"share_polynomial_coefficients": [
"fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579"
],
"participant_shares": [
{
"identifier": 1,
"participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c"
},
{
"identifier": 2,
"participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984"
},
{
"identifier": 3,
"participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc"
}
]
},
"round_one_outputs": {
"outputs": [
{
"identifier": 1,
"hiding_nonce_randomness": "bda8e748e599187762cff956f03dc6ea13fc8e04491a0427b7e6e78600f41c52",
"binding_nonce_randomness": "2ca682429bf05df435b9927b8edb1d748278f3e42fa11ef358e49bbf4a1b780d",
"hiding_nonce": "58cd30723da418156fe9b71870a118e0bbc3d0353ba7c760f9bbc8d60c3dab29",
"binding_nonce": "c22289cc43b82ed938d4b2288efb7381c405fb59f5d43bddc543d98838c60b19",
"hiding_nonce_commitment": "024e34ab3a7ad6b4563dbfe97e9f1206b3378cceb2502491ed0fb709765e1e5ba8",
"binding_nonce_commitment": "03d4b1f3a61dc67e64dfb4abfccabb712f1f6914a6ec9b67749d171370453192cb",
"binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000001",
"binding_factor": "55a3e44879db6daf00c81eb28e828869560c0901f347baff524f1c91a6669604"
},
{
"identifier": 3,
"hiding_nonce_randomness": "70818dd5170672c4a4285fd593d4f222417f941f3118e1244955e7a1098a35d8",
"binding_nonce_randomness": "74ca2da071ed4a2a6cad5087d6758b48a558ab5861c61117fee05757e4b1309e",
"hiding_nonce": "a4109db0a5db30fac8cd1f4e272ff02e08258928f067d82c63d97279b114514a",
"binding_nonce": "ce3837bd963f0d81002279f7bb9eefceac64435f638885c2beae6f1dd881fd9e",
"hiding_nonce_commitment": "02d768658a1b94225645401a1512b803657770c7a21bf9ccccccfa09930a44951b",
"binding_nonce_commitment": "034570a4e5217ee8770a28401185f50b4fce4d3f3933a3af9df7ab39b42381d0eb",
"binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000003",
"binding_factor": "444c1cc7cfe48f4577cce65488f337a9cc8c33dea4cfa986eb590bd8e2b1fa2d"
}
]
},
"round_two_outputs": {
"outputs": [
{
"identifier": 1,
"sig_share": "d6641c4136ee5bf7135272c2cae2ffc1170f9ee44562cf1a1f0ab65c14cfcc4e"
},
{
"identifier": 3,
"sig_share": "e79e49ff66968247cb345da0f43d9ca83522e455478612602ac69e004b93e476"
}
]
},
"final_output": {
"sig": "030c776a9516a77808b70a31e74f1464814a6fcf897fb3a6bd84c7a9a9a7a5bcb8c0828f771deb8b8ab07fbfc2138ddbda6ff32bd9f7a673459538bc96d1f72e70"
}
}

View File

@ -0,0 +1,233 @@
use frost_secp256k1_tr::*;
use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;
#[test]
fn check_zero_key_fails() {
frost_core::tests::ciphersuite_generic::check_zero_key_fails::<Secp256K1Sha256>();
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_dkg_part1_fails_with_invalid_signers_min_signers() {
let rng = thread_rng();
let min_signers = 1;
let max_signers = 3;
let error = Error::InvalidMinSigners;
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_dkg_part1_fails_with_min_signers_greater_than_max() {
let rng = thread_rng();
let min_signers = 3;
let max_signers = 2;
let error: frost_core::Error<Secp256K1Sha256> = Error::InvalidMinSigners;
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_dkg_part1_fails_with_invalid_signers_max_signers() {
let rng = thread_rng();
let min_signers = 3;
let max_signers = 1;
let error = Error::InvalidMaxSigners;
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_rts() {
let rng = thread_rng();
frost_core::tests::repairable::check_rts::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_sign_with_dealer_fails_with_invalid_min_signers() {
let rng = thread_rng();
let min_signers = 1;
let max_signers = 3;
let error = Error::InvalidMinSigners;
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() {
let rng = thread_rng();
let min_signers = 3;
let max_signers = 2;
let error: frost_core::Error<Secp256K1Sha256> = Error::InvalidMinSigners;
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_sign_with_dealer_fails_with_invalid_max_signers() {
let rng = thread_rng();
let min_signers = 3;
let max_signers = 1;
let error = Error::InvalidMaxSigners;
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
/// This is testing that Shamir's secret sharing to compute and arbitrary
/// value is working.
#[test]
fn check_share_generation_secp256k1_sha256() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_share_generation::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_share_generation_fails_with_invalid_min_signers() {
let rng = thread_rng();
let min_signers = 0;
let max_signers = 3;
let error = Error::InvalidMinSigners;
frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_share_generation_fails_with_min_signers_greater_than_max() {
let rng = thread_rng();
let min_signers = 3;
let max_signers = 2;
let error: frost_core::Error<Secp256K1Sha256> = Error::InvalidMinSigners;
frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
#[test]
fn check_share_generation_fails_with_invalid_max_signers() {
let rng = thread_rng();
let min_signers = 3;
let max_signers = 0;
let error = Error::InvalidMaxSigners;
frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::<
Secp256K1Sha256,
_,
>(min_signers, max_signers, error, rng);
}
lazy_static! {
pub static ref VECTORS: Value =
serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim())
.expect("Test vector is valid JSON");
pub static ref VECTORS_BIG_IDENTIFIER: Value =
serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim())
.expect("Test vector is valid JSON");
}
#[test]
fn check_sign_with_test_vectors() {
frost_core::tests::vectors::check_sign_with_test_vectors::<Secp256K1Sha256>(&VECTORS);
}
#[test]
fn check_sign_with_test_vectors_with_big_identifiers() {
frost_core::tests::vectors::check_sign_with_test_vectors::<Secp256K1Sha256>(
&VECTORS_BIG_IDENTIFIER,
);
}
#[test]
fn check_error_culprit() {
frost_core::tests::ciphersuite_generic::check_error_culprit::<Secp256K1Sha256>();
}
#[test]
fn check_identifier_derivation() {
frost_core::tests::ciphersuite_generic::check_identifier_derivation::<Secp256K1Sha256>();
}
// Explicit test which is used in a documentation snippet
#[test]
#[allow(unused_variables)]
fn check_identifier_generation() -> Result<(), Error> {
// ANCHOR: dkg_identifier
let participant_identifier = Identifier::try_from(7u16)?;
let participant_identifier = Identifier::derive("alice@example.com".as_bytes())?;
// ANCHOR_END: dkg_identifier
Ok(())
}
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Secp256K1Sha256,
_,
>(rng);
}
#[test]
fn check_sign_with_missing_identifier() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::<Secp256K1Sha256, _>(
rng,
);
}
#[test]
fn check_sign_with_incorrect_commitments() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::<
Secp256K1Sha256,
_,
>(rng);
}

View File

@ -0,0 +1,123 @@
//! Test for recreating packages from their components, which shows that they
//! can be serialized and deserialized as the user wishes.
use frost_secp256k1_tr::{
keys::{
dkg::{round1, round2},
KeyPackage, PublicKeyPackage, SecretShare,
},
round1::SigningCommitments,
round2::SignatureShare,
SigningPackage,
};
mod helpers;
use helpers::samples;
/// Check if SigningCommitments can be recreated.
#[test]
fn check_signing_commitments_recreation() {
let commitments = samples::signing_commitments();
let hiding = commitments.hiding();
let binding = commitments.binding();
let new_commitments = SigningCommitments::new(*hiding, *binding);
assert!(commitments == new_commitments);
}
/// Check if SigningPackage can be recreated.
#[test]
fn check_signing_package_recreation() {
let signing_package = samples::signing_package();
let commitments = signing_package.signing_commitments();
let message = signing_package.message();
let new_signing_package = SigningPackage::new(commitments.clone(), message);
assert!(signing_package == new_signing_package);
}
/// Check if SignatureShare can be recreated.
#[test]
fn check_signature_share_recreation() {
let signature_share = samples::signature_share();
let encoded = signature_share.serialize();
let new_signature_share = SignatureShare::deserialize(encoded).unwrap();
assert!(signature_share == new_signature_share);
}
/// Check if SecretShare can be recreated.
#[test]
fn check_secret_share_recreation() {
let secret_share = samples::secret_share();
let identifier = secret_share.identifier();
let value = secret_share.signing_share();
let commitment = secret_share.commitment();
let new_secret_share = SecretShare::new(*identifier, *value, commitment.clone());
assert!(secret_share == new_secret_share);
}
/// Check if KeyPackage can be recreated.
#[test]
fn check_key_package_recreation() {
let key_package = samples::key_package();
let identifier = key_package.identifier();
let signing_share = key_package.signing_share();
let verifying_share = key_package.verifying_share();
let verifying_key = key_package.verifying_key();
let min_signers = key_package.min_signers();
let new_key_package = KeyPackage::new(
*identifier,
*signing_share,
*verifying_share,
*verifying_key,
*min_signers,
);
assert!(key_package == new_key_package);
}
/// Check if PublicKeyPackage can be recreated.
#[test]
fn check_public_key_package_recreation() {
let public_key_package = samples::public_key_package();
let verifying_shares = public_key_package.verifying_shares();
let verifying_key = public_key_package.verifying_key();
let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key);
assert!(public_key_package == new_public_key_package);
}
/// Check if round1::Package can be recreated.
#[test]
fn check_round1_package_recreation() {
let round1_package = samples::round1_package();
let vss_commitment = round1_package.commitment();
let signature = round1_package.proof_of_knowledge();
let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature);
assert!(round1_package == new_round1_package);
}
/// Check if round2::Package can be recreated.
#[test]
fn check_round2_package_recreation() {
let round2_package = samples::round2_package();
let signing_share = round2_package.signing_share();
let new_round2_package = round2::Package::new(*signing_share);
assert!(round2_package == new_round2_package);
}

View File

@ -0,0 +1,10 @@
use frost_secp256k1_tr::Secp256K1Sha256;
use rand::thread_rng;
#[test]
fn check_randomized_sign_with_dealer() {
let rng = thread_rng();
let (_msg, _group_signature, _group_pubkey) =
frost_rerandomized::tests::check_randomized_sign_with_dealer::<Secp256K1Sha256, _>(rng);
}

View File

@ -0,0 +1,632 @@
#![cfg(feature = "serde")]
mod helpers;
use frost_secp256k1_tr::{
keys::{
dkg::{round1, round2},
KeyPackage, PublicKeyPackage, SecretShare,
},
round1::SigningCommitments,
round2::SignatureShare,
SigningPackage,
};
use helpers::samples;
#[test]
fn check_signing_commitments_serialization() {
let commitments = samples::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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}"#;
let decoded_commitments: SigningCommitments = serde_json::from_str(json).unwrap();
assert!(commitments == decoded_commitments);
let invalid_json = "{}";
assert!(serde_json::from_str::<SigningCommitments>(invalid_json).is_err());
// Wrong ciphersuite
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST(Wrong, SHA-512)"
},
"hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}"#;
assert!(serde_json::from_str::<SigningCommitments>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}"#;
assert!(serde_json::from_str::<SigningCommitments>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"foo": "0000000000000000000000000000000000000000000000000000000000000000",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}"#;
assert!(serde_json::from_str::<SigningCommitments>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST(Ed25519, SHA-512)"
},
"hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
"extra": 1
}"#;
assert!(serde_json::from_str::<SigningCommitments>(invalid_json).is_err());
}
#[test]
fn check_signing_package_serialization() {
let signing_package = samples::signing_package();
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::<SigningPackage>(invalid_json).is_err());
let json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_commitments": {
"000000000000000000000000000000000000000000000000000000000000002a": {
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
}"#;
let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap();
assert!(signing_package == decoded_signing_package);
// Invalid identifier
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_commitments": {
"0000000000000000000000000000000000000000000000000000000000000000": {
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_commitments": {
"000000000000000000000000000000000000000000000000000000000000002a": {
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_commitments": {
"000000000000000000000000000000000000000000000000000000000000002a": {
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_commitments": {
"000000000000000000000000000000000000000000000000000000000000002a": {
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64",
"extra": 1
}
"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
}
#[test]
fn check_signature_share_serialization() {
let signature_share = samples::signature_share();
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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}"#;
let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap();
assert!(signature_share == decoded_commitments);
let invalid_json = "{}";
assert!(serde_json::from_str::<SignatureShare>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}"#;
assert!(serde_json::from_str::<SignatureShare>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
}
}"#;
assert!(serde_json::from_str::<SignatureShare>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"extra": 1
}"#;
assert!(serde_json::from_str::<SignatureShare>(invalid_json).is_err());
}
#[test]
fn check_secret_share_serialization() {
let secret_share = samples::secret_share();
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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
]
}"#;
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::<SecretShare>(invalid_json).is_err());
// Invalid identifier
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "0000000000000000000000000000000000000000000000000000000000000000",
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
]
}"#;
assert!(serde_json::from_str::<SecretShare>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
]
}"#;
assert!(serde_json::from_str::<SecretShare>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
]
}"#;
assert!(serde_json::from_str::<SecretShare>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
]
"extra": 1,
}"#;
assert!(serde_json::from_str::<SecretShare>(invalid_json).is_err());
}
#[test]
fn check_key_package_serialization() {
let key_package = samples::key_package();
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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"min_signers": 2
}"#;
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::<KeyPackage>(invalid_json).is_err());
// Invalid identifier
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "0000000000000000000000000000000000000000000000000000000000000000",
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"min_signers": 2
}"#;
assert!(serde_json::from_str::<KeyPackage>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}"#;
assert!(serde_json::from_str::<KeyPackage>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}"#;
assert!(serde_json::from_str::<KeyPackage>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"extra_field": 1
}"#;
assert!(serde_json::from_str::<KeyPackage>(invalid_json).is_err());
// Invalid version
let invalid_json = r#"{
"header": {
"version": 1,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"public": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"group_public": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"min_signers": 2
}"#;
assert!(serde_json::from_str::<KeyPackage>(invalid_json).is_err());
}
#[test]
fn check_public_key_package_serialization() {
let public_key_package = samples::public_key_package();
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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"verifying_shares": {
"000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
},
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}"#;
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::<PublicKeyPackage>(invalid_json).is_err());
// Invalid identifier
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"verifying_shares": {
"0000000000000000000000000000000000000000000000000000000000000000": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
},
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}"#;
assert!(serde_json::from_str::<PublicKeyPackage>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"verifying_shares": {
"000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
},
"foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}"#;
assert!(serde_json::from_str::<PublicKeyPackage>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"verifying_shares": {
"000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}
}"#;
assert!(serde_json::from_str::<PublicKeyPackage>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"verifying_shares": {
"000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
},
"verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"extra": 1
}"#;
assert!(serde_json::from_str::<PublicKeyPackage>(invalid_json).is_err());
}
#[test]
fn check_round1_package_serialization() {
let round1_package = samples::round1_package();
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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
],
"proof_of_knowledge": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}"#;
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::<round1::Package>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
],
"foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}"#;
assert!(serde_json::from_str::<round1::Package>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
]
}"#;
assert!(serde_json::from_str::<round1::Package>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"commitment": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
],
"proof_of_knowledge": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"extra": 1
}"#;
assert!(serde_json::from_str::<round1::Package>(invalid_json).is_err());
}
#[test]
fn check_round2_package_serialization() {
let round2_package = samples::round2_package();
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#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}"#;
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::<round2::Package>(invalid_json).is_err());
// Invalid field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
}"#;
assert!(serde_json::from_str::<round2::Package>(invalid_json).is_err());
// Missing field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
}
}"#;
assert!(serde_json::from_str::<round2::Package>(invalid_json).is_err());
// Extra field
let invalid_json = r#"{
"header": {
"version": 0,
"ciphersuite": "FROST-secp256k1-SHA256-TR-v1"
},
"signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81",
"extra": 1
}"#;
assert!(serde_json::from_str::<round2::Package>(invalid_json).is_err());
}

View File

@ -0,0 +1,94 @@
#![cfg(feature = "serialization")]
mod helpers;
use frost_secp256k1_tr::{
keys::{
dkg::{round1, round2},
KeyPackage, PublicKeyPackage, SecretShare,
},
round1::SigningCommitments,
round2::SignatureShare,
SigningPackage,
};
use helpers::samples;
use insta::assert_snapshot;
#[test]
fn check_signing_commitments_postcard_serialization() {
let commitments = samples::signing_commitments();
let bytes: Vec<_> = commitments.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(
commitments,
SigningCommitments::deserialize(&bytes).unwrap()
);
}
#[test]
fn check_signing_package_postcard_serialization() {
let signing_package = samples::signing_package();
let bytes: Vec<_> = signing_package.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(
signing_package,
SigningPackage::deserialize(&bytes).unwrap()
);
}
#[test]
fn check_signature_share_postcard_serialization() {
let signature_share = samples::signature_share();
let bytes = signature_share.serialize();
assert_snapshot!(hex::encode(bytes));
assert_eq!(signature_share, SignatureShare::deserialize(bytes).unwrap());
}
#[test]
fn check_secret_share_postcard_serialization() {
let secret_share = samples::secret_share();
let bytes: Vec<_> = secret_share.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(secret_share, SecretShare::deserialize(&bytes).unwrap());
}
#[test]
fn check_key_package_postcard_serialization() {
let key_package = samples::key_package();
let bytes: Vec<_> = key_package.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(key_package, KeyPackage::deserialize(&bytes).unwrap());
}
#[test]
fn check_public_key_package_postcard_serialization() {
let public_key_package = samples::public_key_package();
let bytes: Vec<_> = public_key_package.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(
public_key_package,
PublicKeyPackage::deserialize(&bytes).unwrap()
);
}
#[test]
fn check_round1_package_postcard_serialization() {
let round1_package = samples::round1_package();
let bytes: Vec<_> = round1_package.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(
round1_package,
round1::Package::deserialize(&bytes).unwrap()
);
}
#[test]
fn check_round2_package_postcard_serialization() {
let round2_package = samples::round2_package();
let bytes: Vec<_> = round2_package.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(
round2_package,
round2::Package::deserialize(&bytes).unwrap()
);
}

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b810279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab301000000000000000000000000000000000000000000000000000000000000002a0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab3010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798410279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(bytes)"
---
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5

View File

@ -0,0 +1,5 @@
---
source: frost-secp256k1-tr/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab301000000000000000000000000000000000000000000000000000000000000002a00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee50b68656c6c6f20776f726c64

View File

@ -290,6 +290,19 @@ fn main() -> ExitCode {
"<S>",
],
),
(
"frost-secp256k1-tr",
&[
"Secp256K1Sha256",
"secp256k1 curve",
"Secp256K1",
"FROST(secp256k1, SHA-256)",
"FROST-secp256k1-SHA256-TR-v1",
"secp256k1_sha256",
"secp256k1",
"<S>",
],
),
] {
// Some test use "sample" values. To make these tests work for another ciphersuites,
// these values must be replaced. To make it cleaner, the strings are