1
1
//! ## FROST multisignature scheme
2
2
//!
3
3
//! The FROST (Flexible Round-Optimized Schnorr Threshold) multisignature scheme allows you aggregate
4
- //! multiple public keys into a single public key. To sign a message under this public key, a threshold t-of-n secret keys
4
+ //! multiple public keys into a single joint 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
- //! to form a signature that is valid under the aggregate key.
6
+ //! to form a signature that is valid under the joint public key.
7
7
//!
8
8
//! This implementation has **not yet** been made compatible with other existing FROST implementations
9
9
//! (notably [secp256k1-zkp]).
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 verifying the shares we recieved as well as proofs-of-possession
46
- //! // and calulate 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.
47
47
//! let (secret_share, frost_key) = frost
48
48
//! .finish_keygen_to_xonly(
49
49
//! keygen.clone(),
75
75
//! ]
76
76
//! .concat();
77
77
//! # let sid3 = [
78
- //! # frost_key.joint_public_key.to_bytes().as_slice(),
79
78
//! # verification_shares_bytes.concat().as_slice(),
80
79
//! # b"frost-very-unique-id".as_slice(),
81
80
//! # b"2".as_slice(),
89
88
//! // recieve public nonces from other participants with their index
90
89
//! let nonces = vec![(0, nonce.public()), (2, recieved_nonce3)];
91
90
//! # let nonces3 = vec![(0, nonce.public()), (2, recieved_nonce3)];
92
- //! // start a sign session with these nonces for this message
91
+ //! // start a sign session with these nonces for a message
93
92
//! let session = frost.start_sign_session(&frost_key, nonces, Message::plain("test", b"test"));
94
93
//! # let session3 = frost.start_sign_session(&frost_key, nonces3, Message::plain("test", b"test"));
95
94
//! // create a partial signature using our secret share and secret nonce
@@ -423,7 +422,7 @@ impl FrostKey {
423
422
}
424
423
}
425
424
426
- /// A [`FrostKey`] that has been converted into a [`XOnlyFrostKey`] key.
425
+ /// A [`FrostKey`] that has been converted into an [`XOnlyFrostKey`] key.
427
426
///
428
427
/// This is the BIP340 compatible version of the key which you can put in a segwitv1 output and create BIP340 signatures under.
429
428
/// Tweaks applied to a `XOnlyFrostKey` are XOnly tweaks to the joint public key.
@@ -900,7 +899,11 @@ mod test {
900
899
901
900
proptest ! {
902
901
#[ test]
903
- fn frost_prop_test( ( n_parties, threshold) in ( 3u32 ..8 ) . prop_flat_map( |n| ( Just ( n) , 3u32 ..=n) ) , tweak1 in option:: of( any:: <Scalar <Public , Zero >>( ) ) , tweak2 in option:: of( any:: <Scalar <Public , Zero >>( ) ) ) {
902
+ fn frost_prop_test(
903
+ ( n_parties, threshold) in ( 3u32 ..8 ) . prop_flat_map( |n| ( Just ( n) , 3u32 ..=n) ) ,
904
+ tweak1 in option:: of( any:: <Scalar <Public , Zero >>( ) ) ,
905
+ tweak2 in option:: of( any:: <Scalar <Public , Zero >>( ) )
906
+ ) {
904
907
let frost = Frost :: new( Schnorr :: <Sha256 , Deterministic <Sha256 >>:: new(
905
908
Deterministic :: <Sha256 >:: default ( ) ,
906
909
) ) ;
@@ -910,7 +913,10 @@ mod test {
910
913
// create some scalar polynomial for each party
911
914
let mut scalar_polys = vec![ ] ;
912
915
for i in 1 ..=n_parties {
913
- let scalar_poly = ( 1 ..=threshold) . map( |j| Scalar :: from_non_zero_u32( NonZeroU32 :: new( i* j) . expect( "starts from 1" ) ) ) . collect( ) ;
916
+ let scalar_poly = ( 1 ..=threshold) . map( |j|
917
+ Scalar :: from_non_zero_u32( NonZeroU32 :: new( i* j)
918
+ . expect( "starts from 1" ) ) )
919
+ . collect( ) ;
914
920
scalar_polys. push( ScalarPoly :: new( scalar_poly) ) ;
915
921
}
916
922
let point_polys: Vec <PointPoly > = scalar_polys. iter( ) . map( |sp| sp. to_point_poly( ) ) . collect( ) ;
@@ -930,37 +936,45 @@ mod test {
930
936
for party_index in 0 ..n_parties {
931
937
recieved_shares. push( vec![ ] ) ;
932
938
for share_index in 0 ..n_parties {
933
- recieved_shares[ party_index as usize ] . push( shares_vec[ share_index as usize ] [ party_index as usize ] . clone( ) ) ;
939
+ recieved_shares[ party_index as usize ]
940
+ . push( shares_vec[ share_index as usize ] [ party_index as usize ] . clone( ) ) ;
934
941
}
935
942
}
936
943
937
944
// finish keygen for each party
938
945
let ( secret_shares, frost_keys) : ( Vec <Scalar >, Vec <XOnlyFrostKey >) = ( 0 ..n_parties) . map( |i| {
939
- let ( secret_share, frost_key) = frost. finish_keygen(
946
+ let ( secret_share, mut frost_key) = frost. finish_keygen(
940
947
KeyGen . clone( ) ,
941
948
i,
942
949
recieved_shares[ i as usize ] . clone( ) ,
943
950
proofs_of_possession. clone( ) ,
944
951
)
945
952
. unwrap( ) ;
946
- let mut xonly_frost_key = frost_key. into_xonly_key( ) ;
953
+ // apply some plain tweak
954
+ if let Some ( tweak) = tweak1 {
955
+ frost_key = frost_key. tweak( tweak) . unwrap( ) ;
956
+ }
947
957
948
- // apply some xonly tweaks
949
- for tweak in [ tweak1, tweak2] {
950
- if let Some ( tweak) = tweak {
951
- xonly_frost_key = xonly_frost_key. tweak( tweak) . unwrap( ) ;
952
- }
958
+ let mut xonly_frost_key = frost_key. into_xonly_key( ) ;
959
+ // apply some xonly tweak
960
+ if let Some ( tweak) = tweak2 {
961
+ xonly_frost_key = xonly_frost_key. tweak( tweak) . unwrap( ) ;
953
962
}
954
963
( secret_share, xonly_frost_key)
955
964
} ) . unzip( ) ;
956
965
957
966
// use a boolean mask for which t participants are signers
958
967
let mut signer_mask = vec![ true ; threshold as usize ] ;
959
968
signer_mask. append( & mut vec![ false ; ( n_parties - threshold) as usize ] ) ;
960
- // shuffle the mask for random signers (roughly shuffled and deterministic based on signers_mask_seed)
969
+ // shuffle the mask for random signers
961
970
signer_mask. shuffle( & mut TestRng :: deterministic_rng( RngAlgorithm :: ChaCha ) ) ;
962
971
963
- let signer_indexes: Vec <_> = signer_mask. iter( ) . enumerate( ) . filter( |( _, is_signer) | * * is_signer) . map( |( i, _) | i) . collect( ) ;
972
+ let signer_indexes: Vec <_> = signer_mask
973
+ . iter( )
974
+ . enumerate( )
975
+ . filter( |( _, is_signer) | * * is_signer)
976
+ . map( |( i, _) | i)
977
+ . collect( ) ;
964
978
965
979
let verification_shares_bytes: Vec <_> = frost_keys[ signer_indexes[ 0 ] ]
966
980
. verification_shares
@@ -988,17 +1002,38 @@ mod test {
988
1002
}
989
1003
990
1004
// Create Frost signing session
991
- let signing_session = frost. start_sign_session( & frost_keys[ signer_indexes[ 0 ] ] , recieved_nonces. clone( ) , Message :: plain( "test" , b"test" ) ) ;
1005
+ let signing_session = frost. start_sign_session(
1006
+ & frost_keys[ signer_indexes[ 0 ] ] ,
1007
+ recieved_nonces. clone( ) ,
1008
+ Message :: plain( "test" , b"test" )
1009
+ ) ;
992
1010
993
1011
let mut signatures = vec![ ] ;
994
1012
for i in 0 ..signer_indexes. len( ) {
995
1013
let signer_index = signer_indexes[ i] as usize ;
996
- let session = frost. start_sign_session( & frost_keys[ signer_index] , recieved_nonces. clone( ) , Message :: plain( "test" , b"test" ) ) ;
997
- let sig = frost. sign( & frost_keys[ signer_index] , & session, signer_index as u32 , & secret_shares[ signer_index] , nonces[ i] . clone( ) ) ;
998
- assert!( frost. verify_signature_share( & frost_keys[ signer_index] , & session, signer_index as u32 , sig) ) ;
1014
+ let session = frost. start_sign_session(
1015
+ & frost_keys[ signer_index] ,
1016
+ recieved_nonces. clone( ) ,
1017
+ Message :: plain( "test" , b"test" )
1018
+ ) ;
1019
+ let sig = frost. sign(
1020
+ & frost_keys[ signer_index] ,
1021
+ & session, signer_index as u32 ,
1022
+ & secret_shares[ signer_index] ,
1023
+ nonces[ i] . clone( )
1024
+ ) ;
1025
+ assert!( frost. verify_signature_share(
1026
+ & frost_keys[ signer_index] ,
1027
+ & session,
1028
+ signer_index as u32 ,
1029
+ sig)
1030
+ ) ;
999
1031
signatures. push( sig) ;
1000
1032
}
1001
- let combined_sig = frost. combine_signature_shares( & frost_keys[ signer_indexes[ 0 ] as usize ] , & signing_session, signatures) ;
1033
+ let combined_sig = frost. combine_signature_shares(
1034
+ & frost_keys[ signer_indexes[ 0 ] as usize ] ,
1035
+ & signing_session,
1036
+ signatures) ;
1002
1037
1003
1038
assert!( frost. schnorr. verify(
1004
1039
& frost_keys[ signer_indexes[ 0 ] as usize ] . joint_public_key,
@@ -1030,23 +1065,23 @@ mod test {
1030
1065
let ( shares3, pop3) = frost. create_shares ( & KeyGen , sp3) ;
1031
1066
let proofs_of_possession = vec ! [ pop1, pop2, pop3] ;
1032
1067
1033
- let ( secret_share1, frost_key) = frost
1068
+ let ( secret_share1, mut frost_key) = frost
1034
1069
. finish_keygen (
1035
1070
KeyGen . clone ( ) ,
1036
1071
0 ,
1037
1072
vec ! [ shares1[ 0 ] . clone( ) , shares2[ 0 ] . clone( ) , shares3[ 0 ] . clone( ) ] ,
1038
1073
proofs_of_possession. clone ( ) ,
1039
1074
)
1040
1075
. unwrap ( ) ;
1041
- let ( _secret_share2, frost_key2) = frost
1076
+ let ( _secret_share2, mut frost_key2) = frost
1042
1077
. finish_keygen (
1043
1078
KeyGen . clone ( ) ,
1044
1079
1 ,
1045
1080
vec ! [ shares1[ 1 ] . clone( ) , shares2[ 1 ] . clone( ) , shares3[ 1 ] . clone( ) ] ,
1046
1081
proofs_of_possession. clone ( ) ,
1047
1082
)
1048
1083
. unwrap ( ) ;
1049
- let ( secret_share3, frost_key3) = frost
1084
+ let ( secret_share3, mut frost_key3) = frost
1050
1085
. finish_keygen (
1051
1086
KeyGen . clone ( ) ,
1052
1087
2 ,
@@ -1058,14 +1093,7 @@ mod test {
1058
1093
assert_eq ! ( frost_key, frost_key2) ;
1059
1094
assert_eq ! ( frost_key, frost_key3) ;
1060
1095
1061
- // Currently we are not doing any non-xonly tweak tests
1062
- let mut xonly_frost_key = frost_key. into_xonly_key ( ) ;
1063
- let mut xonly_frost_key2 = frost_key2. into_xonly_key ( ) ;
1064
- let mut xonly_frost_key3 = frost_key3. into_xonly_key ( ) ;
1065
-
1066
- assert_eq ! ( xonly_frost_key, xonly_frost_key2) ;
1067
- assert_eq ! ( xonly_frost_key, xonly_frost_key3) ;
1068
-
1096
+ // plain tweak
1069
1097
let use_tweak = true ;
1070
1098
let tweak = if use_tweak {
1071
1099
Scalar :: from_bytes ( [
@@ -1078,10 +1106,18 @@ mod test {
1078
1106
Scalar :: zero ( )
1079
1107
} ;
1080
1108
1081
- xonly_frost_key = xonly_frost_key. tweak ( tweak. clone ( ) ) . expect ( "tweak worked" ) ;
1082
- xonly_frost_key2 = xonly_frost_key2. tweak ( tweak. clone ( ) ) . expect ( "tweak worked" ) ;
1083
- xonly_frost_key3 = xonly_frost_key3. tweak ( tweak) . expect ( "tweak worked" ) ;
1109
+ frost_key = frost_key. tweak ( tweak. clone ( ) ) . expect ( "tweak worked" ) ;
1110
+ frost_key2 = frost_key2. tweak ( tweak. clone ( ) ) . expect ( "tweak worked" ) ;
1111
+ frost_key3 = frost_key3. tweak ( tweak) . expect ( "tweak worked" ) ;
1112
+
1113
+ let mut xonly_frost_key = frost_key. into_xonly_key ( ) ;
1114
+ let mut xonly_frost_key2 = frost_key2. into_xonly_key ( ) ;
1115
+ let mut xonly_frost_key3 = frost_key3. into_xonly_key ( ) ;
1116
+
1117
+ assert_eq ! ( xonly_frost_key, xonly_frost_key2) ;
1118
+ assert_eq ! ( xonly_frost_key, xonly_frost_key3) ;
1084
1119
1120
+ // xonly tweak
1085
1121
let tweak = if use_tweak {
1086
1122
Scalar :: from_bytes ( [
1087
1123
0xE8 , 0xF7 , 0x92 , 0xFF , 0x92 , 0x25 , 0xA2 , 0xAF , 0x01 , 0x02 , 0xAF , 0xFF , 0x4A , 0x9A ,
0 commit comments