Noise growth in RLWE is a budget you spend once

Every homomorphic operation draws down the same account. A practical way to reason about depth before you write any code.

The mistake I made for a long time was treating noise as an implementation detail — something the library handles, something you tune at the end. It is not. The noise budget is the resource the entire computation is denominated in, and if you do not plan the spend, you will find out at decryption time that you overdrew.

The account

An RLWE ciphertext is a pair (a,b)(a, b) with b=as+e+Δmb = a \cdot s + e + \Delta m, where ee is small. You can decrypt correctly as long as

e<Δ2\|e\| < \frac{\Delta}{2}

Everything you do to the ciphertext grows e\|e\|. The budget is the gap between the current noise and that threshold, and it never refills — except by bootstrapping, which is a separate and much larger expense.

The exchange rate for each operation

Rough magnitudes, with nn the ring dimension and BB the decomposition base:

OperationEffect on noise
Add two ciphertextse1+e2\|e_1\| + \|e_2\| — additive, essentially free
Multiply by a plaintext scalar ttte\|t\| \cdot \|e\| — cheap if tt is small
Multiply by a plaintext polynomialnte\approx n \cdot \|t\|_\infty \cdot \|e\| — much worse
Ciphertext × ciphertextΔ(e1+e2)\approx \Delta \cdot (\|e_1\| + \|e_2\|) then relinearise
Automorphism + key switchadditive, but adds a key-switching term

The asymmetry between rows two and three is the one people trip over. Multiplying by a constant is nearly free; multiplying by a polynomial with nn nonzero coefficients costs a factor of nn, because every coefficient of the result is a sum of nn products. In a PIR context this is exactly the difference between "the selection vector is a constant" and "the selection vector is encrypted", and it is why query expansion is designed so carefully.

Planning before coding

Write the circuit down as a tree and label each edge with its noise multiplier before you touch a library. For a depth-dd multiplicative circuit the noise is roughly

ede0(nt)d\|e_d\| \approx \|e_0\| \cdot \big(n \cdot \|t\|_\infty\big)^{d}

so the depth you can afford is logarithmic in the budget. Two consequences follow immediately:

  • Rebalance the tree. A left-deep chain of dd multiplications and a balanced tree of the same dd leaves have very different depths. Balanced almost always wins.
  • Push plaintext multiplications to the leaves. Doing them early means their noise contribution gets multiplied by fewer subsequent operations.
       bad                     good
        ×                        ×
       / \                     /   \
      ×   c4                  ×     ×
     / \                     / \   / \
    ×   c3                  c1 c2 c3 c4
   / \
  c1  c2        depth 3              depth 2

The check I run first

Before implementing anything, I compute the budget by hand for the intended parameter set and compare it to the circuit's predicted growth, with a safety factor of at least 2102^{10}. If the margin is not there, no amount of implementation cleverness will save it — the parameters have to change, or the circuit does.

That check takes ten minutes and has saved me weeks.