Usage Examples
Practical examples and tutorials for implementing lattice-based cryptography
Usage Examples
Comprehensive collection of practical examples demonstrating real-world applications of lattice-based cryptography, from basic operations to complex protocols.
Getting Started
Installation
# Install the Lattice library
go get github.com/luxfi/lattice/v6
# Import required packages
import (
"github.com/luxfi/lattice/v6/core/rlwe"
"github.com/luxfi/lattice/v6/schemes/ckks"
"github.com/luxfi/lattice/v6/schemes/bgv"
"github.com/luxfi/lattice/v6/multiparty"
)Basic Setup
package main
import (
"fmt"
"log"
"github.com/luxfi/lattice/v6/core/rlwe"
"github.com/luxfi/lattice/v6/schemes/ckks"
)
func main() {
// Initialize parameters for 128-bit security
params, err := ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
LogN: 14,
LogQ: []int{50, 40, 40, 40},
LogP: []int{60},
LogDefaultScale: 40,
Sigma: 3.2,
},
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Ring dimension: %d\n", params.N())
fmt.Printf("Ciphertext levels: %d\n", params.MaxLevel())
fmt.Printf("Precision: %d bits\n", params.LogDefaultScale())
}Basic Homomorphic Operations
Arithmetic on Encrypted Numbers
func encryptedArithmetic() {
// Setup
params, _ := ckks.NewParametersFromLiteral(ckks.PN14QP438)
kgen := rlwe.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
pk := kgen.GenPublicKeyNew(sk)
rlk := kgen.GenRelinearizationKeyNew(sk)
evk := rlwe.NewMemEvaluationKeySet(rlk)
encoder := ckks.NewEncoder(params)
encryptor := rlwe.NewEncryptor(params, pk)
decryptor := rlwe.NewDecryptor(params, sk)
evaluator := ckks.NewEvaluator(params, evk)
// Prepare data
x := []complex128{1.5, 2.5, 3.5, 4.5}
y := []complex128{0.5, 1.5, 2.5, 3.5}
// Encode and encrypt
ptX := ckks.NewPlaintext(params, params.MaxLevel())
ptY := ckks.NewPlaintext(params, params.MaxLevel())
encoder.Encode(x, ptX)
encoder.Encode(y, ptY)
ctX, _ := encryptor.EncryptNew(ptX)
ctY, _ := encryptor.EncryptNew(ptY)
// Homomorphic operations
// Addition: z = x + y
ctSum, _ := evaluator.AddNew(ctX, ctY)
// Multiplication: z = x * y
ctProd, _ := evaluator.MulRelinNew(ctX, ctY)
evaluator.Rescale(ctProd, ctProd)
// Square: z = x^2
ctSquare, _ := evaluator.MulRelinNew(ctX, ctX)
evaluator.Rescale(ctSquare, ctSquare)
// Decrypt and decode results
ptSum := decryptor.DecryptNew(ctSum)
ptProd := decryptor.DecryptNew(ctProd)
ptSquare := decryptor.DecryptNew(ctSquare)
resSum := encoder.Decode(ptSum, params.LogSlots())
resProd := encoder.Decode(ptProd, params.LogSlots())
resSquare := encoder.Decode(ptSquare, params.LogSlots())
fmt.Println("x + y =", resSum[:4])
fmt.Println("x * y =", resProd[:4])
fmt.Println("x^2 =", resSquare[:4])
}Polynomial Evaluation
func evaluatePolynomial() {
params, _ := ckks.NewParametersFromLiteral(ckks.PN14QP438)
// ... (setup as before)
// Evaluate f(x) = 3x^3 - 2x^2 + x - 5
coefficients := []complex128{-5, 1, -2, 3}
// Input values
x := []complex128{1.0, 2.0, 3.0, 4.0}
ptX := ckks.NewPlaintext(params, params.MaxLevel())
encoder.Encode(x, ptX)
ctX, _ := encryptor.EncryptNew(ptX)
// Horner's method: f(x) = -5 + x(1 + x(-2 + x(3)))
result := ckks.NewCiphertext(params, 1, params.MaxLevel())
// Start with highest coefficient
encoder.Encode([]complex128{3, 3, 3, 3}, result.Plaintext())
encryptor.EncryptZero(result)
evaluator.Add(result, coefficients[3], result)
for i := 2; i >= 0; i-- {
// result = result * x + coefficients[i]
evaluator.MulRelin(result, ctX, result)
evaluator.Rescale(result, result)
// Add constant
constant := make([]complex128, params.Slots())
for j := range constant {
constant[j] = coefficients[i]
}
ptConst := ckks.NewPlaintext(params, result.Level())
encoder.EncodeAtLevelNew(constant, result.Level(), ptConst)
evaluator.Add(result, ptConst, result)
}
// Decrypt result
ptResult := decryptor.DecryptNew(result)
values := encoder.Decode(ptResult, params.LogSlots())
fmt.Println("f(x) =", values[:4])
// Expected: f(1)=-3, f(2)=9, f(3)=49, f(4)=135
}Machine Learning Applications
Encrypted Linear Regression
type EncryptedLinearRegression struct {
params ckks.Parameters
evaluator ckks.Evaluator
encoder ckks.Encoder
}
func (lr *EncryptedLinearRegression) Predict(
ctWeights *ckks.Ciphertext,
ctFeatures []*ckks.Ciphertext,
) (*ckks.Ciphertext, error) {
// Compute y = w0 + w1*x1 + w2*x2 + ... + wn*xn
if len(ctFeatures) == 0 {
return nil, errors.New("no features provided")
}
// Initialize with bias (first weight)
result := ckks.NewCiphertext(lr.params, 1, ctWeights.Level())
lr.evaluator.Mul(ctWeights, ctFeatures[0], result)
// Add weighted features
for i := 1; i < len(ctFeatures); i++ {
temp := ckks.NewCiphertext(lr.params, 1, ctWeights.Level())
// Rotate weights to align with next feature
rotatedWeights := lr.evaluator.RotateNew(ctWeights, i)
// Multiply and add
lr.evaluator.Mul(rotatedWeights, ctFeatures[i], temp)
lr.evaluator.Add(result, temp, result)
}
// Rescale for precision management
lr.evaluator.Rescale(result, result)
return result, nil
}
// Training encrypted model using gradient descent
func (lr *EncryptedLinearRegression) TrainStep(
ctWeights *ckks.Ciphertext,
ctFeatures []*ckks.Ciphertext,
ctLabels *ckks.Ciphertext,
learningRate float64,
) (*ckks.Ciphertext, error) {
// Predict with current weights
predictions, _ := lr.Predict(ctWeights, ctFeatures)
// Compute error: error = predictions - labels
error := lr.evaluator.SubNew(predictions, ctLabels)
// Compute gradients
gradients := make([]*ckks.Ciphertext, len(ctFeatures))
for i, feature := range ctFeatures {
// gradient[i] = mean(error * feature[i])
gradients[i] = lr.evaluator.MulNew(error, feature)
lr.evaluator.Rescale(gradients[i], gradients[i])
}
// Update weights: w = w - learningRate * gradient
ptLR := ckks.NewPlaintext(lr.params, ctWeights.Level())
lr.encoder.Encode([]complex128{complex(learningRate, 0)}, ptLR)
for i, grad := range gradients {
scaledGrad := lr.evaluator.MulNew(grad, ptLR)
lr.evaluator.Rescale(scaledGrad, scaledGrad)
// Update specific weight
rotatedWeights := lr.evaluator.RotateNew(ctWeights, i)
lr.evaluator.Sub(rotatedWeights, scaledGrad, rotatedWeights)
// Rotate back
ctWeights = lr.evaluator.RotateNew(rotatedWeights, -i)
}
return ctWeights, nil
}Encrypted Neural Network Inference
type EncryptedNeuralNetwork struct {
layers []EncryptedLayer
}
type EncryptedLayer interface {
Forward(input *ckks.Ciphertext) (*ckks.Ciphertext, error)
}
// Fully connected layer
type EncryptedDenseLayer struct {
weights *ckks.Ciphertext
bias *ckks.Ciphertext
eval ckks.Evaluator
}
func (l *EncryptedDenseLayer) Forward(input *ckks.Ciphertext) (*ckks.Ciphertext, error) {
// Compute output = weights @ input + bias
output := l.eval.MulRelinNew(l.weights, input)
l.eval.Rescale(output, output)
l.eval.Add(output, l.bias, output)
return output, nil
}
// ReLU activation using polynomial approximation
type EncryptedReLU struct {
eval ckks.Evaluator
polynomial []complex128
}
func (r *EncryptedReLU) Forward(input *ckks.Ciphertext) (*ckks.Ciphertext, error) {
// Approximate ReLU with polynomial
// ReLU(x) ≈ 0.5x + 0.25x^2 + 0.125x^3 for x ∈ [-2, 2]
x2 := r.eval.MulRelinNew(input, input)
r.eval.Rescale(x2, x2)
x3 := r.eval.MulRelinNew(x2, input)
r.eval.Rescale(x3, x3)
// Combine terms
result := r.eval.MulNew(input, 0.5)
r.eval.Add(result, r.eval.MulNew(x2, 0.25), result)
r.eval.Add(result, r.eval.MulNew(x3, 0.125), result)
return result, nil
}
// Run inference
func (nn *EncryptedNeuralNetwork) Predict(input *ckks.Ciphertext) (*ckks.Ciphertext, error) {
current := input
for _, layer := range nn.layers {
next, err := layer.Forward(current)
if err != nil {
return nil, err
}
current = next
}
return current, nil
}Private Information Retrieval
Basic PIR Query
func privateDatabaseQuery() {
// Setup BGV for exact computation
params, _ := bgv.NewParametersFromLiteral(
bgv.ParametersLiteral{
LogN: 13,
LogQ: []int{60, 60, 60},
PlaintextModulus: 65537,
},
)
// Database with 1024 entries
database := make([]uint64, 1024)
for i := range database {
database[i] = uint64(i * i) // Example data
}
// Client wants to query index 42 privately
queryIndex := 42
// Create one-hot encoded query vector
query := make([]uint64, len(database))
query[queryIndex] = 1
// Encrypt query
encoder := bgv.NewEncoder(params)
encryptor := bgv.NewEncryptor(params, pk)
ptQuery := bgv.NewPlaintext(params)
encoder.Encode(query, ptQuery)
ctQuery, _ := encryptor.EncryptNew(ptQuery)
// Server computes response
// result = sum(database[i] * query[i]) for all i
evaluator := bgv.NewEvaluator(params, evk)
ctResult := bgv.NewCiphertext(params, 1, params.MaxLevel())
for i, value := range database {
if value != 0 {
// Rotate query to align with database entry
rotated := evaluator.RotateNew(ctQuery, i)
// Multiply by database value
evaluator.MulScalar(rotated, value, rotated)
// Add to result
evaluator.Add(ctResult, rotated, ctResult)
}
}
// Client decrypts result
ptResult := decryptor.DecryptNew(ctResult)
result := encoder.DecodeUint(ptResult)
fmt.Printf("Database[%d] = %d\n", queryIndex, result[0])
}Multiparty Computation
Threshold Decryption
func thresholdDecryption() {
params, _ := ckks.NewParametersFromLiteral(ckks.PN14QP438)
// Setup: 3 parties, threshold = 2
numParties := 3
threshold := 2
// Each party generates their secret key share
parties := make([]*Party, numParties)
for i := range parties {
parties[i] = &Party{
ID: i,
SK: rlwe.NewKeyGenerator(params).GenSecretKeyNew(),
}
}
// Threshold key generation protocol
tkg := multiparty.NewTKGProtocol(params)
// Each party creates their share
shares := make([]*multiparty.TKGShare, numParties)
for i, party := range parties {
shares[i] = tkg.GenShare(party.SK)
}
// Aggregate to get collective public key
collectivePK := tkg.AggregateShares(shares)
// Encrypt data with collective public key
encoder := ckks.NewEncoder(params)
encryptor := rlwe.NewEncryptor(params, collectivePK)
data := []complex128{1.23, 4.56, 7.89}
pt := ckks.NewPlaintext(params, params.MaxLevel())
encoder.Encode(data, pt)
ct, _ := encryptor.EncryptNew(pt)
// Threshold decryption with any 2 parties
pcks := multiparty.NewPCKSProtocol(params)
// Select subset of parties (meets threshold)
activeParties := parties[:threshold]
// Generate decryption shares
decShares := make([]*multiparty.PCKSShare, threshold)
for i, party := range activeParties {
decShares[i] = pcks.GenShare(party.SK, ct)
}
// Combine shares to decrypt
ptResult := pcks.AggregateShares(decShares, ct)
result := encoder.Decode(ptResult, params.LogSlots())
fmt.Println("Decrypted:", result[:3])
}Secure Multiparty Training
type SecureMLTraining struct {
params ckks.Parameters
parties []*MLParty
protocol *multiparty.Protocol
}
type MLParty struct {
ID int
SK *rlwe.SecretKey
DataSet [][]float64
LocalModel *ckks.Ciphertext
}
func (sml *SecureMLTraining) FederatedAveraging() (*ckks.Ciphertext, error) {
// Each party trains locally and encrypts their model
encryptedModels := make([]*ckks.Ciphertext, len(sml.parties))
for i, party := range sml.parties {
// Local training (in plaintext)
localWeights := party.TrainLocal()
// Encrypt local model
pt := ckks.NewPlaintext(sml.params, sml.params.MaxLevel())
encoder.Encode(localWeights, pt)
encryptedModels[i], _ = encryptor.EncryptNew(pt)
}
// Secure aggregation
globalModel := sml.SecureAggregate(encryptedModels)
// Collaborative decryption
return sml.CollectiveDecrypt(globalModel)
}
func (sml *SecureMLTraining) SecureAggregate(models []*ckks.Ciphertext) *ckks.Ciphertext {
// Average encrypted models
result := models[0].CopyNew()
for i := 1; i < len(models); i++ {
evaluator.Add(result, models[i], result)
}
// Divide by number of parties
scale := 1.0 / float64(len(models))
evaluator.MulScalar(result, scale, result)
evaluator.Rescale(result, result)
return result
}Financial Applications
Private Portfolio Analysis
type PrivatePortfolio struct {
holdings map[string]*ckks.Ciphertext // Encrypted quantities
evaluator ckks.Evaluator
}
func (p *PrivatePortfolio) CalculateValue(prices map[string]float64) *ckks.Ciphertext {
var totalValue *ckks.Ciphertext
for asset, encryptedQuantity := range p.holdings {
price := prices[asset]
// value = quantity * price
value := p.evaluator.MulScalarNew(encryptedQuantity, price)
p.evaluator.Rescale(value, value)
if totalValue == nil {
totalValue = value
} else {
p.evaluator.Add(totalValue, value, totalValue)
}
}
return totalValue
}
func (p *PrivatePortfolio) CalculateRisk(
correlationMatrix [][]float64,
volatilities []float64,
) *ckks.Ciphertext {
// Portfolio risk = sqrt(w^T * Σ * w)
// where Σ is covariance matrix
assets := make([]string, 0, len(p.holdings))
weights := make([]*ckks.Ciphertext, 0, len(p.holdings))
for asset, holding := range p.holdings {
assets = append(assets, asset)
weights = append(weights, holding)
}
// Compute weighted covariance
risk := ckks.NewCiphertext(p.evaluator.Parameters(), 1, weights[0].Level())
for i := range assets {
for j := range assets {
// cov[i,j] = correlation[i,j] * vol[i] * vol[j]
cov := correlationMatrix[i][j] * volatilities[i] * volatilities[j]
// risk += w[i] * w[j] * cov[i,j]
term := p.evaluator.MulNew(weights[i], weights[j])
p.evaluator.Rescale(term, term)
p.evaluator.MulScalar(term, cov, term)
p.evaluator.Add(risk, term, risk)
}
}
// Note: Square root would require polynomial approximation
return risk
}Advanced Techniques
Bootstrapping Example
func bootstrappingExample() {
// Parameters with bootstrapping support
params, _ := ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
LogN: 16,
LogQ: []int{
// Computation levels
60, 50, 50, 50, 50,
// Bootstrapping levels
60, 60, 60, 60, 60, 60, 60, 60,
},
LogP: []int{61, 61, 61},
LogDefaultScale: 50,
H: 192,
},
)
// Setup bootstrapper
btpParams := bootstrapping.ParametersLiteral{
LogN: params.LogN(),
LogSlots: params.LogSlots(),
H: 192,
Sigma: params.Sigma(),
}
bootstrapper, _ := bootstrapping.NewBootstrapper(params, btpParams, evk)
// Perform deep computation
ct := encryptInitialData()
for i := 0; i < 100; i++ {
// Complex computation
ct = evaluator.MulRelinNew(ct, ct)
evaluator.Rescale(ct, ct)
// Check if bootstrapping needed
if ct.Level() == 0 {
fmt.Printf("Bootstrapping at iteration %d\n", i)
ct, _ = bootstrapper.Bootstrap(ct)
}
}
// Decrypt final result
result := decrypt(ct)
fmt.Println("Result after 100 multiplications:", result)
}Custom Polynomial Evaluation
func evaluateCustomFunction() {
// Approximate sin(x) using Chebyshev polynomials
// sin(x) ≈ x - x³/6 + x⁵/120 - x⁷/5040 for x ∈ [-π, π]
coeffs := []float64{
0, // x⁰
1, // x¹
0, // x²
-1.0/6, // x³
0, // x⁴
1.0/120, // x⁵
0, // x⁶
-1.0/5040, // x⁷
}
// Create polynomial evaluator
polyEval := ckks.NewPolynomialEvaluator(params, evaluator)
// Input values
x := []complex128{0, 0.5, 1.0, 1.5}
ptX := encode(x)
ctX := encrypt(ptX)
// Evaluate polynomial
ctResult, _ := polyEval.Evaluate(ctX, coeffs)
// Compare with actual sin(x)
result := decrypt(ctResult)
fmt.Println("Approximate sin(x):", result)
fmt.Println("Actual sin(x):", []float64{
math.Sin(0), math.Sin(0.5), math.Sin(1.0), math.Sin(1.5),
})
}Performance Optimization Tips
Batching Operations
func efficientBatching() {
// Process multiple data items in single ciphertext
batchSize := params.Slots() // Use all available slots
// Pack data efficiently
data := make([]complex128, batchSize)
for i := 0; i < actualDataSize; i++ {
data[i] = actualData[i]
}
// Remaining slots stay zero
// Single encryption for entire batch
pt := encode(data)
ct := encrypt(pt)
// Operations apply to all slots simultaneously
ctSquared := evaluator.MulRelinNew(ct, ct)
evaluator.Rescale(ctSquared, ctSquared)
// Decrypt entire batch
result := decrypt(ctSquared)
// Use first actualDataSize elements
}Memory Management
func memoryEfficientComputation() {
// Reuse ciphertexts to reduce memory
ct1 := encrypt(data1)
ct2 := encrypt(data2)
// In-place operations
evaluator.Add(ct1, ct2, ct1) // Result overwrites ct1
evaluator.MulRelin(ct1, ct1, ct1) // Square in-place
evaluator.Rescale(ct1, ct1)
// Manual memory cleanup for large computations
runtime.GC()
// Use object pools for repeated allocations
ctPool := sync.Pool{
New: func() interface{} {
return ckks.NewCiphertext(params, 1, params.MaxLevel())
},
}
// Get from pool
temp := ctPool.Get().(*ckks.Ciphertext)
// Use temp...
// Return to pool
ctPool.Put(temp)
}Testing and Validation
Unit Testing Homomorphic Operations
func TestHomomorphicAddition(t *testing.T) {
// Setup
params, _ := ckks.NewParametersFromLiteral(testParams)
// ... initialize crypto components
// Test data
a := []complex128{1.0, 2.0, 3.0, 4.0}
b := []complex128{5.0, 6.0, 7.0, 8.0}
expected := []complex128{6.0, 8.0, 10.0, 12.0}
// Encrypt
ctA := encryptVector(a)
ctB := encryptVector(b)
// Homomorphic addition
ctSum := evaluator.AddNew(ctA, ctB)
// Decrypt and verify
result := decryptVector(ctSum)
for i := range expected {
if math.Abs(real(result[i] - expected[i])) > 0.01 {
t.Errorf("Mismatch at %d: got %v, want %v",
i, result[i], expected[i])
}
}
}Benchmarking
func BenchmarkHomomorphicMultiplication(b *testing.B) {
// Setup
params, _ := ckks.NewParametersFromLiteral(ckks.PN14QP438)
// ... initialize
ct1 := encrypt(randomData())
ct2 := encrypt(randomData())
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := evaluator.MulRelinNew(ct1, ct2)
evaluator.Rescale(result, result)
}
}Conclusion
These examples demonstrate the versatility of lattice-based cryptography for:
- Privacy-preserving computation: Process encrypted data without decryption
- Machine learning: Train and evaluate models on encrypted data
- Financial analysis: Perform calculations on sensitive financial data
- Multiparty protocols: Enable secure collaboration without sharing raw data
Key takeaways:
- Always manage noise levels and precision
- Use batching for efficiency
- Implement proper key management
- Test thoroughly with realistic data
- Monitor performance and optimize as needed