Skip to main content

HomOps Reference

HomRingSwitch

Lift a query encrypted in a small ring into the large ring the rest of the pipeline uses.

RwFHE
RunsServer
Changes shapeYes - packing axis becomes the full ring slots (k copies)
Changes scaleNo - inner bootstrap targets the input scale
Levels spentRefresh - restarts from a freshly bootstrapped chain
RotatesYes - inner bootstrap rotations
KeysSquare + rotation + ring-switch key

What it does

Ring switching lets a client encrypt its query in a small ring and have the server lift that ciphertext into the large ring the rest of the computation needs.
The cost of an FHE ciphertext scales with the ring dimension. A query encrypted directly in the computation's ring is large to produce and upload; encrypted in a sub-ring it is dramatically smaller. Ring switching moves the cost of getting into the large ring from the client to the server.
The operator takes a ciphertext in a sub-ring of dimension n' = n / k (holding n'/2 slots) and returns a ciphertext in the full ring of dimension n. The underlying plaintext becomes k consecutive copies of the input plaintext, filling the full ring's slots. Downstream operations see those copies; the plaintext is replicated, not padded.
Concrete example. In the encrypted-search demo, the client encrypts its query in a ring of dimension 2¹¹ and the server searches in a ring of dimension 2¹⁴. The uploaded query ciphertext is 32 KB. Ciphertext size is 2 × n × (number of moduli) × 8 bytes, so the same query encrypted directly in the search ring would be well over an order of magnitude larger.

How it works

Three stages, applied server-side:
  • Embed. The sub-ring ciphertext's coefficients are expanded into the full ring, replicating the plaintext k times across the slots.
  • Key switch. The result is re-keyed from the client's sub-ring secret key onto the full-ring secret key, using a dedicated ring switch key supplied with the client's evaluation keys.
  • Bootstrap. The ciphertext arrives at the smallest modulus level, so it is bootstrapped to restore a usable modulus chain.
Because stage 3 is a full bootstrap, treat the operator as costing roughly one bootstrap. Its output has the level budget of a freshly bootstrapped ciphertext, so the rest of the pipeline starts from a full chain. The inner bootstrap targets the input scale, so pt_scale is unchanged.

HomParams for a bootstrapping pipeline

HomRingSwitch runs an inner bootstrap, so the compiler enables bootstrapping automatically. You do not set a bootstrapping flag; it is not a constructor argument of this operator.
params.pyPYTHON · BOOT
from lattica_build.params import HomParams

LOG_N = 14          # search / computation ring
LOG_N_SUBRING = 11  # client encryption ring

hom_params = HomParams(
    n=2 ** LOG_N,
    full_q_list_precision=((60,), (60,), (60,), (60,)),
    pt_scale=2 ** 30,
    num_special_primes=6,
)
Variant, init-row, and special-prime settings are the same as for a standalone Bootstrap. The deployer derives the sub-ring encryption parameters automatically from this operator; there is no second HomParams set to configure.

Signature

HomRingSwitch(
    log_n_subring,              # required: log2 of the client encryption ring
    log_n_boot_subring=None,    # optional; defaults to log_n_subring
)
Both arguments may be passed; only log_n_subring is required. log_n_boot_subring defaults to log_n_subring when omitted. No set_data.

Parameters

ParameterTypeDefaultDescription
log_n_subringintrequiredLog₂ of the ring the client encrypts in. The input ciphertext holds 2^(log_n_subring - 1) slots. This is not Bootstrap's log_n_subring (sparse packing inside a refresh).
log_n_boot_subringint | NoneNoneLog₂ of the ring the cleartext is packed in — the period of the data within the plaintext. The input cleartext has 2^(log_n_boot_subring - 1) slots. When omitted, defaults to log_n_subring. Usually ≤ log_n_subring, since a small cleartext can be sparsely packed into a larger plaintext. Passed through to the inner bootstrap as its sparse-packing log_n_subring.
Typical call: HomRingSwitch(log_n_subring=11), or HomRingSwitch(log_n_subring=11, log_n_boot_subring=7) when the packed cleartext uses a smaller ring.

Requirements

  • First in the pipeline. Must be the first operation. Any operation before it would have to run in the sub-ring, and a pipeline carries a single context.
  • HomParams. The inner bootstrap causes the compiler to enable bootstrapping automatically; do not set a bootstrapping flag. It is not a constructor argument of HomRingSwitch. See HomParams for a bootstrapping pipeline.
  • Extra key material. The client generates a second (sub-ring) secret key and a ring switch key alongside its normal evaluation keys. The operator also requires the multiplication (square) key and the rotation keys the inner bootstrap needs.
  • Replication, not padding. Downstream operations see k copies of the input across the full ring's slots.

Shape effect - yes

The packing axis becomes the full ring's slot count (internal_n of the pipeline context). The plaintext is repeated k = n / 2^log_n_subring times along that axis.
StageSlots on the packing axis
Input (sub-ring)2log_n_subring - 1
After HomRingSwitchn / 2 (full ring), k copies of the input

Scale effect - no

Rule: the inner bootstrap targets the input scale, so pt_scale carries over unchanged.

Level budget

The switch bootstraps the sub-ring ciphertext, so level consumption restarts from the refreshed chain. Treat the operator as costing roughly one bootstrap. Usable depth after it is the depth you declared in full_q_list_precision, the same as after a standalone Bootstrap.

Keys

Sub-ring secret key. A second secret key, generated on the client, for the ring the query is encrypted in.
Ring switch key. Re-keys the embedded ciphertext from the sub-ring secret onto the full-ring secret. Generated with the evaluation keys.
Square key. Needed for multiply stages inside the inner bootstrap.
Rotation key. Needed for the inner bootstrap's slot↔coefficient transforms (and sparse-packing rotations driven by log_n_boot_subring).

Example

Encrypt the query in a sub-ring, lift on the server, then continue the search pipeline. HomRingSwitch is first:
pipeline.pyPYTHON
from lattica_build.params import HomParams
from lattica_build.base_classes.hom_pipeline import HomomorphicPipeline
from lattica_build.operators.composite.sequential import SequentialHomOp
from lattica_build.operators import HomRingSwitch, HomExpand

LOG_N = 14
LOG_N_SUBRING = 11

hom_params = HomParams(
    n=2 ** LOG_N,
    full_q_list_precision=((60,), (60,), (60,), (60,)),
    pt_scale=2 ** 30,
    num_special_primes=6,
)

pipeline = HomomorphicPipeline(
    hom=SequentialHomOp(
        HomRingSwitch(log_n_subring=11, log_n_boot_subring=7),  # must be first
        HomExpand(k=64, k_axis=1),
    ),
    input_shape=(2 ** (LOG_N_SUBRING - 1),),
)

See also

  • Bootstrap - the inner refresh; its log_n_subring is sparse packing, not the client encryption ring
  • Choosing parameters - HomParams; bootstrapping is inferred, no second parameter set for ring switching
  • Evaluation key - square, rotation, and extra ring-switch key material
  • Repeat · HomExpand - the exact-search opening that follows the lift