Module schnorr_fun::frost

source ·
Expand description

The FROST threshold multisignature scheme.

§Synopsis

use schnorr_fun::binonce::NonceKeyPair;
use schnorr_fun::fun::{s, poly};
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 = poly::scalar::generate(threshold, &mut rng);
let my_public_poly = poly::scalar::to_point_poly(&my_secret_poly);

// Party indicies 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_received = BTreeMap::from_iter([
    (my_index, my_public_poly),
    (party_index2, public_poly2),
    (party_index3, public_poly3),
]);
// (optionally) construct my_polys so we don't trust what's in public_poly_received for our index (in case it has been replaced with something malicious)
let my_polys = BTreeMap::from_iter([(my_index, &my_secret_poly)]);
let keygen = frost.new_keygen(public_polys_received, &my_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 and have the same view of the protocol
// (e.g same keygen_id). 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 (if using deterministic nonces).
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. There is little advantage to using deterministic randomness for this except to be able to reproduce the key generation with every party’s long term static secret key. In theory a more compelling answer to reproducing shares is to use simple MPC protocol to produce a share for any party given a threshold number of parties. This protocol isn’t implemented here yet.

This library doesn’t provide a default policy with regards to polynomial generation but here we give an example of a robust way to generate your secret scalar polynomial that should make sense in most applications:

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

let static_secret_key = /* from local storage */
let nonce_gen = nonce::Synthetic::<Sha256, nonce::GlobalRng<rand::rngs::ThreadRng>>::default().tag(b"my-app-name/frost/keygen");
let mut poly_rng = derive_nonce_rng! {
    // use synthetic nonces that add system randomness in
    nonce_gen => nonce_gen,
    // Use your static secret key to add further protectoin
    secret => static_secret_key,
    // session id should be unique for each key generation session
    public => ["frost_key_session_1053"],
    seedable_rng => ChaCha20Rng
};

let threshold = 3;
let my_secret_poly: Vec<Scalar> = poly::scalar::generate(threshold, &mut poly_rng);

Note that if a key generation session fails you should always start a fresh session with a different session id (but you can use the same nonce_gen).

Re-exports§

Structs§

Enums§

Functions§

Type Aliases§