Lattice

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:

  1. Always manage noise levels and precision
  2. Use batching for efficiency
  3. Implement proper key management
  4. Test thoroughly with realistic data
  5. Monitor performance and optimize as needed