Compare commits

...

24 Commits

Author SHA1 Message Date
zebra-lucky beadd109b7
Merge 20c2c98a93 into 5c8d90501a 2024-04-18 10:27:51 +01:00
dependabot[bot] 5c8d90501a
Bump peaceiris/actions-mdbook from 1.2.0 to 2.0.0 (#637)
Bumps [peaceiris/actions-mdbook](https://github.com/peaceiris/actions-mdbook) from 1.2.0 to 2.0.0.
- [Release notes](https://github.com/peaceiris/actions-mdbook/releases)
- [Changelog](https://github.com/peaceiris/actions-mdbook/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-mdbook/compare/v1.2.0...v2.0.0)

---
updated-dependencies:
- dependency-name: peaceiris/actions-mdbook
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-17 22:10:51 +00:00
dependabot[bot] af0fc6e490
Bump reviewdog/action-actionlint from 1.43.0 to 1.44.0 (#641)
Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/reviewdog/action-actionlint/releases)
- [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.43.0...v1.44.0)

---
updated-dependencies:
- dependency-name: reviewdog/action-actionlint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-17 21:48:55 +00:00
dependabot[bot] 9ca9f59360
Bump codecov/codecov-action from 4.1.0 to 4.3.0 (#638)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.0 to 4.3.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.1.0...v4.3.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-17 21:48:51 +00:00
conduition 20c2c98a93 add integration tests to cover taproot-tweaked signing 2024-03-16 20:29:56 +02:00
conduition 5d2d683e1d fix reference to internal function 2024-03-16 20:29:56 +02:00
conduition 8f5264685b remove unneeded Into invocation 2024-03-16 20:29:56 +02:00
conduition c1b8663acb fix reference to SigningTarget.message instead of cloned signing target 2024-03-16 20:29:56 +02:00
zebra-lucky 1c085ba38a remove debugging assert_eq from tests/vectors.rs 2024-03-07 01:24:53 +02:00
zebra-lucky 1268f5cc60 Fix typo for VerifyingKey.effective_key 2024-03-05 21:18:38 +02:00
conduition 155dfa632b add effective_key method to VerifyingKey
This method allows the group to export their tweaked
VerifyingKey, so 3rd parties can verify their signatures
without learning the taproot tweak.
2024-03-05 21:18:38 +02:00
conduition e5b3f5dd90 encapsulate BIP341 tapscript commitment in new SigningTarget type
I added the SigningTarget type, which encapsulates both the message
to be signed, and also the tapscript merkle root which the signature
should commit to. This allows a FROST group to dynamically select
what taproot tweak they would like to commit their signatures to,
and to optionally elect not to commit to any tweak at all if desired.
2024-03-05 21:18:38 +02:00
zebra-lucky c63a3ca699 update frost-secp256-tr code to changes from 1.0.0 2024-02-24 15:56:12 +02:00
zebra-lucky 0ed163f93a fix docstrings in frost-core/src/traits.rs 2024-02-24 15:07:37 +02:00
conduition 142556fabd Refactor Ciphersuite taproot methods for universal applicability (#2)
The taproot compatibility methods added to Ciphersuite were
very specific to taproot. I renamed them, and tidied up their
usage to remove unnecessary if/else branches. This should make
them applicable to any ciphersuite which needs to modify such
internal processes of the FROST algorithm.

This change should be a pure refactor with no logical changes.
2024-02-24 15:07:37 +02:00
Conrado Gouvea a66b9a2e7c clippy fixes 2024-02-24 15:07:37 +02:00
Conrado Gouvea bdc8fb4cbf fix gencode-related issues 2024-02-24 15:07:37 +02:00
Conrado Gouvea 00cdfe59ae cargo fmt 2024-02-24 15:07:37 +02:00
zebra-lucky 20da59a67a add DKG vector test for frost-secp256k1-tr 2024-02-24 15:07:37 +02:00
zebra-lucky 6d8be7c45b give more consistent names to taproot functions 2024-02-24 15:07:37 +02:00
zebra-lucky a3071302dd additional fixes for use of tweaked pubkey 2024-02-24 15:07:37 +02:00
David Wong 8204166b93 fix use of tweaked public key 2024-02-24 15:07:37 +02:00
zebra-lucky ab6b0d09d4 run cargo fmt on frost-secp256k1-tr 2024-02-24 15:07:37 +02:00
zebra-lucky b380fd589b add frost-secp256k1-tr crate (BIP340/BIP341) 2024-02-24 15:07:37 +02:00
74 changed files with 4800 additions and 128 deletions

View File

@ -44,4 +44,4 @@ jobs:
run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' --output-path lcov.info
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v4.1.0
uses: codecov/codecov-action@v4.3.0

View File

@ -50,7 +50,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1.2.0
uses: peaceiris/actions-mdbook@v2.0.0
with:
mdbook-version: '0.4.18'

View File

@ -143,7 +143,7 @@ jobs:
continue-on-error: true
steps:
- uses: actions/checkout@v4.1.1
- uses: reviewdog/action-actionlint@v1.43.0
- uses: reviewdog/action-actionlint@v1.44.0
with:
level: warning
fail_on_error: false

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

@ -22,6 +22,7 @@ use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *};
pub struct Item<C: Ciphersuite> {
vk: VerifyingKey<C>,
sig: Signature<C>,
sig_params: C::SigningParameters,
c: Challenge<C>,
}
@ -32,9 +33,15 @@ 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 sig_target = SigningTarget::from_message(msg);
let c = <C>::challenge(&sig.R, &vk, &sig_target);
Self { vk, sig, c }
Self {
vk,
sig,
sig_params: sig_target.sig_params,
c,
}
}
}
@ -50,7 +57,8 @@ where
/// requires borrowing the message data, the `Item` type is unlinked
/// from the lifetime of the message.
pub fn verify_single(self) -> Result<(), Error<C>> {
self.vk.verify_prehashed(self.c, &self.sig)
self.vk
.verify_prehashed(self.c, &self.sig, &self.sig_params)
}
}
@ -118,7 +126,8 @@ where
for item in self.signatures.iter() {
let z = item.sig.z;
let R = item.sig.R;
let R = <C>::effective_nonce_element(item.sig.R);
let vk = <C>::effective_pubkey_element(&item.vk, &item.sig_params);
let blind = <<C::Group as Group>::Field>::random(&mut rng);
@ -129,7 +138,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

@ -121,6 +121,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>
@ -687,6 +692,11 @@ where
min_signers,
}
}
/// Negate `SigningShare`.
pub fn negate_signing_share(&mut self) {
self.signing_share.negate();
}
}
#[cfg(feature = "serialization")]

View File

@ -51,7 +51,7 @@ use scalar_mul::VartimeMultiscalarMul;
pub use serde;
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use traits::{Ciphersuite, Element, Field, Group, Scalar};
pub use traits::{Ciphersuite, Element, Field, Group, Scalar, SigningParameters};
pub use verifying_key::VerifyingKey;
/// A type refinement for the scalar field element representing the per-message _[challenge]_.
@ -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
}
@ -342,6 +340,53 @@ fn derive_interpolating_value<C: Ciphersuite>(
)
}
/// The data which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
pub struct SigningTarget<C: Ciphersuite> {
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)
)]
message: Vec<u8>,
#[cfg_attr(feature = "serde", serde(default))]
sig_params: C::SigningParameters,
}
impl<C: Ciphersuite> SigningTarget<C> {
/// Construct a signing target from a message and additional signing parameters.
pub fn new<T: AsRef<[u8]>, P: Into<C::SigningParameters>>(
message: T,
sig_params: P,
) -> SigningTarget<C> {
SigningTarget {
message: message.as_ref().to_vec(),
sig_params: sig_params.into(),
}
}
/// Constructs a signing target from an arbitrary message.
/// This populates [the `sig_params` field][SigningTarget::sig_params] with
/// the [`Default`] instance of the [`Ciphersuite::SigningParameters`].
pub fn from_message<T: AsRef<[u8]>>(message: T) -> SigningTarget<C> {
SigningTarget {
message: message.as_ref().to_vec(),
sig_params: C::SigningParameters::default(),
}
}
}
impl<C: Ciphersuite, T: AsRef<[u8]>> From<T> for SigningTarget<C> {
fn from(message: T) -> Self {
Self::from_message(message)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
@ -355,18 +400,9 @@ pub struct SigningPackage<C: Ciphersuite> {
/// The set of commitments participants published in the first round of the
/// protocol.
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
/// Message which each participant will sign.
///
/// Each signer should perform protocol-specific verification on the
/// message.
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)
)]
message: Vec<u8>,
/// The message and parameters which each participant will use to sign.
/// Each signer should perform protocol-specific verification on the signing target.
sig_target: SigningTarget<C>,
}
impl<C> SigningPackage<C>
@ -376,14 +412,19 @@ where
/// Create a new `SigningPackage`
///
/// The `signing_commitments` are sorted by participant `identifier`.
///
/// The `sig_target` can be any bytes-like type that implements `AsRef<[u8]>`.
/// Some ciphersuites like `frost-secp256k1-tr` allow customization of the signing
/// process by embedding additional parameters into a [`SigningTarget`], but this
/// is optional and not required by most ciphersuites.
pub fn new(
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
message: &[u8],
sig_target: impl Into<SigningTarget<C>>,
) -> SigningPackage<C> {
SigningPackage {
header: Header::default(),
signing_commitments,
message: message.to_vec(),
sig_target: sig_target.into(),
}
}
@ -395,6 +436,11 @@ where
self.signing_commitments.get(identifier).copied()
}
/// Returns the message to be signed.
pub fn message(&self) -> &[u8] {
&self.sig_target.message
}
/// Compute the preimages to H1 to compute the per-signer binding factors
// We separate this out into its own method so it can be tested
#[cfg_attr(feature = "internals", visibility::make(pub))]
@ -414,7 +460,7 @@ where
// The message is hashed with H4 to force the variable-length message
// into a fixed-length byte string, same for hashing the variable-sized
// (between runs of the protocol) set of group commitments, but with H5.
binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref());
binding_factor_input_prefix.extend_from_slice(C::H4(self.message()).as_ref());
binding_factor_input_prefix.extend_from_slice(
C::H5(&round1::encode_group_commitments(self.signing_commitments())[..]).as_ref(),
);
@ -465,6 +511,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,15 +636,17 @@ where
z = z + signature_share.share;
}
let signature = Signature {
R: group_commitment.0,
let signature: Signature<C> = <C>::aggregate_sig_finalize(
z,
};
group_commitment.0,
&pubkeys.verifying_key,
&signing_package.sig_target,
);
// Verify the aggregate signature
let verification_result = pubkeys
.verifying_key
.verify(signing_package.message(), &signature);
.verify(signing_package.sig_target.clone(), &signature);
// Only if the verification of the aggregate signature failed; verify each share to find the cheater.
// This approach is more efficient since we don't need to verify all shares
@ -601,10 +654,10 @@ 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(),
&signing_package.sig_target,
);
// Verify the signature shares.
@ -636,6 +689,9 @@ where
signer_pubkey,
lambda_i,
&challenge,
&group_commitment,
&pubkeys.verifying_key,
&signing_package.sig_target.sig_params,
)?;
}

View File

@ -53,6 +53,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(
@ -316,6 +321,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.
@ -393,6 +404,14 @@ where
#[derive(Clone, Copy, PartialEq)]
pub struct GroupCommitmentShare<C: Ciphersuite>(pub(super) Element<C>);
impl<C: Ciphersuite> GroupCommitmentShare<C> {
/// Return the underlying element.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn to_element(self) -> Element<C> {
self.0
}
}
/// Encode the list of group signing commitments.
///
/// Implements [`encode_group_commitment_list()`] from the spec.

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")]
@ -84,6 +84,7 @@ where
#[cfg(any(feature = "cheater-detection", feature = "internals"))]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
#[allow(clippy::too_many_arguments)]
pub(crate) fn verify(
&self,
identifier: Identifier<C>,
@ -91,9 +92,16 @@ where
verifying_share: &frost::keys::VerifyingShare<C>,
lambda_i: Scalar<C>,
challenge: &Challenge<C>,
group_commitment: &frost::GroupCommitment<C>,
verifying_key: &frost::VerifyingKey<C>,
sig_params: &C::SigningParameters,
) -> Result<(), Error<C>> {
let commitment_share =
<C>::effective_commitment_share(group_commitment_share.clone(), &group_commitment);
let vsh = <C>::effective_verifying_share(&verifying_share, &verifying_key, &sig_params);
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,
@ -151,9 +159,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,
@ -215,19 +221,21 @@ 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(),
&signing_package.sig_target,
);
// Compute the Schnorr signature share.
let signature_share = compute_signature_share(
let signature_share = <C>::compute_signature_share(
signer_nonces,
binding_factor,
group_commitment,
lambda_i,
key_package,
challenge,
&signing_package.sig_target.sig_params,
);
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,10 @@
use rand_core::{CryptoRng, RngCore};
use crate::{random_nonzero, Ciphersuite, Error, Field, Group, Scalar, Signature, VerifyingKey};
use crate::{
random_nonzero, Challenge, Ciphersuite, Error, Field, Group, Scalar, Signature, SigningTarget,
VerifyingKey,
};
/// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`].
#[derive(Copy, Clone, PartialEq, Eq)]
@ -43,18 +46,25 @@ where
<<C::Group as Group>::Field as Field>::serialize(&self.scalar)
}
/// 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);
/// Create a signature on the given `sig_target` using this `SigningKey`.
pub fn sign<R: RngCore + CryptoRng>(
&self,
mut rng: R,
sig_target: impl Into<SigningTarget<C>>,
) -> Signature<C> {
let sig_target = sig_target.into();
let public = VerifyingKey::<C>::from(*self);
let secret = <C>::effective_secret_key(self.scalar, &public, &sig_target.sig_params);
let mut k = random_nonzero::<C, R>(&mut rng);
let R = <C::Group>::generator() * k;
k = <C>::effective_nonce_secret(k, &R);
// Generate Schnorr challenge
let c = crate::challenge::<C>(&R, &VerifyingKey::<C>::from(*self), msg);
let c: Challenge<C> = <C>::challenge(&R, &public, &sig_target);
let z = k + (c.0 * self.scalar);
Signature { R, z }
<C>::single_sig_finalize(k, R, secret, &c, &public, &sig_target.sig_params)
}
/// Creates a SigningKey from a scalar.

View File

@ -5,7 +5,8 @@ use std::{collections::BTreeMap, convert::TryFrom};
use crate as frost;
use crate::{
keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, VerifyingKey,
keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningTarget,
VerifyingKey,
};
use rand_core::{CryptoRng, RngCore};
@ -100,7 +101,8 @@ pub fn check_share_generation_fails_with_invalid_signers<C: Ciphersuite, R: RngC
/// Test FROST signing with trusted dealer with a Ciphersuite.
pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
mut rng: R,
) -> (Vec<u8>, Signature<C>, VerifyingKey<C>) {
signing_target: SigningTarget<C>,
) -> (SigningTarget<C>, Signature<C>, VerifyingKey<C>) {
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////
@ -144,10 +146,11 @@ pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
.collect(),
&mut rng,
pubkeys.clone(),
signing_target.clone(),
);
assert_eq!(r, Err(Error::InvalidSignature));
check_sign(min_signers, key_packages, rng, pubkeys).unwrap()
check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap()
}
/// Test FROST signing with trusted dealer fails with invalid numbers of signers.
@ -192,7 +195,8 @@ pub fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
key_packages: BTreeMap<frost::Identifier<C>, frost::keys::KeyPackage<C>>,
mut rng: R,
pubkey_package: PublicKeyPackage<C>,
) -> Result<(Vec<u8>, Signature<C>, VerifyingKey<C>), Error<C>> {
signing_target: SigningTarget<C>,
) -> Result<(SigningTarget<C>, Signature<C>, VerifyingKey<C>), Error<C>> {
let mut nonces_map: BTreeMap<frost::Identifier<C>, frost::round1::SigningNonces<C>> =
BTreeMap::new();
let mut commitments_map: BTreeMap<frost::Identifier<C>, frost::round1::SigningCommitments<C>> =
@ -220,8 +224,7 @@ pub fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = BTreeMap::new();
let message = "message to sign".as_bytes();
let signing_package = frost::SigningPackage::new(commitments_map, message);
let signing_package = frost::SigningPackage::new(commitments_map, signing_target.clone());
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
@ -268,7 +271,14 @@ pub fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
// key (the verification key).
pubkey_package
.verifying_key
.verify(message, &group_signature)?;
.verify(signing_target.clone(), &group_signature)?;
// Check that the effective verifying key can be verified against the raw message,
// without exposing the SigningParameters.
pubkey_package
.verifying_key
.effective_key(signing_target.sig_params())
.verify(signing_target.message(), &group_signature)?;
// Check that the threshold signature can be verified by the group public
// key (the verification key) from KeyPackage.verifying_key
@ -277,11 +287,11 @@ pub fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
key_package
.verifying_key
.verify(message, &group_signature)?;
.verify(signing_target.clone(), &group_signature)?;
}
Ok((
message.to_owned(),
signing_target,
group_signature,
pubkey_package.verifying_key,
))
@ -301,7 +311,7 @@ fn check_sign_errors<C: Ciphersuite + PartialEq>(
.find(|&&id| id != key_package.identifier)
.unwrap();
commitments.remove(&id);
let signing_package = frost::SigningPackage::new(commitments, signing_package.message());
let signing_package = frost::SigningPackage::new(commitments, signing_package.sig_target);
let r = frost::round2::sign(&signing_package, &signing_nonces, &key_package);
assert_eq!(r, Err(Error::IncorrectNumberOfCommitments));
@ -364,7 +374,8 @@ fn check_aggregate_invalid_share_identifier_for_verifying_shares<C: Ciphersuite
/// Test FROST signing with DKG with a Ciphersuite.
pub fn check_sign_with_dkg<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
mut rng: R,
) -> (Vec<u8>, Signature<C>, VerifyingKey<C>)
signing_target: SigningTarget<C>,
) -> (SigningTarget<C>, Signature<C>, VerifyingKey<C>)
where
C::Group: std::cmp::PartialEq,
{
@ -521,7 +532,7 @@ where
let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap());
// Proceed with the signing test.
check_sign(min_signers, key_packages, rng, pubkeys).unwrap()
check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap()
}
/// Check that calling dkg::part3() with distinct sets of participants fail.
@ -565,7 +576,8 @@ fn check_part3_different_participants<C: Ciphersuite>(
/// Identifiers.
pub fn check_sign_with_dealer_and_identifiers<C: Ciphersuite, R: RngCore + CryptoRng>(
mut rng: R,
) -> (Vec<u8>, Signature<C>, VerifyingKey<C>) {
signing_target: SigningTarget<C>,
) -> (SigningTarget<C>, Signature<C>, VerifyingKey<C>) {
// Check error cases first
// Check repeated identifiers
@ -631,7 +643,7 @@ pub fn check_sign_with_dealer_and_identifiers<C: Ciphersuite, R: RngCore + Crypt
let key_package = frost::keys::KeyPackage::try_from(v).unwrap();
key_packages.insert(k, key_package);
}
check_sign(min_signers, key_packages, rng, pubkeys).unwrap()
check_sign(min_signers, key_packages, rng, pubkeys, signing_target).unwrap()
}
fn check_part2_error<C: Ciphersuite>(

View File

@ -7,7 +7,12 @@ use std::{
use rand_core::{CryptoRng, RngCore};
use crate::{Error, FieldError, GroupError, Signature, VerifyingKey};
use crate::{
challenge,
keys::{KeyPackage, VerifyingShare},
round1, round2, BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError,
Signature, SigningTarget, VerifyingKey,
};
/// A prime order finite field GF(q) over which all scalar values for our prime order group can be
/// multiplied are defined.
@ -40,6 +45,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 +124,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.
///
@ -132,6 +149,25 @@ pub trait Group: Copy + Clone + PartialEq {
/// An element of the [`Ciphersuite`] `C`'s [`Group`].
pub type Element<C> = <<C as Ciphersuite>::Group as Group>::Element;
/// This is a marker trait for types which are passed in to modify the signing logic of a [`Ciphersuite`].
///
/// If the `serde` feature is enabled, any type implementing this trait must also implement
/// [`serde::Serialize`] and [`serde::Deserialize`].
#[cfg(feature = "serde")]
pub trait SigningParameters:
Clone + Debug + Eq + PartialEq + Default + serde::Serialize + for<'d> serde::Deserialize<'d>
{
}
/// This is a marker trait for types which are passed in to modify the signing logic of a [`Ciphersuite`].
///
/// If the `serde` feature is enabled, any type implementing this trait must also implement
/// [`serde::Serialize`] and [`serde::Deserialize`].
#[cfg(not(feature = "serde"))]
pub trait SigningParameters: Clone + Debug + Eq + PartialEq + Default {}
impl SigningParameters for () {}
/// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash
/// function.
///
@ -153,6 +189,10 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
/// `Group::ScalarSerialization`
type SignatureSerialization: AsRef<[u8]> + TryFrom<Vec<u8>>;
/// Additional parameters which should be provided to the ciphersuite's signing code
/// to produce an effective signature. Most ciphersuites will just set this to `()`.
type SigningParameters: SigningParameters;
/// [H1] for a FROST ciphersuite.
///
/// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field.
@ -220,12 +260,139 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
/// (see [`crate::batch::Verifier`]) also uses the default implementation regardless whether a
/// tailored implementation was provided.
fn verify_signature(
msg: &[u8],
sig_target: &SigningTarget<Self>,
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, sig_target);
public_key.verify_prehashed(c, signature)
public_key.verify_prehashed(c, signature, &sig_target.sig_params)
}
/// 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>,
sig_target: &SigningTarget<Self>,
) -> Challenge<Self> {
challenge(R, verifying_key, &sig_target.message)
}
/// Finalize an aggregated group signature. This is used by frost-sepc256k1-tr
/// to ensure the signature is valid under BIP340.
fn aggregate_sig_finalize(
z: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
_verifying_key: &VerifyingKey<Self>,
_sig_target: &SigningTarget<Self>,
) -> Signature<Self> {
Signature { R, z }
}
/// Finalize and output a single-signer Schnorr signature.
fn single_sig_finalize(
k: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
secret: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: &Challenge<Self>,
_verifying_key: &VerifyingKey<Self>,
_sig_params: &Self::SigningParameters,
) -> Signature<Self> {
let z = k + (challenge.0 * secret);
Signature { R, z }
}
/// Compute the signature share for a particular signer on a given challenge.
fn compute_signature_share(
signer_nonces: &round1::SigningNonces<Self>,
binding_factor: BindingFactor<Self>,
_group_commitment: GroupCommitment<Self>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &KeyPackage<Self>,
challenge: Challenge<Self>,
_sig_params: &Self::SigningParameters,
) -> round2::SignatureShare<Self> {
round2::compute_signature_share(
signer_nonces,
binding_factor,
lambda_i,
key_package,
challenge,
)
}
/// Compute the effective group element which should be used for signature operations
/// for the given verifying key.
///
/// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes.
fn effective_pubkey_element(
verifying_key: &VerifyingKey<Self>,
_sig_params: &Self::SigningParameters,
) -> <Self::Group as Group>::Element {
verifying_key.to_element()
}
/// Compute the effective nonce element which should be used for signature operations.
///
/// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity.
fn effective_nonce_element(
R: <Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
R
}
/// Compute the effective secret key which should be used for signature operations
/// for the given verifying key.
///
/// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes.
fn effective_secret_key(
secret: <<Self::Group as Group>::Field as Field>::Scalar,
_public: &VerifyingKey<Self>,
_sig_params: &Self::SigningParameters,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
secret
}
/// Compute the effective nonce secret which should be used for signature operations.
///
/// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity.
fn effective_nonce_secret(
nonce: <<Self::Group as Group>::Field as Field>::Scalar,
_R: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
nonce
}
/// Compute the effective nonce commitment share which should be used for
/// FROST signing.
///
/// In frost-sepc256k1-tr, this negates the commitment share if the group's final
/// commitment has an odd parity.
fn effective_commitment_share(
group_commitment_share: round1::GroupCommitmentShare<Self>,
_group_commitment: &GroupCommitment<Self>,
) -> <Self::Group as Group>::Element {
group_commitment_share.to_element()
}
/// Compute the effective verifying share which should be used for FROST
/// partial signature verification.
///
/// In frost-sepc256k1-tr, this negates the verifying share if the group's final
/// verifying key has an odd parity.
fn effective_verifying_share(
verifying_share: &VerifyingShare<Self>,
_verifying_key: &VerifyingKey<Self>,
_sig_params: &Self::SigningParameters,
) -> <Self::Group as Group>::Element {
verifying_share.0
}
}

View File

@ -1,15 +1,16 @@
use derive_getters::Getters;
use std::fmt::{self, Debug};
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature};
use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature, SigningTarget};
#[cfg(feature = "serde")]
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,22 @@ where
}
/// Return the underlying element.
#[cfg(feature = "internals")]
pub fn to_element(self) -> <C::Group as Group>::Element {
self.element
}
/// Return the effective verifying key given the specific signing parameters
/// to be verified against. For most ciphersuites, this simply returns the
/// same verifying key unchanged.
pub fn effective_key(self, sig_params: &C::SigningParameters) -> Self {
VerifyingKey::new(<C>::effective_pubkey_element(&self, sig_params))
}
/// 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,
@ -58,14 +70,18 @@ where
&self,
challenge: Challenge<C>,
signature: &Signature<C>,
sig_params: &C::SigningParameters,
) -> Result<(), Error<C>> {
// Verify check is h * ( - z * B + R + c * A) == 0
// h * ( z * B - c * A - R) == 0
//
// where h is the cofactor
let R = C::effective_nonce_element(signature.R);
let vk = C::effective_pubkey_element(&self, sig_params);
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(())
@ -74,9 +90,13 @@ where
}
}
/// Verify a purported `signature` over `msg` made by this verification key.
pub fn verify(&self, msg: &[u8], signature: &Signature<C>) -> Result<(), Error<C>> {
C::verify_signature(msg, signature, self)
/// Verify a purported `signature` over `sig_target` made by this verification key.
pub fn verify(
&self,
sig_target: impl Into<SigningTarget<C>>,
signature: &Signature<C>,
) -> Result<(), Error<C>> {
C::verify_signature(&sig_target.into(), signature, self)
}
/// Computes the group public key given the group commitment.

View File

@ -155,6 +155,14 @@ const CONTEXT_STRING: &str = "FROST-ED25519-SHA512-v1";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Ed25519Sha512;
/// The ciphersuite-specific signing parameters which are fed into
/// signing code to ensure correctly compliant signatures are computed.
pub type SigningParameters = ();
/// The message target which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
pub type SigningTarget = frost_core::SigningTarget<Ed25519Sha512>;
impl Ciphersuite for Ed25519Sha512 {
const ID: &'static str = CONTEXT_STRING;
@ -164,6 +172,8 @@ impl Ciphersuite for Ed25519Sha512 {
type SignatureSerialization = [u8; 64];
type SigningParameters = ();
/// H1 for FROST(Ed25519, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.1

View File

@ -12,7 +12,10 @@ fn check_zero_key_fails() {
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(
rng,
b"message".into(),
);
}
#[test]
@ -68,7 +71,10 @@ fn check_rts() {
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(
rng,
b"message".into(),
);
}
#[test]
@ -220,7 +226,7 @@ fn check_sign_with_dealer_and_identifiers() {
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Ed25519Sha512,
_,
>(rng);
>(rng, b"message".into());
}
#[test]

View File

@ -12,12 +12,13 @@ fn check_interoperability_in_sign_with_dkg() {
// and the interoperability check. A smaller number of iterations is used
// because DKG takes longer and otherwise the test would be too slow.
for _ in 0..32 {
let (msg, group_signature, group_pubkey) =
let (target, group_signature, group_pubkey) =
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(
rng.clone(),
b"message".into(),
);
helpers::verify_signature(&msg, group_signature, group_pubkey);
helpers::verify_signature(target.message(), group_signature, group_pubkey);
}
}
@ -28,13 +29,14 @@ fn check_interoperability_in_sign_with_dealer() {
// Test with multiple keys/signatures to better exercise the key generation
// and the interoperability check.
for _ in 0..256 {
let (msg, group_signature, group_pubkey) =
let (target, group_signature, group_pubkey) =
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(
rng.clone(),
b"message".into(),
);
// Check that the threshold signature can be verified by the `ed25519_dalek` crate
// public key (interoperability test)
helpers::verify_signature(&msg, group_signature, group_pubkey);
helpers::verify_signature(target.message(), group_signature, group_pubkey);
}
}

View File

@ -41,9 +41,9 @@ fn check_signing_package_recreation() {
let signing_package = samples::signing_package();
let commitments = signing_package.signing_commitments();
let message = signing_package.message();
let sig_target = signing_package.sig_target();
let new_signing_package = SigningPackage::new(commitments.clone(), message);
let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone());
assert!(signing_package == new_signing_package);
}

View File

@ -112,7 +112,9 @@ fn check_signing_package_serialization() {
"binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap();
assert!(signing_package == decoded_signing_package);
@ -133,7 +135,9 @@ fn check_signing_package_serialization() {
"binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -153,7 +157,9 @@ fn check_signing_package_serialization() {
"binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -172,7 +178,9 @@ fn check_signing_package_serialization() {
"binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -192,7 +200,9 @@ fn check_signing_package_serialization() {
"binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022"
}
},
"message": "68656c6c6f20776f726c64",
"sig_target": {
"message": "68656c6c6f20776f726c64"
},
"extra": 1
}
"#;

View File

@ -150,6 +150,14 @@ const CONTEXT_STRING: &str = "FROST-ED448-SHAKE256-v1";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Ed448Shake256;
/// The ciphersuite-specific signing parameters which are fed into
/// signing code to ensure correctly compliant signatures are computed.
pub type SigningParameters = ();
/// The message target which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
pub type SigningTarget = frost_core::SigningTarget<Ed448Shake256>;
impl Ciphersuite for Ed448Shake256 {
const ID: &'static str = CONTEXT_STRING;
@ -159,6 +167,8 @@ impl Ciphersuite for Ed448Shake256 {
type SignatureSerialization = [u8; 114];
type SigningParameters = ();
/// H1 for FROST(Ed448, SHAKE256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-2.2.2.1

View File

@ -12,7 +12,10 @@ fn check_zero_key_fails() {
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed448Shake256, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed448Shake256, _>(
rng,
b"message".into(),
);
}
#[test]
@ -68,7 +71,10 @@ fn check_rts() {
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed448Shake256, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed448Shake256, _>(
rng,
b"message".into(),
);
}
#[test]
@ -220,7 +226,7 @@ fn check_sign_with_dealer_and_identifiers() {
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Ed448Shake256,
_,
>(rng);
>(rng, b"message".into());
}
#[test]

View File

@ -41,9 +41,9 @@ fn check_signing_package_recreation() {
let signing_package = samples::signing_package();
let commitments = signing_package.signing_commitments();
let message = signing_package.message();
let sig_target = signing_package.sig_target();
let new_signing_package = SigningPackage::new(commitments.clone(), message);
let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone());
assert!(signing_package == new_signing_package);
}

View File

@ -112,7 +112,9 @@ fn check_signing_package_serialization() {
"binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap();
assert!(signing_package == decoded_signing_package);
@ -133,7 +135,9 @@ fn check_signing_package_serialization() {
"binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -153,7 +157,9 @@ fn check_signing_package_serialization() {
"binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -172,7 +178,9 @@ fn check_signing_package_serialization() {
"binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -192,7 +200,9 @@ fn check_signing_package_serialization() {
"binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80"
}
},
"message": "68656c6c6f20776f726c64",
"sig_target": {
"message": "68656c6c6f20776f726c64"
},
"extra": 1
}
"#;

View File

@ -175,6 +175,14 @@ const CONTEXT_STRING: &str = "FROST-P256-SHA256-v1";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct P256Sha256;
/// The ciphersuite-specific signing parameters which are fed into
/// signing code to ensure correctly compliant signatures are computed.
pub type SigningParameters = ();
/// The message target which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
pub type SigningTarget = frost_core::SigningTarget<P256Sha256>;
impl Ciphersuite for P256Sha256 {
const ID: &'static str = CONTEXT_STRING;
@ -184,6 +192,8 @@ impl Ciphersuite for P256Sha256 {
type SignatureSerialization = [u8; 65];
type SigningParameters = ();
/// H1 for FROST(P-256, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-2.2.2.1

View File

@ -12,7 +12,10 @@ fn check_zero_key_fails() {
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<P256Sha256, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<P256Sha256, _>(
rng,
b"message".into(),
);
}
#[test]
@ -68,7 +71,10 @@ fn check_rts() {
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<P256Sha256, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<P256Sha256, _>(
rng,
b"message".into(),
);
}
#[test]
@ -217,6 +223,7 @@ fn check_sign_with_dealer_and_identifiers() {
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<P256Sha256, _>(
rng,
b"message".into(),
);
}

View File

@ -41,9 +41,9 @@ fn check_signing_package_recreation() {
let signing_package = samples::signing_package();
let commitments = signing_package.signing_commitments();
let message = signing_package.message();
let sig_target = signing_package.sig_target();
let new_signing_package = SigningPackage::new(commitments.clone(), message);
let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone());
assert!(signing_package == new_signing_package);
}

View File

@ -112,7 +112,9 @@ fn check_signing_package_serialization() {
"binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap();
assert!(signing_package == decoded_signing_package);
@ -133,7 +135,9 @@ fn check_signing_package_serialization() {
"binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -153,7 +157,9 @@ fn check_signing_package_serialization() {
"binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -172,7 +178,9 @@ fn check_signing_package_serialization() {
"binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -192,7 +200,9 @@ fn check_signing_package_serialization() {
"binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
}
},
"message": "68656c6c6f20776f726c64",
"sig_target": {
"message": "68656c6c6f20776f726c64"
},
"extra": 1
}
"#;

View File

@ -141,6 +141,14 @@ const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512-v1";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Ristretto255Sha512;
/// The ciphersuite-specific signing parameters which are fed into
/// signing code to ensure correctly compliant signatures are computed.
pub type SigningParameters = ();
/// The message target which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
pub type SigningTarget = frost_core::SigningTarget<Ristretto255Sha512>;
impl Ciphersuite for Ristretto255Sha512 {
const ID: &'static str = CONTEXT_STRING;
@ -150,6 +158,8 @@ impl Ciphersuite for Ristretto255Sha512 {
type SignatureSerialization = [u8; 64];
type SigningParameters = SigningParameters;
/// H1 for FROST(ristretto255, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-2.2.2.1

View File

@ -12,7 +12,10 @@ fn check_zero_key_fails() {
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ristretto255Sha512, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ristretto255Sha512, _>(
rng,
b"message".into(),
);
}
#[test]
@ -68,7 +71,10 @@ fn check_rts() {
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ristretto255Sha512, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ristretto255Sha512, _>(
rng,
b"message".into(),
);
}
#[test]
@ -220,7 +226,7 @@ fn check_sign_with_dealer_and_identifiers() {
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Ristretto255Sha512,
_,
>(rng);
>(rng, b"message".into());
}
#[test]

View File

@ -41,9 +41,9 @@ fn check_signing_package_recreation() {
let signing_package = samples::signing_package();
let commitments = signing_package.signing_commitments();
let message = signing_package.message();
let sig_target = signing_package.sig_target();
let new_signing_package = SigningPackage::new(commitments.clone(), message);
let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone());
assert!(signing_package == new_signing_package);
}

View File

@ -112,7 +112,9 @@ fn check_signing_package_serialization() {
"binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap();
assert!(signing_package == decoded_signing_package);
@ -133,7 +135,9 @@ fn check_signing_package_serialization() {
"binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -153,7 +157,9 @@ fn check_signing_package_serialization() {
"binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -172,7 +178,9 @@ fn check_signing_package_serialization() {
"binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -192,7 +200,9 @@ fn check_signing_package_serialization() {
"binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"
}
},
"message": "68656c6c6f20776f726c64",
"sig_target": {
"message": "68656c6c6f20776f726c64"
},
"extra": 1
}
"#;

View File

@ -0,0 +1,64 @@
[package]
name = "frost-secp256k1-tr"
edition = "2021"
# When releasing to crates.io:
# - Update CHANGELOG.md
# - Create git tag.
version = "1.0.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" }
frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" }
k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"] }
serde = { version = "1.0.160", features = ["derive"], optional = true }
rand_core = "0.6"
sha2 = "0.10.2"
[dev-dependencies]
criterion = "0.5"
frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] }
frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.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", "dep: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).
## 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_tr::*;
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,726 @@
#![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},
point::AffineCoordinates,
sec1::{FromEncodedPoint, ToEncodedPoint},
Field as FFField, PrimeField, ScalarPrimitive,
},
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, Challenge, Ciphersuite, Element, Field, FieldError, Group, GroupCommitment, GroupError,
};
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
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
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
fn tweak<T: AsRef<[u8]>>(
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
merkle_root: Option<T>,
) -> Scalar {
match merkle_root {
None => Secp256K1ScalarField::zero(),
Some(root) => {
let mut hasher = tagged_hash("TapTweak");
hasher.update(public_key.to_affine().x());
hasher.update(root.as_ref());
hasher_to_scalar(hasher)
}
}
}
/// Create a BIP341 compliant tweaked public key
fn tweaked_public_key<T: AsRef<[u8]>>(
public_key: &VerifyingKey,
merkle_root: Option<T>,
) -> <<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element {
let mut pk = public_key.to_element();
if pk.to_affine().y_is_odd().into() {
pk = -pk;
}
ProjectivePoint::GENERATOR * tweak(&pk, merkle_root) + pk
}
/// The message target which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
pub type SigningTarget = frost_core::SigningTarget<S>;
/// The ciphersuite-specific signing parameters which are fed into
/// signing code to ensure correctly compliant signatures are computed.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SigningParameters {
/// The tapscript merkle tree root which must be committed to and agreed upon
/// in advance by all participants in the signing round.
///
/// If set to `None` (the default), then no taproot tweak will be committed to in the signature.
/// Best practice suggested by BIP341 is to commit to an empty merkle root in cases
/// where no tapscript tweak is needed, i.e. by supplying `&[0; u8]` as the merkle root.
/// This prevents hiding of taproot commitments inside a linearly aggregated key.
///
/// However, for FROST, this is not strictly required as the group key cannot be
/// poisoned as long as the DKG procedure is conducted correctly.
/// Thus, the [`Default`] trait implementation of taproot `SigningParameters`
/// sets `tapscript_merkle_root` to `None`.
///
/// If 3rd party observers outside the FROST group must be able to verify there
/// is no hidden script-spending path embedded in the FROST group's taproot output key,
/// then you should set `tapscript_merkle_root` to `Some(vec![])`, which proves
/// the tapscript commitment for the tweaked output key is unspendable.
pub tapscript_merkle_root: Option<Vec<u8>>,
}
impl frost_core::SigningParameters for SigningParameters {}
impl Ciphersuite for Secp256K1Sha256 {
const ID: &'static str = CONTEXT_STRING;
type Group = Secp256K1Group;
type HashOutput = [u8; 32];
type SignatureSerialization = [u8; 65];
type SigningParameters = SigningParameters;
/// 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,
sig_target: &SigningTarget,
) -> Challenge<S> {
let mut preimage = vec![];
let tweaked_pk = tweaked_public_key(
&verifying_key,
sig_target.sig_params().tapscript_merkle_root.as_ref(),
);
preimage.extend_from_slice(&R.to_affine().x());
preimage.extend_from_slice(&tweaked_pk.to_affine().x());
preimage.extend_from_slice(sig_target.message().as_ref());
Challenge::from_scalar(S::H2(&preimage[..]))
}
/// Finalizes the signature by negating it depending on whether
/// the group [`VerifyingKey`] is even or odd parity.
fn aggregate_sig_finalize(
z_raw: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
verifying_key: &VerifyingKey,
sig_target: &SigningTarget,
) -> Signature {
let challenge = Self::challenge(&R, verifying_key, &sig_target);
let t = tweak(
verifying_key.element(),
sig_target.sig_params().tapscript_merkle_root.as_ref(),
);
let tc = t * challenge.clone().to_scalar();
let tweaked_pubkey = tweaked_public_key(
verifying_key,
sig_target.sig_params().tapscript_merkle_root.as_ref(),
);
let z_tweaked = if tweaked_pubkey.to_affine().y_is_odd().into() {
z_raw - tc
} else {
z_raw + tc
};
Signature::new(R, z_tweaked)
}
/// Finalize a single-signer BIP340 Schnorr signature.
fn single_sig_finalize(
k: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
secret: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: &Challenge<S>,
verifying_key: &VerifyingKey,
sig_params: &SigningParameters,
) -> Signature {
let tweaked_pubkey =
tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref());
let c = challenge.clone().to_scalar();
let z = if tweaked_pubkey.to_affine().y_is_odd().into() {
k - (c * secret)
} else {
k + (c * secret)
};
Signature::new(R, z)
}
/// Compute a signature share, negating if required by BIP340.
fn compute_signature_share(
signer_nonces: &round1::SigningNonces,
binding_factor: frost::BindingFactor<S>,
group_commitment: GroupCommitment<S>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &frost::keys::KeyPackage<S>,
challenge: Challenge<S>,
sig_params: &SigningParameters,
) -> round2::SignatureShare {
let mut sn = signer_nonces.clone();
if group_commitment.y_is_odd() {
sn.negate_nonces();
}
let mut kp = key_package.clone();
let public_key = key_package.verifying_key();
let pubkey_is_odd: bool = public_key.y_is_odd();
let tweaked_pubkey_is_odd: bool =
tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref())
.to_affine()
.y_is_odd()
.into();
if pubkey_is_odd != tweaked_pubkey_is_odd {
kp.negate_signing_share();
}
frost::round2::compute_signature_share(&sn, binding_factor, lambda_i, &kp, challenge)
}
/// Computes the effective pubkey point by tweaking the verifying key with a
/// provably unspendable taproot tweak.
fn effective_pubkey_element(
public_key: &VerifyingKey,
sig_params: &SigningParameters,
) -> <Self::Group as Group>::Element {
let tweaked_pubkey =
tweaked_public_key(public_key, sig_params.tapscript_merkle_root.as_ref());
if Self::Group::y_is_odd(&tweaked_pubkey) {
-tweaked_pubkey
} else {
tweaked_pubkey
}
}
/// Ensures the nonce has an even Y coordinate.
fn effective_nonce_element(
R: <Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
if Self::Group::y_is_odd(&R) {
-R
} else {
R
}
}
/// Ensures the secret key is negated if the public key has odd parity.
fn effective_secret_key(
secret: <<Self::Group as Group>::Field as Field>::Scalar,
public_key: &VerifyingKey,
sig_params: &SigningParameters,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
let t = tweak(
public_key.element(),
sig_params.tapscript_merkle_root.as_ref(),
);
if Self::Group::y_is_odd(public_key.element()) {
-secret + t
} else {
secret + t
}
}
/// Ensures the nonce secret is negated if the public nonce point has odd parity.
fn effective_nonce_secret(
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
}
}
/// Ensures the commitment share is negated if the group's commitment has odd parity.
fn effective_commitment_share(
group_commitment_share: frost::round1::GroupCommitmentShare<Self>,
group_commitment: &GroupCommitment<Self>,
) -> Element<Self> {
if group_commitment
.clone()
.to_element()
.to_affine()
.y_is_odd()
.into()
{
-group_commitment_share.to_element()
} else {
group_commitment_share.to_element()
}
}
/// Calculate a verifying share compatible with taproot, depending on the parity
/// of the tweaked vs untweaked verifying key.
fn effective_verifying_share(
verifying_share: &keys::VerifyingShare,
verifying_key: &VerifyingKey,
sig_params: &SigningParameters,
) -> <Self::Group as Group>::Element {
let pubkey_is_odd: bool = verifying_key.to_element().to_affine().y_is_odd().into();
let tweaked_pubkey_is_odd: bool =
tweaked_public_key(verifying_key, sig_params.tapscript_merkle_root.as_ref())
.to_affine()
.y_is_odd()
.into();
let vs = verifying_share.to_element();
if pubkey_is_odd != tweaked_pubkey_is_odd {
-vs
} else {
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,5 @@
// Required since each integration test is compiled as a separated crate,
// and each one uses only part of the module.
#![allow(dead_code)]
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,133 @@
//! Generate sample, fixed instances of structs for testing.
use std::collections::BTreeMap;
use frost_core::{round1::Nonce, Ciphersuite, Element, Group, Scalar};
use frost_secp256k1_tr::{
keys::{
dkg::{round1, round2},
KeyPackage, PublicKeyPackage, SecretShare, SigningShare, VerifiableSecretSharingCommitment,
VerifyingShare,
},
round1::{NonceCommitment, SigningCommitments, SigningNonces},
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_nonces() -> SigningNonces {
let serialized_scalar1 = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_scalar2 = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let hiding_nonce = Nonce::deserialize(serialized_scalar1).unwrap();
let binding_nonce = Nonce::deserialize(serialized_scalar2).unwrap();
SigningNonces::from_nonces(hiding_nonce, binding_nonce)
}
/// 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": "2ffc305d1694fd84108b84d98306a1af807c6ad9bc3a2d8e448a09643202a15b"
},
{
"identifier": 3,
"sig_share": "a8c392566ea29e852b4080a028bf5547166c87e703e4fb7136d4ebef65f99b3f"
}
]
},
"final_output": {
"sig": "030c776a9516a77808b70a31e74f1464814a6fcf897fb3a6bd84c7a9a9a7a5bcb8d8bfc2b385379c093bcc0579abc5f6f696e8f2c0c01f28ff7b5ef55397fc3c9a"
}
}

View File

@ -0,0 +1,51 @@
{
"config": {
"MAX_PARTICIPANTS": 3,
"MIN_PARTICIPANTS": 2,
"name": "FROST(secp256k1, SHA-256)",
"group": "secp256k1",
"hash": "SHA-256"
},
"inputs": {
"verifying_key": "034a48daffc43b47b42695611942c481aecffb9137686ad0b3e0ab8e1f1dab0293",
"1": {
"identifier": 1,
"signing_key": "68e3f6904c6043973515a36bf7801a71597da35733f21305d75a5234f06e4529",
"coefficient": "25d2d840a3e2718a431ec69e14ee8a015b000d43c7a9868060f01d5aa52a19d1",
"vss_commitments": ["03e7ba4acb164d2bd5eba4f47b3a788109ddb3f88f1181792424fa332123a25ea8", "037495e920a1f032916193aa80ea97a4c3a611dec9ab47ccc969deb664f5f88bbe"],
"proof_of_knowledge": "03c5e534b1770fc7517983df70823c8a1c879cd9aed515437bdad242cfbdab5e82b0a92d1d6e57e421cbd75159b89d2508c774753476110250fabfa97ff667254d",
"signing_shares": {
"2": "1dd3cb3e2370e6af22917415f0ad584514807b58b3cc40d2230a26e115f02771",
"3": "dd25ee86acd01f996618aa0d1153f5e8fbc929a8e8a18b8f0a15f91d087217e2"
},
"verifying_share": "03e2eada7cdb20ec24babb687eb633580c977148c70254700f1ad4a931316dc6d9",
"signing_share": "89b08895c083bb6a00de882d0e6ff2a20a1878b5e8c0c5aba5983100e3c45d0c"
},
"2": {
"identifier": 2,
"signing_key": "2619be8223b23e0453ddc630a4d164e81f7d8a9e07af33c4d4d02190df8bec13",
"coefficient": "f7ba0cbbffbea8aaceb3ade54bdbf35bafb1cda15b65ad490e0c63dd069a7c9f",
"vss_commitments": ["03ef10370a008cd95e179dc51e2cb7828f30b72d254e5166484f927c84ab326582", "022ce0dac0db217ba326fbbe3e6132d45e2a4bfa0a0c3790d91eacce9a1c2d6a10"],
"proof_of_knowledge": "038ef8fa5a833beb3fb0d83907de2ac3cad158927afc04739f5d52ae50681874a6d43da5d6985bc1190d649342cc3999b30b15e57f177f59cbf2d21a64d6b313e2",
"signing_shares": {
"1": "b489a711942526abbb5330a8215d2e740f7dbddec3452006993a8cea3ac278cb",
"3": "20255dc07b1fb78bdf90bd85fd2389c988c8250faee11826656a09142fa9fc97"
},
"verifying_share": "0312705f7560a146760034ebdd103277e184ce81d2ba6a67a43f0b3f39410cc396",
"signing_share": "ea3cdccc32746d918c2910295b0a03dfa1c94f01d20f860c6fe8c22fb6c0d831"
},
"3": {
"identifier": 3,
"signing_key": "9a267f4cde8087a6eca0969425846209b41b515b73195ebbeeef8a991103f1ec",
"coefficient": "42ff6f39ce4f97f279781378ebcf93df47add84d75882cd31b266e83f76e25f6",
"vss_commitments": ["02da186c3863c5600b471a2799cb6f15ae4d8315a2f225c177798880e75ac820a0", "03e6a36e7fa4b117c1aa428886672e3a35d926bb4c585a9b07d8ee9a3387420067"],
"proof_of_knowledge": "03705c01e6fad6abac2f06211948d93a134c0beb00e98d7e7dc772f166acf1b3c99a674f3d712ebd7e421e59e90310facefcf04a7e7134b0f92f34d0b23afa375e",
"signing_shares": {
"1": "da5c7f5238079835fe71f746364bb8756a7dcb228aeea686fa2aaa44dfec929c",
"2": "0d47e4b622ee3804bff8cfe088653efefe865cce0c065aecbf7e318182b89e2d"
},
"verifying_share": "036607b45621ce6840d999980c9c74d69a15fa5a246e852ee2ca6924ce65fa299b",
"signing_share": "4ac93102a4651fb917739825a7a4151e7ecb48670c15a6317a66f4d1b9871215"
}
}
}

View File

@ -0,0 +1,247 @@
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,
b"message".into(),
);
}
#[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,
b"message".into(),
);
}
#[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_tr_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");
pub static ref VECTORS_DKG: Value =
serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.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_dkg() {
frost_core::tests::vectors_dkg::check_dkg_keygen::<Secp256K1Sha256>(&VECTORS_DKG);
}
#[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, b"message".into());
}
#[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,133 @@
//! 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, SigningNonces},
round2::SignatureShare,
SigningPackage,
};
mod helpers;
use helpers::samples;
/// Check if SigningNonces can be recreated.
#[test]
fn check_signing_nonces_recreation() {
let nonces = samples::signing_nonces();
let hiding = nonces.hiding();
let binding = nonces.binding();
let new_nonces = SigningNonces::from_nonces(*hiding, *binding);
assert!(nonces == new_nonces);
}
/// 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 sig_target = signing_package.sig_target();
let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone());
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,642 @@
#![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"
}
},
"sig_target": {
"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"
}
},
"sig_target": {
"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"
}
},
"sig_target": {
"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"
}
},
"sig_target": {
"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"
}
},
"sig_target": {
"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,102 @@
#![cfg(feature = "serialization")]
mod helpers;
use frost_secp256k1_tr::{
keys::{
dkg::{round1, round2},
KeyPackage, PublicKeyPackage, SecretShare,
},
round1::{SigningCommitments, SigningNonces},
round2::SignatureShare,
SigningPackage,
};
use helpers::samples;
use insta::assert_snapshot;
#[test]
fn check_signing_nonces_postcard_serialization() {
let nonces = samples::signing_nonces();
let bytes: Vec<_> = nonces.serialize().unwrap();
assert_snapshot!(hex::encode(&bytes));
assert_eq!(nonces, SigningNonces::deserialize(&bytes).unwrap());
}
#[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/tests/serialization_tests.rs
expression: "hex::encode(&bytes)"
---
00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b8100230f8ab3034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9

View File

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

View File

@ -0,0 +1,89 @@
use frost_secp256k1_tr::*;
use rand::thread_rng;
#[test]
fn check_tweaked_signing_key() {
let signing_key = SigningKey::deserialize([0xAA; 32]).unwrap();
let untweaked_verifying_key = VerifyingKey::from(signing_key);
let mut rng = rand::thread_rng();
let message = b"message";
let untweaked_signature = signing_key.sign(&mut rng, &message);
untweaked_verifying_key
.verify(&message, &untweaked_signature)
.expect("untweaked signature should be valid under untweaked verifying key");
let signing_target = SigningTarget::new(
&message,
SigningParameters {
tapscript_merkle_root: Some(vec![]),
},
);
let tweaked_signature = signing_key.sign(&mut rng, signing_target.clone());
untweaked_verifying_key
.verify(&message, &tweaked_signature)
.expect_err("tweaked signature should not be valid under untweaked verifying key");
let tweaked_verifying_key = untweaked_verifying_key.effective_key(signing_target.sig_params());
tweaked_verifying_key
.verify(&message, &tweaked_signature)
.expect("tweaked signature should be valid under tweaked verifying key");
untweaked_verifying_key
.verify(signing_target.clone(), &tweaked_signature)
.expect(
"tweaked signature should be valid under untweaked verifying key\
when signing params are provided",
);
}
#[test]
fn check_tweaked_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Secp256K1Sha256, _>(
rng,
SigningTarget::new(
b"message",
SigningParameters {
tapscript_merkle_root: Some(vec![]),
},
),
);
}
#[test]
fn check_tweaked_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Secp256K1Sha256, _>(
rng,
SigningTarget::new(
b"message",
SigningParameters {
tapscript_merkle_root: Some(vec![]),
},
),
);
}
#[test]
fn check_tweaked_sign_with_dealer_and_identifiers() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Secp256K1Sha256,
_,
>(
rng,
SigningTarget::new(
b"message",
SigningParameters {
tapscript_merkle_root: Some(vec![]),
},
),
);
}

View File

@ -175,6 +175,14 @@ const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v1";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Secp256K1Sha256;
/// The ciphersuite-specific signing parameters which are fed into
/// signing code to ensure correctly compliant signatures are computed.
pub type SigningParameters = ();
/// The message target which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
pub type SigningTarget = frost_core::SigningTarget<Secp256K1Sha256>;
impl Ciphersuite for Secp256K1Sha256 {
const ID: &'static str = CONTEXT_STRING;
@ -184,6 +192,8 @@ impl Ciphersuite for Secp256K1Sha256 {
type SignatureSerialization = [u8; 65];
type SigningParameters = ();
/// 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

View File

@ -12,7 +12,10 @@ fn check_zero_key_fails() {
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Secp256K1Sha256, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Secp256K1Sha256, _>(
rng,
b"message".into(),
);
}
#[test]
@ -68,7 +71,10 @@ fn check_rts() {
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Secp256K1Sha256, _>(rng);
frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Secp256K1Sha256, _>(
rng,
b"message".into(),
);
}
#[test]
@ -220,7 +226,7 @@ fn check_sign_with_dealer_and_identifiers() {
frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Secp256K1Sha256,
_,
>(rng);
>(rng, b"message".into());
}
#[test]

View File

@ -41,9 +41,9 @@ fn check_signing_package_recreation() {
let signing_package = samples::signing_package();
let commitments = signing_package.signing_commitments();
let message = signing_package.message();
let sig_target = signing_package.sig_target();
let new_signing_package = SigningPackage::new(commitments.clone(), message);
let new_signing_package = SigningPackage::new(commitments.clone(), sig_target.clone());
assert!(signing_package == new_signing_package);
}

View File

@ -112,7 +112,9 @@ fn check_signing_package_serialization() {
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap();
assert!(signing_package == decoded_signing_package);
@ -133,7 +135,9 @@ fn check_signing_package_serialization() {
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -153,7 +157,9 @@ fn check_signing_package_serialization() {
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -172,7 +178,9 @@ fn check_signing_package_serialization() {
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64"
"sig_target": {
"message": "68656c6c6f20776f726c64"
}
}"#;
assert!(serde_json::from_str::<SigningPackage>(invalid_json).is_err());
@ -192,7 +200,9 @@ fn check_signing_package_serialization() {
"binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
}
},
"message": "68656c6c6f20776f726c64",
"sig_target": {
"message": "68656c6c6f20776f726c64"
},
"extra": 1
}
"#;

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_tr_sha256",
"secp256k1_tr",
"<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