K-Elimination
K-Elimination is the algorithm that gives NINE65 exact integer division in the Residue Number System (RNS). Traditional RNS division requires full CRT reconstruction at O(k^2) cost. K-Elimination does it in O(k).
The Problem
In BFV, homomorphic multiplication produces values at the delta-squared level. To get back to the delta level, you must divide by delta — exactly, with no remainder and no rounding error. In RNS representation, where a number is stored as residues modulo separate primes, exact division has historically required reconstructing the full integer via the Chinese Remainder Theorem, which costs O(k^2) for k primes.
The Solution: Dual-Track Residues
K-Elimination avoids full CRT reconstruction by maintaining the value in two independent residue systems simultaneously:
- Alpha system (main computational primes): stores V mod alpha_cap
- Beta system (anchor primes): stores V mod beta_cap
Where alpha_cap and beta_cap are products of their respective prime sets and are coprime to each other.
The Algorithm
Given a value V represented as:
V = v_alpha (mod alpha_cap)
V = v_beta (mod beta_cap)
The quotient k (which tracks how many times alpha_cap wraps around) is computed as:
k = (v_beta - v_alpha) * alpha_cap_inv (mod beta_cap)
Then the exact value is reconstructed:
V = v_alpha + k * alpha_cap
This is O(k) — one modular subtraction, one modular multiplication, one multiply-and-add. No full CRT sweep.
Coq Proof
The correctness of K-Elimination is machine-checked in Coq:
Theorem k_elimination_complete : forall k v_M M A : nat,
M > 0 -> v_M < M -> k < A ->
let X := v_M + k * M in X / M = k.
Theorem complexity_improvement :
k_elimination_ops k = k /\ mrc_ops k = k * k.
(* O(k) vs O(k^2) *)
The proof file is proofs/coq/KElimination.v.
Configuration
The KElimConfig enum provides four preset configurations:
| Config | Alpha Bits | Beta Bits | Total Capacity | Use Case |
|---|---|---|---|---|
Minimal | ~32 | ~32 | ~64 bits | Testing, small values |
Standard | ~48 | ~62 | ~110 bits | Most FHE operations, N <= 2048 |
Extended | ~48 | ~90 | ~138 bits | Large N (4096+), deep circuits |
Maximum | ~64 | ~124 | ~188 bits | Maximum precision |
You can also build custom configurations with KElimBuilder:
let ke = KElimBuilder::new()
.alpha_primes(&[65537, 65521])
.beta_primes(&[4611686018427387847])
.build()?;
The 3-Anchor to 5-Anchor Fix
This is a critical piece of NINE65 history (commit 73a372e).
The bug: canonical_anchor_primes_for_n() originally returned 3 anchor primes, giving a ~94-bit anchor product. For secure_128 with 3 main primes, K-Elimination silently returned value mod (M*A) instead of the true value during ciphertext-times-ciphertext multiplication. The anchor product was too small to hold the quotient k.
The fix: Return 5 anchor primes instead of 3, giving a ~158-bit anchor product. With 159 bits of anchor capacity, all intermediate values from the tensor product fit comfortably.
The enforcement: DualRNSContext::for_fhe() always uses 5 anchors. The multiplication functions mul_dual_public() and mul_dual_symmetric() include a runtime assertion:
assert!(anchor_count >= 5);
This prevents any code path from accidentally using fewer anchors.
Key Types and Methods
KElimination
let ke = KElimination::from_config(KElimConfig::Standard);
// Check capacity
let bits = ke.capacity_bits(); // ~110 for Standard
// Check how close a value is to the capacity boundary
let report = ke.capacity_proximity(some_value);
if report.is_warning() {
// Value is at 80%+ of capacity
}
// Reconstruct exact value from dual residues
let exact = ke.reconstruct(v_alpha, v_beta);
// Exact division
let quotient = ke.exact_divide(v_alpha, v_beta, divisor);
ExactDivider (arithmetic/exact_divider.rs)
The ExactDivider is a lower-level primitive that performs K-Elimination with a single main modulus and a single anchor modulus:
let ed = ExactDivider::for_fhe(q);
// Reconstruct X from residues mod M and mod A
let x = ed.reconstruct(vm, va);
// Exact division when d | X
let result = ed.exact_divide(vm, va, d);
It uses 62-bit anchor primes (following the “Orbital Patch” of December 2024) to ensure that tensor product intermediates fit within the M*A product.
DualRNSContext
The FHE-level integration point. DualRNSContext::for_fhe() constructs the full dual-RNS setup with:
- Main RNS primes from the
SecureConfig - 5 anchor primes (always, since the fix)
- Precomputed NTT engines for each prime
- Precomputed delta values in RNS form
Key methods:
let ctx = DualRNSContext::for_fhe(&config);
// Anchor capacity is 159 bits for 5 primes
let anchor_bits = ctx.anchor_capacity_bits();
// Check if a value is approaching anchor limits
let report = ctx.check_k_proximity(value);
// Check intermediate multiplication values
let report = ctx.check_intermediate_proximity(intermediate);
Capacity Proximity Checking
K-Elimination has a hard capacity boundary: values must be less than alpha_cap * beta_cap for exact reconstruction. The capacity_proximity() method on KElimination returns a CapacityReport indicating which region a value falls in:
| Region | Utilization | Meaning |
|---|---|---|
Safe | < 80% | No concerns |
Warn80 | 80–89% | Approaching boundary |
Warn90 | 90–99% | Near overflow |
Critical | >= 100% | Overflow imminent or occurred |
For all three production SecureConfig values, intermediate tensor product values sit at ~45–47% of anchor capacity, which is firmly in the Safe region.
Performance
K-Elimination achieves a 40x speedup over traditional Mixed Radix Conversion for exact division in RNS:
| Method | Complexity | Operations for k primes |
|---|---|---|
| Mixed Radix Conversion | O(k^2) | k * k |
| K-Elimination | O(k) | k |
Where to go next
- Boundary Proximity — detailed capacity tracking and warning system
- NTT Engine — the polynomial multiplication that K-Elimination enables
- BFV Scheme — how K-Elimination fits into the full encryption pipeline