CKKS overview
CKKS is an approximate-number homomorphic encryption scheme. It packs real or complex values into encrypted slots, then lets code add, multiply, rotate, and combine those slots without decrypting them. The result is approximate rather than exact: every ciphertext carries scale, level, and noise state, and multiplication consumes level.
Ren exposes CKKS as a high-level API over the same lazy polynomial engine described in the overview. A Plaintext wraps one encoded Poly; a Ciphertext wraps the two encrypted polynomial components a and b. Operations on ciphertexts usually create lazy polynomial graphs, and those graphs run only when they are realized, decrypted, serialized, or captured by JIT.
flowchart TB
A["CKKSParams"]
B["CKKSContext"]
C["Encode slots"]
D["Encrypt plaintext"]
E["Encrypted compute"]
F["Lazy ciphertext output"]
G["Realize, serialize, or JIT"]
H["Decrypt and decode"]
A --> B --> C --> D --> E --> F
F --> G
F --> H
Minimal shape
import numpy as np
from ren.schemes.ckks import CKKSParams, decode, decrypt, encode, encrypt, keygen
params = CKKSParams.python_test()
ctx = keygen(params)
values = np.array([0.25, 0.5, 0.75, 1.0])
with ctx:
ct = encrypt(encode(values, msg_bound=1.0))
result = ct + 1
decoded = decode(decrypt(result), length=len(values))
CKKSParams.python_test() is a small local-test parameter set. Real applications should use parameters chosen for their security, precision, multiplicative depth, and bootstrapping needs.
Message bounds are public
msg_bound must be independent of secret data. Do not compute it from secret values, such as max(abs(v) for v in secret_values).
Main objects
CKKSParams describes the ring, slot count, level schedule, scales, key-switching primes, optional bootstrapping parameters, and runtime flags. The important mental model is that levels[level] describes the active modulus basis and nominal scale at that level. Fresh encryptions start at default_level; multiplication and rescale move down the level schedule.
CKKSContext owns the parameters and keys. Use with ctx: to make it the active context for encode, encrypt, decrypt, arithmetic alignment, key-switching, and level transitions. The context can also generate relinearization keys, rotation keys, conjugation keys, realize key polys, and precompute bootstrap work.
Plaintext is an encoded slot vector. Its main polynomial field is m, and it carries scale/noise metadata plus optional plaintext-value tracking for tests and diagnostics.
Ciphertext is the encrypted value. It has two polynomial fields, a and b, plus scale/noise metadata and optional plaintext-value tracking. Because it is a dataclass containing Poly values, it can be passed directly to ren.jit.
Common operations
encode(values, msg_bound=...)turns Python values into a CKKSPlaintext.decode(pt, length=...)converts a decrypted plaintext back to Python complex values.encrypt(pt)turns a plaintext into aCiphertextusing the active context.decrypt(ct)returns aPlaintextusing the active context's secret key.ct + other,ct - other, andct * otherbuild encrypted arithmetic over ciphertexts, plaintexts, or scalars.Ciphertext.sum(...),Ciphertext.multiply(...), andCiphertext.dot(...)provide higher-level reductions.ct.rotate(step)rotates packed slots when the context has the required rotation key.ct.to_level(level)drops a ciphertext to a lower level when operations need alignment.bootstrap(ct)refreshes a ciphertext when the parameter set supports bootstrapping.
How CKKS fits the pipeline
CKKS is user-facing, but it is not separate from the polynomial compiler. A ciphertext addition builds two polynomial additions, one for a and one for b. A ciphertext multiplication builds polynomial products, relinearization, and rescale work. Rotation builds automorphism and key-switching work. All of those operations eventually enter the same IR, scheduling, memory planning, backend lowering, and execution pipeline as raw Poly code.
This is why CKKS and JIT compose naturally. A jitted function can accept a Ciphertext, Plaintext, list of ciphertexts, or dataclass that contains ciphertexts. JIT keys and swaps the contained polys, not the outer Python object identity.
Where to go next
Read the JIT guide after this page if you want to run the same CKKS computation many times. Return to the internals overview when you want to understand how the lazy polynomial graphs become scheduled kernels.
Future CKKS docs should split into separate pages for:
- parameter selection and level schedules
- encoding, scale, and approximation error
- key generation and key material
- arithmetic patterns such as dot products, rotations, and bootstrapping
- serialization and public/secret context boundaries