11//! ## FROST multisignature scheme
22//!
3- //! The FROST (Flexible Round-Optimize Schnorr Threshold) multisignature scheme allows you aggregate
3+ //! The FROST (Flexible Round-Optimized Schnorr Threshold) multisignature scheme allows you aggregate
44//! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys
55//! must use a common set of nonces to each produce a signature share. These signature shares are then combined
66//! to form a signature that is valid under the aggregate key.
2323//! let frost = Frost::new(Schnorr::<Sha256, Deterministic<Sha256>>::new(
2424//! Deterministic::<Sha256>::default(),
2525//! ));
26- //! // create a random secret scalar poly with two coefficients,
27- //! // corresponding to FROST multisig with a threshold of two
26+ //! // to create a FROST multisig with a threshold of two, each participant creates
27+ //! // a random secret scalar polynomial with two coefficients.
2828//! let scalar_poly = ScalarPoly::random(2, &mut rand::thread_rng());
2929//! # let scalar_poly2 = ScalarPoly::random(2, &mut rand::thread_rng());
3030//! # let scalar_poly3 = ScalarPoly::random(2, &mut rand::thread_rng());
3131//! // share our public point poly, and recieve the point polys from other participants
3232//! # let point_poly2 = scalar_poly2.to_point_poly();
3333//! # let point_poly3 = scalar_poly3.to_point_poly();
3434//! let point_polys = vec![scalar_poly.to_point_poly(), point_poly2, point_poly3];
35- //! // create secret shares and proof of possession using our secret scalar poly
35+ //! // create secret shares and proofs-of- possession using our secret scalar polynomial
3636//! let keygen = frost.new_keygen(point_polys).unwrap();
3737//! let (shares, pop) = frost.create_shares(&keygen, scalar_poly);
3838//! # let (shares2, pop2) = frost.create_shares(&keygen, scalar_poly2);
3939//! # let (shares3, pop3) = frost.create_shares(&keygen, scalar_poly3);
40- //! // send shares at index i and all proofs-of-possession to each other participant i,
40+ //! // send the shares at index i and all proofs-of-possession to each other participant i,
4141//! // and recieve our shares from each other participant as well as their proofs-of-possession.
4242//! let recieved_shares = vec![shares[0].clone(), shares2[0].clone(), shares3[0].clone()];
4343//! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()];
4444//! let proofs_of_possession = vec![pop, pop2, pop3];
45- //! // finish keygen by calculating our secret share of the joint FROST key
45+ //! // finish keygen by verifying the shares we recieved as well as proofs-of-possession
46+ //! // and calulate our secret share of the joint FROST key
4647//! let (secret_share, frost_key) = frost
4748//! .finish_keygen(
4849//! keygen.clone(),
5960//! # proofs_of_possession.clone(),
6061//! # )
6162//! # .unwrap();
62- //! // for signing we must have a unique session ID to derive nonces.
63- //! // we should include all the information that is publicly available.
63+ //! // for signing we must have a unique session ID to derive nonces such that nonces
64+ //! // are never reused. For gen_nonce we use all information that is publicly available.
6465//! let verification_shares_bytes: Vec<_> = frost_key
6566//! .verification_shares
6667//! .iter()
9293//! // start a sign session with these nonces for this message
9394//! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test"));
9495//! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test"));
95- //! // create a partial signature
96+ //! // create a partial signature using our secret share and secret nonce
9697//! let sig = frost.sign(&frost_key, &session, 0, &secret_share, nonce);
9798//! # let sig3 = frost.sign(&frost_key, &session3, 2, &secret_share3, nonce3);
9899//! // recieve partial signature(s) from other participant(s) and verify
99100//! assert!(frost.verify_signature_share(&frost_key, &session, 2, sig3));
100- //! // combine signature
101+ //! // combine signature shares into a single signature that is valid under the joint key
101102//! let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig, sig3]);
102103//! assert!(frost.schnorr.verify(
103104//! &frost_key.joint_public_key,
104105//! Message::<Public>::plain("test", b"test"),
105106//! &combined_sig
106107//! ));
107108//! ```
108- use crate :: {
109- musig:: { Nonce , NonceKeyPair } ,
110- Message , Schnorr , Signature , Vec ,
111- } ;
109+ pub use crate :: binonce:: { Nonce , NonceKeyPair } ;
110+ use crate :: { Message , Schnorr , Signature , Vec } ;
112111use core:: iter;
113112use rand_core:: { CryptoRng , RngCore } ;
114113use secp256kfun:: {
@@ -123,10 +122,13 @@ use secp256kfun::{
123122use std:: collections:: BTreeMap ;
124123
125124/// The FROST context.
125+ /// H: hash for challenges and creating a keygen_id
126+ /// NG: hash for nonce generation
126127#[ derive( Clone ) ]
127128pub struct Frost < H , NG : AddTag > {
128- /// The instance of the Schnorr signature scheme
129+ /// The instance of the Schnorr signature scheme.
129130 pub schnorr : Schnorr < H , NG > ,
131+ /// The hash used to generate the keygen_id.
130132 keygen_id_hash : H ,
131133}
132134
@@ -172,6 +174,8 @@ impl ScalarPoly {
172174
173175 /// Create a scalar polynomial where the first coefficient is a specified secret and
174176 /// the remaining coefficients are random.
177+ ///
178+ /// This will be used for creating secret polynomials with known & reproducable secrets.
175179 pub fn random_using_secret (
176180 n_coefficients : u32 ,
177181 secret : Scalar ,
@@ -219,7 +223,7 @@ pub struct PointPoly<Z = NonZero>(
219223) ;
220224
221225impl < Z > PointPoly < Z > {
222- /// Evaluate the polynomial at position x.
226+ /// Evaluate the point polynomial at position x.
223227 pub fn eval ( & self , x : u32 ) -> Point < Jacobian , Public , Zero > {
224228 let x = Scalar :: from ( x)
225229 . expect_nonzero ( "must be non-zero" )
@@ -234,13 +238,15 @@ impl<Z> PointPoly<Z> {
234238
235239 /// Combine a vector of point polynomials into a joint polynomial.
236240 fn combine ( mut polys : impl Iterator < Item = Self > ) -> PointPoly < Zero > {
241+ // take the first point polynomial and collect its coefficients
237242 let mut combined_poly = polys
238243 . next ( )
239244 . expect ( "cannot combine empty list of polys" )
240245 . 0
241246 . into_iter ( )
242247 . map ( |p| p. mark :: < ( Jacobian , Zero ) > ( ) )
243248 . collect :: < Vec < _ > > ( ) ;
249+ // add the coefficients of the remaining polys
244250 for poly in polys {
245251 for ( combined_point, point) in combined_poly. iter_mut ( ) . zip ( poly. 0 ) {
246252 * combined_point = g ! ( { * combined_point } + point) ;
@@ -279,16 +285,16 @@ impl KeyGen {
279285 }
280286}
281287
282- /// First round errors
288+ /// First round keygen errors
283289#[ derive( Debug , Clone ) ]
284290pub enum NewKeyGenError {
285291 /// Received polynomial is of differing length.
286292 PolyDifferentLength ( usize ) ,
287293 /// Number of parties is less than the length of polynomials specifying the threshold.
288294 NotEnoughParties ,
289- /// Frost key is zero. Should be impossible, or maliciously chosen.
295+ /// Frost key is zero. This should be impossible, likely has been maliciously chosen.
290296 ZeroFrostKey ,
291- /// Verification share is zero. Should be impossible, or maliciously chosen.
297+ /// Verification share is zero. This should be impossible, likely has been maliciously chosen.
292298 ZeroVerificationShare ,
293299}
294300
@@ -298,8 +304,8 @@ impl core::fmt::Display for NewKeyGenError {
298304 match self {
299305 PolyDifferentLength ( i) => write ! ( f, "polynomial commitment from party at index {} was a different length" , i) ,
300306 NotEnoughParties => write ! ( f, "the number of parties was less than the threshold" ) ,
301- ZeroFrostKey => write ! ( f, "The joint FROST key was zero. This means one of the parties was possibly malicious and you are not protecting against this properly " ) ,
302- ZeroVerificationShare => write ! ( f, "One of the verification shares was malicious so we must abort the protocol " ) ,
307+ ZeroFrostKey => write ! ( f, "The joint FROST key was zero. This should be impossible, one party is acting maliciously. " ) ,
308+ ZeroVerificationShare => write ! ( f, "Zero verification share. This should be impossible, one party is acting maliciously. " ) ,
303309 }
304310 }
305311}
@@ -320,10 +326,15 @@ impl core::fmt::Display for FinishKeyGenError {
320326 fn fmt ( & self , f : & mut core:: fmt:: Formatter ) -> core:: fmt:: Result {
321327 use FinishKeyGenError :: * ;
322328 match self {
323- InvalidShare ( i) => write ! ( f, "the share provided by party at index {} was invalid" , i) ,
329+ InvalidShare ( i) => write ! (
330+ f,
331+ "the secret share at index {} does not match the expected evaluation \
332+ of their point polynomial at our index. Check that the order and our index is correct",
333+ i
334+ ) ,
324335 & InvalidProofOfPossession ( i) => write ! (
325336 f,
326- "the proof of possession provided by party at index {} was invalid" ,
337+ "the proof of possession provided by party at index {} was invalid, check ordering. " ,
327338 i
328339 ) ,
329340 }
@@ -341,15 +352,15 @@ impl std::error::Error for FinishKeyGenError {}
341352 serde( crate = "serde_crate" )
342353) ]
343354pub struct FrostKey {
344- /// The joint public key of the FROST multisignature
355+ /// The joint public key of the FROST multisignature.
345356 pub joint_public_key : Point < EvenY > ,
346- /// Everyone else's point poly evaluated at your index, used in partial signature validation.
357+ /// Everyone else's point polynomial evaluated at your index, used in partial signature validation.
347358 pub verification_shares : Vec < Point > ,
348359 /// Number of partial signatures required to create a combined signature under this key.
349360 pub threshold : u32 ,
350361 /// Taproot tweak applied to this FROST key, tracks the aggregate tweak.
351362 tweak : Scalar < Public , Zero > ,
352- /// Whether the secrets need negation in order to sign for the X-Only key
363+ /// Whether the secrets need negation in order to sign for the X-Only key.
353364 needs_negation : bool ,
354365}
355366
@@ -369,26 +380,25 @@ impl FrostKey {
369380 ///
370381 /// This is how you embed a taproot commitment into a key.
371382 ///
372- /// Also updates whether the FROST key needs negation.
373- /// XOR of existing FROST key needs_negation and new tweaked key needs_negation.
374- /// If both need negation, they will cancel out.
375- ///
376- /// Public key
377- /// X = (b*x) * G
378- /// where b = 1 or -1
379- /// For a tweak t: X' = X + t * G.
380- /// If X' needs negation then we need secret
381- /// -(b*x + t) = -b*x - t
382- /// So new b = -b and t = -t.
383- /// If X' doesn't need negation, leave b as is.
384- /// i.e. previous needs_negation XOR new needs_negation.
385- ///
386383 /// ## Return value
387384 ///
388385 /// Returns a new FrostKey with the same parties but a different aggregated public key.
389386 /// In the erroneous case that the tweak is exactly equal to the negation of the aggregate
390387 /// secret key it returns `None`.
391388 pub fn tweak ( & mut self , tweak : Scalar < impl Secrecy , impl ZeroChoice > ) -> Option < Self > {
389+ // Also updates whether the FROST key needs negation.
390+ // XOR of existing FROST key needs_negation and new tweaked key needs_negation.
391+ // If both need negation, they will cancel out.
392+ //
393+ // Public key
394+ // X = (b*x) * G
395+ // where b = 1 or -1
396+ // For a tweak t: X' = X + t * G.
397+ // If X' needs negation then we need secret
398+ // -(b*x + t) = -b*x - t
399+ // So new b = -b and t = -t.
400+ // If X' doesn't need negation, leave b as is.
401+ // i.e. previous needs_negation XOR new needs_negation.
392402 let new_tweak = s ! ( 0 + tweak) . mark :: < Public > ( ) ;
393403 let ( joint_public_key, tweaked_needs_negation) = g ! ( self . joint_public_key + new_tweak * G )
394404 . mark :: < NonZero > ( ) ?
@@ -426,9 +436,8 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag + NonceGen> Frost<H, NG> {
426436 /// Secret shares are created for every other participant by evaluating our secret polynomial
427437 /// at their participant index. f(i) for 1<=i<=n.
428438 ///
429- /// Each secret share f(i) needs to be securely communicated to participant i.
430- ///
431- /// Also creates a proof of possession for the first coefficient of our secret scalar polynomial.
439+ /// Each secret share f(i) needs to be securely communicated to participant i. Additionally
440+ /// we share a proof of possession for the first coefficient in our secret scalar polynomial.
432441 ///
433442 /// ## Return value
434443 ///
@@ -454,11 +463,11 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag + NonceGen> Frost<H, NG> {
454463}
455464
456465impl < H : Digest < OutputSize = U32 > + Clone , NG : AddTag > Frost < H , NG > {
457- /// Verify a proof of possession against a participant's point polynomial
466+ /// Verify a proof of possession against a participant's committed point polynomial
458467 ///
459468 /// ## Return value
460469 ///
461- /// Returns `bool` true if the proof of possession matches this point poly,
470+ /// Returns `bool` true if the proof of possession matches the point polynomial
462471 fn verify_pop ( & self , KeyGen : & KeyGen , point_poly : & PointPoly , pop : Signature ) -> bool {
463472 let ( even_poly_point, _) = point_poly. 0 [ 0 ] . into_point_with_even_y ( ) ;
464473
@@ -473,13 +482,13 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag> Frost<H, NG> {
473482impl < H : Digest < OutputSize = U32 > + Clone , NG : AddTag > Frost < H , NG > {
474483 /// Collect all the public polynomials into a KeyGen session with a FrostKey.
475484 ///
476- /// Takes a vector of point polynomials with your polynomial at index 0.
485+ /// Takes a vector of point polynomials to use for this FrostKey
477486 ///
478487 /// Also prepares a vector of verification shares for later.
479488 ///
480489 /// ## Return value
481490 ///
482- /// Returns a KeyGen
491+ /// Returns a KeyGen containing a FrostKey
483492 pub fn new_keygen ( & self , point_polys : Vec < PointPoly > ) -> Result < KeyGen , NewKeyGenError > {
484493 let len_first_poly = point_polys[ 0 ] . poly_len ( ) ;
485494 {
@@ -578,16 +587,15 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag> Frost<H, NG> {
578587 }
579588
580589 let total_secret_share = total_secret_share. expect_nonzero (
581- "since verification shares are non-zero, corresponding secret shares cannot be zero" ,
590+ "since verification shares are non-zero, the total secret share cannot be zero" ,
582591 ) ;
583592
584593 Ok ( ( total_secret_share, KeyGen . frost_key ) )
585594 }
586595}
587596
588597/// Calculate the lagrange coefficient for participant with index x_j and other signers indexes x_ms
589- pub fn lagrange_lambda ( x_j : u32 , x_ms : & [ u32 ] ) -> Scalar {
590- // TODO change to a single inverse https://people.maths.ox.ac.uk/trefethen/barycentric.pdf (?)
598+ fn lagrange_lambda ( x_j : u32 , x_ms : & [ u32 ] ) -> Scalar {
591599 let x_j = Scalar :: from ( x_j) . expect_nonzero ( "target xcoord can not be zero" ) ;
592600 x_ms. iter ( )
593601 . map ( |x_m| Scalar :: from ( * x_m) . expect_nonzero ( "index can not be zero" ) )
@@ -627,10 +635,8 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen + AddTag> Frost<H, NG> {
627635 ) -> SignSession {
628636 let mut nonce_map: BTreeMap < _ , _ > =
629637 nonces. into_iter ( ) . map ( |( i, nonce) | ( i, nonce) ) . collect ( ) ;
630- // assert_eq!(nonces.len(), nonce_map.len());
631- assert ! ( frost_key. threshold <= nonce_map. len( ) as u32 ) ;
632638
633- let agg_nonces_R1_R2 : Vec < Point > = nonce_map
639+ let agg_nonce_points : [ Point ; 2 ] = nonce_map
634640 . iter ( )
635641 . fold ( [ Point :: zero ( ) . mark :: < Jacobian > ( ) ; 2 ] , |acc, ( _, nonce) | {
636642 [
@@ -645,9 +651,10 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen + AddTag> Frost<H, NG> {
645651 . mark :: < NonZero > ( )
646652 . expect ( "aggregate nonce should be non-zero" )
647653 } )
648- . collect ( ) ;
654+ . collect :: < Vec < _ > > ( )
655+ . try_into ( )
656+ . expect ( "there are only R1 and R2, collecting cant fail" ) ;
649657
650- let agg_nonce_points: [ Point ; 2 ] = [ agg_nonces_R1_R2[ 0 ] , agg_nonces_R1_R2[ 1 ] ] ;
651658 let binding_coeff = Scalar :: from_hash (
652659 self . schnorr
653660 . challenge_hash ( )
@@ -837,7 +844,7 @@ mod test {
837844 dbg!( threshold, n_parties) ;
838845 assert!( threshold <= n_parties) ;
839846
840- // create some scalar poly for each party
847+ // create some scalar polynomial for each party
841848 let mut scalar_polys = vec![ ] ;
842849 for i in 1 ..=n_parties {
843850 let scalar_poly = ( 1 ..=threshold) . map( |j| Scalar :: from_non_zero_u32( NonZeroU32 :: new( i* j) . expect( "starts from 1" ) ) ) . collect( ) ;
0 commit comments