Make tests generic (#105)
* make tests generic * restore serialization test; remove Debug bound by using debugless-unwrap * fix spacings in Cargo.toml
This commit is contained in:
parent
d1ddf72136
commit
298da8f5fd
|
@ -19,6 +19,7 @@ features = ["nightly"]
|
|||
|
||||
[dependencies]
|
||||
byteorder = "1.4"
|
||||
debugless-unwrap = "0.0.4"
|
||||
digest = "0.10"
|
||||
hex = { version = "0.4.3", features = ["serde"] }
|
||||
rand_core = "0.6"
|
||||
|
@ -26,6 +27,11 @@ serde = { version = "1", optional = true, features = ["derive"] }
|
|||
thiserror = "1.0"
|
||||
zeroize = { version = "1.5.4", default-features = false, features = ["derive"] }
|
||||
|
||||
# Test dependencies used with the test-impl feature
|
||||
proptest = { version = "1.0", optional = true }
|
||||
proptest-derive = { version = "0.3", optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
|
||||
lazy_static = "1.4"
|
||||
|
@ -38,3 +44,5 @@ sha2 = "0.10.2"
|
|||
[features]
|
||||
nightly = []
|
||||
default = ["serde"]
|
||||
# Exposes ciphersuite-generic tests for other crates to use
|
||||
test-impl = ["proptest", "proptest-derive", "serde_json"]
|
||||
|
|
|
@ -19,6 +19,8 @@ pub mod frost;
|
|||
mod scalar_mul;
|
||||
mod signature;
|
||||
mod signing_key;
|
||||
#[cfg(any(test, feature = "test-impl"))]
|
||||
pub mod tests;
|
||||
mod verifying_key;
|
||||
|
||||
pub use error::Error;
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
//! Schnorr signatures over prime order groups (or subgroups)
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
// use hex::FromHex;
|
||||
use debugless_unwrap::DebuglessUnwrap;
|
||||
|
||||
use crate::{Ciphersuite, Error, Field, Group};
|
||||
|
||||
|
@ -23,11 +21,7 @@ where
|
|||
<C::Group as Group>::Field: Field,
|
||||
{
|
||||
/// Converts bytes as [`C::SignatureSerialization`] into a `Signature<C>`.
|
||||
pub fn from_bytes(bytes: C::SignatureSerialization) -> Result<Self, Error>
|
||||
where
|
||||
<<C::Group as Group>::Serialization as TryFrom<Vec<u8>>>::Error: Debug,
|
||||
<<<C::Group as Group>::Field as Field>::Serialization as TryFrom<Vec<u8>>>::Error: Debug,
|
||||
{
|
||||
pub fn from_bytes(bytes: C::SignatureSerialization) -> Result<Self, Error> {
|
||||
// To compute the expected length of the encoded point, encode the generator
|
||||
// and get its length. Note that we can't use the identity because it can be encoded
|
||||
// shorter in some cases (e.g. P-256, which uses SEC1 encoding).
|
||||
|
@ -57,16 +51,13 @@ where
|
|||
}
|
||||
|
||||
/// Converts this signature to its [`C::SignatureSerialization`] in bytes.
|
||||
pub fn to_bytes(&self) -> C::SignatureSerialization
|
||||
where
|
||||
<<C as Ciphersuite>::SignatureSerialization as TryFrom<Vec<u8>>>::Error: Debug,
|
||||
{
|
||||
pub fn to_bytes(&self) -> C::SignatureSerialization {
|
||||
let mut bytes = vec![];
|
||||
|
||||
bytes.extend(<C::Group as Group>::serialize(&self.R).as_ref());
|
||||
bytes.extend(<<C::Group as Group>::Field as Field>::serialize(&self.z).as_ref());
|
||||
|
||||
bytes.try_into().unwrap()
|
||||
bytes.try_into().debugless_unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,35 @@
|
|||
//! Ciphersuite-generic test functions.
|
||||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use frost_core::frost::{self, Identifier};
|
||||
use rand::thread_rng;
|
||||
use crate::frost::{self, Identifier};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
mod common;
|
||||
use crate::Ciphersuite;
|
||||
|
||||
use common::ciphersuite::Ristretto255Sha512 as R;
|
||||
pub mod proptests;
|
||||
pub mod vectors;
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_dealer() {
|
||||
let mut rng = thread_rng();
|
||||
/// Test share generation with a Ciphersuite
|
||||
pub fn check_share_generation<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(mut rng: R) {
|
||||
let secret = frost::keys::Secret::<C>::random(&mut rng);
|
||||
|
||||
let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();
|
||||
|
||||
for secret_share in secret_shares.iter() {
|
||||
assert_eq!(secret_share.verify(), Ok(()));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
frost::keys::reconstruct_secret::<C>(secret_shares).unwrap(),
|
||||
secret
|
||||
)
|
||||
}
|
||||
|
||||
/// Test FROST signing with trusted dealer with a Ciphersuite.
|
||||
pub fn check_sign_with_dealer<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(mut rng: R)
|
||||
where
|
||||
<C as Ciphersuite>::Group: std::cmp::PartialEq,
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -21,7 +40,7 @@ fn check_sign_with_dealer() {
|
|||
frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
|
||||
|
||||
// Verifies the secret shares from the dealer
|
||||
let key_packages: HashMap<frost::Identifier<R>, frost::keys::KeyPackage<R>> = shares
|
||||
let key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>> = shares
|
||||
.into_iter()
|
||||
.map(|share| {
|
||||
(
|
||||
|
@ -31,8 +50,8 @@ fn check_sign_with_dealer() {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let mut nonces: HashMap<Identifier<R>, frost::round1::SigningNonces<R>> = HashMap::new();
|
||||
let mut commitments: HashMap<Identifier<R>, frost::round1::SigningCommitments<R>> =
|
||||
let mut nonces: HashMap<Identifier<C>, frost::round1::SigningNonces<C>> = HashMap::new();
|
||||
let mut commitments: HashMap<Identifier<C>, frost::round1::SigningCommitments<C>> =
|
||||
HashMap::new();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -58,7 +77,7 @@ fn check_sign_with_dealer() {
|
|||
// 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<frost::round2::SignatureShare<R>> = Vec::new();
|
||||
let mut signature_shares: Vec<frost::round2::SignatureShare<C>> = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
|
@ -1,14 +1,12 @@
|
|||
use frost_core::*;
|
||||
//! Ciphersuite-generic functions for proptests
|
||||
|
||||
use crate::*;
|
||||
use proptest::prelude::*;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
mod common;
|
||||
|
||||
use common::ciphersuite::Ristretto255Sha512 as R;
|
||||
|
||||
/// A signature test-case, containing signature data and expected validity.
|
||||
#[derive(Clone, Debug)]
|
||||
struct SignatureCase<C: Ciphersuite> {
|
||||
pub struct SignatureCase<C: Ciphersuite> {
|
||||
msg: Vec<u8>,
|
||||
sig: Signature<C>,
|
||||
vk: VerifyingKey<C>,
|
||||
|
@ -18,7 +16,7 @@ struct SignatureCase<C: Ciphersuite> {
|
|||
|
||||
/// A modification to a test-case.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Tweak {
|
||||
pub enum Tweak {
|
||||
/// No-op, used to check that unchanged cases verify.
|
||||
None,
|
||||
/// Change the message the signature is defined for, invalidating the signature.
|
||||
|
@ -41,7 +39,8 @@ impl<C> SignatureCase<C>
|
|||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
|
||||
/// Create a new SignatureCase.
|
||||
pub fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
|
||||
let sk = SigningKey::<C>::new(&mut rng);
|
||||
let sig = sk.sign(&mut rng, &msg);
|
||||
let vk = VerifyingKey::<C>::from(&sk);
|
||||
|
@ -55,24 +54,25 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// Check that signature verification succeeds or fails, as expected.
|
||||
fn check(&self) -> bool {
|
||||
// // The signature data is stored in (refined) byte types, but do a round trip
|
||||
// // conversion to raw bytes to exercise those code paths.
|
||||
// let sig = {
|
||||
// let bytes: [u8; 64] = self.sig.into();
|
||||
// Signature::<C>::from_bytes(bytes)
|
||||
// };
|
||||
/// Check that signature verification succeeds or fails, as expected.
|
||||
pub fn check(&self) -> bool {
|
||||
// The signature data is stored in (refined) byte types, but do a round trip
|
||||
// conversion to raw bytes to exercise those code paths.
|
||||
let _sig = {
|
||||
let bytes = self.sig.to_bytes();
|
||||
Signature::<C>::from_bytes(bytes)
|
||||
};
|
||||
|
||||
// // Check that the verification key is a valid key.
|
||||
// let pub_key = VerifyingKey::<C>::from_bytes(pk_bytes)
|
||||
// .expect("The test verification key to be well-formed.");
|
||||
// Check that the verification key is a valid key.
|
||||
let _pub_key = VerifyingKey::<C>::from_bytes(self.vk.to_bytes())
|
||||
.expect("The test verification key to be well-formed.");
|
||||
|
||||
// Check that signature validation has the expected result.
|
||||
self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok()
|
||||
}
|
||||
|
||||
fn apply_tweak(&mut self, tweak: &Tweak) {
|
||||
/// Apply the given tweak to the signature test case.
|
||||
pub fn apply_tweak(&mut self, tweak: &Tweak) {
|
||||
match tweak {
|
||||
Tweak::None => {}
|
||||
Tweak::ChangeMessage => {
|
||||
|
@ -89,41 +89,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn tweak_strategy() -> impl Strategy<Value = Tweak> {
|
||||
/// Tweak the proptest strategy
|
||||
pub fn tweak_strategy() -> impl Strategy<Value = Tweak> {
|
||||
prop_oneof![
|
||||
10 => Just(Tweak::None),
|
||||
1 => Just(Tweak::ChangeMessage),
|
||||
1 => Just(Tweak::ChangePubkey),
|
||||
]
|
||||
}
|
||||
|
||||
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::<R>::new(&mut rng, msg.to_vec());
|
||||
|
||||
|
||||
// Apply tweaks to each case.
|
||||
for t in &tweaks {
|
||||
sig.apply_tweak(t);
|
||||
}
|
||||
|
||||
assert!(sig.check());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
//! Helper function for testing with test vectors.
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use debugless_unwrap::DebuglessUnwrap;
|
||||
use hex::{self, FromHex};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
frost::{self, keys::*, round1::*, round2::*, *},
|
||||
Ciphersuite, Field, Group, VerifyingKey,
|
||||
};
|
||||
|
||||
/// Parse test vectors for a given ciphersuite.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn parse_test_vectors<C: Ciphersuite>(
|
||||
json_vectors: &Value,
|
||||
) -> (
|
||||
VerifyingKey<C>,
|
||||
HashMap<Identifier<C>, KeyPackage<C>>,
|
||||
Vec<u8>,
|
||||
HashMap<Identifier<C>, SigningNonces<C>>,
|
||||
HashMap<Identifier<C>, SigningCommitments<C>>,
|
||||
Vec<u8>,
|
||||
Rho<C>,
|
||||
HashMap<Identifier<C>, SignatureShare<C>>,
|
||||
Vec<u8>, // Signature<C>,
|
||||
) {
|
||||
let inputs = &json_vectors["inputs"];
|
||||
|
||||
let message = inputs["message"].as_str().unwrap();
|
||||
let message_bytes = hex::decode(message).unwrap();
|
||||
|
||||
let mut key_packages: HashMap<Identifier<C>, KeyPackage<C>> = HashMap::new();
|
||||
|
||||
let possible_signers = json_vectors["inputs"]["signers"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter();
|
||||
|
||||
let group_public =
|
||||
VerifyingKey::<C>::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap();
|
||||
|
||||
for (i, secret_share) in possible_signers {
|
||||
let secret = Secret::<C>::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap();
|
||||
let signer_public = secret.into();
|
||||
|
||||
let key_package = KeyPackage::<C> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
secret_share: secret,
|
||||
public: signer_public,
|
||||
group_public,
|
||||
};
|
||||
|
||||
key_packages.insert(*key_package.identifier(), key_package);
|
||||
}
|
||||
|
||||
// Round one outputs
|
||||
|
||||
let round_one_outputs = &json_vectors["round_one_outputs"];
|
||||
|
||||
let group_binding_factor_input = Vec::<u8>::from_hex(
|
||||
round_one_outputs["group_binding_factor_input"]
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let group_binding_factor =
|
||||
Rho::<C>::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
|
||||
|
||||
let mut signer_nonces: HashMap<Identifier<C>, SigningNonces<C>> = HashMap::new();
|
||||
let mut signer_commitments: HashMap<Identifier<C>, SigningCommitments<C>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
|
||||
let identifier = u16::from_str(i).unwrap().try_into().unwrap();
|
||||
|
||||
let signing_nonces = SigningNonces::<C> {
|
||||
hiding: Nonce::<C>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
|
||||
binding: Nonce::<C>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
|
||||
};
|
||||
|
||||
signer_nonces.insert(identifier, signing_nonces);
|
||||
|
||||
let signing_commitments = SigningCommitments::<C> {
|
||||
identifier,
|
||||
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
|
||||
.unwrap(),
|
||||
binding: NonceCommitment::from_hex(
|
||||
signer["binding_nonce_commitment"].as_str().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
signer_commitments.insert(identifier, signing_commitments);
|
||||
}
|
||||
|
||||
// Round two outputs
|
||||
|
||||
let round_two_outputs = &json_vectors["round_two_outputs"];
|
||||
|
||||
let mut signature_shares: HashMap<Identifier<C>, SignatureShare<C>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
|
||||
let sig_share = <<C::Group as Group>::Field as Field>::Serialization::try_from(
|
||||
hex::decode(signer["sig_share"].as_str().unwrap()).unwrap(),
|
||||
)
|
||||
.debugless_unwrap();
|
||||
|
||||
let signature_share = SignatureShare::<C> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature: SignatureResponse {
|
||||
z_share: <<C::Group as Group>::Field as Field>::deserialize(&sig_share).unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
signature_shares.insert(
|
||||
u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature_share,
|
||||
);
|
||||
}
|
||||
|
||||
// Final output
|
||||
|
||||
let final_output = &json_vectors["final_output"];
|
||||
|
||||
let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap();
|
||||
|
||||
(
|
||||
group_public,
|
||||
key_packages,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
/// Test with the given test vectors for a ciphersuite.
|
||||
pub fn check_sign_with_test_vectors<C: Ciphersuite + PartialEq>(json_vectors: &Value)
|
||||
where
|
||||
C::Group: PartialEq,
|
||||
{
|
||||
let (
|
||||
group_public,
|
||||
key_packages,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
) = parse_test_vectors(json_vectors);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for key_package in key_packages.values() {
|
||||
assert_eq!(
|
||||
*key_package.public(),
|
||||
frost::keys::Public::from(*key_package.secret_share())
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for (i, _) in signer_commitments.clone() {
|
||||
// compute nonce commitments from nonces
|
||||
let nonces = signer_nonces.get(&i).unwrap();
|
||||
let nonce_commitments = signer_commitments.get(&i).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.hiding()),
|
||||
nonce_commitments.hiding()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.binding()),
|
||||
nonce_commitments.binding()
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let signer_commitments_vec = signer_commitments
|
||||
.into_iter()
|
||||
.map(|(_, signing_commitments)| signing_commitments)
|
||||
.collect();
|
||||
|
||||
let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes);
|
||||
|
||||
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
|
||||
|
||||
let rho: frost::Rho<C> = (&signing_package).into();
|
||||
|
||||
assert_eq!(rho, group_binding_factor);
|
||||
|
||||
let mut our_signature_shares: Vec<frost::round2::SignatureShare<C>> = Vec::new();
|
||||
|
||||
// Each participant generates their signature share
|
||||
for identifier in signer_nonces.keys() {
|
||||
let key_package = &key_packages[identifier];
|
||||
let nonces = &signer_nonces[identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap();
|
||||
|
||||
our_signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
for sig_share in our_signature_shares.clone() {
|
||||
assert_eq!(sig_share, signature_shares[sig_share.identifier()]);
|
||||
}
|
||||
|
||||
let signer_pubkeys = key_packages
|
||||
.into_iter()
|
||||
.map(|(i, key_package)| (i, *key_package.public()))
|
||||
.collect();
|
||||
|
||||
let pubkey_package = frost::keys::PublicKeyPackage {
|
||||
signer_pubkeys,
|
||||
group_public,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate the FROST signature from test vector sig shares
|
||||
let group_signature_result = frost::aggregate(
|
||||
&signing_package,
|
||||
&signature_shares
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<frost::round2::SignatureShare<C>>>(),
|
||||
&pubkey_package,
|
||||
);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().as_ref(), signature_bytes);
|
||||
|
||||
// Aggregate the FROST signature from our signature shares
|
||||
let group_signature_result =
|
||||
frost::aggregate(&signing_package, &our_signature_shares, &pubkey_package);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().as_ref(), signature_bytes);
|
||||
}
|
|
@ -10,4 +10,3 @@
|
|||
//! <https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests>
|
||||
|
||||
pub mod ciphersuite;
|
||||
pub mod vectors;
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use hex::{self, FromHex};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::Value;
|
||||
|
||||
use frost_core::{
|
||||
frost::{keys::*, round1::*, round2::*, *},
|
||||
VerifyingKey,
|
||||
};
|
||||
|
||||
use super::ciphersuite::Ristretto255Sha512;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref RISTRETTO255_SHA512: Value =
|
||||
serde_json::from_str(include_str!("vectors.json").trim())
|
||||
.expect("Test vector is valid JSON");
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn parse_test_vectors() -> (
|
||||
VerifyingKey<Ristretto255Sha512>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, KeyPackage<Ristretto255Sha512>>,
|
||||
&'static str,
|
||||
Vec<u8>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, SigningNonces<Ristretto255Sha512>>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, SigningCommitments<Ristretto255Sha512>>,
|
||||
Vec<u8>,
|
||||
Rho<Ristretto255Sha512>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, SignatureShare<Ristretto255Sha512>>,
|
||||
Vec<u8>, // Signature<Ristretto255Sha512>,
|
||||
) {
|
||||
type R = Ristretto255Sha512;
|
||||
|
||||
let inputs = &RISTRETTO255_SHA512["inputs"];
|
||||
|
||||
let message = inputs["message"].as_str().unwrap();
|
||||
let message_bytes = hex::decode(message).unwrap();
|
||||
|
||||
let mut key_packages: HashMap<Identifier<Ristretto255Sha512>, KeyPackage<R>> = HashMap::new();
|
||||
|
||||
let possible_signers = RISTRETTO255_SHA512["inputs"]["signers"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter();
|
||||
|
||||
let group_public =
|
||||
VerifyingKey::<R>::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap();
|
||||
|
||||
for (i, secret_share) in possible_signers {
|
||||
let secret = Secret::<R>::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap();
|
||||
let signer_public = secret.into();
|
||||
|
||||
let key_package = KeyPackage::<R> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
secret_share: secret,
|
||||
public: signer_public,
|
||||
group_public,
|
||||
};
|
||||
|
||||
key_packages.insert(*key_package.identifier(), key_package);
|
||||
}
|
||||
|
||||
// Round one outputs
|
||||
|
||||
let round_one_outputs = &RISTRETTO255_SHA512["round_one_outputs"];
|
||||
|
||||
let group_binding_factor_input = Vec::<u8>::from_hex(
|
||||
round_one_outputs["group_binding_factor_input"]
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let group_binding_factor =
|
||||
Rho::<R>::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
|
||||
|
||||
let mut signer_nonces: HashMap<Identifier<R>, SigningNonces<R>> = HashMap::new();
|
||||
let mut signer_commitments: HashMap<Identifier<R>, SigningCommitments<R>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
|
||||
let identifier = u16::from_str(i).unwrap().try_into().unwrap();
|
||||
|
||||
let signing_nonces = SigningNonces::<R> {
|
||||
hiding: Nonce::<R>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
|
||||
binding: Nonce::<R>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
|
||||
};
|
||||
|
||||
signer_nonces.insert(identifier, signing_nonces);
|
||||
|
||||
let signing_commitments = SigningCommitments::<R> {
|
||||
identifier,
|
||||
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
|
||||
.unwrap(),
|
||||
binding: NonceCommitment::from_hex(
|
||||
signer["binding_nonce_commitment"].as_str().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
signer_commitments.insert(identifier, signing_commitments);
|
||||
}
|
||||
|
||||
// Round two outputs
|
||||
|
||||
let round_two_outputs = &RISTRETTO255_SHA512["round_two_outputs"];
|
||||
|
||||
let mut signature_shares: HashMap<Identifier<R>, SignatureShare<R>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
|
||||
let signature_share = SignatureShare::<R> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature: SignatureResponse {
|
||||
z_share: Scalar::from_canonical_bytes(
|
||||
hex::decode(signer["sig_share"].as_str().unwrap())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
signature_shares.insert(
|
||||
u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature_share,
|
||||
);
|
||||
}
|
||||
|
||||
// Final output
|
||||
|
||||
let final_output = &RISTRETTO255_SHA512["final_output"];
|
||||
|
||||
let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap();
|
||||
|
||||
(
|
||||
group_public,
|
||||
key_packages,
|
||||
message,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
)
|
||||
}
|
|
@ -1,154 +1,30 @@
|
|||
use frost_core::frost::{self};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::thread_rng;
|
||||
use serde_json::Value;
|
||||
|
||||
mod common;
|
||||
|
||||
use common::{ciphersuite::*, vectors::*};
|
||||
use common::ciphersuite::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref RISTRETTO255_SHA512: Value =
|
||||
serde_json::from_str(include_str!("common/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_ristretto255_sha512() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let secret = frost::keys::Secret::<Ristretto255Sha512>::random(&mut rng);
|
||||
|
||||
let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();
|
||||
|
||||
for secret_share in secret_shares.iter() {
|
||||
assert_eq!(secret_share.verify(), Ok(()));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
frost::keys::reconstruct_secret::<Ristretto255Sha512>(secret_shares).unwrap(),
|
||||
secret
|
||||
)
|
||||
let rng = thread_rng();
|
||||
frost_core::tests::check_share_generation::<Ristretto255Sha512, _>(rng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_test_vectors() {
|
||||
let (
|
||||
group_public,
|
||||
key_packages,
|
||||
_message,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
) = parse_test_vectors();
|
||||
|
||||
type R = Ristretto255Sha512;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for key_package in key_packages.values() {
|
||||
assert_eq!(
|
||||
*key_package.public(),
|
||||
frost::keys::Public::from(*key_package.secret_share())
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for (i, _) in signer_commitments.clone() {
|
||||
// compute nonce commitments from nonces
|
||||
let nonces = signer_nonces.get(&i).unwrap();
|
||||
let nonce_commitments = signer_commitments.get(&i).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.hiding()),
|
||||
nonce_commitments.hiding()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.binding()),
|
||||
nonce_commitments.binding()
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let signer_commitments_vec = signer_commitments
|
||||
.into_iter()
|
||||
.map(|(_, signing_commitments)| signing_commitments)
|
||||
.collect();
|
||||
|
||||
let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes);
|
||||
|
||||
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
|
||||
|
||||
let rho: frost::Rho<R> = (&signing_package).into();
|
||||
|
||||
assert_eq!(rho, group_binding_factor);
|
||||
|
||||
let mut our_signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
|
||||
|
||||
// Each participant generates their signature share
|
||||
for identifier in signer_nonces.keys() {
|
||||
let key_package = &key_packages[identifier];
|
||||
let nonces = &signer_nonces[identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap();
|
||||
|
||||
our_signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
for sig_share in our_signature_shares.clone() {
|
||||
assert_eq!(sig_share, signature_shares[sig_share.identifier()]);
|
||||
}
|
||||
|
||||
let signer_pubkeys = key_packages
|
||||
.into_iter()
|
||||
.map(|(i, key_package)| (i, *key_package.public()))
|
||||
.collect();
|
||||
|
||||
let pubkey_package = frost::keys::PublicKeyPackage {
|
||||
signer_pubkeys,
|
||||
group_public,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate the FROST signature from test vector sig shares
|
||||
let group_signature_result = frost::aggregate(
|
||||
&signing_package,
|
||||
&signature_shares
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<frost::round2::SignatureShare<R>>>(),
|
||||
&pubkey_package,
|
||||
);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||
|
||||
// Aggregate the FROST signature from our signature shares
|
||||
let group_signature_result =
|
||||
frost::aggregate(&signing_package, &our_signature_shares, &pubkey_package);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||
frost_core::tests::vectors::check_sign_with_test_vectors::<Ristretto255Sha512>(
|
||||
&RISTRETTO255_SHA512,
|
||||
)
|
||||
}
|
||||
|
||||
// This allows checking that to_scalar() works for all possible inputs;
|
||||
|
|
|
@ -21,7 +21,7 @@ features = ["nightly"]
|
|||
byteorder = "1.4"
|
||||
p256 = { version = "0.11.1", features = ["hash2curve"] }
|
||||
digest = "0.10"
|
||||
frost-core = { path = "../frost-core" }
|
||||
frost-core = { path = "../frost-core", features = ["test-impl"] }
|
||||
hex = { version = "0.4.3", features = ["serde"] }
|
||||
rand_core = "0.6"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
|
|
|
@ -1,153 +1,24 @@
|
|||
use lazy_static::lazy_static;
|
||||
use rand::thread_rng;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::*;
|
||||
|
||||
mod vectors;
|
||||
|
||||
use vectors::*;
|
||||
lazy_static! {
|
||||
pub static ref P256_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_p256_sha256() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let secret = frost::keys::Secret::<P256Sha256>::random(&mut rng);
|
||||
|
||||
let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();
|
||||
|
||||
for secret_share in secret_shares.iter() {
|
||||
assert_eq!(secret_share.verify(), Ok(()));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
frost::keys::reconstruct_secret::<P256Sha256>(secret_shares).unwrap(),
|
||||
secret
|
||||
)
|
||||
let rng = thread_rng();
|
||||
frost_core::tests::check_share_generation::<P256Sha256, _>(rng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_test_vectors() {
|
||||
let (
|
||||
group_public,
|
||||
key_packages,
|
||||
_message,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
) = parse_test_vectors();
|
||||
|
||||
type R = P256Sha256;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for key_package in key_packages.values() {
|
||||
assert_eq!(
|
||||
*key_package.public(),
|
||||
frost::keys::Public::from(*key_package.secret_share())
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for (i, _) in signer_commitments.clone() {
|
||||
// compute nonce commitments from nonces
|
||||
let nonces = signer_nonces.get(&i).unwrap();
|
||||
let nonce_commitments = signer_commitments.get(&i).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.hiding()),
|
||||
nonce_commitments.hiding()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.binding()),
|
||||
nonce_commitments.binding()
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let signer_commitments_vec = signer_commitments
|
||||
.into_iter()
|
||||
.map(|(_, signing_commitments)| signing_commitments)
|
||||
.collect();
|
||||
|
||||
let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes);
|
||||
|
||||
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
|
||||
|
||||
let rho: frost::Rho<R> = (&signing_package).into();
|
||||
|
||||
assert_eq!(rho, group_binding_factor);
|
||||
|
||||
let mut our_signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
|
||||
|
||||
// Each participant generates their signature share
|
||||
for identifier in signer_nonces.keys() {
|
||||
let key_package = &key_packages[identifier];
|
||||
let nonces = &signer_nonces[identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap();
|
||||
|
||||
our_signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
for sig_share in our_signature_shares.clone() {
|
||||
assert_eq!(sig_share, signature_shares[sig_share.identifier()]);
|
||||
}
|
||||
|
||||
let signer_pubkeys = key_packages
|
||||
.into_iter()
|
||||
.map(|(i, key_package)| (i, *key_package.public()))
|
||||
.collect();
|
||||
|
||||
let pubkey_package = frost::keys::PublicKeyPackage {
|
||||
signer_pubkeys,
|
||||
group_public,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate the FROST signature from test vector sig shares
|
||||
let group_signature_result = frost::aggregate(
|
||||
&signing_package,
|
||||
&signature_shares
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<frost::round2::SignatureShare<R>>>(),
|
||||
&pubkey_package,
|
||||
);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||
|
||||
// Aggregate the FROST signature from our signature shares
|
||||
let group_signature_result =
|
||||
frost::aggregate(&signing_package, &our_signature_shares, &pubkey_package);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||
frost_core::tests::vectors::check_sign_with_test_vectors::<P256Sha256>(&P256_SHA256)
|
||||
}
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use hex::{self, FromHex};
|
||||
use lazy_static::lazy_static;
|
||||
use p256::elliptic_curve::PrimeField;
|
||||
use p256::{FieldBytes, Scalar};
|
||||
use serde_json::Value;
|
||||
|
||||
use frost_core::{
|
||||
frost::{keys::*, round1::*, round2::*, *},
|
||||
VerifyingKey,
|
||||
};
|
||||
|
||||
use crate::P256Sha256;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref P256_SHA256: Value = serde_json::from_str(include_str!("vectors.json").trim())
|
||||
.expect("Test vector is valid JSON");
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn parse_test_vectors() -> (
|
||||
VerifyingKey<P256Sha256>,
|
||||
HashMap<Identifier<P256Sha256>, KeyPackage<P256Sha256>>,
|
||||
&'static str,
|
||||
Vec<u8>,
|
||||
HashMap<Identifier<P256Sha256>, SigningNonces<P256Sha256>>,
|
||||
HashMap<Identifier<P256Sha256>, SigningCommitments<P256Sha256>>,
|
||||
Vec<u8>,
|
||||
Rho<P256Sha256>,
|
||||
HashMap<Identifier<P256Sha256>, SignatureShare<P256Sha256>>,
|
||||
Vec<u8>, // Signature<P256Sha256>,
|
||||
) {
|
||||
type R = P256Sha256;
|
||||
|
||||
let inputs = &P256_SHA256["inputs"];
|
||||
|
||||
let message = inputs["message"].as_str().unwrap();
|
||||
let message_bytes = hex::decode(message).unwrap();
|
||||
|
||||
let mut key_packages: HashMap<Identifier<P256Sha256>, KeyPackage<R>> = HashMap::new();
|
||||
|
||||
let possible_signers = P256_SHA256["inputs"]["signers"].as_object().unwrap().iter();
|
||||
|
||||
let group_public =
|
||||
VerifyingKey::<R>::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap();
|
||||
|
||||
for (i, secret_share) in possible_signers {
|
||||
let secret = Secret::<R>::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap();
|
||||
let signer_public = secret.into();
|
||||
|
||||
let key_package = KeyPackage::<R> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
secret_share: secret,
|
||||
public: signer_public,
|
||||
group_public,
|
||||
};
|
||||
|
||||
key_packages.insert(*key_package.identifier(), key_package);
|
||||
}
|
||||
|
||||
// Round one outputs
|
||||
|
||||
let round_one_outputs = &P256_SHA256["round_one_outputs"];
|
||||
|
||||
let group_binding_factor_input = Vec::<u8>::from_hex(
|
||||
round_one_outputs["group_binding_factor_input"]
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let group_binding_factor =
|
||||
Rho::<R>::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
|
||||
|
||||
let mut signer_nonces: HashMap<Identifier<R>, SigningNonces<R>> = HashMap::new();
|
||||
let mut signer_commitments: HashMap<Identifier<R>, SigningCommitments<R>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
|
||||
let identifier = u16::from_str(i).unwrap().try_into().unwrap();
|
||||
|
||||
let signing_nonces = SigningNonces::<R> {
|
||||
hiding: Nonce::<R>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
|
||||
binding: Nonce::<R>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
|
||||
};
|
||||
|
||||
signer_nonces.insert(identifier, signing_nonces);
|
||||
|
||||
let signing_commitments = SigningCommitments::<R> {
|
||||
identifier,
|
||||
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
|
||||
.unwrap(),
|
||||
binding: NonceCommitment::from_hex(
|
||||
signer["binding_nonce_commitment"].as_str().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
signer_commitments.insert(identifier, signing_commitments);
|
||||
}
|
||||
|
||||
// Round two outputs
|
||||
|
||||
let round_two_outputs = &P256_SHA256["round_two_outputs"];
|
||||
|
||||
let mut signature_shares: HashMap<Identifier<R>, SignatureShare<R>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
|
||||
let signature_share = SignatureShare::<R> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature: SignatureResponse {
|
||||
z_share: Scalar::from_repr(*FieldBytes::from_slice(
|
||||
hex::decode(signer["sig_share"].as_str().unwrap())
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
))
|
||||
.unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
signature_shares.insert(
|
||||
u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature_share,
|
||||
);
|
||||
}
|
||||
|
||||
// Final output
|
||||
|
||||
let final_output = &P256_SHA256["final_output"];
|
||||
|
||||
let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap();
|
||||
|
||||
(
|
||||
group_public,
|
||||
key_packages,
|
||||
message,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
)
|
||||
}
|
|
@ -1,98 +1,9 @@
|
|||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use frost_p256::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_dealer() {
|
||||
let mut rng = thread_rng();
|
||||
let rng = thread_rng();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let numsigners = 5;
|
||||
let threshold = 3;
|
||||
let (shares, pubkeys) = keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
|
||||
|
||||
// Verifies the secret shares from the dealer
|
||||
let key_packages: HashMap<Identifier, keys::KeyPackage> = shares
|
||||
.into_iter()
|
||||
.map(|share| (share.identifier, keys::KeyPackage::try_from(share).unwrap()))
|
||||
.collect();
|
||||
|
||||
let mut nonces: HashMap<Identifier, round1::SigningNonces> = HashMap::new();
|
||||
let mut commitments: HashMap<Identifier, round1::SigningCommitments> = HashMap::new();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for participant_index in 1..(threshold 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) = round1::commit(
|
||||
participant_identifier,
|
||||
key_packages
|
||||
.get(&participant_identifier)
|
||||
.unwrap()
|
||||
.secret_share(),
|
||||
&mut rng,
|
||||
);
|
||||
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<round2::SignatureShare> = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
let signing_package = SigningPackage::new(comms, message.to_vec());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for participant_identifier in nonces.keys() {
|
||||
let key_package = key_packages.get(participant_identifier).unwrap();
|
||||
|
||||
let nonces_to_use = &nonces.get(participant_identifier).unwrap();
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = round2::sign(&signing_package, nonces_to_use, key_package).unwrap();
|
||||
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_res = aggregate(&signing_package, &signature_shares[..], &pubkeys);
|
||||
|
||||
assert!(group_signature_res.is_ok());
|
||||
|
||||
let group_signature = group_signature_res.unwrap();
|
||||
|
||||
// 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());
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key) from SharePackage.group_public
|
||||
for (participant_identifier, _) in nonces.clone() {
|
||||
let key_package = key_packages.get(&participant_identifier).unwrap();
|
||||
|
||||
assert!(key_package
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
}
|
||||
frost_core::tests::check_sign_with_dealer::<P256Sha256, _>(rng);
|
||||
}
|
||||
|
|
|
@ -1,94 +1,6 @@
|
|||
use frost_core::tests::proptests::{tweak_strategy, SignatureCase};
|
||||
use frost_p256::*;
|
||||
use proptest::prelude::*;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// A signature test-case, containing signature data and expected validity.
|
||||
#[derive(Clone, Debug)]
|
||||
struct SignatureCase {
|
||||
msg: Vec<u8>,
|
||||
sig: Signature,
|
||||
vk: VerifyingKey,
|
||||
invalid_vk: VerifyingKey,
|
||||
is_valid: bool,
|
||||
}
|
||||
|
||||
/// A modification to a test-case.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Tweak {
|
||||
/// No-op, used to check that unchanged cases verify.
|
||||
None,
|
||||
/// Change the message the signature is defined for, invalidating the signature.
|
||||
ChangeMessage,
|
||||
/// Change the public key the signature is defined for, invalidating the signature.
|
||||
ChangePubkey,
|
||||
/* XXX implement this -- needs to regenerate a custom signature because the
|
||||
nonce commitment is fed into the hash, so it has to have torsion at signing
|
||||
time.
|
||||
/// Change the case to have a torsion component in the signature's `r` value.
|
||||
AddTorsion,
|
||||
*/
|
||||
/* XXX implement this -- needs custom handling of field arithmetic.
|
||||
/// Change the signature's `s` scalar to be unreduced (mod L), invalidating the signature.
|
||||
UnreducedScalar,
|
||||
*/
|
||||
}
|
||||
|
||||
impl SignatureCase {
|
||||
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
|
||||
let sk = SigningKey::new(&mut rng);
|
||||
let sig = sk.sign(&mut rng, &msg);
|
||||
let vk = VerifyingKey::from(&sk);
|
||||
let invalid_vk = VerifyingKey::from(&SigningKey::new(&mut rng));
|
||||
Self {
|
||||
msg,
|
||||
sig,
|
||||
vk,
|
||||
invalid_vk,
|
||||
is_valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Check that signature verification succeeds or fails, as expected.
|
||||
fn check(&self) -> bool {
|
||||
// The signature data is stored in (refined) byte types, but do a round trip
|
||||
// conversion to raw bytes to exercise those code paths.
|
||||
let _sig = {
|
||||
let bytes = self.sig.to_bytes();
|
||||
Signature::from_bytes(bytes)
|
||||
};
|
||||
|
||||
// Check that the verification key is a valid key.
|
||||
let _pub_key = VerifyingKey::from_bytes(self.vk.to_bytes())
|
||||
.expect("The test verification key to be well-formed.");
|
||||
|
||||
// Check that signature validation has the expected result.
|
||||
self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok()
|
||||
}
|
||||
|
||||
fn apply_tweak(&mut self, tweak: &Tweak) {
|
||||
match tweak {
|
||||
Tweak::None => {}
|
||||
Tweak::ChangeMessage => {
|
||||
// Changing the message makes the signature invalid.
|
||||
self.msg.push(90);
|
||||
self.is_valid = false;
|
||||
}
|
||||
Tweak::ChangePubkey => {
|
||||
// Changing the public key makes the signature invalid.
|
||||
self.vk = self.invalid_vk;
|
||||
self.is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tweak_strategy() -> impl Strategy<Value = Tweak> {
|
||||
prop_oneof![
|
||||
10 => Just(Tweak::None),
|
||||
1 => Just(Tweak::ChangeMessage),
|
||||
1 => Just(Tweak::ChangePubkey),
|
||||
]
|
||||
}
|
||||
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
|
@ -107,7 +19,7 @@ proptest! {
|
|||
|
||||
// Create a test case for each signature type.
|
||||
let msg = b"test message for proptests";
|
||||
let mut sig = SignatureCase::new(&mut rng, msg.to_vec());
|
||||
let mut sig = SignatureCase::<P256Sha256>::new(&mut rng, msg.to_vec());
|
||||
|
||||
// Apply tweaks to each case.
|
||||
for t in &tweaks {
|
||||
|
|
|
@ -21,7 +21,7 @@ features = ["nightly"]
|
|||
byteorder = "1.4"
|
||||
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
|
||||
digest = "0.10"
|
||||
frost-core = { path = "../frost-core" }
|
||||
frost-core = { path = "../frost-core", features = ["test-impl"] }
|
||||
hex = { version = "0.4.3", features = ["serde"] }
|
||||
rand_core = "0.6"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
|
|
|
@ -1,153 +1,26 @@
|
|||
use lazy_static::lazy_static;
|
||||
use rand::thread_rng;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::*;
|
||||
|
||||
mod vectors;
|
||||
|
||||
use vectors::*;
|
||||
lazy_static! {
|
||||
pub static ref RISTRETTO255_SHA512: 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_ristretto255_sha512() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let secret = frost::keys::Secret::<Ristretto255Sha512>::random(&mut rng);
|
||||
|
||||
let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();
|
||||
|
||||
for secret_share in secret_shares.iter() {
|
||||
assert_eq!(secret_share.verify(), Ok(()));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
frost::keys::reconstruct_secret::<Ristretto255Sha512>(secret_shares).unwrap(),
|
||||
secret
|
||||
)
|
||||
let rng = thread_rng();
|
||||
frost_core::tests::check_share_generation::<Ristretto255Sha512, _>(rng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_test_vectors() {
|
||||
let (
|
||||
group_public,
|
||||
key_packages,
|
||||
_message,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
) = parse_test_vectors();
|
||||
|
||||
type R = Ristretto255Sha512;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for key_package in key_packages.values() {
|
||||
assert_eq!(
|
||||
*key_package.public(),
|
||||
frost::keys::Public::from(*key_package.secret_share())
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for (i, _) in signer_commitments.clone() {
|
||||
// compute nonce commitments from nonces
|
||||
let nonces = signer_nonces.get(&i).unwrap();
|
||||
let nonce_commitments = signer_commitments.get(&i).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.hiding()),
|
||||
nonce_commitments.hiding()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&frost::round1::NonceCommitment::from(nonces.binding()),
|
||||
nonce_commitments.binding()
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let signer_commitments_vec = signer_commitments
|
||||
.into_iter()
|
||||
.map(|(_, signing_commitments)| signing_commitments)
|
||||
.collect();
|
||||
|
||||
let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes);
|
||||
|
||||
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
|
||||
|
||||
let rho: frost::Rho<R> = (&signing_package).into();
|
||||
|
||||
assert_eq!(rho, group_binding_factor);
|
||||
|
||||
let mut our_signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
|
||||
|
||||
// Each participant generates their signature share
|
||||
for identifier in signer_nonces.keys() {
|
||||
let key_package = &key_packages[identifier];
|
||||
let nonces = &signer_nonces[identifier];
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap();
|
||||
|
||||
our_signature_shares.push(signature_share);
|
||||
}
|
||||
|
||||
for sig_share in our_signature_shares.clone() {
|
||||
assert_eq!(sig_share, signature_shares[sig_share.identifier()]);
|
||||
}
|
||||
|
||||
let signer_pubkeys = key_packages
|
||||
.into_iter()
|
||||
.map(|(i, key_package)| (i, *key_package.public()))
|
||||
.collect();
|
||||
|
||||
let pubkey_package = frost::keys::PublicKeyPackage {
|
||||
signer_pubkeys,
|
||||
group_public,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Aggregation: collects the signing shares from all participants,
|
||||
// generates the final signature.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Aggregate the FROST signature from test vector sig shares
|
||||
let group_signature_result = frost::aggregate(
|
||||
&signing_package,
|
||||
&signature_shares
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<frost::round2::SignatureShare<R>>>(),
|
||||
&pubkey_package,
|
||||
);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||
|
||||
// Aggregate the FROST signature from our signature shares
|
||||
let group_signature_result =
|
||||
frost::aggregate(&signing_package, &our_signature_shares, &pubkey_package);
|
||||
|
||||
// Check that the aggregation passed signature share verification and generation
|
||||
assert!(group_signature_result.is_ok());
|
||||
|
||||
// Check that the generated signature matches the test vector signature
|
||||
let group_signature = group_signature_result.unwrap();
|
||||
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||
frost_core::tests::vectors::check_sign_with_test_vectors::<Ristretto255Sha512>(
|
||||
&RISTRETTO255_SHA512,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use hex::{self, FromHex};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::Value;
|
||||
|
||||
use frost_core::{
|
||||
frost::{keys::*, round1::*, round2::*, *},
|
||||
VerifyingKey,
|
||||
};
|
||||
|
||||
use crate::Ristretto255Sha512;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref RISTRETTO255_SHA512: Value =
|
||||
serde_json::from_str(include_str!("vectors.json").trim())
|
||||
.expect("Test vector is valid JSON");
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn parse_test_vectors() -> (
|
||||
VerifyingKey<Ristretto255Sha512>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, KeyPackage<Ristretto255Sha512>>,
|
||||
&'static str,
|
||||
Vec<u8>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, SigningNonces<Ristretto255Sha512>>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, SigningCommitments<Ristretto255Sha512>>,
|
||||
Vec<u8>,
|
||||
Rho<Ristretto255Sha512>,
|
||||
HashMap<Identifier<Ristretto255Sha512>, SignatureShare<Ristretto255Sha512>>,
|
||||
Vec<u8>, // Signature<Ristretto255Sha512>,
|
||||
) {
|
||||
type R = Ristretto255Sha512;
|
||||
|
||||
let inputs = &RISTRETTO255_SHA512["inputs"];
|
||||
|
||||
let message = inputs["message"].as_str().unwrap();
|
||||
let message_bytes = hex::decode(message).unwrap();
|
||||
|
||||
let mut key_packages: HashMap<Identifier<Ristretto255Sha512>, KeyPackage<R>> = HashMap::new();
|
||||
|
||||
let possible_signers = RISTRETTO255_SHA512["inputs"]["signers"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter();
|
||||
|
||||
let group_public =
|
||||
VerifyingKey::<R>::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap();
|
||||
|
||||
for (i, secret_share) in possible_signers {
|
||||
let secret = Secret::<R>::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap();
|
||||
let signer_public = secret.into();
|
||||
|
||||
let key_package = KeyPackage::<R> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
secret_share: secret,
|
||||
public: signer_public,
|
||||
group_public,
|
||||
};
|
||||
|
||||
key_packages.insert(*key_package.identifier(), key_package);
|
||||
}
|
||||
|
||||
// Round one outputs
|
||||
|
||||
let round_one_outputs = &RISTRETTO255_SHA512["round_one_outputs"];
|
||||
|
||||
let group_binding_factor_input = Vec::<u8>::from_hex(
|
||||
round_one_outputs["group_binding_factor_input"]
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let group_binding_factor =
|
||||
Rho::<R>::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
|
||||
|
||||
let mut signer_nonces: HashMap<Identifier<R>, SigningNonces<R>> = HashMap::new();
|
||||
let mut signer_commitments: HashMap<Identifier<R>, SigningCommitments<R>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
|
||||
let identifier = u16::from_str(i).unwrap().try_into().unwrap();
|
||||
|
||||
let signing_nonces = SigningNonces::<R> {
|
||||
hiding: Nonce::<R>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
|
||||
binding: Nonce::<R>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
|
||||
};
|
||||
|
||||
signer_nonces.insert(identifier, signing_nonces);
|
||||
|
||||
let signing_commitments = SigningCommitments::<R> {
|
||||
identifier,
|
||||
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
|
||||
.unwrap(),
|
||||
binding: NonceCommitment::from_hex(
|
||||
signer["binding_nonce_commitment"].as_str().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
signer_commitments.insert(identifier, signing_commitments);
|
||||
}
|
||||
|
||||
// Round two outputs
|
||||
|
||||
let round_two_outputs = &RISTRETTO255_SHA512["round_two_outputs"];
|
||||
|
||||
let mut signature_shares: HashMap<Identifier<R>, SignatureShare<R>> = HashMap::new();
|
||||
|
||||
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
|
||||
let signature_share = SignatureShare::<R> {
|
||||
identifier: u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature: SignatureResponse {
|
||||
z_share: Scalar::from_canonical_bytes(
|
||||
hex::decode(signer["sig_share"].as_str().unwrap())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
signature_shares.insert(
|
||||
u16::from_str(i).unwrap().try_into().unwrap(),
|
||||
signature_share,
|
||||
);
|
||||
}
|
||||
|
||||
// Final output
|
||||
|
||||
let final_output = &RISTRETTO255_SHA512["final_output"];
|
||||
|
||||
let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap();
|
||||
|
||||
(
|
||||
group_public,
|
||||
key_packages,
|
||||
message,
|
||||
message_bytes,
|
||||
signer_nonces,
|
||||
signer_commitments,
|
||||
group_binding_factor_input,
|
||||
group_binding_factor,
|
||||
signature_shares,
|
||||
signature_bytes,
|
||||
)
|
||||
}
|
|
@ -1,98 +1,9 @@
|
|||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use frost_ristretto255::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_sign_with_dealer() {
|
||||
let mut rng = thread_rng();
|
||||
let rng = thread_rng();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Key generation
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let numsigners = 5;
|
||||
let threshold = 3;
|
||||
let (shares, pubkeys) = keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
|
||||
|
||||
// Verifies the secret shares from the dealer
|
||||
let key_packages: HashMap<Identifier, keys::KeyPackage> = shares
|
||||
.into_iter()
|
||||
.map(|share| (share.identifier, keys::KeyPackage::try_from(share).unwrap()))
|
||||
.collect();
|
||||
|
||||
let mut nonces: HashMap<Identifier, round1::SigningNonces> = HashMap::new();
|
||||
let mut commitments: HashMap<Identifier, round1::SigningCommitments> = HashMap::new();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for participant_index in 1..(threshold 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) = round1::commit(
|
||||
participant_identifier,
|
||||
key_packages
|
||||
.get(&participant_identifier)
|
||||
.unwrap()
|
||||
.secret_share(),
|
||||
&mut rng,
|
||||
);
|
||||
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<round2::SignatureShare> = Vec::new();
|
||||
let message = "message to sign".as_bytes();
|
||||
let comms = commitments.clone().into_values().collect();
|
||||
let signing_package = SigningPackage::new(comms, message.to_vec());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 2: each participant generates their signature share
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
for participant_identifier in nonces.keys() {
|
||||
let key_package = key_packages.get(participant_identifier).unwrap();
|
||||
|
||||
let nonces_to_use = &nonces.get(participant_identifier).unwrap();
|
||||
|
||||
// Each participant generates their signature share.
|
||||
let signature_share = round2::sign(&signing_package, nonces_to_use, key_package).unwrap();
|
||||
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_res = aggregate(&signing_package, &signature_shares[..], &pubkeys);
|
||||
|
||||
assert!(group_signature_res.is_ok());
|
||||
|
||||
let group_signature = group_signature_res.unwrap();
|
||||
|
||||
// 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());
|
||||
|
||||
// Check that the threshold signature can be verified by the group public
|
||||
// key (the verification key) from SharePackage.group_public
|
||||
for (participant_identifier, _) in nonces.clone() {
|
||||
let key_package = key_packages.get(&participant_identifier).unwrap();
|
||||
|
||||
assert!(key_package
|
||||
.group_public
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
}
|
||||
frost_core::tests::check_sign_with_dealer::<Ristretto255Sha512, _>(rng);
|
||||
}
|
||||
|
|
|
@ -1,94 +1,6 @@
|
|||
use frost_core::tests::proptests::{tweak_strategy, SignatureCase};
|
||||
use frost_ristretto255::*;
|
||||
use proptest::prelude::*;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// A signature test-case, containing signature data and expected validity.
|
||||
#[derive(Clone, Debug)]
|
||||
struct SignatureCase {
|
||||
msg: Vec<u8>,
|
||||
sig: Signature,
|
||||
vk: VerifyingKey,
|
||||
invalid_vk: VerifyingKey,
|
||||
is_valid: bool,
|
||||
}
|
||||
|
||||
/// A modification to a test-case.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Tweak {
|
||||
/// No-op, used to check that unchanged cases verify.
|
||||
None,
|
||||
/// Change the message the signature is defined for, invalidating the signature.
|
||||
ChangeMessage,
|
||||
/// Change the public key the signature is defined for, invalidating the signature.
|
||||
ChangePubkey,
|
||||
/* XXX implement this -- needs to regenerate a custom signature because the
|
||||
nonce commitment is fed into the hash, so it has to have torsion at signing
|
||||
time.
|
||||
/// Change the case to have a torsion component in the signature's `r` value.
|
||||
AddTorsion,
|
||||
*/
|
||||
/* XXX implement this -- needs custom handling of field arithmetic.
|
||||
/// Change the signature's `s` scalar to be unreduced (mod L), invalidating the signature.
|
||||
UnreducedScalar,
|
||||
*/
|
||||
}
|
||||
|
||||
impl SignatureCase {
|
||||
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
|
||||
let sk = SigningKey::new(&mut rng);
|
||||
let sig = sk.sign(&mut rng, &msg);
|
||||
let vk = VerifyingKey::from(&sk);
|
||||
let invalid_vk = VerifyingKey::from(&SigningKey::new(&mut rng));
|
||||
Self {
|
||||
msg,
|
||||
sig,
|
||||
vk,
|
||||
invalid_vk,
|
||||
is_valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Check that signature verification succeeds or fails, as expected.
|
||||
fn check(&self) -> bool {
|
||||
// The signature data is stored in (refined) byte types, but do a round trip
|
||||
// conversion to raw bytes to exercise those code paths.
|
||||
let _sig = {
|
||||
let bytes = self.sig.to_bytes();
|
||||
Signature::from_bytes(bytes)
|
||||
};
|
||||
|
||||
// Check that the verification key is a valid key.
|
||||
let _pub_key = VerifyingKey::from_bytes(self.vk.to_bytes())
|
||||
.expect("The test verification key to be well-formed.");
|
||||
|
||||
// Check that signature validation has the expected result.
|
||||
self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok()
|
||||
}
|
||||
|
||||
fn apply_tweak(&mut self, tweak: &Tweak) {
|
||||
match tweak {
|
||||
Tweak::None => {}
|
||||
Tweak::ChangeMessage => {
|
||||
// Changing the message makes the signature invalid.
|
||||
self.msg.push(90);
|
||||
self.is_valid = false;
|
||||
}
|
||||
Tweak::ChangePubkey => {
|
||||
// Changing the public key makes the signature invalid.
|
||||
self.vk = self.invalid_vk;
|
||||
self.is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tweak_strategy() -> impl Strategy<Value = Tweak> {
|
||||
prop_oneof![
|
||||
10 => Just(Tweak::None),
|
||||
1 => Just(Tweak::ChangeMessage),
|
||||
1 => Just(Tweak::ChangePubkey),
|
||||
]
|
||||
}
|
||||
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
|
@ -107,7 +19,7 @@ proptest! {
|
|||
|
||||
// Create a test case for each signature type.
|
||||
let msg = b"test message for proptests";
|
||||
let mut sig = SignatureCase::new(&mut rng, msg.to_vec());
|
||||
let mut sig = SignatureCase::<Ristretto255Sha512>::new(&mut rng, msg.to_vec());
|
||||
|
||||
// Apply tweaks to each case.
|
||||
for t in &tweaks {
|
||||
|
|
Loading…
Reference in New Issue