1
1
//! ## FROST multisignature scheme
2
2
//!
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
4
4
//! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys
5
5
//! must use a common set of nonces to each produce a signature share. These signature shares are then combined
6
6
//! to form a signature that is valid under the aggregate key.
23
23
//! let frost = Frost::new(Schnorr::<Sha256, Deterministic<Sha256>>::new(
24
24
//! Deterministic::<Sha256>::default(),
25
25
//! ));
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.
28
28
//! let scalar_poly = ScalarPoly::random(2, &mut rand::thread_rng());
29
29
//! # let scalar_poly2 = ScalarPoly::random(2, &mut rand::thread_rng());
30
30
//! # let scalar_poly3 = ScalarPoly::random(2, &mut rand::thread_rng());
31
31
//! // share our public point poly, and recieve the point polys from other participants
32
32
//! # let point_poly2 = scalar_poly2.to_point_poly();
33
33
//! # let point_poly3 = scalar_poly3.to_point_poly();
34
34
//! 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
36
36
//! let keygen = frost.new_keygen(point_polys).unwrap();
37
37
//! let (shares, pop) = frost.create_shares(&keygen, scalar_poly);
38
38
//! # let (shares2, pop2) = frost.create_shares(&keygen, scalar_poly2);
39
39
//! # 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,
41
41
//! // and recieve our shares from each other participant as well as their proofs-of-possession.
42
42
//! let recieved_shares = vec![shares[0].clone(), shares2[0].clone(), shares3[0].clone()];
43
43
//! # let recieved_shares3 = vec![shares[2].clone(), shares2[2].clone(), shares3[2].clone()];
44
44
//! 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
46
47
//! let (secret_share, frost_key) = frost
47
48
//! .finish_keygen(
48
49
//! keygen.clone(),
59
60
//! # proofs_of_possession.clone(),
60
61
//! # )
61
62
//! # .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.
64
65
//! let verification_shares_bytes: Vec<_> = frost_key
65
66
//! .verification_shares
66
67
//! .iter()
92
93
//! // start a sign session with these nonces for this message
93
94
//! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test"));
94
95
//! # 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
96
97
//! let sig = frost.sign(&frost_key, &session, 0, &secret_share, nonce);
97
98
//! # let sig3 = frost.sign(&frost_key, &session3, 2, &secret_share3, nonce3);
98
99
//! // recieve partial signature(s) from other participant(s) and verify
99
100
//! 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
101
102
//! let combined_sig = frost.combine_signature_shares(&frost_key, &session, vec![sig, sig3]);
102
103
//! assert!(frost.schnorr.verify(
103
104
//! &frost_key.joint_public_key,
104
105
//! Message::<Public>::plain("test", b"test"),
105
106
//! &combined_sig
106
107
//! ));
107
108
//! ```
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 } ;
112
111
use core:: iter;
113
112
use rand_core:: { CryptoRng , RngCore } ;
114
113
use secp256kfun:: {
@@ -123,10 +122,13 @@ use secp256kfun::{
123
122
use std:: collections:: BTreeMap ;
124
123
125
124
/// The FROST context.
125
+ /// H: hash for challenges and creating a keygen_id
126
+ /// NG: hash for nonce generation
126
127
#[ derive( Clone ) ]
127
128
pub struct Frost < H , NG : AddTag > {
128
- /// The instance of the Schnorr signature scheme
129
+ /// The instance of the Schnorr signature scheme.
129
130
pub schnorr : Schnorr < H , NG > ,
131
+ /// The hash used to generate the keygen_id.
130
132
keygen_id_hash : H ,
131
133
}
132
134
@@ -172,6 +174,8 @@ impl ScalarPoly {
172
174
173
175
/// Create a scalar polynomial where the first coefficient is a specified secret and
174
176
/// the remaining coefficients are random.
177
+ ///
178
+ /// This will be used for creating secret polynomials with known & reproducable secrets.
175
179
pub fn random_using_secret (
176
180
n_coefficients : u32 ,
177
181
secret : Scalar ,
@@ -219,7 +223,7 @@ pub struct PointPoly<Z = NonZero>(
219
223
) ;
220
224
221
225
impl < Z > PointPoly < Z > {
222
- /// Evaluate the polynomial at position x.
226
+ /// Evaluate the point polynomial at position x.
223
227
pub fn eval ( & self , x : u32 ) -> Point < Jacobian , Public , Zero > {
224
228
let x = Scalar :: from ( x)
225
229
. expect_nonzero ( "must be non-zero" )
@@ -234,13 +238,15 @@ impl<Z> PointPoly<Z> {
234
238
235
239
/// Combine a vector of point polynomials into a joint polynomial.
236
240
fn combine ( mut polys : impl Iterator < Item = Self > ) -> PointPoly < Zero > {
241
+ // take the first point polynomial and collect its coefficients
237
242
let mut combined_poly = polys
238
243
. next ( )
239
244
. expect ( "cannot combine empty list of polys" )
240
245
. 0
241
246
. into_iter ( )
242
247
. map ( |p| p. mark :: < ( Jacobian , Zero ) > ( ) )
243
248
. collect :: < Vec < _ > > ( ) ;
249
+ // add the coefficients of the remaining polys
244
250
for poly in polys {
245
251
for ( combined_point, point) in combined_poly. iter_mut ( ) . zip ( poly. 0 ) {
246
252
* combined_point = g ! ( { * combined_point } + point) ;
@@ -279,16 +285,16 @@ impl KeyGen {
279
285
}
280
286
}
281
287
282
- /// First round errors
288
+ /// First round keygen errors
283
289
#[ derive( Debug , Clone ) ]
284
290
pub enum NewKeyGenError {
285
291
/// Received polynomial is of differing length.
286
292
PolyDifferentLength ( usize ) ,
287
293
/// Number of parties is less than the length of polynomials specifying the threshold.
288
294
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.
290
296
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.
292
298
ZeroVerificationShare ,
293
299
}
294
300
@@ -298,8 +304,8 @@ impl core::fmt::Display for NewKeyGenError {
298
304
match self {
299
305
PolyDifferentLength ( i) => write ! ( f, "polynomial commitment from party at index {} was a different length" , i) ,
300
306
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. " ) ,
303
309
}
304
310
}
305
311
}
@@ -320,10 +326,15 @@ impl core::fmt::Display for FinishKeyGenError {
320
326
fn fmt ( & self , f : & mut core:: fmt:: Formatter ) -> core:: fmt:: Result {
321
327
use FinishKeyGenError :: * ;
322
328
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
+ ) ,
324
335
& InvalidProofOfPossession ( i) => write ! (
325
336
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. " ,
327
338
i
328
339
) ,
329
340
}
@@ -341,15 +352,15 @@ impl std::error::Error for FinishKeyGenError {}
341
352
serde( crate = "serde_crate" )
342
353
) ]
343
354
pub struct FrostKey {
344
- /// The joint public key of the FROST multisignature
355
+ /// The joint public key of the FROST multisignature.
345
356
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.
347
358
pub verification_shares : Vec < Point > ,
348
359
/// Number of partial signatures required to create a combined signature under this key.
349
360
pub threshold : u32 ,
350
361
/// Taproot tweak applied to this FROST key, tracks the aggregate tweak.
351
362
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.
353
364
needs_negation : bool ,
354
365
}
355
366
@@ -369,26 +380,25 @@ impl FrostKey {
369
380
///
370
381
/// This is how you embed a taproot commitment into a key.
371
382
///
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
- ///
386
383
/// ## Return value
387
384
///
388
385
/// Returns a new FrostKey with the same parties but a different aggregated public key.
389
386
/// In the erroneous case that the tweak is exactly equal to the negation of the aggregate
390
387
/// secret key it returns `None`.
391
388
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.
392
402
let new_tweak = s ! ( 0 + tweak) . mark :: < Public > ( ) ;
393
403
let ( joint_public_key, tweaked_needs_negation) = g ! ( self . joint_public_key + new_tweak * G )
394
404
. mark :: < NonZero > ( ) ?
@@ -426,9 +436,8 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag + NonceGen> Frost<H, NG> {
426
436
/// Secret shares are created for every other participant by evaluating our secret polynomial
427
437
/// at their participant index. f(i) for 1<=i<=n.
428
438
///
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.
432
441
///
433
442
/// ## Return value
434
443
///
@@ -454,11 +463,11 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag + NonceGen> Frost<H, NG> {
454
463
}
455
464
456
465
impl < 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
458
467
///
459
468
/// ## Return value
460
469
///
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
462
471
fn verify_pop ( & self , KeyGen : & KeyGen , point_poly : & PointPoly , pop : Signature ) -> bool {
463
472
let ( even_poly_point, _) = point_poly. 0 [ 0 ] . into_point_with_even_y ( ) ;
464
473
@@ -473,13 +482,13 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag> Frost<H, NG> {
473
482
impl < H : Digest < OutputSize = U32 > + Clone , NG : AddTag > Frost < H , NG > {
474
483
/// Collect all the public polynomials into a KeyGen session with a FrostKey.
475
484
///
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
477
486
///
478
487
/// Also prepares a vector of verification shares for later.
479
488
///
480
489
/// ## Return value
481
490
///
482
- /// Returns a KeyGen
491
+ /// Returns a KeyGen containing a FrostKey
483
492
pub fn new_keygen ( & self , point_polys : Vec < PointPoly > ) -> Result < KeyGen , NewKeyGenError > {
484
493
let len_first_poly = point_polys[ 0 ] . poly_len ( ) ;
485
494
{
@@ -578,16 +587,15 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: AddTag> Frost<H, NG> {
578
587
}
579
588
580
589
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" ,
582
591
) ;
583
592
584
593
Ok ( ( total_secret_share, KeyGen . frost_key ) )
585
594
}
586
595
}
587
596
588
597
/// 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 {
591
599
let x_j = Scalar :: from ( x_j) . expect_nonzero ( "target xcoord can not be zero" ) ;
592
600
x_ms. iter ( )
593
601
. 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> {
627
635
) -> SignSession {
628
636
let mut nonce_map: BTreeMap < _ , _ > =
629
637
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 ) ;
632
638
633
- let agg_nonces_R1_R2 : Vec < Point > = nonce_map
639
+ let agg_nonce_points : [ Point ; 2 ] = nonce_map
634
640
. iter ( )
635
641
. fold ( [ Point :: zero ( ) . mark :: < Jacobian > ( ) ; 2 ] , |acc, ( _, nonce) | {
636
642
[
@@ -645,9 +651,10 @@ impl<H: Digest<OutputSize = U32> + Clone, NG: NonceGen + AddTag> Frost<H, NG> {
645
651
. mark :: < NonZero > ( )
646
652
. expect ( "aggregate nonce should be non-zero" )
647
653
} )
648
- . collect ( ) ;
654
+ . collect :: < Vec < _ > > ( )
655
+ . try_into ( )
656
+ . expect ( "there are only R1 and R2, collecting cant fail" ) ;
649
657
650
- let agg_nonce_points: [ Point ; 2 ] = [ agg_nonces_R1_R2[ 0 ] , agg_nonces_R1_R2[ 1 ] ] ;
651
658
let binding_coeff = Scalar :: from_hash (
652
659
self . schnorr
653
660
. challenge_hash ( )
@@ -837,7 +844,7 @@ mod test {
837
844
dbg!( threshold, n_parties) ;
838
845
assert!( threshold <= n_parties) ;
839
846
840
- // create some scalar poly for each party
847
+ // create some scalar polynomial for each party
841
848
let mut scalar_polys = vec![ ] ;
842
849
for i in 1 ..=n_parties {
843
850
let scalar_poly = ( 1 ..=threshold) . map( |j| Scalar :: from_non_zero_u32( NonZeroU32 :: new( i* j) . expect( "starts from 1" ) ) ) . collect( ) ;
0 commit comments