@@ -6,6 +6,7 @@ use crate::types::ShareIndex;
66use fastcrypto:: error:: { FastCryptoError , FastCryptoResult } ;
77use fastcrypto:: groups:: GroupElement ;
88use fastcrypto:: hash:: { Blake2b256 , Digest , HashFunction } ;
9+ use itertools:: Itertools ;
910use serde:: { Deserialize , Serialize } ;
1011use tracing:: debug;
1112
@@ -157,6 +158,28 @@ impl<G: GroupElement + Serialize> Nodes<G> {
157158 hash. finalize ( )
158159 }
159160
161+ /// Given an iterator over a set of items, one per share index, this function groups them into
162+ /// a vector of vectors, one per node, according to the share ids of the nodes.
163+ /// Returns error if the number of items does not match the total weight.
164+ pub fn collect_to_nodes < T > (
165+ & self ,
166+ items : impl ExactSizeIterator < Item = T > ,
167+ ) -> FastCryptoResult < Vec < Vec < T > > > {
168+ if items. len ( ) != self . total_weight as usize {
169+ return Err ( FastCryptoError :: InvalidInput ) ;
170+ }
171+ let mut items = items;
172+ Ok ( self
173+ . node_ids_iter ( )
174+ . map ( |id| {
175+ items
176+ . by_ref ( )
177+ . take ( self . weight_of ( id) . unwrap ( ) as usize )
178+ . collect_vec ( )
179+ } )
180+ . collect_vec ( ) )
181+ }
182+
160183 /// Create a new set of nodes. Nodes must have consecutive ids starting from 0.
161184 /// Reduces weights up to an allowed delta in the original total weight.
162185 /// Finds the largest d such that:
@@ -292,132 +315,9 @@ impl<G: GroupElement + Serialize> Nodes<G> {
292315 new_f,
293316 ) )
294317 }
295-
296- /// Create a new set of nodes. Nodes must have consecutive ids starting from 0.
297- /// Like [`Nodes::new_reduced_with_f`], but in addition to the integer divisor
298- /// sweep, fine-sweeps the unit interval above the first feasible integer at
299- /// granularity 0.01 to find a (possibly strictly larger) fractional divisor d.
300- /// Finds the largest d such that:
301- /// - The new threshold is ceil(t / d)
302- /// - The new threshold for Byzantine parties is ceil(f / d)
303- /// - The new weights are all divided by d (floor division)
304- /// - The precision loss, counted as the sum of remainders Σ_i (w_i mod d) plus the
305- /// ceiling overheads (-t) mod d and (-f) mod d, is at most the allowed delta
306- ///
307- /// Operates on a 0.01-multiple grid for d (represented internally in u64
308- /// arithmetic via d_x100 = 100*d). The Stage-1 criterion is the natural
309- /// fractional-d extension of the one in `new_reduced_with_f`, with
310- /// (w mod d) := w - floor(w/d) * d ∈ [0, d) for any real d > 0; the
311- /// Safety, Liveness, and Byzantine-removal proofs go through verbatim.
312- /// Since prop_reduce considers a strict superset of `new_reduced_with_f`'s
313- /// candidates, its reduced total weight (and ceilings t', f') are always
314- /// ≤ those of `new_reduced_with_f`.
315- ///
316- /// In practice, allowed delta will be the extra liveness we would assume above 2f+1.
317- ///
318- /// total_weight_lower_bound allows limiting the level of reduction (e.g., in benchmarks). To
319- /// get the best results, set it to 1.
320- pub fn prop_reduce (
321- nodes_vec : Vec < Node < G > > ,
322- t : u16 ,
323- f : u16 ,
324- allowed_delta : u16 ,
325- total_weight_lower_bound : u16 ,
326- ) -> FastCryptoResult < ( Self , u16 , u16 ) > {
327- let n = Self :: new ( nodes_vec) ?; // checks the input, etc
328- assert ! ( total_weight_lower_bound <= n. total_weight && total_weight_lower_bound > 0 ) ;
329- let allowed_delta_x100 = ( allowed_delta as u64 ) * 100 ;
330- let mut max_d_x100: u32 = 100 ; // d = 1, no reduction
331- // Sweep d downward from 40 to 2. Going down, the first feasible integer
332- // is the largest feasible integer divisor (the criterion is non-monotone
333- // in d, so an upward sweep cannot break early). After locking onto the
334- // first feasible integer d, fine-sweep (d, d+1) at 0.01 in decreasing
335- // order to find the largest feasible fractional divisor in that interval.
336- ' outer: for d in ( 2u16 ..=40 ) . rev ( ) {
337- // Continue if we are below the lower bound. (Going down, W' grows as
338- // d shrinks, so once W' clears the lower bound it stays cleared.)
339- // U16 is safe here since total_weight is u16.
340- let new_total_weight = n. nodes . iter ( ) . map ( |n| n. weight / d) . sum :: < u16 > ( ) ;
341- if new_total_weight < total_weight_lower_bound {
342- continue ;
343- }
344- // Compute the precision loss at integer d.
345- // U16 is safe here since total_weight is u16.
346- let delta =
347- n. nodes . iter ( ) . map ( |n| n. weight % d) . sum :: < u16 > ( ) + neg_mod ( t, d) + neg_mod ( f, d) ;
348- if delta > allowed_delta {
349- continue ;
350- }
351- // Integer d is feasible; lock it as the fallback.
352- max_d_x100 = ( d as u32 ) * 100 ;
353- // Fine-sweep (d, d+1) at 0.01, largest-first. The first hit is the
354- // largest feasible 0.01-multiple in that interval.
355- for k in ( 1 ..100u32 ) . rev ( ) {
356- let d_x100 = ( d as u32 ) * 100 + k;
357- let new_w_total = n
358- . nodes
359- . iter ( )
360- . map ( |n| ( ( n. weight as u32 ) * 100 ) / d_x100)
361- . sum :: < u32 > ( ) ;
362- if ( new_w_total as u16 ) < total_weight_lower_bound {
363- continue ;
364- }
365- // Σ_i (w_i mod d) * 100 = W*100 − W' * d_x100 (telescopes by floor).
366- let sum_mod_x100 =
367- ( n. total_weight as u64 ) * 100 - ( new_w_total as u64 ) * ( d_x100 as u64 ) ;
368- let delta_x100 = sum_mod_x100 + neg_mod_x100 ( t, d_x100) + neg_mod_x100 ( f, d_x100) ;
369- if delta_x100 <= allowed_delta_x100 {
370- max_d_x100 = d_x100;
371- break ;
372- }
373- }
374- break ' outer;
375- }
376- debug ! (
377- "Nodes::prop_reduce reducing from {} with max_d_x100 {}, allowed_delta {}, total_weight_lower_bound {}" ,
378- n. total_weight, max_d_x100, allowed_delta, total_weight_lower_bound
379- ) ;
380-
381- let nodes = n
382- . nodes
383- . iter ( )
384- . map ( |n| Node {
385- id : n. id ,
386- pk : n. pk . clone ( ) ,
387- weight : ( ( ( n. weight as u32 ) * 100 ) / max_d_x100) as u16 ,
388- } )
389- . collect :: < Vec < _ > > ( ) ;
390- let accumulated_weights = Self :: get_accumulated_weights ( & nodes) ;
391- let nodes_with_nonzero_weight = Self :: filter_nonzero_weights ( & nodes) ;
392- // U16 is safe here since the original total_weight is u16.
393- let total_weight = nodes. iter ( ) . map ( |n| n. weight ) . sum :: < u16 > ( ) ;
394- let new_t = ( ( t as u64 ) * 100 ) . div_ceil ( max_d_x100 as u64 ) as u16 ;
395- let new_f = ( ( f as u64 ) * 100 ) . div_ceil ( max_d_x100 as u64 ) as u16 ;
396- Ok ( (
397- Self {
398- nodes,
399- total_weight,
400- accumulated_weights,
401- nodes_with_nonzero_weight,
402- } ,
403- new_t,
404- new_f,
405- ) )
406- }
407318}
408319
409320/// Compute (-x) mod d = d * ceil(x/d) - x
410321fn neg_mod ( x : u16 , d : u16 ) -> u16 {
411322 ( -( x as i32 ) ) . rem_euclid ( d as i32 ) as u16
412323}
413-
414- /// Compute ((-w) mod d) * 100 for the possibly-fractional divisor d = d_x100 / 100.
415- /// Equals (ceil(w/d) * d - w) * 100, a non-negative integer in [0, d_x100).
416- fn neg_mod_x100 ( w : u16 , d_x100 : u32 ) -> u64 {
417- let r = ( ( w as u64 ) * 100 ) % ( d_x100 as u64 ) ;
418- if r == 0 {
419- 0
420- } else {
421- ( d_x100 as u64 ) - r
422- }
423- }
0 commit comments