Skip to main content

HomOps Reference

HomSquare

Square an encrypted value element by element.

SqPolynomials
RunsServer
Changes shapeNo
Changes scaleYes - scale squares
Levels spent1 by default, 0 with with_modswitch=False
RotatesNo
KeysSquare key

What it does

HomSquare computes for every element of a ciphertext. It is a composite wrapper around HomMul: it creates HomMul(x, x) for its one encrypted input.
It is not a registered OP_TYPE.Square leaf. When serialized, it becomes a Mul operation whose ciphertext inputs are the same value.
Squaring is the standard nonlinearity in encrypted neural networks: a quadratic activation between linear layers, or one step of repeated squaring when sharpening a comparison (the exact-search pattern). Since it is a ciphertext × ciphertext multiply, it needs the square key and its scale cost is the full one: the scale squares.

Implicit HomSquare: just write x ** 2

When you raise an encrypted value to the power of two, the compiler infers a HomSquare behind the scenes:
pipeline.pyPYTHON
y = x ** 2        # HomSquare inferred
result = x * y    # HomMul inferred (ciphertext × ciphertext)
The wrapper forwards its arguments to HomMul. Write HomSquare(...) explicitly when you need a HomMul setting, for example with_modswitch or rows_budget.

Signature

HomSquare(
    axis_sum=None,           # forwarded to HomMul; rarely used for square
    keep_axis=False,
    with_modswitch=True,
    rows_budget=None,
)
HomSquare takes no weights, so there is no set_data call. Its effective parameters are forwarded to HomMul.

Parameters

ParameterTypeDefaultDescription
axis_sumintNoneForwarded to HomMul. Sum the product along an axis; usually unnecessary for a square.
keep_axisboolFalseForwarded to HomMul. Keep an axis summed by axis_sum as size 1.
with_modswitchboolTrueForwarded to HomMul. Run a mod-switch after the square: rescale the result and spend one level. The default is True.
rows_budgetsequenceNoneRestricts which levels the mod-switch may spend, by absolute row index. Only relevant when with_modswitch=True. See rows_budget.
Rules that depend on the value
SettingRuleIf you break it
with_modswitchAt least one level must still be droppable when the mod-switch runs.Compilation fails.
rows_budgetAt least one listed row must still be droppable.Compilation fails if none are eligible.

Requirements

  • Square key. Squaring is a ciphertext multiply, so relinearization runs after it; any pipeline containing a HomSquare needs the square key (generated for you during key generation).
  • Level headroom. The scale squares; plan the chain so the growth can be paid down, here or soon after.

Shape effect - no

The output shape equals the input shape; squaring touches values, not layout.

Scale effect - yes

Rule: the output scale is the input scale squared - the same growth as multiplying two inputs at equal scale.
StageEffect on scale
After the squarescale_out = scale_in²
with_modswitch=TrueThen divided by the dropped prime - spending one level.
pt_shape: (128,)pt_scale: 2^30
HomSquare
pt_shape: (128,)pt_scale: 2^60
How scale moves through a pipeline is on Ciphertext state - Scale.

Level budget

ConfigurationLevels spent
with_modswitch=True (default)1 - drops one prime from the chain
with_modswitch=False0 - scale keeps climbing until you rescale
Deep multiply chains usually rescale at every square:
pipeline.pyPYTHON
# Repeated squaring with rescale (exact-search pattern)
*(HomSquare(with_modswitch=True) for _ in range(NUM_SQUARINGS))
How primes are organized in the chain is explained on Level budget - The chain.

Keys

Square key (relinearization key). Squaring produces the same oversized intermediate as any ciphertext multiply, and the square key reduces it back to a normal ciphertext. It is generated for you during key generation. No rotation key.

Example

Quadratic activation in an MNIST classifier

pipeline.pyPYTHON
from lattica_build.operators import HomLinear, HomSquare, HomReshape
from lattica_build.base_classes.hom_pipeline import HomomorphicPipeline
from lattica_build.operators.composite.sequential import SequentialHomOp

pipeline = HomomorphicPipeline(
    client_pre=[HomReshape((28 * 28,))],
    hom=SequentialHomOp(
        HomLinear((50, 28 * 28), bias=False, with_modswitch=False),
        HomSquare(with_modswitch=False),
        HomLinear((10, 50), bias=False, with_modswitch=False),
    ),
    input_shape=(1, 28, 28),
)

pipeline.set_data(0, fc1_weight)
pipeline.set_data(2, fc2_weight)
Shape and scale through the chain:
Stagept_shapept_scale (starting 2^30)
Input, after reshape(784,)2^30
After HomLinear(50,)2^30
After HomSquare(50,)2^60
After HomLinear(10,)grows again per its weights

See also