// ---
// tags: jali, rust
// crystal-type: source
// crystal-domain: comp
// ---
//! Test vectors for jali ring arithmetic.
#[cfg(test)]
mod tests {
use nebu::Goldilocks;
use nebu::field::P;
use crate::ring::RingElement;
use crate::ntt;
use crate::noise::NoiseBudget;
use crate::sample;
use crate::encoding;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
// โโ Ring: zero element โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_zero_is_zero() {
let z = RingElement::new(1024);
assert!(z.is_zero());
}
#[test]
fn ring_zero_add_zero() {
let z1 = RingElement::new(1024);
let z2 = RingElement::new(1024);
let sum = z1.add(&z2);
assert!(sum.is_zero());
}
// โโ Ring: add โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_add_basic() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(1);
a.coeffs[1] = g(2);
b.coeffs[0] = g(3);
b.coeffs[1] = g(4);
let c = a.add(&b);
assert_eq!(c.coeffs[0].as_u64(), 4);
assert_eq!(c.coeffs[1].as_u64(), 6);
for i in 2..n {
assert!(c.coeffs[i].is_zero());
}
}
#[test]
fn ring_add_wraparound() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = Goldilocks::NEG_ONE; // p-1
b.coeffs[0] = g(2);
let c = a.add(&b);
assert_eq!(c.coeffs[0].as_u64(), 1); // (p-1)+2 = p+1 = 1
}
#[test]
fn ring_add_identity() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
a.coeffs[3] = g(100);
let z = RingElement::new(n);
let c = a.add(&z);
assert!(c.eq_ring(&a));
}
#[test]
fn ring_add_commutative() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(7);
a.coeffs[1] = g(11);
b.coeffs[0] = g(13);
b.coeffs[1] = g(17);
let ab = a.add(&b);
let ba = b.add(&a);
assert!(ab.eq_ring(&ba));
}
#[test]
fn ring_add_associative() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
let mut c = RingElement::new(n);
a.coeffs[0] = g(100);
b.coeffs[0] = g(200);
c.coeffs[0] = g(300);
let ab_c = a.add(&b).add(&c);
let a_bc = a.add(&b.add(&c));
assert!(ab_c.eq_ring(&a_bc));
}
// โโ Ring: sub โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_sub_basic() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(10);
b.coeffs[0] = g(3);
let c = a.sub(&b);
assert_eq!(c.coeffs[0].as_u64(), 7);
}
#[test]
fn ring_sub_self_is_zero() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
a.coeffs[1] = g(99);
let c = a.sub(&a);
assert!(c.is_zero());
}
#[test]
fn ring_sub_wraparound() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(0);
b.coeffs[0] = g(1);
let c = a.sub(&b);
assert_eq!(c.coeffs[0].as_u64(), P - 1); // 0 - 1 = -1 = p-1
}
// โโ Ring: neg โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_neg_basic() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(1);
let neg_a = a.neg();
assert_eq!(neg_a.coeffs[0].as_u64(), P - 1);
}
#[test]
fn ring_neg_zero() {
let z = RingElement::new(8);
let neg_z = z.neg();
assert!(neg_z.is_zero());
}
#[test]
fn ring_double_neg() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
a.coeffs[1] = g(99);
let nn = a.neg().neg();
assert!(nn.eq_ring(&a));
}
#[test]
fn ring_add_neg_is_zero() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
a.coeffs[1] = g(99);
let c = a.add(&a.neg());
assert!(c.is_zero());
}
// โโ Ring: scalar_mul โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_scalar_mul_basic() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(5);
let c = a.scalar_mul(g(7));
assert_eq!(c.coeffs[0].as_u64(), 21);
assert_eq!(c.coeffs[1].as_u64(), 35);
}
#[test]
fn ring_scalar_mul_zero() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
let c = a.scalar_mul(Goldilocks::ZERO);
assert!(c.is_zero());
}
#[test]
fn ring_scalar_mul_one() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
a.coeffs[1] = g(99);
let c = a.scalar_mul(Goldilocks::ONE);
assert!(c.eq_ring(&a));
}
// โโ Ring: mul (polynomial multiplication via NTT) โโโโโโโโโโโโโโโโ
#[test]
fn ring_mul_one_times_x() {
// 1 * x = x in R_q
let n = 8;
let mut one_poly = RingElement::new(n);
one_poly.coeffs[0] = g(1); // polynomial "1"
let mut x_poly = RingElement::new(n);
x_poly.coeffs[1] = g(1); // polynomial "x"
let result = one_poly.mul(&x_poly);
assert_eq!(result.coeffs[0].as_u64(), 0);
assert_eq!(result.coeffs[1].as_u64(), 1);
for i in 2..n {
assert!(result.coeffs[i].is_zero());
}
}
#[test]
fn ring_mul_x_times_x() {
// x * x = x^2
let n = 8;
let mut x_poly = RingElement::new(n);
x_poly.coeffs[1] = g(1);
let result = x_poly.mul(&x_poly);
assert_eq!(result.coeffs[2].as_u64(), 1); // x^2
for i in 0..n {
if i != 2 {
assert!(result.coeffs[i].is_zero());
}
}
}
#[test]
fn ring_mul_negacyclic_wraparound() {
// In R_q = F_p[x]/(x^n+1), x^n = -1
// So x^(n-1) * x = x^n = -1
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[n - 1] = g(1); // x^(n-1)
let mut b = RingElement::new(n);
b.coeffs[1] = g(1); // x
let result = a.mul(&b);
// x^(n-1) * x = x^n = -1 mod (x^n+1)
assert_eq!(result.coeffs[0].as_u64(), P - 1); // -1
for i in 1..n {
assert!(result.coeffs[i].is_zero());
}
}
#[test]
fn ring_mul_commutative() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(5);
b.coeffs[0] = g(7);
b.coeffs[2] = g(2);
let ab = a.mul(&b);
let ba = b.mul(&a);
assert!(ab.eq_ring(&ba));
}
#[test]
fn ring_mul_distributive() {
// a * (b + c) = a*b + a*c
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
let mut c = RingElement::new(n);
a.coeffs[0] = g(2);
a.coeffs[1] = g(3);
b.coeffs[0] = g(5);
b.coeffs[1] = g(7);
c.coeffs[0] = g(11);
c.coeffs[2] = g(13);
let lhs = a.mul(&b.add(&c));
let rhs = a.mul(&b).add(&a.mul(&c));
assert!(lhs.eq_ring(&rhs));
}
#[test]
fn ring_mul_zero() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
let z = RingElement::new(n);
let result = a.mul(&z);
assert!(result.is_zero());
}
#[test]
fn ring_mul_identity() {
// 1 * a = a
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(5);
a.coeffs[2] = g(7);
let mut one = RingElement::new(n);
one.coeffs[0] = g(1);
let result = a.mul(&one);
assert!(result.eq_ring(&a));
}
#[test]
fn ring_mul_constant_times_poly() {
// (c) * (a0 + a1*x) = c*a0 + c*a1*x
let n = 8;
let mut c_poly = RingElement::new(n);
c_poly.coeffs[0] = g(5);
let mut a = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(7);
let result = c_poly.mul(&a);
assert_eq!(result.coeffs[0].as_u64(), 15);
assert_eq!(result.coeffs[1].as_u64(), 35);
for i in 2..n {
assert!(result.coeffs[i].is_zero());
}
}
#[test]
fn ring_mul_associative() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
let mut c = RingElement::new(n);
a.coeffs[0] = g(2);
a.coeffs[1] = g(1);
b.coeffs[0] = g(3);
b.coeffs[1] = g(1);
c.coeffs[0] = g(5);
c.coeffs[1] = g(1);
let ab_c = a.mul(&b).mul(&c);
let a_bc = a.mul(&b.mul(&c));
assert!(ab_c.eq_ring(&a_bc));
}
// โโ Ring: larger dimension โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_mul_n1024_negacyclic() {
let n = 1024;
let mut a = RingElement::new(n);
a.coeffs[n - 1] = g(1); // x^(n-1)
let mut b = RingElement::new(n);
b.coeffs[1] = g(1); // x
let result = a.mul(&b);
assert_eq!(result.coeffs[0].as_u64(), P - 1); // x^n = -1
for i in 1..n {
assert!(result.coeffs[i].is_zero());
}
}
// โโ NTT: forward/inverse roundtrip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ntt_roundtrip_small() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(1);
a.coeffs[1] = g(2);
a.coeffs[2] = g(3);
let orig_coeffs: [u64; 8] = core::array::from_fn(|i| a.coeffs[i].as_u64());
ntt::to_ntt(&mut a);
assert!(a.is_ntt);
ntt::from_ntt(&mut a);
assert!(!a.is_ntt);
for i in 0..n {
assert_eq!(a.coeffs[i].as_u64(), orig_coeffs[i], "mismatch at index {}", i);
}
}
#[test]
fn ntt_roundtrip_n1024() {
let n = 1024;
let a = sample::sample_uniform(0xCAFE, n);
let orig: [u64; 1024] = core::array::from_fn(|i| a.coeffs[i].as_u64());
let mut b = a.clone();
ntt::to_ntt(&mut b);
ntt::from_ntt(&mut b);
for i in 0..n {
assert_eq!(b.coeffs[i].as_u64(), orig[i], "mismatch at index {}", i);
}
}
#[test]
fn ntt_roundtrip_all_ones() {
let n = 8;
let coeffs = [Goldilocks::ONE; 8];
let a = RingElement::from_coeffs(&coeffs, n);
let mut b = a.clone();
ntt::to_ntt(&mut b);
ntt::from_ntt(&mut b);
assert!(b.eq_ring(&a));
}
#[test]
fn ntt_negacyclic_property() {
// Verify x^n = -1 in R_q by checking that NTT multiplication
// of x^(n/2) * x^(n/2) = x^n = -1
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[n / 2] = g(1); // x^(n/2)
let result = a.mul(&a); // x^n = -1
assert_eq!(result.coeffs[0].as_u64(), P - 1);
for i in 1..n {
assert!(result.coeffs[i].is_zero());
}
}
#[test]
fn ntt_preserves_zero() {
let n = 8;
let mut z = RingElement::new(n);
ntt::to_ntt(&mut z);
for i in 0..n {
assert!(z.coeffs[i].is_zero());
}
}
#[test]
fn ntt_linearity() {
// NTT(a + b) = NTT(a) + NTT(b) (since NTT is a linear transform)
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(5);
b.coeffs[0] = g(7);
b.coeffs[2] = g(11);
let sum = a.add(&b);
let mut ntt_sum = sum.clone();
ntt::to_ntt(&mut ntt_sum);
let mut ntt_a = a.clone();
let mut ntt_b = b.clone();
ntt::to_ntt(&mut ntt_a);
ntt::to_ntt(&mut ntt_b);
let ntt_add = ntt_a.add(&ntt_b);
assert!(ntt_sum.eq_ring(&ntt_add));
}
// โโ Noise budget โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn noise_fresh() {
let nb = NoiseBudget::fresh(10);
assert_eq!(nb.log_bound, 10);
}
#[test]
fn noise_after_add() {
let a = NoiseBudget::fresh(10);
let b = NoiseBudget::fresh(12);
let c = NoiseBudget::after_add(&a, &b);
assert_eq!(c.log_bound, 13); // max(10,12) + 1
}
#[test]
fn noise_after_add_equal() {
let a = NoiseBudget::fresh(10);
let b = NoiseBudget::fresh(10);
let c = NoiseBudget::after_add(&a, &b);
assert_eq!(c.log_bound, 11); // 10 + 1
}
#[test]
fn noise_after_mul() {
let a = NoiseBudget::fresh(10);
let b = NoiseBudget::fresh(12);
let c = NoiseBudget::after_mul(&a, &b, 1024);
// 10 + 12 + log2(1024) = 10 + 12 + 10 = 32
assert_eq!(c.log_bound, 32);
}
#[test]
fn noise_after_mul_n4096() {
let a = NoiseBudget::fresh(5);
let b = NoiseBudget::fresh(5);
let c = NoiseBudget::after_mul(&a, &b, 4096);
// 5 + 5 + 12 = 22
assert_eq!(c.log_bound, 22);
}
#[test]
fn noise_after_bootstrap() {
let nb = NoiseBudget::after_bootstrap(8);
assert_eq!(nb.log_bound, 8);
}
#[test]
fn noise_needs_bootstrap_yes() {
let nb = NoiseBudget::fresh(50);
assert!(NoiseBudget::needs_bootstrap(&nb, 40));
}
#[test]
fn noise_needs_bootstrap_no() {
let nb = NoiseBudget::fresh(20);
assert!(!NoiseBudget::needs_bootstrap(&nb, 40));
}
#[test]
fn noise_needs_bootstrap_boundary() {
let nb = NoiseBudget::fresh(40);
assert!(NoiseBudget::needs_bootstrap(&nb, 40));
}
#[test]
fn noise_remaining() {
let nb = NoiseBudget::fresh(20);
assert_eq!(NoiseBudget::remaining(&nb, 50), 30);
}
#[test]
fn noise_remaining_zero() {
let nb = NoiseBudget::fresh(50);
assert_eq!(NoiseBudget::remaining(&nb, 40), 0);
}
#[test]
fn noise_chain_operations() {
let a = NoiseBudget::fresh(5);
let b = NoiseBudget::fresh(5);
let after_add = NoiseBudget::after_add(&a, &b); // 6
let after_mul = NoiseBudget::after_mul(&after_add, &a, 1024); // 6+5+10=21
assert_eq!(after_mul.log_bound, 21);
let after_boot = NoiseBudget::after_bootstrap(8); // 8
assert_eq!(NoiseBudget::remaining(&after_boot, 50), 42);
}
// โโ Sample: ternary โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn sample_ternary_range() {
let elem = sample::sample_ternary(12345, 1024);
for i in 0..1024 {
let v = elem.coeffs[i].as_u64();
assert!(v == 0 || v == 1 || v == P - 1,
"ternary sample out of range at index {}: {}", i, v);
}
}
#[test]
fn sample_ternary_not_all_zero() {
let elem = sample::sample_ternary(42, 1024);
assert!(!elem.is_zero(), "ternary sample should not be all zeros");
}
#[test]
fn sample_ternary_deterministic() {
let a = sample::sample_ternary(999, 256);
let b = sample::sample_ternary(999, 256);
assert!(a.eq_ring(&b), "same seed should produce same result");
}
#[test]
fn sample_ternary_different_seeds() {
let a = sample::sample_ternary(1, 256);
let b = sample::sample_ternary(2, 256);
assert!(!a.eq_ring(&b), "different seeds should produce different results");
}
// โโ Sample: CBD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn sample_cbd_bound() {
let eta = 3;
let elem = sample::sample_cbd(54321, 1024, eta);
for i in 0..1024 {
let v = elem.coeffs[i].as_u64();
// Value should be in {0, 1, ..., eta, p-1, p-2, ..., p-eta}
let is_small_pos = v <= eta as u64;
let is_small_neg = v >= P - eta as u64;
assert!(is_small_pos || is_small_neg,
"CBD sample out of range at index {}: 0x{:X}", i, v);
}
}
#[test]
fn sample_cbd_deterministic() {
let a = sample::sample_cbd(777, 256, 2);
let b = sample::sample_cbd(777, 256, 2);
assert!(a.eq_ring(&b));
}
#[test]
fn sample_cbd_eta1() {
let elem = sample::sample_cbd(111, 256, 1);
for i in 0..256 {
let v = elem.coeffs[i].as_u64();
assert!(v == 0 || v == 1 || v == P - 1,
"CBD(1) should give ternary, got 0x{:X} at {}", v, i);
}
}
// โโ Sample: uniform โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn sample_uniform_deterministic() {
let a = sample::sample_uniform(42, 256);
let b = sample::sample_uniform(42, 256);
assert!(a.eq_ring(&b));
}
#[test]
fn sample_uniform_not_zero() {
let a = sample::sample_uniform(42, 256);
assert!(!a.is_zero());
}
// โโ Encoding: roundtrip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn encoding_roundtrip_small() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
a.coeffs[1] = g(0xDEADBEEF);
a.coeffs[7] = Goldilocks::NEG_ONE;
let mut buf = [0u8; 64]; // 8 * 8
let written = encoding::encode_ring(&a, &mut buf);
assert_eq!(written, 64);
let decoded = encoding::decode_ring(&buf, n);
assert!(decoded.eq_ring(&a));
}
#[test]
fn encoding_roundtrip_n1024() {
let n = 1024;
let a = sample::sample_uniform(0xBEEF, n);
let mut buf = [0u8; 8192]; // 1024 * 8
let written = encoding::encode_ring(&a, &mut buf);
assert_eq!(written, 8192);
let decoded = encoding::decode_ring(&buf, n);
assert!(decoded.eq_ring(&a));
}
#[test]
fn encoding_zero() {
let n = 8;
let z = RingElement::new(n);
let mut buf = [0u8; 64];
encoding::encode_ring(&z, &mut buf);
let decoded = encoding::decode_ring(&buf, n);
assert!(decoded.is_zero());
}
#[test]
fn encoding_preserves_exact_values() {
let n = 4;
let values = [g(0), g(1), g(P - 1), g(0x123456789ABCDEF0)];
let a = RingElement::from_coeffs(&values, n);
let mut buf = [0u8; 32]; // 4 * 8
encoding::encode_ring(&a, &mut buf);
let decoded = encoding::decode_ring(&buf, n);
for i in 0..n {
assert_eq!(decoded.coeffs[i].as_u64(), a.coeffs[i].as_u64(),
"mismatch at index {}", i);
}
}
// โโ Automorphism โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn automorphism_identity() {
// k=0: t = 5^0 = 1, so automorphism is identity
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(5);
a.coeffs[2] = g(7);
let result = a.automorphism(0);
assert!(result.eq_ring(&a));
}
#[test]
fn automorphism_constant_poly() {
// Constant polynomial is fixed by all automorphisms
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
let result = a.automorphism(3);
assert!(result.eq_ring(&a));
}
#[test]
fn automorphism_x_to_x5() {
// k=1: x -> x^5 (for n=8, 2n=16, t=5)
// f(x) = x -> f(x^5) = x^5
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[1] = g(1); // x
let result = a.automorphism(1);
// x^5 in degree-8 ring: coefficient at position 5
assert_eq!(result.coeffs[5].as_u64(), 1);
for i in 0..n {
if i != 5 {
assert!(result.coeffs[i].is_zero());
}
}
}
#[test]
fn automorphism_negacyclic_wrap() {
// k=1: x -> x^5 for n=4 (2n=8, t=5)
// For f(x) = x^2, automorphism gives x^(2*5 mod 8) = x^(10 mod 8) = x^2
// Wait: 2*5=10, 10 mod 8 = 2. So x^2 -> x^2
// Let's use x^3 instead: 3*5=15, 15 mod 8 = 7. 7 >= 4, so coeff at 7-4=3 is negated.
let n = 4;
let mut a = RingElement::new(n);
a.coeffs[3] = g(1); // x^3
let result = a.automorphism(1);
// 3*5 = 15, 15 mod 8 = 7, 7 >= 4, so -x^(7-4) = -x^3
assert_eq!(result.coeffs[3].as_u64(), P - 1); // -1
for i in 0..n {
if i != 3 {
assert!(result.coeffs[i].is_zero());
}
}
}
#[test]
fn automorphism_ring_homomorphism() {
// sigma(a + b) = sigma(a) + sigma(b)
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(3);
a.coeffs[1] = g(5);
b.coeffs[0] = g(7);
b.coeffs[2] = g(11);
let k = 1;
let lhs = a.add(&b).automorphism(k);
let rhs = a.automorphism(k).add(&b.automorphism(k));
assert!(lhs.eq_ring(&rhs));
}
// โโ Ring: eq_ring โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn eq_ring_same() {
let n = 8;
let mut a = RingElement::new(n);
a.coeffs[0] = g(42);
assert!(a.eq_ring(&a));
}
#[test]
fn eq_ring_different() {
let n = 8;
let mut a = RingElement::new(n);
let mut b = RingElement::new(n);
a.coeffs[0] = g(42);
b.coeffs[0] = g(43);
assert!(!a.eq_ring(&b));
}
#[test]
fn eq_ring_canonical() {
// Goldilocks::new(P) should canonicalize to 0
let n = 4;
let a = RingElement::new(n); // all zeros
let mut b = RingElement::new(n);
b.coeffs[0] = Goldilocks::new(P); // should be 0 after canonicalization
assert!(a.eq_ring(&b));
}
// โโ Ring: from_coeffs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn from_coeffs_basic() {
let coeffs = [g(1), g(2), g(3), g(4)];
let a = RingElement::from_coeffs(&coeffs, 4);
assert_eq!(a.coeffs[0].as_u64(), 1);
assert_eq!(a.coeffs[1].as_u64(), 2);
assert_eq!(a.coeffs[2].as_u64(), 3);
assert_eq!(a.coeffs[3].as_u64(), 4);
assert_eq!(a.n, 4);
}
// โโ NTT: mul consistency with schoolbook โโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ntt_mul_matches_schoolbook_small() {
// (2 + 3x) * (5 + 7x) mod (x^4 + 1)
// = 10 + 14x + 15x + 21x^2 = 10 + 29x + 21x^2
// No wraparound since degree < n=4
let n = 4;
let mut a = RingElement::new(n);
a.coeffs[0] = g(2);
a.coeffs[1] = g(3);
let mut b = RingElement::new(n);
b.coeffs[0] = g(5);
b.coeffs[1] = g(7);
let result = a.mul(&b);
assert_eq!(result.coeffs[0].as_u64(), 10);
assert_eq!(result.coeffs[1].as_u64(), 29);
assert_eq!(result.coeffs[2].as_u64(), 21);
assert!(result.coeffs[3].is_zero());
}
// โโ Additional ring tests โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[test]
fn ring_mul_x_n_minus_1_times_x() {
// Another negacyclic test at n=4
let n = 4;
let mut a = RingElement::new(n);
a.coeffs[3] = g(2); // 2*x^3
let mut b = RingElement::new(n);
b.coeffs[1] = g(3); // 3*x
let result = a.mul(&b);
// 2*x^3 * 3*x = 6*x^4 = 6*(-1) = -6 mod p
assert_eq!(result.coeffs[0].as_u64(), P - 6);
for i in 1..n {
assert!(result.coeffs[i].is_zero());
}
}
#[test]
fn ring_mul_full_wrap() {
// (1 + x + x^2 + x^3) * x mod (x^4 + 1)
// = x + x^2 + x^3 + x^4 = x + x^2 + x^3 - 1
let n = 4;
let mut a = RingElement::new(n);
for i in 0..4 {
a.coeffs[i] = g(1);
}
let mut b = RingElement::new(n);
b.coeffs[1] = g(1); // x
let result = a.mul(&b);
assert_eq!(result.coeffs[0].as_u64(), P - 1); // -1
assert_eq!(result.coeffs[1].as_u64(), 1);
assert_eq!(result.coeffs[2].as_u64(), 1);
assert_eq!(result.coeffs[3].as_u64(), 1);
}
}
jali/rs/src/vectors.rs
ฯ 0.0%
// ---
// tags: jali, rust
// crystal-type: source
// crystal-domain: comp
// ---
//! Test vectors for jali ring arithmetic.