Compare commits
22 Commits
58af299fac
...
a75bc71a87
Author | SHA1 | Date |
---|---|---|
zebra-lucky | a75bc71a87 | |
natalie | e5c5e53b86 | |
conduition | 20c2c98a93 | |
conduition | 5d2d683e1d | |
conduition | 8f5264685b | |
conduition | c1b8663acb | |
zebra-lucky | 1c085ba38a | |
zebra-lucky | 1268f5cc60 | |
conduition | 155dfa632b | |
conduition | e5b3f5dd90 | |
zebra-lucky | c63a3ca699 | |
zebra-lucky | 0ed163f93a | |
conduition | 142556fabd | |
Conrado Gouvea | a66b9a2e7c | |
Conrado Gouvea | bdc8fb4cbf | |
Conrado Gouvea | 00cdfe59ae | |
zebra-lucky | 20da59a67a | |
zebra-lucky | 6d8be7c45b | |
zebra-lucky | a3071302dd | |
David Wong | 8204166b93 | |
zebra-lucky | ab6b0d09d4 | |
zebra-lucky | b380fd589b |
|
@ -4,3 +4,4 @@ Cargo.lock
|
|||
*~
|
||||
**/.DS_Store
|
||||
.vscode/*
|
||||
*.swp
|
||||
|
|
|
@ -7,6 +7,7 @@ members = [
|
|||
"frost-p256",
|
||||
"frost-ristretto255",
|
||||
"frost-secp256k1",
|
||||
"frost-secp256k1-tr",
|
||||
"frost-rerandomized",
|
||||
"gencode"
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn check_serialize_vss_commitment<C: Ciphersuite, R: RngCore + CryptoRng>(mu
|
|||
|
||||
// ---
|
||||
|
||||
let expected = vec![
|
||||
let expected = [
|
||||
<C::Group>::serialize(&input_1),
|
||||
<C::Group>::serialize(&input_2),
|
||||
<C::Group>::serialize(&input_3),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -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
|
|
@ -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>(())
|
||||
```
|
|
@ -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);
|
|
@ -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>(())
|
||||
```
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>;
|
|
@ -0,0 +1,5 @@
|
|||
mod batch;
|
||||
mod coefficient_commitment;
|
||||
mod deserialize;
|
||||
mod proptests;
|
||||
mod vss_commitment;
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"elements": {
|
||||
"invalid_element": "123456afdf4a7f88885ab26b20d18edb7d4d9589812a6cf1a5a1a09d3808dae5d8"
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
|
||||
"element1": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
"element2": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
|
||||
"scalar1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81"
|
||||
}
|
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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()
|
||||
);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b810279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab301000000000000000000000000000000000000000000000000000000000000002a0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab3010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798410279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(bytes)"
|
||||
---
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b8100230f8ab3034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: frost-secp256k1-tr/tests/serialization_tests.rs
|
||||
expression: "hex::encode(&bytes)"
|
||||
---
|
||||
00230f8ab301000000000000000000000000000000000000000000000000000000000000002a00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee50b68656c6c6f20776f726c6400
|
|
@ -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![]),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue