@@ -5,11 +5,14 @@ use cliquenet::overlay::{Data, DataError, NetworkDown, Overlay};
5
5
use cliquenet:: {
6
6
AddressableCommittee , MAX_MESSAGE_SIZE , Network , NetworkError , NetworkMetrics , Role ,
7
7
} ;
8
+ use rayon:: iter:: ParallelIterator ;
9
+ use rayon:: prelude:: * ;
10
+
8
11
use multisig:: { CommitteeId , PublicKey } ;
9
12
use parking_lot:: RwLock ;
10
13
use sailfish:: types:: { Evidence , Round , RoundNumber } ;
11
14
use serde:: { Deserialize , Serialize } ;
12
- use std:: collections:: { BTreeMap , HashMap , HashSet , VecDeque } ;
15
+ use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet , VecDeque } ;
13
16
use std:: result:: Result as StdResult ;
14
17
use std:: sync:: Arc ;
15
18
use timeboost_crypto:: prelude:: { DkgDecKey , LabeledDkgDecKey , Vess , Vss } ;
@@ -22,7 +25,7 @@ use timeboost_types::{
22
25
} ;
23
26
use tokio:: spawn;
24
27
use tokio:: sync:: mpsc:: { Receiver , Sender , channel} ;
25
- use tokio:: task:: JoinHandle ;
28
+ use tokio:: task:: { JoinError , JoinHandle } ;
26
29
use tracing:: { debug, error, info, trace, warn} ;
27
30
28
31
use crate :: config:: DecrypterConfig ;
@@ -65,16 +68,13 @@ enum Command {
65
68
66
69
/// Holds key material for the next committee
67
70
struct NextKey {
68
- next_dkg_key : LabeledDkgDecKey ,
69
- next_dec_key : DecryptionKey ,
71
+ dkg_key : LabeledDkgDecKey ,
72
+ dec_key : DecryptionKey ,
70
73
}
71
74
72
75
impl NextKey {
73
- pub fn new ( next_dkg_key : LabeledDkgDecKey , next_dec_key : DecryptionKey ) -> Self {
74
- Self {
75
- next_dkg_key,
76
- next_dec_key,
77
- }
76
+ pub fn new ( dkg_key : LabeledDkgDecKey , dec_key : DecryptionKey ) -> Self {
77
+ Self { dkg_key, dec_key }
78
78
}
79
79
}
80
80
@@ -99,7 +99,7 @@ enum NextCommittee {
99
99
/// ciphertexts. The shares are exchanged with other decrypters, and once a sufficient number are
100
100
/// collected, shares can be combined ("hatching") to obtain the plaintext.
101
101
///
102
- /// In addition, the `Decrypter` extracts DKG bundles from inclusion lists and combines them to derive
102
+ /// In addition, the `Decrypter` extracts DKG bundles from candidate lists and combines them to derive
103
103
/// the keys used for threshold decryption/combining.
104
104
pub struct Decrypter {
105
105
/// Public key of the node.
@@ -111,7 +111,7 @@ pub struct Decrypter {
111
111
incls : VecDeque < ( RoundNumber , bool ) > ,
112
112
/// Sender end of the worker commands
113
113
worker_tx : Sender < Command > ,
114
- /// Receiver end of the Worker response
114
+ /// Receiver end of the Worker response.
115
115
worker_rx : Receiver < InclusionList > ,
116
116
/// Worker task handle.
117
117
worker : JoinHandle < EndOfPlay > ,
@@ -121,7 +121,7 @@ pub struct Decrypter {
121
121
key_stores : Arc < RwLock < KeyStoreVec < 2 > > > ,
122
122
/// Current committee.
123
123
current : CommitteeId ,
124
- /// Metrics to keep track of decrypter status
124
+ /// Metrics to keep track of decrypter status.
125
125
metrics : Arc < SequencerMetrics > ,
126
126
}
127
127
@@ -452,11 +452,11 @@ struct Worker {
452
452
/// Number of rounds to retain.
453
453
retain : usize ,
454
454
455
- /// State of the node holding generated threshold decryption keys .
455
+ /// Operational state of the node.
456
456
#[ builder( default ) ]
457
457
state : WorkerState ,
458
458
459
- /// The next committee ID and its round number (if any).
459
+ /// The next committee ID, its round number and generated key (if any).
460
460
#[ builder( default ) ]
461
461
next_committee : NextCommittee ,
462
462
@@ -804,7 +804,7 @@ impl Worker {
804
804
if !is_resharing {
805
805
if let Some ( subset) = acc. try_finalize ( ) {
806
806
let dec_key = subset
807
- . extract_key ( & key_store, & self . dkg_sk , None )
807
+ . extract_key ( & key_store. clone ( ) , & self . dkg_sk , None )
808
808
. map_err ( |e| DecrypterError :: Dkg ( e. to_string ( ) ) ) ?;
809
809
self . dec_key . set ( dec_key) ;
810
810
self . state = WorkerState :: Running ;
@@ -890,24 +890,6 @@ impl Worker {
890
890
Ok ( ( ) )
891
891
}
892
892
893
- /// scan through the inclusion list and extract the ciphertexts from encrypted
894
- /// bundle/tx while preserving the order.
895
- ///
896
- /// dev: Option<_> return type indicates potential failure in ciphertext deserialization
897
- fn extract_ciphertexts ( incl : & InclusionList ) -> impl Iterator < Item = Option < Ciphertext > > {
898
- incl. priority_bundles ( )
899
- . iter ( )
900
- . filter ( move |pb| pb. bundle ( ) . is_encrypted ( ) )
901
- . map ( |pb| pb. bundle ( ) . data ( ) )
902
- . chain (
903
- incl. regular_bundles ( )
904
- . iter ( )
905
- . filter ( move |b| b. is_encrypted ( ) )
906
- . map ( |b| b. data ( ) ) ,
907
- )
908
- . map ( |bytes| deserialize :: < Ciphertext > ( bytes) . ok ( ) )
909
- }
910
-
911
893
/// Produce decryption shares for each encrypted bundle inside the inclusion list.
912
894
///
913
895
/// NOTE: when a ciphertext is malformed, we will skip decrypting it (treat as garbage) here.
@@ -918,17 +900,20 @@ impl Worker {
918
900
} ) ?;
919
901
920
902
let round = Round :: new ( incl. round ( ) , self . current ) ;
921
- let dec_shares = Self :: extract_ciphertexts ( incl)
922
- . map ( |optional_ct| {
923
- optional_ct. and_then ( |ct| {
924
- // TODO: (anders) consider using committee_id as part of `aad`.
925
- <DecryptionScheme as ThresholdEncScheme >:: decrypt (
926
- dec_key. privkey ( ) ,
927
- & ct,
928
- & THRES_AAD . to_vec ( ) ,
929
- )
930
- . ok ( ) // decryption failure result in None
931
- } )
903
+ let ciphertexts: Vec < _ > = incl
904
+ . filter_ciphertexts ( )
905
+ . filter_map ( |bytes| deserialize :: < _ , Ciphertext > ( & bytes) . ok ( ) )
906
+ . collect ( ) ;
907
+
908
+ let dec_shares = ciphertexts
909
+ . par_iter ( )
910
+ . map ( |ct| {
911
+ <DecryptionScheme as ThresholdEncScheme >:: decrypt (
912
+ dec_key. privkey ( ) ,
913
+ ct,
914
+ & THRES_AAD . to_vec ( ) ,
915
+ )
916
+ . ok ( )
932
917
} )
933
918
. collect :: < Vec < _ > > ( ) ;
934
919
@@ -1011,7 +996,7 @@ impl Worker {
1011
996
return Ok ( Some ( incl) ) ;
1012
997
}
1013
998
1014
- let ciphertexts = Self :: extract_ciphertexts ( & incl) ;
999
+ let ciphertexts = incl. filter_ciphertexts ( ) . map ( |b| deserialize ( b ) . ok ( ) ) ;
1015
1000
let Some ( dec_shares) = self . dec_shares . get ( & round) else {
1016
1001
return Ok ( None ) ;
1017
1002
} ;
@@ -1042,51 +1027,90 @@ impl Worker {
1042
1027
1043
1028
let mut decrypted: Vec < Option < Plaintext > > = vec ! [ ] ;
1044
1029
1045
- // Now, after immutable borrow is dropped, get mutable access
1046
1030
let Some ( per_ct_opt_dec_shares) = self . dec_shares . get_mut ( & round) else {
1047
1031
return Ok ( None ) ;
1048
1032
} ;
1049
1033
1050
- for ( opt_ct, opt_dec_shares) in ciphertexts. into_iter ( ) . zip ( per_ct_opt_dec_shares) {
1051
- // only Some(_) for valid ciphertext's decryption shares
1052
- let dec_shares = opt_dec_shares
1053
- . iter ( )
1054
- . filter_map ( |s| s. as_ref ( ) )
1055
- . collect :: < Vec < _ > > ( ) ;
1034
+ // define the result of a combine operation
1035
+ #[ derive( Debug ) ]
1036
+ enum CombineResult {
1037
+ Success ( Plaintext ) ,
1038
+ FaultySubset ( BTreeSet < u32 > ) ,
1039
+ Error ( ThresholdEncError ) ,
1040
+ InsufficientShares ,
1041
+ }
1056
1042
1057
- if dec_shares. len ( ) < key_store. committee ( ) . one_honest_threshold ( ) . into ( ) {
1058
- decrypted. push ( None ) ;
1059
- continue ;
1060
- }
1043
+ // process each ciphertext in parallel using spawn_blocking
1044
+ let combine_results = tokio:: task:: spawn_blocking ( {
1045
+ let key_store = key_store. clone ( ) ;
1046
+ let dec_key = dec_key. clone ( ) ;
1047
+ let ciphertexts: Vec < _ > = ciphertexts. collect ( ) ;
1048
+ let mut per_ct_opt_dec_shares = per_ct_opt_dec_shares. clone ( ) ;
1049
+
1050
+ move || {
1051
+ ciphertexts
1052
+ . into_par_iter ( )
1053
+ . zip ( per_ct_opt_dec_shares. par_iter_mut ( ) )
1054
+ . map ( |( maybe_ct, decryption_shares) | {
1055
+ // Collect valid decryption shares
1056
+ let valid_shares: Vec < _ > = decryption_shares
1057
+ . iter ( )
1058
+ . filter_map ( |s| s. as_ref ( ) )
1059
+ . cloned ( )
1060
+ . collect ( ) ;
1061
+
1062
+ // check if we have enough shares
1063
+ let threshold: usize = key_store. committee ( ) . one_honest_threshold ( ) . into ( ) ;
1064
+ if valid_shares. len ( ) < threshold {
1065
+ return CombineResult :: InsufficientShares ;
1066
+ }
1061
1067
1062
- if let Some ( ct) = opt_ct {
1063
- match DecryptionScheme :: combine (
1064
- key_store. committee ( ) ,
1065
- dec_key. combkey ( ) ,
1066
- dec_shares,
1067
- & ct,
1068
- & THRES_AAD . to_vec ( ) ,
1069
- ) {
1070
- Ok ( pt) => decrypted. push ( Some ( pt) ) ,
1071
- // with f+1 decryption shares, which means ciphertext is valid, we just need to
1072
- // remove bad decryption shares and wait for enough shares from honest nodes
1073
- Err ( ThresholdEncError :: FaultySubset ( wrong_indices) ) => {
1074
- opt_dec_shares. retain ( |opt_s| {
1075
- opt_s
1076
- . clone ( )
1077
- . is_none_or ( |s| !wrong_indices. contains ( & s. index ( ) ) )
1078
- } ) ;
1079
- warn ! ( node = %self . label, ?wrong_indices, "combine found faulty subset" ) ;
1080
- // not ready to hatch this ciphertext, thus the containing inclusion list
1081
- return Ok ( None ) ;
1082
- }
1083
- Err ( e) => {
1084
- warn ! ( node = %self . label, error = ?e, "error in combine" ) ;
1085
- return Err ( DecrypterError :: Decryption ( e) ) ;
1086
- }
1068
+ // skip if no ciphertext
1069
+ let Some ( ct) = maybe_ct else {
1070
+ return CombineResult :: InsufficientShares ;
1071
+ } ;
1072
+
1073
+ // attempt to combine shares
1074
+ match DecryptionScheme :: combine (
1075
+ key_store. committee ( ) ,
1076
+ dec_key. combkey ( ) ,
1077
+ valid_shares. iter ( ) . collect ( ) ,
1078
+ & ct,
1079
+ & THRES_AAD . to_vec ( ) ,
1080
+ ) {
1081
+ Ok ( pt) => CombineResult :: Success ( pt) ,
1082
+ Err ( ThresholdEncError :: FaultySubset ( wrong_indices) ) => {
1083
+ CombineResult :: FaultySubset ( wrong_indices. into ( ) )
1084
+ }
1085
+ Err ( e) => CombineResult :: Error ( e) ,
1086
+ }
1087
+ } )
1088
+ . collect :: < Vec < _ > > ( )
1089
+ }
1090
+ } )
1091
+ . await ?;
1092
+
1093
+ for ( result, decryption_shares) in combine_results. into_iter ( ) . zip ( per_ct_opt_dec_shares) {
1094
+ match result {
1095
+ CombineResult :: Success ( pt) => decrypted. push ( Some ( pt) ) ,
1096
+ CombineResult :: FaultySubset ( wrong_indices) => {
1097
+ // Remove faulty decryption shares
1098
+ decryption_shares. retain ( |opt_s| {
1099
+ opt_s
1100
+ . clone ( )
1101
+ . is_none_or ( |s| !wrong_indices. contains ( & s. index ( ) ) )
1102
+ } ) ;
1103
+ warn ! ( node = %self . label, ?wrong_indices, "combine found faulty subset" ) ;
1104
+ // Not ready to hatch this ciphertext
1105
+ return Ok ( None ) ;
1106
+ }
1107
+ CombineResult :: Error ( e) => {
1108
+ warn ! ( node = %self . label, error = ?e, "error in combine" ) ;
1109
+ return Err ( DecrypterError :: Decryption ( e) ) ;
1110
+ }
1111
+ CombineResult :: InsufficientShares => {
1112
+ decrypted. push ( None ) ;
1087
1113
}
1088
- } else {
1089
- decrypted. push ( None ) ;
1090
1114
}
1091
1115
}
1092
1116
@@ -1188,11 +1212,11 @@ impl Worker {
1188
1212
return Err ( DecrypterError :: Dkg ( "accumulator incomplete" . into ( ) ) ) ;
1189
1213
} ;
1190
1214
1191
- let Some ( new_pos ) = new. committee ( ) . get_index ( & self . label ) else {
1215
+ let Some ( new_node_idx ) = new. committee ( ) . get_index ( & self . label ) else {
1192
1216
return Err ( DecrypterError :: Dkg ( "node not found in committee" . into ( ) ) ) ;
1193
1217
} ;
1194
1218
1195
- let new_dkg_sk = DkgDecKey :: from ( self . dkg_sk . clone ( ) ) . label ( new_pos . into ( ) ) ;
1219
+ let new_dkg_sk = DkgDecKey :: from ( self . dkg_sk . clone ( ) ) . label ( new_node_idx . into ( ) ) ;
1196
1220
let new_dec_key = subset
1197
1221
. extract_key ( & new, & new_dkg_sk, Some ( & old) )
1198
1222
. map_err ( |e| DecrypterError :: Dkg ( format ! ( "key extraction failed: {e}" ) ) ) ?;
@@ -1254,13 +1278,9 @@ impl Worker {
1254
1278
. map_err ( |_: NetworkDown | EndOfPlay :: NetworkDown ) ?;
1255
1279
1256
1280
// update keys if also member of next committee
1257
- if let Some ( NextKey {
1258
- next_dkg_key,
1259
- next_dec_key,
1260
- } ) = next_key
1261
- {
1262
- self . dec_key . set ( next_dec_key. clone ( ) ) ;
1263
- self . dkg_sk = next_dkg_key. clone ( ) ;
1281
+ if let Some ( NextKey { dkg_key, dec_key } ) = next_key {
1282
+ self . dec_key . set ( dec_key. clone ( ) ) ;
1283
+ self . dkg_sk = dkg_key. clone ( ) ;
1264
1284
}
1265
1285
self . current = start. committee ( ) ;
1266
1286
self . next_committee = NextCommittee :: Del ( * start) ;
@@ -1353,9 +1373,12 @@ fn serialize<T: Serialize>(d: &T) -> Result<Data> {
1353
1373
Ok ( Data :: try_from ( b. into_inner ( ) ) ?)
1354
1374
}
1355
1375
1356
- fn deserialize < T : for < ' de > serde:: Deserialize < ' de > > ( d : & bytes:: Bytes ) -> Result < T > {
1376
+ fn deserialize < B , T : for < ' de > serde:: Deserialize < ' de > > ( d : B ) -> Result < T >
1377
+ where
1378
+ B : AsRef < [ u8 ] > ,
1379
+ {
1357
1380
bincode:: serde:: decode_from_slice (
1358
- d,
1381
+ d. as_ref ( ) ,
1359
1382
bincode:: config:: standard ( ) . with_limit :: < MAX_MESSAGE_SIZE > ( ) ,
1360
1383
)
1361
1384
. map ( |( msg, _) | msg)
@@ -1390,6 +1413,9 @@ pub enum DecrypterError {
1390
1413
#[ error( "unexpected internal err: {0}" ) ]
1391
1414
Internal ( String ) ,
1392
1415
1416
+ #[ error( "failed to join task: {0}" ) ]
1417
+ JoinErr ( #[ from] JoinError ) ,
1418
+
1393
1419
#[ error( "empty set of valid decryption shares" ) ]
1394
1420
EmptyDecShares ,
1395
1421
0 commit comments