use num::Float;
use rand::Rng;
fn base_sampler(bytes: [u8; 9]) -> i16 {
const RCDT: [u128; 18] = [
3024686241123004913666,
1564742784480091954050,
636254429462080897535,
199560484645026482916,
47667343854657281903,
8595902006365044063,
1163297957344668388,
117656387352093658,
8867391802663976,
496969357462633,
20680885154299,
638331848991,
14602316184,
247426747,
3104126,
28824,
198,
1,
];
let mut bytes = bytes.to_vec();
bytes.extend_from_slice(&[0u8; 7]);
bytes.reverse();
let u = u128::from_be_bytes(bytes.try_into().expect("should have length 16"));
RCDT.into_iter().filter(|r| u < *r).count() as i16
}
fn approx_exp(x: f64, ccs: f64) -> u64 {
const C: [u64; 13] = [
0x00000004741183a3u64,
0x00000036548cfc06u64,
0x0000024fdcbf140au64,
0x0000171d939de045u64,
0x0000d00cf58f6f84u64,
0x000680681cf796e3u64,
0x002d82d8305b0feau64,
0x011111110e066fd0u64,
0x0555555555070f00u64,
0x155555555581ff00u64,
0x400000000002b400u64,
0x7fffffffffff4800u64,
0x8000000000000000u64,
];
let mut z: u64;
let mut y: u64;
let twoe63 = 1u64 << 63;
y = C[0];
z = Float::floor(x * (twoe63 as f64)) as u64;
for cu in C.iter().skip(1) {
let zy = (z as u128) * (y as u128);
y = cu - ((zy >> 63) as u64);
}
z = Float::floor((twoe63 as f64) * ccs) as u64;
(((z as u128) * (y as u128)) >> 63) as u64
}
fn ber_exp<R: Rng>(x: f64, ccs: f64, rng: &mut R) -> bool {
const LN2: f64 = core::f64::consts::LN_2;
const ILN2: f64 = 1.0 / LN2;
let s = Float::floor(x * ILN2);
let r = x - s * LN2;
let s = (s as u64).min(63);
let z = ((approx_exp(r, ccs) << 1) - 1) >> s;
let mut w = 0_i32;
for i in (0..=56).rev().step_by(8) {
let mut dest = [0_u8; 1];
rng.fill_bytes(&mut dest);
let p = u8::from_be_bytes(dest);
w = (p as i32) - (z >> i & 0xff) as i32;
if w != 0 {
break;
}
}
w < 0
}
pub(crate) fn sampler_z<R: Rng>(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R) -> i16 {
const SIGMA_MAX: f64 = 1.8205;
const INV_2SIGMA_MAX_SQ: f64 = 1f64 / (2f64 * SIGMA_MAX * SIGMA_MAX);
let isigma = 1f64 / sigma;
let dss = 0.5f64 * isigma * isigma;
let s = Float::floor(mu);
let r = mu - s;
let ccs = sigma_min * isigma;
loop {
let mut dest = [0_u8; 9];
rng.fill_bytes(&mut dest);
let z0 = base_sampler(dest);
let mut dest = [0_u8; 1];
rng.fill_bytes(&mut dest);
let random_byte: u8 = dest[0];
let b = (random_byte & 1) as i16;
let z = b + (2 * b - 1) * z0;
let zf_min_r = (z as f64) - r;
let x = zf_min_r * zf_min_r * dss - (z0 * z0) as f64 * INV_2SIGMA_MAX_SQ;
if ber_exp(x, ccs, rng) {
return z + (s as i16);
}
}
}
#[cfg(test)]
mod test {
use alloc::vec::Vec;
use rand::rand_core::{Infallible, TryRng, utils};
use super::{approx_exp, base_sampler, ber_exp, sampler_z};
struct ReplayRng {
bytes: Vec<u8>,
cursor: usize,
}
impl ReplayRng {
fn from_bytes(bytes: Vec<u8>) -> Self {
Self { bytes, cursor: 0 }
}
fn new(hex: &str) -> Self {
let bytes = hex::decode(hex).expect("KAT randomness must be valid hexadecimal");
Self { bytes, cursor: 0 }
}
fn fill(&mut self, dest: &mut [u8]) {
let end = self.cursor + dest.len();
assert!(end <= self.bytes.len(), "sampler requested more bytes than the KAT provides");
dest.copy_from_slice(&self.bytes[self.cursor..end]);
if dest.len() == 9 {
dest.reverse();
}
self.cursor = end;
}
}
impl TryRng for ReplayRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
utils::next_word_via_fill::<u32, _>(self)
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
utils::next_u64_via_u32(self)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
self.fill(dest);
Ok(())
}
}
#[test]
fn ber_exp_decides_at_every_comparison_depth() {
let (x, ccs) = (0.3f64, 0.7f64);
let z = (approx_exp(x, ccs) << 1) - 1;
let z_bytes = z.to_be_bytes().to_vec();
assert!(
z_bytes.iter().all(|&b| b > 0x00 && b < 0xff),
"test parameters must give interior comparison bytes: {z_bytes:?}"
);
for depth in 1..=8usize {
let mut accept = z_bytes[..depth - 1].to_vec();
accept.push(z_bytes[depth - 1] - 1);
let mut rng = ReplayRng::from_bytes(accept);
assert!(ber_exp(x, ccs, &mut rng), "must accept at depth {depth}");
assert_eq!(rng.cursor, depth, "accept must consume exactly {depth} bytes");
let mut reject = z_bytes[..depth - 1].to_vec();
reject.push(z_bytes[depth - 1] + 1);
let mut rng = ReplayRng::from_bytes(reject);
assert!(!ber_exp(x, ccs, &mut rng), "must reject at depth {depth}");
assert_eq!(rng.cursor, depth, "reject must consume exactly {depth} bytes");
}
let mut rng = ReplayRng::from_bytes(z_bytes);
assert!(!ber_exp(x, ccs, &mut rng), "a full tie must reject");
assert_eq!(rng.cursor, 8);
}
#[test]
fn base_sampler_respects_every_rcdt_threshold_boundary() {
const RCDT_PIN: [u128; 18] = [
3024686241123004913666,
1564742784480091954050,
636254429462080897535,
199560484645026482916,
47667343854657281903,
8595902006365044063,
1163297957344668388,
117656387352093658,
8867391802663976,
496969357462633,
20680885154299,
638331848991,
14602316184,
247426747,
3104126,
28824,
198,
1,
];
let draw = |u: u128| {
let mut bytes = [0u8; 9];
bytes.copy_from_slice(&u.to_le_bytes()[..9]);
base_sampler(bytes)
};
assert_eq!(draw(0), 18, "u = 0 lies below every threshold");
assert_eq!(draw((1u128 << 72) - 1), 0, "the maximal draw lies above every threshold");
for (k, threshold) in RCDT_PIN.into_iter().enumerate() {
assert_eq!(draw(threshold), k as i16, "u = RCDT[{k}] must sample {k}");
assert_eq!(draw(threshold - 1), k as i16 + 1, "u = RCDT[{k}] - 1 must sample {k}+1");
}
}
#[test]
fn sampler_z_matches_reference_known_answers() {
#[rustfmt::skip]
let kats: [(f64, f64, f64, &str, i16); 8] = [
(-91.90471153063714, 1.7037990414754918, 1.2778336969128337,
"0FC5442FF043D66E91D1EACAC64EA5450A22941EDC6C", -92),
(-8.322564895434937, 1.7037990414754918, 1.2778336969128337,
"F4DA0F8D8444D1A77265C2EF6F98BBBB4BEE7DB8D9B3", -8),
(-19.096516109216804, 1.7035823083824078, 1.2778336969128334,
"DB47F6D7FB9B19F25C36D6B9334D477A8BC0BE68145D", -20),
(-11.335543982423326, 1.7035823083824078, 1.2778336969128334,
"AE41B4F5209665C74D00DCC1A8168A7BB516B3190CB42C1DED26CD52AED770ECA7DD334E0547BCC3C163CE0B", -12),
(7.9386734193997555, 1.6984647769450156, 1.2778336969128337,
"31054166C1012780C603AE9B833CEC73F2F41CA5807CC89C92158834632F9B1555", 8),
(-28.990850086867255, 1.6984647769450156, 1.2778336969128337,
"737E9D68A50A06DBBC6477", -30),
(-9.071257914091655, 1.6980782114808988, 1.2778336969128339,
"A98DDD14BF0BF22061D632", -10),
(-43.88754568839566, 1.6980782114808988, 1.2778336969128339,
"3CBF6818A68F7AB9991514", -41),
];
for (mu, sigma, sigma_min, octets, expected_z) in kats {
let mut rng = ReplayRng::new(octets);
let z = sampler_z(mu, sigma, sigma_min, &mut rng);
assert_eq!(z, expected_z, "wrong sample for mu = {mu}");
assert_eq!(rng.cursor, rng.bytes.len(), "sampler left unused KAT bytes for mu = {mu}");
}
}
#[test]
fn test_approx_exp() {
let precision = 1u64 << 14;
let kats: [(f64, f64, u64); 10] = [
(0.2314993926072656, 0.8148006314615972, 5962140072160879737),
(0.2648875572812225, 0.12769669655309035, 903712282351034505),
(0.11251957513682391, 0.9264611470305881, 7635725498677341553),
(0.04353439307256617, 0.5306497137523327, 4685877322232397936),
(0.41834495299784347, 0.879438856118578, 5338392138535350986),
(0.32579398973228557, 0.16513412873289002, 1099603299296456803),
(0.5939508073919817, 0.029776019144967303, 151637565622779016),
(0.2932367999399056, 0.37123847662857923, 2553827649386670452),
(0.5005699297417507, 0.31447208863888976, 1758235618083658825),
(0.4876437338498085, 0.6159515298936868, 3488632981903743976),
];
for (x, ccs, answer) in kats {
let difference = (answer as i128) - (approx_exp(x, ccs) as i128);
assert!(
(difference * difference) as u64 <= precision * precision,
"answer: {answer} versus approximation: {}\ndifference: {} whereas precision: {}",
approx_exp(x, ccs),
difference,
precision
);
}
}
}