Skip to content

Commit 41d12cc

Browse files
authored
feat: reduce base-field sampling bias (#320)
1 parent c057545 commit 41d12cc

File tree

2 files changed

+30
-10
lines changed

2 files changed

+30
-10
lines changed

crates/cuda-backend/cuda/src/bn254_poseidon2.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ __global__ void bn254_adjacent_compress_layer_kernel(
324324
//
325325
// Matches MultiFieldTranscript<BabyBear, Bn254Scalar, Perm, WIDTH=3, RATE=2>:
326326
// num_obs_per_word = SF::bits() / CF::bits() = 254/31 = 8
327-
// num_samples_per_word = 7 (base-p decomposition)
327+
// num_samples_per_word = 5 (base-p decomposition, ≥100 bits bias slack)
328328
//
329329
// The sponge uses overwrite-mode duplex with absorb_idx/sample_idx tracking.
330330
// Rust DeviceBn254SpongeState must have identical layout (verified by size assert).

crates/stark-backend/src/transcript/multi_field.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ where
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`.
206220
fn 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

Comments
 (0)