@@ -21,30 +21,10 @@ where
2121 return if deduplicate { vec ! [ 0 ] } else { vec ! [ 0 ; count] } ;
2222 }
2323
24- // Size the entropy chunk so `2^(8·size_bytes) ≥ next_pow2(num_leaves)`.
25- // For pow2 `num_leaves`, the entropy space is an exact multiple of
26- // `num_leaves` and rejection never triggers — bit-identical to the
27- // pre-rejection implementation. For non-pow2 `num_leaves`, rejection
28- // sampling eliminates the modular bias of a plain `% num_leaves`.
29- let bits_needed = num_leaves. next_power_of_two ( ) . ilog2 ( ) as usize ;
30- let size_bytes = bits_needed. div_ceil ( 8 ) ;
31-
32- // Largest multiple of `num_leaves` below `2^(8·size_bytes)`. u128
33- // accommodates `size_bytes ≤ 16` without shift overflow on 64-bit hosts.
34- let entropy_space: u128 = 1u128 << ( 8 * size_bytes) ;
35- let num_leaves_u = num_leaves as u128 ;
36- let threshold: u128 = ( entropy_space / num_leaves_u) * num_leaves_u;
37-
24+ let domain = IndexDomain :: new ( num_leaves) ;
3825 let mut indices = Vec :: with_capacity ( count) ;
39- while indices. len ( ) < count {
40- let mut candidate: u128 = 0 ;
41- for _ in 0 ..size_bytes {
42- candidate = ( candidate << 8 ) | u128:: from ( transcript. verifier_message :: < u8 > ( ) ) ;
43- }
44- if candidate < threshold {
45- indices. push ( ( candidate % num_leaves_u) as usize ) ;
46- }
47- // else: candidate falls in the biased tail — reject and redraw.
26+ for _ in 0 ..count {
27+ indices. push ( domain. sample ( transcript) ) ;
4828 }
4929
5030 if deduplicate {
5434 indices
5535}
5636
37+ /// Rejection-sampling domain for transcript-derived row indices.
38+ ///
39+ /// For power-of-two domains, `entropy_space` is an exact multiple of
40+ /// `num_leaves`, so rejection never triggers and sampling is bit-identical to
41+ /// the legacy `% num_leaves` implementation. For non-power-of-two domains,
42+ /// candidates in the biased tail are rejected and redrawn.
43+ #[ derive( Clone , Copy , Debug ) ]
44+ struct IndexDomain {
45+ num_leaves : usize ,
46+ size_bytes : usize ,
47+ threshold : u128 ,
48+ }
49+
50+ impl IndexDomain {
51+ fn new ( num_leaves : usize ) -> Self {
52+ debug_assert ! ( num_leaves > 1 ) ;
53+ let bits_needed = ceil_log2 ( num_leaves) ;
54+ let size_bytes = bits_needed. div_ceil ( 8 ) ;
55+ let entropy_bits = 8 * size_bytes;
56+ debug_assert ! ( entropy_bits < u128 :: BITS as usize ) ;
57+
58+ let entropy_space = 1u128 << entropy_bits;
59+ let num_leaves_u = num_leaves as u128 ;
60+ let threshold = ( entropy_space / num_leaves_u) * num_leaves_u;
61+
62+ Self {
63+ num_leaves,
64+ size_bytes,
65+ threshold,
66+ }
67+ }
68+
69+ fn sample < T > ( & self , transcript : & mut T ) -> usize
70+ where
71+ T : VerifierMessage ,
72+ u8 : Decoding < [ T :: U ] > ,
73+ {
74+ loop {
75+ let candidate = self . draw_candidate ( transcript) ;
76+ if candidate < self . threshold {
77+ return ( candidate % self . num_leaves as u128 ) as usize ;
78+ }
79+ }
80+ }
81+
82+ fn draw_candidate < T > ( & self , transcript : & mut T ) -> u128
83+ where
84+ T : VerifierMessage ,
85+ u8 : Decoding < [ T :: U ] > ,
86+ {
87+ let mut candidate = 0u128 ;
88+ for _ in 0 ..self . size_bytes {
89+ candidate = ( candidate << 8 ) | u128:: from ( transcript. verifier_message :: < u8 > ( ) ) ;
90+ }
91+ candidate
92+ }
93+ }
94+
95+ fn ceil_log2 ( n : usize ) -> usize {
96+ debug_assert ! ( n > 1 ) ;
97+ usize:: BITS as usize - ( n - 1 ) . leading_zeros ( ) as usize
98+ }
99+
57100#[ cfg( test) ]
58101mod tests {
59102 use super :: * ;
0 commit comments