Skip to content

CKKS concepts

CKKS represents approximate real or complex values. Ren's CKKS layer handles encoding, encryption, arithmetic, key switching, levels, rescale, and optional bootstrapping on top of the polynomial engine. Read CKKS workflow first if you need the runnable path.

Objects

Object Role
CKKSParams Ring, levels, scales, key-switching primes, bootstrap settings, and runtime flags.
CKKSKeySet Device-resident secret, public, and evaluation keys.
CKKSContext Runtime view of a key set used by encode, encrypt, decrypt, rotations, and level transitions.
Plaintext Encoded slot vector backed by one Poly.
Ciphertext Encrypted value backed by two Poly components, a and b.

Use with ctx: around CKKS operations that need the active context. That includes encryption, decryption, level lookup, key switching, and many arithmetic alignment paths.

Levels

Ren uses an explicit level schedule. The level model is:

levels[level] = (active moduli, nominal scale)
bootstrap_output_level = level bootstrapping returns to (None without bootstrapping)
default_level = bootstrap_output_level, or highest_level without bootstrapping
highest_level = top level in the full schedule
highest_ring = ring_at(highest_level)
bootstrap_output_level = highest_level - boot_params.depth

Levels index active modulus bases. Level 0 is the bottom of the schedule. Every level is a contiguous, potentially non-prefix window of highest_ring, which is also the canonical Q basis for key material. Fresh encryptions start at the context's default_level, which defaults to params.default_level. Bootstrap evaluation consumes adjacent transitions from highest_level through bootstrap_output_level; its output level can differ from the fresh-encryption level.

Multiplication grows scale and noise. Rescale and level-transition work drops moduli and moves the ciphertext down the schedule. to_level(level) converts a plaintext or ciphertext to the destination ring and nominal scale when operands need alignment. It uses the quotient-ring truncation shortcut where applicable and otherwise rescales directly to the destination. Passing through intermediate levels explicitly can produce different coefficients because it introduces additional rounding. Bootstrapping uses support levels above the normal compute range when the parameter set enables it.

Scale

Scale is CKKS's fixed-point denominator. Encoding a slot value stores something close to value * scale in the polynomial representation, then decoding divides by the current scale to recover an approximate value.

Each level has a nominal scale. Multiplication multiplies the represented values, but it also grows the internal scale and noise. Rescale moves the ciphertext through the next scheduled modulus window so the decoded value remains approximately the same while the bookkeeping scale returns to the level schedule. A computation level and the separate bootstrap base may share the same physical ring but have different logical scales; scheduled ring lookup wins, while bootstrap code uses bootstrap_base_level.scale explicitly.

The main invariant to keep in mind is:

same represented value ~= polynomial coefficients / current scale

A CKKSTransition contains executable RescaleStep operations and derives its polynomial and message ratios from them. Each step retains its rounding boundary. Plaintext, ciphertext, and bootstrap preparation use the same operations to preserve that invariant. The polynomial ratio describes how rescale transforms coefficients; the message ratio describes how callers interpret the decoded value against the destination scale.

Message bounds

msg_bound is public operation metadata. Ren uses it for correctness and noise checks. Do not compute it from secret values.

Message bounds are public

Do not write msg_bound=max(abs(v) for v in secret_values). Choose a bound from the public input contract.

Keys

Fresh encryption needs encryption key material in the active context. Multiplication needs a relinearization key after ciphertext-ciphertext multiplication introduces an extra secret-power component. Rotation needs the matching rotation key. Conjugation needs a conjugation key.

CKKSKeySet.generate(params) creates a local key set with a secret key, public key, and relinearization key. Pass rotations= or conjugation=True to request Galois keys; bootstrapping adds its own requirements automatically. keygen(params) is a convenience wrapper for the same operation. Pass secret_key to either function to generate keys from existing secret material, or construct CKKSKeySet(...) directly when assembling existing key material without generating anything. Call keys.context() to create the runtime context; runtime-only policy such as track_plaintext_values and strict_noise_bounds is passed there rather than to CKKSParams.

Parameter sets

CKKSParams stores the concrete moduli, scales, and algorithm choices needed for computation. generate_prefix(...) chooses these from targets such as precision, depth, and prime widths. Those generator targets are not stored alongside the resulting schedule. Execution choices such as diagnostic tracking and the fresh-encryption level belong to CKKSContext.

BootstrappingParams describes the bootstrap algorithm and is passed as boot_params= to either generate_prefix(...) or from_levels(...). The generator chooses the bootstrap-input moduli and scale using bootstrap_level_bit_length and bootstrap_message_ratio, then stores them as params.bootstrap_base_level. With an explicit schedule, supply that base level yourself. Construction requires the bootstrap algorithm and its base together, and validates their compatibility with the schedule. params.bootstrap_message_ratio is derived from the concrete base moduli and scale.

CKKSParams.python_test() is fast and not secure. Use it for small examples and unit tests.

CKKSParams.default() and CKKSParams.high_precision_default() are larger presets. Treat them as starting points, not a substitute for reviewing the security, precision, depth, bootstrap, and deployment requirements of a real application.

CKKSParams.cheddar() is the cached public preset for Cheddar's fixed 40-bit modulus and level schedule, taken from bootparam_40.json. It uses Ren's bootstrapping implementation, with bootstrap output and the default fresh-encryption level at 13. Benchmarks and tests request keys.context(default_level=16) to preserve the Cheddar workload's starting level. They use the preset directly so its constants have one owner.