├── LOGO.png ├── mktfhe ├── mktfhe.go ├── lwe.go ├── binary_decryptor.go ├── bootstrap_key.go ├── bootstrap_key_marshal.go ├── bootstrap_keygen.go ├── binary_encryptor_public.go ├── lwe_ops.go ├── glwe_enc.go ├── binary_encryptor.go ├── fft_glwe_enc.go ├── fft_glwe.go ├── params_list.go ├── glwe.go └── fft_glwe_conv.go ├── tfhe ├── tfhe.go ├── asm_decompose_stub_amd64.go ├── keyswitch_keygen.go ├── asm_decompose.go ├── fft_glwe_enc_public.go ├── binary_encoder.go ├── asm_decompose_amd64.go ├── glwe_enc_public.go ├── lwe_enc_public.go ├── binary_encryptor_public.go ├── keyswitch.go ├── lwe_ops.go ├── asm_decompose_amd64.s ├── bootstrap_key.go ├── encryptor_public.go ├── binary_encryptor.go ├── keyswitch_key.go ├── bootstrap_lut.go ├── lwe_enc.go ├── binary_tfhe_test.go ├── encryptor_key_marshal.go └── encryptor_key.go ├── xtfhe ├── xtfhe.go ├── bfv_params_list.go ├── fhew_test.go ├── sanitization_test.go ├── fhew_params_list.go ├── manylut_test.go ├── circuit_bootstrap_test.go ├── sanitization_params_list.go ├── bfv_test.go ├── manylut_params.go ├── circuit_bootstrap_key.go ├── circuit_bootstrap_params_list.go ├── fhew_enc.go ├── fhew_params.go ├── circuit_bootstrap_params.go ├── sanitization_sampler.go ├── manylut_params_list.go ├── manylut_evaluator.go ├── fhew_evaluator.go ├── sanitization_params.go ├── bfv_keygen.go └── fhew_bootstrap_keygen.go ├── math ├── csprng │ ├── csprng.go │ ├── gaussian_sampler_test.go │ ├── binary_sampler.go │ └── uniform_sampler.go ├── poly │ ├── asm_fft_stub_amd64.go │ ├── asm_fold_stub_amd64.go │ ├── asm_vec_cmplx_stub_amd64.go │ ├── asm_fft_test.go │ ├── poly_test.go │ ├── poly.go │ └── asm_vec_cmplx_test.go └── vec │ ├── asm_vec_stub_amd64.go │ └── asm_vec_test.go ├── internal └── asmgen │ ├── go.mod │ ├── go.sum │ ├── fft_butterfly.go │ ├── asmgen.go │ └── decompose.go ├── go.mod ├── go.sum ├── .github └── workflows │ └── ci.yml ├── SECURITY.md └── .gitignore /LOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sp301415/tfhe-go/HEAD/LOGO.png -------------------------------------------------------------------------------- /mktfhe/mktfhe.go: -------------------------------------------------------------------------------- 1 | // Package mktfhe implements multi-key variant of TFHE scheme. 2 | package mktfhe 3 | -------------------------------------------------------------------------------- /tfhe/tfhe.go: -------------------------------------------------------------------------------- 1 | // Package tfhe implements TFHE(Fully Homomorphic Encryption over the Torus) scheme. 2 | package tfhe 3 | -------------------------------------------------------------------------------- /xtfhe/xtfhe.go: -------------------------------------------------------------------------------- 1 | // Package xtfhe implements experimental or advanced functionalities of the TFHE scheme. 2 | package xtfhe 3 | -------------------------------------------------------------------------------- /math/csprng/csprng.go: -------------------------------------------------------------------------------- 1 | // Package csprng implements various samplers used throughout TFHE. 2 | // All samplers in this package are CSPRNG(Cryptographically Secure Pseudo Random Generator), 3 | // hence the name. 4 | package csprng 5 | -------------------------------------------------------------------------------- /internal/asmgen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sp301415/tfhe-go/internal/asmgen 2 | 3 | go 1.18 4 | 5 | require github.com/mmcloughlin/avo v0.6.0 6 | 7 | require ( 8 | golang.org/x/mod v0.20.0 // indirect 9 | golang.org/x/sync v0.8.0 // indirect 10 | golang.org/x/tools v0.24.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sp301415/tfhe-go 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.10.0 7 | golang.org/x/sys v0.30.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /xtfhe/bfv_params_list.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | var ( 6 | // ParamsBFVKeySwitchLogN11 is a gadget parameter set for keyswitching keys in BFV type operations 7 | // where N = 2^11 = 2048. 8 | ParamsBFVKeySwitchLogN11 = tfhe.GadgetParametersLiteral[uint64]{ 9 | Base: 1 << 9, 10 | Level: 4, 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /math/poly/asm_fft_stub_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by command: go run asmgen.go -fft -out ../../math/poly/asm_fft_amd64.s -stubs ../../math/poly/asm_fft_stub_amd64.go -pkg=poly. DO NOT EDIT. 2 | 3 | //go:build amd64 && !purego 4 | 5 | package poly 6 | 7 | //go:noescape 8 | func fwdFFTInPlaceAVX2(coeffs []float64, tw []complex128) 9 | 10 | //go:noescape 11 | func invFFTInPlaceAVX2(coeffs []float64, twInv []complex128, scale float64) 12 | -------------------------------------------------------------------------------- /tfhe/asm_decompose_stub_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by command: go run asmgen.go -decompose -out ../../tfhe/asm_decompose_amd64.s -stubs ../../tfhe/asm_decompose_stub_amd64.go -pkg=tfhe. DO NOT EDIT. 2 | 3 | //go:build amd64 && !purego 4 | 5 | package tfhe 6 | 7 | //go:noescape 8 | func decomposePolyToUint32AVX2(dcmpOut [][]uint32, p []uint32, base uint32, logBase uint32, logLastBaseQ uint32) 9 | 10 | //go:noescape 11 | func decomposePolyToUint64AVX2(dcmpOut [][]uint64, p []uint64, base uint64, logBase uint64, logLastBaseQ uint64) 12 | -------------------------------------------------------------------------------- /xtfhe/fhew_test.go: -------------------------------------------------------------------------------- 1 | package xtfhe_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sp301415/tfhe-go/xtfhe" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var ( 11 | fhewParams = xtfhe.ParamsFHEWBinary.Compile() 12 | fhewEnc = xtfhe.NewFHEWEncryptor(fhewParams) 13 | fhewEval = xtfhe.NewFHEWEvaluator(fhewParams, fhewEnc.GenEvalKeyParallel()) 14 | ) 15 | 16 | func TestFHEW(t *testing.T) { 17 | for _, msg := range []int{0, 1} { 18 | ct := fhewEnc.EncryptLWE(msg) 19 | ctOut := fhewEval.BootstrapFunc(ct, func(x int) int { return x ^ 1 }) 20 | assert.Equal(t, fhewEnc.DecryptLWE(ctOut), msg^1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/asmgen/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= 2 | github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= 3 | golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= 4 | golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 5 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 6 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 7 | golang.org/x/tools v0.24.1 h1:vxuHLTNS3Np5zrYoPRpcheASHX/7KiGo+8Y4ZM1J2O8= 8 | golang.org/x/tools v0.24.1/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 9 | -------------------------------------------------------------------------------- /xtfhe/sanitization_test.go: -------------------------------------------------------------------------------- 1 | package xtfhe_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sp301415/tfhe-go/tfhe" 7 | "github.com/sp301415/tfhe-go/xtfhe" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var ( 12 | sanitizationParams = xtfhe.ParamsSanitizeBinary.Compile() 13 | sanitizationEnc = tfhe.NewEncryptor(sanitizationParams.BaseParams()) 14 | sanitizationEval = xtfhe.NewSanitizer(sanitizationParams, sanitizationEnc.GenPublicKey(), sanitizationEnc.GenEvalKeyParallel()) 15 | ) 16 | 17 | func TestSanitization(t *testing.T) { 18 | for _, m := range []int{0, 1} { 19 | ct := sanitizationEnc.EncryptLWE(m) 20 | ctOut := sanitizationEval.SanitizeFunc(ct, func(i int) int { return i }) 21 | 22 | assert.Equal(t, sanitizationEnc.DecryptLWE(ctOut), m) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tfhe/keyswitch_keygen.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // GenLWEKeySwitchKey samples a new keyswitch key skIn -> LWEKey. 4 | func (e *Encryptor[T]) GenLWEKeySwitchKey(skIn LWESecretKey[T], gadgetParams GadgetParameters[T]) LWEKeySwitchKey[T] { 5 | ksk := NewLWEKeySwitchKey(e.Params, len(skIn.Value), gadgetParams) 6 | 7 | for i := 0; i < ksk.InputLWEDimension(); i++ { 8 | e.EncryptLevScalarTo(ksk.Value[i], skIn.Value[i]) 9 | } 10 | 11 | return ksk 12 | } 13 | 14 | // GenGLWEKeySwitchKey samples a new keyswitch key skIn -> GLWEKey. 15 | func (e *Encryptor[T]) GenGLWEKeySwitchKey(skIn GLWESecretKey[T], gadgetParams GadgetParameters[T]) GLWEKeySwitchKey[T] { 16 | ksk := NewGLWEKeySwitchKey(e.Params, len(skIn.Value), gadgetParams) 17 | 18 | for i := 0; i < ksk.InputGLWERank(); i++ { 19 | e.EncryptFFTGLevPolyTo(ksk.Value[i], skIn.Value[i]) 20 | } 21 | 22 | return ksk 23 | } 24 | -------------------------------------------------------------------------------- /xtfhe/fhew_params_list.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | var ( 6 | // ParamsFHEWBinary is a default parameter set for binary FHEW. 7 | ParamsFHEWBinary = FHEWParametersLiteral[uint64]{ 8 | BaseParams: tfhe.ParametersLiteral[uint64]{ 9 | LWEDimension: 447, 10 | GLWERank: 1, 11 | PolyRank: 1024, 12 | 13 | LWEStdDev: 0.000143051147460938, 14 | GLWEStdDev: 0.00000000393811205889363, 15 | 16 | MessageModulus: 1 << 1, 17 | 18 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 19 | Base: 1 << 10, 20 | Level: 3, 21 | }, 22 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 23 | Base: 1 << 3, 24 | Level: 4, 25 | }, 26 | 27 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 28 | }, 29 | 30 | SecretKeyStdDev: 0.00000000000000000017347234759768072, 31 | 32 | WindowSize: 10, 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /tfhe/asm_decompose.go: -------------------------------------------------------------------------------- 1 | //go:build !(amd64 && !purego) 2 | 3 | package tfhe 4 | 5 | import ( 6 | "github.com/sp301415/tfhe-go/math/num" 7 | "github.com/sp301415/tfhe-go/math/poly" 8 | ) 9 | 10 | // decomposePolyTo decomposes p with respect to gadgetParams and writes it to dcmpOut. 11 | func decomposePolyTo[T TorusInt](dcmpOut []poly.Poly[T], p poly.Poly[T], gadgetParams GadgetParameters[T]) { 12 | logLastBaseQ := gadgetParams.LogLastBaseQ() 13 | for i := 0; i < p.Rank(); i++ { 14 | c := num.DivRoundBits(p.Coeffs[i], logLastBaseQ) 15 | for j := gadgetParams.level - 1; j >= 1; j-- { 16 | dcmpOut[j].Coeffs[i] = c & (gadgetParams.base - 1) 17 | c >>= gadgetParams.logBase 18 | c += dcmpOut[j].Coeffs[i] >> (gadgetParams.logBase - 1) 19 | dcmpOut[j].Coeffs[i] -= (dcmpOut[j].Coeffs[i] & (gadgetParams.base >> 1)) << 1 20 | } 21 | dcmpOut[0].Coeffs[i] = c & (gadgetParams.base - 1) 22 | dcmpOut[0].Coeffs[i] -= (dcmpOut[0].Coeffs[i] & (gadgetParams.base >> 1)) << 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /math/poly/asm_fold_stub_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by command: go run asmgen.go -fold -out ../../math/poly/asm_fold_amd64.s -stubs ../../math/poly/asm_fold_stub_amd64.go -pkg=poly. DO NOT EDIT. 2 | 3 | //go:build amd64 && !purego 4 | 5 | package poly 6 | 7 | //go:noescape 8 | func foldPolyToUint32AVX2(fpOut []float64, p []uint32) 9 | 10 | //go:noescape 11 | func foldPolyToUint64AVX2(fpOut []float64, p []uint64) 12 | 13 | //go:noescape 14 | func floatModQInPlaceAVX2(coeffs []float64, q float64, qInv float64) 15 | 16 | //go:noescape 17 | func unfoldPolyToUint32AVX2(pOut []uint32, fp []float64) 18 | 19 | //go:noescape 20 | func unfoldPolyAddToUint32AVX2(pOut []uint32, fp []float64) 21 | 22 | //go:noescape 23 | func unfoldPolySubToUint32AVX2(pOut []uint32, fp []float64) 24 | 25 | //go:noescape 26 | func unfoldPolyToUint64AVX2(pOut []uint64, fp []float64) 27 | 28 | //go:noescape 29 | func unfoldPolyAddToUint64AVX2(pOut []uint64, fp []float64) 30 | 31 | //go:noescape 32 | func unfoldPolySubToUint64AVX2(pOut []uint64, fp []float64) 33 | -------------------------------------------------------------------------------- /xtfhe/manylut_test.go: -------------------------------------------------------------------------------- 1 | package xtfhe_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sp301415/tfhe-go/math/num" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | "github.com/sp301415/tfhe-go/xtfhe" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | manyLUTParams = xtfhe.ParamsUint2LUT4.Compile() 14 | manyLUTEnc = tfhe.NewEncryptor(manyLUTParams.BaseParams()) 15 | manyLUTEval = xtfhe.NewManyLUTEvaluator(manyLUTParams, manyLUTEnc.GenEvalKeyParallel()) 16 | ) 17 | 18 | func TestManyLUT(t *testing.T) { 19 | m := int(num.Sqrt(manyLUTParams.BaseParams().MessageModulus())) 20 | fs := make([]func(int) int, manyLUTParams.LUTCount()) 21 | for i := 0; i < manyLUTParams.LUTCount(); i++ { 22 | j := i 23 | fs[i] = func(x int) int { return 2*x + j } 24 | } 25 | 26 | ct := manyLUTEnc.EncryptLWE(m) 27 | ctOut := manyLUTEval.BootstrapFunc(ct, fs) 28 | 29 | for i := 0; i < manyLUTParams.LUTCount(); i++ { 30 | assert.Equal(t, manyLUTEnc.DecryptLWE(ctOut[i]), fs[i](m)%int(manyLUTParams.BaseParams().MessageModulus())) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 6 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 8 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /xtfhe/circuit_bootstrap_test.go: -------------------------------------------------------------------------------- 1 | package xtfhe_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sp301415/tfhe-go/math/vec" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | "github.com/sp301415/tfhe-go/xtfhe" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | cbParams = xtfhe.ParamsCircuitBootstrapMedium.Compile() 14 | cbEnc = tfhe.NewEncryptor(cbParams.Params()) 15 | cbKeyGen = xtfhe.NewCircuitBootstrapKeyGenerator(cbParams, cbEnc.SecretKey) 16 | cbEval = xtfhe.NewCircuitBootstrapper(cbParams, cbEnc.GenEvalKeyParallel(), cbKeyGen.GenCircuitBootstrapKey()) 17 | ) 18 | 19 | func TestCircuitBootstrap(t *testing.T) { 20 | msgGLWE := []int{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0} 21 | ctGLWE := cbEnc.EncryptGLWE(msgGLWE) 22 | 23 | for _, c := range []int{0, 1} { 24 | ctLWE := cbEnc.EncryptLWE(c) 25 | ctFFTGGSW := cbEval.CircuitBootstrap(ctLWE) 26 | ctGLWEOut := cbEval.ExternalProdGLWE(ctFFTGGSW, ctGLWE) 27 | 28 | msgGLWEOut := cbEnc.DecryptGLWE(ctGLWEOut)[:len(msgGLWE)] 29 | assert.Equal(t, msgGLWEOut, vec.ScalarMul(msgGLWE, c)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xtfhe/sanitization_params_list.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | var ( 6 | // ParamsSanitizeBinary is the default parameters for sanitizing binary TFHE ciphertexts. 7 | ParamsSanitizeBinary = SanitizationParametersLiteral[uint64]{ 8 | BaseParams: tfhe.ParametersLiteral[uint64]{ 9 | LWEDimension: 612, 10 | GLWERank: 1, 11 | PolyRank: 2048, 12 | 13 | BlockSize: 1, 14 | 15 | MessageModulus: 1 << 1, 16 | 17 | LWEStdDev: 0.00007905932600864546, 18 | GLWEStdDev: 0.0000000000000003472576015484159, 19 | 20 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 21 | Base: 1 << 11, 22 | Level: 3, 23 | }, 24 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 25 | Base: 1 << 3, 26 | Level: 4, 27 | }, 28 | 29 | BootstrapOrder: tfhe.OrderKeySwitchBlindRotate, 30 | }, 31 | 32 | RandSigma: 0.00000000000000008449279779330043, 33 | RandTau: 0.000000000570793820594853, 34 | LinEvalSigma: 0.000000000000000012106655611491527, 35 | LinEvalTau: 0.008864348118988643, 36 | } 37 | ) 38 | -------------------------------------------------------------------------------- /tfhe/fft_glwe_enc_public.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // EncryptFFTGLWE encodes and encrypts integer messages to FFTGLWE ciphertext. 4 | func (e *PublicEncryptor[T]) EncryptFFTGLWE(messages []int) FFTGLWECiphertext[T] { 5 | return e.EncryptFFTGLWEPlaintext(e.EncodeGLWE(messages)) 6 | } 7 | 8 | // EncryptFFTGLWETo encrypts and encrypts integer messages to FFTGLWE ciphertext and writes it to ctOut. 9 | func (e *PublicEncryptor[T]) EncryptFFTGLWETo(ctOut FFTGLWECiphertext[T], messages []int) { 10 | e.EncryptFFTGLWEPlaintextTo(ctOut, e.EncodeGLWE(messages)) 11 | } 12 | 13 | // EncryptFFTGLWEPlaintext encrypts GLWE plaintext to FFTGLWE ciphertext. 14 | func (e *PublicEncryptor[T]) EncryptFFTGLWEPlaintext(pt GLWEPlaintext[T]) FFTGLWECiphertext[T] { 15 | ctOut := NewFFTGLWECiphertext(e.Params) 16 | e.EncryptFFTGLWEPlaintextTo(ctOut, pt) 17 | return ctOut 18 | } 19 | 20 | // EncryptFFTGLWEPlaintextTo encrypts GLWE plaintext to FFTGLWE ciphertext and writes it to ctOut. 21 | func (e *PublicEncryptor[T]) EncryptFFTGLWEPlaintextTo(ctOut FFTGLWECiphertext[T], pt GLWEPlaintext[T]) { 22 | e.EncryptGLWEPlaintextTo(e.buf.ctGLWE, pt) 23 | e.FwdFFTGLWECiphertextTo(ctOut, e.buf.ctGLWE) 24 | } 25 | -------------------------------------------------------------------------------- /math/csprng/gaussian_sampler_test.go: -------------------------------------------------------------------------------- 1 | package csprng_test 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/sp301415/tfhe-go/math/csprng" 8 | "github.com/sp301415/tfhe-go/math/vec" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func meanStdDev(v []float64) (mean, stdDev float64) { 13 | sum := 0.0 14 | for _, x := range v { 15 | sum += x 16 | } 17 | 18 | mean = sum / float64(len(v)) 19 | 20 | variance := 0.0 21 | for _, x := range v { 22 | variance += (x - mean) * (x - mean) 23 | } 24 | stdDev = math.Sqrt(variance / float64(len(v))) 25 | 26 | return 27 | } 28 | 29 | func TestGaussianSampler(t *testing.T) { 30 | mean := 0.0 31 | sigma := math.Exp2(16) 32 | 33 | gs := csprng.NewGaussianSampler[int64]() 34 | samples := make([]int64, 1024) 35 | gs.SampleVecTo(samples, sigma) 36 | samplesFloat := vec.Cast[float64](samples) 37 | meanSample, stdDevSample := meanStdDev(samplesFloat) 38 | 39 | k := 3.29 // From the GLITCH test suite 40 | N := float64(len(samples)) 41 | meanBound := meanSample + k*stdDevSample/math.Sqrt(N) 42 | stdDevBound := stdDevSample + k*stdDevSample/math.Sqrt(2*(N-1)) 43 | 44 | assert.GreaterOrEqual(t, meanBound, mean) 45 | assert.GreaterOrEqual(t, stdDevBound, sigma) 46 | } 47 | -------------------------------------------------------------------------------- /tfhe/binary_encoder.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // BinaryEncoder encodes boolean messages to TFHE plaintexts. 4 | // Encoder is embedded in Encryptor and Evaluator, 5 | // so usually manual instantiation isn't needed. 6 | // 7 | // BinaryEncoder is safe for concurrent use. 8 | type BinaryEncoder[T TorusInt] struct { 9 | // Params is parameters for this Encoder. 10 | Params Parameters[T] 11 | // Encoder is a generic Encoder for this BinaryEncoder. 12 | Encoder *Encoder[T] 13 | } 14 | 15 | // NewBinaryEncoder returns a initialized BinaryEncoder with given parameters. 16 | func NewBinaryEncoder[T TorusInt](params Parameters[T]) *BinaryEncoder[T] { 17 | return &BinaryEncoder[T]{ 18 | Params: params, 19 | Encoder: NewEncoder(params), 20 | } 21 | } 22 | 23 | // EncodeLWEBool encodes boolean message to LWE plaintext. 24 | // 25 | // Note that this is different from calling EncodeLWE with 0 or 1. 26 | func (e *BinaryEncoder[T]) EncodeLWEBool(message bool) LWEPlaintext[T] { 27 | if message { 28 | return LWEPlaintext[T]{Value: 1 << (e.Params.logQ - 3)} 29 | } 30 | return LWEPlaintext[T]{Value: 7 << (e.Params.logQ - 3)} 31 | } 32 | 33 | // DecodeLWEBool decodes LWE plaintext to boolean message. 34 | func (e *BinaryEncoder[T]) DecodeLWEBool(pt LWEPlaintext[T]) bool { 35 | return pt.Value < (1 << (e.Params.logQ - 1)) 36 | } 37 | -------------------------------------------------------------------------------- /math/poly/asm_vec_cmplx_stub_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by command: go run asmgen.go -vec_cmplx -out ../../math/poly/asm_vec_cmplx_amd64.s -stubs ../../math/poly/asm_vec_cmplx_stub_amd64.go -pkg=poly. DO NOT EDIT. 2 | 3 | //go:build amd64 && !purego 4 | 5 | package poly 6 | 7 | //go:noescape 8 | func addCmplxToAVX2(vOut []float64, v0 []float64, v1 []float64) 9 | 10 | //go:noescape 11 | func subCmplxToAVX2(vOut []float64, v0 []float64, v1 []float64) 12 | 13 | //go:noescape 14 | func negCmplxToAVX2(vOut []float64, v []float64) 15 | 16 | //go:noescape 17 | func floatMulCmplxToAVX2(vOut []float64, v []float64, c float64) 18 | 19 | //go:noescape 20 | func floatMulAddCmplxToAVX2(vOut []float64, v []float64, c float64) 21 | 22 | //go:noescape 23 | func floatMulSubCmplxToAVX2(vOut []float64, v []float64, c float64) 24 | 25 | //go:noescape 26 | func cmplxMulCmplxToAVX2(vOut []float64, v []float64, c complex128) 27 | 28 | //go:noescape 29 | func cmplxMulAddCmplxToAVX2(vOut []float64, v []float64, c complex128) 30 | 31 | //go:noescape 32 | func cmplxMulSubCmplxToAVX2(vOut []float64, v []float64, c complex128) 33 | 34 | //go:noescape 35 | func mulCmplxToAVX2(vOut []float64, v0 []float64, v1 []float64) 36 | 37 | //go:noescape 38 | func mulAddCmplxToAVX2(vOut []float64, v0 []float64, v1 []float64) 39 | 40 | //go:noescape 41 | func mulSubCmplxToAVX2(vOut []float64, v0 []float64, v1 []float64) 42 | -------------------------------------------------------------------------------- /internal/asmgen/fft_butterfly.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/mmcloughlin/avo/build" 5 | "github.com/mmcloughlin/avo/reg" 6 | ) 7 | 8 | func FwdButterflyAVX2(uR, uI, vR, vI, wR, wI reg.VecVirtual) { 9 | vwR := YMM() 10 | VMULPD(wR, vR, vwR) 11 | VFNMADD231PD(wI, vI, vwR) 12 | 13 | vwI := YMM() 14 | VMULPD(wI, vR, vwI) 15 | VFMADD231PD(wR, vI, vwI) 16 | 17 | VSUBPD(vwR, uR, vR) 18 | VSUBPD(vwI, uI, vI) 19 | VADDPD(vwR, uR, uR) 20 | VADDPD(vwI, uI, uI) 21 | } 22 | 23 | func FwdButterflyAVX2XMM(uR, uI, vR, vI, wR, wI reg.VecVirtual) { 24 | vwR := XMM() 25 | VMULPD(wR, vR, vwR) 26 | VFNMADD231PD(wI, vI, vwR) 27 | 28 | vwI := XMM() 29 | VMULPD(wI, vR, vwI) 30 | VFMADD231PD(wR, vI, vwI) 31 | 32 | VSUBPD(vwR, uR, vR) 33 | VSUBPD(vwI, uI, vI) 34 | VADDPD(vwR, uR, uR) 35 | VADDPD(vwI, uI, uI) 36 | } 37 | 38 | func InvButterflyAVX2(uR, uI, vR, vI, wR, wI reg.VecVirtual) { 39 | vuR, vuI := YMM(), YMM() 40 | 41 | VSUBPD(vR, uR, vuR) 42 | VADDPD(vR, uR, uR) 43 | 44 | VSUBPD(vI, uI, vuI) 45 | VADDPD(vI, uI, uI) 46 | 47 | VMULPD(wR, vuR, vR) 48 | VFNMADD231PD(wI, vuI, vR) 49 | 50 | VMULPD(wI, vuR, vI) 51 | VFMADD231PD(wR, vuI, vI) 52 | } 53 | 54 | func InvButterflyAVX2XMM(uR, uI, vR, vI, wR, wI reg.VecVirtual) { 55 | vuR, vuI := XMM(), XMM() 56 | 57 | VSUBPD(vR, uR, vuR) 58 | VADDPD(vR, uR, uR) 59 | 60 | VSUBPD(vI, uI, vuI) 61 | VADDPD(vI, uI, uI) 62 | 63 | VMULPD(wR, vuR, vR) 64 | VFNMADD231PD(wI, vuI, vR) 65 | 66 | VMULPD(wI, vuR, vI) 67 | VFMADD231PD(wR, vuI, vI) 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Tests 2 | on: 3 | push: 4 | 5 | jobs: 6 | tests: 7 | name: Run Go ${{ matrix.go }} Tests 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | go: [ '1.18', '1.25' ] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Setup Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: ${{ matrix.go }} 20 | 21 | - name: Test math/csprng 22 | run: | 23 | go test ./math/csprng -timeout=0 -count=1 24 | 25 | - name: Test math/poly 26 | run: | 27 | go test ./math/poly -timeout=0 -count=1 28 | go test ./math/poly -timeout=0 -count=1 -tags=purego 29 | GODEBUG=cpu.all=off go test ./math/poly -count=1 30 | 31 | - name: Test math/vec 32 | run: | 33 | go test ./math/vec -timeout=0 -count=1 34 | go test ./math/vec -timeout=0 -count=1 -tags=purego 35 | GODEBUG=cpu.all=off go test ./math/vec -count=1 36 | 37 | - name: Test tfhe 38 | run: | 39 | go test ./tfhe -timeout=0 -count=1 40 | go test ./tfhe -timeout=0 -count=1 -tags=purego 41 | GODEBUG=cpu.all=off go test ./tfhe -count=1 42 | 43 | - name: Test mktfhe 44 | run: | 45 | go test ./mktfhe -timeout=0 -count=1 46 | go test ./mktfhe -timeout=0 -count=1 -tags=purego 47 | GODEBUG=cpu.all=off go test ./mktfhe -count=1 48 | 49 | - name: Test xtfhe 50 | run: | 51 | go test ./xtfhe -timeout=0 -count=1 52 | go test ./xtfhe -timeout=0 -count=1 -tags=purego 53 | GODEBUG=cpu.all=off go test ./xtfhe -count=1 54 | -------------------------------------------------------------------------------- /math/vec/asm_vec_stub_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by command: go run asmgen.go -vec -out ../../math/vec/asm_vec_amd64.s -stubs ../../math/vec/asm_vec_stub_amd64.go -pkg=vec. DO NOT EDIT. 2 | 3 | //go:build amd64 && !purego 4 | 5 | package vec 6 | 7 | //go:noescape 8 | func addToUint32AVX2(vOut []uint32, v0 []uint32, v1 []uint32) 9 | 10 | //go:noescape 11 | func subToUint32AVX2(vOut []uint32, v0 []uint32, v1 []uint32) 12 | 13 | //go:noescape 14 | func addToUint64AVX2(vOut []uint64, v0 []uint64, v1 []uint64) 15 | 16 | //go:noescape 17 | func subToUint64AVX2(vOut []uint64, v0 []uint64, v1 []uint64) 18 | 19 | //go:noescape 20 | func scalarMulToUint32AVX2(vOut []uint32, v []uint32, c uint32) 21 | 22 | //go:noescape 23 | func scalarMulAddToUint32AVX2(vOut []uint32, v []uint32, c uint32) 24 | 25 | //go:noescape 26 | func scalarMulSubToUint32AVX2(vOut []uint32, v []uint32, c uint32) 27 | 28 | //go:noescape 29 | func scalarMulToUint64AVX2(vOut []uint64, v []uint64, c uint64) 30 | 31 | //go:noescape 32 | func scalarMulAddToUint64AVX2(vOut []uint64, v []uint64, c uint64) 33 | 34 | //go:noescape 35 | func scalarMulSubToUint64AVX2(vOut []uint64, v []uint64, c uint64) 36 | 37 | //go:noescape 38 | func mulToUint32AVX2(vOut []uint32, v0 []uint32, v1 []uint32) 39 | 40 | //go:noescape 41 | func mulAddToUint32AVX2(vOut []uint32, v0 []uint32, v1 []uint32) 42 | 43 | //go:noescape 44 | func mulSubToUint32AVX2(vOut []uint32, v0 []uint32, v1 []uint32) 45 | 46 | //go:noescape 47 | func mulToUint64AVX2(vOut []uint64, v0 []uint64, v1 []uint64) 48 | 49 | //go:noescape 50 | func mulAddToUint64AVX2(vOut []uint64, v0 []uint64, v1 []uint64) 51 | 52 | //go:noescape 53 | func mulSubToUint64AVX2(vOut []uint64, v0 []uint64, v1 []uint64) 54 | -------------------------------------------------------------------------------- /tfhe/asm_decompose_amd64.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 && !purego 2 | 3 | package tfhe 4 | 5 | import ( 6 | "unsafe" 7 | 8 | "github.com/sp301415/tfhe-go/math/num" 9 | "github.com/sp301415/tfhe-go/math/poly" 10 | "golang.org/x/sys/cpu" 11 | ) 12 | 13 | // decomposePolyTo decomposes p with respect to gadgetParams and writes it to dcmpOut. 14 | func decomposePolyTo[T TorusInt](dcmpOut []poly.Poly[T], p poly.Poly[T], gadgetParams GadgetParameters[T]) { 15 | if cpu.X86.HasAVX && cpu.X86.HasAVX2 { 16 | var z T 17 | switch any(z).(type) { 18 | case uint32: 19 | decomposePolyToUint32AVX2( 20 | *(*[][]uint32)(unsafe.Pointer(&dcmpOut)), 21 | *(*[]uint32)(unsafe.Pointer(&p)), 22 | uint32(gadgetParams.base), 23 | uint32(gadgetParams.logBase), 24 | uint32(gadgetParams.LogLastBaseQ()), 25 | ) 26 | return 27 | case uint64: 28 | decomposePolyToUint64AVX2( 29 | *(*[][]uint64)(unsafe.Pointer(&dcmpOut)), 30 | *(*[]uint64)(unsafe.Pointer(&p)), 31 | uint64(gadgetParams.base), 32 | uint64(gadgetParams.logBase), 33 | uint64(gadgetParams.LogLastBaseQ()), 34 | ) 35 | return 36 | } 37 | } 38 | 39 | logLastBaseQ := gadgetParams.LogLastBaseQ() 40 | for i := 0; i < p.Rank(); i++ { 41 | c := num.DivRoundBits(p.Coeffs[i], logLastBaseQ) 42 | for j := gadgetParams.level - 1; j >= 1; j-- { 43 | dcmpOut[j].Coeffs[i] = c & (gadgetParams.base - 1) 44 | c >>= gadgetParams.logBase 45 | c += dcmpOut[j].Coeffs[i] >> (gadgetParams.logBase - 1) 46 | dcmpOut[j].Coeffs[i] -= (dcmpOut[j].Coeffs[i] & (gadgetParams.base >> 1)) << 1 47 | } 48 | dcmpOut[0].Coeffs[i] = c & (gadgetParams.base - 1) 49 | dcmpOut[0].Coeffs[i] -= (dcmpOut[0].Coeffs[i] & (gadgetParams.base >> 1)) << 1 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mktfhe/lwe.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/vec" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // LWECiphertext is a multi-key variant of [tfhe.LWECiphertext]. 9 | type LWECiphertext[T tfhe.TorusInt] struct { 10 | // Value has length DefaultDimension + 1. 11 | Value []T 12 | } 13 | 14 | // NewLWECiphertext creates a new LWE ciphertext. 15 | func NewLWECiphertext[T tfhe.TorusInt](params Parameters[T]) LWECiphertext[T] { 16 | return LWECiphertext[T]{Value: make([]T, params.DefaultLWEDimension()+1)} 17 | } 18 | 19 | // NewLWECiphertextCustom creates a new LWE ciphertext with given dimension. 20 | func NewLWECiphertextCustom[T tfhe.TorusInt](lweDimension int) LWECiphertext[T] { 21 | return LWECiphertext[T]{Value: make([]T, lweDimension+1)} 22 | } 23 | 24 | // Copy returns a copy of the ciphertext. 25 | func (ct LWECiphertext[T]) Copy() LWECiphertext[T] { 26 | return LWECiphertext[T]{Value: vec.Copy(ct.Value)} 27 | } 28 | 29 | // CopyFrom copies values from the ciphertext. 30 | func (ct *LWECiphertext[T]) CopyFrom(ctIn LWECiphertext[T]) { 31 | copy(ct.Value, ctIn.Value) 32 | } 33 | 34 | // CopyFromSubKey copies values from the single-key ciphertext. 35 | // 36 | // Panics if the dimension of the ciphertext is not a multiple of the dimension of the single-key ciphertext. 37 | func (ct *LWECiphertext[T]) CopyFromSubKey(ctIn tfhe.LWECiphertext[T], idx int) { 38 | if (len(ct.Value)-1)%(len(ctIn.Value)-1) != 0 { 39 | panic("LWE Dimension mismatch") 40 | } 41 | 42 | ct.Clear() 43 | singleKeyLWEDimension := len(ctIn.Value) - 1 44 | copy(ct.Value[1+idx*singleKeyLWEDimension:1+(idx+1)*singleKeyLWEDimension], ctIn.Value[1:]) 45 | ct.Value[0] = ctIn.Value[0] 46 | } 47 | 48 | // Clear clears the ciphertext. 49 | func (ct *LWECiphertext[T]) Clear() { 50 | vec.Fill(ct.Value, 0) 51 | } 52 | -------------------------------------------------------------------------------- /mktfhe/binary_decryptor.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | // BinaryDecryptor is a multi-key TFHE binary decryptor. 6 | type BinaryDecryptor[T tfhe.TorusInt] struct { 7 | // BinaryEncoder is an embedded encoder for this BinaryDecryptor. 8 | *tfhe.BinaryEncoder[T] 9 | // Params is parameters for this BinaryDecryptor. 10 | Params Parameters[T] 11 | // Decryptor is a generic Decryptor for this BinaryDecryptor. 12 | Decryptor *Decryptor[T] 13 | } 14 | 15 | // NewBinaryDecryptor creates a new BinaryDecryptor. 16 | func NewBinaryDecryptor[T tfhe.TorusInt](params Parameters[T], sk map[int]tfhe.SecretKey[T]) *BinaryDecryptor[T] { 17 | return &BinaryDecryptor[T]{ 18 | BinaryEncoder: tfhe.NewBinaryEncoder(params.subParams), 19 | Params: params, 20 | Decryptor: NewDecryptor(params, sk), 21 | } 22 | } 23 | 24 | // SafeCopy returns a thread-safe copy. 25 | func (d *BinaryDecryptor[T]) SafeCopy() *BinaryDecryptor[T] { 26 | return &BinaryDecryptor[T]{ 27 | BinaryEncoder: d.BinaryEncoder, 28 | Params: d.Params, 29 | Decryptor: d.Decryptor.SafeCopy(), 30 | } 31 | } 32 | 33 | // DecryptLWEBool decrypts LWE ciphertext to boolean message. 34 | // Like most languages, false == 0, and true == 1. 35 | // 36 | // Note that this is different from calling DecryptLWE and comparing with 0. 37 | func (d *BinaryDecryptor[T]) DecryptLWEBool(ct LWECiphertext[T]) bool { 38 | return d.DecodeLWEBool(d.Decryptor.DecryptLWEPlaintext(ct)) 39 | } 40 | 41 | // DecryptLWEBits decrypts a slice of binary LWE ciphertext 42 | // to integer message. 43 | // The order of bits of LWE ciphertexts are assumed to be little-endian. 44 | func (d *BinaryDecryptor[T]) DecryptLWEBits(ct []LWECiphertext[T]) int { 45 | var message int 46 | for i := len(ct) - 1; i >= 0; i-- { 47 | message <<= 1 48 | if d.DecryptLWEBool(ct[i]) { 49 | message += 1 50 | } 51 | } 52 | return message 53 | } 54 | -------------------------------------------------------------------------------- /xtfhe/bfv_test.go: -------------------------------------------------------------------------------- 1 | package xtfhe_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sp301415/tfhe-go/math/num" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | "github.com/sp301415/tfhe-go/xtfhe" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | bfvParams = tfhe.ParamsUint3.Compile() 14 | bfvKeySwitchParams = xtfhe.ParamsBFVKeySwitchLogN11.Compile() 15 | bfvEnc = tfhe.NewEncryptor(bfvParams) 16 | bfvKeyGen = xtfhe.NewBFVKeyGenerator(bfvParams, bfvEnc.SecretKey) 17 | bfvEval = xtfhe.NewBFVEvaluator(bfvParams, xtfhe.BFVEvaluationKey[uint64]{ 18 | RelinKey: bfvKeyGen.GenRelinKey(bfvKeySwitchParams), 19 | GaloisKeys: bfvKeyGen.GenGaloisKeysForPack(bfvKeySwitchParams), 20 | }) 21 | ) 22 | 23 | func TestBFVMul(t *testing.T) { 24 | m0 := int(num.Sqrt(bfvParams.MessageModulus()) - 1) 25 | m1 := int(num.Sqrt(bfvParams.MessageModulus()) - 2) 26 | 27 | ct0 := bfvEnc.EncryptGLWE([]int{m0}) 28 | ct1 := bfvEnc.EncryptGLWE([]int{m1}) 29 | 30 | ctMul := bfvEval.Mul(ct0, ct1) 31 | 32 | assert.Equal(t, bfvEnc.DecryptGLWE(ctMul)[0], m0*m1) 33 | } 34 | 35 | func TestBFVPermute(t *testing.T) { 36 | m0 := 1 37 | m1 := 1 38 | d := 1<<5 + 1 39 | 40 | ct0 := bfvEnc.EncryptGLWE([]int{m0, m1}) 41 | ctAut := bfvEval.Permute(ct0, d) 42 | 43 | assert.Equal(t, bfvEnc.DecryptGLWE(ctAut)[0], m0) 44 | assert.Equal(t, bfvEnc.DecryptGLWE(ctAut)[d], m1) 45 | } 46 | 47 | func TestLWEToGLWECiphertext(t *testing.T) { 48 | m := 3 49 | ctLWE := bfvEnc.EncryptLWE(m) 50 | ctGLWE := bfvEval.Pack(ctLWE) 51 | 52 | assert.Equal(t, bfvEnc.DecryptGLWE(ctGLWE)[0], m) 53 | } 54 | 55 | func BenchmarkBFVMul(b *testing.B) { 56 | ct0 := bfvEnc.EncryptGLWE(nil) 57 | ct1 := bfvEnc.EncryptGLWE(nil) 58 | ctMul := bfvEnc.EncryptGLWE(nil) 59 | 60 | b.ResetTimer() 61 | for i := 0; i < b.N; i++ { 62 | bfvEval.MulTo(ctMul, ct0, ct1) 63 | } 64 | } 65 | 66 | func BenchmarkBFVRingPack(b *testing.B) { 67 | ctLWE := bfvEnc.EncryptLWE(0) 68 | ctGLWE := bfvEnc.EncryptGLWE(nil) 69 | 70 | b.ResetTimer() 71 | for i := 0; i < b.N; i++ { 72 | bfvEval.PackTo(ctGLWE, ctLWE) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mktfhe/bootstrap_key.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | // EvaluationKey is a multi-key variant of [tfhe.EvaluationKey]. 6 | type EvaluationKey[T tfhe.TorusInt] struct { 7 | // EvaluationKey is an embedded single-key EvaluationKey. 8 | tfhe.EvaluationKey[T] 9 | // CRSPublicKey is a public key from the common reference string. 10 | CRSPublicKey tfhe.FFTGLevCiphertext[T] 11 | // RelinKey is a relinearization key. 12 | RelinKey FFTUniEncryption[T] 13 | } 14 | 15 | // NewEvaluationKey creates a new EvaluationKey. 16 | func NewEvaluationKey[T tfhe.TorusInt](params Parameters[T]) EvaluationKey[T] { 17 | return EvaluationKey[T]{ 18 | EvaluationKey: tfhe.NewEvaluationKey(params.subParams), 19 | CRSPublicKey: tfhe.NewFFTGLevCiphertext(params.subParams, params.relinKeyParams), 20 | RelinKey: NewFFTUniEncryption(params, params.relinKeyParams), 21 | } 22 | } 23 | 24 | // NewEvaluationKeyCustom creates a new EvaluationKey with custom parameters. 25 | func NewEvaluationKeyCustom[T tfhe.TorusInt](lweDimension, polyRank int, blindRotateParams, keySwitchParams, relinParams tfhe.GadgetParameters[T]) EvaluationKey[T] { 26 | return EvaluationKey[T]{ 27 | EvaluationKey: tfhe.NewEvaluationKeyCustom(lweDimension, 1, polyRank, blindRotateParams, keySwitchParams), 28 | CRSPublicKey: tfhe.NewFFTGLevCiphertextCustom(1, polyRank, relinParams), 29 | RelinKey: NewFFTUniEncryptionCustom(polyRank, relinParams), 30 | } 31 | } 32 | 33 | // Copy returns a copy of the key. 34 | func (evk EvaluationKey[T]) Copy() EvaluationKey[T] { 35 | return EvaluationKey[T]{ 36 | EvaluationKey: evk.EvaluationKey.Copy(), 37 | CRSPublicKey: evk.CRSPublicKey.Copy(), 38 | RelinKey: evk.RelinKey.Copy(), 39 | } 40 | } 41 | 42 | // CopyFrom copies values from key. 43 | func (evk *EvaluationKey[T]) CopyFrom(evkIn EvaluationKey[T]) { 44 | evk.EvaluationKey.CopyFrom(evkIn.EvaluationKey) 45 | evk.CRSPublicKey.CopyFrom(evkIn.CRSPublicKey) 46 | evk.RelinKey.CopyFrom(evkIn.RelinKey) 47 | } 48 | 49 | // Clear clears the key. 50 | func (evk *EvaluationKey[T]) Clear() { 51 | evk.EvaluationKey.Clear() 52 | evk.CRSPublicKey.Clear() 53 | evk.RelinKey.Clear() 54 | } 55 | -------------------------------------------------------------------------------- /xtfhe/manylut_params.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/num" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // ManyLUTParametersLiteral is a structure for PBSManyLUT Parameters. 9 | // 10 | // PolyRank does not equal LUTSize. 11 | type ManyLUTParametersLiteral[T tfhe.TorusInt] struct { 12 | // BaseParams is a base parameters for this ManyLUTParametersLiteral. 13 | BaseParams tfhe.ParametersLiteral[T] 14 | 15 | // LUTCount is the number of LUTs that can be evaluated at once. 16 | // LUTCount must be a power of 2. 17 | LUTCount int 18 | } 19 | 20 | // Compile transforms ParametersLiteral to read-only Parameters. 21 | // If there is any invalid parameter in the literal, it panics. 22 | // Default parameters are guaranteed to be compiled without panics. 23 | func (p ManyLUTParametersLiteral[T]) Compile() ManyLUTParameters[T] { 24 | baseParams := p.BaseParams.Compile() 25 | 26 | switch { 27 | case baseParams.PolyRank() != baseParams.LUTSize(): 28 | panic("PolyRank does not equal LUTSize") 29 | case !num.IsPowerOfTwo(p.LUTCount): 30 | panic("lutCount not power of two") 31 | } 32 | 33 | return ManyLUTParameters[T]{ 34 | baseParams: baseParams, 35 | 36 | lutCount: p.LUTCount, 37 | logLUTCount: num.Log2(p.LUTCount), 38 | } 39 | } 40 | 41 | // ManyLUTParameters is a parameter set for PBSManyLUT. 42 | type ManyLUTParameters[T tfhe.TorusInt] struct { 43 | // baseParams is a base parameters for this ManyLUTParameters. 44 | baseParams tfhe.Parameters[T] 45 | 46 | // LUTCount is the number of LUTs that can be evaluated at once. 47 | // LUTCount must be a power of 2. 48 | lutCount int 49 | // LogLUTCount equals log(LUTCount). 50 | logLUTCount int 51 | } 52 | 53 | // BaseParams returns the base parameters for this ManyLUTParameters. 54 | func (p ManyLUTParameters[T]) BaseParams() tfhe.Parameters[T] { 55 | return p.baseParams 56 | } 57 | 58 | // LUTCount returns the number of LUTs that can be evaluated at once. 59 | func (p ManyLUTParameters[T]) LUTCount() int { 60 | return p.lutCount 61 | } 62 | 63 | // LogLUTCount returns log(LUTCount). 64 | func (p ManyLUTParameters[T]) LogLUTCount() int { 65 | return p.logLUTCount 66 | } 67 | -------------------------------------------------------------------------------- /mktfhe/bootstrap_key_marshal.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | // ByteSize returns the size of the key in bytes. 9 | func (evk EvaluationKey[T]) ByteSize() int { 10 | return evk.EvaluationKey.ByteSize() + evk.CRSPublicKey.ByteSize() + evk.RelinKey.ByteSize() 11 | } 12 | 13 | // WriteTo implements the [io.WriterTo] interface. 14 | // 15 | // The encoded form is as follows: 16 | // 17 | // EvaluationKey 18 | // CRSPublicKey 19 | // RelinKey 20 | func (evk EvaluationKey[T]) WriteTo(w io.Writer) (n int64, err error) { 21 | var nWrite int64 22 | 23 | if nWrite, err = evk.EvaluationKey.WriteTo(w); err != nil { 24 | return n + nWrite, err 25 | } 26 | n += nWrite 27 | 28 | if nWrite, err = evk.CRSPublicKey.WriteTo(w); err != nil { 29 | return n + nWrite, err 30 | } 31 | n += nWrite 32 | 33 | if nWrite, err = evk.RelinKey.WriteTo(w); err != nil { 34 | return n + nWrite, err 35 | } 36 | n += nWrite 37 | 38 | if n < int64(evk.ByteSize()) { 39 | return n, io.ErrShortWrite 40 | } 41 | 42 | return 43 | } 44 | 45 | // ReadFrom implements the [io.ReaderFrom] interface. 46 | func (evk *EvaluationKey[T]) ReadFrom(r io.Reader) (n int64, err error) { 47 | var nRead int64 48 | 49 | if nRead, err = evk.EvaluationKey.ReadFrom(r); err != nil { 50 | return n + nRead, err 51 | } 52 | n += nRead 53 | 54 | if nRead, err = evk.CRSPublicKey.ReadFrom(r); err != nil { 55 | return n + nRead, err 56 | } 57 | n += nRead 58 | 59 | if nRead, err = evk.RelinKey.ReadFrom(r); err != nil { 60 | return n + nRead, err 61 | } 62 | n += nRead 63 | 64 | if n < int64(evk.ByteSize()) { 65 | return n, io.ErrShortWrite 66 | } 67 | 68 | return 69 | } 70 | 71 | // MarshalBinary implements the [encoding.BinaryMarshaler] interface. 72 | func (evk EvaluationKey[T]) MarshalBinary() (data []byte, err error) { 73 | buf := bytes.NewBuffer(make([]byte, 0, evk.ByteSize())) 74 | _, err = evk.WriteTo(buf) 75 | return buf.Bytes(), err 76 | } 77 | 78 | // UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. 79 | func (evk *EvaluationKey[T]) UnmarshalBinary(data []byte) error { 80 | buf := bytes.NewBuffer(data) 81 | _, err := evk.ReadFrom(buf) 82 | return err 83 | } 84 | -------------------------------------------------------------------------------- /tfhe/glwe_enc_public.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // EncryptGLWE encodes and encrypts integer messages to GLWE ciphertext. 4 | func (e *PublicEncryptor[T]) EncryptGLWE(messages []int) GLWECiphertext[T] { 5 | ctOut := NewGLWECiphertext(e.Params) 6 | e.EncryptGLWETo(ctOut, messages) 7 | return ctOut 8 | } 9 | 10 | // EncryptGLWETo encodes and encrypts integer messages to GLWE ciphertext and writes it to ctOut. 11 | func (e *PublicEncryptor[T]) EncryptGLWETo(ctOut GLWECiphertext[T], messages []int) { 12 | e.EncodeGLWETo(e.buf.ptGLWE, messages) 13 | e.EncryptGLWEPlaintextTo(ctOut, e.buf.ptGLWE) 14 | } 15 | 16 | // EncryptGLWEPlaintext encrypts GLWE plaintext to GLWE ciphertext. 17 | func (e *PublicEncryptor[T]) EncryptGLWEPlaintext(pt GLWEPlaintext[T]) GLWECiphertext[T] { 18 | ctOut := NewGLWECiphertext(e.Params) 19 | e.EncryptGLWEPlaintextTo(ctOut, pt) 20 | return ctOut 21 | } 22 | 23 | // EncryptGLWEPlaintextTo encrypts GLWE plaintext to GLWE ciphertext and writes it to ctOut. 24 | func (e *PublicEncryptor[T]) EncryptGLWEPlaintextTo(ctOut GLWECiphertext[T], pt GLWEPlaintext[T]) { 25 | ctOut.Value[0].CopyFrom(pt.Value) 26 | e.EncryptGLWEBody(ctOut) 27 | } 28 | 29 | // EncryptGLWEBody encrypts the value in the body of GLWE ciphertext and overrides it. 30 | // This avoids the need for most buffers. 31 | func (e *PublicEncryptor[T]) EncryptGLWEBody(ct GLWECiphertext[T]) { 32 | for i := 0; i < e.Params.glweRank; i++ { 33 | e.BinarySampler.SamplePolyTo(e.buf.auxKey.Value[i]) 34 | } 35 | e.FwdFFTGLWESecretKeyTo(e.buf.auxFourierKey, e.buf.auxKey) 36 | 37 | e.PolyEvaluator.ShortFFTPolyMulAddPolyTo(ct.Value[0], e.PublicKey.GLWEKey.Value[0].Value[0], e.buf.auxFourierKey.Value[0]) 38 | for j := 1; j < e.Params.glweRank+1; j++ { 39 | e.PolyEvaluator.ShortFFTPolyMulAddPolyTo(ct.Value[j], e.PublicKey.GLWEKey.Value[0].Value[j], e.buf.auxFourierKey.Value[0]) 40 | } 41 | for i := 1; i < e.Params.glweRank; i++ { 42 | for j := 0; j < e.Params.glweRank+1; j++ { 43 | e.PolyEvaluator.ShortFFTPolyMulAddPolyTo(ct.Value[j], e.PublicKey.GLWEKey.Value[i].Value[j], e.buf.auxFourierKey.Value[i]) 44 | } 45 | } 46 | 47 | for j := 0; j < e.Params.glweRank+1; j++ { 48 | e.GaussianSampler.SamplePolyAddTo(ct.Value[j], e.Params.GLWEStdDevQ()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /math/csprng/binary_sampler.go: -------------------------------------------------------------------------------- 1 | package csprng 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/num" 5 | "github.com/sp301415/tfhe-go/math/poly" 6 | "github.com/sp301415/tfhe-go/math/vec" 7 | ) 8 | 9 | // BinarySampler samples values from uniform and block binary distribution. 10 | type BinarySampler[T num.Integer] struct { 11 | baseSampler *UniformSampler[uint64] 12 | } 13 | 14 | // NewBinarySampler creates a new BinarySampler. 15 | // 16 | // Panics when read from crypto/rand or AES initialization fails. 17 | func NewBinarySampler[T num.Integer]() *BinarySampler[T] { 18 | return &BinarySampler[T]{ 19 | baseSampler: NewUniformSampler[uint64](), 20 | } 21 | } 22 | 23 | // NewBinarySamplerWithSeed creates a new BinarySampler, with user supplied seed. 24 | // 25 | // Panics when AES initialization fails. 26 | func NewBinarySamplerWithSeed[T num.Integer](seed []byte) *BinarySampler[T] { 27 | return &BinarySampler[T]{ 28 | baseSampler: NewUniformSamplerWithSeed[uint64](seed), 29 | } 30 | } 31 | 32 | // Sample uniformly samples a random binary integer. 33 | func (s *BinarySampler[T]) Sample() T { 34 | return T(s.baseSampler.Sample() & 1) 35 | } 36 | 37 | // SampleVecTo samples uniform binary values to vOut. 38 | func (s *BinarySampler[T]) SampleVecTo(vOut []T) { 39 | var buf uint64 40 | for i := 0; i < len(vOut); i++ { 41 | if i&63 == 0 { 42 | buf = s.baseSampler.Sample() 43 | } 44 | vOut[i] = T(buf & 1) 45 | buf >>= 1 46 | } 47 | } 48 | 49 | // SamplePolyTo samples uniform binary values to pOut. 50 | func (s *BinarySampler[T]) SamplePolyTo(pOut poly.Poly[T]) { 51 | s.SampleVecTo(pOut.Coeffs) 52 | } 53 | 54 | // SampleBlockVecTo samples block binary values to vOut. 55 | func (s *BinarySampler[T]) SampleBlockVecTo(vOut []T, blockSize int) { 56 | if len(vOut)%blockSize != 0 { 57 | panic("length not multiple of blocksize") 58 | } 59 | 60 | for i := 0; i < len(vOut); i += blockSize { 61 | vec.Fill(vOut[i:i+blockSize], 0) 62 | offset := int(s.baseSampler.SampleN(uint64(blockSize) + 1)) 63 | if offset == blockSize { 64 | continue 65 | } 66 | vOut[i+offset] = 1 67 | } 68 | } 69 | 70 | // SampleBlockPolyTo samples block binary values to pOut. 71 | func (s *BinarySampler[T]) SampleBlockPolyTo(pOut poly.Poly[T], blockSize int) { 72 | s.SampleBlockVecTo(pOut.Coeffs, blockSize) 73 | } 74 | -------------------------------------------------------------------------------- /xtfhe/circuit_bootstrap_key.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | // CircuitBootstrapKey is a key for Circuit Bootstrapping. 6 | type CircuitBootstrapKey[T tfhe.TorusInt] struct { 7 | // SchemeSwitchKey is a key for scheme switching. 8 | // It has length GLWERank, 9 | // with i-th element being an FFTGGSWCiphertext of i-th secret key. 10 | SchemeSwitchKey []tfhe.FFTGGSWCiphertext[T] 11 | // TraceKeys is a map of galois keys used for LWE to GLWE packing. 12 | TraceKeys map[int]tfhe.GLWEKeySwitchKey[T] 13 | } 14 | 15 | // CircuitBootstrapKeyGenerator generates a key for Circuit Bootstrapping. 16 | type CircuitBootstrapKeyGenerator[T tfhe.TorusInt] struct { 17 | // BFVKeyGenerator is a BFVKeyGenerator for this CircuitBootstrapKeyGenerator. 18 | *BFVKeyGenerator[T] 19 | 20 | // Params is parameters for this CircuitBootstrapKeyGenerator. 21 | Params CircuitBootstrapParameters[T] 22 | } 23 | 24 | // NewCircuitBootstrapKeyGenerator creates a new CircuitBootstrapKeyGenerator. 25 | func NewCircuitBootstrapKeyGenerator[T tfhe.TorusInt](params CircuitBootstrapParameters[T], sk tfhe.SecretKey[T]) *CircuitBootstrapKeyGenerator[T] { 26 | return &CircuitBootstrapKeyGenerator[T]{ 27 | BFVKeyGenerator: NewBFVKeyGenerator(params.Params(), sk), 28 | Params: params, 29 | } 30 | } 31 | 32 | // SafeCopy creates a shallow copy of this CircuitBootstrapKeyGenerator. 33 | // Returned CircuitBootstrapKeyGenerator is safe for concurrent use. 34 | func (kg *CircuitBootstrapKeyGenerator[T]) SafeCopy() *CircuitBootstrapKeyGenerator[T] { 35 | return &CircuitBootstrapKeyGenerator[T]{ 36 | BFVKeyGenerator: kg.BFVKeyGenerator.SafeCopy(), 37 | Params: kg.Params, 38 | } 39 | } 40 | 41 | // GenCircuitBootstrapKey generates a key for Circuit Bootstrapping. 42 | func (kg *CircuitBootstrapKeyGenerator[T]) GenCircuitBootstrapKey() CircuitBootstrapKey[T] { 43 | schemeSwitchKey := make([]tfhe.FFTGGSWCiphertext[T], kg.Params.Params().GLWERank()) 44 | for i := 0; i < kg.Params.Params().GLWERank(); i++ { 45 | schemeSwitchKey[i] = kg.Encryptor.EncryptFFTGGSWPoly(kg.Encryptor.SecretKey.GLWEKey.Value[i], kg.Params.schemeSwitchParameters) 46 | } 47 | 48 | return CircuitBootstrapKey[T]{ 49 | SchemeSwitchKey: schemeSwitchKey, 50 | TraceKeys: kg.GenGaloisKeysForPack(kg.Params.traceKeySwitchParameters), 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tfhe/lwe_enc_public.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/math/vec" 6 | ) 7 | 8 | // EncryptLWE encodes and encrypts integer message to LWE ciphertext. 9 | func (e *PublicEncryptor[T]) EncryptLWE(message int) LWECiphertext[T] { 10 | return e.EncryptLWEPlaintext(e.EncodeLWE(message)) 11 | } 12 | 13 | // EncryptLWETo encodes and encrypts integer message to LWE ciphertext and writes it to ctOut. 14 | func (e *PublicEncryptor[T]) EncryptLWETo(ctOut LWECiphertext[T], message int) { 15 | e.EncryptLWEPlaintextTo(ctOut, e.EncodeLWE(message)) 16 | } 17 | 18 | // EncryptLWEPlaintext encrypts LWE plaintext to LWE ciphertext. 19 | func (e *PublicEncryptor[T]) EncryptLWEPlaintext(pt LWEPlaintext[T]) LWECiphertext[T] { 20 | ctOut := NewLWECiphertext(e.Params) 21 | e.EncryptLWEPlaintextTo(ctOut, pt) 22 | return ctOut 23 | } 24 | 25 | // EncryptLWEPlaintextTo encrypts LWE plaintext to LWE ciphertext and writes it to ctOut. 26 | func (e *PublicEncryptor[T]) EncryptLWEPlaintextTo(ctOut LWECiphertext[T], pt LWEPlaintext[T]) { 27 | ctOut.Value[0] = pt.Value 28 | e.EncryptLWEBody(ctOut) 29 | } 30 | 31 | // EncryptLWEBody encrypts the value in the body of LWE ciphertext and overrides it. 32 | // This avoids the need for most buffers. 33 | func (e *PublicEncryptor[T]) EncryptLWEBody(ct LWECiphertext[T]) { 34 | for i := 0; i < e.Params.glweRank; i++ { 35 | e.BinarySampler.SamplePolyTo(e.buf.auxKey.Value[i]) 36 | ct.Value[0] += vec.Dot(e.buf.auxKey.Value[i].Coeffs, e.PublicKey.LWEKey.Value[i].Value[0].Coeffs) 37 | } 38 | ct.Value[0] += e.GaussianSampler.Sample(e.Params.GLWEStdDevQ()) 39 | 40 | for i := 0; i < e.Params.glweRank; i++ { 41 | vec.ReverseInPlace(e.buf.auxKey.Value[i].Coeffs) 42 | } 43 | e.FwdFFTGLWESecretKeyTo(e.buf.auxFourierKey, e.buf.auxKey) 44 | 45 | ctGLWE := make([]poly.Poly[T], e.Params.glweRank) 46 | for i := 0; i < e.Params.glweRank; i++ { 47 | ctGLWE[i] = poly.Poly[T]{Coeffs: ct.Value[1+i*e.Params.polyRank : 1+(i+1)*e.Params.polyRank]} 48 | } 49 | 50 | for i := 0; i < e.Params.glweRank; i++ { 51 | for j := 0; j < e.Params.glweRank; j++ { 52 | e.PolyEvaluator.ShortFFTPolyMulAddPolyTo(ctGLWE[j], e.PublicKey.LWEKey.Value[i].Value[j+1], e.buf.auxFourierKey.Value[i]) 53 | } 54 | } 55 | 56 | for j := 0; j < e.Params.glweRank; j++ { 57 | e.GaussianSampler.SamplePolyAddTo(ctGLWE[j], e.Params.GLWEStdDevQ()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mktfhe/bootstrap_keygen.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/tfhe" 5 | ) 6 | 7 | // GenEvalKey samples a new evaluation key for bootstrapping. 8 | // 9 | // This can take a long time. 10 | // Use [*Encryptor.GenEvalKeyParallel] for better key generation performance. 11 | func (e *Encryptor[T]) GenEvalKey() EvaluationKey[T] { 12 | return EvaluationKey[T]{ 13 | EvaluationKey: e.SubEncryptor.GenEvalKey(), 14 | CRSPublicKey: e.GenCRSPublicKey(), 15 | RelinKey: e.GenRelinKey(), 16 | } 17 | } 18 | 19 | // GenEvalKeyParallel samples a new evaluation key for bootstrapping in parallel. 20 | func (e *Encryptor[T]) GenEvalKeyParallel() EvaluationKey[T] { 21 | return EvaluationKey[T]{ 22 | EvaluationKey: e.SubEncryptor.GenEvalKeyParallel(), 23 | CRSPublicKey: e.GenCRSPublicKey(), 24 | RelinKey: e.GenRelinKey(), 25 | } 26 | } 27 | 28 | // GenDefaultKeySwitchKey samples a new keyswitch key LWELargeKey -> LWEKey, 29 | // used for bootstrapping. 30 | // 31 | // This can take a long time. 32 | // Use [*Encryptor.GenDefaultKeySwitchKeyParallel] for better key generation performance. 33 | func (e *Encryptor[T]) GenDefaultKeySwitchKey() tfhe.LWEKeySwitchKey[T] { 34 | return e.SubEncryptor.GenDefaultKeySwitchKey() 35 | } 36 | 37 | // GenDefaultKeySwitchKeyParallel samples a new keyswitch key LWELargeKey -> LWEKey in parallel, 38 | // used for bootstrapping. 39 | func (e *Encryptor[T]) GenDefaultKeySwitchKeyParallel() tfhe.LWEKeySwitchKey[T] { 40 | return e.SubEncryptor.GenDefaultKeySwitchKeyParallel() 41 | } 42 | 43 | // GenCRSPublicKey samples a new public key from the common reference string. 44 | func (e *Encryptor[T]) GenCRSPublicKey() tfhe.FFTGLevCiphertext[T] { 45 | pk := tfhe.NewFFTGLevCiphertext(e.Params.subParams, e.Params.relinKeyParams) 46 | for i := 0; i < e.Params.relinKeyParams.Level(); i++ { 47 | e.buf.ctSubGLWE.Value[1].CopyFrom(e.CRS[i]) 48 | 49 | e.SubEncryptor.GaussianSampler.SamplePolyTo(e.buf.ctSubGLWE.Value[0], e.Params.GLWEStdDevQ()) 50 | e.SubEncryptor.PolyEvaluator.ShortFFTPolyMulSubPolyTo(e.buf.ctSubGLWE.Value[0], e.buf.ctSubGLWE.Value[1], e.SecretKey.FFTGLWEKey.Value[0]) 51 | 52 | e.SubEncryptor.FwdFFTGLWECiphertextTo(pk.Value[i], e.buf.ctSubGLWE) 53 | } 54 | return pk 55 | } 56 | 57 | // GenRelinKey samples a new relinearization key. 58 | func (e *Encryptor[T]) GenRelinKey() FFTUniEncryption[T] { 59 | return e.FFTUniEncryptPoly(e.SubEncryptor.SecretKey.GLWEKey.Value[0], e.Params.relinKeyParams) 60 | } 61 | -------------------------------------------------------------------------------- /xtfhe/circuit_bootstrap_params_list.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | var ( 6 | // ParamsCircuitBootstrapMedium is a default circuit bootstrapping parameter set for binary TFHE 7 | // with max depth 1194. 8 | ParamsCircuitBootstrapMedium = CircuitBootstrapParametersLiteral[uint64]{ 9 | ManyLUTParams: ManyLUTParametersLiteral[uint64]{ 10 | BaseParams: tfhe.ParametersLiteral[uint64]{ 11 | LWEDimension: 571, 12 | GLWERank: 1, 13 | PolyRank: 2048, 14 | 15 | LWEStdDev: 0.00016996595057126976, 16 | GLWEStdDev: 0.0000000000000003472576015484159, 17 | 18 | MessageModulus: 1 << 1, 19 | 20 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 21 | Base: 1 << 15, 22 | Level: 2, 23 | }, 24 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 25 | Base: 1 << 2, 26 | Level: 5, 27 | }, 28 | 29 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 30 | }, 31 | 32 | LUTCount: 4, 33 | }, 34 | 35 | SchemeSwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 36 | Base: 1 << 17, 37 | Level: 2, 38 | }, 39 | TraceKeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 40 | Base: 1 << 13, 41 | Level: 3, 42 | }, 43 | OutputParams: tfhe.GadgetParametersLiteral[uint64]{ 44 | Base: 1 << 4, 45 | Level: 4, 46 | }, 47 | } 48 | 49 | // ParamsCircuitBootstrapLarge is a default circuit bootstrapping parameter set for binary TFHE 50 | // with max depth 13410. 51 | ParamsCircuitBootstrapLarge = CircuitBootstrapParametersLiteral[uint64]{ 52 | ManyLUTParams: ManyLUTParametersLiteral[uint64]{ 53 | BaseParams: tfhe.ParametersLiteral[uint64]{ 54 | LWEDimension: 571, 55 | GLWERank: 1, 56 | PolyRank: 2048, 57 | 58 | LWEStdDev: 0.00016996595057126976, 59 | GLWEStdDev: 0.0000000000000003472576015484159, 60 | 61 | MessageModulus: 1 << 1, 62 | 63 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 64 | Base: 1 << 15, 65 | Level: 2, 66 | }, 67 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 68 | Base: 1 << 2, 69 | Level: 5, 70 | }, 71 | 72 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 73 | }, 74 | 75 | LUTCount: 4, 76 | }, 77 | 78 | SchemeSwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 79 | Base: 1 << 17, 80 | Level: 2, 81 | }, 82 | TraceKeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 83 | Base: 1 << 8, 84 | Level: 6, 85 | }, 86 | OutputParams: tfhe.GadgetParametersLiteral[uint64]{ 87 | Base: 1 << 5, 88 | Level: 4, 89 | }, 90 | } 91 | ) 92 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | Theoretical security of TFHE is based on hardness of [LWE](https://en.wikipedia.org/wiki/Learning_with_errors) and [RLWE](https://en.wikipedia.org/wiki/Ring_learning_with_errors) problem, which are the foundations of lattice-based cryptography. 3 | 4 | ## Implementation-related Attacks 5 | TFHE-go has not been audited or reviewed by security experts. It is highly likely that it contains critical vulnerabilities to implementation-related attacks, such as side-channel attacks. Use at your own risk. 6 | 7 | ## Parameter Selection 8 | Single-Key parameters of TFHE-go ensure at least 128 bits of security, with bootstrapping failure rate less than 2-64. Multi-Key parameters are based on [[KMS22](https://eprint.iacr.org/2022/1460)], which ensure at least 110 bits of security. They were validated using latest commit([5ba00f5](https://github.com/malb/lattice-estimator/commit/5ba00f56dd1086c3a42b98fc596c64907adb96ff)) of [lattice-estimator](https://github.com/malb/lattice-estimator). Attack costs were estimated with two strategies, `primal_usvp` and `dual_hybrid`, with `red_cost_model = RC.BDGL16`. 9 | 10 | ### Block Binary Keys 11 | 12 | TFHE-go implements block binary distribution [[LMSS23](https://eprint.iacr.org/2023/958)] for sampling secret keys, which may lower the security level. Impacted parameters were carefully adjusted following the authors' security estimation to support 128 bit security. To use uniform binary secret keys like the original TFHE scheme, you can set `BlockSize` to 1. 13 | 14 | ### IND-CPAD Security 15 | Recently, [[CCP+24](https://eprint.iacr.org/2024/127)] proposed an attack against TFHE over IND-CPAD security model. This attack may be effective, often resulting in full key recovery, if bootstrapping failure proabability is high enough. TFHE-go only considers IND-CPA security, and assumes that decrypted plaintexts are not shared with any third parties. If you need such functionality, you must use parameters with lower bootstrapping failure rate. 16 | 17 | ## Distributed Decryption 18 | In multi-key FHE schemes, decrypting a ciphertext requires all parties to engage in a distributed decryption protocol, which allows parties to obtain decrypted messages without any information leak. However, in multi-key TFHE, this protocol is typically expensive, requring multi-party garbling [[Ben18](https://eprint.iacr.org/2017/1186)] or modified noise flooding [[DDK+23](https://eprint.iacr.org/2023/815)]. For simplicity, TFHE-go assumes the presence of a trusted third party (known as the *Decryptor*) who possesses the secret keys of all parties to decrypt ciphertexts. 19 | -------------------------------------------------------------------------------- /mktfhe/binary_encryptor_public.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | // BinaryPublicEncryptor is a multi-key variant of [tfhe.BinaryPublicEncryptor]. 6 | type BinaryPublicEncryptor[T tfhe.TorusInt] struct { 7 | // BinaryEncoder is an embedded encoder for this BinaryPublicEncryptor. 8 | *tfhe.BinaryEncoder[T] 9 | // Params is parameters for this BinaryPublicEncryptor. 10 | Params Parameters[T] 11 | // Encryptor is a generic PublicEncryptor for this BinaryPublicEncryptor. 12 | Encryptor *PublicEncryptor[T] 13 | } 14 | 15 | // NewBinaryPublicEncryptor creates a new BinaryPublicEncryptor. 16 | func NewBinaryPublicEncryptor[T tfhe.TorusInt](params Parameters[T], idx int, pk tfhe.PublicKey[T]) *BinaryPublicEncryptor[T] { 17 | return &BinaryPublicEncryptor[T]{ 18 | BinaryEncoder: tfhe.NewBinaryEncoder(params.subParams), 19 | Params: params, 20 | Encryptor: NewPublicEncryptor(params, idx, pk), 21 | } 22 | } 23 | 24 | // SafeCopy returns a thread-safe copy. 25 | func (e *BinaryPublicEncryptor[T]) SafeCopy() *BinaryPublicEncryptor[T] { 26 | return &BinaryPublicEncryptor[T]{ 27 | BinaryEncoder: e.BinaryEncoder, 28 | Params: e.Params, 29 | Encryptor: e.Encryptor.SafeCopy(), 30 | } 31 | } 32 | 33 | // EncryptLWEBool encrypts boolean message to LWE ciphertexts. 34 | // Like most languages, false == 0, and true == 1. 35 | // 36 | // Note that this is different from calling EncryptLWE with 0 or 1. 37 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBool(message bool) LWECiphertext[T] { 38 | return e.Encryptor.EncryptLWEPlaintext(e.EncodeLWEBool(message)) 39 | } 40 | 41 | // EncryptLWEBoolTo encrypts boolean message to LWE ciphertexts. 42 | // Like most languages, false == 0, and true == 1. 43 | // 44 | // Note that this is different from calling EncryptLWE with 0 or 1. 45 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBoolTo(ctOut LWECiphertext[T], message bool) { 46 | e.Encryptor.EncryptLWEPlaintextTo(ctOut, e.EncodeLWEBool(message)) 47 | } 48 | 49 | // EncryptLWEBits encrypts each bits of an integer message. 50 | // The order of the bits are little-endian. 51 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBits(message, bits int) []LWECiphertext[T] { 52 | ctOut := make([]LWECiphertext[T], bits) 53 | e.EncryptLWEBitsTo(ctOut, message) 54 | return ctOut 55 | } 56 | 57 | // EncryptLWEBitsTo encrypts each bits of an integer message. 58 | // The order of the bits are little-endian, 59 | // and will be cut by the length of ctOut. 60 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBitsTo(ctOut []LWECiphertext[T], message int) { 61 | for i := 0; i < len(ctOut); i++ { 62 | ctOut[i] = e.EncryptLWEBool(message&1 == 1) 63 | message >>= 1 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tfhe/binary_encryptor_public.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // BinaryPublicEncryptor encrypts binary TFHE ciphertexts with public key. 4 | // This is meant to be public, usually for servers. 5 | // 6 | // We use compact public key, explained in https://eprint.iacr.org/2023/603. 7 | // This means that not all parameters support public key encryption. 8 | // 9 | // BinaryEncryptor is not safe for concurrent use. 10 | // Use [*BinaryPublicEncryptor.SafeCopy] to get a safe copy. 11 | type BinaryPublicEncryptor[T TorusInt] struct { 12 | // BinaryEncoder is an embedded encoder for this BinaryPublicEncryptor. 13 | *BinaryEncoder[T] 14 | // Params is parameters for this BinaryPublicEncryptor. 15 | Params Parameters[T] 16 | // Encryptor is a generic PublicEncryptor for this BinaryPublicEncryptor. 17 | Encryptor *PublicEncryptor[T] 18 | } 19 | 20 | // NewBinaryPublicEncryptor creates a new BinaryPublicEncryptor. 21 | func NewBinaryPublicEncryptor[T TorusInt](params Parameters[T], pk PublicKey[T]) *BinaryPublicEncryptor[T] { 22 | return &BinaryPublicEncryptor[T]{ 23 | BinaryEncoder: NewBinaryEncoder(params), 24 | Params: params, 25 | Encryptor: NewPublicEncryptor(params, pk), 26 | } 27 | } 28 | 29 | // SafeCopy returns a thread-safe copy. 30 | func (e *BinaryPublicEncryptor[T]) SafeCopy() *BinaryPublicEncryptor[T] { 31 | return &BinaryPublicEncryptor[T]{ 32 | BinaryEncoder: e.BinaryEncoder, 33 | Params: e.Params, 34 | Encryptor: e.Encryptor.SafeCopy(), 35 | } 36 | } 37 | 38 | // EncryptLWEBool encrypts boolean message to LWE ciphertexts. 39 | // 40 | // Note that this is different from calling EncryptLWE with 0 or 1. 41 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBool(message bool) LWECiphertext[T] { 42 | return e.Encryptor.EncryptLWEPlaintext(e.EncodeLWEBool(message)) 43 | } 44 | 45 | // EncryptLWEBoolTo encrypts boolean message to LWE ciphertexts. 46 | // 47 | // Note that this is different from calling EncryptLWE with 0 or 1. 48 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBoolTo(ctOut LWECiphertext[T], message bool) { 49 | e.Encryptor.EncryptLWEPlaintextTo(ctOut, e.EncodeLWEBool(message)) 50 | } 51 | 52 | // EncryptLWEBits encrypts each bits of an integer message. 53 | // The order of the bits are little-endian. 54 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBits(message, bits int) []LWECiphertext[T] { 55 | ctOut := make([]LWECiphertext[T], bits) 56 | e.EncryptLWEBitsTo(ctOut, message) 57 | return ctOut 58 | } 59 | 60 | // EncryptLWEBitsTo encrypts each bits of an integer message. 61 | // The order of the bits are little-endian, 62 | // and will be cut by the length of ctOut. 63 | func (e *BinaryPublicEncryptor[T]) EncryptLWEBitsTo(ctOut []LWECiphertext[T], message int) { 64 | for i := 0; i < len(ctOut); i++ { 65 | ctOut[i] = e.EncryptLWEBool(message&1 == 1) 66 | message >>= 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /xtfhe/fhew_enc.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // FHEWEncryptor wraps around [tfhe.Encryptor] and implements FHEW encryption algorithm. 9 | type FHEWEncryptor[T tfhe.TorusInt] struct { 10 | // Encryptor is an embedded [tfhe.Encryptor] for this FHEWEncryptor. 11 | *tfhe.Encryptor[T] 12 | 13 | // Params is parameters for this FHEWEncryptor. 14 | Params FHEWParameters[T] 15 | 16 | buf fhewEncryptorBuffer[T] 17 | } 18 | 19 | // fhewEncryptorBuffer is a buffer for FHEWEncryptor. 20 | type fhewEncryptorBuffer[T tfhe.TorusInt] struct { 21 | // skPermute is a permuted secret key. 22 | skPermute tfhe.GLWESecretKey[T] 23 | // ctGLWE is a standard GLWE Ciphertext for Fourier encryption / decryptions. 24 | ctGLWE tfhe.GLWECiphertext[T] 25 | // ptGGSW is GLWEKey * Pt in GGSW encryption. 26 | ptGGSW poly.Poly[T] 27 | } 28 | 29 | // NewFHEWEncryptor creates a new FHEWEncryptor. 30 | func NewFHEWEncryptor[T tfhe.TorusInt](params FHEWParameters[T]) *FHEWEncryptor[T] { 31 | encryptor := FHEWEncryptor[T]{ 32 | Encryptor: tfhe.NewEncryptorWithKey(params.BaseParams(), tfhe.SecretKey[T]{}), 33 | Params: params, 34 | buf: newFHEWEncryptorBuffer(params), 35 | } 36 | encryptor.Encryptor.SecretKey = encryptor.GenSecretKey() 37 | 38 | return &encryptor 39 | } 40 | 41 | // NewFHEWEncryptorWithKey creates a new FHEWEncryptor with given parameters and secret key. 42 | func NewFHEWEncryptorWithKey[T tfhe.TorusInt](params FHEWParameters[T], sk tfhe.SecretKey[T]) *FHEWEncryptor[T] { 43 | return &FHEWEncryptor[T]{ 44 | Encryptor: tfhe.NewEncryptorWithKey(params.BaseParams(), sk), 45 | Params: params, 46 | buf: newFHEWEncryptorBuffer(params), 47 | } 48 | } 49 | 50 | // newFHEWEncryptorBuffer creates a new fhewEncryptorBuffer. 51 | func newFHEWEncryptorBuffer[T tfhe.TorusInt](params FHEWParameters[T]) fhewEncryptorBuffer[T] { 52 | return fhewEncryptorBuffer[T]{ 53 | skPermute: tfhe.NewGLWESecretKey(params.BaseParams()), 54 | ctGLWE: tfhe.NewGLWECiphertext(params.BaseParams()), 55 | ptGGSW: poly.NewPoly[T](params.BaseParams().PolyRank()), 56 | } 57 | } 58 | 59 | // SafeCopy returns a thread-safe copy. 60 | func (e *FHEWEncryptor[T]) SafeCopy() *FHEWEncryptor[T] { 61 | return &FHEWEncryptor[T]{ 62 | Encryptor: e.Encryptor.SafeCopy(), 63 | Params: e.Params, 64 | buf: newFHEWEncryptorBuffer(e.Params), 65 | } 66 | } 67 | 68 | // GenSecretKey samples a new SecretKey. 69 | // The SecretKey of the Encryptor is not changed. 70 | func (e *FHEWEncryptor[T]) GenSecretKey() tfhe.SecretKey[T] { 71 | sk := tfhe.NewSecretKey(e.Params.baseParams) 72 | 73 | e.GaussianSampler.SampleVecTo(sk.LWELargeKey.Value, e.Params.SecretKeyStdDevQ()) 74 | e.FwdFFTGLWESecretKeyTo(sk.FFTGLWEKey, sk.GLWEKey) 75 | 76 | return sk 77 | } 78 | -------------------------------------------------------------------------------- /tfhe/keyswitch.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // KeySwitchLWE switches key of ct. 4 | // Input ciphertext should be of length ksk.InputLWEDimension + 1. 5 | func (e *Evaluator[T]) KeySwitchLWE(ct LWECiphertext[T], ksk LWEKeySwitchKey[T]) LWECiphertext[T] { 6 | ctOut := NewLWECiphertext(e.Params) 7 | e.KeySwitchLWETo(ctOut, ct, ksk) 8 | return ctOut 9 | } 10 | 11 | // KeySwitchLWETo switches key of ct and writes it to ctOut. 12 | // Input ciphertext should be of length ksk.InputLWEDimension + 1. 13 | func (e *Evaluator[T]) KeySwitchLWETo(ctOut, ct LWECiphertext[T], ksk LWEKeySwitchKey[T]) { 14 | cDcmp := e.Decomposer.ScalarBuffer(ksk.GadgetParams) 15 | 16 | e.Decomposer.DecomposeScalarTo(cDcmp, ct.Value[1], ksk.GadgetParams) 17 | e.ScalarMulLWETo(e.buf.ctProdLWE, ksk.Value[0].Value[0], cDcmp[0]) 18 | for j := 1; j < ksk.GadgetParams.level; j++ { 19 | e.ScalarMulAddLWETo(e.buf.ctProdLWE, ksk.Value[0].Value[j], cDcmp[j]) 20 | } 21 | 22 | for i := 1; i < ksk.InputLWEDimension(); i++ { 23 | e.Decomposer.DecomposeScalarTo(cDcmp, ct.Value[i+1], ksk.GadgetParams) 24 | for j := 0; j < ksk.GadgetParams.level; j++ { 25 | e.ScalarMulAddLWETo(e.buf.ctProdLWE, ksk.Value[i].Value[j], cDcmp[j]) 26 | } 27 | } 28 | 29 | ctOut.Value[0] = ct.Value[0] + e.buf.ctProdLWE.Value[0] 30 | copy(ctOut.Value[1:], e.buf.ctProdLWE.Value[1:]) 31 | } 32 | 33 | // KeySwitchGLWE switches key of ct. 34 | // Input ciphertext should be of length ksk.InputGLWERank + 1. 35 | func (e *Evaluator[T]) KeySwitchGLWE(ct GLWECiphertext[T], ksk GLWEKeySwitchKey[T]) GLWECiphertext[T] { 36 | ctOut := NewGLWECiphertext(e.Params) 37 | e.KeySwitchGLWETo(ctOut, ct, ksk) 38 | return ctOut 39 | } 40 | 41 | // KeySwitchGLWETo switches key of ct and writes it to ctOut. 42 | // Input ciphertext should be of length ksk.InputGLWERank + 1. 43 | func (e *Evaluator[T]) KeySwitchGLWETo(ctOut, ct GLWECiphertext[T], ksk GLWEKeySwitchKey[T]) { 44 | fpDcmp := e.Decomposer.FFTPolyBuffer(ksk.GadgetParameters) 45 | 46 | e.Decomposer.FourierDecomposePolyTo(fpDcmp, ct.Value[1], ksk.GadgetParameters) 47 | e.FFTPolyMulFFTGLWETo(e.buf.ctFFTProdGLWE, ksk.Value[0].Value[0], fpDcmp[0]) 48 | for j := 1; j < ksk.GadgetParameters.level; j++ { 49 | e.FFTPolyMulAddFFTGLWETo(e.buf.ctFFTProdGLWE, ksk.Value[0].Value[j], fpDcmp[j]) 50 | } 51 | 52 | for i := 1; i < ksk.InputGLWERank(); i++ { 53 | e.Decomposer.FourierDecomposePolyTo(fpDcmp, ct.Value[i+1], ksk.GadgetParameters) 54 | for j := 0; j < ksk.GadgetParameters.level; j++ { 55 | e.FFTPolyMulAddFFTGLWETo(e.buf.ctFFTProdGLWE, ksk.Value[i].Value[j], fpDcmp[j]) 56 | } 57 | } 58 | 59 | ctOut.Value[0].CopyFrom(ct.Value[0]) 60 | e.PolyEvaluator.InvFFTAddToUnsafe(ctOut.Value[0], e.buf.ctFFTProdGLWE.Value[0]) 61 | for i := 0; i < e.Params.glweRank; i++ { 62 | e.PolyEvaluator.InvFFTToUnsafe(ctOut.Value[i+1], e.buf.ctFFTProdGLWE.Value[i+1]) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/go,windows,macos,linux,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,windows,macos,linux,visualstudiocode 3 | 4 | ### Go ### 5 | # If you prefer the allow list template instead of the deny list, see community template: 6 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 7 | # 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | 27 | ### Linux ### 28 | *~ 29 | 30 | # temporary files which can be created if a process still has a handle open of a deleted file 31 | .fuse_hidden* 32 | 33 | # KDE directory preferences 34 | .directory 35 | 36 | # Linux trash folder which might appear on any partition or disk 37 | .Trash-* 38 | 39 | # .nfs files are created when an open file is removed but is still being accessed 40 | .nfs* 41 | 42 | ### macOS ### 43 | # General 44 | .DS_Store 45 | .AppleDouble 46 | .LSOverride 47 | 48 | # Icon must end with two \r 49 | Icon 50 | 51 | 52 | # Thumbnails 53 | ._* 54 | 55 | # Files that might appear in the root of a volume 56 | .DocumentRevisions-V100 57 | .fseventsd 58 | .Spotlight-V100 59 | .TemporaryItems 60 | .Trashes 61 | .VolumeIcon.icns 62 | .com.apple.timemachine.donotpresent 63 | 64 | # Directories potentially created on remote AFP share 65 | .AppleDB 66 | .AppleDesktop 67 | Network Trash Folder 68 | Temporary Items 69 | .apdisk 70 | 71 | ### macOS Patch ### 72 | # iCloud generated files 73 | *.icloud 74 | 75 | ### VisualStudioCode ### 76 | .vscode/* 77 | !.vscode/settings.json 78 | !.vscode/tasks.json 79 | !.vscode/launch.json 80 | !.vscode/extensions.json 81 | !.vscode/*.code-snippets 82 | 83 | # Local History for Visual Studio Code 84 | .history/ 85 | 86 | # Built Visual Studio Code Extensions 87 | *.vsix 88 | 89 | ### VisualStudioCode Patch ### 90 | # Ignore all local history of files 91 | .history 92 | .ionide 93 | 94 | ### Windows ### 95 | # Windows thumbnail cache files 96 | Thumbs.db 97 | Thumbs.db:encryptable 98 | ehthumbs.db 99 | ehthumbs_vista.db 100 | 101 | # Dump file 102 | *.stackdump 103 | 104 | # Folder config file 105 | [Dd]esktop.ini 106 | 107 | # Recycle Bin used on file shares 108 | $RECYCLE.BIN/ 109 | 110 | # Windows Installer files 111 | *.cab 112 | *.msi 113 | *.msix 114 | *.msm 115 | *.msp 116 | 117 | # Windows shortcuts 118 | *.lnk 119 | 120 | # End of https://www.toptal.com/developers/gitignore/api/go,windows,macos,linux,visualstudiocode 121 | 122 | scratchpad/ 123 | -------------------------------------------------------------------------------- /tfhe/lwe_ops.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/vec" 5 | ) 6 | 7 | // AddLWE returns ct0 + ct1. 8 | func (e *Evaluator[T]) AddLWE(ct0, ct1 LWECiphertext[T]) LWECiphertext[T] { 9 | ctOut := NewLWECiphertext(e.Params) 10 | e.AddLWETo(ctOut, ct0, ct1) 11 | return ctOut 12 | } 13 | 14 | // AddLWETo computes ctOut = ct0 + ct1. 15 | func (e *Evaluator[T]) AddLWETo(ctOut, ct0, ct1 LWECiphertext[T]) { 16 | vec.AddTo(ctOut.Value, ct0.Value, ct1.Value) 17 | } 18 | 19 | // AddPlainLWE returns ct + pt. 20 | func (e *Evaluator[T]) AddPlainLWE(ct LWECiphertext[T], pt LWEPlaintext[T]) LWECiphertext[T] { 21 | ctOut := NewLWECiphertext(e.Params) 22 | e.AddPlainLWETo(ctOut, ct, pt) 23 | return ctOut 24 | } 25 | 26 | // AddPlainLWETo computes ctOut = ct + pt. 27 | func (e *Evaluator[T]) AddPlainLWETo(ctOut, ct LWECiphertext[T], pt LWEPlaintext[T]) { 28 | ctOut.CopyFrom(ct) 29 | ctOut.Value[0] += pt.Value 30 | } 31 | 32 | // SubLWE returns ct0 - ct1. 33 | func (e *Evaluator[T]) SubLWE(ct0, ct1 LWECiphertext[T]) LWECiphertext[T] { 34 | ctOut := NewLWECiphertext(e.Params) 35 | e.SubLWETo(ctOut, ct0, ct1) 36 | return ctOut 37 | } 38 | 39 | // SubLWETo computes ctOut = ct0 - ct1. 40 | func (e *Evaluator[T]) SubLWETo(ctOut, ct0, ct1 LWECiphertext[T]) { 41 | vec.SubTo(ctOut.Value, ct0.Value, ct1.Value) 42 | } 43 | 44 | // SubPlainLWE returns ct - pt. 45 | func (e *Evaluator[T]) SubPlainLWE(ct LWECiphertext[T], pt LWEPlaintext[T]) LWECiphertext[T] { 46 | ctOut := NewLWECiphertext(e.Params) 47 | e.SubPlainLWETo(ctOut, ct, pt) 48 | return ctOut 49 | } 50 | 51 | // SubPlainLWETo computes ctOut = ct0 - pt. 52 | func (e *Evaluator[T]) SubPlainLWETo(ctOut, ct0 LWECiphertext[T], pt LWEPlaintext[T]) { 53 | ctOut.CopyFrom(ct0) 54 | ctOut.Value[0] -= pt.Value 55 | } 56 | 57 | // NegLWE returns -ct. 58 | func (e *Evaluator[T]) NegLWE(ct LWECiphertext[T]) LWECiphertext[T] { 59 | ctOut := NewLWECiphertext(e.Params) 60 | e.NegLWETo(ctOut, ct) 61 | return ctOut 62 | } 63 | 64 | // NegLWETo computes ctOut = -ct. 65 | func (e *Evaluator[T]) NegLWETo(ctOut, ct LWECiphertext[T]) { 66 | vec.NegTo(ctOut.Value, ct.Value) 67 | } 68 | 69 | // ScalarMulLWE returns c * ct. 70 | func (e *Evaluator[T]) ScalarMulLWE(ct LWECiphertext[T], c T) LWECiphertext[T] { 71 | ctOut := NewLWECiphertext(e.Params) 72 | e.ScalarMulLWETo(ctOut, ct, c) 73 | return ctOut 74 | } 75 | 76 | // ScalarMulLWETo computes ctOut = c * ct0. 77 | func (e *Evaluator[T]) ScalarMulLWETo(ctOut, ct LWECiphertext[T], c T) { 78 | vec.ScalarMulTo(ctOut.Value, ct.Value, c) 79 | } 80 | 81 | // ScalarMulAddLWETo computes ctOut += c * ct0. 82 | func (e *Evaluator[T]) ScalarMulAddLWETo(ctOut, ct LWECiphertext[T], c T) { 83 | vec.ScalarMulAddTo(ctOut.Value, ct.Value, c) 84 | } 85 | 86 | // ScalarMulSubLWETo computes ctOut -= c * ct0. 87 | func (e *Evaluator[T]) ScalarMulSubLWETo(ctOut, ct LWECiphertext[T], c T) { 88 | vec.ScalarMulSubTo(ctOut.Value, ct.Value, c) 89 | } 90 | -------------------------------------------------------------------------------- /math/poly/asm_fft_test.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | "math/rand" 7 | "testing" 8 | 9 | "github.com/sp301415/tfhe-go/math/vec" 10 | ) 11 | 12 | func fwdFFTInPlaceRef(coeffs, tw []complex128) { 13 | N := len(coeffs) 14 | 15 | t := N 16 | for m := 1; m <= N/2; m <<= 1 { 17 | t >>= 1 18 | for i := 0; i < m; i++ { 19 | j1 := i * t << 1 20 | j2 := j1 + t 21 | for j := j1; j < j2; j++ { 22 | U, V := coeffs[j], coeffs[j+t]*tw[i] 23 | coeffs[j], coeffs[j+t] = U+V, U-V 24 | } 25 | } 26 | } 27 | } 28 | 29 | func invFFTInPlaceRef(coeffs, twInv []complex128) { 30 | N := len(coeffs) 31 | 32 | t := 1 33 | for m := N / 2; m >= 1; m >>= 1 { 34 | for i := 0; i < m; i++ { 35 | j1 := i * t << 1 36 | j2 := j1 + t 37 | for j := j1; j < j2; j++ { 38 | U, V := coeffs[j], coeffs[j+t] 39 | coeffs[j], coeffs[j+t] = U+V, (U-V)*twInv[i] 40 | } 41 | } 42 | t <<= 1 43 | } 44 | } 45 | 46 | func TestFFTAssembly(t *testing.T) { 47 | r := rand.New(rand.NewSource(0)) 48 | 49 | N := 64 50 | eps := 1e-10 51 | 52 | coeffs := make([]complex128, N) 53 | for i := 0; i < N; i++ { 54 | coeffs[i] = complex(r.Float64(), r.Float64()) 55 | } 56 | coeffsAVX2 := vec.CmplxToFloat4(coeffs) 57 | coeffsAVX2Out := make([]complex128, N) 58 | 59 | twRef := make([]complex128, N/2) 60 | twInvRef := make([]complex128, N/2) 61 | for i := 0; i < N/2; i++ { 62 | e := -2 * math.Pi * float64(i) / float64(N) 63 | twRef[i] = cmplx.Exp(complex(0, e)) 64 | twInvRef[i] = cmplx.Exp(-complex(0, e)) 65 | } 66 | vec.BitReverseInPlace(twRef) 67 | vec.BitReverseInPlace(twInvRef) 68 | 69 | twist := make([]complex128, N) 70 | twistInv := make([]complex128, N) 71 | for i := 0; i < N; i++ { 72 | e := 2 * math.Pi * float64(i) / float64(4*N) 73 | twist[i] = cmplx.Exp(complex(0, e)) 74 | twistInv[i] = cmplx.Exp(-complex(0, e)) / complex(float64(N), 0) 75 | } 76 | 77 | tw, twInv := genTwiddleFactors(N) 78 | 79 | t.Run("FwdFFT", func(t *testing.T) { 80 | vec.CmplxToFloat4To(coeffsAVX2, coeffs) 81 | fwdFFTInPlace(coeffsAVX2, tw) 82 | vec.Float4ToCmplxTo(coeffsAVX2Out, coeffsAVX2) 83 | 84 | vec.MulTo(coeffs, coeffs, twist) 85 | fwdFFTInPlaceRef(coeffs, twRef) 86 | 87 | for i := 0; i < N; i++ { 88 | if cmplx.Abs(coeffs[i]-coeffsAVX2Out[i]) > eps { 89 | t.Fatalf("FwdFFT: %v != %v", coeffs[i], coeffsAVX2Out[i]) 90 | } 91 | } 92 | }) 93 | 94 | t.Run("InvFFT", func(t *testing.T) { 95 | vec.CmplxToFloat4To(coeffsAVX2, coeffs) 96 | invFFTInPlace(coeffsAVX2, twInv) 97 | vec.Float4ToCmplxTo(coeffsAVX2Out, coeffsAVX2) 98 | 99 | invFFTInPlaceRef(coeffs, twInvRef) 100 | vec.MulTo(coeffs, coeffs, twistInv) 101 | 102 | for i := 0; i < N; i++ { 103 | if cmplx.Abs(coeffs[i]-coeffsAVX2Out[i]) > eps { 104 | t.Fatalf("InvFFT: %v != %v", coeffs[i], coeffsAVX2Out[i]) 105 | } 106 | } 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /mktfhe/lwe_ops.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/vec" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // AddLWE returns ct0 + ct1. 9 | func (e *Evaluator[T]) AddLWE(ct0, ct1 LWECiphertext[T]) LWECiphertext[T] { 10 | ctOut := NewLWECiphertext(e.Params) 11 | e.AddLWETo(ctOut, ct0, ct1) 12 | return ctOut 13 | } 14 | 15 | // AddLWETo computes ctOut = ct0 + ct1. 16 | func (e *Evaluator[T]) AddLWETo(ctOut, ct0, ct1 LWECiphertext[T]) { 17 | vec.AddTo(ctOut.Value, ct0.Value, ct1.Value) 18 | } 19 | 20 | // AddPlainLWE returns ct0 + pt. 21 | func (e *Evaluator[T]) AddPlainLWE(ct0 LWECiphertext[T], pt tfhe.LWEPlaintext[T]) LWECiphertext[T] { 22 | ctOut := NewLWECiphertext(e.Params) 23 | e.AddPlainLWETo(ctOut, ct0, pt) 24 | return ctOut 25 | } 26 | 27 | // AddPlainLWETo computes ctOut = ct0 + pt. 28 | func (e *Evaluator[T]) AddPlainLWETo(ctOut LWECiphertext[T], ct0 LWECiphertext[T], pt tfhe.LWEPlaintext[T]) { 29 | ctOut.CopyFrom(ct0) 30 | ctOut.Value[0] += pt.Value 31 | } 32 | 33 | // SubLWE returns ct0 - ct1. 34 | func (e *Evaluator[T]) SubLWE(ct0, ct1 LWECiphertext[T]) LWECiphertext[T] { 35 | ctOut := NewLWECiphertext(e.Params) 36 | e.SubLWETo(ctOut, ct0, ct1) 37 | return ctOut 38 | } 39 | 40 | // SubLWETo computes ctOut = ct0 - ct1. 41 | func (e *Evaluator[T]) SubLWETo(ctOut, ct0, ct1 LWECiphertext[T]) { 42 | vec.SubTo(ctOut.Value, ct0.Value, ct1.Value) 43 | } 44 | 45 | // SubPlainLWE returns ct0 - pt. 46 | func (e *Evaluator[T]) SubPlainLWE(ct0 LWECiphertext[T], pt tfhe.LWEPlaintext[T]) LWECiphertext[T] { 47 | ctOut := NewLWECiphertext(e.Params) 48 | e.SubPlainLWETo(ctOut, ct0, pt) 49 | return ctOut 50 | } 51 | 52 | // SubPlainLWETo computes ctOut = ct0 - pt. 53 | func (e *Evaluator[T]) SubPlainLWETo(ctOut LWECiphertext[T], ct0 LWECiphertext[T], pt tfhe.LWEPlaintext[T]) { 54 | ctOut.CopyFrom(ct0) 55 | ctOut.Value[0] -= pt.Value 56 | } 57 | 58 | // NegLWE returns -ct0. 59 | func (e *Evaluator[T]) NegLWE(ct0 LWECiphertext[T]) LWECiphertext[T] { 60 | ctOut := NewLWECiphertext(e.Params) 61 | e.NegLWETo(ctOut, ct0) 62 | return ctOut 63 | } 64 | 65 | // NegLWETo computes ctOut = -ct0. 66 | func (e *Evaluator[T]) NegLWETo(ctOut, ct0 LWECiphertext[T]) { 67 | vec.NegTo(ctOut.Value, ct0.Value) 68 | } 69 | 70 | // ScalarMulLWE returns c * ct0. 71 | func (e *Evaluator[T]) ScalarMulLWE(ct0 LWECiphertext[T], c T) LWECiphertext[T] { 72 | ctOut := NewLWECiphertext(e.Params) 73 | e.ScalarMulLWETo(ctOut, ct0, c) 74 | return ctOut 75 | } 76 | 77 | // ScalarMulLWETo computes ctOut = c * ct0. 78 | func (e *Evaluator[T]) ScalarMulLWETo(ctOut LWECiphertext[T], ct0 LWECiphertext[T], c T) { 79 | vec.ScalarMulTo(ctOut.Value, ct0.Value, c) 80 | } 81 | 82 | // ScalarMulAddLWETo computes ctOut += c * ct0. 83 | func (e *Evaluator[T]) ScalarMulAddLWETo(ctOut LWECiphertext[T], ct0 LWECiphertext[T], c T) { 84 | vec.ScalarMulAddTo(ctOut.Value, ct0.Value, c) 85 | } 86 | 87 | // ScalarMulSubLWETo computes ctOut -= c * ct0. 88 | func (e *Evaluator[T]) ScalarMulSubLWETo(ctOut LWECiphertext[T], ct0 LWECiphertext[T], c T) { 89 | vec.ScalarMulSubTo(ctOut.Value, ct0.Value, c) 90 | } 91 | -------------------------------------------------------------------------------- /xtfhe/fhew_params.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/sp301415/tfhe-go/math/poly" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | ) 9 | 10 | // FHEWParametersLiteral is a structure for FHEW Parameters. 11 | // 12 | // BlockSize must be 1, and PolyRank does not equal LUTSize. 13 | type FHEWParametersLiteral[T tfhe.TorusInt] struct { 14 | // BaseParams is a base parameters for this FHEWParametersLiteral. 15 | BaseParams tfhe.ParametersLiteral[T] 16 | 17 | // SecretKeyStdDev is the standard deviation of the secret key. 18 | // Must be smaller than [poly.ShortPolyBound] / 8Q. 19 | SecretKeyStdDev float64 20 | 21 | // WindowSize is the window size for the FHEW blind rotation. 22 | WindowSize int 23 | } 24 | 25 | // Compile transforms ParametersLiteral to read-only Parameters. 26 | // If there is any invalid parameter in the literal, it panics. 27 | // Default parameters are guaranteed to be compiled without panics. 28 | func (p FHEWParametersLiteral[T]) Compile() FHEWParameters[T] { 29 | baseParams := p.BaseParams.Compile() 30 | 31 | logQ := float64(baseParams.LogQ()) 32 | logTailCut := math.Log2(GaussianTailCut) 33 | switch { 34 | case baseParams.BlockSize() != 1: 35 | panic("BlockSize not 1") 36 | case baseParams.PolyRank() != baseParams.LUTSize(): 37 | panic("PolyRank does not equal LUTSize") 38 | case p.SecretKeyStdDev <= 0: 39 | panic("SecretKeyStdDev smaller than or equal to zero") 40 | case math.Log2(p.SecretKeyStdDev)+logQ+logTailCut > poly.ShortLogBound: 41 | panic("SecretKeyStdDev too large") 42 | case p.WindowSize <= 0: 43 | panic("WindowSize smaller than or equal to zero") 44 | } 45 | 46 | return FHEWParameters[T]{ 47 | baseParams: baseParams, 48 | 49 | secretKeyStdDev: p.SecretKeyStdDev, 50 | windowSize: p.WindowSize, 51 | } 52 | } 53 | 54 | // FHEWParameters are read-only, compiled parameters for FHEW. 55 | type FHEWParameters[T tfhe.TorusInt] struct { 56 | // BaseParams is a base parameters for this FHEWParameters. 57 | baseParams tfhe.Parameters[T] 58 | 59 | // SecretKeyStdDev is the standard deviation of the secret key. 60 | // Must be smaller than [poly.ShortPolyBound] / 8Q. 61 | secretKeyStdDev float64 62 | 63 | // WindowSize is the window size for the FHEW blind rotation. 64 | windowSize int 65 | } 66 | 67 | // BaseParams returns the base parameters for this FHEWParameters. 68 | func (p FHEWParameters[T]) BaseParams() tfhe.Parameters[T] { 69 | return p.baseParams 70 | } 71 | 72 | // SecretKeyStdDev returns the standard deviation of the secret key. 73 | // 74 | // This is a normalized standard deviation. 75 | // For actual sampling, use [FHEWParameters.SecretKeyStdDevQ]. 76 | func (p FHEWParameters[T]) SecretKeyStdDev() float64 { 77 | return p.secretKeyStdDev 78 | } 79 | 80 | // SecretKeyStdDevQ returns SecretKeyStdDev * Q 81 | func (p FHEWParameters[T]) SecretKeyStdDevQ() float64 { 82 | return p.secretKeyStdDev * math.Exp2(float64(p.baseParams.LogQ())) 83 | } 84 | 85 | // WindowSize returns the window size for the FHEW blind rotation. 86 | func (p FHEWParameters[T]) WindowSize() int { 87 | return p.windowSize 88 | } 89 | -------------------------------------------------------------------------------- /internal/asmgen/asmgen.go: -------------------------------------------------------------------------------- 1 | //go:generate go run . -fold -out ../../math/poly/asm_fold_amd64.s -stubs ../../math/poly/asm_fold_stub_amd64.go -pkg=poly 2 | //go:generate go run . -fft -out ../../math/poly/asm_fft_amd64.s -stubs ../../math/poly/asm_fft_stub_amd64.go -pkg=poly 3 | //go:generate go run . -vec_cmplx -out ../../math/poly/asm_vec_cmplx_amd64.s -stubs ../../math/poly/asm_vec_cmplx_stub_amd64.go -pkg=poly 4 | //go:generate go run . -vec -out ../../math/vec/asm_vec_amd64.s -stubs ../../math/vec/asm_vec_stub_amd64.go -pkg=vec 5 | //go:generate go run . -decompose -out ../../tfhe/asm_decompose_amd64.s -stubs ../../tfhe/asm_decompose_stub_amd64.go -pkg=tfhe 6 | package main 7 | 8 | import ( 9 | "flag" 10 | 11 | . "github.com/mmcloughlin/avo/build" 12 | "github.com/mmcloughlin/avo/buildtags" 13 | ) 14 | 15 | type OpType int 16 | 17 | const ( 18 | OpPure OpType = iota 19 | OpAdd 20 | OpSub 21 | ) 22 | 23 | var ( 24 | fold = flag.Bool("fold", false, "asm_fold_amd64.s") 25 | fft = flag.Bool("fft", false, "asm_fft_amd64.s") 26 | vecCmplx = flag.Bool("vec_cmplx", false, "asm_vec_cmplx_amd64.s") 27 | 28 | vec = flag.Bool("vec", false, "asm_vec_amd64.s") 29 | 30 | decompose = flag.Bool("decompose", false, "asm_decompose_amd64.s") 31 | ) 32 | 33 | func main() { 34 | flag.Parse() 35 | 36 | Constraint(buildtags.Term("amd64")) 37 | Constraint(buildtags.Not("purego")) 38 | 39 | if *fold { 40 | FoldConstants() 41 | 42 | FoldPolyToUint32AVX2() 43 | foldPolyToUint64AVX2() 44 | 45 | FloatModQInPlaceAVX2() 46 | 47 | UnfoldPolyToUint32AVX2(OpPure) 48 | UnfoldPolyToUint32AVX2(OpAdd) 49 | UnfoldPolyToUint32AVX2(OpSub) 50 | 51 | UnfoldPolyToUint64AVX2(OpPure) 52 | UnfoldPolyToUint64AVX2(OpAdd) 53 | UnfoldPolyToUint64AVX2(OpSub) 54 | 55 | } 56 | 57 | if *fft { 58 | FwdFFTInPlaceAVX2() 59 | InvFFTInPlaceAVX2() 60 | } 61 | 62 | if *vecCmplx { 63 | AddSubCmplxToAVX2(OpAdd) 64 | AddSubCmplxToAVX2(OpSub) 65 | 66 | NegCmplxToAVX2() 67 | 68 | FloatMulCmplxToAVX2(OpPure) 69 | FloatMulCmplxToAVX2(OpAdd) 70 | FloatMulCmplxToAVX2(OpSub) 71 | 72 | CmplxMulCmplxToAVX2(OpPure) 73 | CmplxMulCmplxToAVX2(OpAdd) 74 | CmplxMulCmplxToAVX2(OpSub) 75 | 76 | MulCmplxToAVX2(OpPure) 77 | MulCmplxToAVX2(OpAdd) 78 | MulCmplxToAVX2(OpSub) 79 | } 80 | 81 | if *vec { 82 | VecConstants() 83 | 84 | AddSubToUint32AVX2(OpAdd) 85 | AddSubToUint32AVX2(OpSub) 86 | 87 | AddToUint64AVX2(OpAdd) 88 | AddToUint64AVX2(OpSub) 89 | 90 | ScalarMulToUint32AVX2(OpPure) 91 | ScalarMulToUint32AVX2(OpAdd) 92 | ScalarMulToUint32AVX2(OpSub) 93 | 94 | ScalarMulToUint64AVX2(OpPure) 95 | ScalarMulToUint64AVX2(OpAdd) 96 | ScalarMulToUint64AVX2(OpSub) 97 | 98 | MulToUint32AVX2(OpPure) 99 | MulToUint32AVX2(OpAdd) 100 | MulToUint32AVX2(OpSub) 101 | 102 | MulToUint64AVX2(OpPure) 103 | MulToUint64AVX2(OpAdd) 104 | MulToUint64AVX2(OpSub) 105 | } 106 | 107 | if *decompose { 108 | DecomposeConstants() 109 | 110 | DecomposePolyToUint32AVX2() 111 | DecomposePolyToUint64AVX2() 112 | } 113 | 114 | Generate() 115 | } 116 | -------------------------------------------------------------------------------- /xtfhe/circuit_bootstrap_params.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/tfhe" 5 | ) 6 | 7 | // CircuitBootstrapParametersLiteral is a structure for Circuit Bootstrapping Parameters. 8 | type CircuitBootstrapParametersLiteral[T tfhe.TorusInt] struct { 9 | // ManyLUTParams is a base ManyLUTParams for this CircuitBootstrapParametersLiteral. 10 | ManyLUTParams ManyLUTParametersLiteral[T] 11 | 12 | // SchemeSwitchParams is the gadget parameters for scheme switching. 13 | SchemeSwitchParams tfhe.GadgetParametersLiteral[T] 14 | // TraceKeySwitchParams is the gadget parameters for LWE to GLWE packing. 15 | TraceKeySwitchParams tfhe.GadgetParametersLiteral[T] 16 | // OutputParams is the gadget parameters for the output of circuit bootstrapping. 17 | OutputParams tfhe.GadgetParametersLiteral[T] 18 | } 19 | 20 | // Compile transforms ParametersLiteral to read-only Parameters. 21 | // If there is any invalid parameter in the literal, it panics. 22 | // Default parameters are guaranteed to be compiled without panics. 23 | func (p CircuitBootstrapParametersLiteral[T]) Compile() CircuitBootstrapParameters[T] { 24 | return CircuitBootstrapParameters[T]{ 25 | manyLUTParameters: p.ManyLUTParams.Compile(), 26 | 27 | schemeSwitchParameters: p.SchemeSwitchParams.Compile(), 28 | traceKeySwitchParameters: p.TraceKeySwitchParams.Compile(), 29 | outputParameters: p.OutputParams.Compile(), 30 | } 31 | } 32 | 33 | // CircuitBootstrapParametersLiteral is a parameter set for Circuit Bootstrapping. 34 | type CircuitBootstrapParameters[T tfhe.TorusInt] struct { 35 | // manyLUTParameters is a base ManyLUTParameters for this CircuitBootstrapParameters. 36 | manyLUTParameters ManyLUTParameters[T] 37 | 38 | // schemeSwitchParameters is the gadget parameters for scheme switching. 39 | schemeSwitchParameters tfhe.GadgetParameters[T] 40 | // traceKeySwitchParameters is the gadget parameters for LWE to GLWE packing. 41 | traceKeySwitchParameters tfhe.GadgetParameters[T] 42 | // outputParameters is the gadget parameters for the output of circuit bootstrapping. 43 | outputParameters tfhe.GadgetParameters[T] 44 | } 45 | 46 | // ManyLUTParams returns the base ManyLUTParams for this CircuitBootstrapParameters. 47 | func (p CircuitBootstrapParameters[T]) ManyLUTParams() ManyLUTParameters[T] { 48 | return p.manyLUTParameters 49 | } 50 | 51 | // Params returns the base parameters for this ManyLUTParameters. 52 | func (p CircuitBootstrapParameters[T]) Params() tfhe.Parameters[T] { 53 | return p.manyLUTParameters.baseParams 54 | } 55 | 56 | // SchemeSwitchParams returns the gadget parameters for scheme switching. 57 | func (p CircuitBootstrapParameters[T]) SchemeSwitchParams() tfhe.GadgetParameters[T] { 58 | return p.schemeSwitchParameters 59 | } 60 | 61 | // TraceKeySwitchParams returns the gadget parameters for LWE to GLWE packing. 62 | func (p CircuitBootstrapParameters[T]) TraceKeySwitchParams() tfhe.GadgetParameters[T] { 63 | return p.traceKeySwitchParameters 64 | } 65 | 66 | // OutputParams returns the gadget parameters for the output of circuit bootstrapping. 67 | func (p CircuitBootstrapParameters[T]) OutputParams() tfhe.GadgetParameters[T] { 68 | return p.outputParameters 69 | } 70 | -------------------------------------------------------------------------------- /xtfhe/sanitization_sampler.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "math" 5 | "sort" 6 | 7 | "github.com/sp301415/tfhe-go/math/csprng" 8 | "github.com/sp301415/tfhe-go/math/num" 9 | "github.com/sp301415/tfhe-go/math/poly" 10 | ) 11 | 12 | const ( 13 | // GaussianTailCut is the tail cut for the Gaussian sampler. 14 | // Essentially, for a Gaussian variable x, 15 | // center - tailCut * stdDev <= x <= center + tailCut * stdDev 16 | // with overwhelming probability. 17 | GaussianTailCut = 12 18 | ) 19 | 20 | // CDTSampler samples from a discrete Gaussian distribution 21 | // with a fixed center and standard deviation. 22 | type CDTSampler[T num.Integer] struct { 23 | baseSampler *csprng.UniformSampler[uint64] 24 | 25 | center float64 26 | stdDev float64 27 | 28 | table []uint64 29 | offset int64 30 | } 31 | 32 | // NewCDTSampler creates a new CDTSampler. 33 | func NewCDTSampler[T num.Integer](center float64, stdDev float64) *CDTSampler[T] { 34 | cFloor := math.Floor(center) 35 | cFrac := center - cFloor 36 | 37 | tailLo := int64(math.Floor(center - GaussianTailCut*stdDev)) 38 | tailHi := int64(math.Ceil(center + GaussianTailCut*stdDev)) 39 | tableSize := tailHi - tailLo + 1 40 | 41 | table := make([]uint64, tableSize) 42 | cdf := 0.0 43 | for i, x := 0, tailLo; x <= tailHi; i, x = i+1, x+1 { 44 | xf := float64(x) 45 | cdf += math.Exp(-(xf-cFrac)*(xf-cFrac)/(2*stdDev*stdDev)) / (math.Sqrt(2*math.Pi) * stdDev) 46 | if cdf >= 1 { 47 | table[i] = math.MaxUint64 48 | } else { 49 | table[i] = uint64(cdf * math.MaxUint64) 50 | } 51 | } 52 | 53 | return &CDTSampler[T]{ 54 | baseSampler: csprng.NewUniformSampler[uint64](), 55 | 56 | center: center, 57 | stdDev: stdDev, 58 | 59 | table: table, 60 | offset: tailLo + int64(cFloor), 61 | } 62 | } 63 | 64 | // Center returns the center of the Gaussian distribution. 65 | func (s *CDTSampler[T]) Center() float64 { 66 | return s.center 67 | } 68 | 69 | // StdDev returns the standard deviation of the Gaussian distribution. 70 | func (s *CDTSampler[T]) StdDev() float64 { 71 | return s.stdDev 72 | } 73 | 74 | // Sample samples from the discrete Gaussian distribution. 75 | func (s *CDTSampler[T]) Sample() T { 76 | r := s.baseSampler.Sample() 77 | 78 | x := sort.Search(len(s.table), func(i int) bool { 79 | return s.table[i] > r 80 | }) 81 | 82 | return T(int64(x) + s.offset) 83 | } 84 | 85 | // SampleVecTo samples Gaussian values and writes it to vOut. 86 | func (s *CDTSampler[T]) SampleVecTo(vOut []T) { 87 | for i := range vOut { 88 | vOut[i] = s.Sample() 89 | } 90 | } 91 | 92 | // SamplePolyTo samples Gaussian values and writes it to pOut. 93 | func (s *CDTSampler[T]) SamplePolyTo(pOut poly.Poly[T]) { 94 | s.SampleVecTo(pOut.Coeffs) 95 | } 96 | 97 | // SamplePolyAddTo samples Gaussian values and adds to pOut. 98 | func (s *CDTSampler[T]) SamplePolyAddTo(pOut poly.Poly[T]) { 99 | for i := range pOut.Coeffs { 100 | pOut.Coeffs[i] += s.Sample() 101 | } 102 | } 103 | 104 | // SamplePolySubTo samples Gaussian values and subtracts from pOut. 105 | func (s *CDTSampler[T]) SamplePolySubTo(pOut poly.Poly[T]) { 106 | for i := range pOut.Coeffs { 107 | pOut.Coeffs[i] -= s.Sample() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /math/csprng/uniform_sampler.go: -------------------------------------------------------------------------------- 1 | package csprng 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "crypto/sha512" 8 | 9 | "github.com/sp301415/tfhe-go/math/num" 10 | "github.com/sp301415/tfhe-go/math/poly" 11 | ) 12 | 13 | // bufSize is the default buffer size of UniformSampler. 14 | const bufSize = 8192 15 | 16 | // UniformSampler samples values from uniform distribution. 17 | // This uses AES-CTR as a underlying prng. 18 | type UniformSampler[T num.Integer] struct { 19 | prng cipher.Stream 20 | 21 | buf [bufSize]byte 22 | ptr int 23 | 24 | byteSizeT int 25 | maxT T 26 | } 27 | 28 | // NewUniformSampler creates a new UniformSampler. 29 | // 30 | // Panics when read from crypto/rand or AES initialization fails. 31 | func NewUniformSampler[T num.Integer]() *UniformSampler[T] { 32 | var seed [32]byte 33 | if _, err := rand.Read(seed[:]); err != nil { 34 | panic(err) 35 | } 36 | 37 | return NewUniformSamplerWithSeed[T](seed[:]) 38 | } 39 | 40 | // NewUniformSamplerWithSeed creates a new UniformSampler, with user supplied seed. 41 | // 42 | // Panics when AES initialization fails. 43 | func NewUniformSamplerWithSeed[T num.Integer](seed []byte) *UniformSampler[T] { 44 | r := sha512.Sum384(seed) 45 | 46 | block, err := aes.NewCipher(r[:32]) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | prng := cipher.NewCTR(block, r[32:]) 52 | 53 | return &UniformSampler[T]{ 54 | prng: prng, 55 | 56 | buf: [bufSize]byte{}, 57 | ptr: bufSize, 58 | 59 | byteSizeT: num.ByteSizeT[T](), 60 | maxT: T(num.MaxT[T]()), 61 | } 62 | } 63 | 64 | // Sample uniformly samples a random integer of type T. 65 | func (s *UniformSampler[T]) Sample() T { 66 | if s.ptr == bufSize { 67 | s.prng.XORKeyStream(s.buf[:], s.buf[:]) 68 | s.ptr = 0 69 | } 70 | 71 | var res uint64 72 | switch s.byteSizeT { 73 | case 1: 74 | res |= uint64(s.buf[s.ptr+0]) 75 | case 2: 76 | res |= uint64(s.buf[s.ptr+0]) 77 | res |= uint64(s.buf[s.ptr+1]) << 8 78 | case 4: 79 | res |= uint64(s.buf[s.ptr+0]) 80 | res |= uint64(s.buf[s.ptr+1]) << 8 81 | res |= uint64(s.buf[s.ptr+2]) << 16 82 | res |= uint64(s.buf[s.ptr+3]) << 24 83 | case 8: 84 | res |= uint64(s.buf[s.ptr+0]) 85 | res |= uint64(s.buf[s.ptr+1]) << 8 86 | res |= uint64(s.buf[s.ptr+2]) << 16 87 | res |= uint64(s.buf[s.ptr+3]) << 24 88 | res |= uint64(s.buf[s.ptr+4]) << 32 89 | res |= uint64(s.buf[s.ptr+5]) << 40 90 | res |= uint64(s.buf[s.ptr+6]) << 48 91 | res |= uint64(s.buf[s.ptr+7]) << 56 92 | } 93 | s.ptr += s.byteSizeT 94 | 95 | return T(res) 96 | } 97 | 98 | // SampleN uniformly samples a random integer of type T in [0, N). 99 | func (s *UniformSampler[T]) SampleN(N T) T { 100 | bound := s.maxT - (s.maxT % N) 101 | for { 102 | res := s.Sample() 103 | if 0 <= res && res < bound { 104 | return res % N 105 | } 106 | } 107 | } 108 | 109 | // SampleVecTo samples uniform values to vOut. 110 | func (s *UniformSampler[T]) SampleVecTo(vOut []T) { 111 | for i := range vOut { 112 | vOut[i] = s.Sample() 113 | } 114 | } 115 | 116 | // SamplePolyTo samples uniform values to p. 117 | func (s *UniformSampler[T]) SamplePolyTo(pOut poly.Poly[T]) { 118 | s.SampleVecTo(pOut.Coeffs) 119 | } 120 | -------------------------------------------------------------------------------- /math/poly/poly_test.go: -------------------------------------------------------------------------------- 1 | package poly_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | "testing" 8 | 9 | "github.com/sp301415/tfhe-go/math/poly" 10 | ) 11 | 12 | var ( 13 | LogN = []int{9, 10, 11, 12, 13, 14, 15} 14 | ) 15 | 16 | func BenchmarkOps(b *testing.B) { 17 | r := rand.New(rand.NewSource(0)) 18 | 19 | for _, logN := range LogN { 20 | b.Run(fmt.Sprintf("LogN=%v", logN), func(b *testing.B) { 21 | N := 1 << logN 22 | 23 | pev := poly.NewEvaluator[uint64](N) 24 | 25 | p0 := pev.NewPoly() 26 | p1 := pev.NewPoly() 27 | pOut := pev.NewPoly() 28 | 29 | for i := 0; i < pev.Rank(); i++ { 30 | p0.Coeffs[i] = r.Uint64() 31 | p1.Coeffs[i] = r.Uint64() 32 | } 33 | 34 | fp := pev.FwdFFTPoly(p0) 35 | 36 | b.Run("Add", func(b *testing.B) { 37 | for i := 0; i < b.N; i++ { 38 | pev.AddPolyTo(pOut, p0, p1) 39 | } 40 | }) 41 | 42 | b.Run("Sub", func(b *testing.B) { 43 | for i := 0; i < b.N; i++ { 44 | pev.SubPolyTo(pOut, p0, p1) 45 | } 46 | }) 47 | 48 | b.Run("BinaryMul", func(b *testing.B) { 49 | for i := 0; i < b.N; i++ { 50 | pev.ShortFFTPolyMulPolyTo(pOut, p0, fp) 51 | } 52 | }) 53 | 54 | b.Run("Mul", func(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | pev.MulPolyTo(pOut, p0, p1) 57 | } 58 | }) 59 | }) 60 | } 61 | } 62 | 63 | func BenchmarkFFTOps(b *testing.B) { 64 | r := rand.New(rand.NewSource(0)) 65 | 66 | for _, logN := range LogN { 67 | b.Run(fmt.Sprintf("LogN=%v", logN), func(b *testing.B) { 68 | N := 1 << logN 69 | 70 | pev := poly.NewEvaluator[uint64](N) 71 | 72 | fp0 := pev.NewFFTPoly() 73 | fp1 := pev.NewFFTPoly() 74 | fpOut := pev.NewFFTPoly() 75 | 76 | for i := 0; i < pev.Rank(); i++ { 77 | fp0.Coeffs[i] = (2*r.Float64() - 1.0) * math.Exp(63) 78 | fp1.Coeffs[i] = (2*r.Float64() - 1.0) * math.Exp(63) 79 | } 80 | 81 | b.Run("Add", func(b *testing.B) { 82 | for i := 0; i < b.N; i++ { 83 | pev.AddFFTPolyTo(fpOut, fp0, fp1) 84 | } 85 | }) 86 | 87 | b.Run("Sub", func(b *testing.B) { 88 | for i := 0; i < b.N; i++ { 89 | pev.SubFFTPolyTo(fpOut, fp0, fp1) 90 | } 91 | }) 92 | 93 | b.Run("Mul", func(b *testing.B) { 94 | for i := 0; i < b.N; i++ { 95 | pev.MulFFTPolyTo(fpOut, fp0, fp1) 96 | } 97 | }) 98 | }) 99 | } 100 | } 101 | 102 | func BenchmarkFFT(b *testing.B) { 103 | r := rand.New(rand.NewSource(0)) 104 | 105 | for _, logN := range LogN { 106 | b.Run(fmt.Sprintf("LogN=%v", logN), func(b *testing.B) { 107 | N := 1 << logN 108 | 109 | pev := poly.NewEvaluator[uint64](N) 110 | 111 | p := pev.NewPoly() 112 | fp := pev.NewFFTPoly() 113 | 114 | for i := 0; i < pev.Rank(); i++ { 115 | p.Coeffs[i] = r.Uint64() 116 | } 117 | 118 | b.Run("FwdFFT", func(b *testing.B) { 119 | for i := 0; i < b.N; i++ { 120 | pev.FwdFFTPolyTo(fp, p) 121 | } 122 | }) 123 | 124 | x := N / 3 125 | b.Run("MonomialFwdFFT", func(b *testing.B) { 126 | for i := 0; i < b.N; i++ { 127 | pev.MonomialFwdFFTTo(fp, x) 128 | } 129 | }) 130 | 131 | b.Run("InvFFT", func(b *testing.B) { 132 | for i := 0; i < b.N; i++ { 133 | pev.InvFFTToUnsafe(p, fp) 134 | } 135 | }) 136 | }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /xtfhe/manylut_params_list.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | var ( 6 | // ParamsBinaryLUT2 is a default parameter set for binary TFHE with 2 LUTs. 7 | ParamsBinaryLUT2 = ManyLUTParametersLiteral[uint32]{ 8 | BaseParams: tfhe.ParametersLiteral[uint32]{ 9 | LWEDimension: 687, 10 | GLWERank: 1, 11 | PolyRank: 1024, 12 | LUTSize: 1024, 13 | 14 | LWEStdDev: 0.00002024849643226141, 15 | GLWEStdDev: 0.0000000444843247619562, 16 | 17 | BlockSize: 3, 18 | 19 | MessageModulus: 1 << 1, 20 | 21 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint32]{ 22 | Base: 1 << 6, 23 | Level: 3, 24 | }, 25 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint32]{ 26 | Base: 1 << 3, 27 | Level: 4, 28 | }, 29 | 30 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 31 | }, 32 | 33 | LUTCount: 2, 34 | } 35 | 36 | // ParamsBinaryCompactLUT2 is a parameter set for binary TFHE 37 | // with OrderKeySwitchBlindRotate with 2 LUTs. 38 | ParamsBinaryCompactLUT2 = ManyLUTParametersLiteral[uint32]{ 39 | BaseParams: tfhe.ParametersLiteral[uint32]{ 40 | LWEDimension: 687, 41 | GLWERank: 1, 42 | PolyRank: 1024, 43 | LUTSize: 1024, 44 | 45 | LWEStdDev: 0.00002024849643226141, 46 | GLWEStdDev: 0.0000000444843247619562, 47 | 48 | BlockSize: 3, 49 | 50 | MessageModulus: 1 << 1, 51 | 52 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint32]{ 53 | Base: 1 << 6, 54 | Level: 3, 55 | }, 56 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint32]{ 57 | Base: 1 << 4, 58 | Level: 3, 59 | }, 60 | 61 | BootstrapOrder: tfhe.OrderKeySwitchBlindRotate, 62 | }, 63 | 64 | LUTCount: 2, 65 | } 66 | 67 | // ParamsUint2LUT4 is a parameter set with 2 bits of message space with 4 LUTs. 68 | ParamsUint2LUT4 = ManyLUTParametersLiteral[uint64]{ 69 | BaseParams: tfhe.ParametersLiteral[uint64]{ 70 | LWEDimension: 804, 71 | GLWERank: 1, 72 | PolyRank: 2048, 73 | LUTSize: 2048, 74 | 75 | LWEStdDev: 0.000002433797421598353, 76 | GLWEStdDev: 0.0000000000000003472576015484159, 77 | 78 | BlockSize: 4, 79 | 80 | MessageModulus: 1 << 2, 81 | 82 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 83 | Base: 1 << 23, 84 | Level: 1, 85 | }, 86 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 87 | Base: 1 << 6, 88 | Level: 2, 89 | }, 90 | 91 | BootstrapOrder: tfhe.OrderKeySwitchBlindRotate, 92 | }, 93 | 94 | LUTCount: 4, 95 | } 96 | 97 | // ParamsUint3LUT2 is a parameter set with 3 bits of message space with 2 LUTs. 98 | ParamsUint3LUT2 = ManyLUTParametersLiteral[uint64]{ 99 | BaseParams: tfhe.ParametersLiteral[uint64]{ 100 | 101 | LWEDimension: 804, 102 | GLWERank: 1, 103 | PolyRank: 2048, 104 | LUTSize: 2048, 105 | 106 | LWEStdDev: 0.000002433797421598353, 107 | GLWEStdDev: 0.0000000000000003472576015484159, 108 | 109 | BlockSize: 4, 110 | 111 | MessageModulus: 1 << 3, 112 | 113 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 114 | Base: 1 << 23, 115 | Level: 1, 116 | }, 117 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 118 | Base: 1 << 6, 119 | Level: 2, 120 | }, 121 | 122 | BootstrapOrder: tfhe.OrderKeySwitchBlindRotate, 123 | }, 124 | 125 | LUTCount: 2, 126 | } 127 | ) 128 | -------------------------------------------------------------------------------- /math/vec/asm_vec_test.go: -------------------------------------------------------------------------------- 1 | package vec_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/sp301415/tfhe-go/math/vec" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestVec(t *testing.T) { 12 | r := rand.New(rand.NewSource(0)) 13 | 14 | N := 687 15 | 16 | v0 := make([]uint32, N) 17 | v1 := make([]uint32, N) 18 | vOut := make([]uint32, N) 19 | vOutAVX := make([]uint32, N) 20 | 21 | for i := 0; i < N; i++ { 22 | v0[i] = r.Uint32() 23 | v1[i] = r.Uint32() 24 | } 25 | 26 | w0 := make([]uint64, N) 27 | w1 := make([]uint64, N) 28 | wOut := make([]uint64, N) 29 | wOutAVX := make([]uint64, N) 30 | 31 | for i := 0; i < N; i++ { 32 | w0[i] = r.Uint64() 33 | w1[i] = r.Uint64() 34 | } 35 | 36 | t.Run("AddTo", func(t *testing.T) { 37 | for i := 0; i < N; i++ { 38 | vOut[i] = v0[i] + v1[i] 39 | wOut[i] = w0[i] + w1[i] 40 | } 41 | vec.AddTo(vOutAVX, v0, v1) 42 | vec.AddTo(wOutAVX, w0, w1) 43 | 44 | assert.Equal(t, vOut, vOutAVX) 45 | assert.Equal(t, wOut, wOutAVX) 46 | }) 47 | 48 | t.Run("SubTo", func(t *testing.T) { 49 | for i := 0; i < N; i++ { 50 | vOut[i] = v0[i] - v1[i] 51 | wOut[i] = w0[i] - w1[i] 52 | } 53 | vec.SubTo(vOutAVX, v0, v1) 54 | vec.SubTo(wOutAVX, w0, w1) 55 | 56 | assert.Equal(t, vOut, vOutAVX) 57 | assert.Equal(t, wOut, wOutAVX) 58 | }) 59 | 60 | t.Run("ScalarMulTo", func(t *testing.T) { 61 | cv := vOut[0] 62 | cw := wOut[0] 63 | for i := 0; i < N; i++ { 64 | vOut[i] = cv * v0[i] 65 | wOut[i] = cw * w0[i] 66 | } 67 | vec.ScalarMulTo(vOutAVX, v0, cv) 68 | vec.ScalarMulTo(wOutAVX, w0, cw) 69 | 70 | assert.Equal(t, vOut, vOutAVX) 71 | assert.Equal(t, wOut, wOutAVX) 72 | }) 73 | 74 | t.Run("ScalarMulAddTo", func(t *testing.T) { 75 | cv := vOut[0] 76 | cw := wOut[0] 77 | for i := 0; i < N; i++ { 78 | vOut[i] += cv * v0[i] 79 | wOut[i] += cw * w0[i] 80 | } 81 | vec.ScalarMulAddTo(vOutAVX, v0, cv) 82 | vec.ScalarMulAddTo(wOutAVX, w0, cw) 83 | 84 | assert.Equal(t, vOut, vOutAVX) 85 | assert.Equal(t, wOut, wOutAVX) 86 | }) 87 | 88 | t.Run("ScalarMulSubTo", func(t *testing.T) { 89 | cv := vOut[0] 90 | cw := wOut[0] 91 | for i := 0; i < N; i++ { 92 | vOut[i] -= cv * v0[i] 93 | wOut[i] -= cw * w0[i] 94 | } 95 | vec.ScalarMulSubTo(vOutAVX, v0, cv) 96 | vec.ScalarMulSubTo(wOutAVX, w0, cw) 97 | 98 | assert.Equal(t, vOut, vOutAVX) 99 | assert.Equal(t, wOut, wOutAVX) 100 | }) 101 | 102 | t.Run("MulTo", func(t *testing.T) { 103 | for i := 0; i < N; i++ { 104 | vOut[i] = v0[i] * v1[i] 105 | wOut[i] = w0[i] * w1[i] 106 | } 107 | vec.MulTo(vOutAVX, v0, v1) 108 | vec.MulTo(wOutAVX, w0, w1) 109 | 110 | assert.Equal(t, vOut, vOutAVX) 111 | assert.Equal(t, wOut, wOutAVX) 112 | }) 113 | 114 | t.Run("MulAddTo", func(t *testing.T) { 115 | for i := 0; i < N; i++ { 116 | vOut[i] += v0[i] * v1[i] 117 | wOut[i] += w0[i] * w1[i] 118 | } 119 | vec.MulAddTo(vOutAVX, v0, v1) 120 | vec.MulAddTo(wOutAVX, w0, w1) 121 | 122 | assert.Equal(t, vOut, vOutAVX) 123 | assert.Equal(t, wOut, wOutAVX) 124 | }) 125 | 126 | t.Run("MulSubTo", func(t *testing.T) { 127 | for i := 0; i < N; i++ { 128 | vOut[i] -= v0[i] * v1[i] 129 | wOut[i] -= w0[i] * w1[i] 130 | } 131 | vec.MulSubTo(vOutAVX, v0, v1) 132 | vec.MulSubTo(wOutAVX, w0, w1) 133 | 134 | assert.Equal(t, vOut, vOutAVX) 135 | assert.Equal(t, wOut, wOutAVX) 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /xtfhe/manylut_evaluator.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // ManyLUTEvaluator wraps around [tfhe.Evaluator], and implements PBSManyLUT algorithm. 9 | // For more details, see https://eprint.iacr.org/2021/729. 10 | // 11 | // ManyLUTEvaluator is not safe for concurrent use. 12 | // Use [*ManyLUTEvaluator.SafeCopy] to get a safe copy. 13 | type ManyLUTEvaluator[T tfhe.TorusInt] struct { 14 | // Evaluator is an embedded Evaluator for this ManyLUTEvaluator. 15 | *tfhe.Evaluator[T] 16 | 17 | // Params is parameters for this ManyLUTEvaluator. 18 | Params ManyLUTParameters[T] 19 | 20 | buf manyLUTEvaluatorBuffer[T] 21 | } 22 | 23 | // manyLUTEvaluatorBuffer is a buffer for ManyLUTEvaluator. 24 | type manyLUTEvaluatorBuffer[T tfhe.TorusInt] struct { 25 | // ctFFTAcc is a fourier transformed accumulator in Blind Rotation. 26 | ctFFTAcc tfhe.FFTGLWECiphertext[T] 27 | // ctFFTBlockAcc is an auxiliary accumulator in BlindRotateBlock. 28 | ctFFTBlockAcc tfhe.FFTGLWECiphertext[T] 29 | // ctFFTAccDcmp is a decomposed ctAcc in Blind Rotation. 30 | ctFFTAccDcmp [][]poly.FFTPoly 31 | // fMono is a fourier transformed monomial in Blind Rotation. 32 | fMono poly.FFTPoly 33 | 34 | // ctRotate is a blind rotated GLWE ciphertext for bootstrapping. 35 | ctRotate tfhe.GLWECiphertext[T] 36 | // ctExtract is the extracted LWE ciphertext after Blind Rotation. 37 | ctExtract tfhe.LWECiphertext[T] 38 | // ctKeySwitch is the LWEDimension sized ciphertext from keyswitching for bootstrapping. 39 | ctKeySwitch tfhe.LWECiphertext[T] 40 | 41 | // lut is an empty lut, used for BlindRotateFunc. 42 | lut tfhe.LookUpTable[T] 43 | } 44 | 45 | // NewManyLUTEvaluator creates a new ManyLUTEvaluator. 46 | func NewManyLUTEvaluator[T tfhe.TorusInt](params ManyLUTParameters[T], evk tfhe.EvaluationKey[T]) *ManyLUTEvaluator[T] { 47 | return &ManyLUTEvaluator[T]{ 48 | Evaluator: tfhe.NewEvaluator(params.baseParams, evk), 49 | 50 | Params: params, 51 | 52 | buf: newManyLUTEvaluatorBuffer(params), 53 | } 54 | } 55 | 56 | // newManyLUTEvaluatorBuffer creates a new manyLUTEvaluatorBuffer. 57 | func newManyLUTEvaluatorBuffer[T tfhe.TorusInt](params ManyLUTParameters[T]) manyLUTEvaluatorBuffer[T] { 58 | ctFFTAccDcmp := make([][]poly.FFTPoly, params.baseParams.GLWERank()+1) 59 | for i := 0; i < params.baseParams.GLWERank()+1; i++ { 60 | ctFFTAccDcmp[i] = make([]poly.FFTPoly, params.baseParams.BlindRotateParams().Level()) 61 | for j := 0; j < params.baseParams.BlindRotateParams().Level(); j++ { 62 | ctFFTAccDcmp[i][j] = poly.NewFFTPoly(params.baseParams.PolyRank()) 63 | } 64 | } 65 | 66 | return manyLUTEvaluatorBuffer[T]{ 67 | ctFFTAcc: tfhe.NewFFTGLWECiphertext(params.baseParams), 68 | ctFFTBlockAcc: tfhe.NewFFTGLWECiphertext(params.baseParams), 69 | ctFFTAccDcmp: ctFFTAccDcmp, 70 | fMono: poly.NewFFTPoly(params.baseParams.PolyRank()), 71 | 72 | ctRotate: tfhe.NewGLWECiphertext(params.baseParams), 73 | ctExtract: tfhe.NewLWECiphertextCustom[T](params.baseParams.GLWEDimension()), 74 | ctKeySwitch: tfhe.NewLWECiphertextCustom[T](params.baseParams.LWEDimension()), 75 | 76 | lut: tfhe.NewLUT(params.baseParams), 77 | } 78 | } 79 | 80 | // SafeCopy returns a thread-safe copy. 81 | func (e *ManyLUTEvaluator[T]) SafeCopy() *ManyLUTEvaluator[T] { 82 | return &ManyLUTEvaluator[T]{ 83 | Evaluator: e.Evaluator.SafeCopy(), 84 | Params: e.Params, 85 | buf: newManyLUTEvaluatorBuffer(e.Params), 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tfhe/asm_decompose_amd64.s: -------------------------------------------------------------------------------- 1 | // Code generated by command: go run asmgen.go -decompose -out ../../tfhe/asm_decompose_amd64.s -stubs ../../tfhe/asm_decompose_stub_amd64.go -pkg=tfhe. DO NOT EDIT. 2 | 3 | //go:build amd64 && !purego 4 | 5 | #include "textflag.h" 6 | 7 | DATA ONE<>+0(SB)/8, $0x0000000000000001 8 | GLOBL ONE<>(SB), RODATA|NOPTR, $8 9 | 10 | // func decomposePolyToUint32AVX2(dcmpOut [][]uint32, p []uint32, base uint32, logBase uint32, logLastBaseQ uint32) 11 | // Requires: AVX, AVX2 12 | TEXT ·decomposePolyToUint32AVX2(SB), NOSPLIT, $0-60 13 | MOVQ dcmpOut_base+0(FP), AX 14 | MOVQ p_base+24(FP), CX 15 | MOVQ p_len+32(FP), DX 16 | MOVQ dcmpOut_len+8(FP), BX 17 | VPBROADCASTD base+48(FP), Y0 18 | VPBROADCASTD logBase+52(FP), Y1 19 | VPBROADCASTD logLastBaseQ+56(FP), Y2 20 | VPBROADCASTD ONE<>+0(SB), Y3 21 | VPSUBD Y3, Y0, Y4 22 | VPSRLD $0x01, Y0, Y0 23 | VPSUBD Y3, Y1, Y5 24 | XORQ SI, SI 25 | JMP N_loop_end 26 | 27 | N_loop_body: 28 | VMOVDQU (CX)(SI*4), Y6 29 | VPSRLVD Y2, Y6, Y7 30 | VPSLLD $0x01, Y6, Y6 31 | VPSRLVD Y2, Y6, Y6 32 | VANDPD Y3, Y6, Y6 33 | VPADDD Y6, Y7, Y6 34 | MOVQ BX, DI 35 | SUBQ $0x01, DI 36 | MOVQ DI, R8 37 | ADDQ DI, R8 38 | ADDQ DI, R8 39 | JMP level_loop_end 40 | 41 | level_loop_body: 42 | VANDPD Y4, Y6, Y7 43 | VPSRLVD Y1, Y6, Y6 44 | VPSRLVD Y5, Y7, Y8 45 | VPADDD Y8, Y6, Y6 46 | VANDPD Y0, Y7, Y8 47 | VPSLLD $0x01, Y8, Y8 48 | VPSUBD Y8, Y7, Y7 49 | MOVQ (AX)(R8*8), R9 50 | VMOVDQU Y7, (R9)(SI*4) 51 | SUBQ $0x01, DI 52 | SUBQ $0x03, R8 53 | 54 | level_loop_end: 55 | CMPQ DI, $0x01 56 | JGE level_loop_body 57 | VANDPD Y4, Y6, Y6 58 | VANDPD Y0, Y6, Y7 59 | VPSLLD $0x01, Y7, Y7 60 | VPSUBD Y7, Y6, Y6 61 | MOVQ (AX), DI 62 | VMOVDQU Y6, (DI)(SI*4) 63 | ADDQ $0x08, SI 64 | 65 | N_loop_end: 66 | CMPQ SI, DX 67 | JL N_loop_body 68 | RET 69 | 70 | // func decomposePolyToUint64AVX2(dcmpOut [][]uint64, p []uint64, base uint64, logBase uint64, logLastBaseQ uint64) 71 | // Requires: AVX, AVX2 72 | TEXT ·decomposePolyToUint64AVX2(SB), NOSPLIT, $0-72 73 | MOVQ dcmpOut_base+0(FP), AX 74 | MOVQ p_base+24(FP), CX 75 | MOVQ p_len+32(FP), DX 76 | MOVQ dcmpOut_len+8(FP), BX 77 | VPBROADCASTQ base+48(FP), Y0 78 | VPBROADCASTQ logBase+56(FP), Y1 79 | VPBROADCASTQ logLastBaseQ+64(FP), Y2 80 | VPBROADCASTQ ONE<>+0(SB), Y3 81 | VPSUBQ Y3, Y0, Y4 82 | VPSRLQ $0x01, Y0, Y0 83 | VPSUBQ Y3, Y1, Y5 84 | XORQ SI, SI 85 | JMP N_loop_end 86 | 87 | N_loop_body: 88 | VMOVDQU (CX)(SI*8), Y6 89 | VPSRLVQ Y2, Y6, Y7 90 | VPSLLQ $0x01, Y6, Y6 91 | VPSRLVQ Y2, Y6, Y6 92 | VANDPD Y3, Y6, Y6 93 | VPADDQ Y6, Y7, Y6 94 | MOVQ BX, DI 95 | SUBQ $0x01, DI 96 | MOVQ DI, R8 97 | ADDQ DI, R8 98 | ADDQ DI, R8 99 | JMP level_loop_end 100 | 101 | level_loop_body: 102 | VANDPD Y4, Y6, Y7 103 | VPSRLVQ Y1, Y6, Y6 104 | VPSRLVQ Y5, Y7, Y8 105 | VPADDQ Y8, Y6, Y6 106 | VANDPD Y0, Y7, Y8 107 | VPSLLQ $0x01, Y8, Y8 108 | VPSUBQ Y8, Y7, Y7 109 | MOVQ (AX)(R8*8), R9 110 | VMOVDQU Y7, (R9)(SI*8) 111 | SUBQ $0x01, DI 112 | SUBQ $0x03, R8 113 | 114 | level_loop_end: 115 | CMPQ DI, $0x01 116 | JGE level_loop_body 117 | VANDPD Y4, Y6, Y6 118 | VANDPD Y0, Y6, Y7 119 | VPSLLQ $0x01, Y7, Y7 120 | VPSUBQ Y7, Y6, Y6 121 | MOVQ (AX), DI 122 | VMOVDQU Y6, (DI)(SI*8) 123 | ADDQ $0x04, SI 124 | 125 | N_loop_end: 126 | CMPQ SI, DX 127 | JL N_loop_body 128 | RET 129 | -------------------------------------------------------------------------------- /tfhe/bootstrap_key.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // EvaluationKey is a public key for Evaluator, 4 | // which consists of BlindRotation Key and KeySwitching Key. 5 | // All keys should be treated as read-only. 6 | // Changing them mid-operation will usually result in wrong results. 7 | type EvaluationKey[T TorusInt] struct { 8 | // BlindRotateKey is a blindrotate key. 9 | BlindRotateKey BlindRotateKey[T] 10 | // KeySwitchKey is a keyswitch key switching LWELargeKey -> LWEKey. 11 | KeySwitchKey LWEKeySwitchKey[T] 12 | } 13 | 14 | // NewEvaluationKey creates a new EvaluationKey. 15 | func NewEvaluationKey[T TorusInt](params Parameters[T]) EvaluationKey[T] { 16 | return EvaluationKey[T]{ 17 | BlindRotateKey: NewBlindRotateKey(params), 18 | KeySwitchKey: NewKeySwitchKeyForBootstrap(params), 19 | } 20 | } 21 | 22 | // NewEvaluationKeyCustom creates a new EvaluationKey with custom parameters. 23 | func NewEvaluationKeyCustom[T TorusInt](lweDimension, glweRank, polyRank int, blindRotateParams, keySwitchParams GadgetParameters[T]) EvaluationKey[T] { 24 | return EvaluationKey[T]{ 25 | BlindRotateKey: NewBlindRotateKeyCustom(lweDimension, glweRank, polyRank, blindRotateParams), 26 | KeySwitchKey: NewKeySwitchKeyForBootstrapCustom(lweDimension, glweRank, polyRank, keySwitchParams), 27 | } 28 | } 29 | 30 | // Copy returns a copy of the key. 31 | func (evk EvaluationKey[T]) Copy() EvaluationKey[T] { 32 | return EvaluationKey[T]{ 33 | BlindRotateKey: evk.BlindRotateKey.Copy(), 34 | KeySwitchKey: evk.KeySwitchKey.Copy(), 35 | } 36 | } 37 | 38 | // CopyFrom copies values from key. 39 | func (evk *EvaluationKey[T]) CopyFrom(evkIn EvaluationKey[T]) { 40 | evk.BlindRotateKey.CopyFrom(evkIn.BlindRotateKey) 41 | evk.KeySwitchKey.CopyFrom(evkIn.KeySwitchKey) 42 | } 43 | 44 | // Clear clears the key. 45 | func (evk *EvaluationKey[T]) Clear() { 46 | evk.BlindRotateKey.Clear() 47 | evk.KeySwitchKey.Clear() 48 | } 49 | 50 | // BlindRotateKey is a key for blind rotation. 51 | // Essentially, this is a GGSW encryption of LWEKey with GLWEKey. 52 | // However, FFT is already applied for fast external product. 53 | type BlindRotateKey[T TorusInt] struct { 54 | GadgetParameters GadgetParameters[T] 55 | 56 | // Value has length LWEDimension. 57 | Value []FFTGGSWCiphertext[T] 58 | } 59 | 60 | // NewBlindRotateKey creates a new BlindRotateKey. 61 | func NewBlindRotateKey[T TorusInt](params Parameters[T]) BlindRotateKey[T] { 62 | brk := make([]FFTGGSWCiphertext[T], params.lweDimension) 63 | for i := 0; i < params.lweDimension; i++ { 64 | brk[i] = NewFFTGGSWCiphertext(params, params.blindRotateParams) 65 | } 66 | return BlindRotateKey[T]{Value: brk, GadgetParameters: params.blindRotateParams} 67 | } 68 | 69 | // NewBlindRotateKeyCustom creates a new BlindRotateKey with custom parameters. 70 | func NewBlindRotateKeyCustom[T TorusInt](lweDimension, glweRank, polyRank int, gadgetParams GadgetParameters[T]) BlindRotateKey[T] { 71 | brk := make([]FFTGGSWCiphertext[T], lweDimension) 72 | for i := 0; i < lweDimension; i++ { 73 | brk[i] = NewFFTGGSWCiphertextCustom(glweRank, polyRank, gadgetParams) 74 | } 75 | return BlindRotateKey[T]{Value: brk, GadgetParameters: gadgetParams} 76 | } 77 | 78 | // Copy returns a copy of the key. 79 | func (brk BlindRotateKey[T]) Copy() BlindRotateKey[T] { 80 | brkCopy := make([]FFTGGSWCiphertext[T], len(brk.Value)) 81 | for i := range brk.Value { 82 | brkCopy[i] = brk.Value[i].Copy() 83 | } 84 | return BlindRotateKey[T]{Value: brkCopy, GadgetParameters: brk.GadgetParameters} 85 | } 86 | 87 | // CopyFrom copies values from key. 88 | func (brk *BlindRotateKey[T]) CopyFrom(brkIn BlindRotateKey[T]) { 89 | for i := range brk.Value { 90 | brk.Value[i].CopyFrom(brkIn.Value[i]) 91 | } 92 | brk.GadgetParameters = brkIn.GadgetParameters 93 | } 94 | 95 | // Clear clears the key. 96 | func (brk *BlindRotateKey[T]) Clear() { 97 | for i := range brk.Value { 98 | brk.Value[i].Clear() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tfhe/encryptor_public.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/csprng" 5 | "github.com/sp301415/tfhe-go/math/poly" 6 | ) 7 | 8 | // PublicEncryptor encrypts TFHE ciphertexts with public key. 9 | // This is meant to be public, usually for servers. 10 | // 11 | // We use compact public key, explained in https://eprint.iacr.org/2023/603. 12 | // This means that not all parameters support public key encryption. 13 | // 14 | // PublicEncryptor is not safe for concurrent use. 15 | // Use [*PublicEncryptor.SafeCopy] to get a safe copy. 16 | type PublicEncryptor[T TorusInt] struct { 17 | // Encoder is an embedded encoder for this PublicEncryptor. 18 | *Encoder[T] 19 | // GLWETransformer is an embedded GLWETransformer for this PublicEncryptor. 20 | *GLWETransformer[T] 21 | 22 | // Params is a parameters for this PublicEncryptor. 23 | Params Parameters[T] 24 | 25 | // UniformSampler is used for sampling the mask of encryptions. 26 | UniformSampler *csprng.UniformSampler[T] 27 | // BinarySampler is used for sampling the auxiliary "key" in encryptions. 28 | BinarySampler *csprng.BinarySampler[T] 29 | // GaussianSampler is used for sampling noise in LWE and GLWE encryption. 30 | GaussianSampler *csprng.GaussianSampler[T] 31 | 32 | // PolyEvaluator is a PolyEvaluator for this PublicEncryptor. 33 | PolyEvaluator *poly.Evaluator[T] 34 | 35 | // PublicKey is a public key for this PublicEncryptor. 36 | PublicKey PublicKey[T] 37 | 38 | buf publicEncryptorBuffer[T] 39 | } 40 | 41 | // publicEncryptorBuffer is a buffer for PublicEncryptor. 42 | type publicEncryptorBuffer[T TorusInt] struct { 43 | // ptGLWE is a GLWE plaintext for GLWE encryption / decryptions. 44 | ptGLWE GLWEPlaintext[T] 45 | // ctGLWE is a standard GLWE Ciphertext for Fourier encryption / decryptions. 46 | ctGLWE GLWECiphertext[T] 47 | 48 | // auxKey is an auxiliary key for encryption. 49 | // This must be sampled fresh for each encryption. 50 | auxKey GLWESecretKey[T] 51 | // auxFourierKey is a fourier transform of auxKey. 52 | auxFourierKey FFTGLWESecretKey[T] 53 | } 54 | 55 | // NewPublicEncryptor creates a new PublicEncryptor. 56 | // 57 | // Panics when the parameters do not support public key encryption. 58 | func NewPublicEncryptor[T TorusInt](params Parameters[T], pk PublicKey[T]) *PublicEncryptor[T] { 59 | if !params.IsPublicKeyEncryptable() { 60 | panic("Parameters do not support public key encryption") 61 | } 62 | 63 | return &PublicEncryptor[T]{ 64 | Encoder: NewEncoder(params), 65 | GLWETransformer: NewGLWETransformer[T](params.polyRank), 66 | 67 | Params: params, 68 | 69 | UniformSampler: csprng.NewUniformSampler[T](), 70 | BinarySampler: csprng.NewBinarySampler[T](), 71 | GaussianSampler: csprng.NewGaussianSampler[T](), 72 | 73 | PolyEvaluator: poly.NewEvaluator[T](params.polyRank), 74 | 75 | PublicKey: pk, 76 | 77 | buf: newPublicEncryptorBuffer(params), 78 | } 79 | } 80 | 81 | // newPublicEncryptorBuffer creates a new publicEncryptorBuffer. 82 | func newPublicEncryptorBuffer[T TorusInt](params Parameters[T]) publicEncryptorBuffer[T] { 83 | return publicEncryptorBuffer[T]{ 84 | ptGLWE: NewGLWEPlaintext(params), 85 | ctGLWE: NewGLWECiphertext(params), 86 | 87 | auxKey: NewGLWESecretKey(params), 88 | auxFourierKey: NewFFTGLWESecretKey(params), 89 | } 90 | } 91 | 92 | // SafeCopy returns a thread-safe copy. 93 | func (e *PublicEncryptor[T]) SafeCopy() *PublicEncryptor[T] { 94 | return &PublicEncryptor[T]{ 95 | Encoder: e.Encoder, 96 | GLWETransformer: e.GLWETransformer.SafeCopy(), 97 | 98 | Params: e.Params, 99 | 100 | UniformSampler: csprng.NewUniformSampler[T](), 101 | BinarySampler: csprng.NewBinarySampler[T](), 102 | GaussianSampler: csprng.NewGaussianSampler[T](), 103 | 104 | PolyEvaluator: e.PolyEvaluator.SafeCopy(), 105 | 106 | PublicKey: e.PublicKey, 107 | 108 | buf: newPublicEncryptorBuffer(e.Params), 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /mktfhe/glwe_enc.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/num" 5 | "github.com/sp301415/tfhe-go/math/poly" 6 | "github.com/sp301415/tfhe-go/math/vec" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | ) 9 | 10 | // UniEncrypt encrypts integer messages to UniEncryption. 11 | func (e *Encryptor[T]) UniEncrypt(messages []int, gadgetParams tfhe.GadgetParameters[T]) UniEncryption[T] { 12 | ctOut := NewUniEncryption(e.Params, gadgetParams) 13 | e.UniEncryptTo(ctOut, messages, gadgetParams) 14 | return ctOut 15 | } 16 | 17 | // UniEncryptTo encrypts integer messages to UniEncryption and writes it to ctOut. 18 | func (e *Encryptor[T]) UniEncryptTo(ctOut UniEncryption[T], messages []int, gadgetParams tfhe.GadgetParameters[T]) { 19 | length := num.Min(e.Params.PolyRank(), len(messages)) 20 | for i := 0; i < length; i++ { 21 | e.buf.ptGLWE.Value.Coeffs[i] = T(messages[i]) % e.Params.MessageModulus() 22 | } 23 | vec.Fill(e.buf.ptGLWE.Value.Coeffs[length:], 0) 24 | 25 | e.UniEncryptPolyTo(ctOut, e.buf.ptGLWE.Value) 26 | } 27 | 28 | // UniEncryptPoly encrypts polynomial to UniEncryption. 29 | func (e *Encryptor[T]) UniEncryptPoly(p poly.Poly[T], gadgetParams tfhe.GadgetParameters[T]) UniEncryption[T] { 30 | ctOut := NewUniEncryption(e.Params, gadgetParams) 31 | e.UniEncryptPolyTo(ctOut, p) 32 | return ctOut 33 | } 34 | 35 | // UniEncryptPolyTo encrypts polynomial to UniEncryption and writes it to ctOut. 36 | func (e *Encryptor[T]) UniEncryptPolyTo(ctOut UniEncryption[T], p poly.Poly[T]) { 37 | e.SubEncryptor.BinarySampler.SamplePolyTo(e.buf.auxKey.Value[0]) 38 | e.SubEncryptor.FwdFFTGLWESecretKeyTo(e.buf.auxFourierKey, e.buf.auxKey) 39 | 40 | for i := 0; i < ctOut.GadgetParams.Level(); i++ { 41 | ctOut.Value[0].Value[i].Value[1].CopyFrom(e.CRS[i]) 42 | e.SubEncryptor.PolyEvaluator.ScalarMulPolyTo(ctOut.Value[0].Value[i].Value[0], p, ctOut.GadgetParams.BaseQ(i)) 43 | 44 | e.SubEncryptor.PolyEvaluator.ShortFFTPolyMulAddPolyTo(ctOut.Value[0].Value[i].Value[0], ctOut.Value[0].Value[i].Value[1], e.buf.auxFourierKey.Value[0]) 45 | e.SubEncryptor.GaussianSampler.SamplePolyAddTo(ctOut.Value[0].Value[i].Value[0], e.Params.GLWEStdDevQ()) 46 | } 47 | 48 | for i := 0; i < ctOut.GadgetParams.Level(); i++ { 49 | e.SubEncryptor.PolyEvaluator.ScalarMulPolyTo(ctOut.Value[1].Value[i].Value[0], e.buf.auxKey.Value[0], ctOut.GadgetParams.BaseQ(i)) 50 | e.SubEncryptor.EncryptGLWEBody(ctOut.Value[1].Value[i]) 51 | } 52 | } 53 | 54 | // UniDecrypt decrypts UniEncryption to integer messages. 55 | func (e *Encryptor[T]) UniDecrypt(ct UniEncryption[T]) []int { 56 | messagesOut := make([]int, e.Params.PolyRank()) 57 | e.UniDecryptTo(messagesOut, ct) 58 | return messagesOut 59 | } 60 | 61 | // UniDecryptTo decrypts UniEncryption to integer messages and writes it to messagesOut. 62 | func (e *Encryptor[T]) UniDecryptTo(messagesOut []int, ct UniEncryption[T]) { 63 | e.UniDecryptPolyTo(e.buf.ptGLWE.Value, ct) 64 | 65 | length := num.Min(e.Params.PolyRank(), len(messagesOut)) 66 | for i := 0; i < length; i++ { 67 | messagesOut[i] = int(e.buf.ptGLWE.Value.Coeffs[i] % e.Params.MessageModulus()) 68 | } 69 | } 70 | 71 | // UniDecryptPoly decrypts UniEncryption to polynomial. 72 | func (e *Encryptor[T]) UniDecryptPoly(ct UniEncryption[T]) poly.Poly[T] { 73 | pOut := poly.NewPoly[T](e.Params.PolyRank()) 74 | e.UniDecryptPolyTo(pOut, ct) 75 | return pOut 76 | } 77 | 78 | // UniDecryptPolyTo decrypts UniEncryption to GLWE plaintext and writes it to ptOut. 79 | func (e *Encryptor[T]) UniDecryptPolyTo(pOut poly.Poly[T], ct UniEncryption[T]) { 80 | e.SubEncryptor.DecryptGLevPolyTo(e.buf.auxKey.Value[0], ct.Value[1]) 81 | e.SubEncryptor.FwdFFTGLWESecretKeyTo(e.buf.auxFourierKey, e.buf.auxKey) 82 | 83 | pOut.CopyFrom(ct.Value[0].Value[0].Value[0]) 84 | e.SubEncryptor.PolyEvaluator.ShortFFTPolyMulSubPolyTo(pOut, ct.Value[0].Value[0].Value[1], e.buf.auxFourierKey.Value[0]) 85 | for i := 0; i < e.Params.PolyRank(); i++ { 86 | pOut.Coeffs[i] = num.DivRoundBits(pOut.Coeffs[i], ct.GadgetParams.LogFirstBaseQ()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /xtfhe/fhew_evaluator.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // FHEWEvaluator wraps around [tfhe.Evaluator] and implements FHEW blind rotation. 9 | // 10 | // FHEWEvaluator is not safe for concurrent use. 11 | // Use [*FHEWEvaluator.SafeCopy] to get a safe copy. 12 | type FHEWEvaluator[T tfhe.TorusInt] struct { 13 | // Evaluator is an embedded [tfhe.Evaluator] for this FHEWEvaluator. 14 | *tfhe.Evaluator[T] 15 | 16 | // Params is parameters for this Evaluator. 17 | Params FHEWParameters[T] 18 | 19 | // EvalKey is the evaluation key for this Evaluator. 20 | EvalKey FHEWEvaluationKey[T] 21 | 22 | // autIdxMap holds the map ±5^i -> ±i mod 2N. 23 | autIdxMap []int 24 | 25 | buf fhewEvaluatorBuffer[T] 26 | } 27 | 28 | // fhewEvaluatorBuffer is a buffer for FHEWEvaluator. 29 | type fhewEvaluatorBuffer[T tfhe.TorusInt] struct { 30 | // ctFFTAcc is a fourier transformed accumulator in Blind Rotation. 31 | ctFFTAcc tfhe.FFTGLWECiphertext[T] 32 | // ctFFTAccDcmp is a decomposed ctAcc in Blind Rotation. 33 | ctFFTAccDcmp [][]poly.FFTPoly 34 | 35 | // ctPermute is a permuted accumulator for bootstrapping. 36 | ctPermute tfhe.GLWECiphertext[T] 37 | // ctRotate is a blind rotated GLWE ciphertext for bootstrapping. 38 | ctRotate tfhe.GLWECiphertext[T] 39 | // ctExtract is an extracted LWE ciphertext after Blind Rotation. 40 | ctExtract tfhe.LWECiphertext[T] 41 | // ctKeySwitch is the LWEDimension sized ciphertext from keyswitching for bootstrapping. 42 | ctKeySwitch tfhe.LWECiphertext[T] 43 | 44 | // lut is an empty lut, used for BlindRotateFunc. 45 | lut tfhe.LookUpTable[T] 46 | } 47 | 48 | // NewFHEWEvaluator creates a new FHEWEvaluator. 49 | func NewFHEWEvaluator[T tfhe.TorusInt](params FHEWParameters[T], evk FHEWEvaluationKey[T]) *FHEWEvaluator[T] { 50 | autIdxMap := make([]int, 2*params.baseParams.PolyRank()) 51 | autIdx := 1 52 | for i := 0; i < params.baseParams.PolyRank()/2; i++ { 53 | autIdxMap[autIdx] = i 54 | autIdxMap[2*params.baseParams.PolyRank()-autIdx] = -i 55 | autIdx = (autIdx * 5) % (2 * params.baseParams.PolyRank()) 56 | } 57 | autIdxMap[2*params.baseParams.PolyRank()-1] = 2 * params.baseParams.PolyRank() 58 | 59 | return &FHEWEvaluator[T]{ 60 | Evaluator: tfhe.NewEvaluator(params.baseParams, tfhe.EvaluationKey[T]{KeySwitchKey: evk.KeySwitchKey}), 61 | 62 | Params: params, 63 | 64 | EvalKey: evk, 65 | 66 | autIdxMap: autIdxMap, 67 | 68 | buf: newFHEWEvaluatorBuffer(params), 69 | } 70 | } 71 | 72 | // newFHEWEvaluatorBuffer creates a new fhewEvaluatorBuffer. 73 | func newFHEWEvaluatorBuffer[T tfhe.TorusInt](params FHEWParameters[T]) fhewEvaluatorBuffer[T] { 74 | ctFFTAccDcmp := make([][]poly.FFTPoly, params.baseParams.GLWERank()+1) 75 | for i := 0; i < params.baseParams.GLWERank()+1; i++ { 76 | ctFFTAccDcmp[i] = make([]poly.FFTPoly, params.baseParams.BlindRotateParams().Level()) 77 | for j := 0; j < params.baseParams.BlindRotateParams().Level(); j++ { 78 | ctFFTAccDcmp[i][j] = poly.NewFFTPoly(params.baseParams.PolyRank()) 79 | } 80 | } 81 | 82 | return fhewEvaluatorBuffer[T]{ 83 | ctFFTAcc: tfhe.NewFFTGLWECiphertext(params.baseParams), 84 | ctFFTAccDcmp: ctFFTAccDcmp, 85 | 86 | ctPermute: tfhe.NewGLWECiphertext(params.baseParams), 87 | ctRotate: tfhe.NewGLWECiphertext(params.baseParams), 88 | ctExtract: tfhe.NewLWECiphertextCustom[T](params.baseParams.GLWEDimension()), 89 | ctKeySwitch: tfhe.NewLWECiphertextCustom[T](params.baseParams.LWEDimension()), 90 | 91 | lut: tfhe.NewLUT(params.baseParams), 92 | } 93 | } 94 | 95 | // SafeCopy creates a shallow copy of this FHEWEvaluator. 96 | // Returned FHEWEvaluator is safe for concurrent use. 97 | func (e *FHEWEvaluator[T]) SafeCopy() *FHEWEvaluator[T] { 98 | return &FHEWEvaluator[T]{ 99 | Evaluator: e.Evaluator.SafeCopy(), 100 | 101 | Params: e.Params, 102 | 103 | EvalKey: e.EvalKey, 104 | 105 | autIdxMap: e.autIdxMap, 106 | 107 | buf: newFHEWEvaluatorBuffer(e.Params), 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /mktfhe/binary_encryptor.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/tfhe" 5 | ) 6 | 7 | // BinaryEncryptor is a multi-key variant of [tfhe.BinaryEncryptor]. 8 | type BinaryEncryptor[T tfhe.TorusInt] struct { 9 | // BinaryEncoder is an embedded encoder for this BinaryEncryptor. 10 | *tfhe.BinaryEncoder[T] 11 | // Params is parameters for this BinaryEncryptor. 12 | Params Parameters[T] 13 | // Encryptor is a generic Encryptor for this BinaryEncryptor. 14 | Encryptor *Encryptor[T] 15 | } 16 | 17 | // NewBinaryEncryptor creates a new BinaryEncryptor. 18 | func NewBinaryEncryptor[T tfhe.TorusInt](params Parameters[T], idx int, crsSeed []byte) *BinaryEncryptor[T] { 19 | return &BinaryEncryptor[T]{ 20 | BinaryEncoder: tfhe.NewBinaryEncoder(params.subParams), 21 | Params: params, 22 | Encryptor: NewEncryptor(params, idx, crsSeed), 23 | } 24 | } 25 | 26 | // NewBinaryEncryptorWithKey creates a new BinaryEncryptor with a given key. 27 | func NewBinaryEncryptorWithKey[T tfhe.TorusInt](params Parameters[T], idx int, crsSeed []byte, sk tfhe.SecretKey[T]) *BinaryEncryptor[T] { 28 | return &BinaryEncryptor[T]{ 29 | BinaryEncoder: tfhe.NewBinaryEncoder(params.subParams), 30 | Params: params, 31 | Encryptor: NewEncryptorWithKey(params, idx, crsSeed, sk), 32 | } 33 | } 34 | 35 | // SafeCopy returns a thread-safe copy. 36 | func (e *BinaryEncryptor[T]) SafeCopy() *BinaryEncryptor[T] { 37 | return &BinaryEncryptor[T]{ 38 | BinaryEncoder: e.BinaryEncoder, 39 | Params: e.Params, 40 | Encryptor: e.Encryptor.SafeCopy(), 41 | } 42 | } 43 | 44 | // EncryptLWEBool encrypts boolean message to LWE ciphertexts. 45 | // Like most languages, false == 0, and true == 1. 46 | // 47 | // Note that this is different from calling EncryptLWE with 0 or 1. 48 | func (e *BinaryEncryptor[T]) EncryptLWEBool(message bool) LWECiphertext[T] { 49 | return e.Encryptor.EncryptLWEPlaintext(e.EncodeLWEBool(message)) 50 | } 51 | 52 | // EncryptLWEBoolTo encrypts boolean message to LWE ciphertexts. 53 | // Like most languages, false == 0, and true == 1. 54 | // 55 | // Note that this is different from calling EncryptLWE with 0 or 1. 56 | func (e *BinaryEncryptor[T]) EncryptLWEBoolTo(ctOut LWECiphertext[T], message bool) { 57 | e.Encryptor.EncryptLWEPlaintextTo(ctOut, e.EncodeLWEBool(message)) 58 | } 59 | 60 | // EncryptLWEBits encrypts each bits of an integer message. 61 | // The order of the bits are little-endian. 62 | func (e *BinaryEncryptor[T]) EncryptLWEBits(message, bits int) []LWECiphertext[T] { 63 | ctOut := make([]LWECiphertext[T], bits) 64 | e.EncryptLWEBitsTo(ctOut, message) 65 | return ctOut 66 | } 67 | 68 | // EncryptLWEBitsTo encrypts each bits of an integer message. 69 | // The order of the bits are little-endian, 70 | // and will be cut by the length of ctOut. 71 | func (e *BinaryEncryptor[T]) EncryptLWEBitsTo(ctOut []LWECiphertext[T], message int) { 72 | for i := 0; i < len(ctOut); i++ { 73 | ctOut[i] = e.EncryptLWEBool(message&1 == 1) 74 | message >>= 1 75 | } 76 | } 77 | 78 | // GenEvalKey samples a new evaluation key for bootstrapping. 79 | // 80 | // This can take a long time. 81 | // Use [*Encryptor.GenEvalKeyParallel] for better key generation performance. 82 | func (e *BinaryEncryptor[T]) GenEvalKey() EvaluationKey[T] { 83 | return e.Encryptor.GenEvalKey() 84 | } 85 | 86 | // GenEvalKeyParallel samples a new evaluation key for bootstrapping in parallel. 87 | func (e *BinaryEncryptor[T]) GenEvalKeyParallel() EvaluationKey[T] { 88 | return e.Encryptor.GenEvalKeyParallel() 89 | } 90 | 91 | // GenPublicKey samples a new public key. 92 | // 93 | // Panics when the parameters do not support public key encryption. 94 | func (e *BinaryEncryptor[T]) GenPublicKey() tfhe.PublicKey[T] { 95 | return e.Encryptor.GenPublicKey() 96 | } 97 | 98 | // PublicEncryptor returns a BinaryPublicEncryptor with the same parameters. 99 | func (e *BinaryEncryptor[T]) PublicEncryptor() *BinaryPublicEncryptor[T] { 100 | return NewBinaryPublicEncryptor(e.Params, e.Encryptor.Index, e.GenPublicKey()) 101 | } 102 | -------------------------------------------------------------------------------- /mktfhe/fft_glwe_enc.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/num" 5 | "github.com/sp301415/tfhe-go/math/poly" 6 | "github.com/sp301415/tfhe-go/math/vec" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | ) 9 | 10 | // FFTUniEncrypt encrypts integer messages to FFTUniEncryption. 11 | func (e *Encryptor[T]) FFTUniEncrypt(messages []int, gadgetParams tfhe.GadgetParameters[T]) FFTUniEncryption[T] { 12 | ctOut := NewFFTUniEncryption(e.Params, gadgetParams) 13 | e.FFTUniEncryptTo(ctOut, messages, gadgetParams) 14 | return ctOut 15 | } 16 | 17 | // FFTUniEncryptTo encrypts integer messages to FFTUniEncryption and writes it to ctOut. 18 | func (e *Encryptor[T]) FFTUniEncryptTo(ctOut FFTUniEncryption[T], messages []int, gadgetParams tfhe.GadgetParameters[T]) { 19 | length := num.Min(e.Params.PolyRank(), len(messages)) 20 | for i := 0; i < length; i++ { 21 | e.buf.ptGLWE.Value.Coeffs[i] = T(messages[i]) % e.Params.MessageModulus() 22 | } 23 | vec.Fill(e.buf.ptGLWE.Value.Coeffs[length:], 0) 24 | 25 | e.FFTUniEncryptPolyTo(ctOut, e.buf.ptGLWE.Value) 26 | } 27 | 28 | // FFTUniEncryptPoly encrypts polynomial to FFTUniEncryption. 29 | func (e *Encryptor[T]) FFTUniEncryptPoly(p poly.Poly[T], gadgetParams tfhe.GadgetParameters[T]) FFTUniEncryption[T] { 30 | ctOut := NewFFTUniEncryption(e.Params, gadgetParams) 31 | e.FFTUniEncryptPolyTo(ctOut, p) 32 | return ctOut 33 | } 34 | 35 | // FFTUniEncryptPolyTo encrypts polynomial to FFTUniEncryption and writes it to ctOut. 36 | func (e *Encryptor[T]) FFTUniEncryptPolyTo(ctOut FFTUniEncryption[T], p poly.Poly[T]) { 37 | e.SubEncryptor.BinarySampler.SamplePolyTo(e.buf.auxKey.Value[0]) 38 | e.SubEncryptor.FwdFFTGLWESecretKeyTo(e.buf.auxFourierKey, e.buf.auxKey) 39 | 40 | for i := 0; i < ctOut.GadgetParams.Level(); i++ { 41 | e.buf.ctSubGLWE.Value[1].CopyFrom(e.CRS[i]) 42 | e.SubEncryptor.PolyEvaluator.ScalarMulPolyTo(e.buf.ctSubGLWE.Value[0], p, ctOut.GadgetParams.BaseQ(i)) 43 | 44 | e.SubEncryptor.PolyEvaluator.ShortFFTPolyMulAddPolyTo(e.buf.ctSubGLWE.Value[0], e.buf.ctSubGLWE.Value[1], e.buf.auxFourierKey.Value[0]) 45 | e.SubEncryptor.GaussianSampler.SamplePolyAddTo(e.buf.ctSubGLWE.Value[0], e.Params.GLWEStdDevQ()) 46 | 47 | e.SubEncryptor.FwdFFTGLWECiphertextTo(ctOut.Value[0].Value[i], e.buf.ctSubGLWE) 48 | } 49 | 50 | for i := 0; i < ctOut.GadgetParams.Level(); i++ { 51 | e.SubEncryptor.PolyEvaluator.ScalarMulPolyTo(e.buf.ctSubGLWE.Value[0], e.buf.auxKey.Value[0], ctOut.GadgetParams.BaseQ(i)) 52 | e.SubEncryptor.EncryptGLWEBody(e.buf.ctSubGLWE) 53 | e.SubEncryptor.FwdFFTGLWECiphertextTo(ctOut.Value[1].Value[i], e.buf.ctSubGLWE) 54 | } 55 | } 56 | 57 | // FFTUniDecrypt decrypts FFTUniEncryption to integer messages. 58 | func (e *Encryptor[T]) FFTUniDecrypt(ct FFTUniEncryption[T]) []int { 59 | messages := make([]int, e.Params.PolyRank()) 60 | e.FFTUniDecryptTo(messages, ct) 61 | return messages 62 | } 63 | 64 | // FFTUniDecryptTo decrypts FFTUniEncryption to integer messages and writes it to messagesOut. 65 | func (e *Encryptor[T]) FFTUniDecryptTo(messagesOut []int, ct FFTUniEncryption[T]) { 66 | e.FFTUniDecryptPolyTo(e.buf.ptGLWE.Value, ct) 67 | 68 | length := num.Min(e.Params.PolyRank(), len(messagesOut)) 69 | for i := 0; i < length; i++ { 70 | messagesOut[i] = int(e.buf.ptGLWE.Value.Coeffs[i] % e.Params.MessageModulus()) 71 | } 72 | } 73 | 74 | // FFTUniDecryptPoly decrypts FFTUniEncryption to polynomial. 75 | func (e *Encryptor[T]) FFTUniDecryptPoly(ct FFTUniEncryption[T]) poly.Poly[T] { 76 | pOut := poly.NewPoly[T](e.Params.PolyRank()) 77 | e.FFTUniDecryptPolyTo(pOut, ct) 78 | return pOut 79 | } 80 | 81 | // FFTUniDecryptPolyTo decrypts FFTUniEncryption to polynomial and writes it to pOut. 82 | func (e *Encryptor[T]) FFTUniDecryptPolyTo(pOut poly.Poly[T], ct FFTUniEncryption[T]) { 83 | e.SubEncryptor.DecryptFFTGLevPolyTo(e.buf.auxKey.Value[0], ct.Value[1]) 84 | e.SubEncryptor.FwdFFTGLWESecretKeyTo(e.buf.auxFourierKey, e.buf.auxKey) 85 | 86 | e.SubEncryptor.InvFFTGLWECiphertextTo(e.buf.ctSubGLWE, ct.Value[0].Value[0]) 87 | pOut.CopyFrom(e.buf.ctSubGLWE.Value[0]) 88 | e.SubEncryptor.PolyEvaluator.ShortFFTPolyMulSubPolyTo(pOut, e.buf.ctSubGLWE.Value[1], e.buf.auxFourierKey.Value[0]) 89 | for i := 0; i < e.Params.PolyRank(); i++ { 90 | pOut.Coeffs[i] = num.DivRoundBits(pOut.Coeffs[i], ct.GadgetParams.LogFirstBaseQ()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mktfhe/fft_glwe.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // FFTGLWECiphertext is a multi-key variant of [tfhe.FFTGLWECiphertext]. 9 | type FFTGLWECiphertext[T tfhe.TorusInt] struct { 10 | // Value has length GLWERank + 1. 11 | Value []poly.FFTPoly 12 | } 13 | 14 | // NewFFTGLWECiphertext creates a new FFT GLWE ciphertext. 15 | func NewFFTGLWECiphertext[T tfhe.TorusInt](params Parameters[T]) FFTGLWECiphertext[T] { 16 | ct := make([]poly.FFTPoly, params.GLWERank()+1) 17 | for i := 0; i < params.GLWERank()+1; i++ { 18 | ct[i] = poly.NewFFTPoly(params.PolyRank()) 19 | } 20 | return FFTGLWECiphertext[T]{Value: ct} 21 | } 22 | 23 | // NewFFTGLWECiphertextCustom creates a new FFT GLWE ciphertext with given dimension and polyRank 24 | func NewFFTGLWECiphertextCustom[T tfhe.TorusInt](glweRank, polyRank int) FFTGLWECiphertext[T] { 25 | ct := make([]poly.FFTPoly, glweRank+1) 26 | for i := 0; i < glweRank+1; i++ { 27 | ct[i] = poly.NewFFTPoly(polyRank) 28 | } 29 | return FFTGLWECiphertext[T]{Value: ct} 30 | } 31 | 32 | // Copy returns a copy of the ciphertext. 33 | func (ct FFTGLWECiphertext[T]) Copy() FFTGLWECiphertext[T] { 34 | ctCopy := make([]poly.FFTPoly, len(ct.Value)) 35 | for i := range ct.Value { 36 | ctCopy[i] = ct.Value[i].Copy() 37 | } 38 | return FFTGLWECiphertext[T]{Value: ctCopy} 39 | } 40 | 41 | // CopyFrom copies values from the ciphertext. 42 | func (ct *FFTGLWECiphertext[T]) CopyFrom(ctIn FFTGLWECiphertext[T]) { 43 | for i := range ct.Value { 44 | ct.Value[i].CopyFrom(ctIn.Value[i]) 45 | } 46 | } 47 | 48 | // CopyFromSubKey copies values from the single-key ciphertext. 49 | // 50 | // Panics if GLWERank of ctIn is not 1. 51 | func (ct *FFTGLWECiphertext[T]) CopyFromSubKey(ctIn tfhe.FFTGLWECiphertext[T], idx int) { 52 | if len(ctIn.Value) != 2 { 53 | panic("GLWERank of ctIn must be 1") 54 | } 55 | 56 | ct.Clear() 57 | ct.Value[0].CopyFrom(ctIn.Value[0]) 58 | ct.Value[1+idx].CopyFrom(ctIn.Value[1]) 59 | } 60 | 61 | // Clear clears the ciphertext. 62 | func (ct *FFTGLWECiphertext[T]) Clear() { 63 | for i := range ct.Value { 64 | ct.Value[i].Clear() 65 | } 66 | } 67 | 68 | // FFTUniEncryption is a multi-key variant of [tfhe.FFTGGSWCiphertext]. 69 | type FFTUniEncryption[T tfhe.TorusInt] struct { 70 | GadgetParams tfhe.GadgetParameters[T] 71 | 72 | // Value has length 2, which equals to single-key GLWERank + 1. 73 | // The first element is always encrypted with the CRS as the mask. 74 | Value []tfhe.FFTGLevCiphertext[T] 75 | } 76 | 77 | // NewFFTUniEncryption creates a new FFT UniEncryption. 78 | func NewFFTUniEncryption[T tfhe.TorusInt](params Parameters[T], gadgetParams tfhe.GadgetParameters[T]) FFTUniEncryption[T] { 79 | return FFTUniEncryption[T]{ 80 | GadgetParams: gadgetParams, 81 | Value: []tfhe.FFTGLevCiphertext[T]{ 82 | tfhe.NewFFTGLevCiphertextCustom(1, params.PolyRank(), gadgetParams), 83 | tfhe.NewFFTGLevCiphertextCustom(1, params.PolyRank(), gadgetParams), 84 | }, 85 | } 86 | } 87 | 88 | // NewFFTUniEncryptionCustom creates a new FFTUniEncryption with given polyRank and partyCount. 89 | func NewFFTUniEncryptionCustom[T tfhe.TorusInt](polyRank int, gadgetParams tfhe.GadgetParameters[T]) FFTUniEncryption[T] { 90 | return FFTUniEncryption[T]{ 91 | GadgetParams: gadgetParams, 92 | Value: []tfhe.FFTGLevCiphertext[T]{ 93 | tfhe.NewFFTGLevCiphertextCustom(1, polyRank, gadgetParams), 94 | tfhe.NewFFTGLevCiphertextCustom(1, polyRank, gadgetParams), 95 | }, 96 | } 97 | } 98 | 99 | // Copy returns a copy of the ciphertext. 100 | func (ct FFTUniEncryption[T]) Copy() FFTUniEncryption[T] { 101 | ctCopy := make([]tfhe.FFTGLevCiphertext[T], len(ct.Value)) 102 | for i := range ct.Value { 103 | ctCopy[i] = ct.Value[i].Copy() 104 | } 105 | return FFTUniEncryption[T]{GadgetParams: ct.GadgetParams, Value: ctCopy} 106 | } 107 | 108 | // CopyFrom copies values from the ciphertext. 109 | func (ct *FFTUniEncryption[T]) CopyFrom(ctIn FFTUniEncryption[T]) { 110 | for i := range ct.Value { 111 | ct.Value[i].CopyFrom(ctIn.Value[i]) 112 | } 113 | } 114 | 115 | // Clear clears the ciphertext. 116 | func (ct *FFTUniEncryption[T]) Clear() { 117 | for i := range ct.Value { 118 | ct.Value[i].Clear() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /math/poly/poly.go: -------------------------------------------------------------------------------- 1 | // Package poly implements polynomial and its operations. 2 | package poly 3 | 4 | import ( 5 | "math" 6 | 7 | "github.com/sp301415/tfhe-go/math/num" 8 | "github.com/sp301415/tfhe-go/math/vec" 9 | ) 10 | 11 | // Poly is a polynomial over Z_Q[X]/(X^N + 1). 12 | type Poly[T num.Integer] struct { 13 | Coeffs []T 14 | } 15 | 16 | // NewPoly creates a polynomial with rank N with empty coefficients. 17 | // 18 | // Panics when N is not a power of two, or when N is smaller than [MinRank]. 19 | func NewPoly[T num.Integer](N int) Poly[T] { 20 | switch { 21 | case !num.IsPowerOfTwo(N): 22 | panic("rank not power of two") 23 | case N < MinRank: 24 | panic("rank smaller than MinRank") 25 | } 26 | 27 | return Poly[T]{Coeffs: make([]T, N)} 28 | } 29 | 30 | // From creates a new polynomial from given coefficient slice. 31 | // The given slice is copied, and extended to rank N. 32 | func From[T num.Integer](coeffs []T, N int) Poly[T] { 33 | p := NewPoly[T](N) 34 | copy(p.Coeffs, coeffs) 35 | return p 36 | } 37 | 38 | // Copy returns a copy of the polynomial. 39 | func (p Poly[T]) Copy() Poly[T] { 40 | return Poly[T]{Coeffs: vec.Copy(p.Coeffs)} 41 | } 42 | 43 | // CopyFrom copies p0 to p. 44 | func (p *Poly[T]) CopyFrom(pIn Poly[T]) { 45 | copy(p.Coeffs, pIn.Coeffs) 46 | } 47 | 48 | // Rank returns the rank of the polynomial. 49 | // This is equivalent with length of coefficients. 50 | func (p Poly[T]) Rank() int { 51 | return len(p.Coeffs) 52 | } 53 | 54 | // Clear clears all the coefficients to zero. 55 | func (p Poly[T]) Clear() { 56 | vec.Fill(p.Coeffs, 0) 57 | } 58 | 59 | // Equals checks if p0 is equal with p. 60 | func (p Poly[T]) Equals(p0 Poly[T]) bool { 61 | return vec.Equals(p.Coeffs, p0.Coeffs) 62 | } 63 | 64 | // FFTPoly is a fourier transformed polynomial over C[X]/(X^N/2 + 1). 65 | // This corresponds to a polynomial over Z_Q[X]/(X^N + 1). 66 | type FFTPoly struct { 67 | // Coeffs is represented as float-4 complex vector 68 | // for efficient computation. 69 | // 70 | // Namely, 71 | // 72 | // [(r0, i0), (r1, i1), (r2, i2), (r3, i3), ...] 73 | // 74 | // is represented as 75 | // 76 | // [(r0, r1, r2, r3), (i0, i1, i2, i3), ...] 77 | // 78 | Coeffs []float64 79 | } 80 | 81 | // NewFFTPoly creates a fourier polynomial with rank N/2 with empty coefficients. 82 | // 83 | // Panics when N is not a power of two, or when N is smaller than [MinRank]. 84 | func NewFFTPoly(N int) FFTPoly { 85 | switch { 86 | case !num.IsPowerOfTwo(N): 87 | panic("rank not power of two") 88 | case N < MinRank: 89 | panic("rank smaller than MinRank") 90 | } 91 | 92 | return FFTPoly{Coeffs: make([]float64, N)} 93 | } 94 | 95 | // Rank returns the (doubled) rank of the polynomial. 96 | func (p FFTPoly) Rank() int { 97 | return len(p.Coeffs) 98 | } 99 | 100 | // Copy returns a copy of the polynomial. 101 | func (p FFTPoly) Copy() FFTPoly { 102 | return FFTPoly{Coeffs: vec.Copy(p.Coeffs)} 103 | } 104 | 105 | // CopyFrom copies p0 to p. 106 | func (p *FFTPoly) CopyFrom(pIn FFTPoly) { 107 | copy(p.Coeffs, pIn.Coeffs) 108 | } 109 | 110 | // Clear clears all the coefficients to zero. 111 | func (p FFTPoly) Clear() { 112 | vec.Fill(p.Coeffs, 0) 113 | } 114 | 115 | // Equals checks if p0 is equal with p. 116 | // Note that due to floating point errors, 117 | // this function may return false even if p0 and p are equal. 118 | func (p FFTPoly) Equals(p0 FFTPoly) bool { 119 | return vec.Equals(p.Coeffs, p0.Coeffs) 120 | } 121 | 122 | // Approx checks if p0 is approximately equal with p, 123 | // with a difference smaller than eps. 124 | func (p FFTPoly) Approx(p0 FFTPoly, eps float64) bool { 125 | for i := range p.Coeffs { 126 | if math.Abs(p.Coeffs[i]-p0.Coeffs[i]) > eps { 127 | return false 128 | } 129 | } 130 | return true 131 | } 132 | 133 | // checkConsistentPoly checks if [Poly] is consistent with [Evaluator], 134 | // and panics if not. 135 | func checkConsistentPoly[T num.Integer](N int, ps ...Poly[T]) { 136 | if len(ps) == 0 { 137 | return 138 | } 139 | 140 | for i := range ps { 141 | if len(ps[i].Coeffs) != N { 142 | panic("inconsistent inputs") 143 | } 144 | } 145 | } 146 | 147 | // checkConsistentFFTPoly checks if [FFTPoly] is consistent with [Evaluator], 148 | // and panics if not. 149 | func checkConsistentFFTPoly(N int, ps ...FFTPoly) { 150 | if len(ps) == 0 { 151 | return 152 | } 153 | 154 | for i := range ps { 155 | if len(ps[i].Coeffs) != N { 156 | panic("inconsistent inputs") 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /tfhe/binary_encryptor.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // BinaryEncryptor encrypts binary TFHE plaintexts and ciphertexts. 4 | // This is meant to be private, only for clients. 5 | // 6 | // BinaryEncryptor is not safe for concurrent use. 7 | // Use [*BinaryEncryptor.SafeCopy] to get a safe copy. 8 | type BinaryEncryptor[T TorusInt] struct { 9 | // BinaryEncoder is an embedded encoder for this BinaryEncryptor. 10 | *BinaryEncoder[T] 11 | // Params is parameters for this BinaryEncryptor. 12 | Params Parameters[T] 13 | // Encryptor is a generic Encryptor for this BinaryEncryptor. 14 | Encryptor *Encryptor[T] 15 | } 16 | 17 | // NewBinaryEncryptor returns a initialized BinaryEncryptor with given parameters. 18 | // It also automatically samples LWE and GLWE key. 19 | func NewBinaryEncryptor[T TorusInt](params Parameters[T]) *BinaryEncryptor[T] { 20 | return &BinaryEncryptor[T]{ 21 | BinaryEncoder: NewBinaryEncoder(params), 22 | Params: params, 23 | Encryptor: NewEncryptor(params), 24 | } 25 | } 26 | 27 | // NewBinaryEncryptorWithKey returns a initialized BinaryEncryptor with given parameters and key. 28 | func NewBinaryEncryptorWithKey[T TorusInt](params Parameters[T], sk SecretKey[T]) *BinaryEncryptor[T] { 29 | return &BinaryEncryptor[T]{ 30 | BinaryEncoder: NewBinaryEncoder(params), 31 | Params: params, 32 | Encryptor: NewEncryptorWithKey(params, sk), 33 | } 34 | } 35 | 36 | // SafeCopy returns a thread-safe copy. 37 | func (e *BinaryEncryptor[T]) SafeCopy() *BinaryEncryptor[T] { 38 | return &BinaryEncryptor[T]{ 39 | BinaryEncoder: e.BinaryEncoder, 40 | Params: e.Params, 41 | Encryptor: e.Encryptor.SafeCopy(), 42 | } 43 | } 44 | 45 | // EncryptLWEBool encrypts boolean message to LWE ciphertexts. 46 | // 47 | // Note that this is different from calling EncryptLWE with 0 or 1. 48 | func (e *BinaryEncryptor[T]) EncryptLWEBool(message bool) LWECiphertext[T] { 49 | return e.Encryptor.EncryptLWEPlaintext(e.EncodeLWEBool(message)) 50 | } 51 | 52 | // EncryptLWEBoolTo encrypts boolean message to LWE ciphertexts and writes to ctOut. 53 | // 54 | // Note that this is different from calling EncryptLWE with 0 or 1. 55 | func (e *BinaryEncryptor[T]) EncryptLWEBoolTo(ctOut LWECiphertext[T], message bool) { 56 | e.Encryptor.EncryptLWEPlaintextTo(ctOut, e.EncodeLWEBool(message)) 57 | } 58 | 59 | // DecryptLWEBool decrypts LWE ciphertext to boolean value. 60 | func (e *BinaryEncryptor[T]) DecryptLWEBool(ct LWECiphertext[T]) bool { 61 | return e.DecodeLWEBool(e.Encryptor.DecryptLWEPhase(ct)) 62 | } 63 | 64 | // EncryptLWEBits encrypts each bits of an integer message. 65 | // The order of the bits are little-endian. 66 | func (e *BinaryEncryptor[T]) EncryptLWEBits(message, bits int) []LWECiphertext[T] { 67 | ctOut := make([]LWECiphertext[T], bits) 68 | e.EncryptLWEBitsTo(ctOut, message) 69 | return ctOut 70 | } 71 | 72 | // EncryptLWEBitsTo encrypts each bit of an integer message and writes the bits 73 | // into ctOut in little-endian order. The output slice length determines how 74 | // many bits are produced (the message will be truncated to that length). 75 | func (e *BinaryEncryptor[T]) EncryptLWEBitsTo(ctOut []LWECiphertext[T], message int) { 76 | for i := 0; i < len(ctOut); i++ { 77 | ctOut[i] = e.EncryptLWEBool(message&1 == 1) 78 | message >>= 1 79 | } 80 | } 81 | 82 | // DecryptLWEBits decrypts a slice of binary LWE ciphertext 83 | // to integer message. 84 | // The order of bits of LWE ciphertexts are assumed to be little-endian. 85 | func (e *BinaryEncryptor[T]) DecryptLWEBits(ct []LWECiphertext[T]) int { 86 | var message int 87 | for i := len(ct) - 1; i >= 0; i-- { 88 | message <<= 1 89 | if e.DecryptLWEBool(ct[i]) { 90 | message += 1 91 | } 92 | } 93 | return message 94 | } 95 | 96 | // GenPublicKey samples a new public key. 97 | // 98 | // Panics when the parameters do not support public key encryption. 99 | func (e *BinaryEncryptor[T]) GenPublicKey() PublicKey[T] { 100 | return e.Encryptor.GenPublicKey() 101 | } 102 | 103 | // PublicEncryptor returns a BinaryPublicEncryptor with the same parameters. 104 | func (e *BinaryEncryptor[T]) PublicEncryptor() *BinaryPublicEncryptor[T] { 105 | return NewBinaryPublicEncryptor(e.Params, e.GenPublicKey()) 106 | } 107 | 108 | // GenEvalKey samples a new evaluation key for bootstrapping. 109 | // 110 | // This can take a long time. 111 | // Use [*BinaryEncryptor.GenEvalKeyParallel] for better key generation performance. 112 | func (e *BinaryEncryptor[T]) GenEvalKey() EvaluationKey[T] { 113 | return e.Encryptor.GenEvalKey() 114 | } 115 | 116 | // GenEvalKeyParallel samples a new evaluation key for bootstrapping in parallel. 117 | func (e *BinaryEncryptor[T]) GenEvalKeyParallel() EvaluationKey[T] { 118 | return e.Encryptor.GenEvalKeyParallel() 119 | } 120 | -------------------------------------------------------------------------------- /tfhe/keyswitch_key.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | // LWEKeySwitchKey is a LWE keyswitch key from one LWEKey to another LWEKey. 4 | type LWEKeySwitchKey[T TorusInt] struct { 5 | GadgetParams GadgetParameters[T] 6 | 7 | // Value has length InputLWEDimension. 8 | Value []LevCiphertext[T] 9 | } 10 | 11 | // NewLWEKeySwitchKey creates a new LWEKeySwitchingKey. 12 | func NewLWEKeySwitchKey[T TorusInt](params Parameters[T], inputDimension int, gadgetParams GadgetParameters[T]) LWEKeySwitchKey[T] { 13 | ksk := make([]LevCiphertext[T], inputDimension) 14 | for i := 0; i < inputDimension; i++ { 15 | ksk[i] = NewLevCiphertext(params, gadgetParams) 16 | } 17 | return LWEKeySwitchKey[T]{Value: ksk, GadgetParams: gadgetParams} 18 | } 19 | 20 | // NewLWEKeySwitchKeyCustom creates a new LWEKeySwitchingKey with custom parameters. 21 | func NewLWEKeySwitchKeyCustom[T TorusInt](inputDimension, outputDimension int, gadgetParams GadgetParameters[T]) LWEKeySwitchKey[T] { 22 | ksk := make([]LevCiphertext[T], inputDimension) 23 | for i := 0; i < inputDimension; i++ { 24 | ksk[i] = NewLevCiphertextCustom(outputDimension, gadgetParams) 25 | } 26 | return LWEKeySwitchKey[T]{Value: ksk, GadgetParams: gadgetParams} 27 | } 28 | 29 | // NewKeySwitchKeyForBootstrap creates a new LWEKeySwitchingKey for bootstrapping. 30 | func NewKeySwitchKeyForBootstrap[T TorusInt](params Parameters[T]) LWEKeySwitchKey[T] { 31 | return NewLWEKeySwitchKeyCustom(params.glweDimension-params.lweDimension, params.lweDimension, params.keySwitchParams) 32 | } 33 | 34 | // NewKeySwitchKeyForBootstrapCustom creates a new LWEKeySwitchingKey with custom parameters. 35 | func NewKeySwitchKeyForBootstrapCustom[T TorusInt](lweDimension, glweRank, polyRank int, gadgetParams GadgetParameters[T]) LWEKeySwitchKey[T] { 36 | return NewLWEKeySwitchKeyCustom(glweRank*polyRank-lweDimension, lweDimension, gadgetParams) 37 | } 38 | 39 | // InputLWEDimension returns the input LWEDimension of this key. 40 | func (ksk LWEKeySwitchKey[T]) InputLWEDimension() int { 41 | return len(ksk.Value) 42 | } 43 | 44 | // Copy returns a copy of the key. 45 | func (ksk LWEKeySwitchKey[T]) Copy() LWEKeySwitchKey[T] { 46 | kskCopy := make([]LevCiphertext[T], len(ksk.Value)) 47 | for i := range ksk.Value { 48 | kskCopy[i] = ksk.Value[i].Copy() 49 | } 50 | return LWEKeySwitchKey[T]{Value: kskCopy, GadgetParams: ksk.GadgetParams} 51 | } 52 | 53 | // CopyFrom copies values from key. 54 | func (ksk *LWEKeySwitchKey[T]) CopyFrom(kskIn LWEKeySwitchKey[T]) { 55 | for i := range ksk.Value { 56 | ksk.Value[i].CopyFrom(kskIn.Value[i]) 57 | } 58 | ksk.GadgetParams = kskIn.GadgetParams 59 | } 60 | 61 | // Clear clears the key. 62 | func (ksk *LWEKeySwitchKey[T]) Clear() { 63 | for i := range ksk.Value { 64 | ksk.Value[i].Clear() 65 | } 66 | } 67 | 68 | // GLWEKeySwitchKey is a GLWE keyswitch key from one GLWEKey to another GLWEKey. 69 | type GLWEKeySwitchKey[T TorusInt] struct { 70 | GadgetParameters GadgetParameters[T] 71 | 72 | // Value has length InputGLWERank. 73 | Value []FFTGLevCiphertext[T] 74 | } 75 | 76 | // NewGLWEKeySwitchKey creates a new GLWEKeySwitchingKey. 77 | func NewGLWEKeySwitchKey[T TorusInt](params Parameters[T], inputGLWERank int, gadgetParams GadgetParameters[T]) GLWEKeySwitchKey[T] { 78 | ksk := make([]FFTGLevCiphertext[T], inputGLWERank) 79 | for i := 0; i < inputGLWERank; i++ { 80 | ksk[i] = NewFFTGLevCiphertext(params, gadgetParams) 81 | } 82 | return GLWEKeySwitchKey[T]{Value: ksk, GadgetParameters: gadgetParams} 83 | } 84 | 85 | // NewGLWEKeySwitchKeyCustom creates a new GLWEKeySwitchingKey with custom parameters. 86 | func NewGLWEKeySwitchKeyCustom[T TorusInt](inputGLWERank, outputGLWERank, polyRank int, gadgetParams GadgetParameters[T]) GLWEKeySwitchKey[T] { 87 | ksk := make([]FFTGLevCiphertext[T], inputGLWERank) 88 | for i := 0; i < inputGLWERank; i++ { 89 | ksk[i] = NewFFTGLevCiphertextCustom(outputGLWERank, polyRank, gadgetParams) 90 | } 91 | return GLWEKeySwitchKey[T]{Value: ksk, GadgetParameters: gadgetParams} 92 | } 93 | 94 | // InputGLWERank returns the input GLWERank of this key. 95 | func (ksk GLWEKeySwitchKey[T]) InputGLWERank() int { 96 | return len(ksk.Value) 97 | } 98 | 99 | // Copy returns a copy of the key. 100 | func (ksk GLWEKeySwitchKey[T]) Copy() GLWEKeySwitchKey[T] { 101 | kskCopy := make([]FFTGLevCiphertext[T], len(ksk.Value)) 102 | for i := range ksk.Value { 103 | kskCopy[i] = ksk.Value[i].Copy() 104 | } 105 | return GLWEKeySwitchKey[T]{Value: kskCopy, GadgetParameters: ksk.GadgetParameters} 106 | } 107 | 108 | // CopyFrom copies values from key. 109 | func (ksk *GLWEKeySwitchKey[T]) CopyFrom(kskIn GLWEKeySwitchKey[T]) { 110 | for i := range ksk.Value { 111 | ksk.Value[i].CopyFrom(kskIn.Value[i]) 112 | } 113 | ksk.GadgetParameters = kskIn.GadgetParameters 114 | } 115 | 116 | // Clear clears the key. 117 | func (ksk *GLWEKeySwitchKey[T]) Clear() { 118 | for i := range ksk.Value { 119 | ksk.Value[i].Clear() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /mktfhe/params_list.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import "github.com/sp301415/tfhe-go/tfhe" 4 | 5 | var ( 6 | // ParamsBinaryParty2 is a parameter set for binary TFHE with 2 parties. 7 | ParamsBinaryParty2 = ParametersLiteral[uint64]{ 8 | SubParams: tfhe.ParametersLiteral[uint64]{ 9 | LWEDimension: 560, 10 | GLWERank: 1, 11 | PolyRank: 2048, 12 | 13 | LWEStdDev: 0.0000305, 14 | GLWEStdDev: 0.00000000000000000463, 15 | 16 | BlockSize: 2, 17 | 18 | MessageModulus: 1 << 1, 19 | 20 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 21 | Base: 1 << 12, 22 | Level: 3, 23 | }, 24 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 25 | Base: 1 << 2, 26 | Level: 8, 27 | }, 28 | 29 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 30 | }, 31 | 32 | PartyCount: 2, 33 | 34 | AccumulatorParams: tfhe.GadgetParametersLiteral[uint64]{ 35 | Base: 1 << 6, 36 | Level: 2, 37 | }, 38 | RelinKeyParams: tfhe.GadgetParametersLiteral[uint64]{ 39 | Base: 1 << 10, 40 | Level: 3, 41 | }, 42 | } 43 | 44 | // ParamsBinaryParty4 is a parameter set for binary TFHE with 4 parties. 45 | ParamsBinaryParty4 = ParametersLiteral[uint64]{ 46 | SubParams: tfhe.ParametersLiteral[uint64]{ 47 | LWEDimension: 560, 48 | GLWERank: 1, 49 | PolyRank: 2048, 50 | 51 | LWEStdDev: 0.0000305, 52 | GLWEStdDev: 0.00000000000000000463, 53 | 54 | BlockSize: 2, 55 | 56 | MessageModulus: 1 << 1, 57 | 58 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 59 | Base: 1 << 8, 60 | Level: 5, 61 | }, 62 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 63 | Base: 1 << 2, 64 | Level: 8, 65 | }, 66 | 67 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 68 | }, 69 | 70 | PartyCount: 4, 71 | 72 | AccumulatorParams: tfhe.GadgetParametersLiteral[uint64]{ 73 | Base: 1 << 8, 74 | Level: 2, 75 | }, 76 | RelinKeyParams: tfhe.GadgetParametersLiteral[uint64]{ 77 | Base: 1 << 6, 78 | Level: 7, 79 | }, 80 | } 81 | 82 | // ParamsBinaryParty8 is a parameter set for binary TFHE with 8 parties. 83 | ParamsBinaryParty8 = ParametersLiteral[uint64]{ 84 | SubParams: tfhe.ParametersLiteral[uint64]{ 85 | LWEDimension: 560, 86 | GLWERank: 1, 87 | PolyRank: 2048, 88 | 89 | LWEStdDev: 0.0000305, 90 | GLWEStdDev: 0.00000000000000000463, 91 | 92 | BlockSize: 2, 93 | 94 | MessageModulus: 1 << 1, 95 | 96 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 97 | Base: 1 << 9, 98 | Level: 4, 99 | }, 100 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 101 | Base: 1 << 2, 102 | Level: 8, 103 | }, 104 | 105 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 106 | }, 107 | 108 | PartyCount: 8, 109 | 110 | AccumulatorParams: tfhe.GadgetParametersLiteral[uint64]{ 111 | Base: 1 << 6, 112 | Level: 3, 113 | }, 114 | RelinKeyParams: tfhe.GadgetParametersLiteral[uint64]{ 115 | Base: 1 << 4, 116 | Level: 8, 117 | }, 118 | } 119 | 120 | // ParamsBinaryParty16 is a parameter set for binary TFHE with 16 parties. 121 | ParamsBinaryParty16 = ParametersLiteral[uint64]{ 122 | SubParams: tfhe.ParametersLiteral[uint64]{ 123 | LWEDimension: 560, 124 | GLWERank: 1, 125 | PolyRank: 2048, 126 | 127 | LWEStdDev: 0.0000305, 128 | GLWEStdDev: 0.00000000000000000463, 129 | 130 | BlockSize: 2, 131 | 132 | MessageModulus: 1 << 1, 133 | 134 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 135 | Base: 1 << 8, 136 | Level: 5, 137 | }, 138 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 139 | Base: 1 << 2, 140 | Level: 8, 141 | }, 142 | 143 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 144 | }, 145 | 146 | PartyCount: 16, 147 | 148 | AccumulatorParams: tfhe.GadgetParametersLiteral[uint64]{ 149 | Base: 1 << 6, 150 | Level: 3, 151 | }, 152 | RelinKeyParams: tfhe.GadgetParametersLiteral[uint64]{ 153 | Base: 1 << 4, 154 | Level: 9, 155 | }, 156 | } 157 | 158 | // ParamsBinaryParty32 is a parameter set for binary TFHE with 32 parties. 159 | ParamsBinaryParty32 = ParametersLiteral[uint64]{ 160 | SubParams: tfhe.ParametersLiteral[uint64]{ 161 | LWEDimension: 560, 162 | GLWERank: 1, 163 | PolyRank: 2048, 164 | 165 | LWEStdDev: 0.0000305, 166 | GLWEStdDev: 0.00000000000000000463, 167 | 168 | BlockSize: 2, 169 | 170 | MessageModulus: 1 << 1, 171 | 172 | BlindRotateParams: tfhe.GadgetParametersLiteral[uint64]{ 173 | Base: 1 << 7, 174 | Level: 6, 175 | }, 176 | KeySwitchParams: tfhe.GadgetParametersLiteral[uint64]{ 177 | Base: 1 << 2, 178 | Level: 8, 179 | }, 180 | 181 | BootstrapOrder: tfhe.OrderBlindRotateKeySwitch, 182 | }, 183 | 184 | PartyCount: 32, 185 | 186 | AccumulatorParams: tfhe.GadgetParametersLiteral[uint64]{ 187 | Base: 1 << 7, 188 | Level: 3, 189 | }, 190 | RelinKeyParams: tfhe.GadgetParametersLiteral[uint64]{ 191 | Base: 1 << 2, 192 | Level: 16, 193 | }, 194 | } 195 | ) 196 | -------------------------------------------------------------------------------- /tfhe/bootstrap_lut.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/num" 5 | "github.com/sp301415/tfhe-go/math/poly" 6 | "github.com/sp301415/tfhe-go/math/vec" 7 | ) 8 | 9 | // LookUpTable is a polynomial that is the lookup table 10 | // for function evaluations during programmable bootstrapping. 11 | type LookUpTable[T TorusInt] struct { 12 | // Value has length lutExtendFactor. 13 | Value []poly.Poly[T] 14 | } 15 | 16 | // NewLUT creates a new lookup table. 17 | func NewLUT[T TorusInt](params Parameters[T]) LookUpTable[T] { 18 | lut := make([]poly.Poly[T], params.lutExtendFactor) 19 | for i := 0; i < params.lutExtendFactor; i++ { 20 | lut[i] = poly.NewPoly[T](params.polyRank) 21 | } 22 | 23 | return LookUpTable[T]{Value: lut} 24 | } 25 | 26 | // NewLUTCustom creates a new lookup table with custom size. 27 | func NewLUTCustom[T TorusInt](extendFactor, polyRank int) LookUpTable[T] { 28 | lut := make([]poly.Poly[T], extendFactor) 29 | for i := 0; i < extendFactor; i++ { 30 | lut[i] = poly.NewPoly[T](polyRank) 31 | } 32 | 33 | return LookUpTable[T]{Value: lut} 34 | } 35 | 36 | // Copy returns a copy of the LUT. 37 | func (lut LookUpTable[T]) Copy() LookUpTable[T] { 38 | lutCopy := make([]poly.Poly[T], len(lut.Value)) 39 | for i := 0; i < len(lut.Value); i++ { 40 | lutCopy[i] = lut.Value[i].Copy() 41 | } 42 | return LookUpTable[T]{Value: lutCopy} 43 | } 44 | 45 | // CopyFrom copies values from the LUT. 46 | func (lut *LookUpTable[T]) CopyFrom(lutIn LookUpTable[T]) { 47 | for i := 0; i < len(lut.Value); i++ { 48 | lut.Value[i].CopyFrom(lutIn.Value[i]) 49 | } 50 | } 51 | 52 | // Clear clears the LUT. 53 | func (lut *LookUpTable[T]) Clear() { 54 | for i := 0; i < len(lut.Value); i++ { 55 | lut.Value[i].Clear() 56 | } 57 | } 58 | 59 | // GenLUT generates a lookup table based on function f. 60 | // Input and output of f is cut by MessageModulus. 61 | func (e *Evaluator[T]) GenLUT(f func(int) int) LookUpTable[T] { 62 | lutOut := NewLUT(e.Params) 63 | e.GenLUTTo(lutOut, f) 64 | return lutOut 65 | } 66 | 67 | // GenLUTTo generates a lookup table based on function f and writes it to lutOut. 68 | // Input and output of f is cut by MessageModulus. 69 | func (e *Evaluator[T]) GenLUTTo(lutOut LookUpTable[T], f func(int) int) { 70 | e.GenLUTCustomTo(lutOut, f, e.Params.messageModulus, e.Params.scale) 71 | } 72 | 73 | // GenLUTFull generates a lookup table based on function f. 74 | // Output of f is encoded as-is. 75 | func (e *Evaluator[T]) GenLUTFull(f func(int) T) LookUpTable[T] { 76 | lutOut := NewLUT(e.Params) 77 | e.GenLUTFullTo(lutOut, f) 78 | return lutOut 79 | } 80 | 81 | // GenLUTFullTo generates a lookup table based on function f and writes it to lutOut. 82 | // Output of f is encoded as-is. 83 | func (e *Evaluator[T]) GenLUTFullTo(lutOut LookUpTable[T], f func(int) T) { 84 | e.GenLUTCustomFullTo(lutOut, f, e.Params.messageModulus) 85 | } 86 | 87 | // GenLUTCustom generates a lookup table based on function f using custom messageModulus and scale. 88 | // Input and output of f is cut by messageModulus. 89 | func (e *Evaluator[T]) GenLUTCustom(f func(int) int, messageModulus, scale T) LookUpTable[T] { 90 | lutOut := NewLUT(e.Params) 91 | e.GenLUTCustomTo(lutOut, f, messageModulus, scale) 92 | return lutOut 93 | } 94 | 95 | // GenLUTCustomTo generates a lookup table based on function f using custom messageModulus and scale and writes it to lutOut. 96 | // Input and output of f is cut by messageModulus. 97 | func (e *Evaluator[T]) GenLUTCustomTo(lutOut LookUpTable[T], f func(int) int, messageModulus, scale T) { 98 | e.GenLUTCustomFullTo(lutOut, func(x int) T { return e.EncodeLWECustom(f(x), messageModulus, scale).Value }, messageModulus) 99 | } 100 | 101 | // GenLUTCustomFull generates a lookup table based on function f using custom messageModulus and scale. 102 | // Output of f is encoded as-is. 103 | func (e *Evaluator[T]) GenLUTCustomFull(f func(int) T, messageModulus T) LookUpTable[T] { 104 | lutOut := NewLUT(e.Params) 105 | e.GenLUTCustomFullTo(lutOut, f, messageModulus) 106 | return lutOut 107 | } 108 | 109 | // GenLUTCustomFullTo generates a lookup table based on function f using custom messageModulus and scale and writes it to lutOut. 110 | // Output of f is encoded as-is. 111 | func (e *Evaluator[T]) GenLUTCustomFullTo(lutOut LookUpTable[T], f func(int) T, messageModulus T) { 112 | for x := 0; x < int(messageModulus); x++ { 113 | start := num.DivRound(x*e.Params.lutSize, int(messageModulus)) 114 | end := num.DivRound((x+1)*e.Params.lutSize, int(messageModulus)) 115 | y := f(x) 116 | for xx := start; xx < end; xx++ { 117 | e.buf.lutRaw[xx] = y 118 | } 119 | } 120 | 121 | offset := num.DivRound(e.Params.lutSize, int(2*messageModulus)) 122 | vec.RotateInPlace(e.buf.lutRaw, -offset) 123 | for i := e.Params.lutSize - offset; i < e.Params.lutSize; i++ { 124 | e.buf.lutRaw[i] = -e.buf.lutRaw[i] 125 | } 126 | 127 | for i := 0; i < e.Params.lutExtendFactor; i++ { 128 | for j := 0; j < e.Params.polyRank; j++ { 129 | lutOut.Value[i].Coeffs[j] = e.buf.lutRaw[j*e.Params.lutExtendFactor+i] 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /xtfhe/sanitization_params.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/sp301415/tfhe-go/math/num" 7 | "github.com/sp301415/tfhe-go/tfhe" 8 | ) 9 | 10 | // SanitizationParametersLiteral is a structure for TFHE Sanitization Parameters. 11 | type SanitizationParametersLiteral[T tfhe.TorusInt] struct { 12 | // BaseParams is a base parameters for this SanitizationParametersLiteral. 13 | BaseParams tfhe.ParametersLiteral[T] 14 | 15 | // RandSigma is the standard deviation used for 16 | // Discrete Gaussian sampling in Rand operation. 17 | RandSigma float64 18 | // RandTau is the standard deviation used for 19 | // Rounded Gaussian sampling in Rand operation. 20 | RandTau float64 21 | 22 | // LinEvalSigma is the standard deviation used for 23 | // Discrete Gaussian sampling in LinEval operation. 24 | LinEvalSigma float64 25 | // LinEvalTau is the standard deviation used for 26 | // Rounded Gaussian sampling in LinEval operation. 27 | LinEvalTau float64 28 | } 29 | 30 | // Compile transforms ParametersLiteral to read-only Parameters. 31 | // If there is any invalid parameter in the literal, it panics. 32 | // Default parameters are guaranteed to be compiled without panics. 33 | func (p SanitizationParametersLiteral[T]) Compile() SanitizationParameters[T] { 34 | baseParameters := p.BaseParams.Compile() 35 | 36 | switch { 37 | case baseParameters.GLWERank() != 1: 38 | panic("GLWERank not 1") 39 | case baseParameters.PolyRank() != baseParameters.LUTSize(): 40 | panic("PolyRank does not equal LUTSize") 41 | case baseParameters.BootstrapOrder() != tfhe.OrderKeySwitchBlindRotate: 42 | panic("BootstrapOrder not OrderKeySwitchBlindRotate") 43 | case p.RandSigma <= 0: 44 | panic("RandSigma smaller than zero") 45 | case p.RandTau <= 0: 46 | panic("RandTau smaller than zero") 47 | case p.LinEvalSigma <= 0: 48 | panic("LinEvalSigma smaller than zero") 49 | case p.LinEvalTau <= 0: 50 | panic("LinEvalTau smaller than zero") 51 | } 52 | 53 | return SanitizationParameters[T]{ 54 | baseParams: baseParameters, 55 | 56 | floatQ: math.Exp2(float64(num.SizeT[T]())), 57 | 58 | randSigma: p.RandSigma, 59 | randTau: p.RandTau, 60 | 61 | linEvalSigma: p.LinEvalSigma, 62 | linEvalTau: p.LinEvalTau, 63 | } 64 | } 65 | 66 | // SanitizationParameters is a parameter set for TFHE Sanitization. 67 | type SanitizationParameters[T tfhe.TorusInt] struct { 68 | // baseParams is a base parameters for this SanitizationParameters. 69 | baseParams tfhe.Parameters[T] 70 | 71 | // floatQ is the value of Q as float64. 72 | floatQ float64 73 | 74 | // RandSigma is the standard deviation used for 75 | // Discrete Gaussian sampling in Rand operation. 76 | randSigma float64 77 | // RandTau is the standard deviation used for 78 | // Rounded Gaussian sampling in Rand operation. 79 | randTau float64 80 | 81 | // LinEvalSigma is the standard deviation used for 82 | // Discrete Gaussian sampling in LinEval operation. 83 | linEvalSigma float64 84 | // LinEvalTau is the standard deviation used for 85 | // Rounded Gaussian sampling in LinEval operation. 86 | linEvalTau float64 87 | } 88 | 89 | // BaseParams returns the base parameters for this SanitizationParameters. 90 | func (p SanitizationParameters[T]) BaseParams() tfhe.Parameters[T] { 91 | return p.baseParams 92 | } 93 | 94 | // RandSigma returns the standard deviation used for 95 | // Discrete Gaussian sampling in Rand operation. 96 | // 97 | // This is a normalized standard deviation. 98 | // For actual sampling, use [Parameters.RandSigmaQ]. 99 | func (p SanitizationParameters[T]) RandSigma() float64 { 100 | return p.randSigma 101 | } 102 | 103 | // RandSigmaQ returns RandSigma * Q. 104 | func (p SanitizationParameters[T]) RandSigmaQ() float64 { 105 | return p.randSigma * p.floatQ 106 | } 107 | 108 | // RandTau returns the standard deviation used for 109 | // Rounded Gaussian sampling in Rand operation. 110 | // 111 | // This is a normalized standard deviation. 112 | // For actual sampling, use [Parameters.RandTauQ]. 113 | func (p SanitizationParameters[T]) RandTau() float64 { 114 | return p.randTau 115 | } 116 | 117 | // RandTauQ returns RandTau * Q. 118 | func (p SanitizationParameters[T]) RandTauQ() float64 { 119 | return p.randTau * p.floatQ 120 | } 121 | 122 | // LinEvalSigma returns the standard deviation used for 123 | // Discrete Gaussian sampling in LinEval operation. 124 | // 125 | // This is a normalized standard deviation. 126 | // For actual sampling, use [Parameters.LinEvalSigmaQ]. 127 | func (p SanitizationParameters[T]) LinEvalSigma() float64 { 128 | return p.linEvalSigma 129 | } 130 | 131 | // LinEvalSigmaQ returns LinEvalSigma * Q. 132 | func (p SanitizationParameters[T]) LinEvalSigmaQ() float64 { 133 | return p.linEvalSigma * p.floatQ 134 | } 135 | 136 | // LinEvalTau returns the standard deviation used for 137 | // Rounded Gaussian sampling in LinEval operation. 138 | // 139 | // This is a normalized standard deviation. 140 | // For actual sampling, use [Parameters.LinEvalTauQ]. 141 | func (p SanitizationParameters[T]) LinEvalTau() float64 { 142 | return p.linEvalTau 143 | } 144 | 145 | // LinEvalTauQ returns LinEvalTau * Q. 146 | func (p SanitizationParameters[T]) LinEvalTauQ() float64 { 147 | return p.linEvalTau * p.floatQ 148 | } 149 | -------------------------------------------------------------------------------- /tfhe/lwe_enc.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/num" 5 | "github.com/sp301415/tfhe-go/math/vec" 6 | ) 7 | 8 | // EncryptLWE encodes and encrypts integer message to LWE ciphertext. 9 | func (e *Encryptor[T]) EncryptLWE(message int) LWECiphertext[T] { 10 | return e.EncryptLWEPlaintext(e.EncodeLWE(message)) 11 | } 12 | 13 | // EncryptLWETo encodes and encrypts integer message to LWE ciphertext and writes it to ctOut. 14 | func (e *Encryptor[T]) EncryptLWETo(ctOut LWECiphertext[T], message int) { 15 | e.EncryptLWEPlaintextTo(ctOut, e.EncodeLWE(message)) 16 | } 17 | 18 | // EncryptLWEPlaintext encrypts LWE plaintext to LWE ciphertext. 19 | func (e *Encryptor[T]) EncryptLWEPlaintext(pt LWEPlaintext[T]) LWECiphertext[T] { 20 | ctOut := NewLWECiphertext(e.Params) 21 | e.EncryptLWEPlaintextTo(ctOut, pt) 22 | return ctOut 23 | } 24 | 25 | // EncryptLWEPlaintextTo encrypts LWE plaintext to LWE ciphertext and writes it to ctOut. 26 | func (e *Encryptor[T]) EncryptLWEPlaintextTo(ctOut LWECiphertext[T], pt LWEPlaintext[T]) { 27 | ctOut.Value[0] = pt.Value 28 | e.EncryptLWEBody(ctOut) 29 | } 30 | 31 | // EncryptLWEBody encrypts the value in the body of LWE ciphertext and overrides it. 32 | // This avoids the need for most buffers. 33 | func (e *Encryptor[T]) EncryptLWEBody(ct LWECiphertext[T]) { 34 | e.UniformSampler.SampleVecTo(ct.Value[1:]) 35 | ct.Value[0] += -vec.Dot(ct.Value[1:], e.DefaultLWESecretKey().Value) 36 | ct.Value[0] += e.GaussianSampler.Sample(e.Params.DefaultLWEStdDevQ()) 37 | } 38 | 39 | // DecryptLWE decrypts and decodes LWE ciphertext to integer message. 40 | func (e *Encryptor[T]) DecryptLWE(ct LWECiphertext[T]) int { 41 | return e.DecodeLWE(e.DecryptLWEPhase(ct)) 42 | } 43 | 44 | // DecryptLWEPhase decrypts LWE ciphertext to LWE plaintext including errors. 45 | func (e *Encryptor[T]) DecryptLWEPhase(ct LWECiphertext[T]) LWEPlaintext[T] { 46 | ptOut := ct.Value[0] + vec.Dot(ct.Value[1:], e.DefaultLWESecretKey().Value) 47 | return LWEPlaintext[T]{Value: ptOut} 48 | } 49 | 50 | // EncryptLev encrypts integer message to Lev ciphertext. 51 | func (e *Encryptor[T]) EncryptLev(message int, gadgetParams GadgetParameters[T]) LevCiphertext[T] { 52 | return e.EncryptLevScalar(T(message)%e.Params.messageModulus, gadgetParams) 53 | } 54 | 55 | // EncryptLevTo encrypts integer message to Lev ciphertext and writes it to ctOut. 56 | func (e *Encryptor[T]) EncryptLevTo(ctOut LevCiphertext[T], message int) { 57 | e.EncryptLevScalarTo(ctOut, T(message)%e.Params.messageModulus) 58 | } 59 | 60 | // EncryptLevScalar encrypts scalar to Lev ciphertext. 61 | func (e *Encryptor[T]) EncryptLevScalar(c T, gadgetParams GadgetParameters[T]) LevCiphertext[T] { 62 | ctOut := NewLevCiphertext(e.Params, gadgetParams) 63 | e.EncryptLevScalarTo(ctOut, c) 64 | return ctOut 65 | } 66 | 67 | // EncryptLevScalarTo encrypts scalar to Lev ciphertext and writes it to ctOut. 68 | func (e *Encryptor[T]) EncryptLevScalarTo(ctOut LevCiphertext[T], c T) { 69 | for i := 0; i < ctOut.GadgetParams.level; i++ { 70 | ctOut.Value[i].Value[0] = c << ctOut.GadgetParams.LogBaseQ(i) 71 | e.EncryptLWEBody(ctOut.Value[i]) 72 | } 73 | } 74 | 75 | // DecryptLev decrypts Lev ciphertext to integer message. 76 | func (e *Encryptor[T]) DecryptLev(ct LevCiphertext[T]) int { 77 | return int(e.DecryptLevScalar(ct) % e.Params.messageModulus) 78 | } 79 | 80 | // DecryptLevScalar decrypts Lev ciphertext to scalar. 81 | func (e *Encryptor[T]) DecryptLevScalar(ct LevCiphertext[T]) T { 82 | ptOut := e.DecryptLWEPhase(ct.Value[0]) 83 | return num.DivRoundBits(ptOut.Value, ct.GadgetParams.LogFirstBaseQ()) % ct.GadgetParams.base 84 | } 85 | 86 | // EncryptGSW encrypts integer message to GSW ciphertext. 87 | func (e *Encryptor[T]) EncryptGSW(message int, gadgetParams GadgetParameters[T]) GSWCiphertext[T] { 88 | return e.EncryptGSWScalar(T(message)%e.Params.messageModulus, gadgetParams) 89 | } 90 | 91 | // EncryptGSWTo encrypts integer message to GSW ciphertext and writes it to ctOut. 92 | func (e *Encryptor[T]) EncryptGSWTo(ctOut GSWCiphertext[T], message int) { 93 | e.EncryptGSWScalarTo(ctOut, T(message)%e.Params.messageModulus) 94 | } 95 | 96 | // EncryptGSWScalar encrypts scalar to GSW ciphertext. 97 | func (e *Encryptor[T]) EncryptGSWScalar(c T, gadgetParams GadgetParameters[T]) GSWCiphertext[T] { 98 | ctOut := NewGSWCiphertext(e.Params, gadgetParams) 99 | e.EncryptGSWScalarTo(ctOut, c) 100 | return ctOut 101 | } 102 | 103 | // EncryptGSWScalarTo encrypts LWE plaintext to GSW ciphertext and writes it to ctOut. 104 | func (e *Encryptor[T]) EncryptGSWScalarTo(ctOut GSWCiphertext[T], c T) { 105 | e.EncryptLevScalarTo(ctOut.Value[0], c) 106 | 107 | for i := 0; i < e.Params.DefaultLWEDimension(); i++ { 108 | for j := 0; j < ctOut.GadgetParams.level; j++ { 109 | ctOut.Value[i+1].Value[j].Value[0] = e.DefaultLWESecretKey().Value[i] * c << ctOut.GadgetParams.LogBaseQ(j) 110 | e.EncryptLWEBody(ctOut.Value[i+1].Value[j]) 111 | } 112 | } 113 | } 114 | 115 | // DecryptGSW decrypts GSW ciphertext to integer message. 116 | func (e *Encryptor[T]) DecryptGSW(ct GSWCiphertext[T]) int { 117 | return e.DecryptLev(ct.Value[0]) 118 | } 119 | 120 | // DecryptGSWScalar decrypts GSW ciphertext to LWE plaintext. 121 | func (e *Encryptor[T]) DecryptGSWScalar(ct GSWCiphertext[T]) T { 122 | return e.DecryptLevScalar(ct.Value[0]) 123 | } 124 | -------------------------------------------------------------------------------- /xtfhe/bfv_keygen.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // BFVEvaluationKey is a keyswitching key for BFV type operations. 9 | // It holds relinearization and automorphism keys. 10 | type BFVEvaluationKey[T tfhe.TorusInt] struct { 11 | // RelinKey is a relinearization key. 12 | RelinKey tfhe.GLWEKeySwitchKey[T] 13 | // GaloisKeys is a map of automorphism keys. 14 | GaloisKeys map[int]tfhe.GLWEKeySwitchKey[T] 15 | } 16 | 17 | // BFVKeyGenerator generates keyswitching keys for BFV type operations. 18 | // 19 | // BFVKeyGenerator is not safe for concurrent use. 20 | // Use [*BFVKeyGenerator.SafeCopy] to get a safe copy. 21 | type BFVKeyGenerator[T tfhe.TorusInt] struct { 22 | // Encryptor is a base encryptor for this BFVKeyGenerator. 23 | Encryptor *tfhe.Encryptor[T] 24 | // PolyEvaluator is a PolyEvaluator for this BFVKeyGenerator. 25 | PolyEvaluator *poly.Evaluator[T] 26 | 27 | // Params is parameters for this BFVKeyGenerator. 28 | Params tfhe.Parameters[T] 29 | } 30 | 31 | // NewBFVKeyGenerator creates a new BFVKeyGenerator. 32 | func NewBFVKeyGenerator[T tfhe.TorusInt](params tfhe.Parameters[T], sk tfhe.SecretKey[T]) *BFVKeyGenerator[T] { 33 | return &BFVKeyGenerator[T]{ 34 | Encryptor: tfhe.NewEncryptorWithKey(params, sk), 35 | PolyEvaluator: poly.NewEvaluator[T](params.PolyRank()), 36 | Params: params, 37 | } 38 | } 39 | 40 | // SafeCopy creates a shallow copy of this BFVKeyGenerator. 41 | func (kg *BFVKeyGenerator[T]) SafeCopy() *BFVKeyGenerator[T] { 42 | return &BFVKeyGenerator[T]{ 43 | Encryptor: kg.Encryptor.SafeCopy(), 44 | PolyEvaluator: kg.PolyEvaluator.SafeCopy(), 45 | Params: kg.Params, 46 | } 47 | } 48 | 49 | // GenEvalKey generates an evaluation key for BFV type operations. 50 | func (kg *BFVKeyGenerator[T]) GenEvalKey(idx []int, kskParams tfhe.GadgetParameters[T]) BFVEvaluationKey[T] { 51 | return BFVEvaluationKey[T]{ 52 | RelinKey: kg.GenRelinKey(kskParams), 53 | GaloisKeys: kg.GenGaloisKeys(idx, kskParams), 54 | } 55 | } 56 | 57 | // GenRelinKey generates a relinearization key for BFV multiplication. 58 | func (kg *BFVKeyGenerator[T]) GenRelinKey(kskParams tfhe.GadgetParameters[T]) tfhe.GLWEKeySwitchKey[T] { 59 | rlkRank := kg.Params.GLWERank() * (kg.Params.GLWERank() + 1) / 2 60 | skOut := tfhe.NewGLWESecretKeyCustom[T](rlkRank, kg.Params.PolyRank()) 61 | fskOut := tfhe.NewFFTGLWESecretKeyCustom[T](rlkRank, kg.Params.PolyRank()) 62 | 63 | skOutIdx := 0 64 | for i := 0; i < kg.Params.GLWERank(); i++ { 65 | for j := i; j < kg.Params.GLWERank(); j++ { 66 | kg.Encryptor.PolyEvaluator.MulFFTPolyTo(fskOut.Value[skOutIdx], kg.Encryptor.SecretKey.FFTGLWEKey.Value[i], kg.Encryptor.SecretKey.FFTGLWEKey.Value[j]) 67 | skOutIdx++ 68 | } 69 | } 70 | 71 | for i := range fskOut.Value { 72 | kg.Encryptor.PolyEvaluator.InvFFTToUnsafe(skOut.Value[i], fskOut.Value[i]) 73 | } 74 | 75 | return kg.Encryptor.GenGLWEKeySwitchKey(skOut, kskParams) 76 | } 77 | 78 | // GenGaloisKeys generate galois keys for BFV automorphism. 79 | func (kg *BFVKeyGenerator[T]) GenGaloisKeys(idx []int, kskParams tfhe.GadgetParameters[T]) map[int]tfhe.GLWEKeySwitchKey[T] { 80 | galKeys := make(map[int]tfhe.GLWEKeySwitchKey[T], len(idx)) 81 | skOut := tfhe.NewGLWESecretKey(kg.Params) 82 | 83 | for _, d := range idx { 84 | for i := 0; i < kg.Params.GLWERank(); i++ { 85 | kg.Encryptor.PolyEvaluator.PermutePolyTo(skOut.Value[i], kg.Encryptor.SecretKey.GLWEKey.Value[i], d) 86 | } 87 | galKeys[d] = kg.Encryptor.GenGLWEKeySwitchKey(skOut, kskParams) 88 | } 89 | return galKeys 90 | } 91 | 92 | // GenGaloisKeysTo generates automorphism keys for BFV automorphism and assigns them to the given map. 93 | // If a key for a given automorphism degree already exists in the map, it will be overwritten. 94 | func (kg *BFVKeyGenerator[T]) GenGaloisKeysTo(galKeysOut map[int]tfhe.GLWEKeySwitchKey[T], idx []int, kskParams tfhe.GadgetParameters[T]) { 95 | skOut := tfhe.NewGLWESecretKey(kg.Params) 96 | 97 | for _, d := range idx { 98 | for i := 0; i < kg.Params.GLWERank(); i++ { 99 | kg.Encryptor.PolyEvaluator.PermutePolyTo(skOut.Value[i], kg.Encryptor.SecretKey.GLWEKey.Value[i], d) 100 | } 101 | galKeysOut[d] = kg.Encryptor.GenGLWEKeySwitchKey(skOut, kskParams) 102 | } 103 | } 104 | 105 | // GenGaloisKeysForPack generates automorphism keys for BFV automorphism for LWE to GLWE packing. 106 | func (kg *BFVKeyGenerator[T]) GenGaloisKeysForPack(kskParams tfhe.GadgetParameters[T]) map[int]tfhe.GLWEKeySwitchKey[T] { 107 | auts := make([]int, kg.Params.LogPolyRank()) 108 | for i := range auts { 109 | auts[i] = 1<<(kg.Params.LogPolyRank()-i) + 1 110 | } 111 | return kg.GenGaloisKeys(auts, kskParams) 112 | } 113 | 114 | // GenGaloisKeysForPackTo generates automorphism keys for BFV automorphism for LWE to GLWE packing and assigns them to the given map. 115 | // If a key for a given automorphism degree already exists in the map, it will be overwritten. 116 | func (kg *BFVKeyGenerator[T]) GenGaloisKeysForPackTo(galKeysOut map[int]tfhe.GLWEKeySwitchKey[T], kskParams tfhe.GadgetParameters[T]) { 117 | auts := make([]int, kg.Params.LogPolyRank()) 118 | for i := range auts { 119 | auts[i] = 1<<(kg.Params.LogPolyRank()-i) + 1 120 | } 121 | kg.GenGaloisKeysTo(galKeysOut, auts, kskParams) 122 | } 123 | -------------------------------------------------------------------------------- /mktfhe/glwe.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // GLWECiphertext is a multi-key variant of [tfhe.GLWECiphertext]. 9 | // Due to the structure of UniEncryption, 10 | // all multi-key GLWE ciphertexts are actually RLWE ciphertexts. 11 | type GLWECiphertext[T tfhe.TorusInt] struct { 12 | // Value has length GLWERank + 1. 13 | Value []poly.Poly[T] 14 | } 15 | 16 | // NewGLWECiphertext creates a new GLWE ciphertext. 17 | func NewGLWECiphertext[T tfhe.TorusInt](params Parameters[T]) GLWECiphertext[T] { 18 | ct := make([]poly.Poly[T], params.GLWERank()+1) 19 | for i := 0; i < params.GLWERank()+1; i++ { 20 | ct[i] = poly.NewPoly[T](params.PolyRank()) 21 | } 22 | return GLWECiphertext[T]{Value: ct} 23 | } 24 | 25 | // NewGLWECiphertextCustom creates a new GLWE ciphertext with given dimension and partyCount. 26 | func NewGLWECiphertextCustom[T tfhe.TorusInt](glweRank, polyRank int) GLWECiphertext[T] { 27 | ct := make([]poly.Poly[T], glweRank+1) 28 | for i := 0; i < glweRank+1; i++ { 29 | ct[i] = poly.NewPoly[T](polyRank) 30 | } 31 | return GLWECiphertext[T]{Value: ct} 32 | } 33 | 34 | // Copy returns a copy of the ciphertext. 35 | func (ct GLWECiphertext[T]) Copy() GLWECiphertext[T] { 36 | ctCopy := make([]poly.Poly[T], len(ct.Value)) 37 | for i := range ct.Value { 38 | ctCopy[i] = ct.Value[i].Copy() 39 | } 40 | return GLWECiphertext[T]{Value: ctCopy} 41 | } 42 | 43 | // CopyFrom copies values from the ciphertext. 44 | func (ct *GLWECiphertext[T]) CopyFrom(ctIn GLWECiphertext[T]) { 45 | for i := range ct.Value { 46 | ct.Value[i].CopyFrom(ctIn.Value[i]) 47 | } 48 | } 49 | 50 | // CopyFromSubKey copies values from the single-key ciphertext. 51 | // 52 | // Panics if GLWERank of ctIn is not 1. 53 | func (ct *GLWECiphertext[T]) CopyFromSubKey(ctIn tfhe.GLWECiphertext[T], idx int) { 54 | if len(ctIn.Value) != 2 { 55 | panic("GLWERank of ctIn must be 1") 56 | } 57 | 58 | ct.Clear() 59 | ct.Value[0].CopyFrom(ctIn.Value[0]) 60 | ct.Value[1+idx].CopyFrom(ctIn.Value[1]) 61 | } 62 | 63 | // Clear clears the ciphertext. 64 | func (ct *GLWECiphertext[T]) Clear() { 65 | for i := range ct.Value { 66 | ct.Value[i].Clear() 67 | } 68 | } 69 | 70 | // AsLWECiphertext extracts LWE ciphertext of given index from GLWE ciphertext. 71 | // The output ciphertext will be of length GLWEDimension + 1, 72 | // encrypted with LWELargeKey. 73 | func (ct GLWECiphertext[T]) AsLWECiphertext(idx int) LWECiphertext[T] { 74 | ctOut := NewLWECiphertextCustom[T]((len(ct.Value) - 1) * ct.Value[0].Rank()) 75 | ct.AsLWECiphertextTo(ctOut, idx) 76 | return ctOut 77 | } 78 | 79 | // AsLWECiphertextTo extracts LWE ciphertext of given index from GLWE ciphertext and writes it to ctOut. 80 | // The output ciphertext should be of length GLWEDimension + 1, 81 | // and it will be a ciphertext encrypted with LWELargeKey. 82 | func (ct GLWECiphertext[T]) AsLWECiphertextTo(ctOut LWECiphertext[T], idx int) { 83 | ctOut.Value[0] = ct.Value[0].Coeffs[idx] 84 | 85 | for i := 0; i < len(ct.Value)-1; i++ { 86 | for j := 0; j <= idx; j++ { 87 | ctOut.Value[1+i*ct.Value[i+1].Rank()+j] = ct.Value[i+1].Coeffs[idx-j] 88 | } 89 | for j := idx + 1; j < ct.Value[i+1].Rank(); j++ { 90 | ctOut.Value[1+i*ct.Value[i+1].Rank()+j] = -ct.Value[i+1].Coeffs[idx-j+ct.Value[i+1].Rank()] 91 | } 92 | } 93 | } 94 | 95 | // UniEncryption is a multi-key variant of [tfhe.GGSWCiphertext]. 96 | type UniEncryption[T tfhe.TorusInt] struct { 97 | GadgetParams tfhe.GadgetParameters[T] 98 | 99 | // Value has length 2, which equals to single-key GLWERank + 1. 100 | // The first element is always encrypted with the CRS as the mask. 101 | Value []tfhe.GLevCiphertext[T] 102 | } 103 | 104 | // NewUniEncryption creates a new UniEncryption. 105 | func NewUniEncryption[T tfhe.TorusInt](params Parameters[T], gadgetParams tfhe.GadgetParameters[T]) UniEncryption[T] { 106 | return UniEncryption[T]{ 107 | GadgetParams: gadgetParams, 108 | Value: []tfhe.GLevCiphertext[T]{ 109 | tfhe.NewGLevCiphertextCustom(1, params.PolyRank(), gadgetParams), 110 | tfhe.NewGLevCiphertextCustom(1, params.PolyRank(), gadgetParams), 111 | }, 112 | } 113 | } 114 | 115 | // NewUniEncryptionCustom creates a new UniEncryption with given polyRank and partyCount. 116 | func NewUniEncryptionCustom[T tfhe.TorusInt](polyRank int, gadgetParams tfhe.GadgetParameters[T]) UniEncryption[T] { 117 | return UniEncryption[T]{ 118 | GadgetParams: gadgetParams, 119 | Value: []tfhe.GLevCiphertext[T]{ 120 | tfhe.NewGLevCiphertextCustom(1, polyRank, gadgetParams), 121 | tfhe.NewGLevCiphertextCustom(1, polyRank, gadgetParams), 122 | }, 123 | } 124 | } 125 | 126 | // Copy returns a copy of the ciphertext. 127 | func (ct UniEncryption[T]) Copy() UniEncryption[T] { 128 | ctCopy := make([]tfhe.GLevCiphertext[T], len(ct.Value)) 129 | for i := range ct.Value { 130 | ctCopy[i] = ct.Value[i].Copy() 131 | } 132 | return UniEncryption[T]{GadgetParams: ct.GadgetParams, Value: ctCopy} 133 | } 134 | 135 | // CopyFrom copies values from the ciphertext. 136 | func (ct *UniEncryption[T]) CopyFrom(ctIn UniEncryption[T]) { 137 | for i := range ct.Value { 138 | ct.Value[i].CopyFrom(ctIn.Value[i]) 139 | } 140 | } 141 | 142 | // Clear clears the ciphertext. 143 | func (ct *UniEncryption[T]) Clear() { 144 | for i := range ct.Value { 145 | ct.Value[i].Clear() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /internal/asmgen/decompose.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/mmcloughlin/avo/build" 5 | . "github.com/mmcloughlin/avo/operand" 6 | ) 7 | 8 | func DecomposeConstants() { 9 | ConstData("ONE", U64(1)) 10 | } 11 | 12 | func DecomposePolyToUint32AVX2() { 13 | TEXT("decomposePolyToUint32AVX2", NOSPLIT, "func(dcmpOut [][]uint32, p []uint32, base uint32, logBase uint32, logLastBaseQ uint32)") 14 | Pragma("noescape") 15 | 16 | dcmpOut := Load(Param("dcmpOut").Base(), GP64()) 17 | p := Load(Param("p").Base(), GP64()) 18 | 19 | N := Load(Param("p").Len(), GP64()) 20 | level := Load(Param("dcmpOut").Len(), GP64()) 21 | 22 | // VET: go vet complains about VBROADCASTD on uint32 values. 23 | // See https://github.com/golang/go/issues/47625. 24 | base, logBase, logLastBaseQ := YMM(), YMM(), YMM() 25 | VPBROADCASTD(NewParamAddr("base", 48), base) 26 | VPBROADCASTD(NewParamAddr("logBase", 52), logBase) 27 | VPBROADCASTD(NewParamAddr("logLastBaseQ", 56), logLastBaseQ) 28 | 29 | allOne := YMM() 30 | VPBROADCASTD(NewDataAddr(NewStaticSymbol("ONE"), 0), allOne) 31 | 32 | baseMask, baseHalf, logBaseSubOne := YMM(), YMM(), YMM() 33 | VPSUBD(allOne, base, baseMask) 34 | VPSRLD(Imm(1), base, baseHalf) 35 | VPSUBD(allOne, logBase, logBaseSubOne) 36 | 37 | i := GP64() 38 | XORQ(i, i) 39 | JMP(LabelRef("N_loop_end")) 40 | Label("N_loop_body") 41 | 42 | c := YMM() 43 | VMOVDQU(Mem{Base: p, Index: i, Scale: 4}, c) 44 | 45 | cDiv, cCarry := YMM(), YMM() 46 | VPSRLVD(logLastBaseQ, c, cDiv) 47 | VPSLLD(Imm(1), c, cCarry) 48 | VPSRLVD(logLastBaseQ, cCarry, cCarry) 49 | VANDPD(allOne, cCarry, cCarry) 50 | VPADDD(cCarry, cDiv, c) 51 | 52 | j := GP64() 53 | MOVQ(level, j) 54 | SUBQ(Imm(1), j) 55 | 56 | jj := GP64() 57 | MOVQ(j, jj) 58 | ADDQ(j, jj) 59 | ADDQ(j, jj) 60 | JMP(LabelRef("level_loop_end")) 61 | Label("level_loop_body") 62 | 63 | u := YMM() 64 | VANDPD(baseMask, c, u) 65 | VPSRLVD(logBase, c, c) 66 | 67 | uDiv := YMM() 68 | VPSRLVD(logBaseSubOne, u, uDiv) 69 | VPADDD(uDiv, c, c) 70 | 71 | uCarry := YMM() 72 | VANDPD(baseHalf, u, uCarry) 73 | VPSLLD(Imm(1), uCarry, uCarry) 74 | VPSUBD(uCarry, u, u) 75 | 76 | dcmpOutj := GP64() 77 | MOVQ(Mem{Base: dcmpOut, Index: jj, Scale: 8}, dcmpOutj) 78 | VMOVDQU(u, Mem{Base: dcmpOutj, Index: i, Scale: 4}) 79 | 80 | SUBQ(Imm(1), j) 81 | SUBQ(Imm(3), jj) 82 | 83 | Label("level_loop_end") 84 | CMPQ(j, Imm(1)) 85 | JGE(LabelRef("level_loop_body")) 86 | 87 | u = YMM() 88 | VANDPD(baseMask, c, u) 89 | 90 | uCarry = YMM() 91 | VANDPD(baseHalf, u, uCarry) 92 | VPSLLD(Imm(1), uCarry, uCarry) 93 | VPSUBD(uCarry, u, u) 94 | 95 | dcmpOut0 := GP64() 96 | MOVQ(Mem{Base: dcmpOut}, dcmpOut0) 97 | VMOVDQU(u, Mem{Base: dcmpOut0, Index: i, Scale: 4}) 98 | 99 | ADDQ(Imm(8), i) 100 | 101 | Label("N_loop_end") 102 | CMPQ(i, N) 103 | JL(LabelRef("N_loop_body")) 104 | 105 | RET() 106 | } 107 | 108 | func DecomposePolyToUint64AVX2() { 109 | TEXT("decomposePolyToUint64AVX2", NOSPLIT, "func(dcmpOut [][]uint64, p []uint64, base uint64, logBase uint64, logLastBaseQ uint64)") 110 | Pragma("noescape") 111 | 112 | dcmpOut := Load(Param("dcmpOut").Base(), GP64()) 113 | p := Load(Param("p").Base(), GP64()) 114 | 115 | N := Load(Param("p").Len(), GP64()) 116 | level := Load(Param("dcmpOut").Len(), GP64()) 117 | 118 | base, logBase, logLastBaseQ := YMM(), YMM(), YMM() 119 | VPBROADCASTQ(NewParamAddr("base", 48), base) 120 | VPBROADCASTQ(NewParamAddr("logBase", 56), logBase) 121 | VPBROADCASTQ(NewParamAddr("logLastBaseQ", 64), logLastBaseQ) 122 | 123 | one := YMM() 124 | VPBROADCASTQ(NewDataAddr(NewStaticSymbol("ONE"), 0), one) 125 | 126 | baseMask, baseHalf, logBaseSubOne := YMM(), YMM(), YMM() 127 | VPSUBQ(one, base, baseMask) 128 | VPSRLQ(Imm(1), base, baseHalf) 129 | VPSUBQ(one, logBase, logBaseSubOne) 130 | 131 | i := GP64() 132 | XORQ(i, i) 133 | JMP(LabelRef("N_loop_end")) 134 | Label("N_loop_body") 135 | 136 | c := YMM() 137 | VMOVDQU(Mem{Base: p, Index: i, Scale: 8}, c) 138 | 139 | cDiv, cCarry := YMM(), YMM() 140 | VPSRLVQ(logLastBaseQ, c, cDiv) 141 | VPSLLQ(Imm(1), c, cCarry) 142 | VPSRLVQ(logLastBaseQ, cCarry, cCarry) 143 | VANDPD(one, cCarry, cCarry) 144 | VPADDQ(cCarry, cDiv, c) 145 | 146 | j := GP64() 147 | MOVQ(level, j) 148 | SUBQ(Imm(1), j) 149 | 150 | jj := GP64() 151 | MOVQ(j, jj) 152 | ADDQ(j, jj) 153 | ADDQ(j, jj) 154 | JMP(LabelRef("level_loop_end")) 155 | Label("level_loop_body") 156 | 157 | u := YMM() 158 | VANDPD(baseMask, c, u) 159 | VPSRLVQ(logBase, c, c) 160 | 161 | uDiv := YMM() 162 | VPSRLVQ(logBaseSubOne, u, uDiv) 163 | VPADDQ(uDiv, c, c) 164 | 165 | uCarry := YMM() 166 | VANDPD(baseHalf, u, uCarry) 167 | VPSLLQ(Imm(1), uCarry, uCarry) 168 | VPSUBQ(uCarry, u, u) 169 | 170 | dcmpOutj := GP64() 171 | MOVQ(Mem{Base: dcmpOut, Index: jj, Scale: 8}, dcmpOutj) 172 | VMOVDQU(u, Mem{Base: dcmpOutj, Index: i, Scale: 8}) 173 | 174 | SUBQ(Imm(1), j) 175 | SUBQ(Imm(3), jj) 176 | 177 | Label("level_loop_end") 178 | CMPQ(j, Imm(1)) 179 | JGE(LabelRef("level_loop_body")) 180 | 181 | u = YMM() 182 | VANDPD(baseMask, c, u) 183 | 184 | uCarry = YMM() 185 | VANDPD(baseHalf, u, uCarry) 186 | VPSLLQ(Imm(1), uCarry, uCarry) 187 | VPSUBQ(uCarry, u, u) 188 | 189 | dcmpOut0 := GP64() 190 | MOVQ(Mem{Base: dcmpOut}, dcmpOut0) 191 | VMOVDQU(u, Mem{Base: dcmpOut0, Index: i, Scale: 8}) 192 | 193 | ADDQ(Imm(4), i) 194 | 195 | Label("N_loop_end") 196 | CMPQ(i, N) 197 | JL(LabelRef("N_loop_body")) 198 | 199 | RET() 200 | } 201 | -------------------------------------------------------------------------------- /mktfhe/fft_glwe_conv.go: -------------------------------------------------------------------------------- 1 | package mktfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/tfhe" 6 | ) 7 | 8 | // GLWETransformer is a multi-key variant of [tfhe.GLWETransformer]. 9 | type GLWETransformer[T tfhe.TorusInt] struct { 10 | // PolyEvaluator is a PolyEvaluator for this GLWETransformer. 11 | PolyEvaluator *poly.Evaluator[T] 12 | } 13 | 14 | // NewGLWETransformer creates a new GLWE transformer with given parameters. 15 | func NewGLWETransformer[T tfhe.TorusInt](N int) *GLWETransformer[T] { 16 | return &GLWETransformer[T]{ 17 | PolyEvaluator: poly.NewEvaluator[T](N), 18 | } 19 | } 20 | 21 | // SafeCopy returns a thread-safe copy. 22 | func (e *GLWETransformer[T]) SafeCopy() *GLWETransformer[T] { 23 | return &GLWETransformer[T]{ 24 | PolyEvaluator: e.PolyEvaluator.SafeCopy(), 25 | } 26 | } 27 | 28 | // FwdFFTGLWESecretKey transforms GLWE secret key to FFT GLWE secret key. 29 | func (e *GLWETransformer[T]) FwdFFTGLWESecretKey(sk tfhe.GLWESecretKey[T]) tfhe.FFTGLWESecretKey[T] { 30 | skOut := tfhe.NewFFTGLWESecretKeyCustom[T](len(sk.Value), e.PolyEvaluator.Rank()) 31 | e.FwdFFTGLWESecretKeyTo(skOut, sk) 32 | return skOut 33 | } 34 | 35 | // FwdFFTGLWESecretKeyTo transforms GLWE secret key to FFT GLWE secret key and writes it to skOut. 36 | func (e *GLWETransformer[T]) FwdFFTGLWESecretKeyTo(skOut tfhe.FFTGLWESecretKey[T], sk tfhe.GLWESecretKey[T]) { 37 | for i := 0; i < len(sk.Value); i++ { 38 | e.PolyEvaluator.FwdFFTPolyTo(skOut.Value[i], sk.Value[i]) 39 | } 40 | } 41 | 42 | // InvFFTGLWESecretKey transforms FFT GLWE secret key to GLWE secret key. 43 | func (e *GLWETransformer[T]) InvFFTGLWESecretKey(sk tfhe.FFTGLWESecretKey[T]) tfhe.GLWESecretKey[T] { 44 | skOut := tfhe.NewGLWESecretKeyCustom[T](len(sk.Value), e.PolyEvaluator.Rank()) 45 | e.InvFFTGLWESecretKeyTo(skOut, sk) 46 | return skOut 47 | } 48 | 49 | // InvFFTGLWESecretKeyTo transforms FFT GLWE secret key to GLWE secret key and writes it to skOut. 50 | func (e *GLWETransformer[T]) InvFFTGLWESecretKeyTo(skOut tfhe.GLWESecretKey[T], sk tfhe.FFTGLWESecretKey[T]) { 51 | for i := 0; i < len(sk.Value); i++ { 52 | e.PolyEvaluator.InvFFTTo(skOut.Value[i], sk.Value[i]) 53 | } 54 | } 55 | 56 | // FwdFFTGLWECiphertext transforms GLWE ciphertext to FFT GLWE ciphertext. 57 | func (e *GLWETransformer[T]) FwdFFTGLWECiphertext(ct GLWECiphertext[T]) FFTGLWECiphertext[T] { 58 | ctOut := NewFFTGLWECiphertextCustom[T](len(ct.Value)-1, e.PolyEvaluator.Rank()) 59 | e.FwdFFTGLWECiphertextTo(ctOut, ct) 60 | return ctOut 61 | } 62 | 63 | // FwdFFTGLWECiphertextTo transforms GLWE ciphertext to FFT GLWE ciphertext and writes it to ctOut. 64 | func (e *GLWETransformer[T]) FwdFFTGLWECiphertextTo(ctOut FFTGLWECiphertext[T], ct GLWECiphertext[T]) { 65 | for i := 0; i < len(ct.Value); i++ { 66 | e.PolyEvaluator.FwdFFTPolyTo(ctOut.Value[i], ct.Value[i]) 67 | } 68 | } 69 | 70 | // InvGLWECiphertext transforms FFT GLWE ciphertext to GLWE ciphertext. 71 | func (e *GLWETransformer[T]) InvGLWECiphertext(ct FFTGLWECiphertext[T]) GLWECiphertext[T] { 72 | ctOut := NewGLWECiphertextCustom[T](len(ct.Value)-1, e.PolyEvaluator.Rank()) 73 | e.InvGLWECiphertextTo(ctOut, ct) 74 | return ctOut 75 | } 76 | 77 | // InvGLWECiphertextTo transforms FFT GLWE ciphertext to GLWE ciphertext and writes it to ctOut. 78 | func (e *GLWETransformer[T]) InvGLWECiphertextTo(ctOut GLWECiphertext[T], ct FFTGLWECiphertext[T]) { 79 | for i := 0; i < len(ct.Value); i++ { 80 | e.PolyEvaluator.InvFFTTo(ctOut.Value[i], ct.Value[i]) 81 | } 82 | } 83 | 84 | // FwdFFTUniEncryption transforms UniEncryption to FFT UniEncryption. 85 | func (e *GLWETransformer[T]) FwdFFTUniEncryption(ct UniEncryption[T]) FFTUniEncryption[T] { 86 | ctOut := NewFFTUniEncryptionCustom(e.PolyEvaluator.Rank(), ct.GadgetParams) 87 | e.FwdFFTUniEncryptionTo(ctOut, ct) 88 | return ctOut 89 | } 90 | 91 | // FwdFFTUniEncryptionTo transforms UniEncryption to FFT UniEncryption and writes it to ctOut. 92 | func (e *GLWETransformer[T]) FwdFFTUniEncryptionTo(ctOut FFTUniEncryption[T], ct UniEncryption[T]) { 93 | for j := 0; j < ct.GadgetParams.Level(); j++ { 94 | e.PolyEvaluator.FwdFFTPolyTo(ctOut.Value[0].Value[j].Value[0], ct.Value[0].Value[j].Value[0]) 95 | e.PolyEvaluator.FwdFFTPolyTo(ctOut.Value[0].Value[j].Value[1], ct.Value[0].Value[j].Value[1]) 96 | 97 | e.PolyEvaluator.FwdFFTPolyTo(ctOut.Value[1].Value[j].Value[0], ct.Value[1].Value[j].Value[0]) 98 | e.PolyEvaluator.FwdFFTPolyTo(ctOut.Value[1].Value[j].Value[1], ct.Value[1].Value[j].Value[1]) 99 | } 100 | } 101 | 102 | // InvFFTUniEncryption transforms FFT UniEncryption to UniEncryption. 103 | func (e *GLWETransformer[T]) InvFFTUniEncryption(ct FFTUniEncryption[T]) UniEncryption[T] { 104 | ctOut := NewUniEncryptionCustom(e.PolyEvaluator.Rank(), ct.GadgetParams) 105 | e.InvFFTUniEncryptionTo(ctOut, ct) 106 | return ctOut 107 | } 108 | 109 | // InvFFTUniEncryptionTo transforms FFT UniEncryption to UniEncryption and writes it to ctOut. 110 | func (e *GLWETransformer[T]) InvFFTUniEncryptionTo(ctOut UniEncryption[T], ct FFTUniEncryption[T]) { 111 | for j := 0; j < ct.GadgetParams.Level(); j++ { 112 | e.PolyEvaluator.InvFFTTo(ctOut.Value[0].Value[j].Value[0], ct.Value[0].Value[j].Value[0]) 113 | e.PolyEvaluator.InvFFTTo(ctOut.Value[0].Value[j].Value[1], ct.Value[0].Value[j].Value[1]) 114 | 115 | e.PolyEvaluator.InvFFTTo(ctOut.Value[1].Value[j].Value[0], ct.Value[1].Value[j].Value[0]) 116 | e.PolyEvaluator.InvFFTTo(ctOut.Value[1].Value[j].Value[1], ct.Value[1].Value[j].Value[1]) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /math/poly/asm_vec_cmplx_test.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "math/cmplx" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/sp301415/tfhe-go/math/vec" 9 | ) 10 | 11 | func TestVecCmplxAssembly(t *testing.T) { 12 | r := rand.New(rand.NewSource(0)) 13 | 14 | N := 1 << 10 15 | eps := 1e-10 16 | 17 | v0 := make([]complex128, N) 18 | v1 := make([]complex128, N) 19 | for i := 0; i < N; i++ { 20 | v0[i] = complex(r.Float64(), r.Float64()) 21 | v1[i] = complex(r.Float64(), r.Float64()) 22 | } 23 | v0Float4 := vec.CmplxToFloat4(v0) 24 | v1Float4 := vec.CmplxToFloat4(v1) 25 | 26 | vOut := make([]complex128, N) 27 | vOutAVX2 := make([]complex128, N) 28 | vOutAVX2Float4 := make([]float64, 2*N) 29 | 30 | t.Run("Add", func(t *testing.T) { 31 | vec.AddTo(vOut, v0, v1) 32 | addCmplxTo(vOutAVX2Float4, v0Float4, v1Float4) 33 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 34 | for i := 0; i < N; i++ { 35 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 36 | t.Fatalf("Add: %v != %v", vOut[i], vOutAVX2[i]) 37 | } 38 | } 39 | }) 40 | 41 | t.Run("Sub", func(t *testing.T) { 42 | vec.SubTo(vOut, v0, v1) 43 | subCmplxTo(vOutAVX2Float4, v0Float4, v1Float4) 44 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 45 | for i := 0; i < N; i++ { 46 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 47 | t.Fatalf("Sub: %v != %v", vOut[i], vOutAVX2[i]) 48 | } 49 | } 50 | }) 51 | 52 | t.Run("Neg", func(t *testing.T) { 53 | vec.NegTo(vOut, v0) 54 | negCmplxTo(vOutAVX2Float4, v0Float4) 55 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 56 | for i := 0; i < N; i++ { 57 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 58 | t.Fatalf("Neg: %v != %v", vOut[i], vOutAVX2[i]) 59 | } 60 | } 61 | }) 62 | 63 | t.Run("FloatMul", func(t *testing.T) { 64 | c := r.Float64() 65 | vec.ScalarMulTo(vOut, v0, complex(c, 0)) 66 | floatMulCmplxTo(vOutAVX2Float4, v0Float4, c) 67 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 68 | for i := 0; i < N; i++ { 69 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 70 | t.Fatalf("FloatMul: %v != %v", vOut[i], vOutAVX2[i]) 71 | } 72 | } 73 | }) 74 | 75 | t.Run("FloatMulAdd", func(t *testing.T) { 76 | vec.Fill(vOut, 0) 77 | vec.Fill(vOutAVX2Float4, 0) 78 | 79 | c := r.Float64() 80 | vec.ScalarMulAddTo(vOut, v0, complex(c, 0)) 81 | floatMulAddCmplxTo(vOutAVX2Float4, v0Float4, c) 82 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 83 | for i := 0; i < N; i++ { 84 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 85 | t.Fatalf("FloatMulAdd: %v != %v", vOut[i], vOutAVX2[i]) 86 | } 87 | } 88 | }) 89 | 90 | t.Run("FloatMulSub", func(t *testing.T) { 91 | vec.Fill(vOut, 0) 92 | vec.Fill(vOutAVX2Float4, 0) 93 | 94 | c := r.Float64() 95 | vec.ScalarMulSubTo(vOut, v0, complex(c, 0)) 96 | floatMulSubCmplxTo(vOutAVX2Float4, v0Float4, c) 97 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 98 | for i := 0; i < N; i++ { 99 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 100 | t.Fatalf("FloatMulSub: %v != %v", vOut[i], vOutAVX2[i]) 101 | } 102 | } 103 | }) 104 | 105 | t.Run("CmplxMul", func(t *testing.T) { 106 | c := complex(r.Float64(), r.Float64()) 107 | vec.ScalarMulTo(vOut, v0, c) 108 | cmplxMulCmplxTo(vOutAVX2Float4, v0Float4, c) 109 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 110 | for i := 0; i < N; i++ { 111 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 112 | t.Fatalf("CmplxMul: %v != %v", vOut[i], vOutAVX2[i]) 113 | } 114 | } 115 | }) 116 | 117 | t.Run("CmplxMulAdd", func(t *testing.T) { 118 | vec.Fill(vOut, 0) 119 | vec.Fill(vOutAVX2Float4, 0) 120 | 121 | c := complex(r.Float64(), r.Float64()) 122 | vec.ScalarMulAddTo(vOut, v0, c) 123 | cmplxMulAddCmplxTo(vOutAVX2Float4, v0Float4, c) 124 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 125 | for i := 0; i < N; i++ { 126 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 127 | t.Fatalf("CmplxMulAdd: %v != %v", vOut[i], vOutAVX2[i]) 128 | } 129 | } 130 | }) 131 | 132 | t.Run("CmplxMulSub", func(t *testing.T) { 133 | vec.Fill(vOut, 0) 134 | vec.Fill(vOutAVX2Float4, 0) 135 | 136 | c := complex(r.Float64(), r.Float64()) 137 | vec.ScalarMulSubTo(vOut, v0, c) 138 | cmplxMulSubCmplxTo(vOutAVX2Float4, v0Float4, c) 139 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 140 | for i := 0; i < N; i++ { 141 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 142 | t.Fatalf("CmplxMulSub: %v != %v", vOut[i], vOutAVX2[i]) 143 | } 144 | } 145 | }) 146 | 147 | t.Run("Mul", func(t *testing.T) { 148 | vec.MulTo(vOut, v0, v1) 149 | mulCmplxTo(vOutAVX2Float4, v0Float4, v1Float4) 150 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 151 | for i := 0; i < N; i++ { 152 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 153 | t.Fatalf("Mul: %v != %v", vOut[i], vOutAVX2[i]) 154 | } 155 | } 156 | }) 157 | 158 | t.Run("MulAdd", func(t *testing.T) { 159 | vec.Fill(vOut, 0) 160 | vec.Fill(vOutAVX2Float4, 0) 161 | 162 | vec.MulAddTo(vOut, v0, v1) 163 | mulAddCmplxTo(vOutAVX2Float4, v0Float4, v1Float4) 164 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 165 | for i := 0; i < N; i++ { 166 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 167 | t.Fatalf("MulAdd: %v != %v", vOut[i], vOutAVX2[i]) 168 | } 169 | } 170 | }) 171 | 172 | t.Run("MulSub", func(t *testing.T) { 173 | vec.Fill(vOut, 0) 174 | vec.Fill(vOutAVX2Float4, 0) 175 | 176 | vec.MulSubTo(vOut, v0, v1) 177 | mulSubCmplxTo(vOutAVX2Float4, v0Float4, v1Float4) 178 | vec.Float4ToCmplxTo(vOutAVX2, vOutAVX2Float4) 179 | for i := 0; i < N; i++ { 180 | if cmplx.Abs(vOut[i]-vOutAVX2[i]) > eps { 181 | t.Fatalf("MulSub: %v != %v", vOut[i], vOutAVX2[i]) 182 | } 183 | } 184 | }) 185 | } 186 | -------------------------------------------------------------------------------- /tfhe/binary_tfhe_test.go: -------------------------------------------------------------------------------- 1 | package tfhe_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/sp301415/tfhe-go/tfhe" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | paramsBinary = tfhe.ParamsBinary.Compile() 14 | encBinary = tfhe.NewBinaryEncryptor(paramsBinary) 15 | evalBinary = tfhe.NewBinaryEvaluator(paramsBinary, encBinary.GenEvalKeyParallel()) 16 | ) 17 | 18 | func TestBinaryParams(t *testing.T) { 19 | t.Run("Compile/ParamsBinary", func(t *testing.T) { 20 | assert.NotPanics(t, func() { tfhe.ParamsBinary.Compile() }) 21 | }) 22 | 23 | t.Run("Compile/ParamsBinaryCompact", func(t *testing.T) { 24 | assert.NotPanics(t, func() { tfhe.ParamsBinaryCompact.Compile() }) 25 | }) 26 | 27 | t.Run("Compile/ParamsBinaryOriginal", func(t *testing.T) { 28 | assert.NotPanics(t, func() { tfhe.ParamsBinaryOriginal.Compile() }) 29 | }) 30 | 31 | t.Run("FailureProbability/ParamsBinary", func(t *testing.T) { 32 | params := tfhe.ParamsBinary.Compile() 33 | 34 | msStdDev := params.EstimateModSwitchStdDev() 35 | brStdDev := params.EstimateBlindRotateStdDev() 36 | ksStdDev := params.EstimateDefaultKeySwitchStdDev() 37 | maxErrorStdDev := math.Sqrt(msStdDev*msStdDev + 4*brStdDev*brStdDev + 4*ksStdDev*ksStdDev) 38 | 39 | bound := math.Exp2(float64(params.LogQ())) / 16 40 | failureProbability := math.Erfc(bound / (math.Sqrt2 * maxErrorStdDev)) 41 | assert.LessOrEqual(t, math.Log2(failureProbability), -64.0) 42 | }) 43 | 44 | t.Run("FailureProbability/ParamsBinaryCompact", func(t *testing.T) { 45 | params := tfhe.ParamsBinaryCompact.Compile() 46 | 47 | msStdDev := params.EstimateModSwitchStdDev() 48 | ksStdDev := params.EstimateDefaultKeySwitchStdDev() 49 | brStdDev := params.EstimateBlindRotateStdDev() 50 | maxErrorStdDev := math.Sqrt(msStdDev*msStdDev + ksStdDev*ksStdDev + 4*brStdDev*brStdDev) 51 | 52 | bound := math.Exp2(float64(params.LogQ())) / 16 53 | failureProbability := math.Erfc(bound / (math.Sqrt2 * maxErrorStdDev)) 54 | assert.LessOrEqual(t, math.Log2(failureProbability), -64.0) 55 | }) 56 | } 57 | 58 | func TestBinaryEvaluator(t *testing.T) { 59 | tests := []struct { 60 | pt0 bool 61 | pt1 bool 62 | ct0 tfhe.LWECiphertext[uint32] 63 | ct1 tfhe.LWECiphertext[uint32] 64 | }{ 65 | {true, true, encBinary.EncryptLWEBool(true), encBinary.EncryptLWEBool(true)}, 66 | {true, false, encBinary.EncryptLWEBool(true), encBinary.EncryptLWEBool(false)}, 67 | {false, true, encBinary.EncryptLWEBool(false), encBinary.EncryptLWEBool(true)}, 68 | {false, false, encBinary.EncryptLWEBool(false), encBinary.EncryptLWEBool(false)}, 69 | } 70 | 71 | t.Run("AND", func(t *testing.T) { 72 | for _, tc := range tests { 73 | assert.Equal(t, tc.pt0 && tc.pt1, encBinary.DecryptLWEBool(evalBinary.AND(tc.ct0, tc.ct1))) 74 | } 75 | }) 76 | 77 | t.Run("NAND", func(t *testing.T) { 78 | for _, tc := range tests { 79 | assert.Equal(t, !(tc.pt0 && tc.pt1), encBinary.DecryptLWEBool(evalBinary.NAND(tc.ct0, tc.ct1))) 80 | } 81 | }) 82 | 83 | t.Run("OR", func(t *testing.T) { 84 | for _, tc := range tests { 85 | assert.Equal(t, tc.pt0 || tc.pt1, encBinary.DecryptLWEBool(evalBinary.OR(tc.ct0, tc.ct1))) 86 | } 87 | }) 88 | 89 | t.Run("NOR", func(t *testing.T) { 90 | for _, tc := range tests { 91 | assert.Equal(t, !(tc.pt0 || tc.pt1), encBinary.DecryptLWEBool(evalBinary.NOR(tc.ct0, tc.ct1))) 92 | } 93 | }) 94 | 95 | t.Run("XOR", func(t *testing.T) { 96 | for _, tc := range tests { 97 | assert.Equal(t, tc.pt0 != tc.pt1, encBinary.DecryptLWEBool(evalBinary.XOR(tc.ct0, tc.ct1))) 98 | } 99 | }) 100 | 101 | t.Run("XNOR", func(t *testing.T) { 102 | for _, tc := range tests { 103 | assert.Equal(t, tc.pt0 == tc.pt1, encBinary.DecryptLWEBool(evalBinary.XNOR(tc.ct0, tc.ct1))) 104 | } 105 | }) 106 | 107 | t.Run("Bits", func(t *testing.T) { 108 | msg0, msg1 := 0b01, 0b10 109 | ct0 := encBinary.EncryptLWEBits(msg0, 4) 110 | ct1 := encBinary.EncryptLWEBits(msg1, 4) 111 | 112 | ctOut := encBinary.EncryptLWEBits(0, 4) 113 | for i := range ctOut { 114 | evalBinary.XORTo(ctOut[i], ct0[i], ct1[i]) 115 | } 116 | 117 | assert.Equal(t, encBinary.DecryptLWEBits(ctOut), msg0^msg1) 118 | }) 119 | 120 | t.Run("BootstrapOriginal", func(t *testing.T) { 121 | paramsBinaryOriginal := paramsBinary.Literal().WithBlockSize(1).Compile() 122 | 123 | originalEncryptor := tfhe.NewBinaryEncryptorWithKey(paramsBinaryOriginal, encBinary.Encryptor.SecretKey) 124 | originalEvaluator := tfhe.NewBinaryEvaluator(paramsBinaryOriginal, evalBinary.Evaluator.EvalKey) 125 | 126 | for _, tc := range tests { 127 | assert.Equal(t, tc.pt0 && tc.pt1, originalEncryptor.DecryptLWEBool(originalEvaluator.AND(tc.ct0, tc.ct1))) 128 | } 129 | }) 130 | } 131 | 132 | func BenchmarkGateBootstrap(b *testing.B) { 133 | ct0 := encBinary.EncryptLWEBool(true) 134 | ct1 := encBinary.EncryptLWEBool(false) 135 | ctOut := tfhe.NewLWECiphertext(paramsBinary) 136 | b.ResetTimer() 137 | 138 | for i := 0; i < b.N; i++ { 139 | evalBinary.ANDTo(ctOut, ct0, ct1) 140 | } 141 | } 142 | 143 | func ExampleBinaryEvaluator() { 144 | params := tfhe.ParamsBinary.Compile() 145 | 146 | enc := tfhe.NewBinaryEncryptor(params) 147 | 148 | bits := 16 149 | ct0 := enc.EncryptLWEBits(3, bits) 150 | ct1 := enc.EncryptLWEBits(3, bits) 151 | 152 | eval := tfhe.NewBinaryEvaluator(params, enc.GenEvalKeyParallel()) 153 | 154 | ctXNOR := tfhe.NewLWECiphertext(params) 155 | ctOut := eval.XNOR(ct0[0], ct1[0]) 156 | for i := 1; i < bits; i++ { 157 | eval.XNORTo(ctXNOR, ct0[i], ct1[i]) 158 | eval.ANDTo(ctOut, ctXNOR, ctOut) 159 | } 160 | 161 | fmt.Println(enc.DecryptLWEBool(ctOut)) 162 | // Output: 163 | // true 164 | } 165 | -------------------------------------------------------------------------------- /tfhe/encryptor_key_marshal.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | 8 | "github.com/sp301415/tfhe-go/math/num" 9 | ) 10 | 11 | // ByteSize returns the size of the key in bytes. 12 | func (sk SecretKey[T]) ByteSize() int { 13 | glweRank := len(sk.GLWEKey.Value) 14 | polyRank := sk.GLWEKey.Value[0].Rank() 15 | 16 | return 24 + glweRank*polyRank*num.ByteSizeT[T]() + glweRank*polyRank*8 17 | } 18 | 19 | // headerWriteTo writes the header. 20 | func (sk SecretKey[T]) headerWriteTo(w io.Writer) (n int64, err error) { 21 | var nWrite int 22 | var buf [8]byte 23 | 24 | lweDimension := len(sk.LWEKey.Value) 25 | binary.BigEndian.PutUint64(buf[:], uint64(lweDimension)) 26 | if nWrite, err = w.Write(buf[:]); err != nil { 27 | return n + int64(nWrite), err 28 | } 29 | n += int64(nWrite) 30 | 31 | glweRank := len(sk.GLWEKey.Value) 32 | binary.BigEndian.PutUint64(buf[:], uint64(glweRank)) 33 | if nWrite, err = w.Write(buf[:]); err != nil { 34 | return n + int64(nWrite), err 35 | } 36 | n += int64(nWrite) 37 | 38 | polyRank := sk.GLWEKey.Value[0].Rank() 39 | binary.BigEndian.PutUint64(buf[:], uint64(polyRank)) 40 | if nWrite, err = w.Write(buf[:]); err != nil { 41 | return n + int64(nWrite), err 42 | } 43 | n += int64(nWrite) 44 | 45 | return 46 | } 47 | 48 | // WriteTo implements the [io.WriterTo] interface. 49 | // 50 | // The encoded form is as follows: 51 | // 52 | // [8] LWEDimension 53 | // [8] GLWERank 54 | // [8] PolyRank 55 | // LWELargeKey 56 | // FFTGLWEKey 57 | func (sk SecretKey[T]) WriteTo(w io.Writer) (n int64, err error) { 58 | var nWrite int64 59 | 60 | if nWrite, err = sk.headerWriteTo(w); err != nil { 61 | return n + nWrite, err 62 | } 63 | n += nWrite 64 | 65 | if nWrite, err = sk.LWELargeKey.valueWriteTo(w); err != nil { 66 | return n + nWrite, err 67 | } 68 | n += nWrite 69 | 70 | if nWrite, err = sk.FFTGLWEKey.valueWriteTo(w); err != nil { 71 | return n + nWrite, err 72 | } 73 | n += nWrite 74 | 75 | if n < int64(sk.ByteSize()) { 76 | return n, io.ErrShortWrite 77 | } 78 | 79 | return 80 | } 81 | 82 | // headerReadFrom reads the header, and initializes the value. 83 | func (sk *SecretKey[T]) headerReadFrom(r io.Reader) (n int64, err error) { 84 | var nRead int 85 | var buf [8]byte 86 | 87 | if nRead, err = io.ReadFull(r, buf[:]); err != nil { 88 | return n + int64(nRead), err 89 | } 90 | n += int64(nRead) 91 | lweDimension := int(binary.BigEndian.Uint64(buf[:])) 92 | 93 | if nRead, err = io.ReadFull(r, buf[:]); err != nil { 94 | return n + int64(nRead), err 95 | } 96 | n += int64(nRead) 97 | glweRank := int(binary.BigEndian.Uint64(buf[:])) 98 | 99 | if nRead, err = io.ReadFull(r, buf[:]); err != nil { 100 | return n + int64(nRead), err 101 | } 102 | n += int64(nRead) 103 | polyRank := int(binary.BigEndian.Uint64(buf[:])) 104 | 105 | *sk = NewSecretKeyCustom[T](lweDimension, glweRank, polyRank) 106 | 107 | return 108 | } 109 | 110 | // ReadFrom implements the [io.ReaderFrom] interface. 111 | func (sk *SecretKey[T]) ReadFrom(r io.Reader) (n int64, err error) { 112 | var nRead int64 113 | 114 | if nRead, err = sk.headerReadFrom(r); err != nil { 115 | return n + nRead, err 116 | } 117 | n += nRead 118 | 119 | if nRead, err = sk.LWELargeKey.valueReadFrom(r); err != nil { 120 | return n + nRead, err 121 | } 122 | n += nRead 123 | 124 | if nRead, err = sk.FFTGLWEKey.valueReadFrom(r); err != nil { 125 | return n + nRead, err 126 | } 127 | n += nRead 128 | 129 | return 130 | } 131 | 132 | // MarshalBinary implements the [encoding.BinaryMarshaler] interface. 133 | func (sk SecretKey[T]) MarshalBinary() (data []byte, err error) { 134 | buf := bytes.NewBuffer(make([]byte, 0, sk.ByteSize())) 135 | _, err = sk.WriteTo(buf) 136 | return buf.Bytes(), err 137 | } 138 | 139 | // UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. 140 | func (sk *SecretKey[T]) UnmarshalBinary(data []byte) error { 141 | buf := bytes.NewBuffer(data) 142 | _, err := sk.ReadFrom(buf) 143 | return err 144 | } 145 | 146 | // ByteSize returns the size of the key in bytes. 147 | func (pk PublicKey[T]) ByteSize() int { 148 | return pk.LWEKey.ByteSize() + pk.GLWEKey.ByteSize() 149 | } 150 | 151 | // WriteTo implements the [io.WriterTo] interface. 152 | // 153 | // The encoded form is as follows: 154 | // 155 | // LWEKey 156 | // GLWEKey 157 | func (pk PublicKey[T]) WriteTo(w io.Writer) (n int64, err error) { 158 | var nWrite int64 159 | 160 | if nWrite, err = pk.LWEKey.WriteTo(w); err != nil { 161 | return n + nWrite, err 162 | } 163 | n += nWrite 164 | 165 | if nWrite, err = pk.GLWEKey.WriteTo(w); err != nil { 166 | return n + nWrite, err 167 | } 168 | n += nWrite 169 | 170 | if n < int64(pk.ByteSize()) { 171 | return n, io.ErrShortWrite 172 | } 173 | 174 | return 175 | } 176 | 177 | // ReadFrom implements the [io.ReaderFrom] interface. 178 | func (pk *PublicKey[T]) ReadFrom(r io.Reader) (n int64, err error) { 179 | var nRead int64 180 | 181 | if nRead, err = pk.LWEKey.ReadFrom(r); err != nil { 182 | return n + nRead, err 183 | } 184 | n += nRead 185 | 186 | if nRead, err = pk.GLWEKey.ReadFrom(r); err != nil { 187 | return n + nRead, err 188 | } 189 | n += nRead 190 | 191 | return 192 | } 193 | 194 | // MarshalBinary implements the [encoding.BinaryMarshaler] interface. 195 | func (pk PublicKey[T]) MarshalBinary() (data []byte, err error) { 196 | buf := bytes.NewBuffer(make([]byte, 0, pk.ByteSize())) 197 | _, err = pk.WriteTo(buf) 198 | return buf.Bytes(), err 199 | } 200 | 201 | // UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. 202 | func (pk *PublicKey[T]) UnmarshalBinary(data []byte) error { 203 | buf := bytes.NewBuffer(data) 204 | _, err := pk.ReadFrom(buf) 205 | return err 206 | } 207 | -------------------------------------------------------------------------------- /xtfhe/fhew_bootstrap_keygen.go: -------------------------------------------------------------------------------- 1 | package xtfhe 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | 7 | "github.com/sp301415/tfhe-go/math/num" 8 | "github.com/sp301415/tfhe-go/tfhe" 9 | ) 10 | 11 | // EvaluationKey is a public key for FHEW Evaluator. 12 | type FHEWEvaluationKey[T tfhe.TorusInt] struct { 13 | // BlindRotateKey is the key for blind rotation. 14 | BlindRotateKey tfhe.BlindRotateKey[T] 15 | // KeySwitchKey is the key for key switching. 16 | KeySwitchKey tfhe.LWEKeySwitchKey[T] 17 | // GaloisKey is the key for Galois automorphisms. 18 | // GaloisKey has length WindowSize + 1, 19 | // where the first element is key for X -> X^-5, 20 | // and the next WindowSize elements are keys for X -> X^5^i. 21 | GaloisKey []tfhe.GLWEKeySwitchKey[T] 22 | } 23 | 24 | // GenEvalKey samples a new evaluation key for bootstrapping. 25 | // 26 | // This can take a long time. 27 | // Use [*FHEWEncryptor.GenEvalKeyParallel] for better key generation performance. 28 | func (e *FHEWEncryptor[T]) GenEvalKey() FHEWEvaluationKey[T] { 29 | return FHEWEvaluationKey[T]{ 30 | BlindRotateKey: e.GenBlindRotateKey(), 31 | KeySwitchKey: e.GenDefaultKeySwitchKey(), 32 | GaloisKey: e.GenGaloisKey(), 33 | } 34 | } 35 | 36 | // GenEvalKeyParallel samples a new evaluation key for bootstrapping in parallel. 37 | func (e *FHEWEncryptor[T]) GenEvalKeyParallel() FHEWEvaluationKey[T] { 38 | return FHEWEvaluationKey[T]{ 39 | BlindRotateKey: e.GenBlindRotateKeyParallel(), 40 | KeySwitchKey: e.GenDefaultKeySwitchKeyParallel(), 41 | GaloisKey: e.GenGaloisKey(), 42 | } 43 | } 44 | 45 | // GenBlindRotateKey samples a new bootstrapping key. 46 | // 47 | // This can take a long time. 48 | // Use [*FHEWEncryptor.GenBlindRotateKeyParallel] for better key generation performance. 49 | func (e *FHEWEncryptor[T]) GenBlindRotateKey() tfhe.BlindRotateKey[T] { 50 | brk := tfhe.NewBlindRotateKey(e.Params.baseParams) 51 | 52 | for i := 0; i < e.Params.baseParams.LWEDimension(); i++ { 53 | var sMonoIdx int 54 | 55 | var z T 56 | switch any(z).(type) { 57 | case uint32: 58 | sMonoIdx = int(int32(e.SecretKey.LWEKey.Value[i])) 59 | case uint64: 60 | sMonoIdx = int(int64(e.SecretKey.LWEKey.Value[i])) 61 | } 62 | 63 | for j := 0; j < e.Params.baseParams.GLWERank()+1; j++ { 64 | if j == 0 { 65 | e.buf.ptGGSW.Clear() 66 | e.buf.ptGGSW.Coeffs[0] = 1 67 | e.PolyEvaluator.MonomialMulPolyInPlace(e.buf.ptGGSW, sMonoIdx) 68 | } else { 69 | e.PolyEvaluator.MonomialMulPolyTo(e.buf.ptGGSW, e.SecretKey.GLWEKey.Value[j-1], sMonoIdx) 70 | } 71 | for k := 0; k < e.Params.baseParams.BlindRotateParams().Level(); k++ { 72 | e.PolyEvaluator.ScalarMulPolyTo(e.buf.ctGLWE.Value[0], e.buf.ptGGSW, e.Params.baseParams.BlindRotateParams().BaseQ(k)) 73 | e.EncryptGLWEBody(e.buf.ctGLWE) 74 | e.FwdFFTGLWECiphertextTo(brk.Value[i].Value[j].Value[k], e.buf.ctGLWE) 75 | } 76 | } 77 | } 78 | 79 | return brk 80 | } 81 | 82 | // GenBlindRotateKeyParallel samples a new bootstrapping key in parallel. 83 | func (e *FHEWEncryptor[T]) GenBlindRotateKeyParallel() tfhe.BlindRotateKey[T] { 84 | brk := tfhe.NewBlindRotateKey(e.Params.baseParams) 85 | 86 | workSize := e.Params.baseParams.LWEDimension() * (e.Params.baseParams.GLWERank() + 1) 87 | chunkCount := num.Min(runtime.NumCPU(), num.Sqrt(workSize)) 88 | 89 | encryptorPool := make([]*FHEWEncryptor[T], chunkCount) 90 | for i := range encryptorPool { 91 | encryptorPool[i] = e.SafeCopy() 92 | } 93 | 94 | jobs := make(chan [2]int) 95 | go func() { 96 | defer close(jobs) 97 | for i := 0; i < e.Params.baseParams.LWEDimension(); i++ { 98 | for j := 0; j < e.Params.baseParams.GLWERank()+1; j++ { 99 | jobs <- [2]int{i, j} 100 | } 101 | } 102 | }() 103 | 104 | var wg sync.WaitGroup 105 | wg.Add(chunkCount) 106 | for i := 0; i < chunkCount; i++ { 107 | go func(i int) { 108 | eIdx := encryptorPool[i] 109 | for job := range jobs { 110 | i, j := job[0], job[1] 111 | 112 | var sMonoIdx int 113 | 114 | var z T 115 | switch any(z).(type) { 116 | case uint32: 117 | sMonoIdx = int(int32(eIdx.SecretKey.LWEKey.Value[i])) 118 | case uint64: 119 | sMonoIdx = int(int64(eIdx.SecretKey.LWEKey.Value[i])) 120 | } 121 | 122 | if j == 0 { 123 | eIdx.buf.ptGGSW.Clear() 124 | eIdx.buf.ptGGSW.Coeffs[0] = 1 125 | eIdx.PolyEvaluator.MonomialMulPolyInPlace(eIdx.buf.ptGGSW, sMonoIdx) 126 | } else { 127 | eIdx.PolyEvaluator.MonomialMulPolyTo(eIdx.buf.ptGGSW, eIdx.SecretKey.GLWEKey.Value[j-1], sMonoIdx) 128 | } 129 | for k := 0; k < eIdx.Params.baseParams.BlindRotateParams().Level(); k++ { 130 | eIdx.PolyEvaluator.ScalarMulPolyTo(eIdx.buf.ctGLWE.Value[0], eIdx.buf.ptGGSW, eIdx.Params.baseParams.BlindRotateParams().BaseQ(k)) 131 | eIdx.EncryptGLWEBody(eIdx.buf.ctGLWE) 132 | eIdx.FwdFFTGLWECiphertextTo(brk.Value[i].Value[j].Value[k], eIdx.buf.ctGLWE) 133 | } 134 | } 135 | wg.Done() 136 | }(i) 137 | } 138 | wg.Wait() 139 | 140 | return brk 141 | } 142 | 143 | // GenGaloisKey samples a new Galois key for bootstrapping. 144 | func (e *FHEWEncryptor[T]) GenGaloisKey() []tfhe.GLWEKeySwitchKey[T] { 145 | glk := make([]tfhe.GLWEKeySwitchKey[T], e.Params.windowSize+1) 146 | 147 | for j := 0; j < e.Params.baseParams.GLWERank(); j++ { 148 | e.PolyEvaluator.PermutePolyTo(e.buf.skPermute.Value[j], e.SecretKey.GLWEKey.Value[j], -5) 149 | } 150 | glk[0] = e.GenGLWEKeySwitchKey(e.buf.skPermute, e.Params.baseParams.BlindRotateParams()) 151 | 152 | for i := 1; i < e.Params.windowSize+1; i++ { 153 | d := num.ModExp(5, i, 2*e.Params.baseParams.PolyRank()) 154 | for j := 0; j < e.Params.baseParams.GLWERank(); j++ { 155 | e.PolyEvaluator.PermutePolyTo(e.buf.skPermute.Value[j], e.SecretKey.GLWEKey.Value[j], d) 156 | } 157 | glk[i] = e.GenGLWEKeySwitchKey(e.buf.skPermute, e.Params.baseParams.BlindRotateParams()) 158 | } 159 | 160 | return glk 161 | } 162 | -------------------------------------------------------------------------------- /tfhe/encryptor_key.go: -------------------------------------------------------------------------------- 1 | package tfhe 2 | 3 | import ( 4 | "github.com/sp301415/tfhe-go/math/poly" 5 | "github.com/sp301415/tfhe-go/math/vec" 6 | ) 7 | 8 | // SecretKey is a structure containing LWE and GLWE key. 9 | // All keys should be treated as read-only. 10 | // Changing them mid-operation will usually result in wrong results. 11 | // 12 | // LWEKey and GLWEKey is sampled together, as explained in https://eprint.iacr.org/2023/958. 13 | // As a result, LWEKey and GLWEKey share the same backing slice, so modifying one will affect the other. 14 | type SecretKey[T TorusInt] struct { 15 | // LWELargeKey is a LWE key with length GLWEDimension. 16 | // Essentially, this is same as GLWEKey but parsed differently. 17 | LWELargeKey LWESecretKey[T] 18 | // GLWEKey is a key used for GLWE encryption and decryption. 19 | // Essentially, this is same as LWEKey but parsed differently. 20 | GLWEKey GLWESecretKey[T] 21 | // FFTGLWEKey is a fourier transformed GLWEKey. 22 | // Used for GLWE encryption. 23 | FFTGLWEKey FFTGLWESecretKey[T] 24 | // LWEKey is a LWE key with length LWEDimension. 25 | // Essentially, this is the first LWEDimension elements of LWEKey. 26 | LWEKey LWESecretKey[T] 27 | } 28 | 29 | // NewSecretKey creates a new SecretKey. 30 | // Each key shares the same backing slice, held by LWEKey. 31 | func NewSecretKey[T TorusInt](params Parameters[T]) SecretKey[T] { 32 | lweLargeKey := LWESecretKey[T]{Value: make([]T, params.glweDimension)} 33 | 34 | glweKey := GLWESecretKey[T]{Value: make([]poly.Poly[T], params.glweRank)} 35 | for i := 0; i < params.glweRank; i++ { 36 | glweKey.Value[i].Coeffs = lweLargeKey.Value[i*params.polyRank : (i+1)*params.polyRank] 37 | } 38 | FFTGLWEKey := NewFFTGLWESecretKey(params) 39 | 40 | lweKey := LWESecretKey[T]{Value: lweLargeKey.Value[:params.lweDimension]} 41 | 42 | return SecretKey[T]{ 43 | LWELargeKey: lweLargeKey, 44 | GLWEKey: glweKey, 45 | FFTGLWEKey: FFTGLWEKey, 46 | LWEKey: lweKey, 47 | } 48 | } 49 | 50 | // NewSecretKeyCustom creates a new SecretKey with given dimension and polyRank. 51 | // Each key shares the same backing slice, held by LWEKey. 52 | func NewSecretKeyCustom[T TorusInt](lweDimension, glweRank, polyRank int) SecretKey[T] { 53 | lweLargeKey := LWESecretKey[T]{Value: make([]T, glweRank*polyRank)} 54 | 55 | glweKey := GLWESecretKey[T]{Value: make([]poly.Poly[T], glweRank)} 56 | for i := 0; i < glweRank; i++ { 57 | glweKey.Value[i].Coeffs = lweLargeKey.Value[i*polyRank : (i+1)*polyRank] 58 | } 59 | FFTGLWEKey := NewFFTGLWESecretKeyCustom[T](glweRank, polyRank) 60 | 61 | lweKey := LWESecretKey[T]{Value: lweLargeKey.Value[:lweDimension]} 62 | 63 | return SecretKey[T]{ 64 | LWELargeKey: lweLargeKey, 65 | GLWEKey: glweKey, 66 | FFTGLWEKey: FFTGLWEKey, 67 | LWEKey: lweKey, 68 | } 69 | } 70 | 71 | // Copy returns a copy of the key. 72 | func (sk SecretKey[T]) Copy() SecretKey[T] { 73 | lweLargeKey := sk.LWELargeKey.Copy() 74 | 75 | glweKey := GLWESecretKey[T]{Value: make([]poly.Poly[T], len(sk.GLWEKey.Value))} 76 | for i := range glweKey.Value { 77 | polyRank := sk.GLWEKey.Value[i].Rank() 78 | glweKey.Value[i].Coeffs = lweLargeKey.Value[i*polyRank : (i+1)*polyRank] 79 | } 80 | FFTGLWEKey := sk.FFTGLWEKey.Copy() 81 | 82 | lweKey := LWESecretKey[T]{Value: lweLargeKey.Value[:len(sk.LWEKey.Value)]} 83 | 84 | return SecretKey[T]{ 85 | LWELargeKey: lweLargeKey, 86 | GLWEKey: glweKey, 87 | FFTGLWEKey: FFTGLWEKey, 88 | LWEKey: lweKey, 89 | } 90 | } 91 | 92 | // CopyFrom copies values from the key. 93 | func (sk *SecretKey[T]) CopyFrom(skIn SecretKey[T]) { 94 | copy(sk.LWELargeKey.Value, skIn.LWELargeKey.Value) 95 | sk.FFTGLWEKey.CopyFrom(skIn.FFTGLWEKey) 96 | } 97 | 98 | // Clear clears the key. 99 | func (sk *SecretKey[T]) Clear() { 100 | vec.Fill(sk.LWELargeKey.Value, 0) 101 | sk.FFTGLWEKey.Clear() 102 | } 103 | 104 | // PublicKey is a structure containing LWE and GLWE public key. 105 | // All keys should be treated as read-only. 106 | // Changing them mid-operation will usually result in wrong computation. 107 | // 108 | // We use compact public key, explained in https://eprint.iacr.org/2023/603. 109 | // This means that not all parameters support public key encryption. 110 | type PublicKey[T TorusInt] struct { 111 | // LWEKey is a public key used for LWE encryption. 112 | // It is essentially a GLWE encryption of zero, but with reversed GLWE key, 113 | // as explained in https://eprint.iacr.org/2023/603. 114 | LWEKey LWEPublicKey[T] 115 | 116 | // GLWEKey is a public key used for LWE and GLWE encryption. 117 | // It is essentially a GLWE encryption of zero. 118 | GLWEKey GLWEPublicKey[T] 119 | } 120 | 121 | // NewPublicKey creates a new PublicKey. 122 | // 123 | // Panics when the parameters do not support public key encryption. 124 | func NewPublicKey[T TorusInt](params Parameters[T]) PublicKey[T] { 125 | if !params.IsPublicKeyEncryptable() { 126 | panic("Parameters do not support public key encryption") 127 | } 128 | 129 | return PublicKey[T]{ 130 | LWEKey: NewLWEPublicKey(params), 131 | GLWEKey: NewGLWEPublicKey(params), 132 | } 133 | } 134 | 135 | // NewPublicKeyCustom creates a new PublicKey with given dimension and polyRank. 136 | func NewPublicKeyCustom[T TorusInt](glweRank, polyRank int) PublicKey[T] { 137 | return PublicKey[T]{ 138 | LWEKey: NewLWEPublicKeyCustom[T](glweRank, polyRank), 139 | GLWEKey: NewGLWEPublicKeyCustom[T](glweRank, polyRank), 140 | } 141 | } 142 | 143 | // Copy returns a copy of the key. 144 | func (pk PublicKey[T]) Copy() PublicKey[T] { 145 | return PublicKey[T]{ 146 | LWEKey: pk.LWEKey.Copy(), 147 | GLWEKey: pk.GLWEKey.Copy(), 148 | } 149 | } 150 | 151 | // CopyFrom copies values from the key. 152 | func (pk *PublicKey[T]) CopyFrom(pkIn PublicKey[T]) { 153 | pk.LWEKey.CopyFrom(pkIn.LWEKey) 154 | pk.GLWEKey.CopyFrom(pkIn.GLWEKey) 155 | } 156 | 157 | // Clear clears the key. 158 | func (pk *PublicKey[T]) Clear() { 159 | pk.LWEKey.Clear() 160 | pk.GLWEKey.Clear() 161 | } 162 | --------------------------------------------------------------------------------