Module schnorr_fun::frost

source ·
Expand description

The FROST threshold multisignature scheme.

Synopsis

use schnorr_fun::binonce::NonceKeyPair;
use schnorr_fun::fun::s;
use schnorr_fun::{
    frost,
    Message,
};
use std::collections::BTreeMap;
use rand_chacha::ChaCha20Rng;
use sha2::Sha256;
// use sha256 to produce deterministic nonces -- be careful!
let frost = frost::new_with_deterministic_nonces::<Sha256>();
// Use randomness from ThreadRng to create synthetic nonces -- harder to make a mistake.
let frost = frost::new_with_synthetic_nonces::<Sha256, rand::rngs::ThreadRng>();
// We need an RNG for key generation -- don't use ThreadRng in practice see note below.
let mut rng = rand::thread_rng();
// we're doing a 2 out of 3
let threshold = 2;
// Generate our secret scalar polynomial we'll use in the key generation protocol
let my_secret_poly = frost::generate_scalar_poly(threshold, &mut rng);
let my_public_poly = frost::to_point_poly(&my_secret_poly);

// Party indexes can be any non-zero scalar
let my_index = s!(1).public();
let party_index2 = s!(2).public();
let party_index3 = s!(3).public();
// share our public point poly, and receive the point polys from other participants
let public_polys = BTreeMap::from_iter([
    (my_index, my_public_poly),
    (party_index2, public_poly2),
    (party_index3, public_poly3),
]);
let keygen = frost.new_keygen(public_polys).expect("something wrong with what was provided by other parties");
// Generate secret shares for others and proof-of-possession to protect against rogue key attacks.
// We need pass a message to sign for the proof-of-possession. We choose the keygen
// id here but anything works (you can even use the empty message).
let keygen_id = frost.keygen_id(&keygen);
let pop_message = Message::raw(&keygen_id);
let (mut shares_i_generated, my_pop) = frost.create_shares_and_pop(&keygen, &my_secret_poly, pop_message);
// Now we send the corresponding shares we generated to the other parties along with our proof-of-possession.
// Eventually we'll receive shares from the others and combine them to create our secret key share:
let share_i_generated_for_myself = (shares_i_generated.remove(&my_index).unwrap(), my_pop);
let my_shares = BTreeMap::from_iter([
    (my_index, share_i_generated_for_myself),
    (party_index2, share_and_pop_from_2),
    (party_index3, share_and_pop_from_3)
]);
// finish keygen by verifying the shares we received, verifying all proofs-of-possession,
// and calculate our long-lived secret share of the joint FROST key.
let (my_secret_share, frost_key) = frost
    .finish_keygen(
        keygen,
        my_index,
        my_shares,
        pop_message,
    )
    .expect("something was wrong with the shares we received");
// ⚠️ At this point you probably want to check out of band that all the other parties received their
// secret shares correctly. If they all give the OK then we're ready to use the key and do some signing!
let xonly_frost_key = frost_key.into_xonly_key();
let message =  Message::plain("my-app", b"chancellor on brink of second bailout for banks");
// Generate nonces for this signing session.
// ⚠️ session_id MUST be different for every signing attempt to avoid nonce reuse
let session_id = b"signing-ominous-message-about-banks-attempt-1".as_slice();
let mut nonce_rng: ChaCha20Rng = frost.seed_nonce_rng(&xonly_frost_key, &my_secret_share, session_id);
let my_nonce = frost.gen_nonce(&mut nonce_rng);
// share your public nonce with the other signing participant(s) receive public nonces
let nonces = BTreeMap::from_iter([(my_index, my_nonce.public()), (party_index3, received_nonce3)]);
// start a sign session with these nonces for a message
let session = frost.start_sign_session(&xonly_frost_key, nonces, message);
// create a partial signature using our secret share and secret nonce
let my_sig_share = frost.sign(&xonly_frost_key, &session, my_index, &my_secret_share, my_nonce);
// receive the partial signature(s) from the other participant(s) and verify
assert!(frost.verify_signature_share(&xonly_frost_key, &session, party_index3, sig_share3));
// combine signature shares into a single signature that is valid under the FROST key
let combined_sig = frost.combine_signature_shares(&xonly_frost_key, &session, vec![my_sig_share, sig_share3]);
assert!(frost.schnorr.verify(
    &xonly_frost_key.public_key(),
    message,
    &combined_sig
));

Description

In FROST, multiple parties cooperatively generate a single joint public key (FrostKey) for creating Schnorr signatures. Unlike in musig, only some threshold t of the n signers are required to generate a signature under the key (rather than all n).

This implementation is not yet compatible with other existing FROST implementations (notably secp256k1-zkp).

The original scheme was introduced in FROST: Flexible Round-Optimized Schnorr Threshold Signatures. A more satisfying security proof was provided in Security of Multi- and Threshold Signatures.

⚠️ At this stage this implementation is for API exploration purposes only. The way it is currently implemented is not proven secure.

Polynomial Generation

The FROST key generation protocol takes as input a secret polynomial of degree threshold - 1. We represent a polynomial as a Vec<Scalar> where each Scalar represents a coefficient in the polynomial.

The security of the protocol is only guaranteed if you sample your secret polynomial uniformly at random from the perspective of the other parties. You might also want to be able to deterministically re-generate the polynomial from some secret data so that you may restore the polynomial later from the secret. We don’t have tools to use the restored polynomial in this library yet but plan to in the future.

This implementation doesn’t provide a default policy with regards to polynomial generation. Here we give an example of how to generate a deterministic RNG for the frost key generation session that should make sense in most applications:

use schnorr_fun::{frost, fun::{ Scalar, nonce, Tag, derive_nonce_rng }};
use sha2::Sha256;
use rand_chacha::ChaCha20Rng;

let static_secret_key = /* from local storage */
let mut poly_rng = derive_nonce_rng! {
    // use Deterministic nonce gen so we reproduce it later
    nonce_gen => nonce::Deterministic::<Sha256>::default().tag(b"my-app-name/frost/keygen"),
    secret => static_secret_key,
    // session id must be unique for each key generation session
    public => ["frost_key_session_1053"],
    seedable_rng => ChaCha20Rng
};

let threshold = 2;
// we can always reproduce my_secret_poly knowing `static_secret_key`,
// the threshold and the session id
let my_secret_poly: Vec<Scalar> = frost::generate_scalar_poly(threshold, &mut poly_rng);

Note that if a key generation session fails you must always start a fresh session with a different session id.

Re-exports

Structs

Enums

Functions