Fully Homomorphic Encryption (FHE) promises a revolutionary capability: performing arbitrary computations on encrypted data without ever decrypting it. This breakthrough enables secure cloud computing where sensitive data remains protected even during processing.
However, early FHE schemes faced a critical bottleneck. Consider a simple task like adding two encrypted vectors of length 1000. This would require 1000 separate homomorphic additions, each involving expensive operations on large polynomials. For real-world applications processing massive datasets, this element-by-element approach creates prohibitive computational overhead.
Modern FHE schemes like BGV, BFV and CKKS solve this challenge through an elegant mathematical insight: they can pack multiple plaintext values into a single ciphertext and perform operations on all packed values simultaneously.
Instead of encrypting individual numbers, these schemes:
Pack hundreds or thousands of values into "slots" within a single ciphertext
Perform homomorphic operations that affect all slots in parallel
Support sophisticated data movement operations like rotations and permutations
This Single Instruction, Multiple Data (SIMD) approach transforms FHE from a theoretical curiosity into a practical tool. A single homomorphic multiplication can now process an entire vector at once, delivering speedups of several orders of magnitude.
This post explores the mathematical foundations underlying these SIMD operations. We'll see how abstract concepts from algebra and number theory combine to create a powerful framework for parallel homomorphic computation. While the mathematics is sophisticated, understanding these foundations is crucial for implementing efficient FHE applications and pushing the boundaries of what's possible with encrypted computation.
Mathematical Foundations
This post assumes you're comfortable with the basics of groups and rings, things like the distributive laws and fundamental properties of rings with identity. If you've worked with elementary group theory, you should be in good shape.
Ideals
Definition. Let R be a ring. A subset I⊆R is called an ideal when:
(I,+) forms a subgroup of (R,+)
For any r∈R and a∈I, both r×a and a×r belong to I
That second condition is what makes ideals special, they "absorb" multiplication by any element in the ring. It's like having a mathematical black hole that pulls in anything you multiply with it.
Example. In the integers Z, consider the set nZ={nk∣k∈Z} of all multiples of n. This forms an ideal because multiplying any multiple of n by any integer gives you another multiple of n.
Principal Ideals
A principal ideal is the simplest kind, one generated by a single element. Given a∈R, we can create the ideal
(a)={r×a∣r∈R}.
This captures the smallest ideal that contains a. It's everything you can get by multiplying a by elements from the ring.
Building New Rings: Quotients
Given a ring R and an ideal I, we can construct a brand new ring called the quotient ring R/I.
The elements of R/I are cosets, think of them as equivalence classes a+I={a+i∣i∈I} for a∈R. We define operations on these cosets by:
Addition: (a+I)+(b+I)=(a+b)+I
Multiplication: (a+I)×(b+I)=(a×b)+I
Examples:
Z/nZ: This gives us the familiar "clock arithmetic" - integers modulo n with elements {0,1,2,…,n−1}.
Polynomial quotients: Taking R=F[X] and I=(f(X)), we get polynomials modulo f(X) - essentially polynomial arithmetic where we replace high powers using the relation f(X)=0.
Roots of Unity
In any ring R, an n-th root of unity is an element ζ such that ζn=1. But we're particularly interested in primitive n-th roots of unityζk=1 for all positive integers k<n.
Here's a key insight: ζnk is a primitive n-th root of unity if and only ifgcd(k,n)=1.
Why this works
Let ζn=e2πi/n. The n-th roots of unity are the n distinct powers ζn0,ζn1,…,ζnn−1, so ζn itself is primitive.
The key observation is that ζna=1 if and only if a is divisible by n. Here's why: write n=aq+r with 0≤r<a. Then ζnr=ζnn−aq=1. But since ζn is primitive, this only happens when r=0.
Now, if gcd(k,n)=d>1, then (ζnk)n/d=ζnk⋅n/d=ζnn⋅k/d=1, so ζnk is not primitive.
Conversely, if gcd(k,n)=1 and (ζnk)r=1, then n divides kr. Since n and k are coprime, we must have n divides r. This means the first positive power of ζnk that equals 1 is the n-th power.
Euler's totient function φ(n) counts exactly these "good" exponents - the positive integers k≤n that are coprime to n. So there are precisely φ(n) primitive n-th roots of unity.
Example. In the complex numbers, the primitive n-th roots of unity are exactly e2πik/n where gcd(k,n)=1.
Euler's totient functionφ(n) counts the positive integers up to n that are coprime to n:
φ(n)=∣{1≤k≤n∣gcd(k,n)=1}∣
A Concrete Example: 8th Roots of Unity in F32
Let's work out a specific case. We'll find the 8th roots of unity in the quadratic extension
F32=F3[X]/(X2+1)
How Many Roots Should We Expect?
In a finite field of size q, the multiplicative group Fq× has order q−1.
If n is coprime to q−1, then Fq contains only the trivial n-th root of unity: just 1.
If n divides q−1, the field contains exactly n distinct n-th roots of unity, forming the unique subgroup of that order.
Here, q=9, so q−1=8. Since 8 divides 8, we expect all eight 8th roots of unity to live inside F32×.
Finding All Eight Roots
We write elements of F32 in the form a+bx with a,b∈{0,1,2}, where x2=−1≡2(mod3).
Using Sage (or by hand if you're feeling ambitious):
R.<t>= PolynomialRing(GF(3))F.<x>= GF(3^2, modulus = t^2+1)roots8 =[z for z in F if z^8==1]print(roots8)
This gives us:
1,x+1,2x,2x+1,2,2x+2,x,x+2
All eight solutions to z8=1 in F32.
Which Ones Are Primitive?
An element is primitive when its order is exactly 8. Euler's totient tells us φ(8)=4, so exactly four of our eight roots should be primitive.
g = F.multiplicative_generator()primitive =[g^k for k in(1,3,5,7)]print(primitive)
This reveals:
{x+1,2x+1,2x+2,x+2}
These are our four primitive 8th roots. The remaining roots have orders 1, 2, or 4.
Cyclotomic Polynomials: Collecting the Primitives
The n-th cyclotomic polynomialΦn(X) is designed to capture exactly the primitive n-th roots of unity. Over the rationals:
Φn(X)=1≤k≤ngcd(k,n)=1∏(X−ζnk)
This polynomial has degree φ(n) - one factor for each primitive root.
Let's construct Φ8(X)
By definition:
Φ8(X)=1≤k≤8gcd(k,8)=1∏(X−ζ8k)=X4+1
This gives us a monic quartic (degree φ(8)=4) that's irreducible over Q.
Homomorphisms: Structure-Preserving Maps
Group Homomorphisms
A group homomorphism between groups (G,⋅) and (H,∗) is a function ϕ:G→H that respects the group operation:
ϕ(a⋅b)=ϕ(a)∗ϕ(b)
Two important subsets come with every homomorphism:
The kernel: ker(ϕ)={g∈G∣ϕ(g)=eH} (elements that map to the identity)
The image: im(ϕ)={ϕ(g)∣g∈G} (all possible outputs)
Both form subgroups of their respective groups.
Ring Homomorphisms
Similarly, a ring homomorphism between rings (R,+,×) and (S,⊕,⊙) preserves both operations:
ψ(a+b)=ψ(a)⊕ψ(b)
ψ(a×b)=ψ(a)⊙ψ(b)
ψ(1R)=1S (preserves multiplicative identity)
Isomorphisms: When Structures Are "The Same"
When a homomorphism is bijective, we call it an isomorphism. Isomorphic structures are essentially identical from an algebraic perspective - they have the same "shape."
An automorphism is an isomorphism from a structure to itself. The collection of all automorphisms of a group G forms a group under composition, denoted Aut(G).
The Chinese Remainder Theorem: A Powerful Decomposition
Let R be a commutative ring with unity, and let I1,I2,…,In be comaximal ideals (meaning Ii+Ij=R whenever i=j). Define I=I1∩I2∩⋯∩In. Then we have an isomorphism:
R/I≅(R/I1)×(R/I2)×⋯×(R/In)Why this works
We'll prove this by induction on the number of ideals.
Base Case (n=2): Let I,J be comaximal ideals with I+J=R. We want to show R/(I∩J)≅(R/I)×(R/J).
Consider the natural map:
φ:R→(R/I)×(R/J),x↦(x+I,x+J)
By the First Isomorphism Theorem, it suffices to show φ is surjective with kernel I∩J.
First Isomorphism Theorem for Rings: If φ:R⟶S is a surjective ring homomorphism with kernel ker(φ), then φ induces a natural ring isomorphism
φ:R/ker(φ)∼S,r+ker(φ)↦φ(r).
Kernel verification: We have
x∈kerφ⇔(x+I,x+J)=(0+I,0+J)⇔x∈I and x∈J⇔x∈I∩J
Surjectivity: Take any (x1+I,x2+J)∈(R/I)×(R/J). Since I+J=R, we can find y1∈I and y2∈J with y1+y2=1.
Define x=x1+y1(x2−x1)=x2−y2(x2−x1).
Then x≡x1(modI) and x≡x2(modJ), so φ(x)=(x1+I,x2+J).
Inductive Step: Suppose the theorem holds for n ideals. For n+1 comaximal ideals I1,…,In+1, observe that I1,…,In−1,In∩In+1 are pairwise comaximal.
By the inductive hypothesis:
R/I≅(R/I1)×⋯×(R/In−1)×(R/(In∩In+1))
Applying the base case to In and In+1:
R/(In∩In+1)≅R/In×R/In+1
Combining these gives the desired result.
SIMD Operations in Fully Homomorphic Encryption
Now we get to the exciting part - how all this abstract algebra enables powerful parallel computation in encrypted data.
Where Our Polynomials Live
We start with a quotient ring A=Z[X]/(Φm(X)), where Φm(X) is the m-th cyclotomic polynomial. This is our fundamental workspace.
In homomorphic encryption, we don't encrypt integers directly. Instead, we work over a prime field Fp=Z/pZ that serves as our plaintext space. Reducing all coefficients modulo p gives us the plaintext ring:
Ap=Fp[X]/(Φm(X))
The overline just reminds us we've done the modular reduction.
The Magic of Cyclotomic Factorization
Here's where number theory delivers something beautiful. As long as p doesn't divide m, the cyclotomic polynomial Φm(X) completely factors over Fp into irreducible pieces of equal degree:
Φm(X)=F1(X)F2(X)⋯Fn(X)modp
Each Fi is irreducible with degree d=ordm(p) - the multiplicative order of p modulo m.
Let's pick any irreducible factor, say F1(X), and define:
E=Zp[X]/(F1(X)),η=[XmodF1(X)]∈E
Since F1(X) is irreducible, E becomes a field with pd elements. Every element in E can be written as f(η) for some polynomial f(X). Notice that η is a root of F1(X), making it a primitive m-th root of unity.
Why the coset η=[X] is instantly a root of F1: Forming the quotient ring E=Zp[X]/(F1(X))
is nothing more than declaring inside the ring that the polynomial F1(X) now equals zero.
The projection π:Zp[X]→E sends every polynomial to its coset. Write η:=π(X)=X+(F1(X)).
This η is the “image of X’’.
Because F1(X) is in the ideal we factor by, its coset is 0:
π(F1(X))=0.
But π is a homomorphism, so π(F1(X))=F1(π(X))=F1(η). Hence F1(η)=0 in E. By construction, η behaves exactly like a root of F1.
The polynomial Φm(X) has φ(m) distinct roots in E, specifically ηj for each j in the unit group Zm∗. These roots distribute evenly among the irreducible factors, with each factor getting exactly d roots.
To understand the distribution, consider the subgroup:
H=⟨pˉ⟩⊂Zm∗,pˉ:=[pmodm]
This subgroup has order d and contains 1,pˉ,pˉ2,…,pˉd−1.
When we form the quotient group Zm∗/H, we get n=φ(m)/d distinct cosets:
kH={k⋅h:h∈H}⊂Zm∗
Choosing representatives k1,k2,…,kn from each coset, we can arrange things so each Fi(X) has exactly the d roots ηk where k∈kiH.
The Chinese Remainder Breakthrough
Now comes the payoff. We can establish isomorphisms for each factor:
This is the key insight: we can perform component-wise operations on vectors in En by doing ring operations in Ap!
If we have:
a∈Ap↔(α1,…,αn)∈Enb∈Ap↔(β1,…,βn)∈En
Then:
a+b↔(α1+β1,…,αn+βn)a⋅b↔(α1β1,…,αnβn)
Converting between the coefficient representation in Ap and the slot representation in En is computationally straightforward using the Number Theoretic Transform (NTT).
Cyclotomic Polynomial Example: NTT Implementation
Let's work through a concrete example with specific parameters:
Any polynomial in A3 has a unique representative:
f(X)=a0+a1X+a2X2+a3X3,ai∈F3.
This means we need 4 evaluations of our function to interpolate it using NTT. Because the Frobenius automorphism x↦x3 fixes each F3 component, we have:
f(η)=2=f(η3),f(η5)=1=f(η7)
Setting Up the Inverse NTT
Indeed ω:=η2=2x,
ω2=(2x)2=4x2=x2=2, ω4=22=1,
and ω=1, ω2=1, so ω is a primitive 4-th root of unity.
We need f evaluated at the odd powers of η:
{η1,η3,η5,η7}={ηω0,ηω1,ηω2,ηω3}.
Factor out wj and absorb it into the coefficients:
To understand how to move data between slots, we return to our ring A=Z[X]/(Φm(X)), with x representing the image of X in A.
For each j∈Zm∗, we can define:
θj:A→A,θj(f(x))=f(xj)
This is well-defined because if gcd(j,m)=1, then whenever ω is a primitive m-th root of unity, so is ωj. This means that if Φm(ω)=0, then Φm(ωj)=0 as well, giving us:
Φm(X)∣Φm(Xj)in Z[X]
These maps have a natural group structure: since (xj)k=xjk in A, we have:
θj∘θk=θjk
This gives us an injective group homomorphism:
Zm∗↪Aut(A),j↦θj
These θj maps are precisely the Galois automorphisms of our cyclotomic extension.
The Frobenius Map: A Special Automorphism
The Frobenius automorphism deserves special attention:
σ:E→E,f(η)↦f(ηp)
For every α∈E, we have σ(α)=αp. Importantly:
α∈Zp⇔σ(α)=α
Under our correspondence between Ap and En, if we let pˉ=[pmodm]∈Zm∗ and apply θpˉ:
θpˉ(f(x))→(σ(f(ηk1)),…,σ(f(ηkn)))∈En
This means θpˉ acts slot-wise as the Frobenius map on E - crucial for understanding rotations.
One-Dimensional Rotations: Making Data Move
Consider an element g∈Zm∗ such that 1,g,g2,…,gn−1 forms a complete set of coset representatives for H in Zm∗. This means gn must lie in H.
In the ideal case where gn=1, using representatives 1,g,…,gn−1, our slot isomorphism becomes:
f(x)∈Ap↔(f(η1),f(ηg),…,f(ηgn−1))∈En
When we apply θg:
θg(f(x))↔(f(ηg),f(ηg2),…,f(ηgn−1),f(η1))
Since the last entry wraps around to f(η1), the automorphism θg rotates the slots one position to the left! More generally, θge rotates left by e positions, while θg−e rotates right by e positions.
When gn=1, things get trickier. If gn=pˉs for some s∈{1,…,d−1}:
θg(f(x))↔(f(ηg),f(ηg2),…,f(ηgn−1),σs(f(η1)))
This gives a rotation, but the last slot gets perturbed by σs. This isn't a clean rotation unless the first slot contains a value in Zp, where σs acts trivially.
Clean Rotations Through Masking
Even when slots contain values outside Zp, we can achieve perfect rotations using a clever masking technique.
For a rotation by e∈{1,…,n−1} positions, we create masking elements:
This produces an element whose slots are exactly those of a rotated e places to the left, regardless of whether slot values lie outside Zp.
Multi-Dimensional Operations: The Hypercube Approach
For more complex data arrangements, we can organize our n slots as a multi-dimensional hypercube. Let n=n1n2⋯nℓ be our slot count factored into ℓ positive integers.
We choose generators g1,…,gℓ∈Zm∗ and arrange the coset representatives as:
g1e1g2e2⋯gℓeℓ,ei∈[ni]:={0,…,ni−1}
This creates an ℓ-dimensional hypercube with side-lengths (n1,…,nℓ), where every slot has coordinates (e1,…,eℓ).
Axis-wise Rotations Made Simple
For each dimension i, the Galois automorphism θgi shifts every hyper-column(e1,…,ei−1,∗,ei+1,…,eℓ) forward by one step in the i-th coordinate. Applying θgie shifts by e positions, while θgi−e shifts backwards.
Multi-Dimensional Masking
For true rotations without Frobenius disturbance, we use masking elements Me(i) that place ones in the first ni−e slots of each hyper-column and zeros elsewhere.
The formula for rotating left by e positions along the i-th axis is:
Me(i)θgie(a)+(1−Me(i))θgie−ni(a)
SIMD Operations in BGV with Lattigo
This example demonstrates slot-wise homomorphic addition using the Lattigo library’s BGV API. With BGV’s powerful SIMD capabilities, we can pack an entire vector of integers into a single ciphertext, perform encrypted computations on each vector entry in parallel, and then recover the results after decryption.
package main
import (
"fmt"
"log"
"math/rand"
"time"
"github.com/tuneinsight/lattigo/v6/examples"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
)
func main() {
paramDef := examples.BGVParamsN12QP109
params, err := bgv.NewParametersFromLiteral(paramDef)
if err != nil {
log.Fatalf("params error: %v", err)
}
kgen := bgv.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
pk := kgen.GenPublicKeyNew(sk)
encoder := bgv.NewEncoder(params)
encryptor := bgv.NewEncryptor(params, pk)
decryptor := bgv.NewDecryptor(params, sk)
evaluator := bgv.NewEvaluator(params, nil)
T := params.PlaintextModulus()
slots := params.MaxSlots()
rand.Seed(time.Now().UnixNano())
a := make([]uint64, slots)
b := make([]uint64, slots)
expected := make([]uint64, slots)
for i := 0; i < slots; i++ {
a[i] = uint64(rand.Int63n(int64(T)))
b[i] = uint64(rand.Int63n(int64(T)))
expected[i] = (a[i] + b[i]) % T
}
ptA := bgv.NewPlaintext(params, params.MaxLevel())
ptB := bgv.NewPlaintext(params, params.MaxLevel())
if err := encoder.Encode(a, ptA); err != nil {
log.Fatal(err)
}
if err := encoder.Encode(b, ptB); err != nil {
log.Fatal(err)
}
ctA, err := encryptor.EncryptNew(ptA)
if err != nil { log.Fatal(err) }
ctB, err := encryptor.EncryptNew(ptB)
if err != nil { log.Fatal(err) }
ctC, err := evaluator.AddNew(ctA, ctB)
if err != nil { log.Fatal(err) }
ptRes := decryptor.DecryptNew(ctC)
res := make([]uint64, slots)
if err := encoder.Decode(ptRes, res); err != nil {
log.Fatal(err)
}
fmt.Printf("i\t a\t b\t (a+b mod T)\t result\t match\n")
for i := 0; i < slots; i++ {
fmt.Printf("%d:\t%d\t%d\t%d\t\t%d\t%v\n",
i, a[i], b[i], expected[i], res[i], res[i] == expected[i])
}
}
Conclusion
The mathematical foundations explored in this post reveal how abstract algebra transforms fully homomorphic encryption from a theoretical curiosity into a practical tool for secure computation.
The key insights we've covered include:
Algebraic Structure: The plaintext ring Ap=Fp[X]/(Φm(X)) naturally decomposes into a product of extension fields En through cyclotomic factorization. This decomposition is what makes slot-based packing possible.
Galois Automorphisms: The maps θj:f(x)↦f(xj) provide the mechanism for data movement between slots. These automorphisms preserve the ring structure while permuting slot contents according to the multiplicative group Zm∗.
Rotation Techniques: Clean rotations require careful handling of the Frobenius map through masking operations. The hypercube organization extends this to multi-dimensional data arrangements, enabling sophisticated data movement patterns.
Practical Impact: These mathematical tools deliver speedups of several orders of magnitude compared to element-wise processing. A single homomorphic multiplication can now process an entire vector, making FHE viable for real-world applications in secure cloud computing, privacy-preserving machine learning, and confidential data analytics.
Acknowledgements
This post builds upon decades of research in both algebraic number theory and cryptography. Particular acknowledgment goes to Nigel Smart and Frederik Vercauteren for their foundational paper "Fully Homomorphic SIMD Operations", which established the theoretical framework for slot-based parallel computation in FHE schemes. Their work demonstrated how cyclotomic polynomial arithmetic could be leveraged to achieve true vectorization in homomorphic encryption.