From 5d86d940e65845164ca15b6b41de28145607a5e1 Mon Sep 17 00:00:00 2001 From: Nick Farrow Date: Wed, 3 Jul 2024 20:29:42 +1000 Subject: [PATCH] [frost] updated frost proptest and minor fixups --- schnorr_fun/src/{frost.rs => frost/mod.rs} | 0 schnorr_fun/src/frost/share.rs | 21 +++-- schnorr_fun/tests/frost_prop.rs | 102 +++++++++++++++++++++ 3 files changed, 113 insertions(+), 10 deletions(-) rename schnorr_fun/src/{frost.rs => frost/mod.rs} (100%) create mode 100644 schnorr_fun/tests/frost_prop.rs diff --git a/schnorr_fun/src/frost.rs b/schnorr_fun/src/frost/mod.rs similarity index 100% rename from schnorr_fun/src/frost.rs rename to schnorr_fun/src/frost/mod.rs diff --git a/schnorr_fun/src/frost/share.rs b/schnorr_fun/src/frost/share.rs index 7bf1ffac..c349a695 100644 --- a/schnorr_fun/src/frost/share.rs +++ b/schnorr_fun/src/frost/share.rs @@ -2,11 +2,11 @@ use secp256kfun::{marker::*, poly, Scalar}; /// A *[Shamir secret share]*. /// /// Each share is an `(x,y)` pair where `y = p(x)` for some polynomial `p`. With a sufficient -/// number of unique pairs you can recontruct `p` as a vector of `Scalar`s where each `Scalar` is a +/// number of unique pairs you can reconstruct `p` as a vector of `Scalar`s where each `Scalar` is a /// coefficient `p`. This structure is useful for hiding a secret `y*` by having `p(0) = y*` until a -/// sufficient number of shares come together to resconstruct `y*`. +/// sufficient number of shares come together to reconstruct `y*`. /// -/// Signing keys in FROST are also shamir secert shares which is why this is here. +/// Signing keys in FROST are also shamir secret shares which is why this is here. /// /// ## Backup format (bech32 chars) /// @@ -15,10 +15,10 @@ use secp256kfun::{marker::*, poly, Scalar}; /// the payload. /// /// We optionally have the index in the human readable part since users can more easily identify -/// shares that way. Share identification can help for keeping track of them and distinguishing them -/// there are only a small numbner of shares. +/// shares that way. Share identification can help for keeping track of them and distinguishing shares +/// when there are only a small number of them. /// -/// The backuip format is enabled with the `share_backup` feature and accessed with the `FromStr` +/// The backup format is enabled with the `share_backup` feature and accessed with the `FromStr` /// and `Display`. /// /// ### Index in human readable part @@ -30,7 +30,7 @@ use secp256kfun::{marker::*, poly, Scalar}; /// /// The payload consists of: /// -/// - `secret_share` (`[u8;32]`): the 32 bytes that reperesents the secret share scalar in big-endian encoding +/// - `secret_share` (`[u8;32]`): the 32 bytes that represents the secret share scalar in big-endian encoding /// /// ### Index in payload /// @@ -42,7 +42,7 @@ use secp256kfun::{marker::*, poly, Scalar}; /// The payload consists of: /// /// - `secret_share` (`[u8;32]`): the 32 bytes that reperesents the secret share scalar in big-endian encoding -/// - `share_index`: [u8;1..32] which is the index where the polynomial was evalulated to create the share. This is also a big-endian scalar except that the leading zero bytes are dropped so the smaller the index the smaller the encoding. +/// - `share_index`: [u8;1..32] which is the index where the polynomial was evaluated to create the share. This is also a big-endian scalar except that the leading zero bytes are dropped so the smaller the index the smaller the encoding. /// /// [Shamir secret share]: https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing /// [`bech32m`]: https://bips.xyz/350 @@ -282,10 +282,11 @@ mod test { }; proptest! { #[test] - fn recover_secret(parties in 1usize..10, threshold in 1usize..5) { + fn recover_secret( + (parties, threshold) in (1usize..=10).prop_flat_map(|n| (Just(n), 1usize..=n)), + ) { use rand::seq::SliceRandom; let frost = frost::new_with_deterministic_nonces::(); - let parties = parties.max(threshold); let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha); let (frost_key, shares) = frost.simulate_keygen(threshold, parties, &mut rng); diff --git a/schnorr_fun/tests/frost_prop.rs b/schnorr_fun/tests/frost_prop.rs new file mode 100644 index 00000000..adfd9b56 --- /dev/null +++ b/schnorr_fun/tests/frost_prop.rs @@ -0,0 +1,102 @@ +#![cfg(feature = "alloc")] +use rand::seq::SliceRandom; +use rand_chacha::ChaCha20Rng; +use schnorr_fun::{ + frost::*, + fun::{marker::*, Scalar}, + Message, +}; +use secp256kfun::proptest::{ + arbitrary::any, + option, proptest, + strategy::{Just, Strategy}, + test_runner::{RngAlgorithm, TestRng}, +}; +use sha2::Sha256; +use std::collections::BTreeMap; + +proptest! { + + #[test] + fn frost_prop_test( + (n_parties, threshold) in (2usize..=4).prop_flat_map(|n| (Just(n), 2usize..=n)), + plain_tweak in option::of(any::>()), + xonly_tweak in option::of(any::>()) + ) { + let proto = new_with_deterministic_nonces::(); + assert!(threshold <= n_parties); + + // // create some scalar polynomial for each party + let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha); + let (mut frost_key, secret_shares) = proto.simulate_keygen(threshold, n_parties, &mut rng); + + if let Some(tweak) = plain_tweak { + frost_key = frost_key.tweak(tweak).unwrap(); + } + + let mut frost_key = frost_key.into_xonly_key(); + + if let Some(tweak) = xonly_tweak { + frost_key = frost_key.tweak(tweak).unwrap(); + } + + // use a boolean mask for which t participants are signers + let mut signer_mask = vec![true; threshold]; + signer_mask.append(&mut vec![false; n_parties - threshold]); + // shuffle the mask for random signers + signer_mask.shuffle(&mut rng); + + let secret_shares = signer_mask.into_iter().zip(secret_shares.into_iter()).filter(|(is_signer, _)| *is_signer) + .map(|(_, secret_share)| secret_share).collect::>(); + + + let sid = b"frost-prop-test".as_slice(); + let message = Message::plain("test", b"test"); + + let mut secret_nonces: BTreeMap<_, _> = secret_shares.iter().map(|secret_share| { + (secret_share.index, proto.gen_nonce::( + &mut proto.seed_nonce_rng( + &frost_key, + &secret_share.secret, + sid, + ))) + }).collect(); + + + let public_nonces = secret_nonces.iter().map(|(signer_index, sn)| (*signer_index, sn.public())).collect::>(); + dbg!(&public_nonces); + + let signing_session = proto.start_sign_session( + &frost_key, + public_nonces, + message + ); + + let mut signatures = vec![]; + for secret_share in secret_shares { + let sig = proto.sign( + &frost_key, + &signing_session, + &secret_share, + secret_nonces.remove(&secret_share.index).unwrap() + ); + assert!(proto.verify_signature_share( + &frost_key, + &signing_session, + secret_share.index, + sig) + ); + signatures.push(sig); + } + let combined_sig = proto.combine_signature_shares( + &frost_key, + &signing_session, + signatures); + + assert!(proto.schnorr.verify( + &frost_key.public_key(), + message, + &combined_sig + )); + } +}