add secp256k1 ciphersuite (#175)

* add secp256k1 ciphersuite

* use workaround for hash2field

* fix secp256k1 docs caused by gendoc bug

* Apply suggestions from code review

Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>

* removed random_nonzero which is no longer needed

* typo

* cargo fmt

* rustdoc url

Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>
This commit is contained in:
Conrado Gouvea 2022-11-22 18:09:21 -03:00 committed by GitHub
parent 1815280576
commit 3e1fe25dbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 717 additions and 2 deletions

View File

@ -3,8 +3,9 @@ resolver = "2"
members = [
"frost-core",
#"frost-redjubjub",
"frost-ristretto255",
"frost-p256",
"frost-ed25519",
"frost-p256",
"frost-ristretto255",
"frost-secp256k1",
"gendoc"
]

View File

@ -0,0 +1,48 @@
[package]
name = "frost-secp256k1"
edition = "2021"
# When releasing to crates.io:
# - Update html_root_url
# - Update CHANGELOG.md
# - Create git tag.
version = "0.1.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", "ristretto", "threshold", "signature"]
description = "A Schnorr signature scheme over the prime-order Ristretto group that supports FROST ."
[package.metadata.docs.rs]
features = ["nightly"]
[dependencies]
byteorder = "1.4"
digest = "0.10"
frost-core = { path = "../frost-core", features = ["test-impl"] }
hex = { version = "0.4.3", features = ["serde"] }
# k256 = { version = "0.11.6", features = ["arithmetic", "hash2curve"] }
k256 = { git = "https://github.com/RustCrypto/elliptic-curves", rev = "42a18e9e13f3bacba89af00d15ef732dbfd03d01", features = ["arithmetic", "hash2curve"] }
rand_core = "0.6"
serde = { version = "1", optional = true, features = ["derive"] }
sha2 = "0.10.2"
thiserror = "1.0"
zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
[dev-dependencies]
bincode = "1"
criterion = "0.4"
ed25519-dalek = "1.0.1"
ed25519-zebra = "3.0.0"
lazy_static = "1.4"
proptest = "1.0"
proptest-derive = "0.3"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"
[features]
nightly = []
default = ["serde"]

98
frost-secp256k1/README.md Normal file
View File

@ -0,0 +1,98 @@
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
use frost_secp256k1 as frost;
use rand::thread_rng;
use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?;
// Verifies the secret shares from the dealer and store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let key_packages: HashMap<_, _> = shares
.into_iter()
.map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?)))
.collect::<Result<_, _>>()?;
let mut nonces = HashMap::new();
let mut commitments = HashMap::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");
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
&mut rng,
);
// In practice, the nonces and commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
}
// 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 = Vec::new();
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// 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(comms, message.to_vec());
////////////////////////////////////////////////////////////////////////////
// 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.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
signature_shares.push(signature_share);
}
////////////////////////////////////////////////////////////////////////////
// Aggregation: collects the signing shares from all participants,
// generates the final signature.
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
.group_public
.verify(message, &group_signature)
.is_ok());
# Ok::<(), frost::Error>(())
```

386
frost-secp256k1/src/lib.rs Normal file
View File

@ -0,0 +1,386 @@
#![allow(non_snake_case)]
#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
use digest::generic_array::GenericArray;
use k256::{
elliptic_curve::{
bigint::{Encoding, U384},
group::prime::PrimeCurveAffine,
hash2curve::{ExpandMsg, ExpandMsgXmd, Expander},
sec1::{FromEncodedPoint, ToEncodedPoint},
Field as FFField, PrimeField,
},
AffinePoint, ProjectivePoint, Scalar,
};
use rand_core::{CryptoRng, RngCore};
use sha2::{digest::Update, Digest, Sha256};
use frost_core::{frost, Ciphersuite, Field, Group};
#[cfg(test)]
mod tests;
pub use frost_core::Error;
#[derive(Clone, Copy)]
/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite scalar field.
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, Error> {
// [`Scalar`]'s Eq/PartialEq does a constant-time comparison
if *scalar == <Self as Field>::zero() {
Err(Error::InvalidZeroScalar)
} else {
Ok(scalar.invert().unwrap())
}
}
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, Error> {
let field_bytes: &k256::FieldBytes = buf.into();
match Scalar::from_repr(*field_bytes).into() {
Some(s) => Ok(s),
None => Err(Error::MalformedScalar),
}
}
fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
let mut array = Self::serialize(scalar);
array.reverse();
array
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite group.
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 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).
// TODO: Note that identity elements shouldn't be serialized in FROST. This will likely become
// part of the API and when that happens, we should return an error instead of
// doing this padding.
{
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, Error> {
let encoded_point =
k256::EncodedPoint::from_bytes(buf).map_err(|_| Error::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(Error::InvalidIdentityElement)
} else {
Ok(ProjectivePoint::from(point))
}
}
None => Err(Error::MalformedElement),
}
}
}
/// hash2field implementation from <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3>
///
/// From <https://github.com/serai-dex/serai/blob/5df74ac9e28f9299e674e98d08e64c99c34e579c/crypto/ciphersuite/src/kp256.rs#L45-L62>
//
// After https://github.com/RustCrypto/elliptic-curves/pull/673/ merges this should
// be removed, and a similar implementation to p256 should be used.
fn hash_to_field(msg: &[u8], dst: &[u8]) -> Scalar {
let mut modulus = [0; 48];
modulus[16..].copy_from_slice(&(Scalar::ZERO - Scalar::ONE).to_bytes());
let modulus = U384::from_be_slice(&modulus).wrapping_add(&U384::ONE);
let unreduced = U384::from_be_bytes({
let mut bytes = [0; 48];
ExpandMsgXmd::<Sha256>::expand_message(&[msg], dst, 48)
.expect("should never return error according to error cases described in ExpandMsgXmd")
.fill_bytes(&mut bytes);
bytes
})
.reduce(&modulus)
.unwrap()
.to_be_bytes();
let array = *GenericArray::from_slice(&unreduced[16..]);
Scalar::from_repr(array).unwrap()
}
/// Context string from the ciphersuite in the [spec].
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.5-1
const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v11";
#[derive(Clone, Copy, PartialEq, Eq)]
/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite.
pub struct Secp256K1Sha256;
impl Ciphersuite for Secp256K1Sha256 {
type Group = Secp256K1Group;
type HashOutput = [u8; 32];
type SignatureSerialization = [u8; 65];
/// H1 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.5-2.2.2.1
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
let dst = CONTEXT_STRING.to_owned() + "rho";
hash_to_field(m, dst.as_bytes())
}
/// H2 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.5-2.2.2.2
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
let dst = CONTEXT_STRING.to_owned() + "chal";
hash_to_field(m, dst.as_bytes())
}
/// H3 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.5-2.2.2.3
fn H3(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
let dst = CONTEXT_STRING.to_owned() + "nonce";
hash_to_field(m, dst.as_bytes())
}
/// H4 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.5-2.2.2.4
fn H4(m: &[u8]) -> Self::HashOutput {
let h = Sha256::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("msg")
.chain(m);
let mut output = [0u8; 32];
output.copy_from_slice(h.finalize().as_slice());
output
}
/// H5 for FROST(secp256k1, SHA-256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.5-2.2.2.5
fn H5(m: &[u8]) -> Self::HashOutput {
let h = Sha256::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("com")
.chain(m);
let mut output = [0u8; 32];
output.copy_from_slice(h.finalize().as_slice());
output
}
/// HDKG for FROST(secp256k1, SHA-256)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
let dst = CONTEXT_STRING.to_owned() + "dkg";
Some(hash_to_field(m, dst.as_bytes()))
}
}
type R = Secp256K1Sha256;
/// A FROST(secp256k1, SHA-256) participant identifier.
pub type Identifier = frost::Identifier<R>;
/// FROST(secp256k1, SHA-256) keys, key generation, key shares.
pub mod keys {
use super::*;
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
pub fn keygen_with_dealer<RNG: RngCore + CryptoRng>(
max_signers: u16,
min_signers: u16,
mut rng: RNG,
) -> Result<(Vec<SecretShare>, PublicKeyPackage), Error> {
frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_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<R>;
/// 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<R>;
/// 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<R>;
}
/// FROST(secp256k1, SHA-256) Round 1 functionality and types.
pub mod round1 {
use frost_core::frost::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<R>;
/// 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<R>;
/// 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>(
participant_identifier: frost::Identifier<R>,
secret: &SigningShare<R>,
rng: &mut RNG,
) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<R, RNG>(participant_identifier, secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<R>;
/// 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<R>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<R>;
/// 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<R>;
/// 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: &round2::SigningPackage,
signature_shares: &[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<R>;
/// A valid verifying key for Schnorr signatures on FROST(secp256k1, SHA-256).
pub type VerifyingKey = frost_core::VerifyingKey<R>;

View File

@ -0,0 +1,24 @@
use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;
use crate::*;
lazy_static! {
pub static ref SECP256K1_SHA256: Value =
serde_json::from_str(include_str!("tests/vectors.json").trim())
.expect("Test vector is valid JSON");
}
/// This is testing that Shamir's secret sharing to compute and arbitrary
/// value is working.
#[test]
fn check_share_generation_secp256k1_sha256() {
let rng = thread_rng();
frost_core::tests::check_share_generation::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_sign_with_test_vectors() {
frost_core::tests::vectors::check_sign_with_test_vectors::<Secp256K1Sha256>(&SECP256K1_SHA256)
}

View File

@ -0,0 +1,68 @@
{
"config": {
"MAX_PARTICIPANTS": "3",
"NUM_PARTICIPANTS": "2",
"MIN_PARTICIPANTS": "2",
"name": "FROST(secp256k1, SHA-256)",
"group": "secp256k1",
"hash": "SHA-256"
},
"inputs": {
"group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114",
"group_public_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f",
"message": "74657374",
"share_polynomial_coefficients": [
"fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579"
],
"participants": {
"1": {
"participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c"
},
"2": {
"participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984"
},
"3": {
"participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc"
}
}
},
"round_one_outputs": {
"participant_list": "1,3",
"participants": {
"1": {
"hiding_nonce_randomness": "80cbea5e405d169999d8c4b30b755fedb26ab07ec8198cda4873ed8ce5e16773",
"binding_nonce_randomness": "f6d5b38197843046b68903048c1feba433e3500145281fa8bb1e26fdfeef5e7f",
"hiding_nonce": "acc83278035223c1ba464e2d11bfacfc872b2b23e1041cf5f6130da21e4d8068",
"binding_nonce": "c3ef169995bc3d2c2d48f30b83d0c63751e67ceb057695bcb2a6aa40ed5d926b",
"hiding_nonce_commitment": "036673d68a928793c33ae07776908eae8ea15dd947ed81284e939aaba118573a5e",
"binding_nonce_commitment": "03d2a96dd4ec1ee29dc22067109d1290dabd8016cb41856ee8ff9281c3fa1baffd",
"binding_factor_input": "a645d8249457bbcac34fa7b740f66bcce08fc39506b8bbf1a1c81092f6272eda82ae39234d714f87a7b91dd67d124a06561a36817c1ecaa255c3527d694fc4f10000000000000000000000000000000000000000000000000000000000000001",
"binding_factor": "d7bcbd29408dedc9e138262d99b09d8b5705d76eb5de2369d9103e4423f8ac79"
},
"3": {
"hiding_nonce_randomness": "b9794047604beda0c5c0529ac9dfd83c0a80399a7bdf4c3e23cef2faf69cdcc3",
"binding_nonce_randomness": "c28ce6252631620b84c2702b34774fab365e286ebc77030a112ebccccbffa78b",
"hiding_nonce": "cb3387defef07fc9010c0564ba6495ed41876626ed86b886ca26cbbd3566ffbc",
"binding_nonce": "4559459735eb68e8c16319a9fd9a14016053957cb8cea273a24b7c7bc1ee26f6",
"hiding_nonce_commitment": "030278e6e6055fb963b40e0c3c37099f803f3f38930fc89092517f8ce1b47e8d6b",
"binding_nonce_commitment": "028eb6d238c6c0fc6216906706ad0ff9943c6c1d6079cdf74f674481ebb2485db3",
"binding_factor_input": "a645d8249457bbcac34fa7b740f66bcce08fc39506b8bbf1a1c81092f6272eda82ae39234d714f87a7b91dd67d124a06561a36817c1ecaa255c3527d694fc4f10000000000000000000000000000000000000000000000000000000000000003",
"binding_factor": "ecc057259f3c8b195308c9b73aaaf840660a37eb264ebce342412c58102ee437"
}
}
},
"round_two_outputs": {
"participant_list": "1,3",
"participants": {
"1": {
"sig_share": "1750b2a314a81b66fd81366583617aaafcffa68f14495204795aa0434b907aa3"
},
"3": {
"sig_share": "e4dbbbbbcb035eb3512918b0368c4ab2c836a92dccff3251efa7a4aacc7d3790"
}
}
},
"final_output": {
"sig": "0259696aac722558e8638485d252bb2556f6241a7adfdf284c8c87a3428d46448dfc2c6e5edfab7a1a4eaa4f15b9edc55dc5364fbce1488456690244ee180db233"
}
}

View File

@ -0,0 +1,41 @@
use frost_core::{Ciphersuite, Group};
use frost_secp256k1::*;
use rand::thread_rng;
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::check_sign_with_dealer::<Secp256K1Sha256, _>(rng);
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::check_sign_with_dkg::<Secp256K1Sha256, _>(rng);
}
#[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 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 as Group>::deserialize(&encoded_identity);
assert_eq!(r, Err(Error::MalformedElement));
}

View File

@ -0,0 +1,33 @@
use frost_core::tests::proptests::{tweak_strategy, SignatureCase};
use frost_secp256k1::*;
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 mut 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(&mut rng, msg.to_vec());
// Apply tweaks to each case.
for t in &tweaks {
sig.apply_tweak(t);
}
assert!(sig.check());
}
}

View File

@ -168,4 +168,20 @@ fn main() {
&["frost_ed25519", "Ed25519 curve"],
);
}
write_docs(
&docs,
"frost-secp256k1/src/lib.rs",
&["Secp256K1Sha556", "Secp256K1", "<E>"],
old_suite_names_doc,
&["FROST(secp256k1, SHA-256)"],
);
for filename in ["README.md", "dkg.md"] {
copy_and_replace(
filename,
"frost-secp256k1/README.md",
original_strings,
&["frost_secp256k1", "secp256k1 curve"],
);
}
}