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 canon_mod(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;
}
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);
}
}
pub(super) fn carry_reduce_pre(r: [u128; 5]) -> bool {
let mut ok = true;
for i in 0..5 {
ok &= r[i] < 95 << 104;
}
ok
}
pub(super) fn carry_reduce_post(r: [u128; 5], out: &Fe) -> bool {
let mut c = [0u128; 5];
for i in 0..5 {
c[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_mod(y);
let top = d[5] as u128 + ((d[6] as u128) << 51);
let k = c[4] + top;
let mut value_r = [0u128; N];
for i in 0..5 {
value_r[i] = r[i];
}
let mut nineteen_k = [0u128; N];
nineteen_k[0] = 19 * k;
let mut k_at_255 = [0u128; N];
k_at_255[5] = k;
let (x, y) = (
canon_mod(plus(value_r, nineteen_k)),
canon_mod(plus(value(out.0), k_at_255)),
);
let mut same = true;
for j in 0..N {
same &= x[j] == y[j];
}
same & (out.0[0] < (1 << 51) + (1 << 14))
& (out.0[1] < 1 << 51)
& (out.0[2] < 1 << 51)
& (out.0[3] < 1 << 51)
& (out.0[4] < 1 << 51)
}
#[kani::proof_for_contract(carry_reduce)]
fn carry_reduce_meets_its_contract() {
carry_reduce(
kani::any(),
kani::any(),
kani::any(),
kani::any(),
kani::any(),
);
}
#[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));
}
impl kani::Arbitrary for Fe {
fn any() -> Fe {
Fe(kani::any())
}
}
pub(super) fn limbs52(a: &[u64; 5]) -> bool {
let mut ok = true;
for i in 0..5 {
ok &= a[i] < 1 << 52;
}
ok
}
fn any_limbs52() -> [u64; 5] {
let a: [u64; 5] = kani::any();
kani::assume(limbs52(&a));
a
}
pub(super) fn eq128(a: &[u128; 5], b: &[u128; 5]) -> bool {
let mut same = true;
for i in 0..5 {
same &= a[i] == b[i];
}
same
}
fn limb_product(x: u64, y: u64) -> u128 {
(x as u128).wrapping_mul(y as u128)
}
fn scaled(x: u64, c: u64) -> u64 {
x.wrapping_mul(c)
}
pub(super) fn mul_columns_spec(a: [u64; 5], b: [u64; 5]) -> [u128; 5] {
let mut cols = [0u128; 5];
for i in 0..5 {
for j in 0..5 {
if i + j < 5 {
cols[i + j] += limb_product(a[i], b[j]);
} else {
cols[i + j - 5] += limb_product(a[i], scaled(b[j], 19));
}
}
}
cols
}
pub(super) fn square_columns_spec(a: [u64; 5]) -> [u128; 5] {
let mut cols = [0u128; 5];
for i in 0..5 {
for j in i..5 {
let c = if i == j { 1 } else { 2 } * if i + j >= 5 { 19 } else { 1 };
cols[(i + j) % 5] += limb_product(scaled(a[i], c), a[j]);
}
}
cols
}
#[kani::proof_for_contract(mul_columns)]
#[kani::solver(bitwuzla)]
fn mul_columns_meets_its_contract() {
let (a, b): ([u64; 5], [u64; 5]) = (kani::any(), kani::any());
mul_columns(&a, &b);
}
#[kani::proof_for_contract(square_columns)]
#[kani::solver(bitwuzla)]
fn square_columns_meets_its_contract() {
let a: [u64; 5] = kani::any();
square_columns(&a);
}
#[kani::proof]
#[kani::stub_verified(mul_columns)]
#[kani::stub_verified(carry_reduce)]
fn mul_calls_stay_in_their_contracts() {
let (a, b) = (any_limbs52(), any_limbs52());
Fe(a).mul(&Fe(b));
}
#[kani::proof]
#[kani::stub_verified(square_columns)]
#[kani::stub_verified(carry_reduce)]
fn square_calls_stay_in_their_contracts() {
let a = any_limbs52();
Fe(a).square();
}