6565 // Base-F::ORDER decomposition must extract at least 1 digit per squeeze.
6666 assert ! (
6767 num_samples_per_elem > 0 ,
68- "SF::order () must be >= F::order()^2 for base-F::ORDER sampling"
68+ "SF::bits () must be > 1 + BIAS_BITS + F::bits() for base-F::ORDER sampling"
6969 ) ;
7070
7171 Self {
@@ -200,16 +200,36 @@ where
200200 }
201201}
202202
203- /// Number of uniformly-distributed base-F::ORDER digits extractable from one SF element.
203+ const BIAS_BITS : usize = 100 ;
204+
205+ /// Returns the largest `k` such that extracting `k` base-`p` digits from a
206+ /// uniform `SF` element has statistical distance at most `2^{-BIAS_BITS}` from
207+ /// uniform, where `p = F::ORDER` and `q = SF::order()`.
208+ ///
209+ /// # Bias analysis
210+ ///
211+ /// Extracting `k` base-`p` digits from a uniform `X ∈ Z_q` is equivalent to
212+ /// computing `X mod p^k`. Write `q = m * p^k + r` with `0 <= r < p^k`. Then:
213+ ///
214+ /// - residues `0, ..., r - 1` each have `m + 1` preimages in `[0, q)`,
215+ /// - residues `r, ..., p^k - 1` each have only `m`.
204216///
205- /// Returns the largest k such that `p^(k+1) <= q`, where p = F::ORDER_U32 and q = SF::order().
217+ /// The exact statistical distance from uniform is `r * (p^k - r) / (q * p^k)`,
218+ /// which by AM-GM is at most `p^k / (4q)`. We pick the largest `k` satisfying
219+ /// `p^k * 2^(BIAS_BITS - 2) <= q`.
206220fn compute_num_samples_per_elem < F : PrimeField32 , SF : PrimeField > ( ) -> usize {
207221 let q = SF :: order ( ) ;
208222 let p = BigUint :: from ( F :: ORDER_U32 ) ;
209- let mut p_pow = & p * & p ; // p^2
223+
210224 let mut k = 0usize ;
211- while p_pow <= q {
212- p_pow *= & p;
225+ let mut p_pow = BigUint :: from ( 1u32 ) ; // p^k
226+
227+ loop {
228+ let next = & p_pow * & p; // p^(k+1)
229+ if ( & next << ( BIAS_BITS - 2 ) ) > q {
230+ break ;
231+ }
232+ p_pow = next;
213233 k += 1 ;
214234 }
215235 k
@@ -244,7 +264,7 @@ mod tests {
244264 fn test_constants_bn254_babybear ( ) {
245265 let t = TestTranscript :: new ( MockPerm ) ;
246266 assert_eq ! ( t. num_obs_per_elem, 8 ) ;
247- assert_eq ! ( t. num_samples_per_elem, 7 ) ;
267+ assert_eq ! ( t. num_samples_per_elem, 5 ) ;
248268 }
249269
250270 #[ test]
@@ -269,11 +289,11 @@ mod tests {
269289 BigUint :: from ( 1u64 ) + BigUint :: from ( 2u64 ) * & p + BigUint :: from ( 3u64 ) * & p * & p;
270290 let val = Bn254 :: from_biguint ( val_big) . unwrap ( ) ;
271291 let digits = t. extract_samples ( val) ;
272- assert_eq ! ( digits. len( ) , 7 ) ;
292+ assert_eq ! ( digits. len( ) , 5 ) ;
273293 assert_eq ! ( digits[ 0 ] , BabyBear :: from_int( 1u32 ) ) ;
274294 assert_eq ! ( digits[ 1 ] , BabyBear :: from_int( 2u32 ) ) ;
275295 assert_eq ! ( digits[ 2 ] , BabyBear :: from_int( 3u32 ) ) ;
276- for digit in & digits[ 3 ..7 ] {
296+ for digit in & digits[ 3 ..5 ] {
277297 assert_eq ! ( * digit, BabyBear :: ZERO ) ;
278298 }
279299 }
0 commit comments