use super::*;
const N: usize = 7;
const M51: u128 = (1 << 51) - 1;
const P51: [u128; N] = [(1 << 51) - 19, M51, M51, M51, M51, 0, 0];
const TWO_P51: [u128; N] = [
(1 << 52) - 38,
(1 << 52) - 2,
(1 << 52) - 2,
(1 << 52) - 2,
(1 << 52) - 2,
0,
0,
];
fn canon(c: [u128; N]) -> [u64; N] {
let mut out = [0u64; N];
let mut acc: u128 = 0;
for j in 0..N {
acc += c[j];
out[j] = (acc & M51) as u64;
acc >>= 51;
}
assert_eq!(acc, 0);
out
}
fn lt_digits(a: &[u64; N], b: &[u64; N]) -> bool {
for i in (0..N).rev() {
if a[i] != b[i] {
return a[i] < b[i];
}
}
false
}
fn value(l: [u64; 5]) -> [u128; N] {
let mut c = [0u128; N];
for i in 0..5 {
c[i] = l[i] as u128;
}
c
}
fn plus(a: [u128; N], b: [u128; N]) -> [u128; N] {
let mut r = [0u128; N];
for j in 0..N {
r[j] = a[j] + b[j];
}
r
}
fn times_p(k: u128) -> [u128; N] {
let mut r = [0u128; N];
for j in 0..N {
r[j] = P51[j] * k;
}
r
}
#[kani::proof]
fn add_is_the_sum_of_the_values() {
let a: [u64; 5] = kani::any();
let b: [u64; 5] = kani::any();
for limb in a {
kani::assume(limb < 1 << 63);
}
for limb in b {
kani::assume(limb < 1 << 63);
}
let out = Fe(a).add(&Fe(b));
assert_eq!(canon(value(out.0)), canon(plus(value(a), value(b))));
}
#[kani::proof]
fn weak_reduce_keeps_the_value_mod_p_and_bounds_the_limbs() {
let r: [u64; 5] = kani::any();
for limb in r {
kani::assume(limb < 1 << 53);
}
let out = Fe(r).weak_reduce();
let c4 = (r[4] >> 51) as u128;
assert_eq!(canon(value(r)), canon(plus(value(out.0), times_p(c4))));
for limb in out.0 {
assert!(limb < (1 << 51) + 64);
}
}
#[kani::proof]
fn sub_is_a_minus_b_mod_p_with_bounded_limbs() {
let a: [u64; 5] = kani::any();
let b: [u64; 5] = kani::any();
for limb in a {
kani::assume(limb < 1 << 53);
}
for limb in b {
kani::assume(limb < (1 << 52) - 38);
}
let out = Fe(a).sub(&Fe(b));
let raw: [u128; 5] = core::array::from_fn(|i| a[i] as u128 + TWO_P51[i] - b[i] as u128);
let c4 = raw[4] >> 51;
assert_eq!(
canon(plus(value(a), TWO_P51)),
canon(plus(plus(value(out.0), value(b)), times_p(c4)))
);
for limb in out.0 {
assert!(limb < (1 << 51) + 96);
}
}
#[kani::proof]
fn carry_reduce_keeps_the_value_mod_p_and_bounds_the_limbs() {
let bound: u128 = 95 << 104;
let r0: u128 = kani::any();
let r1: u128 = kani::any();
let r2: u128 = kani::any();
let r3: u128 = kani::any();
let r4: u128 = kani::any();
for col in [r0, r1, r2, r3, r4] {
kani::assume(col < bound);
}
let r = [r0, r1, r2, r3, r4];
let out = carry_reduce(r0, r1, r2, r3, r4);
let c: [u128; 5] = core::array::from_fn(|i| r[i] >> 51);
let mut y = [0u128; N];
y[0] = (r[0] & M51) + 19 * c[4];
for i in 1..5 {
y[i] = (r[i] & M51) + c[i - 1];
}
let d = canon(y);
let top = d[5] as u128 + ((d[6] as u128) << 51);
let k = c[4] + top;
let mut value_r = [0u128; N];
value_r[..5].copy_from_slice(&r);
let mut nineteen_k = [0u128; N];
nineteen_k[0] = 19 * k;
let mut k_at_255 = [0u128; N];
k_at_255[5] = k;
assert_eq!(
canon(plus(value_r, nineteen_k)),
canon(plus(value(out.0), k_at_255))
);
assert!(out.0[0] < (1 << 51) + (1 << 14));
for limb in &out.0[1..] {
assert!(*limb < 1 << 51);
}
}
#[kani::proof]
fn to_bytes_is_the_canonical_representative_below_p() {
let r: [u64; 5] = kani::any();
for limb in r {
kani::assume(limb < 1 << 53);
}
let bytes = Fe(r).to_bytes();
let mut v = [0u128; N];
for bit in 0..256 {
let b = ((bytes[bit / 8] >> (bit % 8)) & 1) as u128;
v[bit / 51] += b << (bit % 51);
}
let vd = canon(v);
assert!(lt_digits(&vd, &canon(P51)));
let inp = canon(value(r));
assert!((0..5u128).any(|k| canon(plus(v, times_p(k))) == inp));
}