Clockwork Core
clockwork-core is a standalone Rust crate that implements the Loki-Clockwork Formal Specification v1.0. It provides exact, integer-only RNS (Residue Number System) arithmetic with bound tracking, GRO timing gates, triple-redundant integrity, and key lifecycle management. The crate has 46 tests and zero floating-point operations.
Source: crates/clockwork-core/src/
What clockwork-core is
Clockwork-core is the formal-spec compliant arithmetic foundation that sits underneath nine65’s FHE operations. While nine65::arithmetic provides the K-Elimination and NTT implementations optimized for FHE ciphertext processing, clockwork-core provides the reference RNS types with mathematically proven invariants. Think of it as the spec-aligned layer that guarantees correctness, while nine65::arithmetic is the performance-optimized layer that implements the same algorithms at scale.
Design invariants
The crate enforces seven invariants from the formal specification (section 3):
| ID | Invariant | Enforcement |
|---|---|---|
| INV-1 | RLWE modulus q is fixed. No operation modifies it. | DecodeToQ stores q as an immutable field. No setter exists. |
| INV-2 | Round-trip through Clockwork residues is the identity on Z_q. | DecodeToQ::verify_roundtrip tests this for all c < q. Tested exhaustively in CT-05. |
| INV-3 | Bound tracker is always sound: |Center(X)| < 2^H(X). | GearStack derives bounds from operation rules (D7), never from actual values. Verified in CT-03. |
| INV-4 | GRO window sizes are schedule-determined. | GroGate computes windows from phase increments, not from runtime data. |
| INV-5 | Full secret key s is never stored after SPLIT phase. | KeyLifecycle state machine enforces this: initialize() transitions through SPLIT to ACTIVE, where only (s1, s2) exist. |
| INV-7 | Operation schedule is public and value-independent. | Bound updates use Bound::add_bound, Bound::mul_bound etc., which depend only on operand bounds, not values. |
| INV-8 | Triple-redundant tier state must pass MajVote before any DecQ operation. | TripleRedundant::read() returns VoteResult::Failed on double corruption, triggering fail-closed (T15). |
Modules
basis (RnsBasis) – Formal Spec D1-D4
File: basis.rs
The foundational type. RnsBasis holds a vector of pairwise coprime moduli, their product (capacity M_k), and precomputed CRT coefficients.
Key operations:
RnsBasis::new(moduli)– validates pairwise coprimality, computes capacity and CRT coefficients. ReturnsBasisErroron failure.encode(x)– D2: Enc_p(X) = (X mod p_0, …, X mod p_k).encode_signed(x)– handles negative values via modular arithmetic.decode(residues)– D3: CRT reconstruction to the unique X in [0, M_k).centered_lift(x)– D4: Center_{M_k}(x) in [-M_k/2, M_k/2].extend(new_modulus)– D8: basis promotion by appending a coprime modulus.promote_verified(residues, new_modulus, claimed_residue)– checked promotion with consistency verification.demote(residues, target_len)– restrict to first k moduli (T3 guarantee).
All u128 multiplication uses a shift-and-add approach to avoid overflow. Zero floating-point.
Theorems covered: T1 (CRT bijectivity, tested exhaustively in CT-01), T3 (promotion/demotion roundtrip, CT-04), T5 (DecQ roundtrip, CT-05).
bound_tracker (Bound) – Formal Spec D7
File: bound_tracker.rs
Deterministic integer bounds on the magnitude of centered-lift values. Replaces all floating-point log2_height estimates with exact, auditable, schedule-based bounds.
Key property (T4 – Bound Tracker Soundness): if all input values satisfy |Center(X)| < 2^H(X), then all computed values satisfy |Center(Y)| < 2^H(Y) under these rules:
| Operation | Bound rule |
|---|---|
| Addition | H(X+Y) <= max(H(X), H(Y)) + 1 |
| Subtraction | H(X-Y) <= max(H(X), H(Y)) + 1 |
| Multiplication | H(X*Y) <= H(X) + H(Y) |
| Dot product | H(sum w_j*x_j) <= ceil(log2(L)) + max_j(H(w_j) + H(x_j)) |
| Negation | H(-X) = H(X) |
| Scalar multiply | H(c*X) <= H(c) + H(X) |
The Bound type stores a single u32 exponent. needs_promotion(capacity_bits, guard) implements the D7 promotion policy: M_k > 2^(H + guard).
decode_to_q (DecodeToQ) – Formal Spec D9
File: decode_to_q.rs
The bridge from Clockwork representation space (Z_{M_k}) back to RLWE ciphertext space (Z_q). This is where INV-1 and INV-2 are enforced.
DecodeToQ::new(q, convention)– creates the bridge with a FIXED modulus. SupportsUnsignedorCentereddecode conventions.decode(basis, residues)– D9: DecQ_p(r_0, …, r_k) = Dec_p(r_0, …, r_k) mod q. Assertsbasis.capacity() >= qat runtime.encode(basis, c)– inverse direction: lifts a Z_q coefficient to Clockwork residues.verify_roundtrip(basis, c)– CT-05 test obligation: encode then decode must return the original.
garner (k_eliminate) – Formal Spec D5-D6
File: garner.rs
The K-Elimination 2-gear step and full Garner mixed-radix decomposition.
k_eliminate(r, s, m, a, m_inv_mod_a)– D6: compute k = (s - r) * m^(-1) mod a. T2 guarantees k in {0, …, a-1} and X = r + km (mod ma).k_eliminate_ct(...)– constant-time version for secret-dependent paths. Uses uniform execution time regardless of input values (T16 requirement). All subtraction is done via(s + a - r_mod_a) % ato avoid branching.garner_decompose(residues, moduli)– D5: full iterative Garner algorithm producingGarnerDigits. Each step is a K-Elimination.garner_decompose_ct(...)– constant-time version of the full decomposition.GarnerDigits::reconstruct()– recover X = d_0 + d_1p_0 + d_2p_0*p_1 + …
Tested exhaustively in CT-02 (K-Elimination vs naive CRT decode) and with 1M random samples for the large-modulus case.
gearstack (GearStack) – composite working type
File: gearstack.rs
The primary working type in Clockwork: holds a value’s RNS residues, a reference to its basis, and a bound tracker entry. Every arithmetic operation returns a new GearStack with an updated bound derived from the operand bounds (never from the actual value).
GearStack::from_value(value, bound_bits, basis)– create from unsigned value.GearStack::from_signed(value, bound_bits, basis)– create from signed value.add(&self, other)– lane-wise addition with D7 bound update.sub(&self, other)– lane-wise subtraction.mul(&self, other)– lane-wise multiplication.verify_bound()– DEBUG/TEST only: reconstructs the full value and checks it against the claimed bound (CT-03).needs_promotion(delta_bits)– checks if the basis has capacity for an operation that increases the bound.dot_product(weights, values)– dot product with tracked bounds.
Default guard margin is 8 bits (DEFAULT_GUARD_BITS).
gro (GroGate) – Formal Spec D13-D16
File: gro.rs
Golden Ratio Oscillator pair timing model for side-channel protection. This solves the B5 k-leakage problem: timing isolation ensures K-Elimination reveals nothing through execution time.
GroGate::new(params)– construct from explicit phase increments. Validates N_acc in [8, 63], nonzero increments, and reasonable window width.GroGate::golden_ratio(n_acc, delta_phi_a, window_width)– construct with phi-approximating phase increments using Fibonacci ratios (F_86/F_85). Adjusts to ensure odd phase difference for maximal period (T9).is_window(t)– D14: is time step t inside a coincidence window? True when|phase_A(t) - phase_B(t)| < window_width.coincidence_period()– T9:T_coinc = 2^N_acc / gcd(delta_phi_A - delta_phi_B, 2^N_acc). When the difference is odd, the period is maximal:2^N_acc.next_window(from, max_search)– find the next coincidence window.count_windows_in_range(from, to)– for equidistribution testing (GT-02).
All phase computation uses wrapping integer multiplication and bit masking. The golden ratio is approximated via F(86)/F(85) with integer division only (Axiom A3: DDS frequencies are rational).
integrity (TripleRedundant) – Formal Spec D22-D23
File: integrity.rs
Triple-redundant storage with CRC32 verification for tier state and GearStack metadata.
TripleRedundant::new(value)– D22: stores three copies and a CRC32 checksum.read()– D23 MajVote algorithm. Returns:VoteResult::AllAgree(value)– no corruption.VoteResult::Recovered { value, corrupted_copy }– single-location corruption detected and corrected (T14).VoteResult::Failed– two or more copies corrupted, fail-closed (T15).
write(value)– overwrites all copies and recomputes CRC.repair()– restores triple redundancy after single-copy corruption.
The TierState struct (num_gears, bound_bits, active_moduli) implements AsBytes for CRC computation.
Test IT-03 verifies CRC collision resistance: over 1M random double-corruption patterns, detection rate exceeds 99.99%.
key_lifecycle – Formal Spec D18-D21
File: key_lifecycle.rs
FHE secret key lifecycle management with additive sharing and scheduled resharing.
KeySharePair::split(s, r, q)– D18: s1 = r, s2 = (s - r) mod q. T12 guarantees s1 is uniform in Z_q, independent of s.KeySharePair::reconstruct()– recover s = s1 + s2 mod q. WARNING: puts full key in memory; must be zeroed after use.KeySharePair::reshare(r)– D19: s1’ = s1 + r, s2’ = s2 - r (mod q). T11 guarantees s1’ + s2’ = s.KeyLifecyclestate machine (D20):Keygen -> Split -> Active -> Zeroed, with reshare cycles during Active.initialize(s, r)– splits key and transitions to Active.record_operation()– returns true when reshare interval reached (public, schedule-based per INV-7).reshare(r)– reshares with fresh randomness; old shares zeroed by Drop.destroy()– zeroes all material, transitions to Zeroed (terminal).
Memory zeroing (Axiom A4) uses zeroize::Zeroize with compiler barriers to prevent dead-store elimination. KeySharePair implements Drop to auto-zero on scope exit.
This module is the ONLY place in clockwork-core that uses unsafe – specifically through the zeroize crate’s volatile zeroing mechanism.
Zero float guarantee
The crate enforces zero floating-point arithmetic at the compiler level:
#![deny(clippy::float_arithmetic)]
#![deny(clippy::float_cmp)]
Combined with CI grep checks, this guarantees no f32 or f64 values exist anywhere in the crate.
Safety
The crate declares #![deny(unsafe_code)] at the crate root. The only exception is key_lifecycle.rs, which uses the zeroize crate’s volatile zeroing mechanism (required by Axiom A4 for guaranteed memory clearing). All other code is safe Rust.
Relationship to nine65::arithmetic
| Concern | clockwork-core | nine65::arithmetic |
|---|---|---|
| Purpose | Formal spec compliance, reference implementation | Performance-optimized FHE arithmetic |
| K-Elimination | garner::k_eliminate (spec-aligned, both standard and CT) | k_elimination::KElimination (production, with DualRNS support) |
| NTT | Not included | NTTEngine, NTTEngineFFT |
| Montgomery | Not included | PersistentMontgomery, MontgomeryContext |
| Bound tracking | Bound (formal D7 rules) | Noise budget (millibits) via NoiseBudget |
| Integrity | TripleRedundant (CRC32 + MajVote) | Not duplicated – uses clockwork-core’s types |
The clockwork feature flag in nine65 enables integration: GRO timing gates, bound tracking, key lifecycle, and integrity checks from this crate.
Running tests
cargo test -p clockwork-core --release
All 46 tests should pass. The test suite covers:
- CT-01: Exhaustive CRT encode/decode roundtrip (all X in [0, M_k) for small bases, 1M random samples for larger bases).
- CT-02: K-Elimination vs naive CRT decode (exhaustive for small M, 1M random for large).
- CT-03: Bound tracker soundness verification (100K samples, worst-case addition and multiplication).
- CT-04: Promotion/demotion roundtrip (100K samples).
- CT-05: DecQ roundtrip for all c in [0, q) across 5 different basis choices.
- CT-06: Lane-wise add/mul matches ring operations (100K samples each).
- GT-01: GRO coincidence period = 2^N_acc for odd phase differences (100 random parameter sets).
- GT-02: GRO window equidistribution across 16 bins over one full period.
- IT-01 through IT-04: Triple-redundant corruption detection, recovery, and CRC collision resistance.
- KT-01 through KT-04: Key share correctness, memory zeroing, lifecycle state machine, and share distribution uniformity.
Where to go next
- Noise Budget – how nine65’s runtime noise tracking builds on these formal bounds.
- Bootstrap – the three bootstrap paths that use K-Elimination from this spec.
- Formal Proofs – the Coq and Lean4 proofs that verify these algorithms.