mirror of
https://github.com/tuneinsight/lattigo.git
synced 2025-09-13 03:27:14 +00:00
230 lines
9.3 KiB
Go
230 lines
9.3 KiB
Go
// Package main implements an example showcasing the basics of the bootstrapping for fixed-point approximate arithmetic over the reals/complexes.
|
|
// The bootstrapping is a circuit that homomorphically re-encrypts a ciphertext at level zero to a ciphertext at a higher level, enabling further computations.
|
|
// Note that, unlike other bootstrappings (BGV/BFV/TFHE), the this bootstrapping does not reduce the error in the ciphertext, but only enables further computations.
|
|
// This example shows how to bootstrap a single ciphertext whose ring degree is the same as the one of the bootstrapping parameters.
|
|
// Use the flag -short to run the examples fast but with insecure parameters.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/tuneinsight/lattigo/v4/core/rlwe"
|
|
"github.com/tuneinsight/lattigo/v4/he/hefloat"
|
|
"github.com/tuneinsight/lattigo/v4/he/hefloat/bootstrapper"
|
|
"github.com/tuneinsight/lattigo/v4/ring"
|
|
"github.com/tuneinsight/lattigo/v4/utils"
|
|
"github.com/tuneinsight/lattigo/v4/utils/sampling"
|
|
)
|
|
|
|
var flagShort = flag.Bool("short", false, "run the example with a smaller and insecure ring degree.")
|
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
// Default LogN, which with the following defined parameters
|
|
// provides a security of 128-bit.
|
|
LogN := 16
|
|
|
|
if *flagShort {
|
|
LogN -= 3
|
|
}
|
|
|
|
//==============================
|
|
//=== 1) RESIDUAL PARAMETERS ===
|
|
//==============================
|
|
|
|
// First we must define the residual parameters.
|
|
// The residual parameters are the parameters used outside of the bootstrapping circuit.
|
|
// For this example, we have a LogN=16, logQ = 55 + 10*40 and logP = 3*61, so LogQP = 638.
|
|
// With LogN=16, LogQP=638 and H=192, these parameters achieve well over 128-bit of security.
|
|
params, err := hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
|
|
LogN: LogN, // Log2 of the ringdegree
|
|
LogQ: []int{55, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40}, // Log2 of the ciphertext prime moduli
|
|
LogP: []int{61, 61, 61}, // Log2 of the key-switch auxiliary prime moduli
|
|
LogDefaultScale: 40, // Log2 of the scale
|
|
Xs: ring.Ternary{H: 192},
|
|
})
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
//==========================================
|
|
//=== 2) BOOTSTRAPPING PARAMETERSLITERAL ===
|
|
//==========================================
|
|
|
|
// The bootstrapping circuit use its own Parameters which will be automatically
|
|
// instantiated given the residual parameters and the bootstrapping parameters.
|
|
|
|
// !WARNING! The bootstrapping parameters are not ensure to be 128-bit secure, it is the
|
|
// responsibility of the user to check that the meet the security requirement and tweak them if necessary.
|
|
|
|
// Note that the default bootstrapping parameters use LogN=16 and a ternary secret with H=192 non-zero coefficients
|
|
// which provides parameters which are at least 128-bit if their LogQP <= 1550.
|
|
|
|
// For this first example, we do not specify any circuit specific optional field in the bootstrapping parameters literal.
|
|
// Thus we expect the bootstrapping to give a precision of 27.25 bits with H=192 (and 23.8 with H=N/2)
|
|
// if the plaintext values are uniformly distributed in [-1, 1] for both the real and imaginary part.
|
|
// See `he/float/bootstrapper/bootstrapping/parameters_literal.go` for detailed information about the optional fields.
|
|
btpParametersLit := bootstrapper.ParametersLiteral{
|
|
// We specify LogN to ensure that both the residual parameters and the bootstrapping parameters
|
|
// have the same LogN. This is not required, but we want it for this example.
|
|
LogN: utils.Pointy(LogN),
|
|
|
|
// In this example we need manually specify the number of auxiliary primes (i.e. #Pi) used by the
|
|
// evaluation keys of the bootstrapping circuit, so that the size of LogQP meets the security target.
|
|
LogP: []int{61, 61, 61, 61},
|
|
|
|
// In this example we manually specify the bootstrapping parameters' secret distribution.
|
|
// This is not necessary, but we ensure here that they are the same as the residual parameters.
|
|
Xs: params.Xs(),
|
|
}
|
|
|
|
//===================================
|
|
//=== 3) BOOTSTRAPPING PARAMETERS ===
|
|
//===================================
|
|
|
|
// Now that the residual parameters and the bootstrapping parameters literals are defined, we can instantiate
|
|
// the bootstrapping parameters.
|
|
// The instantiated bootstrapping parameters store their own hefloat.Parameter, which are the parameters of the
|
|
// ring used by the bootstrapping circuit.
|
|
// The bootstrapping parameters are a wrapper of hefloat.Parameters, with additional information.
|
|
// They therefore has the same API as the hefloat.Parameters and we can use this API to print some information.
|
|
btpParams, err := bootstrapper.NewParametersFromLiteral(params, btpParametersLit)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if *flagShort {
|
|
// Corrects the message ratio Q0/|m(X)| to take into account the smaller number of slots and keep the same precision
|
|
btpParams.Mod1ParametersLiteral.LogMessageRatio += 16 - params.LogN()
|
|
}
|
|
|
|
// We print some information about the residual parameters.
|
|
fmt.Printf("Residual parameters: logN=%d, logSlots=%d, H=%d, sigma=%f, logQP=%f, levels=%d, scale=2^%d\n",
|
|
params.LogN(),
|
|
params.LogMaxSlots(),
|
|
params.XsHammingWeight(),
|
|
params.Xe(), params.LogQP(),
|
|
params.MaxLevel(),
|
|
params.LogDefaultScale())
|
|
|
|
// And some information about the bootstrapping parameters.
|
|
// We can notably check that the LogQP of the bootstrapping parameters is smaller than 1550, which ensures
|
|
// 128-bit of security as explained above.
|
|
fmt.Printf("Bootstrapping parameters: logN=%d, logSlots=%d, H(%d; %d), sigma=%f, logQP=%f, levels=%d, scale=2^%d\n",
|
|
btpParams.LogN(),
|
|
btpParams.LogMaxSlots(),
|
|
btpParams.XsHammingWeight(),
|
|
btpParams.EphemeralSecretWeight,
|
|
btpParams.Xe(),
|
|
btpParams.LogQP(),
|
|
btpParams.QCount(),
|
|
btpParams.LogDefaultScale())
|
|
|
|
//===========================
|
|
//=== 4) KEYGEN & ENCRYPT ===
|
|
//===========================
|
|
|
|
// Now that both the residual and bootstrapping parameters are instantiated, we can
|
|
// instantiate the usual necessary object to encode, encrypt and decrypt.
|
|
|
|
// Scheme context and keys
|
|
kgen := rlwe.NewKeyGenerator(params)
|
|
|
|
sk, pk := kgen.GenKeyPairNew()
|
|
|
|
encoder := hefloat.NewEncoder(params)
|
|
decryptor := rlwe.NewDecryptor(params, sk)
|
|
encryptor := rlwe.NewEncryptor(params, pk)
|
|
|
|
fmt.Println()
|
|
fmt.Println("Generating bootstrapping keys...")
|
|
evk, _, err := btpParams.GenBootstrappingKeys(sk)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println("Done")
|
|
|
|
//========================
|
|
//=== 5) BOOTSTRAPPING ===
|
|
//========================
|
|
|
|
// Instantiates the bootstrapper
|
|
var btp *bootstrapper.Bootstrapper
|
|
if btp, err = bootstrapper.NewBootstrapper(btpParams, evk); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Generate a random plaintext with values uniformely distributed in [-1, 1] for the real and imaginary part.
|
|
valuesWant := make([]complex128, params.MaxSlots())
|
|
for i := range valuesWant {
|
|
valuesWant[i] = sampling.RandComplex128(-1, 1)
|
|
}
|
|
|
|
// We encrypt at level 0
|
|
plaintext := hefloat.NewPlaintext(params, 0)
|
|
if err := encoder.Encode(valuesWant, plaintext); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Encrypt
|
|
ciphertext1, err := encryptor.EncryptNew(plaintext)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Decrypt, print and compare with the plaintext values
|
|
fmt.Println()
|
|
fmt.Println("Precision of values vs. ciphertext")
|
|
valuesTest1 := printDebug(params, ciphertext1, valuesWant, decryptor, encoder)
|
|
|
|
// Bootstrap the ciphertext (homomorphic re-encryption)
|
|
// It takes a ciphertext at level 0 (if not at level 0, then it will reduce it to level 0)
|
|
// and returns a ciphertext with the max level of `floatParamsResidualLit`.
|
|
// CAUTION: the scale of the ciphertext MUST be equal (or very close) to params.DefaultScale()
|
|
// To equalize the scale, the function evaluator.SetScale(ciphertext, parameters.DefaultScale()) can be used at the expense of one level.
|
|
// If the ciphertext is is at level one or greater when given to the bootstrapper, this equalization is automatically done.
|
|
fmt.Println("Bootstrapping...")
|
|
ciphertext2, err := btp.Bootstrap(ciphertext1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println("Done")
|
|
|
|
//==================
|
|
//=== 6) DECRYPT ===
|
|
//==================
|
|
|
|
// Decrypt, print and compare with the plaintext values
|
|
fmt.Println()
|
|
fmt.Println("Precision of ciphertext vs. Bootstrap(ciphertext)")
|
|
printDebug(params, ciphertext2, valuesTest1, decryptor, encoder)
|
|
}
|
|
|
|
func printDebug(params hefloat.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []complex128, decryptor *rlwe.Decryptor, encoder *hefloat.Encoder) (valuesTest []complex128) {
|
|
|
|
valuesTest = make([]complex128, ciphertext.Slots())
|
|
|
|
if err := encoder.Decode(decryptor.DecryptNew(ciphertext), valuesTest); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Printf("Level: %d (logQ = %d)\n", ciphertext.Level(), params.LogQLvl(ciphertext.Level()))
|
|
|
|
fmt.Printf("Scale: 2^%f\n", math.Log2(ciphertext.Scale.Float64()))
|
|
fmt.Printf("ValuesTest: %6.10f %6.10f %6.10f %6.10f...\n", valuesTest[0], valuesTest[1], valuesTest[2], valuesTest[3])
|
|
fmt.Printf("ValuesWant: %6.10f %6.10f %6.10f %6.10f...\n", valuesWant[0], valuesWant[1], valuesWant[2], valuesWant[3])
|
|
|
|
precStats := hefloat.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
|
|
|
|
fmt.Println(precStats.String())
|
|
fmt.Println()
|
|
|
|
return
|
|
}
|