Security Configurations
The Three Production Configs
| Config | n | Primes | log2(q) | Classical | Quantum | Hybrid |
|---|---|---|---|---|---|---|
secure_128 | 4096 | 3 x 30-bit | 90 | 129 | 86 | 129 |
secure_192 | 16384 | 5 x ~30-bit | 147 | 374 | 213 | 318 |
secure_256 | 16384 | 6 x ~30-bit | 177 | 311 | 177 | 264 |
What the numbers mean
- n: Ring dimension. Larger = more security but slower operations. Must be a power of 2.
- Primes: NTT-friendly primes used to represent the ciphertext modulus Q via RNS.
- log2(q): Total bit-width of Q (product of all primes). Larger Q = more noise headroom = deeper circuits, but weakens security for fixed n.
- Classical: Bits of security against classical computers (BKZ lattice attack cost).
- Quantum: Bits of security against quantum computers (Grover speedup applied).
- Hybrid: Bits of security against meet-in-the-middle + BKZ hybrid attack (accounts for ternary secret).
Security threshold
A config “meets” its claimed level if the binding security (minimum of classical and hybrid) is at or above the target. Under the conservative Core-SVP cost model, all three configs meet their claimed levels.
Using Configs in Code
use nine65::params::secure_configs::SecureConfig;
// Production configs (always available)
let config = SecureConfig::secure_128().into_config();
let config = SecureConfig::secure_192().into_config();
let config = SecureConfig::secure_256().into_config();
// Deeper circuit variant (128-bit security, 4 primes, n=8192)
let config = SecureConfig::secure_128_deep().into_config();
Test-only configs
These are gated behind #[cfg(any(test, debug_assertions, feature = "allow_insecure"))] and will not compile in release builds:
// Only available in test/debug builds
let config = SecureConfig::test_fast_insecure().into_config(); // n=1024, ~40 bits
let config = SecureConfig::test_medium_insecure().into_config(); // n=2048, ~80 bits
Verifying Security
Run the built-in lattice estimator:
# Core-SVP (conservative)
cargo run -p nine65 --release --bin security_estimator_baseline
# MATZOV (aggressive/realistic)
cargo run -p nine65 --release --bin security_estimator_baseline -- --cost-model matzov
Output (2026-02-25 baseline):
Core-SVP: secure_128=129, secure_192=318, secure_256=264
MATZOV: secure_128=116, secure_192=286, secure_256=237
The estimator is in crates/nine65/src/params/security_estimator.rs. It uses:
- HE Standard v1.1 methodology
- Integer-only arithmetic (millibits precision)
- Ternary secret penalty for hybrid attacks
- Optimal MITM/BKZ split search
- Zero external dependencies
HE Standard Compliance
The HE Standard v1.1 table defines maximum log2(q) for each (n, security level) pair:
| n | Max log2(q) for 128-bit | Max log2(q) for 192-bit | Max log2(q) for 256-bit |
|---|---|---|---|
| 4096 | 109 | 75 | 58 |
| 8192 | 218 | 152 | 118 |
| 16384 | 438 | 305 | 237 |
Our configs:
secure_128: log2(q)=90 at n=4096 — well under the 109 limitsecure_192: log2(q)=147 at n=16384 — well under the 305 limitsecure_256: log2(q)=177 at n=16384 — under the 237 limit
Parameter History
The parameters were hardened in stages:
- Initial (pre-Feb 20): secure_192 used n=8192, secure_256 used 7 primes
- Feb 20 release: secure_192 upgraded to n=16384 (code), but old baseline doc not refreshed
- Feb 22 fix: secure_256 dropped 7th prime (log2q 203 to 177), improving hybrid security from 226 to 264 bits
- Feb 25 baseline: Fresh estimator run confirming all current parameters
The old baseline doc (LATTICE_ESTIMATOR_BASELINE_2026-02-09.md) was a stale snapshot. The current baseline (2026-02-25) is authoritative.