├── .github ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ ├── feature-request.md │ ├── question.md │ └── suggestion.md ├── SECURITY.md └── workflows │ └── ci.yml ├── .gitignore ├── LICENCE ├── Makefile ├── README.md ├── examples ├── README.md ├── example_test.go ├── examples.go ├── multi_party │ ├── int │ │ ├── pir │ │ │ ├── main.go │ │ │ └── main_test.go │ │ └── psi │ │ │ ├── main.go │ │ │ └── main_test.go │ └── setup │ │ ├── one_round │ │ └── main.go │ │ └── threshold │ │ ├── main.go │ │ └── main_test.go ├── params.go └── single_party │ ├── hebin │ └── blind_rotations │ │ ├── main.go │ │ └── main_test.go │ ├── hefloat │ ├── advanced │ │ ├── bootstrapping │ │ │ ├── basics │ │ │ │ ├── main.go │ │ │ │ └── main_test.go │ │ │ ├── high_precision │ │ │ │ ├── main.go │ │ │ │ └── main_test.go │ │ │ └── slim │ │ │ │ ├── main.go │ │ │ │ └── main_test.go │ │ ├── polynomial_evaluation │ │ │ ├── main.go │ │ │ └── main_test.go │ │ └── scheme_switching │ │ │ ├── main.go │ │ │ └── main_test.go │ ├── basics │ │ ├── main.go │ │ └── main_test.go │ └── template │ │ ├── main.go │ │ └── main_test.go │ └── heint │ └── template │ ├── main.go │ └── main_test.go ├── go.mod ├── go.sum ├── he ├── bootstrapper.go ├── he.go ├── hebin │ ├── README.md │ ├── blindrotation.go │ ├── blindrotation_benchmark_test.go │ ├── blindrotation_test.go │ ├── evaluator.go │ ├── keys.go │ └── utils.go ├── hefloat │ ├── README.md │ ├── bootstrapping │ │ ├── README.md │ │ ├── bootstrapper.go │ │ ├── bootstrapping.go │ │ ├── bootstrapping_bench_test.go │ │ ├── bootstrapping_test.go │ │ ├── evaluator.go │ │ ├── failure_probability.go │ │ ├── keys.go │ │ ├── parameters.go │ │ ├── parameters_literal.go │ │ └── sk_bootstrapper.go │ ├── bridge.go │ ├── bridge_test.go │ ├── comparisons.go │ ├── comparisons_test.go │ ├── cosine │ │ └── cosine_approx.go │ ├── dft.go │ ├── dft_test.go │ ├── encoder.go │ ├── encoder_test.go │ ├── evaluator.go │ ├── evaluator_test.go │ ├── example_parameters.go │ ├── hefloat.go │ ├── hefloat_benchmark_test.go │ ├── hefloat_test.go │ ├── inverse.go │ ├── inverse_test.go │ ├── linear_transformation.go │ ├── linear_transformation_test.go │ ├── minimax_composite_polynomial.go │ ├── minimax_composite_polynomial_evaluator.go │ ├── mod1_evaluator.go │ ├── mod1_evaluator_test.go │ ├── mod1_parameters.go │ ├── parameters_literal.go │ ├── parameters_test.go │ ├── params.go │ ├── polynomial_evaluator.go │ ├── polynomial_evaluator_sim.go │ ├── polynomial_evaluator_test.go │ ├── scaling.go │ ├── stats.go │ ├── utils.go │ └── vector_ops.go ├── heint │ ├── README.md │ ├── encoder.go │ ├── encoder_test.go │ ├── evaluator.go │ ├── evaluator_test.go │ ├── examples_parameters.go │ ├── heint.go │ ├── heint_benchmark_test.go │ ├── heint_test.go │ ├── linear_transformation_test.go │ ├── parameters.go │ ├── parameters_literal.go │ ├── parameters_test.go │ ├── polynomial_evaluator.go │ ├── polynomial_evaluator_sim.go │ └── polynomial_evaluator_test.go ├── linear_transformation.go ├── linear_transformation_diagonals.go ├── linear_transformation_evaluator.go ├── linear_transformation_permutation.go ├── polynomial.go ├── polynomial_encoded.go ├── polynomial_evaluator.go ├── polynomial_evaluator_sim.go ├── power_basis.go ├── power_basis_test.go ├── ring_packing.go ├── ring_packing_keys.go ├── ring_packing_test.go └── types.go ├── lattigo.go ├── logo.png ├── mhe ├── README.md ├── additive_shares.go ├── circular_ciphertext.go ├── circular_gadget_ciphertext.go ├── evaluation_key.go ├── gadget_ciphertext.go ├── galois_key.go ├── keyswitch.go ├── mhe.go ├── mhe_benchmark_test.go ├── mhe_test.go ├── mhefloat │ ├── mhefloat.go │ ├── mhefloat_benchmark_test.go │ ├── mhefloat_test.go │ ├── refresh.go │ ├── sharing.go │ ├── test_params.go │ ├── transform.go │ └── utils.go ├── mheint │ ├── mheint.go │ ├── mheint_benchmark_test.go │ ├── mheint_test.go │ ├── refresh.go │ ├── sharing.go │ ├── test_parameters.go │ └── transform.go ├── public_key.go ├── refresh.go ├── relinearization_key.go ├── shares.go ├── test_params.go ├── threshold.go └── utils.go ├── rgsw ├── encryptor.go ├── evaluator.go ├── rgsw.go ├── rgsw_test.go ├── test_parameters.go └── utils.go ├── ring ├── README.md ├── automorphism.go ├── distribution.go ├── interpolation.go ├── interpolation_test.go ├── modular_reduction.go ├── ntt.go ├── ntt_benchmark_test.go ├── ntt_conjugate_invariant.go ├── ntt_standard.go ├── ntt_test.go ├── poly.go ├── primes.go ├── ring.go ├── ring_ops.go ├── rns_automorphism.go ├── rns_basis_extension.go ├── rns_conjugate_invariant.go ├── rns_ntt.go ├── rns_poly.go ├── rns_ring.go ├── rns_ring_benchmark_test.go ├── rns_ring_ops.go ├── rns_ring_test.go ├── rns_sampler.go ├── rns_sampler_gaussian.go ├── rns_sampler_ternary.go ├── rns_sampler_uniform.go ├── rns_scalar.go ├── rns_scaling.go ├── structs.go ├── structs_test.go ├── test_params.go ├── utils.go └── vec_ops.go ├── rlwe ├── README.md ├── ciphertext.go ├── compression.go ├── decryptor.go ├── digit_decomposition.go ├── distribution.go ├── element.go ├── encryptor.go ├── evaluator.go ├── evaluator_automorphism.go ├── evaluator_evaluationkey.go ├── evaluator_gadget_product.go ├── example_parameters.go ├── gadgetciphertext.go ├── keygenerator.go ├── keys.go ├── metadata.go ├── operand.go ├── parameters_literal.go ├── params.go ├── plaintext.go ├── rlwe.go ├── rlwe_benchmark_test.go ├── rlwe_test.go ├── scale.go ├── security.go ├── test_params_test.go ├── traces.go └── utils.go └── utils ├── bignum ├── bignum.go ├── bignum_test.go ├── chebyshev_approximation.go ├── complex.go ├── eval.go ├── float.go ├── float_test.go ├── int.go ├── interval.go ├── metadata.go ├── minimax_approximation.go ├── polynomial.go └── remez_test.go ├── buffer ├── buffer.go ├── reader.go ├── utils.go └── writer.go ├── concurrency ├── ressource_manager_test.go └── ressources_manager.go ├── factorization ├── factorization.go ├── factorization_test.go └── weierstrass.go ├── pointy.go ├── sampling └── source.go ├── slices.go ├── structs ├── map.go ├── matrix.go ├── structs.go ├── structs_test.go └── vector.go ├── utils.go └── utils_test.go /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report. 3 | title: "Bug [package]: " 4 | labels: ["bug"] 5 | assignees: 6 | - Pro7ech 7 | body: 8 | - type: input 9 | id: contact 10 | attributes: 11 | label: Contact Details 12 | description: How can we get in touch with you if we need more info? 13 | placeholder: ex. email@example.com 14 | validations: 15 | required: false 16 | - type: dropdown 17 | id: version 18 | attributes: 19 | label: Does this issue still happens with the latest release (@latest) ? 20 | options: 21 | - "Yes" 22 | - "No" 23 | default: 0 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: description 28 | attributes: 29 | label: Description 30 | description: Please describe what you were trying to do, what you where expecting to happen and what actually happened. 31 | value: "Describe what happened" 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: logs 36 | attributes: 37 | label: Relevant Log Output 38 | description: Please copy and paste any relevant log output, they will be automatically formatted into code. 39 | render: shell 40 | - type: textarea 41 | id: reproducibility 42 | attributes: 43 | label: Reproducibility 44 | description: Please provide a short self-contained main.go that reproduces the issue, along with the go.mod and go.sum if necessary. 45 | 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: General Question 4 | url: https://github.com/Pro7ech/lattigo/discussions 5 | about: Ask a question that is not necessarily related to the use or implementation of the library. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature-Request 3 | about: Propose or request a new feature 4 | title: 'Feature Request:' 5 | labels: new feature 6 | assignees: '' 7 | --- 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Library Question 3 | about: Ask a question related to the use, behavior or implementation of the library. 4 | title: 'Question:' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggestion.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pro7ech/lattigo/cf329e68cfab1d4c3dac5a423598a42aa63b5105/.github/ISSUE_TEMPLATE/suggestion.md -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Report a Vulnerability 2 | To report a vulnerability please contact us directly using the following email: [jeanphilippe.bossuat@gmail.com](mailto:jeanphilippe.bossuat@gmail.com). 3 | 4 | # Security of Approximate Homomorphic Encryption 5 | Homomorphic encryption schemes are by definition malleable, and are therefore not secure against chosen ciphertext attacks (CCA security). They can be though secure against chosen plaintext attacks (CPA security). 6 | 7 | Classified as an _approximate decryption_ scheme, the CKKS scheme is secure as long as the plaintext result of a decryption is only revealed to entities with knowledge of the secret-key. This is because, given a ciphertext $(-as + m + e, a)$, the decryption outputs a plaintext $m+e$. [Li and Micciancio](https://eprint.iacr.org/2020/1533) show that using this plaintext, it is possible to recover the secret-key with $((-as + m + e) - (m + e)) \cdot a^{-1} = asa^{-1} = s$ (the probability of $a$ being invertible is overwhelming, and if $a$ is not invertible, only a few more samples are required). 8 | 9 | This attack demonstrates that, when using an approximate homomorphic encryption scheme, the usual CPA security may not sufficient depending on the application setting. Many applications do not require to share the result with external parties and are not affected by this attack, but the ones that do must take the appropriate steps to ensure that no key-dependent information is leaked. A homomorphic encryption scheme that provides such functionality and that can be secure when releasing decrypted plaintext to external parties is defined to be CPAD secure. The corresponding indistinguishability notion (IND-CPAD) is defined as "indistinguishability under chosen plaintext attacks with decryption oracles." 10 | 11 | # CPAD Security for Approximate Homomorphic Encryption 12 | The library implements tools to mitigate _Li and Micciancio_'s attack. In particular, the decoding step of CKKS (and its real-number variant R-CKKS) allows the user to specify the desired fixed-point bit-precision. 13 | 14 | Let $\epsilon$ be the scheme error after the decoding step. We compute the bit precision of the output as $\log_{2}(1/\epsilon)$. 15 | 16 | If at any point of an application, decrypted values have to be shared with external parties, then the user must ensure that each shared plaintext is first _sanitized_ before being shared. To do so, the user must use the $\textsf{DecodePublic}$ method instead of the usual $\textsf{Decode}$. $\textsf{DecodePublic}$ takes as additional input the desired $\log_{2}(1/\epsilon)$-bit precision and rounds the value by evaluating $y = \lfloor x / \epsilon \rceil \cdot \epsilon$. 17 | 18 | Estimating $\text{Pr}[\epsilon < x] \leq 2^{-s}$ of the circuit must be done carefully and we suggest the following process to do so: 19 | 1. Given a security parameter $\lambda$ and a circuit $C$ that takes as inputs length-$n$ vectors $\omega$ following a distribution $\chi$, select the appropriate parameters enabling the homomorphic evaluation of $C(\omega)$, denoted by $H(C(\omega))$, which includes the encoding, encryption, evaluation, decryption and decoding. 20 | 2. Sample input vectors $\omega$ from the distribution $\chi$ and record $\epsilon = C(\omega) - H(C(\omega))$ for each slots. The user should make sure that the underlying circuit computed by $H(C(\cdot))$ is identical to $C(\cdot)$; i.e., if the homomorphic implementation $H(C(\cdot))$ uses polynomial approximations, then $C(\cdot)$ should use them too, instead of using the original exact function. Repeat until enough data points are collected to construct a CDF of $\textsf{Pr}[\epsilon > x]$. 21 | 3. Use the CDF to select the value $\text{E}[\epsilon]$ such that any given slot will fail with probability $2^{-\varepsilon}$ (where $\varepsilon$ is a user-defined security parameter) to reach $\log_{2}(1/\epsilon)$ bits of precision. 22 | 4. Use the encoder method $\textsf{DecodePublic}$ with the parameter $\log_{2}(1/\epsilon)$ to decode plaintexts that will be published. 23 | 24 | Note that, for composability with differential privacy, the variance of the error introduced by the rounding is $\text{Var}[x - \lfloor x \cdot \epsilon \rceil / \epsilon] = \tfrac{\epsilon^2}{12}$ and therefore $\text{Var}[x - \lfloor x/(\sigma\sqrt{12})\rceil\cdot(\sigma\sqrt{12})] = \sigma^2$. 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | on: 3 | push: 4 | jobs: 5 | checks: 6 | name: Run static checks 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | with: 11 | fetch-depth: 1 12 | - name: Setup Go 13 | uses: actions/setup-go@v4 14 | with: 15 | go-version: '1.23.2' 16 | 17 | - uses: actions/cache@v3 18 | with: 19 | path: ~/go/pkg/mod 20 | key: ${{ runner.os }}-go-tools-${{ hashFiles('**/go.sum') }} 21 | restore-keys: | 22 | ${{ runner.os }}-go-tools 23 | 24 | - name: Setup tools 25 | run: make get_tools 26 | 27 | - name: Run Makefile checks 28 | run: make lint 29 | 30 | tests: 31 | name: Run Go ${{ matrix.go }} tests 32 | runs-on: ubuntu-latest 33 | strategy: 34 | matrix: 35 | go: ['1.23.2'] 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | 40 | - name: Setup Go 41 | uses: actions/setup-go@v4 42 | with: 43 | go-version: ${{ matrix.go }} 44 | 45 | - uses: actions/cache@v3 46 | with: 47 | path: | 48 | ~/.cache/go-build 49 | ~/go/pkg/mod 50 | key: ${{ runner.os }}-go-${{ matrix.go }}-build-${{ hashFiles('**/go.sum') }} 51 | restore-keys: | 52 | ${{ runner.os }}-go-${{ matrix.go }}-build- 53 | 54 | - name: Build 55 | run: go build ./... 56 | 57 | - name: Run Makefile tests 58 | run: make test 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Go template 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 17 | .glide/ 18 | 19 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 20 | *.o 21 | *.a 22 | 23 | # Folders 24 | _obj 25 | _test 26 | 27 | # Architecture specific extensions/prefixes 28 | *.[568vq] 29 | [568vq].out 30 | 31 | *.cgo1.go 32 | *.cgo2.c 33 | _cgo_defun.c 34 | _cgo_gotypes.go 35 | _cgo_export.* 36 | 37 | _testmain.go 38 | 39 | *.prof 40 | *.iml 41 | *.cov 42 | *.gob 43 | 44 | .DS_Store 45 | 46 | # Dependency directories (remove the comment below to include it) 47 | # vendor/ 48 | 49 | .idea/ 50 | .vscode/ 51 | 52 | 53 | # generated 54 | /Coding 55 | 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := test 2 | 3 | .PHONY: test_gotest 4 | test_gotest: 5 | go test -timeout=0 ./... 6 | 7 | .PHONY: lint 8 | lint: check_tools 9 | @echo Checking correct formatting of files 10 | 11 | @FMTOUT=$$(go fmt ./...); \ 12 | if [ -z $$FMTOUT ]; then\ 13 | echo "go fmt: OK";\ 14 | else \ 15 | echo "go fmt: problems in files:";\ 16 | echo $$FMTOUT;\ 17 | false;\ 18 | fi 19 | 20 | @if GOVETOUT=$$(go vet ./... 2>&1); then\ 21 | echo "go vet: OK";\ 22 | else \ 23 | echo "go vet: problems in files:";\ 24 | echo "$$GOVETOUT";\ 25 | false;\ 26 | fi 27 | 28 | @GOIMPORTSOUT=$$(goimports -l .); \ 29 | if [ -z "$$GOIMPORTSOUT" ]; then\ 30 | echo "goimports: OK";\ 31 | else \ 32 | echo "goimports: problems in files:";\ 33 | echo "$$GOIMPORTSOUT";\ 34 | false;\ 35 | fi 36 | 37 | @STATICCHECKOUT=$$(staticcheck -go 1.23 -checks all ./...); \ 38 | if [ -z "$$STATICCHECKOUT" ]; then\ 39 | echo "staticcheck: OK";\ 40 | else \ 41 | echo "staticcheck: problems in files:";\ 42 | echo "$$STATICCHECKOUT";\ 43 | false;\ 44 | fi 45 | 46 | @echo Checking all local changes are committed 47 | go mod tidy 48 | out=`git status --porcelain`; echo "$$out"; [ -z "$$out" ] 49 | 50 | .PHONY: test 51 | test: test_gotest 52 | 53 | .PHONY: ci_test 54 | ci_test: lint test_gotest 55 | 56 | EXECUTABLES = goimports staticcheck 57 | .PHONY: get_tools 58 | get_tools: 59 | go install golang.org/x/tools/cmd/goimports@latest 60 | go install honnef.co/go/tools/cmd/staticcheck@latest 61 | 62 | .PHONY: check_tools 63 | check_tools: 64 | @$(foreach exec,$(EXECUTABLES),\ 65 | $(if $(shell which $(exec)),true,$(error "$(exec) not found in PATH, consider running `make get_tools`."))) -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Single Party Examples 2 | 3 | ## Applications 4 | 5 | Application examples are examples showcasing specific capabilities of the library on scaled-down real world scenarios. 6 | 7 | ### Binary 8 | 9 | - `bin_blind_rotations`: an example showcasing the evaluation of the sign function using blind rotations on RLWE ciphertexts. 10 | 11 | ### Integers 12 | 13 | - `int_ride_hailing`: an example on privacy preserving ride hailing. 14 | - `int_vectorized_OLE`: an example on vectorized oblivious linear evaluation using an RLWE trapdoor. 15 | 16 | ### Reals/Complexes 17 | 18 | - `reals_bootstrapping`: a series of examples showcasing the capabilities of the bootstrapping for fixed point arithmetic. 19 | - `basics`: an example showcasing the basic capabilities of the bootstrapping. 20 | - `high_precision`: an example showcasing high-precision bootstrapping. 21 | - `slim`: an example showcasing slim bootstrapping, i.e. re-ordering the steps of the bootstrapping. 22 | 23 | - `reals_scheme_switching`: an example showcasing scheme switching between `hefloat` and `hebin` to complement fixed-point arithmetic with lookup tables. 24 | - `reals_sigmoid_chebyshev`: an example showcasing polynomial evaluation of a Chebyshev approximation of the sigmoid. 25 | - `reals_sigmoid_minimax`: an example showcasing polynomial evaluation of a minimax approximation of the sigmoid. 26 | - `reals_vectorized_polynomial_evaluation`: an example showcasing vectorized polynomial evaluation, i.e. evaluating different polynomials in parallel on specific slots. 27 | 28 | ## Templates 29 | 30 | Templates are files containing the basic instantiation, i.e. parameters, key-generation, encoding, encryption and decryption. 31 | 32 | - `reals`: a template for `hefloat`. 33 | - `int`: a template for `heint`. 34 | 35 | ## Tutorials 36 | 37 | Tutorials are examples showcasing the basic capabilities of the library. 38 | 39 | - `reals`: a tutorial on all the basic capabilities of the package `hefloat`. 40 | 41 | # Multi Party Examples 42 | 43 | - `int_pir`: an example showcasing multi-party private information retrieval. 44 | - `int_psi`: an example showcasing multi-party private set intersection. 45 | - `thresh_eval_key_gen`: an example showcasing multi-party threshold key-generation. 46 | 47 | ## Parameters 48 | 49 | The `params.go` file contains several sets of example parameters for both `heint` and `hefloat`. 50 | These parameter are chosen to represent several degrees of homomorphic capacity for a fixed 128-bit security 51 | (according to the standard estimates at the time of writing). They do not represent a set of default parameters 52 | to be used in real HE applications. Rather, they are meant to facilitate quick tests and experimentation 53 | with the library. -------------------------------------------------------------------------------- /examples/example_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Pro7ech/lattigo/he/hefloat" 7 | "github.com/Pro7ech/lattigo/he/heint" 8 | ) 9 | 10 | func TestExampleParams(t *testing.T) { 11 | for _, pl := range HEIntParams { 12 | p, err := heint.NewParametersFromLiteral(pl) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | p.RingQ() 17 | t.Logf("HEIntParams: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots()) 18 | } 19 | 20 | for _, pl := range HEIntScaleInvariantParams { 21 | p, err := heint.NewParametersFromLiteral(pl) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | p.RingQ() 26 | t.Logf("HEIntScaleInvariantParams: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots()) 27 | } 28 | 29 | for _, pl := range HEFloatComplexParams { 30 | p, err := hefloat.NewParametersFromLiteral(pl) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | p.RingQ() 35 | t.Logf("HEFloatComplex: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots()) 36 | } 37 | 38 | for _, pl := range HEFloatRealParams { 39 | p, err := hefloat.NewParametersFromLiteral(pl) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | p.RingQ() 44 | t.Logf("HEFloatReal: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/examples.go: -------------------------------------------------------------------------------- 1 | // Package examples contains several example Go applications that use lattigo in both the single- and multiparty settings, 2 | // as well as several example parameter sets. See examples/README.md for more information about the examples. 3 | // 4 | // Note that the code in this package, including the example parameter sets, is solely meant to illustrate the use of the library and facilitate quick experiments. 5 | // It should not be depended upon and may at any time be changed or be removed. 6 | package examples 7 | -------------------------------------------------------------------------------- /examples/multi_party/int/pir/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMain(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipped in -short mode") 11 | } 12 | oldArgs := os.Args 13 | defer func() { os.Args = oldArgs }() 14 | os.Args = os.Args[:1] 15 | main() 16 | } 17 | -------------------------------------------------------------------------------- /examples/multi_party/int/psi/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMain(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipped in -short mode") 11 | } 12 | oldArgs := os.Args 13 | defer func() { os.Args = oldArgs }() 14 | os.Args = os.Args[:1] 15 | main() 16 | } 17 | -------------------------------------------------------------------------------- /examples/multi_party/setup/threshold/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestMain(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skipped in -short mode") 8 | } 9 | main() 10 | } 11 | -------------------------------------------------------------------------------- /examples/single_party/hebin/blind_rotations/main.go: -------------------------------------------------------------------------------- 1 | // Package main implements an example of Blind Rotation (a.k.a. Lookup Table) evaluation. 2 | // These packages can be used to implement all the functionalities of the TFHE scheme. 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | 9 | "github.com/Pro7ech/lattigo/he/hebin" 10 | "github.com/Pro7ech/lattigo/ring" 11 | "github.com/Pro7ech/lattigo/rlwe" 12 | ) 13 | 14 | // Function to evaluate 15 | func sign(x float64) float64 { 16 | if x >= 0 { 17 | return 1 18 | } 19 | 20 | return -1 21 | } 22 | 23 | func main() { 24 | // RLWE parameters of the Blind Rotation 25 | // N=1024, Q=0x7fff801 -> ~2^128 ROP-security 26 | paramsBR, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ 27 | LogN: 10, 28 | Q: []uint64{0x7fff801}, 29 | NTTFlag: true, 30 | }) 31 | 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | // RLWE parameters of the samples 37 | // N=512, Q=0x3001 -> ~2^128 ROP-security 38 | paramsLWE, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ 39 | LogN: 9, 40 | Q: []uint64{0x3001}, 41 | NTTFlag: true, 42 | }) 43 | 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | // Set the parameters for the blind rotation keys 49 | evkParams := rlwe.EvaluationKeyParameters{} 50 | evkParams.DigitDecomposition.Type = rlwe.Signed 51 | evkParams.Log2Basis = 7 52 | 53 | // Scale of the RLWE samples 54 | scaleLWE := float64(paramsLWE.Q()[0]) / 4.0 55 | 56 | // Scale of the test poly 57 | scaleBR := float64(paramsBR.Q()[0]) / 4.0 58 | 59 | // Number of values samples stored in the RLWE sample 60 | slots := 32 61 | 62 | // Test poly 63 | testPoly := hebin.InitTestPolynomial(sign, rlwe.NewScale(scaleBR), paramsBR.RingQ(), -1, 1) 64 | 65 | // Index map of which test poly to evaluate on which slot 66 | testPolyMap := make(map[int]*ring.RNSPoly) 67 | for i := 0; i < slots; i++ { 68 | testPolyMap[i] = &testPoly 69 | } 70 | 71 | // RLWE secret for the samples 72 | skLWE := rlwe.NewKeyGenerator(paramsLWE).GenSecretKeyNew() 73 | 74 | // RLWE encryptor for the samples 75 | encryptorLWE := rlwe.NewEncryptor(paramsLWE, skLWE) 76 | 77 | // Values to encrypt in the RLWE sample 78 | values := make([]float64, slots) 79 | for i := 0; i < slots; i++ { 80 | values[i] = (-1.0 + float64(2*i)/float64(slots)) 81 | } 82 | 83 | // Encode multiples values in a single RLWE 84 | ptLWE := rlwe.NewPlaintext(paramsLWE, paramsLWE.MaxLevel(), -1) 85 | for i := range values { 86 | if values[i] < 0 { 87 | ptLWE.Q.At(0)[i] = paramsLWE.Q()[0] - uint64(-values[i]*scaleLWE) 88 | } else { 89 | ptLWE.Q.At(0)[i] = uint64(values[i] * scaleLWE) 90 | } 91 | } 92 | 93 | paramsLWE.RingQ().NTT(ptLWE.Q, ptLWE.Q) 94 | 95 | // Encrypt the multiples values in a single RLWE 96 | ctLWE := rlwe.NewCiphertext(paramsLWE, 1, paramsLWE.MaxLevel(), -1) 97 | if err = encryptorLWE.Encrypt(ptLWE, ctLWE); err != nil { 98 | panic(err) 99 | } 100 | 101 | // Evaluator for the Blind Rotations 102 | eval := hebin.NewEvaluator(paramsBR, paramsLWE) 103 | 104 | // Secret of the RGSW ciphertexts encrypting the bits of skLWE 105 | skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew() 106 | 107 | // Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR 108 | blindeRotateKey := hebin.GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams) 109 | 110 | // Evaluation of BlindRotate(ctLWE) = testPoly(X) * X^{dec{ctLWE}} 111 | // Returns one RLWE sample per slot in ctLWE 112 | 113 | now := time.Now() 114 | ctsBR, err := eval.Evaluate(ctLWE, testPolyMap, blindeRotateKey) 115 | if err != nil { 116 | panic(err) 117 | } 118 | fmt.Printf("Done: %s (avg/BlindRotation %3.1f [ms])\n", time.Since(now), float64(time.Since(now).Milliseconds())/float64(slots)) 119 | 120 | // Decrypts, decodes and compares 121 | q := paramsBR.Q()[0] 122 | qHalf := q >> 1 123 | decryptorBR := rlwe.NewDecryptor(paramsBR, skBR) 124 | ptBR := rlwe.NewPlaintext(paramsBR, paramsBR.MaxLevel(), -1) 125 | for i := 0; i < slots; i++ { 126 | 127 | decryptorBR.Decrypt(ctsBR[i], ptBR) 128 | 129 | if ptBR.IsNTT { 130 | paramsBR.RingQ().INTT(ptBR.Q, ptBR.Q) 131 | } 132 | 133 | c := ptBR.Q.At(0)[0] 134 | 135 | var a float64 136 | if c >= qHalf { 137 | a = -float64(q-c) / scaleBR 138 | } else { 139 | a = float64(c) / scaleBR 140 | } 141 | 142 | fmt.Printf("%7.4f - %7.4f\n", a, values[i]) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /examples/single_party/hebin/blind_rotations/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestMain(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skipped in -short mode") 8 | } 9 | main() 10 | } 11 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/advanced/bootstrapping/basics/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMain(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipped in -short mode") 11 | } 12 | oldArgs := os.Args 13 | defer func() { os.Args = oldArgs }() 14 | os.Args = append(os.Args, "-short") 15 | main() 16 | } 17 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/advanced/bootstrapping/high_precision/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMain(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipped in -short mode") 11 | } 12 | oldArgs := os.Args 13 | defer func() { os.Args = oldArgs }() 14 | os.Args = append(os.Args, "-short") 15 | main() 16 | } 17 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/advanced/bootstrapping/slim/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMain(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipped in -short mode") 11 | } 12 | oldArgs := os.Args 13 | defer func() { os.Args = oldArgs }() 14 | os.Args = append(os.Args, "-short") 15 | main() 16 | } 17 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/advanced/polynomial_evaluation/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestMain(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skipped in -short mode") 8 | } 9 | main() 10 | } 11 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/advanced/scheme_switching/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMain(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipped in -short mode") 11 | } 12 | oldArgs := os.Args 13 | defer func() { os.Args = oldArgs }() 14 | os.Args = append(os.Args, "-short") 15 | main() 16 | } 17 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/basics/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestMain(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skipped in -short mode") 8 | } 9 | main() 10 | } 11 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/template/main.go: -------------------------------------------------------------------------------- 1 | // Package main is a template encrypted arithmetic with floating point values, with a set of example parameters, key generation, encoding, encryption, decryption and decoding. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | 8 | "github.com/Pro7ech/lattigo/he/hefloat" 9 | "github.com/Pro7ech/lattigo/rlwe" 10 | ) 11 | 12 | func main() { 13 | var err error 14 | var params hefloat.Parameters 15 | 16 | // 128-bit secure parameters enabling depth-7 circuits. 17 | // LogN:14, LogQP: 431. 18 | if params, err = hefloat.NewParametersFromLiteral( 19 | hefloat.ParametersLiteral{ 20 | LogN: 14, // log2(ring degree) 21 | LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus) 22 | LogP: []int{61}, // log2(primes P) (auxiliary modulus) 23 | LogDefaultScale: 45, // log2(scale) 24 | }); err != nil { 25 | panic(err) 26 | } 27 | 28 | // Key Generator 29 | kgen := rlwe.NewKeyGenerator(params) 30 | 31 | // Secret Key 32 | sk := kgen.GenSecretKeyNew() 33 | 34 | // Encoder 35 | ecd := hefloat.NewEncoder(params) 36 | 37 | // Encryptor 38 | enc := rlwe.NewEncryptor(params, sk) 39 | 40 | // Decryptor 41 | dec := rlwe.NewDecryptor(params, sk) 42 | 43 | // Vector of plaintext values 44 | values := make([]float64, params.MaxSlots()) 45 | 46 | // Source for sampling random plaintext values (not cryptographically secure) 47 | /* #nosec G404 */ 48 | r := rand.New(rand.NewSource(0)) 49 | 50 | // Populates the vector of plaintext values 51 | for i := range values { 52 | values[i] = 2*r.Float64() - 1 // uniform in [-1, 1] 53 | } 54 | 55 | // Allocates a plaintext at the max level. 56 | // Default rlwe.MetaData: 57 | // - IsBatched = true (slots encoding) 58 | // - Scale = params.DefaultScale() 59 | pt := hefloat.NewPlaintext(params, params.MaxLevel()) 60 | 61 | // Encodes the vector of plaintext values 62 | if err = ecd.Encode(values, pt); err != nil { 63 | panic(err) 64 | } 65 | 66 | // Encrypts the vector of plaintext values 67 | ct := hefloat.NewCiphertext(params, 1, pt.Level()) 68 | if err = enc.Encrypt(pt, ct); err != nil { 69 | panic(err) 70 | } 71 | 72 | // Allocates a vector for the reference values 73 | want := make([]float64, params.MaxSlots()) 74 | copy(want, values) 75 | 76 | PrintPrecisionStats(params, ct, want, ecd, dec) 77 | } 78 | 79 | // PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext. 80 | func PrintPrecisionStats(params hefloat.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *hefloat.Encoder, dec *rlwe.Decryptor) { 81 | 82 | var err error 83 | 84 | // Decrypts the vector of plaintext values 85 | pt := dec.DecryptNew(ct) 86 | 87 | // Decodes the plaintext 88 | have := make([]float64, params.MaxSlots()) 89 | if err = ecd.Decode(pt, have); err != nil { 90 | panic(err) 91 | } 92 | 93 | // Pretty prints some values 94 | fmt.Printf("Have: ") 95 | for i := 0; i < 4; i++ { 96 | fmt.Printf("%20.15f ", have[i]) 97 | } 98 | fmt.Printf("...\n") 99 | 100 | fmt.Printf("Want: ") 101 | for i := 0; i < 4; i++ { 102 | fmt.Printf("%20.15f ", want[i]) 103 | } 104 | fmt.Printf("...\n") 105 | 106 | // Pretty prints the precision stats 107 | fmt.Println(hefloat.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String()) 108 | } 109 | -------------------------------------------------------------------------------- /examples/single_party/hefloat/template/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestMain(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skipped in -short mode") 8 | } 9 | main() 10 | } 11 | -------------------------------------------------------------------------------- /examples/single_party/heint/template/main.go: -------------------------------------------------------------------------------- 1 | // Package main is a template encrypted modular arithmetic integers, with a set of example parameters, key generation, encoding, encryption, decryption and decoding. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | "slices" 8 | 9 | "github.com/Pro7ech/lattigo/he/heint" 10 | "github.com/Pro7ech/lattigo/rlwe" 11 | ) 12 | 13 | func main() { 14 | var err error 15 | var params heint.Parameters 16 | 17 | // 128-bit secure parameters enabling depth-7 circuits. 18 | // LogN:14, LogQP: 431. 19 | if params, err = heint.NewParametersFromLiteral( 20 | heint.ParametersLiteral{ 21 | LogN: 14, // log2(ring degree) 22 | LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus) 23 | LogP: []int{61}, // log2(primes P) (auxiliary modulus) 24 | T: 0x10001, 25 | R: 1, 26 | }); err != nil { 27 | panic(err) 28 | } 29 | 30 | // Key Generator 31 | kgen := rlwe.NewKeyGenerator(params) 32 | 33 | // Secret Key 34 | sk := kgen.GenSecretKeyNew() 35 | 36 | // Encoder 37 | ecd := heint.NewEncoder(params) 38 | 39 | // Encryptor 40 | enc := rlwe.NewEncryptor(params, sk) 41 | 42 | // Decryptor 43 | dec := rlwe.NewDecryptor(params, sk) 44 | 45 | // Vector of plaintext values 46 | values := make([]uint64, params.MaxSlots()) 47 | 48 | // Source for sampling random plaintext values (not cryptographically secure) 49 | /* #nosec G404 */ 50 | r := rand.New(rand.NewSource(0)) 51 | 52 | // Populates the vector of plaintext values 53 | T := params.PlaintextModulus() 54 | for i := range values { 55 | values[i] = r.Uint64() % T 56 | } 57 | 58 | // Allocates a plaintext at the max level. 59 | // Default rlwe.MetaData: 60 | // - IsBatched = true (slots encoding) 61 | // - Scale = params.DefaultScale() 62 | pt := heint.NewPlaintext(params, params.MaxLevel()) 63 | 64 | // Encodes the vector of plaintext values 65 | if err = ecd.Encode(values, pt); err != nil { 66 | panic(err) 67 | } 68 | 69 | // Encrypts the vector of plaintext values 70 | ct := heint.NewCiphertext(params, 1, pt.Level()) 71 | if err = enc.Encrypt(pt, ct); err != nil { 72 | panic(err) 73 | } 74 | 75 | // Allocates a vector for the reference values 76 | want := make([]uint64, params.MaxSlots()) 77 | copy(want, values) 78 | 79 | PrintPrecisionStats(params, ct, want, ecd, dec) 80 | } 81 | 82 | // PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext. 83 | func PrintPrecisionStats(params heint.Parameters, ct *rlwe.Ciphertext, want []uint64, ecd *heint.Encoder, dec *rlwe.Decryptor) { 84 | 85 | var err error 86 | 87 | // Decrypts the vector of plaintext values 88 | pt := dec.DecryptNew(ct) 89 | 90 | // Decodes the plaintext 91 | have := make([]uint64, params.MaxSlots()) 92 | if err = ecd.Decode(pt, have); err != nil { 93 | panic(err) 94 | } 95 | 96 | // Pretty prints some values 97 | fmt.Printf("Have: ") 98 | for i := 0; i < 4; i++ { 99 | fmt.Printf("%d ", have[i]) 100 | } 101 | fmt.Printf("...\n") 102 | 103 | fmt.Printf("Want: ") 104 | for i := 0; i < 4; i++ { 105 | fmt.Printf("%d ", want[i]) 106 | } 107 | fmt.Printf("...\n") 108 | 109 | if !slices.Equal(want, have) { 110 | panic("wrong result: bad decryption or encrypted/plaintext circuits do not match") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples/single_party/heint/template/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestMain(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skipped in -short mode") 8 | } 9 | main() 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Pro7ech/lattigo 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/ALTree/bigfloat v0.2.0 7 | github.com/google/go-cmp v0.6.0 8 | github.com/stretchr/testify v1.9.0 9 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM= 2 | github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 6 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 10 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 11 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= 12 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /he/bootstrapper.go: -------------------------------------------------------------------------------- 1 | package he 2 | 3 | // Bootstrapper is a scheme-independent generic interface to handle bootstrapping. 4 | type Bootstrapper[CiphertextType any] interface { 5 | 6 | // Bootstrap defines a method that takes a single Ciphertext as input and applies 7 | // an in place scheme-specific bootstrapping. The result is also returned. 8 | // An error should notably be returned if ct.Level() < Bootstrapper.MinimumInputLevel(). 9 | Bootstrap(ct *CiphertextType) (*CiphertextType, error) 10 | 11 | // BootstrapMany defines a method that takes a slice of Ciphertexts as input and applies an 12 | // in place scheme-specific bootstrapping to each Ciphertext. The result is also returned. 13 | // An error should notably be returned if cts[i].Level() < Bootstrapper.MinimumInputLevel(). 14 | BootstrapMany(cts []CiphertextType) ([]CiphertextType, error) 15 | 16 | // Depth is the number of levels consumed by the bootstrapping circuit. 17 | // This value is equivalent to params.MaxLevel() - OutputLevel(). 18 | Depth() int 19 | 20 | // MinimumInputLevel defines the minimum level that the ciphertext 21 | // must be at when given to the bootstrapper. 22 | // For the centralized bootstrapping this value is usually zero. 23 | // For the collective bootstrapping it is given by the user-defined 24 | // security parameters 25 | MinimumInputLevel() int 26 | 27 | // OutputLevel defines the level that the ciphertext will be at 28 | // after the bootstrapping. 29 | // For the centralized bootstrapping this value is the maximum 30 | // level minus the depth of the bootstrapping circuit. 31 | // For the collective bootstrapping this value is usually the 32 | // maximum level. 33 | OutputLevel() int 34 | } 35 | -------------------------------------------------------------------------------- /he/he.go: -------------------------------------------------------------------------------- 1 | // Package he implements scheme agnostic functionalities for RLWE-based Homomorphic Encryption schemes implemented in lattigo. 2 | package he 3 | 4 | import ( 5 | "github.com/Pro7ech/lattigo/rlwe" 6 | ) 7 | 8 | // Encoder defines a set of common and scheme agnostic method provided by an Encoder struct. 9 | type Encoder interface { 10 | Embed(values interface{}, metaData *rlwe.MetaData, output interface{}) (err error) 11 | } 12 | 13 | // Evaluator defines a set of common and scheme agnostic method provided by an Evaluator struct. 14 | type Evaluator interface { 15 | rlwe.ParameterProvider 16 | Add(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) 17 | AddNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) 18 | Sub(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) 19 | SubNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) 20 | Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) 21 | MulNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) 22 | MulRelin(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) 23 | MulRelinNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) 24 | MulThenAdd(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) 25 | Relinearize(op0, op1 *rlwe.Ciphertext) (err error) 26 | Rescale(op0, op1 *rlwe.Ciphertext) (err error) 27 | GetEvaluatorBuffer() *rlwe.EvaluatorBuffers // TODO extract 28 | DropLevel(op0 *rlwe.Ciphertext, Level int) 29 | LevelsConsumedPerRescaling() int 30 | } 31 | -------------------------------------------------------------------------------- /he/hebin/README.md: -------------------------------------------------------------------------------- 1 | ## References 2 | 3 | 1. Efficient FHEW Bootstrapping with Small Evaluation Keys, and Applications to Threshold Homomorphic Encryption () -------------------------------------------------------------------------------- /he/hebin/blindrotation.go: -------------------------------------------------------------------------------- 1 | // Package hebin implements blind rotations evaluation for RLWE schemes. 2 | package hebin 3 | 4 | import ( 5 | "github.com/Pro7ech/lattigo/ring" 6 | "github.com/Pro7ech/lattigo/rlwe" 7 | ) 8 | 9 | // InitTestPolynomial takes a function g, and creates a test polynomial polynomial for the function in the interval [a, b]. 10 | // Inputs to the blind rotation evaluation are assumed to have been normalized with the change of basis (2*x - a - b)/(b-a). 11 | // Interval [a, b] should take into account the "drift" of the value x, caused by the change of modulus from Q to 2N. 12 | func InitTestPolynomial(g func(x float64) (y float64), scale rlwe.Scale, rQ ring.RNSRing, a, b float64) (F ring.RNSPoly) { 13 | F = rQ.NewRNSPoly() 14 | Q := rQ.ModuliChain()[:rQ.Level()+1] 15 | 16 | sf64 := scale.Float64() 17 | 18 | N := rQ.N() 19 | 20 | // Discretization interval 21 | interval := 2.0 / float64(N) 22 | 23 | for j, qi := range Q { 24 | 25 | // Interval [-1, 0] of g(x) 26 | for i := 0; i < (N>>1)+1; i++ { 27 | F.At(j)[i] = scaleUp(g(normalizeInv(-interval*float64(i), a, b)), sf64, qi) 28 | } 29 | 30 | // Interval ]0, 1[ of g(x) 31 | for i := (N >> 1) + 1; i < N; i++ { 32 | F.At(j)[i] = scaleUp(-g(normalizeInv(interval*float64(N-i), a, b)), sf64, qi) 33 | } 34 | } 35 | 36 | rQ.NTT(F, F) 37 | 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /he/hebin/blindrotation_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package hebin 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Pro7ech/lattigo/rlwe" 7 | "github.com/Pro7ech/lattigo/utils/sampling" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func BenchmarkHEBin(b *testing.B) { 13 | 14 | b.Run("BlindRotateCore/LogN=(9,10)/LogQ=(13.6,26.99)/Gadget=2^7", func(b *testing.B) { 15 | 16 | // RLWE parameters of the BlindRotation 17 | // N=1024, Q=0x7fff801 -> 131 bit secure 18 | paramsBR, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ 19 | LogN: 10, 20 | Q: []uint64{0x7fff801}, 21 | NTTFlag: NTTFlag, 22 | }) 23 | 24 | require.NoError(b, err) 25 | 26 | // RLWE parameters of the samples 27 | // N=512, Q=0x3001 -> 135 bit secure 28 | paramsLWE, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ 29 | LogN: 9, 30 | Q: []uint64{0x3001}, 31 | NTTFlag: NTTFlag, 32 | }) 33 | 34 | require.NoError(b, err) 35 | 36 | evkParams := rlwe.EvaluationKeyParameters{} 37 | evkParams.DigitDecomposition.Type = rlwe.Unsigned 38 | evkParams.Log2Basis = 7 39 | 40 | // RLWE secret for the samples 41 | skLWE := rlwe.NewKeyGenerator(paramsLWE).GenSecretKeyNew() 42 | 43 | // Secret of the RGSW ciphertexts encrypting the bits of skLWE 44 | skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew() 45 | 46 | // Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR 47 | BRK := GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams) 48 | 49 | // Random LWE mask mod 2N with odd coefficients 50 | a := make([]uint64, paramsLWE.N()) 51 | mask := uint64(2*paramsLWE.N() - 1) 52 | r := sampling.NewSource([32]byte{}) 53 | for i := range a { 54 | ai := r.Uint64() & mask 55 | if ai&1 == 0 && ai != 0 { 56 | ai ^= 1 57 | } 58 | a[i] = ai 59 | } 60 | 61 | acc := rlwe.NewCiphertext(paramsBR, 1, paramsBR.MaxLevel(), -1) 62 | 63 | // Evaluator for the Blind Rotation evaluation 64 | eval := NewEvaluator(paramsBR, paramsLWE) 65 | 66 | b.ResetTimer() 67 | 68 | for i := 0; i < b.N; i++ { 69 | if err := eval.BlindRotateCore(a, acc, BRK); err != nil { 70 | panic(err) 71 | } 72 | } 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /he/hebin/keys.go: -------------------------------------------------------------------------------- 1 | package hebin 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Pro7ech/lattigo/rgsw" 7 | "github.com/Pro7ech/lattigo/ring" 8 | "github.com/Pro7ech/lattigo/rlwe" 9 | "github.com/Pro7ech/lattigo/utils" 10 | ) 11 | 12 | const ( 13 | // Parameter w of Algorithm 3 in https://eprint.iacr.org/2022/198 14 | windowSize = 10 15 | ) 16 | 17 | // BlindRotationEvaluationKeySet is a interface implementing methods 18 | // to load the blind rotation keys (RGSW) and automorphism keys 19 | // (via the rlwe.EvaluationKeySet interface). 20 | // Implementation of this interface must be safe for concurrent use. 21 | type BlindRotationEvaluationKeySet interface { 22 | 23 | // GetBlindRotationKey should return RGSW(X^{s[i]}) 24 | GetBlindRotationKey(i int) (brk *rgsw.Ciphertext, err error) 25 | 26 | // GetEvaluationKeySet should return an rlwe.EvaluationKeySet 27 | // providing access to all the required automorphism keys. 28 | GetEvaluationKeySet() (evk rlwe.EvaluationKeySet, err error) 29 | } 30 | 31 | // MemBlindRotationEvaluationKeySet is a basic in-memory implementation of the BlindRotationEvaluationKeySet interface. 32 | type MemBlindRotationEvaluationKeySet struct { 33 | BlindRotationKeys []*rgsw.Ciphertext 34 | AutomorphismKeys []*rlwe.GaloisKey 35 | } 36 | 37 | func (evk MemBlindRotationEvaluationKeySet) GetBlindRotationKey(i int) (*rgsw.Ciphertext, error) { 38 | return evk.BlindRotationKeys[i], nil 39 | } 40 | 41 | func (evk MemBlindRotationEvaluationKeySet) GetEvaluationKeySet() (rlwe.EvaluationKeySet, error) { 42 | return rlwe.NewMemEvaluationKeySet(nil, evk.AutomorphismKeys...), nil 43 | } 44 | 45 | // GenEvaluationKeyNew generates a new Blind Rotation evaluation key 46 | func GenEvaluationKeyNew(paramsRLWE rlwe.ParameterProvider, skRLWE *rlwe.SecretKey, paramsLWE rlwe.ParameterProvider, skLWE *rlwe.SecretKey, evkParams ...rlwe.EvaluationKeyParameters) (key MemBlindRotationEvaluationKeySet) { 47 | 48 | pRLWE := *paramsRLWE.GetRLWEParameters() 49 | pLWE := *paramsLWE.GetRLWEParameters() 50 | 51 | skLWECopy := skLWE.Clone() 52 | pLWE.RingQ().AtLevel(0).INTT(skLWECopy.Q, skLWECopy.Q) 53 | pLWE.RingQ().AtLevel(0).IMForm(skLWECopy.Q, skLWECopy.Q) 54 | sk := make([]big.Int, pLWE.N()) 55 | pLWE.RingQ().AtLevel(0).PolyToBigintCentered(skLWECopy.Q, 1, sk) 56 | 57 | encryptor := rgsw.NewEncryptor(pRLWE, skRLWE) 58 | 59 | levelQ, levelP, dd := rlwe.ResolveEvaluationKeyParameters(pRLWE, evkParams) 60 | 61 | skiRGSW := make([]*rgsw.Ciphertext, pLWE.N()) 62 | 63 | ptXi := make(map[int]*rlwe.Plaintext) 64 | 65 | for i, si := range sk { 66 | 67 | siInt := int(si.Int64()) 68 | 69 | if _, ok := ptXi[siInt]; !ok { 70 | 71 | pt := &rlwe.Plaintext{} 72 | pt.Point = &ring.Point{} 73 | pt.MetaData = &rlwe.MetaData{} 74 | pt.IsNTT = true 75 | pt.Q = pRLWE.RingQ().NewMonomialXi(siInt) 76 | pRLWE.RingQ().NTT(pt.Q, pt.Q) 77 | 78 | ptXi[siInt] = pt 79 | } 80 | 81 | skiRGSW[i] = rgsw.NewCiphertext(pRLWE, levelQ, levelP, dd) 82 | 83 | // Sanity check, this error should never happen unless this algorithm 84 | // has been improperly modified to provides invalid inputs. 85 | if err := encryptor.Encrypt(ptXi[siInt], skiRGSW[i]); err != nil { 86 | panic(err) 87 | } 88 | } 89 | 90 | kgen := rlwe.NewKeyGenerator(pRLWE) 91 | 92 | galEls := make([]uint64, windowSize) 93 | for i := 0; i < windowSize; i++ { 94 | galEls[i] = pRLWE.GaloisElement(i + 1) 95 | } 96 | 97 | galEls = append(galEls, pRLWE.RingQ().NthRoot()-ring.GaloisGen) 98 | 99 | gks := kgen.GenGaloisKeysNew(galEls, skRLWE, rlwe.EvaluationKeyParameters{ 100 | LevelQ: utils.Pointy(levelQ), 101 | LevelP: utils.Pointy(levelP), 102 | DigitDecomposition: dd, 103 | }) 104 | 105 | return MemBlindRotationEvaluationKeySet{BlindRotationKeys: skiRGSW, AutomorphismKeys: gks} 106 | } 107 | -------------------------------------------------------------------------------- /he/hebin/utils.go: -------------------------------------------------------------------------------- 1 | package hebin 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Pro7ech/lattigo/utils" 7 | "github.com/Pro7ech/lattigo/utils/bignum" 8 | ) 9 | 10 | // MulBySmallMonomialMod2N multiplies pol by x^n, with 0 <= n < N 11 | func mulBySmallMonomialMod2N(mask uint64, coeffs []uint64, n int) { 12 | if n != 0 { 13 | utils.RotateSliceAllocFree(coeffs, len(coeffs)-n, coeffs) 14 | for j := 0; j < n; j++ { 15 | coeffs[j] = -coeffs[j] & mask 16 | } 17 | } 18 | } 19 | 20 | func normalizeInv(x, a, b float64) (y float64) { 21 | return (x*(b-a) + b + a) / 2.0 22 | } 23 | 24 | func scaleUp(value float64, scale float64, Q uint64) (res uint64) { 25 | 26 | var isNegative bool 27 | var xFlo *big.Float 28 | var xInt *big.Int 29 | 30 | isNegative = false 31 | if value < 0 { 32 | isNegative = true 33 | xFlo = big.NewFloat(-scale * value) 34 | } else { 35 | xFlo = big.NewFloat(scale * value) 36 | } 37 | 38 | xFlo.Add(xFlo, big.NewFloat(0.5)) 39 | 40 | xInt = new(big.Int) 41 | xFlo.Int(xInt) 42 | xInt.Mod(xInt, bignum.NewInt(Q)) 43 | 44 | res = xInt.Uint64() 45 | 46 | if isNegative { 47 | res = Q - res 48 | } 49 | 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /he/hefloat/bootstrapping/README.md: -------------------------------------------------------------------------------- 1 | ## References 2 | 3 | 1. Bootstrapping for Approximate Homomorphic Encryption () 4 | 2. Improved Bootstrapping for Approximate Homomorphic Encryption () 5 | 3. Better Bootstrapping for Approximate Homomorphic Encryption () 6 | 4. Faster Homomorphic Discrete Fourier Transforms and Improved FHE Bootstrapping () 7 | 5. Efficient Bootstrapping for Approximate Homomorphic Encryption with Non-Sparse Keys () 8 | 6. High-Precision Bootstrapping for Approximate Homomorphic Encryption by Error Variance Minimization () 9 | 7. High-Precision Bootstrapping of RNS-CKKS Homomorphic Encryption Using Optimal Minimax Polynomial Approximation and Inverse Sine Function () 10 | 8. Bootstrapping for Approximate Homomorphic Encryption with Negligible Failure-Probability by Using Sparse-Secret Encapsulation () 11 | 9. META-BTS: Bootstrapping Precision Beyond the Limit () -------------------------------------------------------------------------------- /he/hefloat/bootstrapping/bootstrapper.go: -------------------------------------------------------------------------------- 1 | package bootstrapping 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Pro7ech/lattigo/he" 7 | "github.com/Pro7ech/lattigo/ring" 8 | "github.com/Pro7ech/lattigo/rlwe" 9 | ) 10 | 11 | // Ensures that the Evaluator complies to the he.Bootstrapper interface 12 | var _ he.Bootstrapper[rlwe.Ciphertext] = (*Evaluator)(nil) 13 | 14 | // Bootstrap bootstraps a single ciphertext and returns the bootstrapped ciphertext. 15 | func (eval Evaluator) Bootstrap(ct *rlwe.Ciphertext) (*rlwe.Ciphertext, error) { 16 | cts := []rlwe.Ciphertext{*ct} 17 | cts, err := eval.BootstrapMany(cts) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return &cts[0], nil 22 | } 23 | 24 | // BootstrapMany bootstraps a list of ciphertext and returns the list of bootstrapped ciphertexts. 25 | func (eval Evaluator) BootstrapMany(cts []rlwe.Ciphertext) ([]rlwe.Ciphertext, error) { 26 | 27 | var err error 28 | 29 | switch eval.ResidualParameters.RingType() { 30 | case ring.ConjugateInvariant: 31 | 32 | for i := 0; i < len(cts); i = i + 2 { 33 | 34 | even, odd := i, i+1 35 | 36 | ct0 := &cts[even] 37 | 38 | var ct1 *rlwe.Ciphertext 39 | if odd < len(cts) { 40 | ct1 = &cts[odd] 41 | } 42 | 43 | if ct0, ct1, err = eval.EvaluateConjugateInvariant(ct0, ct1); err != nil { 44 | return nil, fmt.Errorf("cannot BootstrapMany: %w", err) 45 | } 46 | 47 | cts[even] = *ct0 48 | 49 | if ct1 != nil { 50 | cts[odd] = *ct1 51 | } 52 | } 53 | 54 | default: 55 | 56 | LogSlots := cts[0].LogSlots() 57 | nbCiphertexts := len(cts) 58 | 59 | if cts, err = eval.PackAndSwitchN1ToN2(cts); err != nil { 60 | return nil, fmt.Errorf("cannot BootstrapMany: %w", err) 61 | } 62 | 63 | for i := range cts { 64 | var ct *rlwe.Ciphertext 65 | if ct, err = eval.Evaluate(&cts[i]); err != nil { 66 | return nil, fmt.Errorf("cannot BootstrapMany: %w", err) 67 | } 68 | cts[i] = *ct 69 | } 70 | 71 | if cts, err = eval.UnpackAndSwitchN2Tn1(cts, LogSlots, nbCiphertexts); err != nil { 72 | return nil, fmt.Errorf("cannot BootstrapMany: %w", err) 73 | } 74 | } 75 | 76 | return cts, err 77 | } 78 | 79 | // Depth returns the multiplicative depth (number of levels consumed) of the bootstrapping circuit. 80 | func (eval Evaluator) Depth() int { 81 | return eval.BootstrappingParameters.MaxLevel() - eval.ResidualParameters.MaxLevel() 82 | } 83 | 84 | // OutputLevel returns the output level after the evaluation of the bootstrapping circuit. 85 | func (eval Evaluator) OutputLevel() int { 86 | return eval.ResidualParameters.MaxLevel() 87 | } 88 | 89 | // MinimumInputLevel returns the minimum level at which a ciphertext must be to be bootstrapped. 90 | func (eval Evaluator) MinimumInputLevel() int { 91 | return eval.BootstrappingParameters.LevelsConsumedPerRescaling() - 1 92 | } 93 | -------------------------------------------------------------------------------- /he/hefloat/bootstrapping/bootstrapping_bench_test.go: -------------------------------------------------------------------------------- 1 | package bootstrapping 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/Pro7ech/lattigo/he/hefloat" 9 | "github.com/Pro7ech/lattigo/rlwe" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func ParamsToString(params hefloat.Parameters, LogSlots int, opname string) string { 14 | return fmt.Sprintf("%slogN=%d/LogSlots=%d/logQP=%f/levels=%d/a=%d/b=%d", 15 | opname, 16 | params.LogN(), 17 | LogSlots, 18 | params.LogQP(), 19 | params.MaxLevel()+1, 20 | params.PCount(), 21 | len(params.DecompositionMatrixDimensions(params.MaxLevelQ(), params.MaxLevelP(), rlwe.DigitDecomposition{}))) 22 | } 23 | 24 | func BenchmarkBootstrap(b *testing.B) { 25 | 26 | paramsLit := hefloat.ParametersLiteral{ 27 | LogN: 16, 28 | LogQ: []int{55, 40, 40, 40, 40, 40, 40, 40, 40, 40}, 29 | LogP: []int{61, 61, 61}, 30 | LogDefaultScale: 40, 31 | } 32 | 33 | btpLit := NewParametersLiteral() 34 | 35 | params, err := hefloat.NewParametersFromLiteral(paramsLit) 36 | require.NoError(b, err) 37 | 38 | btpParams, err := NewParametersFromLiteral(params, btpLit) 39 | require.Nil(b, err) 40 | 41 | kgen := rlwe.NewKeyGenerator(params) 42 | sk := kgen.GenSecretKeyNew() 43 | 44 | evk, _, err := btpParams.GenEvaluationKeys(sk) 45 | require.NoError(b, err) 46 | 47 | eval, err := NewEvaluator(btpParams, evk) 48 | require.NoError(b, err) 49 | 50 | b.Run(ParamsToString(params, btpParams.LogMaxDimensions().Cols, "Bootstrap/"), func(b *testing.B) { 51 | 52 | var err error 53 | 54 | for i := 0; i < b.N; i++ { 55 | 56 | b.StopTimer() 57 | ct := hefloat.NewCiphertext(params, 1, 0) 58 | b.StartTimer() 59 | 60 | var t time.Time 61 | var ct0, ct1 *rlwe.Ciphertext 62 | 63 | // ScaleDown 64 | t = time.Now() 65 | ct, _, err = eval.ScaleDown(ct) 66 | require.NoError(b, err) 67 | b.Log("ScaleDown:", time.Since(t), ct.Level(), ct.Scale.Float64()) 68 | 69 | // ModUp ct_{Q_0} -> ct_{Q_L} 70 | t = time.Now() 71 | ct, err = eval.ModUp(ct) 72 | require.NoError(b, err) 73 | b.Log("ModUp :", time.Since(t), ct.Level(), ct.Scale.Float64()) 74 | 75 | // Part 1 : Coeffs to slots 76 | t = time.Now() 77 | ct0, ct1, err = eval.CoeffsToSlots(ct) 78 | require.NoError(b, err) 79 | b.Log("CtS :", time.Since(t), ct0.Level(), ct0.Scale.Float64()) 80 | 81 | // Part 2 : SineEval 82 | t = time.Now() 83 | ct0, err = eval.EvalMod(ct0) 84 | require.NoError(b, err) 85 | if ct1 != nil { 86 | ct1, err = eval.EvalMod(ct1) 87 | require.NoError(b, err) 88 | } 89 | b.Log("EvalMod :", time.Since(t), ct0.Level(), ct0.Scale.Float64()) 90 | 91 | // Part 3 : Slots to coeffs 92 | t = time.Now() 93 | ct0, err = eval.SlotsToCoeffs(ct0, ct1) 94 | require.NoError(b, err) 95 | b.Log("StC :", time.Since(t), ct0.Level(), ct0.Scale.Float64()) 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /he/hefloat/bootstrapping/sk_bootstrapper.go: -------------------------------------------------------------------------------- 1 | package bootstrapping 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/he/hefloat" 5 | "github.com/Pro7ech/lattigo/rlwe" 6 | "github.com/Pro7ech/lattigo/utils/bignum" 7 | ) 8 | 9 | // SecretKeyBootstrapper is an implementation of the rlwe.Bootstrapping interface that 10 | // uses the secret-key to decrypt and re-encrypt the bootstrapped ciphertext. 11 | type SecretKeyBootstrapper struct { 12 | hefloat.Parameters 13 | *hefloat.Encoder 14 | *rlwe.Decryptor 15 | *rlwe.Encryptor 16 | sk *rlwe.SecretKey 17 | Values []bignum.Complex 18 | Counter int // records the number of bootstrapping 19 | MinLevel int 20 | } 21 | 22 | func NewSecretKeyBootstrapper(params hefloat.Parameters, sk *rlwe.SecretKey) *SecretKeyBootstrapper { 23 | return &SecretKeyBootstrapper{ 24 | Parameters: params, 25 | Encoder: hefloat.NewEncoder(params), 26 | Decryptor: rlwe.NewDecryptor(params, sk), 27 | Encryptor: rlwe.NewEncryptor(params, sk), 28 | sk: sk, 29 | Values: make([]bignum.Complex, params.N())} 30 | } 31 | 32 | func (d *SecretKeyBootstrapper) Bootstrap(ct *rlwe.Ciphertext) (*rlwe.Ciphertext, error) { 33 | values := d.Values[:1< sum((-1)^i * X^{i*n+1}) for 0 <= i < N and returns the result on a new ciphertext. 11 | // For log(n) = logSlots. 12 | func (eval Evaluator) TraceNew(ctIn *rlwe.Ciphertext, logSlots int) (opOut *rlwe.Ciphertext, err error) { 13 | opOut = NewCiphertext(eval.Parameters(), 1, ctIn.Level()) 14 | return opOut, eval.Trace(ctIn, logSlots, opOut) 15 | } 16 | 17 | // Average returns the average of vectors of batchSize elements. 18 | // The operation assumes that ctIn encrypts SlotCount/'batchSize' sub-vectors of size 'batchSize'. 19 | // It then replaces all values of those sub-vectors by the component-wise average between all the sub-vectors. 20 | // Example for batchSize=4 and slots=8: [{a, b, c, d}, {e, f, g, h}] -> [0.5*{a+e, b+f, c+g, d+h}, 0.5*{a+e, b+f, c+g, d+h}] 21 | // Operation requires log2(SlotCout/'batchSize') rotations. 22 | // Required rotation keys can be generated with 'RotationsForInnerSumLog(batchSize, SlotCount/batchSize)” 23 | func (eval Evaluator) Average(ctIn *rlwe.Ciphertext, logBatchSize int, buf rlwe.HoistingBuffer, opOut *rlwe.Ciphertext) (err error) { 24 | 25 | if ctIn.Degree() != 1 || opOut.Degree() != 1 { 26 | return fmt.Errorf("cannot Average: ctIn.Degree() != 1 or opOut.Degree() != 1") 27 | } 28 | 29 | if logBatchSize > ctIn.LogDimensions.Cols { 30 | return fmt.Errorf("cannot Average: batchSize must be smaller or equal to the number of slots") 31 | } 32 | 33 | level := min(ctIn.Level(), opOut.Level()) 34 | 35 | rQ := eval.Parameters().RingQ().AtLevel(level) 36 | 37 | n := 1 << (ctIn.LogDimensions.Cols - logBatchSize) 38 | 39 | // pre-multiplication by n^-1 40 | for i, s := range rQ { 41 | 42 | invN := ring.ModExp(uint64(n), s.Modulus-2, s.Modulus) 43 | invN = ring.MForm(invN, s.Modulus, s.BRedConstant) 44 | 45 | s.MulScalarMontgomery(ctIn.Q[0].At(i), invN, opOut.Q[0].At(i)) 46 | s.MulScalarMontgomery(ctIn.Q[1].At(i), invN, opOut.Q[1].At(i)) 47 | } 48 | 49 | return eval.InnerSum(opOut, 1< 0; i-- { 63 | if x, ok := pb[i]; ok { 64 | maximumCiphertextDegree = max(maximumCiphertextDegree, x.Degree) 65 | } 66 | } 67 | 68 | if minimumDegreeNonZeroCoefficient < 1 { 69 | maximumCiphertextDegree = 0 70 | } 71 | 72 | tLevelNew = tLevelOld 73 | tScaleNew = tScaleOld 74 | 75 | if lead { 76 | for i := 0; i < d.LevelsConsumedPerRescaling(); i++ { 77 | tScaleNew = tScaleNew.Mul(rlwe.NewScale(d.params.Q()[tLevelNew-i])) 78 | } 79 | } 80 | 81 | return 82 | } 83 | 84 | // UpdateLevelAndScaleGiantStep returns the updated level and scale for a giant-step. 85 | func (d simEvaluator) UpdateLevelAndScaleGiantStep(lead bool, tLevelOld int, tScaleOld, xPowScale rlwe.Scale, pol *he.Polynomial) (tLevelNew int, tScaleNew rlwe.Scale) { 86 | 87 | Q := d.params.Q() 88 | 89 | var qi *big.Int 90 | if lead { 91 | qi = bignum.NewInt(Q[tLevelOld]) 92 | for i := 1; i < d.LevelsConsumedPerRescaling(); i++ { 93 | qi.Mul(qi, bignum.NewInt(Q[tLevelOld-i])) 94 | } 95 | } else { 96 | qi = bignum.NewInt(Q[tLevelOld+d.LevelsConsumedPerRescaling()]) 97 | for i := 1; i < d.LevelsConsumedPerRescaling(); i++ { 98 | qi.Mul(qi, bignum.NewInt(Q[tLevelOld+d.LevelsConsumedPerRescaling()-i])) 99 | } 100 | } 101 | 102 | tLevelNew = tLevelOld + d.LevelsConsumedPerRescaling() 103 | tScaleNew = tScaleOld.Mul(rlwe.NewScale(qi)) 104 | tScaleNew = tScaleNew.Div(xPowScale) 105 | 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /he/hefloat/scaling.go: -------------------------------------------------------------------------------- 1 | package hefloat 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Pro7ech/lattigo/ring" 7 | "github.com/Pro7ech/lattigo/utils/bignum" 8 | ) 9 | 10 | func bigComplexToRNSScalar(r ring.RNSRing, scale *big.Float, cmplx *bignum.Complex) (RNSReal, RNSImag ring.RNSScalar) { 11 | 12 | if scale == nil { 13 | scale = new(big.Float).SetFloat64(1) 14 | } 15 | 16 | real := new(big.Int) 17 | bReal := new(big.Float).Mul(&cmplx[0], scale) 18 | 19 | if cmp := cmplx[0].Cmp(new(big.Float)); cmp > 0 { 20 | bReal.Add(bReal, new(big.Float).SetFloat64(0.5)) 21 | } else if cmp < 0 { 22 | bReal.Sub(bReal, new(big.Float).SetFloat64(0.5)) 23 | } 24 | 25 | bReal.Int(real) 26 | 27 | imag := new(big.Int) 28 | bImag := new(big.Float).Mul(&cmplx[1], scale) 29 | 30 | if cmp := cmplx[1].Cmp(new(big.Float)); cmp > 0 { 31 | bImag.Add(bImag, new(big.Float).SetFloat64(0.5)) 32 | } else if cmp < 0 { 33 | bImag.Sub(bImag, new(big.Float).SetFloat64(0.5)) 34 | } 35 | 36 | bImag.Int(imag) 37 | 38 | return r.NewRNSScalarFromBigint(real), r.NewRNSScalarFromBigint(imag) 39 | } 40 | 41 | // Divides x by n, returns a float. 42 | func scaleDown(coeff *big.Int, n float64) (x float64) { 43 | 44 | x, _ = new(big.Float).SetInt(coeff).Float64() 45 | x /= n 46 | 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /he/heint/README.md: -------------------------------------------------------------------------------- 1 | 2 | # BGV 3 | 4 | The BGV package provides a unified RNS-accelerated variant of the Brakerski-Fan-Vercauteren (BFV) scale invariant homomorphic encryption scheme and Brakerski-Gentry-Vaikuntanathan (BGV) homomorphic encryption scheme. It enables SIMD modular arithmetic over encrypted vectors or integers. 5 | 6 | ## Implementation Notes 7 | 8 | The proposed implementation provides all the functionalities of the BFV and BGV schemes under a unified scheme. 9 | This is enabled by the equivalence between the LSB and MSB encoding when T is coprime to Q (Appendix A of ). 10 | 11 | ### Intuition 12 | 13 | The textbook BGV scheme encodes the plaintext in the LSB and the encryption is done by scaling the error by $T$: 14 | 15 | ```math 16 | \textsf{Encrypt}_{s}(\textsf{Encode}(m)) = [-as + m + Te, a]_{Q_{\ell}} 17 | ``` 18 | 19 | where $Q_{\ell} = \prod_{i=0}^{L} q_{i}$ in the RNS variant of the scheme. 20 | 21 | The decoding process is then carried out by taking the decrypted plaintext $[m + Te]_{Q_{\ell}}$ modulo $T$ which vanishes the error. 22 | 23 | We observe that the only non-linear part of the BGV scheme is its modulus switching operation and that this operation is identical to a CKKS-style rescaling (quantization of the ciphertext by $\frac{1}{q_{\ell}}$) with a pre- and post-processing: 24 | 25 | 1) Multiply the ciphertext by $T^{-1}\mod Q_{\ell}$ (switch from LSB to MSB encoding) 26 | 27 | ```math 28 | T^{-1} \cdot [-as + m + eT, a]_{Q_{\ell}}\rightarrow[-bs + mT^{-1} + e, b]_{Q_{\ell}} 29 | ``` 30 | 31 | 2) Apply the Full-RNS CKKS-style rescaling (division by $q_{\ell} = Q_{\ell}/Q_{\ell-1}$): 32 | 33 | ```math 34 | \lfloor q_{\ell}^{-1}\cdot[-bs + mT^{-1} + e, b]_{Q_{\ell}}\rceil\rightarrow[-cs + mq_{\ell}^{-1}T^{-1} + \lfloor e/q_{\ell}\rfloor + e_{\textsf{round}}, c]_{Q_{\ell-1}} 35 | ``` 36 | 37 | 3) Multiply the ciphertext by $T \mod Q_{\ell-1}$ (switch from MSB to LSB encoding) 38 | 39 | ```math 40 | T\cdot[-cs + mq_{\ell}^{-1}T^{-1} + \lfloor e/q_{\ell}\rceil + e_{\textsf{round}}, c]_{Q_{\ell-1}}\rightarrow[-ds + mq_{\ell}^{-1} + T(\lfloor e/q_{\ell}\rceil + e_{\textsf{round}}), d]_{Q_{\ell-1}} 41 | ``` 42 | 43 | The process returns a new ciphertext modulo $Q_{\ell-1}$ where the error has been quantized by $q_{\ell}$ and the message multiplied by a factor of $q_{\ell}^{-1} \mod T$. 44 | 45 | Since the modulus switch is the only non-linear part of the BGV scheme, we can move steps 1) and 2) to the encoding and decoding steps respectively, i.e. instead of scaling the error during the encryption by $T$ we scale the plaintext by $T^{-1}\mod Q_{\ell}$ during the encoding. 46 | 47 | The tensoring operations have to be slightly modified to take into account the additional multiples of $T^{-1}$ (but this can be done for free when operands are switched in the Montgomery domain). 48 | 49 | ### Functionalities 50 | 51 | The above change enables an implementation of the BGV scheme with an MSB encoding, which is essentially the BFV scheme. In other words, if $T$ is coprime with $Q$ then the BFV and BGV encoding (and thus scheme) are indistinguishable up to a plaintext scaling factor of $T^{-1}\mod Q$. 52 | 53 | This unified scheme can also be seen as a variant of the BGV scheme with two tensoring operations: 54 | 55 | - The BGV-style tensoring with a noise growth proportional to the current noise, 56 | - The BFV-style tensoring with a noise growth invariant to the current noise. 57 | 58 | ## References 59 | 60 | 1. Practical Bootstrapping in Quasilinear Time () 61 | 2. A Full RNS Variant of FV Like Somewhat Homomorphic Encryption Schemes () 62 | 3. An Improved RNS Variant of the BFV Homomorphic Encryption Scheme () 63 | -------------------------------------------------------------------------------- /he/heint/encoder_test.go: -------------------------------------------------------------------------------- 1 | package heint_test 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | 7 | "github.com/Pro7ech/lattigo/he/heint" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func testEncoder(tc *testContext, t *testing.T) { 12 | 13 | for _, lvl := range tc.testLevel { 14 | t.Run(GetTestName("Encoder/Uint/IsBatched=true", tc.params, lvl), func(t *testing.T) { 15 | values, plaintext, _ := newTestVectorsLvl(lvl, tc.params.DefaultScale(), tc, nil) 16 | verifyTestVectors(tc, nil, values, plaintext, t) 17 | }) 18 | } 19 | 20 | for _, lvl := range tc.testLevel { 21 | t.Run(GetTestName("Encoder/Int/IsBatched=true", tc.params, lvl), func(t *testing.T) { 22 | 23 | T := tc.params.PlaintextModulus() 24 | THalf := T >> 1 25 | coeffs := make([]int64, tc.params.MaxSlots()) 26 | for i := range coeffs { 27 | if i > int(THalf) { 28 | coeffs[i] = int64(-i) 29 | } else { 30 | coeffs[i] = int64(i) 31 | } 32 | } 33 | 34 | plaintext := heint.NewPlaintext(tc.params, lvl) 35 | require.NoError(t, tc.encoder.Encode(coeffs, plaintext)) 36 | have := make([]int64, tc.params.MaxSlots()) 37 | require.NoError(t, tc.encoder.Decode(plaintext, have)) 38 | require.True(t, slices.Equal(coeffs, have)) 39 | }) 40 | } 41 | 42 | for _, lvl := range tc.testLevel { 43 | t.Run(GetTestName("Encoder/Uint/IsBatched=false", tc.params, lvl), func(t *testing.T) { 44 | coeffs := make([]uint64, tc.params.N()) 45 | T := tc.params.PlaintextModulus() 46 | for i := range coeffs { 47 | coeffs[i] = uint64(i) % T 48 | } 49 | 50 | plaintext := heint.NewPlaintext(tc.params, lvl) 51 | plaintext.IsBatched = false 52 | require.NoError(t, tc.encoder.Encode(coeffs, plaintext)) 53 | have := make([]uint64, tc.params.N()) 54 | require.NoError(t, tc.encoder.Decode(plaintext, have)) 55 | require.True(t, slices.Equal(coeffs, have)) 56 | }) 57 | } 58 | 59 | for _, lvl := range tc.testLevel { 60 | t.Run(GetTestName("Encoder/Int/IsBatched=false", tc.params, lvl), func(t *testing.T) { 61 | 62 | T := int64(tc.params.PlaintextModulus()) 63 | THalf := T >> 1 64 | coeffs := make([]int64, tc.params.N()) 65 | for i := range coeffs { 66 | c := int64(i) % T 67 | if c >= THalf { 68 | coeffs[i] = c - T 69 | } else { 70 | coeffs[i] = c 71 | } 72 | } 73 | plaintext := heint.NewPlaintext(tc.params, lvl) 74 | plaintext.IsBatched = false 75 | require.NoError(t, tc.encoder.Encode(coeffs, plaintext)) 76 | have := make([]int64, tc.params.N()) 77 | require.NoError(t, tc.encoder.Decode(plaintext, have)) 78 | require.True(t, slices.Equal(coeffs, have)) 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /he/heint/examples_parameters.go: -------------------------------------------------------------------------------- 1 | package heint 2 | 3 | var ( 4 | // ExampleParameters128BitLogN14LogQP438 is an example parameters set with logN=14, logQP=438 5 | // and a 16-bit plaintext modulus, offering 128-bit of security. 6 | ExampleParameters128BitLogN14LogQP438 = ParametersLiteral{ 7 | LogN: 14, 8 | LogQ: []int{ 9 | 40, 29, 29, 10 | 29, 29, 29, 11 | 29, 29, 29, 12 | 29, 29, 29}, // 40 + 11*29 bits 13 | LogP: []int{40, 39}, // 40 + 39 bits 14 | T: 65537, // 16 bits 15 | R: 1, // T^{R} 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /he/heint/heint.go: -------------------------------------------------------------------------------- 1 | // Package heint provides Homomorphic Encryption for encrypted modular arithmetic over the integers. 2 | // It is implemented as an unified RNS-accelerated version of the Fan-Vercauteren version of the 3 | // Brakerski's scale invariant homomorphic encryption scheme (BFV) and 4 | // Brakerski-Gentry-Vaikuntanathan (BGV) homomorphic encryption scheme. 5 | package heint 6 | 7 | import ( 8 | "github.com/Pro7ech/lattigo/rlwe" 9 | ) 10 | 11 | // NewPlaintext allocates a new rlwe.Plaintext. 12 | // 13 | // inputs: 14 | // - params: an rlwe.ParameterProvider interface 15 | // - level: the level of the plaintext 16 | // 17 | // output: a newly allocated rlwe.Plaintext at the specified level. 18 | // 19 | // Note: the user can update the field `MetaData` to set a specific scaling factor, 20 | // plaintext dimensions (if applicable) or encoding domain, before encoding values 21 | // on the created plaintext. 22 | func NewPlaintext(params Parameters, level int) (pt *rlwe.Plaintext) { 23 | pt = rlwe.NewPlaintext(params, level, -1) 24 | pt.IsBatched = true 25 | pt.Scale = params.DefaultScale() 26 | pt.LogDimensions = params.LogMaxDimensions() 27 | return 28 | } 29 | 30 | // NewCiphertext allocates a new rlwe.Ciphertext. 31 | // 32 | // inputs: 33 | // - params: an rlwe.ParameterProvider interface 34 | // - degree: the degree of the ciphertext 35 | // - level: the level of the Ciphertext 36 | // 37 | // output: a newly allocated rlwe.Ciphertext of the specified degree and level. 38 | func NewCiphertext(params Parameters, degree, level int) (ct *rlwe.Ciphertext) { 39 | ct = rlwe.NewCiphertext(params, degree, level, -1) 40 | ct.IsBatched = true 41 | ct.Scale = params.DefaultScale() 42 | ct.LogDimensions = params.LogMaxDimensions() 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /he/heint/parameters_test.go: -------------------------------------------------------------------------------- 1 | package heint_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/Pro7ech/lattigo/he/heint" 9 | "github.com/Pro7ech/lattigo/ring" 10 | "github.com/Pro7ech/lattigo/rlwe" 11 | "github.com/Pro7ech/lattigo/utils/buffer" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func testParameters(tc *testContext, t *testing.T) { 16 | t.Run(GetTestName("Parameters/Binary", tc.params, 0), func(t *testing.T) { 17 | buffer.RequireSerializerCorrect(t, &tc.params) 18 | 19 | }) 20 | 21 | t.Run(GetTestName("Parameters/JSON", tc.params, 0), func(t *testing.T) { 22 | var err error 23 | // checks that the Parameters can be unmarshalled with log-moduli definition without error 24 | dataWithLogModuli := []byte(fmt.Sprintf(`{"LogN":%d,"LogQ":[50,50],"LogP":[60], "T":65537, "R":1}`, tc.params.LogN())) 25 | var paramsWithLogModuli heint.Parameters 26 | err = json.Unmarshal(dataWithLogModuli, ¶msWithLogModuli) 27 | require.Nil(t, err) 28 | require.Equal(t, 2, paramsWithLogModuli.QCount()) 29 | require.Equal(t, 1, paramsWithLogModuli.PCount()) 30 | require.True(t, paramsWithLogModuli.Xe().Equal(&rlwe.DefaultXe)) // Omitting Xe should result in Default being used 31 | require.True(t, paramsWithLogModuli.Xs().Equal(&rlwe.DefaultXs)) // Omitting Xe should result in Default being used 32 | 33 | // checks that one can provide custom parameters for the secret-key and error distributions 34 | dataWithCustomSecrets := []byte(fmt.Sprintf(`{"LogN":%d,"LogQ":[50,50],"LogP":[60], "T":65537, "R":1, "Xs": {"Type": "Ternary", "H": 192}, "Xe": {"Type": "DiscreteGaussian", "Sigma": 6.6, "Bound": 39.6}}`, tc.params.LogN())) 35 | var paramsWithCustomSecrets heint.Parameters 36 | err = json.Unmarshal(dataWithCustomSecrets, ¶msWithCustomSecrets) 37 | require.Nil(t, err) 38 | require.True(t, paramsWithCustomSecrets.Xe().Equal(&ring.DiscreteGaussian{Sigma: 6.6, Bound: 39.6})) 39 | require.True(t, paramsWithCustomSecrets.Xs().Equal(&ring.Ternary{H: 192})) 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /he/heint/polynomial_evaluator_sim.go: -------------------------------------------------------------------------------- 1 | package heint 2 | 3 | import ( 4 | "math/big" 5 | "math/bits" 6 | 7 | "github.com/Pro7ech/lattigo/he" 8 | "github.com/Pro7ech/lattigo/ring" 9 | "github.com/Pro7ech/lattigo/rlwe" 10 | ) 11 | 12 | // simEvaluator is a struct used to pre-computed the scaling 13 | // factors of the polynomial coefficients used by the inlined 14 | // polynomial evaluation by running the polynomial evaluation 15 | // with dummy operands. 16 | // This struct implements the interface he.SimEvaluator. 17 | type simEvaluator struct { 18 | params Parameters 19 | InvariantTensoring bool 20 | } 21 | 22 | // PolynomialDepth returns the depth of the polynomial. 23 | func (d simEvaluator) PolynomialDepth(degree int) int { 24 | if d.InvariantTensoring { 25 | return 0 26 | } 27 | return bits.Len64(uint64(degree)) - 1 28 | } 29 | 30 | // LogDimensions returns the base-two logarithm of the plaintext shape. 31 | func (d simEvaluator) LogDimensions() ring.Dimensions { 32 | return d.params.LogMaxDimensions() 33 | } 34 | 35 | // Rescale rescales the target he.SimOperand n times and returns it. 36 | func (d simEvaluator) Rescale(op0 *he.SimOperand) { 37 | if !d.InvariantTensoring { 38 | op0.Scale = op0.Scale.Div(rlwe.NewScale(d.params.Q()[op0.Level])) 39 | op0.Level-- 40 | } 41 | } 42 | 43 | // MulNew multiplies two he.SimOperand, stores the result the target he.SimOperand and returns the result. 44 | func (d simEvaluator) MulNew(op0, op1 *he.SimOperand) (opOut *he.SimOperand) { 45 | opOut = new(he.SimOperand) 46 | opOut.Level = min(op0.Level, op1.Level) 47 | opOut.Degree = 1 48 | 49 | // If op0 or op1 Degree == 0, then its ct x pt mul with regular 50 | // tensoring 51 | if !d.InvariantTensoring || op0.Degree == 0 || op1.Degree == 0 { 52 | opOut.Scale = op0.Scale.Mul(op1.Scale) 53 | } else { 54 | opOut.Scale = UpdateScaleInvariant(d.params, op0.Scale, op1.Scale, opOut.Level) 55 | } 56 | 57 | return 58 | } 59 | 60 | // UpdateLevelAndScaleBabyStep returns the updated level and scale for a baby-step. 61 | func (d simEvaluator) UpdateLevelAndScaleBabyStep(lead bool, tLevelOld int, tScaleOld rlwe.Scale, pol *he.Polynomial, pb he.SimPowerBasis) (tLevelNew int, tScaleNew rlwe.Scale, maximumCiphertextDegree int) { 62 | 63 | minimumDegreeNonZeroCoefficient := len(pol.Coeffs) - 1 64 | if pol.IsEven && !pol.IsOdd { 65 | minimumDegreeNonZeroCoefficient = max(0, minimumDegreeNonZeroCoefficient-1) 66 | } 67 | 68 | maximumCiphertextDegree = 0 69 | for i := pol.Degree(); i > 0; i-- { 70 | if x, ok := pb[i]; ok { 71 | maximumCiphertextDegree = max(maximumCiphertextDegree, x.Degree) 72 | } 73 | } 74 | 75 | if minimumDegreeNonZeroCoefficient < 1 { 76 | maximumCiphertextDegree = 0 77 | } 78 | 79 | tLevelNew = tLevelOld 80 | tScaleNew = tScaleOld 81 | if !d.InvariantTensoring && lead { 82 | tScaleNew = tScaleOld.Mul(d.params.NewScale(d.params.Q()[tLevelOld])) 83 | } 84 | 85 | return 86 | } 87 | 88 | // UpdateLevelAndScaleGiantStep returns the updated level and scale for a giant-step. 89 | func (d simEvaluator) UpdateLevelAndScaleGiantStep(lead bool, tLevelOld int, tScaleOld, xPowScale rlwe.Scale, pol *he.Polynomial) (tLevelNew int, tScaleNew rlwe.Scale) { 90 | 91 | Q := d.params.Q() 92 | 93 | tLevelNew = tLevelOld 94 | tScaleNew = tScaleOld.Div(xPowScale) 95 | 96 | // tScaleNew = targetScale*currentQi/XPow.Scale 97 | if !d.InvariantTensoring { 98 | 99 | var currentQi uint64 100 | if lead { 101 | currentQi = Q[tLevelNew] 102 | } else { 103 | currentQi = Q[tLevelNew+1] 104 | } 105 | 106 | tScaleNew = tScaleNew.Mul(d.params.NewScale(currentQi)) 107 | 108 | } else { 109 | 110 | minimumDegreeNonZeroCoefficient := len(pol.Coeffs) - 1 111 | if pol.IsEven && !pol.IsOdd { 112 | minimumDegreeNonZeroCoefficient = max(0, minimumDegreeNonZeroCoefficient-1) 113 | } 114 | 115 | // If minimumDegreeNonZeroCoefficient == 0, then the target scale stays the same 116 | // since we have pt x ct multiplication (no invariant tensoring and not rescaling) 117 | if minimumDegreeNonZeroCoefficient != 0 { 118 | T := d.params.PlaintextModulus() 119 | 120 | // -Q mod T 121 | qModTNeg := new(big.Int).Mod(d.params.RingQ().AtLevel(tLevelNew).Modulus(), new(big.Int).SetUint64(T)).Uint64() 122 | qModTNeg = T - qModTNeg 123 | tScaleNew = tScaleNew.Mul(d.params.NewScale(qModTNeg)) 124 | } 125 | 126 | } 127 | 128 | if !d.InvariantTensoring { 129 | tLevelNew++ 130 | } 131 | 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /he/heint/polynomial_evaluator_test.go: -------------------------------------------------------------------------------- 1 | package heint_test 2 | 3 | import ( 4 | //"fmt" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/Pro7ech/lattigo/he" 9 | "github.com/Pro7ech/lattigo/he/heint" 10 | "github.com/Pro7ech/lattigo/ring" 11 | "github.com/Pro7ech/lattigo/utils/bignum" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func testPolynomialEvaluator(tc *testContext, t *testing.T) { 16 | 17 | t.Run("PolynomialEvaluator", func(t *testing.T) { 18 | 19 | t.Run("Single", func(t *testing.T) { 20 | 21 | if tc.params.MaxLevel() < 4 { 22 | t.Skip("MaxLevel() to low") 23 | } 24 | 25 | values, _, ciphertext := newTestVectorsLvl(tc.params.MaxLevel(), tc.params.NewScale(1), tc, tc.encryptorSk) 26 | 27 | coeffs := []uint64{0, 0, 1} 28 | 29 | T := tc.params.PlaintextModulus() 30 | for i := range values { 31 | values[i] = ring.EvalPolyModP(values[i], coeffs, T) 32 | } 33 | 34 | poly := bignum.NewPolynomial(bignum.Monomial, coeffs, nil) 35 | 36 | t.Run(GetTestName("Standard", tc.params, tc.params.MaxLevel()), func(t *testing.T) { 37 | polyEval := heint.NewPolynomialEvaluator(tc.params, tc.evaluator, false) 38 | res, err := polyEval.Evaluate(ciphertext, poly, tc.params.DefaultScale()) 39 | require.NoError(t, err) 40 | require.NoError(t, polyEval.Rescale(res, res)) 41 | require.Equal(t, res.Scale.Cmp(tc.params.DefaultScale()), 0) 42 | verifyTestVectors(tc, tc.decryptor, values, res, t) 43 | }) 44 | 45 | t.Run(GetTestName("Invariant", tc.params, tc.params.MaxLevel()), func(t *testing.T) { 46 | polyEval := heint.NewPolynomialEvaluator(tc.params, tc.evaluator, true) 47 | res, err := polyEval.Evaluate(ciphertext, poly, tc.params.DefaultScale()) 48 | require.NoError(t, err) 49 | require.Equal(t, res.Level(), ciphertext.Level()) 50 | require.Equal(t, res.Scale.Cmp(tc.params.DefaultScale()), 0) 51 | verifyTestVectors(tc, tc.decryptor, values, res, t) 52 | }) 53 | }) 54 | 55 | t.Run("Vector", func(t *testing.T) { 56 | 57 | if tc.params.MaxLevel() < 4 { 58 | t.Skip("MaxLevel() to low") 59 | } 60 | 61 | values, _, ciphertext := newTestVectorsLvl(tc.params.MaxLevel(), tc.params.NewScale(7), tc, tc.encryptorSk) 62 | 63 | coeffs0 := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 64 | coeffs1 := []uint64{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17} 65 | 66 | mapping := make([]int, len(values)) 67 | for i := range mapping { 68 | if i&1 == 1 { 69 | mapping[i] = 1 70 | } 71 | } 72 | 73 | p0 := he.NewPolynomial(bignum.NewPolynomial(bignum.Monomial, coeffs0, nil)) 74 | p1 := he.NewPolynomial(bignum.NewPolynomial(bignum.Monomial, coeffs1, nil)) 75 | 76 | polyVector, err := he.NewPolynomialVector(map[int]*he.Polynomial{ 77 | 0: p0, 78 | 1: p1, 79 | }, mapping) 80 | require.NoError(t, err) 81 | 82 | TInt := new(big.Int).SetUint64(tc.params.PlaintextModulus()) 83 | 84 | for i, j := range mapping { 85 | if p, ok := polyVector.Value[j]; ok { 86 | values[i] = p.EvaluateModP(new(big.Int).SetUint64(values[i]), TInt).Uint64() 87 | } else { 88 | values[i] = 0 89 | } 90 | } 91 | 92 | t.Run(GetTestName("Standard", tc.params, tc.params.MaxLevel()), func(t *testing.T) { 93 | 94 | polyEval := heint.NewPolynomialEvaluator(tc.params, tc.evaluator, false) 95 | 96 | res, err := polyEval.Evaluate(ciphertext, polyVector, tc.params.DefaultScale()) 97 | require.NoError(t, err) 98 | 99 | require.NoError(t, polyEval.Rescale(res, res)) 100 | 101 | require.Equal(t, res.Scale.Cmp(tc.params.DefaultScale()), 0) 102 | 103 | verifyTestVectors(tc, tc.decryptor, values, res, t) 104 | }) 105 | 106 | t.Run(GetTestName("Invariant", tc.params, tc.params.MaxLevel()), func(t *testing.T) { 107 | 108 | polyEval := heint.NewPolynomialEvaluator(tc.params, tc.evaluator, true) 109 | 110 | res, err := polyEval.Evaluate(ciphertext, polyVector, tc.params.DefaultScale()) 111 | require.NoError(t, err) 112 | 113 | require.Equal(t, res.Level(), ciphertext.Level()) 114 | require.Equal(t, res.Scale.Cmp(tc.params.DefaultScale()), 0) 115 | 116 | verifyTestVectors(tc, tc.decryptor, values, res, t) 117 | }) 118 | }) 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /he/linear_transformation_diagonals.go: -------------------------------------------------------------------------------- 1 | package he 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | 8 | "github.com/Pro7ech/lattigo/ring" 9 | "github.com/Pro7ech/lattigo/rlwe" 10 | "github.com/Pro7ech/lattigo/utils" 11 | ) 12 | 13 | type Diagonals[T any] map[int][]T 14 | 15 | func (m Diagonals[T]) Add(d Diagonals[T], add func(a, b, c []T), clone func(a []T) (b []T)) { 16 | for i, di := range d { 17 | if mi, ok := m[i]; ok { 18 | add(mi, di, mi) 19 | } else { 20 | m[i] = clone(di) 21 | } 22 | } 23 | } 24 | 25 | func (m Diagonals[T]) Mul(d Diagonals[T], buff []T, add func(a, b, c []T), rot func(a []T, k int, b []T), mul func(a, b []T, c []T), clone func(a []T) (b []T)) { 26 | 27 | tmp := map[int][]T{} 28 | 29 | dim := len(buff) 30 | 31 | for i, mi := range m { 32 | for j, dj := range d { 33 | 34 | rot(mi, j, buff) 35 | mul(buff, dj, buff) 36 | 37 | k := (i + j) % dim 38 | 39 | if _, ok := tmp[k]; !ok { 40 | tmp[k] = clone(buff) 41 | } else { 42 | add(tmp[k], buff, tmp[k]) 43 | } 44 | } 45 | } 46 | 47 | for i := range m { 48 | delete(m, i) 49 | } 50 | for i := range tmp { 51 | m[i] = tmp[i] 52 | } 53 | } 54 | 55 | // Indexes returns the list of the non-zero diagonals of the square matrix. 56 | // A non zero diagonals is a diagonal with a least one non-zero element. 57 | func (m Diagonals[T]) Indexes() (indexes []int) { 58 | return slices.Collect(maps.Keys(m)) 59 | } 60 | 61 | func (m Diagonals[T]) GaloisElements(params rlwe.ParameterProvider, LogDimensions ring.Dimensions, GiantStep int) (galEls []uint64) { 62 | ltParams := &LinearTransformationParameters{ 63 | Indexes: m.Indexes(), 64 | LogDimensions: LogDimensions, 65 | GiantStep: GiantStep, 66 | } 67 | return ltParams.GaloisElements(params) 68 | } 69 | 70 | // At returns the i-th non-zero diagonal. 71 | // Method accepts negative values with the equivalency -i = n - i. 72 | func (m Diagonals[T]) At(i, slots int) ([]T, error) { 73 | 74 | v, ok := m[i] 75 | 76 | if !ok { 77 | 78 | var j int 79 | if i > 0 { 80 | j = i - slots 81 | } else if j < 0 { 82 | j = i + slots 83 | } else { 84 | return nil, fmt.Errorf("cannot At[0]: diagonal does not exist") 85 | } 86 | 87 | v, ok := m[j] 88 | 89 | if !ok { 90 | return nil, fmt.Errorf("cannot At[%d or %d]: diagonal does not exist", i, j) 91 | } 92 | 93 | return v, nil 94 | } 95 | 96 | return v, nil 97 | } 98 | 99 | // Evaluate evaluates the [hefloat.Diagonals] on the input vector. 100 | // - zero: evaluates c[i] = 0 101 | // - add: evaluates c[i] = a[i] + b[i] 102 | // - muladd: evaluates c[i] = a[i] * b[i] 103 | func (m Diagonals[T]) Evaluate(in, buff, out []T, LTParams LinearTransformationParameters, zero func(a []T), add func(a, b, c []T), muladd func(a, b, c []T)) { 104 | 105 | rows := 1 << LTParams.LogDimensions.Rows 106 | cols := 1 << LTParams.LogDimensions.Cols 107 | 108 | n := len(in) 109 | 110 | keys := slices.Collect(maps.Keys(m)) 111 | slices.Sort(keys) 112 | 113 | zero(out) 114 | 115 | if LTParams.GiantStep > 0 { 116 | 117 | index, _, _ := BSGSIndex(keys, n, LTParams.GiantStep) 118 | 119 | keys = slices.Collect(maps.Keys(index)) 120 | slices.Sort(keys) 121 | 122 | for _, j := range keys { 123 | 124 | rot := -j & (n - 1) 125 | 126 | zero(buff) 127 | 128 | for _, i := range index[j] { 129 | 130 | v, ok := m[j+i] 131 | if !ok { 132 | v = m[j+i-n] 133 | } 134 | 135 | for k := 0; k < rows; k++ { 136 | muladd(utils.RotateSlice(in[k*cols:(k+1)*cols], i), utils.RotateSlice(v[k*cols:(k+1)*cols], rot), buff[k*cols:(k+1)*cols]) 137 | } 138 | } 139 | 140 | for k := 0; k < rows; k++ { 141 | add(out[k*cols:(k+1)*cols], utils.RotateSlice(buff[k*cols:(k+1)*cols], j), out[k*cols:(k+1)*cols]) 142 | } 143 | } 144 | } else { 145 | for _, j := range keys { 146 | for k := 0; k < rows; k++ { 147 | muladd(utils.RotateSlice(in[k*cols:(k+1)*cols], j), m[j][k*cols:(k+1)*cols], out[k*cols:(k+1)*cols]) 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /he/linear_transformation_permutation.go: -------------------------------------------------------------------------------- 1 | package he 2 | 3 | import ( 4 | "maps" 5 | "slices" 6 | 7 | "github.com/Pro7ech/lattigo/ring" 8 | "github.com/Pro7ech/lattigo/rlwe" 9 | ) 10 | 11 | // Permutation is a struct that defines a linear transformation 12 | // acting as a permutation over a vector. 13 | // The defined permutation can be injective but not surjective. 14 | type Permutation[T any] []struct { 15 | X int // Start index 16 | Y int // End index 17 | C T // Scalar factor 18 | } 19 | 20 | // NewPermutation allocates a new [hefloat.Permutation] that can 21 | // then be populated manually. 22 | func NewPermutation[T any](size int) Permutation[T] { 23 | return make([]struct { 24 | X int 25 | Y int 26 | C T 27 | }, size) 28 | } 29 | 30 | func (p Permutation[T]) Indexes(LogDimensions ring.Dimensions) (indexes []int) { 31 | cols := 1 << LogDimensions.Cols 32 | m := map[int]bool{} 33 | for _, pi := range p { 34 | m[(cols+pi.X-pi.Y)&(cols-1)] = true 35 | } 36 | return slices.Collect(maps.Keys(m)) 37 | } 38 | 39 | // Diagonals returns the [hefloat.Diagonals] representation 40 | // of the permutation. 41 | // The [hefloat.Diagonals] struct is used to instantiate an 42 | // [hefloat.LinearTransformationParameters]. 43 | func (p Permutation[T]) Diagonals(LogDimensions ring.Dimensions) Diagonals[T] { 44 | 45 | rows := 1 << LogDimensions.Rows 46 | cols := 1 << LogDimensions.Cols 47 | 48 | diagonals := map[int][]T{} 49 | 50 | for _, m := range p { 51 | 52 | idx := (cols + m.X - m.Y) & (cols - 1) 53 | 54 | if _, ok := diagonals[idx]; !ok { 55 | diagonals[idx] = make([]T, rows*cols) 56 | } 57 | 58 | diagonals[idx][m.Y] = m.C 59 | } 60 | 61 | return Diagonals[T](diagonals) 62 | } 63 | 64 | func (p Permutation[T]) GaloisElements(params rlwe.ParameterProvider, LogDimensions ring.Dimensions) (galEls []uint64) { 65 | ltParams := &LinearTransformationParameters{ 66 | Indexes: p.Indexes(LogDimensions), 67 | LogDimensions: LogDimensions, 68 | } 69 | return ltParams.GaloisElements(params) 70 | } 71 | -------------------------------------------------------------------------------- /he/polynomial_evaluator_sim.go: -------------------------------------------------------------------------------- 1 | package he 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/ring" 5 | "github.com/Pro7ech/lattigo/rlwe" 6 | ) 7 | 8 | // SimOperand is a dummy operand that 9 | // only stores its level and scale. 10 | type SimOperand struct { 11 | Level int 12 | Degree int 13 | Scale rlwe.Scale 14 | } 15 | 16 | // SimEvaluator defines a set of method on SimOperands. 17 | type SimEvaluator interface { 18 | LogDimensions() ring.Dimensions 19 | MulNew(op0, op1 *SimOperand) *SimOperand 20 | Rescale(op0 *SimOperand) 21 | PolynomialDepth(degree int) int 22 | UpdateLevelAndScaleGiantStep(lead bool, tLevelOld int, tScaleOld, xPowScale rlwe.Scale, p *Polynomial) (tLevelNew int, tScaleNew rlwe.Scale) 23 | UpdateLevelAndScaleBabyStep(lead bool, tLevelOld int, tScaleOld rlwe.Scale, p *Polynomial, pb SimPowerBasis) (tLevelNew int, tScaleNew rlwe.Scale, degree int) 24 | } 25 | 26 | // SimPowerBasis is a map storing powers of SimOperands indexed by their power. 27 | type SimPowerBasis map[int]*SimOperand 28 | 29 | // GenPower populates the target SimPowerBasis with the nth power. 30 | func (d SimPowerBasis) GenPower(Lazy bool, n int, eval SimEvaluator) { 31 | 32 | if n < 2 { 33 | return 34 | } 35 | 36 | a, b := SplitDegree(n) 37 | 38 | d.GenPower(Lazy, a, eval) 39 | d.GenPower(Lazy, b, eval) 40 | 41 | d[a].Degree = 1 42 | d[b].Degree = 1 43 | 44 | d[n] = eval.MulNew(d[a], d[b]) 45 | 46 | if Lazy { 47 | d[n].Degree = 2 48 | } else { 49 | d[n].Degree = 1 50 | } 51 | 52 | eval.Rescale(d[n]) 53 | } 54 | -------------------------------------------------------------------------------- /he/power_basis_test.go: -------------------------------------------------------------------------------- 1 | package he 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Pro7ech/lattigo/rlwe" 7 | "github.com/Pro7ech/lattigo/utils/bignum" 8 | "github.com/Pro7ech/lattigo/utils/buffer" 9 | "github.com/Pro7ech/lattigo/utils/sampling" 10 | ) 11 | 12 | func TestPowerBasis(t *testing.T) { 13 | t.Run("WriteAndRead", func(t *testing.T) { 14 | var err error 15 | var params rlwe.Parameters 16 | if params, err = rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ 17 | LogN: 10, 18 | Q: []uint64{0x200000440001, 0x7fff80001}, 19 | P: []uint64{0x3ffffffb80001, 0x4000000800001}, 20 | }); err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | levelQ := params.MaxLevelQ() 25 | 26 | source := sampling.NewSource(sampling.NewSeed()) 27 | 28 | ct := rlwe.NewCiphertext(params, 1, levelQ, -1) 29 | ct.Randomize(params, source) 30 | 31 | basis := NewPowerBasis(ct, bignum.Chebyshev) 32 | 33 | basis.Value[2] = rlwe.NewCiphertext(params, 1, levelQ, -1) 34 | basis.Value[3] = rlwe.NewCiphertext(params, 2, levelQ, -1) 35 | basis.Value[4] = rlwe.NewCiphertext(params, 1, levelQ, -1) 36 | basis.Value[8] = rlwe.NewCiphertext(params, 1, levelQ, -1) 37 | 38 | basis.Value[2].Randomize(params, source) 39 | basis.Value[3].Randomize(params, source) 40 | basis.Value[4].Randomize(params, source) 41 | basis.Value[8].Randomize(params, source) 42 | 43 | buffer.RequireSerializerCorrect(t, basis) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /he/types.go: -------------------------------------------------------------------------------- 1 | package he 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Pro7ech/lattigo/utils/bignum" 7 | ) 8 | 9 | type Integer interface { 10 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | big.Int 11 | } 12 | 13 | type Float interface { 14 | ~float32 | ~float64 | big.Float 15 | } 16 | 17 | type Complex interface { 18 | ~complex64 | ~complex128 | bignum.Complex 19 | } 20 | -------------------------------------------------------------------------------- /lattigo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package lattigo is the open-source community-version of Tune Insight's Homomorphic Encryption library. 3 | It provides a pure Go implementation of state-of-the-art Homomorphic Encryption (HE) and Multiparty Homomorphic 4 | Encryption (MHE) schemes, enabling code-simplicity, cross-platform compatibility and easy builds, while retaining 5 | the same performance as C++ libraries. 6 | */ 7 | package lattigo 8 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pro7ech/lattigo/cf329e68cfab1d4c3dac5a423598a42aa63b5105/logo.png -------------------------------------------------------------------------------- /mhe/additive_shares.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // AdditiveShare is a type for storing additively shared values in Z_Q[X] (RNS domain). 8 | type AdditiveShare struct { 9 | Value []uint64 10 | } 11 | 12 | // AdditiveShareBigint is a type for storing additively shared values 13 | // in Z (positional domain). 14 | type AdditiveShareBigint struct { 15 | Value []big.Int 16 | } 17 | 18 | // NewAdditiveShare instantiates a new additive share struct for the ring defined 19 | // by the given parameters at maximum level. 20 | func NewAdditiveShare(N int) *AdditiveShare { 21 | return &AdditiveShare{Value: make([]uint64, N)} 22 | } 23 | 24 | // NewAdditiveShareBigint instantiates a new additive share struct composed of n big.Int elements. 25 | func NewAdditiveShareBigint(n int) *AdditiveShareBigint { 26 | return &AdditiveShareBigint{Value: make([]big.Int, n)} 27 | } 28 | -------------------------------------------------------------------------------- /mhe/evaluation_key.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Pro7ech/lattigo/rlwe" 7 | ) 8 | 9 | // EvaluationKeyProtocol is the structure storing the parameters for the collective EvaluationKey generation. 10 | type EvaluationKeyProtocol struct { 11 | GadgetCiphertextProtocol 12 | } 13 | 14 | // ShallowCopy creates a shallow copy of the receiver in which all the read-only data-structures are 15 | // shared with the receiver and the temporary buffers are reallocated. The receiver and the returned 16 | // object can be used concurrently. 17 | func (p EvaluationKeyProtocol) ShallowCopy() *EvaluationKeyProtocol { 18 | return &EvaluationKeyProtocol{*p.GadgetCiphertextProtocol.ShallowCopy()} 19 | } 20 | 21 | // NewEvaluationKeyProtocol instantiates a new [mhe.EvaluationKeyProtocol]. 22 | func NewEvaluationKeyProtocol(params rlwe.ParameterProvider) (p *EvaluationKeyProtocol) { 23 | return &EvaluationKeyProtocol{*NewGadgetCiphertextProtocol(params)} 24 | } 25 | 26 | // Allocate allocates a party's share in the [mhe.EvaluationKeyProtocol]. 27 | func (p EvaluationKeyProtocol) Allocate(evkParams ...rlwe.EvaluationKeyParameters) *EvaluationKeyShare { 28 | params := *p.GetRLWEParameters() 29 | LevelQ, LevelP, dd := rlwe.ResolveEvaluationKeyParameters(params, evkParams) 30 | return &EvaluationKeyShare{GadgetCiphertextShare: *p.GadgetCiphertextProtocol.Allocate(LevelQ, LevelP, dd)} 31 | } 32 | 33 | // Gen generates a party's share in the [mhe.EvaluationKeyProtocol]. 34 | func (p EvaluationKeyProtocol) Gen(skIn, skOut *rlwe.SecretKey, seed [32]byte, share *EvaluationKeyShare) (err error) { 35 | return p.GadgetCiphertextProtocol.Gen(skOut, skIn.AsPlaintext(), seed, &share.GadgetCiphertextShare) 36 | } 37 | 38 | // Aggregate sets share3 to share1 + share2. 39 | func (p EvaluationKeyProtocol) Aggregate(share1, share2, share3 *EvaluationKeyShare) (err error) { 40 | return share3.Aggregate(p.GetRLWEParameters(), share1, share2) 41 | } 42 | 43 | // Finalize finalizes the protocol and populates the input computed collective [rlwe.EvaluationKey]. 44 | func (p EvaluationKeyProtocol) Finalize(share *EvaluationKeyShare, evk *rlwe.EvaluationKey) (err error) { 45 | return share.Get(p.GetRLWEParameters(), evk) 46 | } 47 | 48 | // FinalizeNew finalizes the protocol and returns the computed collective [rlwe.EvaluationKey]. 49 | func (p EvaluationKeyProtocol) FinalizeNew(share *EvaluationKeyShare) (evk *rlwe.EvaluationKey) { 50 | return share.GetNew(p.GetRLWEParameters()) 51 | } 52 | 53 | type EvaluationKeyShare struct { 54 | GadgetCiphertextShare 55 | } 56 | 57 | // Equal performs a deep equal between the receiver and the operand. 58 | func (share EvaluationKeyShare) Equal(other *EvaluationKeyShare) bool { 59 | return share.GadgetCiphertextShare.Equal(&other.GadgetCiphertextShare) 60 | } 61 | 62 | // Aggregate sets the receiver to a + b. 63 | func (share *EvaluationKeyShare) Aggregate(params rlwe.ParameterProvider, a, b *EvaluationKeyShare) (err error) { 64 | 65 | if a.Seed != b.Seed { 66 | return fmt.Errorf("shares seed do not match") 67 | } 68 | 69 | if a.DigitDecomposition != b.DigitDecomposition { 70 | return fmt.Errorf("shares digit decomposition do not match") 71 | } 72 | 73 | share.Seed = a.Seed 74 | share.DigitDecomposition = a.DigitDecomposition 75 | 76 | p := params.GetRLWEParameters() 77 | 78 | return share.Vector[0].Aggregate(p.RingQ(), p.RingP(), &a.Vector[0], &b.Vector[0]) 79 | } 80 | 81 | // Get copies the data of the receiver on a pre-allocated [rlwe.EvaluationKey]. 82 | func (share EvaluationKeyShare) Get(params rlwe.ParameterProvider, evk *rlwe.EvaluationKey) (err error) { 83 | return share.GadgetCiphertextShare.Get(params, &evk.GadgetCiphertext) 84 | } 85 | 86 | // AsEvaluationKey wraps the receiver into an [rlwe.EvaluationKey]. 87 | func (share EvaluationKeyShare) AsEvaluationKey(params rlwe.ParameterProvider) (evk *rlwe.EvaluationKey) { 88 | return &rlwe.EvaluationKey{GadgetCiphertext: *share.GadgetCiphertextShare.AsGadgetCiphertext(params)} 89 | } 90 | 91 | // GetNew copies the data of the receiver on a new [rlwe.EvaluationKey]. 92 | func (share EvaluationKeyShare) GetNew(params rlwe.ParameterProvider) (evk *rlwe.EvaluationKey) { 93 | return &rlwe.EvaluationKey{GadgetCiphertext: *share.GadgetCiphertextShare.GetNew(params)} 94 | } 95 | -------------------------------------------------------------------------------- /mhe/mhe.go: -------------------------------------------------------------------------------- 1 | // Package mhe implements RLWE-based scheme agnostic multiparty key-generation and proxy re-rencryption. 2 | // See README.md for more details about multiparty homomorphic encryption. 3 | package mhe 4 | -------------------------------------------------------------------------------- /mhe/mhefloat/mhefloat.go: -------------------------------------------------------------------------------- 1 | // Package mhefloat implements homomorphic decryption to Linear-Secret-Shared-Shares (LSSS) 2 | // and homomorphic re-encryption from LSSS, as well as interactive bootstrapping for the package `he/hefloat` 3 | // See `mhe/README.md` for additional information on multiparty schemes. 4 | package mhefloat 5 | -------------------------------------------------------------------------------- /mhe/mhefloat/refresh.go: -------------------------------------------------------------------------------- 1 | package mhefloat 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/he/hefloat" 5 | "github.com/Pro7ech/lattigo/mhe" 6 | 7 | "github.com/Pro7ech/lattigo/rlwe" 8 | ) 9 | 10 | // RefreshProtocol is a struct storing the relevant parameters for the Refresh protocol. 11 | type RefreshProtocol struct { 12 | MaskedTransformProtocol 13 | } 14 | 15 | // NewRefreshProtocol creates a new Refresh protocol instance. 16 | // prec : the log2 of decimal precision of the internal encoder. 17 | func NewRefreshProtocol(params hefloat.Parameters, prec uint) *RefreshProtocol { 18 | return &RefreshProtocol{*NewMaskedTransformProtocol(params, params, prec)} 19 | } 20 | 21 | // ShallowCopy creates a shallow copy of RefreshProtocol in which all the read-only data-structures are 22 | // shared with the receiver and the temporary buffers are reallocated. The receiver and the returned 23 | // RefreshProtocol can be used concurrently. 24 | func (rfp RefreshProtocol) ShallowCopy() *RefreshProtocol { 25 | return &RefreshProtocol{*rfp.MaskedTransformProtocol.ShallowCopy()} 26 | } 27 | 28 | // Allocate allocates the shares of the PermuteProtocol 29 | func (rfp RefreshProtocol) Allocate(inputLevel, outputLevel int) *mhe.RefreshShare { 30 | return rfp.MaskedTransformProtocol.Allocate(inputLevel, outputLevel) 31 | } 32 | 33 | // Gen generates a share for the Refresh protocol. 34 | // This protocol requires additional inputs which are : 35 | // logBound : the bit length of the masks 36 | // ct1 : the degree 1 element the ciphertext to refresh, i.e. ct1 = ckk.Ciphetext.Value[1]. 37 | // scale : the scale of the ciphertext entering the refresh. 38 | // The method "GetMinimumLevelForBootstrapping" should be used to get the minimum level at which the refresh can be called while still ensure 128-bits of security, as well as the 39 | // value for logBound. 40 | func (rfp RefreshProtocol) Gen(sk *rlwe.SecretKey, logBound uint, ct *rlwe.Ciphertext, seed [32]byte, shareOut *mhe.RefreshShare) (err error) { 41 | return rfp.MaskedTransformProtocol.Gen(sk, sk, logBound, ct, seed, nil, shareOut) 42 | } 43 | 44 | // Aggregate aggregates two parties' shares in the Refresh protocol. 45 | func (rfp RefreshProtocol) Aggregate(share1, share2, shareOut *mhe.RefreshShare) (err error) { 46 | return rfp.MaskedTransformProtocol.Aggregate(share1, share2, shareOut) 47 | } 48 | 49 | // Finalize applies Decrypt, Recode and Recrypt on the input ciphertext. 50 | // The ciphertext scale is reset to the default scale. 51 | func (rfp RefreshProtocol) Finalize(ctIn *rlwe.Ciphertext, share *mhe.RefreshShare, opOut *rlwe.Ciphertext) (err error) { 52 | return rfp.MaskedTransformProtocol.Finalize(ctIn, nil, share, opOut) 53 | } 54 | -------------------------------------------------------------------------------- /mhe/mhefloat/test_params.go: -------------------------------------------------------------------------------- 1 | package mhefloat 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/he/hefloat" 5 | ) 6 | 7 | var ( 8 | 9 | // testInsecurePrec45 are insecure parameters used for the sole purpose of fast testing. 10 | testInsecurePrec45 = hefloat.ParametersLiteral{ 11 | LogN: 10, 12 | Q: []uint64{ 13 | 0x80000000080001, 14 | 0x2000000a0001, 15 | 0x2000000e0001, 16 | 0x2000001d0001, 17 | 0x1fffffcf0001, 18 | 0x1fffffc20001, 19 | 0x200000440001, 20 | }, 21 | P: []uint64{ 22 | 0x80000000130001, 23 | 0x7fffffffe90001, 24 | }, 25 | LogDefaultScale: 45, 26 | } 27 | 28 | // testInsecurePrec90 are insecure parameters used for the sole purpose of fast testing. 29 | testInsecurePrec90 = hefloat.ParametersLiteral{ 30 | LogN: 10, 31 | Q: []uint64{ 32 | 0x80000000080001, 33 | 0x80000000440001, 34 | 0x2000000a0001, 35 | 0x2000000e0001, 36 | 0x1fffffc20001, 37 | 0x200000440001, 38 | 0x200000500001, 39 | 0x200000620001, 40 | 0x1fffff980001, 41 | 0x2000006a0001, 42 | 0x1fffff7e0001, 43 | 0x200000860001, 44 | }, 45 | P: []uint64{ 46 | 0xffffffffffc0001, 47 | 0x10000000006e0001, 48 | }, 49 | LogDefaultScale: 90, 50 | } 51 | 52 | testParamsLiteral = []hefloat.ParametersLiteral{testInsecurePrec45, testInsecurePrec90} 53 | ) 54 | -------------------------------------------------------------------------------- /mhe/mhefloat/utils.go: -------------------------------------------------------------------------------- 1 | package mhefloat 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/Pro7ech/lattigo/rlwe" 7 | ) 8 | 9 | // GetMinimumLevelForRefresh takes the security parameter lambda, the ciphertext scale, the number of parties and the moduli chain 10 | // and returns the minimum level at which the collective refresh can be called with a security of at least 128-bits. 11 | // It returns 3 parameters : 12 | // minLevel : the minimum level at which the collective refresh must be called to ensure correctness 13 | // logBound : the bit length of the masks to be sampled to mask the plaintext and ensure 128-bits of statistical indistinguishability 14 | // ok : a boolean flag, which is set to false if no such instance exist 15 | func GetMinimumLevelForRefresh(lambda int, scale rlwe.Scale, nParties int, moduli []uint64) (minLevel int, logBound uint, ok bool) { 16 | logBound = uint(lambda + int(math.Ceil(math.Log2(scale.Float64())))) 17 | maxBound := math.Ceil(float64(logBound) + math.Log2(float64(nParties))) 18 | 19 | minLevel = -1 20 | logQ := 0.0 21 | 22 | for i := 0; logQ < maxBound; i++ { 23 | 24 | if i >= len(moduli) { 25 | return 0, 0, false 26 | } 27 | 28 | logQ += math.Log2(float64(moduli[i])) 29 | minLevel++ 30 | } 31 | 32 | if len(moduli) < minLevel { 33 | return 0, 0, false 34 | } 35 | 36 | return minLevel, logBound, true 37 | } 38 | -------------------------------------------------------------------------------- /mhe/mheint/mheint.go: -------------------------------------------------------------------------------- 1 | // Package mheint implements homomorphic decryption to Linear-Secret-Shared-Shares (LSSS) 2 | // and homomorphic re-encryption from LSSS, as well as interactive bootstrapping for the package `he/heint` 3 | // See `mhe/README.md` for additional information on multiparty schemes. 4 | package mheint 5 | -------------------------------------------------------------------------------- /mhe/mheint/mheint_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package mheint 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/Pro7ech/lattigo/he/heint" 8 | "github.com/Pro7ech/lattigo/mhe" 9 | "github.com/Pro7ech/lattigo/rlwe" 10 | ) 11 | 12 | func BenchmarkInteger(b *testing.B) { 13 | 14 | var err error 15 | 16 | paramsLiterals := testParams 17 | 18 | if *flagParamString != "" { 19 | var jsonParams heint.ParametersLiteral 20 | if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil { 21 | b.Fatal(err) 22 | } 23 | paramsLiterals = []heint.ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag 24 | } 25 | 26 | for _, p := range paramsLiterals { 27 | 28 | for _, T := range testPlaintextModulus[:] { 29 | 30 | p.T = T 31 | p.R = 1 32 | 33 | var params heint.Parameters 34 | if params, err = heint.NewParametersFromLiteral(p); err != nil { 35 | b.Fatal(err) 36 | } 37 | 38 | nParties := 3 39 | 40 | var tc *testContext 41 | if tc, err = gentestContext(nParties, params); err != nil { 42 | b.Fatal(err) 43 | } 44 | 45 | benchRefresh(tc, b) 46 | } 47 | } 48 | } 49 | 50 | func benchRefresh(tc *testContext, b *testing.B) { 51 | 52 | skShares := tc.skShares 53 | 54 | minLevel := 0 55 | maxLevel := tc.params.MaxLevel() 56 | 57 | type Party struct { 58 | RefreshProtocol 59 | s *rlwe.SecretKey 60 | share *mhe.RefreshShare 61 | } 62 | 63 | p := new(Party) 64 | p.RefreshProtocol = *NewRefreshProtocol(tc.params) 65 | p.s = skShares[0] 66 | p.share = p.Allocate(minLevel, maxLevel) 67 | 68 | ciphertext := heint.NewCiphertext(tc.params, 1, minLevel) 69 | 70 | seed := [32]byte{} 71 | 72 | b.Run(GetTestName("Refresh/Round1/Gen", tc.params, tc.NParties), func(b *testing.B) { 73 | 74 | for i := 0; i < b.N; i++ { 75 | p.Gen(p.s, ciphertext, seed, p.share) 76 | } 77 | }) 78 | 79 | b.Run(GetTestName("Refresh/Round1/Agg", tc.params, tc.NParties), func(b *testing.B) { 80 | 81 | for i := 0; i < b.N; i++ { 82 | p.Aggregate(p.share, p.share, p.share) 83 | } 84 | }) 85 | 86 | b.Run(GetTestName("Refresh/Finalize", tc.params, tc.NParties), func(b *testing.B) { 87 | opOut := heint.NewCiphertext(tc.params, 1, maxLevel) 88 | for i := 0; i < b.N; i++ { 89 | p.Finalize(ciphertext, p.share, opOut) 90 | } 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /mhe/mheint/refresh.go: -------------------------------------------------------------------------------- 1 | package mheint 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/he/heint" 5 | "github.com/Pro7ech/lattigo/mhe" 6 | 7 | "github.com/Pro7ech/lattigo/rlwe" 8 | ) 9 | 10 | // RefreshProtocol is a struct storing the relevant parameters for the Refresh protocol. 11 | type RefreshProtocol struct { 12 | MaskedTransformProtocol 13 | } 14 | 15 | // ShallowCopy creates a shallow copy of RefreshProtocol in which all the read-only data-structures are 16 | // shared with the receiver and the temporary buffers are reallocated. The receiver and the returned 17 | // RefreshProtocol can be used concurrently. 18 | func (rfp RefreshProtocol) ShallowCopy() *RefreshProtocol { 19 | return &RefreshProtocol{*rfp.MaskedTransformProtocol.ShallowCopy()} 20 | } 21 | 22 | // NewRefreshProtocol creates a new Refresh protocol instance. 23 | func NewRefreshProtocol(params heint.Parameters) *RefreshProtocol { 24 | return &RefreshProtocol{*NewMaskedTransformProtocol(params, params)} 25 | } 26 | 27 | // Allocate allocates the shares of the PermuteProtocol 28 | func (rfp RefreshProtocol) Allocate(inputLevel, outputLevel int) *mhe.RefreshShare { 29 | return rfp.MaskedTransformProtocol.Allocate(inputLevel, outputLevel) 30 | } 31 | 32 | // Gen generates a share for the Refresh protocol. 33 | // ct1 is degree 1 element of a rlwe.Ciphertext, i.e. rlwe.Ciphertext.Value[1]. 34 | func (rfp RefreshProtocol) Gen(sk *rlwe.SecretKey, ct *rlwe.Ciphertext, seed [32]byte, share *mhe.RefreshShare) (err error) { 35 | return rfp.MaskedTransformProtocol.Gen(sk, sk, ct, seed, nil, share) 36 | } 37 | 38 | // Aggregate aggregates two parties' shares in the Refresh protocol. 39 | func (rfp RefreshProtocol) Aggregate(share1, share2, share3 *mhe.RefreshShare) (err error) { 40 | return rfp.MaskedTransformProtocol.Aggregate(share1, share2, share3) 41 | } 42 | 43 | // Finalize applies Decrypt, Recode and Recrypt on the input ciphertext. 44 | func (rfp RefreshProtocol) Finalize(ctIn *rlwe.Ciphertext, share *mhe.RefreshShare, opOut *rlwe.Ciphertext) (err error) { 45 | return rfp.MaskedTransformProtocol.Finalize(ctIn, nil, share, opOut) 46 | } 47 | -------------------------------------------------------------------------------- /mhe/mheint/test_parameters.go: -------------------------------------------------------------------------------- 1 | package mheint 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/he/heint" 5 | ) 6 | 7 | var ( 8 | 9 | // testInsecure are insecure parameters used for the sole purpose of fast testing. 10 | testInsecure = heint.ParametersLiteral{ 11 | LogN: 10, 12 | Q: []uint64{0x3fffffa8001, 0x1000090001, 0x10000c8001, 0x10000f0001, 0xffff00001}, 13 | P: []uint64{0x7fffffd8001}, 14 | } 15 | 16 | testPlaintextModulus = []uint64{0x101, 0xffc001} 17 | 18 | testParams = []heint.ParametersLiteral{testInsecure} 19 | ) 20 | -------------------------------------------------------------------------------- /mhe/public_key.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/ring" 5 | "github.com/Pro7ech/lattigo/rlwe" 6 | "github.com/Pro7ech/lattigo/utils/sampling" 7 | ) 8 | 9 | // PublicKeyProtocol is the structure storing the parameters and and precomputations for 10 | // the collective encryption key generation protocol. 11 | type PublicKeyProtocol struct { 12 | *rlwe.Encryptor 13 | } 14 | 15 | type PublicKeyShare struct { 16 | VectorShare 17 | } 18 | 19 | // NewPublicKeyProtocol creates a new PublicKeyProtocol instance 20 | func NewPublicKeyProtocol(params rlwe.ParameterProvider) *PublicKeyProtocol { 21 | return &PublicKeyProtocol{rlwe.NewEncryptor(params, nil)} 22 | } 23 | 24 | // ShallowCopy creates a shallow copy of the receiver in which all the read-only data-structures are 25 | // shared with the receiver and the temporary buffers are reallocated. The receiver and the returned 26 | // struct can be used concurrently. 27 | func (p PublicKeyProtocol) ShallowCopy() *PublicKeyProtocol { 28 | return &PublicKeyProtocol{p.Encryptor.ShallowCopy()} 29 | } 30 | 31 | // Allocate allocates the share of the PublicKeyGen protocol. 32 | func (p PublicKeyProtocol) Allocate() (pkg *PublicKeyShare) { 33 | params := p.GetRLWEParameters() 34 | return &PublicKeyShare{VectorShare: *NewVectorShare(params, params.MaxLevelQ(), params.MaxLevelP(), 2)} 35 | } 36 | 37 | // Gen generates the party's public key share from its secret key as: 38 | // 39 | // a[seeded]*s_i + e_i 40 | // 41 | // for the receiver protocol. Has no effect is the share was already generated. 42 | func (p PublicKeyProtocol) Gen(sk *rlwe.SecretKey, seed [32]byte, share *PublicKeyShare) (err error) { 43 | share.Seed = seed 44 | ct := &rlwe.Ciphertext{} 45 | ct.Vector = &share.Vector 46 | ct.MetaData = &rlwe.MetaData{IsNTT: true, IsMontgomery: true} 47 | return p.WithKey(sk).WithSeededPublicRandomness(seed).EncryptZero(ct) 48 | } 49 | 50 | // Aggregate evalutes share3 = share1 + share2 51 | func (p PublicKeyProtocol) Aggregate(share1, share2, share3 *PublicKeyShare) (err error) { 52 | return share3.Aggregate(p.GetRLWEParameters(), &share1.VectorShare, &share2.VectorShare) 53 | } 54 | 55 | // Finalize return the current aggregation of the received shares as a bfv.PublicKey. 56 | func (p PublicKeyProtocol) Finalize(share *PublicKeyShare, pubkey *rlwe.PublicKey) (err error) { 57 | params := p.GetRLWEParameters() 58 | pubkey.Q[0].Copy(&share.Q[0]) 59 | point := ring.Point{Q: pubkey.Q[1]} 60 | if share.LevelP() > -1 { 61 | pubkey.P[0].Copy(&share.P[0]) 62 | point.P = pubkey.P[1] 63 | } 64 | point.Randomize(params.RingQ(), params.RingP(), sampling.NewSource(share.Seed)) 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /mhe/refresh.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | 7 | "github.com/Pro7ech/lattigo/rlwe" 8 | "github.com/Pro7ech/lattigo/utils/buffer" 9 | ) 10 | 11 | // RefreshShare is a struct storing the decryption and recryption shares. 12 | type RefreshShare struct { 13 | MetaData rlwe.MetaData 14 | EncToShareShare KeySwitchingShare 15 | ShareToEncShare KeySwitchingShare 16 | } 17 | 18 | func (share RefreshShare) Equal(other *RefreshShare) bool { 19 | return share.MetaData.Equal(&other.MetaData) && share.EncToShareShare.Equal(&other.EncToShareShare) && share.ShareToEncShare.Equal(&other.ShareToEncShare) 20 | } 21 | 22 | // BinarySize returns the serialized size of the object in bytes. 23 | func (share RefreshShare) BinarySize() int { 24 | return share.MetaData.BinarySize() + share.EncToShareShare.BinarySize() + share.ShareToEncShare.BinarySize() 25 | } 26 | 27 | // WriteTo writes the object on an io.Writer. It implements the io.WriterTo 28 | // interface, and will write exactly object.BinarySize() bytes on w. 29 | // 30 | // Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), 31 | // it will be wrapped into a bufio.Writer. Since this requires allocations, it 32 | // is preferable to pass a buffer.Writer directly: 33 | // 34 | // - When writing multiple times to a io.Writer, it is preferable to first wrap the 35 | // io.Writer in a pre-allocated bufio.Writer. 36 | // - When writing to a pre-allocated var b []byte, it is preferable to pass 37 | // buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). 38 | func (share RefreshShare) WriteTo(w io.Writer) (n int64, err error) { 39 | switch w := w.(type) { 40 | case buffer.Writer: 41 | 42 | var inc int64 43 | 44 | if inc, err = share.MetaData.WriteTo(w); err != nil { 45 | return n + inc, err 46 | } 47 | 48 | n += inc 49 | 50 | if inc, err = share.EncToShareShare.WriteTo(w); err != nil { 51 | return n + inc, err 52 | } 53 | 54 | n += inc 55 | 56 | inc, err = share.ShareToEncShare.WriteTo(w) 57 | 58 | return n + inc, err 59 | default: 60 | return share.WriteTo(bufio.NewWriter(w)) 61 | } 62 | } 63 | 64 | // ReadFrom reads on the object from an io.Writer. It implements the 65 | // io.ReaderFrom interface. 66 | // 67 | // Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), 68 | // it will be wrapped into a bufio.Reader. Since this requires allocation, it 69 | // is preferable to pass a buffer.Reader directly: 70 | // 71 | // - When reading multiple values from a io.Reader, it is preferable to first 72 | // first wrap io.Reader in a pre-allocated bufio.Reader. 73 | // - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) 74 | // as w (see lattigo/utils/buffer/buffer.go). 75 | func (share *RefreshShare) ReadFrom(r io.Reader) (n int64, err error) { 76 | switch r := r.(type) { 77 | case buffer.Reader: 78 | 79 | var inc int64 80 | 81 | if inc, err = share.MetaData.ReadFrom(r); err != nil { 82 | return n + inc, err 83 | } 84 | 85 | n += inc 86 | 87 | if inc, err = share.EncToShareShare.ReadFrom(r); err != nil { 88 | return n + inc, err 89 | } 90 | 91 | n += inc 92 | 93 | inc, err = share.ShareToEncShare.ReadFrom(r) 94 | 95 | return n + inc, err 96 | default: 97 | return share.ReadFrom(bufio.NewReader(r)) 98 | } 99 | } 100 | 101 | // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. 102 | func (share RefreshShare) MarshalBinary() (p []byte, err error) { 103 | buf := buffer.NewBufferSize(share.BinarySize()) 104 | _, err = share.WriteTo(buf) 105 | return buf.Bytes(), err 106 | } 107 | 108 | // UnmarshalBinary decodes a slice of bytes generated by 109 | // MarshalBinary or WriteTo on the object. 110 | func (share *RefreshShare) UnmarshalBinary(p []byte) (err error) { 111 | _, err = share.ReadFrom(buffer.NewBuffer(p)) 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /mhe/relinearization_key.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/rlwe" 5 | ) 6 | 7 | // RelinearizationKeyProtocol is the structure storing the parameters and 8 | // precomputations for the collective relinearization key generation protocol. 9 | // The protocol is based on https://eprint.iacr.org/2021/1085. 10 | type RelinearizationKeyProtocol struct { 11 | *rlwe.Encryptor 12 | } 13 | 14 | // RelinearizationKeyShare is a share in the [mhe.RelinearizationKeyProtocol]. 15 | type RelinearizationKeyShare struct { 16 | rlwe.GadgetCiphertext 17 | } 18 | 19 | // NewRelinearizationKeyProtocol creates a new [mhe.RelinearizationKeyProtocol] struct. 20 | func NewRelinearizationKeyProtocol(params rlwe.ParameterProvider) *RelinearizationKeyProtocol { 21 | return &RelinearizationKeyProtocol{rlwe.NewEncryptor(params, nil)} 22 | } 23 | 24 | // ShallowCopy creates a shallow copy of the receiver in which all the read-only data-structures are 25 | // shared with the receiver and the temporary buffers are reallocated. The receiver and the returned 26 | // object can be used concurrently. 27 | func (p *RelinearizationKeyProtocol) ShallowCopy() *RelinearizationKeyProtocol { 28 | return &RelinearizationKeyProtocol{p.Encryptor.ShallowCopy()} 29 | } 30 | 31 | // Gen generates a party's share in the RelinearizationKey Generation Protocol. 32 | func (p RelinearizationKeyProtocol) Gen(sk *rlwe.SecretKey, pk *rlwe.PublicKey, share *RelinearizationKeyShare) (err error) { 33 | 34 | if err = p.WithKey(pk).EncryptZero(&share.GadgetCiphertext); err != nil { 35 | return 36 | } 37 | 38 | params := p.GetRLWEParameters() 39 | 40 | return rlwe.AddPlaintextToMatrix(params.RingQ(), params.RingP(), sk.Q, p.BuffQ[0], share.Vector[1], share.DigitDecomposition) 41 | } 42 | 43 | // Aggregate sets share3 to share1 + share2. 44 | func (p RelinearizationKeyProtocol) Aggregate(share1, share2, share3 *RelinearizationKeyShare) (err error) { 45 | params := p.GetRLWEParameters() 46 | if err = share3.Vector[0].Aggregate(params.RingQ(), params.RingP(), &share1.Vector[0], &share2.Vector[0]); err != nil { 47 | return 48 | } 49 | return share3.Vector[1].Aggregate(params.RingQ(), params.RingP(), &share1.Vector[1], &share2.Vector[1]) 50 | } 51 | 52 | // Finalize finalizes the protocol and populates the input computed collective RelinearizationKey. 53 | func (p RelinearizationKeyProtocol) Finalize(share *RelinearizationKeyShare, evk *rlwe.RelinearizationKey) (err error) { 54 | evk.Copy(share.Vector) 55 | return 56 | } 57 | 58 | // Allocate allocates the share of the [mhe.RelinearizationKeyProtocol]. 59 | func (p RelinearizationKeyProtocol) Allocate(evkParams ...rlwe.EvaluationKeyParameters) *RelinearizationKeyShare { 60 | params := *p.GetRLWEParameters() 61 | LevelQ, LevelP, dd := rlwe.ResolveEvaluationKeyParameters(params, evkParams) 62 | return &RelinearizationKeyShare{GadgetCiphertext: *rlwe.NewGadgetCiphertext(params, 1, LevelQ, LevelP, dd)} 63 | } 64 | -------------------------------------------------------------------------------- /mhe/shares.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/Pro7ech/lattigo/ring" 9 | "github.com/Pro7ech/lattigo/rlwe" 10 | "github.com/Pro7ech/lattigo/utils/buffer" 11 | ) 12 | 13 | // VectorShare is a struct storing the PublicKeyGen protocol's share. 14 | type VectorShare struct { 15 | Seed [32]byte 16 | ring.Vector 17 | } 18 | 19 | func NewVectorShare(params rlwe.ParameterProvider, LevelQ, LevelP, size int) *VectorShare { 20 | p := params.GetRLWEParameters() 21 | return &VectorShare{Vector: *ring.NewVector(p.N(), LevelQ, LevelP, size)} 22 | } 23 | 24 | func (share VectorShare) Aggregate(params rlwe.ParameterProvider, share1, share2 *VectorShare) (err error) { 25 | 26 | if share1.Seed != share2.Seed { 27 | return fmt.Errorf("shares seed do not match") 28 | } 29 | p := params.GetRLWEParameters() 30 | return share.Vector.Aggregate(p.RingQ(), p.RingP(), &share1.Vector, &share2.Vector) 31 | } 32 | 33 | // Equal performs a deep equal between the receiver and the operand. 34 | func (share VectorShare) Equal(other *VectorShare) bool { 35 | return share.Seed == other.Seed && share.Vector.Equal(&other.Vector) 36 | } 37 | 38 | // BinarySize returns the serialized size of the object in bytes. 39 | func (share VectorShare) BinarySize() (size int) { 40 | return 32 + share.Vector.BinarySize() 41 | } 42 | 43 | // WriteTo writes the object on an io.Writer. It implements the io.WriterTo 44 | // interface, and will write exactly object.BinarySize() bytes on w. 45 | // 46 | // Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), 47 | // it will be wrapped into a bufio.Writer. Since this requires allocations, it 48 | // is preferable to pass a buffer.Writer directly: 49 | // 50 | // - When writing multiple times to a io.Writer, it is preferable to first wrap the 51 | // io.Writer in a pre-allocated bufio.Writer. 52 | // - When writing to a pre-allocated var b []byte, it is preferable to pass 53 | // buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). 54 | func (share VectorShare) WriteTo(w io.Writer) (n int64, err error) { 55 | switch w := w.(type) { 56 | case buffer.Writer: 57 | 58 | var inc int64 59 | 60 | if inc, err = buffer.Write(w, share.Seed[:]); err != nil { 61 | return n + inc, err 62 | } 63 | 64 | n += inc 65 | 66 | if inc, err = share.Vector.WriteTo(w); err != nil { 67 | return 68 | } 69 | 70 | return n + inc, w.Flush() 71 | default: 72 | return share.WriteTo(bufio.NewWriter(w)) 73 | } 74 | } 75 | 76 | // ReadFrom reads on the object from an io.Writer. It implements the 77 | // io.ReaderFrom interface. 78 | // 79 | // Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), 80 | // it will be wrapped into a bufio.Reader. Since this requires allocation, it 81 | // is preferable to pass a buffer.Reader directly: 82 | // 83 | // - When reading multiple values from a io.Reader, it is preferable to first 84 | // first wrap io.Reader in a pre-allocated bufio.Reader. 85 | // - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) 86 | // as w (see lattigo/utils/buffer/buffer.go). 87 | func (share *VectorShare) ReadFrom(r io.Reader) (n int64, err error) { 88 | switch r := r.(type) { 89 | case buffer.Reader: 90 | 91 | var inc int64 92 | 93 | if inc, err = buffer.Read(r, share.Seed[:]); err != nil { 94 | return n + inc, err 95 | } 96 | 97 | n += inc 98 | 99 | if inc, err = share.Vector.ReadFrom(r); err != nil { 100 | return n + inc, err 101 | } 102 | 103 | return n + inc, nil 104 | default: 105 | return share.ReadFrom(bufio.NewReader(r)) 106 | } 107 | } 108 | 109 | // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. 110 | func (share VectorShare) MarshalBinary() (p []byte, err error) { 111 | buf := buffer.NewBufferSize(share.BinarySize()) 112 | _, err = share.WriteTo(buf) 113 | return buf.Bytes(), err 114 | } 115 | 116 | // UnmarshalBinary decodes a slice of bytes generated by 117 | // MarshalBinary or WriteTo on the object. 118 | func (share *VectorShare) UnmarshalBinary(p []byte) (err error) { 119 | _, err = share.ReadFrom(buffer.NewBuffer(p)) 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /mhe/test_params.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/rlwe" 5 | ) 6 | 7 | type TestParametersLiteral struct { 8 | rlwe.DigitDecomposition 9 | rlwe.ParametersLiteral 10 | } 11 | 12 | var ( 13 | logN = 11 14 | logQ = []int{45, 45, 45, 45, 45} 15 | logP = []int{58, 58} 16 | 17 | // testInsecure are insecure parameters used for the sole purpose of fast testing. 18 | testInsecure = []TestParametersLiteral{ 19 | // RNS decomposition, no Pw2 decomposition 20 | { 21 | DigitDecomposition: rlwe.DigitDecomposition{}, 22 | 23 | ParametersLiteral: rlwe.ParametersLiteral{ 24 | LogN: logN, 25 | LogQ: logQ, 26 | LogP: logP, 27 | NTTFlag: true, 28 | }, 29 | }, 30 | // RNS decomposition, Pw2 decomposition 31 | { 32 | DigitDecomposition: rlwe.DigitDecomposition{ 33 | Type: rlwe.Unsigned, 34 | Log2Basis: 16, 35 | }, 36 | 37 | ParametersLiteral: rlwe.ParametersLiteral{ 38 | LogN: logN, 39 | LogQ: logQ, 40 | LogP: logP[:1], 41 | NTTFlag: true, 42 | }, 43 | }, 44 | // No RNS decomposition, Pw2 decomposition 45 | { 46 | DigitDecomposition: rlwe.DigitDecomposition{ 47 | Type: rlwe.SignedBalanced, 48 | Log2Basis: 2, 49 | }, 50 | 51 | ParametersLiteral: rlwe.ParametersLiteral{ 52 | LogN: logN, 53 | LogQ: logQ[:1], 54 | LogP: nil, 55 | NTTFlag: true, 56 | }, 57 | }, 58 | } 59 | ) 60 | -------------------------------------------------------------------------------- /mhe/utils.go: -------------------------------------------------------------------------------- 1 | package mhe 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/Pro7ech/lattigo/rlwe" 7 | ) 8 | 9 | // NoiseRelinearizationKey returns the standard deviation of the noise of each individual elements in the collective RelinearizationKey. 10 | func NoiseRelinearizationKey(params rlwe.Parameters, nbParties int) (std float64) { 11 | 12 | // rlk noise = [s*e0 + u*e1 + e2 + e3] 13 | // 14 | // s = sum(s_i) 15 | // u = sum(u_i) 16 | // e0 = sum(e_i0) 17 | // e1 = sum(e_i1) 18 | // e2 = sum(e_i2) 19 | // e3 = sum(e_i3) 20 | 21 | H := float64(nbParties * params.XsHammingWeight()) // var(sk) and var(u) 22 | e := float64(nbParties) * params.NoiseFreshSK() * params.NoiseFreshSK() // var(e0), var(e1), var(e2), var(e3) 23 | 24 | // var([s*e0 + u*e1 + e2 + e3]) = H*e + H*e + e + e = e(2H+2) = 2e(H+1) 25 | return math.Sqrt(2 * e * (H + 1)) 26 | } 27 | 28 | func NoiseCircularCiphertext(params rlwe.Parameters, hasP bool, d, Log2Basis, nbParties int) (std float64) { 29 | 30 | B := math.Exp2(float64(Log2Basis)) 31 | n := float64(nbParties) 32 | N := float64(params.N()) 33 | Xs := float64(params.XsHammingWeight()+1) / N 34 | Xe := params.NoiseFreshSK() * params.NoiseFreshSK() 35 | 36 | var noiseKS float64 37 | if hasP { 38 | noiseKS = 1 / (n * 12.0) 39 | } else { 40 | noiseKS = float64(d) * B * B / (n * 12) 41 | } 42 | 43 | return math.Sqrt(N * n * n * Xe * (2*Xs + noiseKS)) 44 | } 45 | 46 | // NoiseGadgetCiphertext returns the standard deviation of the noise of each individual elements in a gadget ciphertext 47 | // encrypted with the collective key. 48 | func NoiseGadgetCiphertext(params rlwe.Parameters, nbParties int) (std float64) { 49 | return math.Sqrt(float64(nbParties)) * params.NoiseFreshSK() 50 | } 51 | 52 | // NoiseEvaluationKey returns the standard deviation of the noise of each individual elements in a collective EvaluationKey. 53 | func NoiseEvaluationKey(params rlwe.Parameters, nbParties int) (std float64) { 54 | return NoiseGadgetCiphertext(params, nbParties) 55 | } 56 | 57 | // NoiseGaloisKey returns the standard deviation of the noise of each individual elements in a collective GaloisKey. 58 | func NoiseGaloisKey(params rlwe.Parameters, nbParties int) (std float64) { 59 | return NoiseEvaluationKey(params, nbParties) 60 | } 61 | 62 | func NoiseKeySwitch(nbParties int, noisect, noisefresh, noiseflood float64) (std float64) { 63 | std = noisefresh * noisefresh 64 | std += noiseflood * noiseflood 65 | std *= float64(nbParties) 66 | std += noisect * noisect 67 | return math.Sqrt(std) 68 | } 69 | -------------------------------------------------------------------------------- /rgsw/encryptor.go: -------------------------------------------------------------------------------- 1 | package rgsw 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Pro7ech/lattigo/ring" 7 | "github.com/Pro7ech/lattigo/rlwe" 8 | ) 9 | 10 | // Encryptor is a type for encrypting RGSW ciphertexts. It implements the rlwe.Encryptor 11 | // interface overriding the `Encrypt` and `EncryptZero` methods to accept rgsw.Ciphertext 12 | // types in addition to ciphertexts types in the rlwe package. 13 | type Encryptor struct { 14 | *rlwe.Encryptor 15 | } 16 | 17 | // NewEncryptor creates a new Encryptor type. Note that only secret-key encryption is 18 | // supported at the moment. 19 | func NewEncryptor(params rlwe.ParameterProvider, key rlwe.EncryptionKey) *Encryptor { 20 | return &Encryptor{rlwe.NewEncryptor(params, key)} 21 | } 22 | 23 | // Encrypt encrypts a plaintext pt into an [rgsw.Ciphertext]. 24 | func (enc Encryptor) Encrypt(pt *rlwe.Plaintext, ct *Ciphertext) (err error) { 25 | 26 | if err = enc.EncryptZero(ct); err != nil { 27 | return 28 | } 29 | 30 | if pt != nil { 31 | 32 | params := enc.GetRLWEParameters() 33 | 34 | levelQ := ct.LevelQ() 35 | rQ := params.RingQ().AtLevel(levelQ) 36 | 37 | if pt.Level() < ct.LevelQ() { 38 | return fmt.Errorf("invalid [%T]: [%T].Level() < [%T].LevelQ()", pt, pt, ct) 39 | } 40 | 41 | var ptTmp ring.RNSPoly 42 | 43 | if !pt.IsNTT { 44 | 45 | ptTmp = enc.BuffQ[0] 46 | 47 | rQ.NTT(pt.Q, ptTmp) 48 | 49 | if !pt.IsMontgomery { 50 | rQ.MForm(ptTmp, ptTmp) 51 | } 52 | 53 | } else { 54 | 55 | if !pt.IsMontgomery { 56 | ptTmp = enc.BuffQ[0] 57 | rQ.MForm(pt.Q, ptTmp) 58 | } else { 59 | ptTmp = pt.Q 60 | } 61 | } 62 | 63 | if err := rlwe.AddPlaintextToMatrix(rQ, params.RingP(), ptTmp, enc.BuffQ[1], ct.Matrix[0][0], ct.DigitDecomposition); err != nil { 64 | // Sanity check, this error should not happen. 65 | panic(err) 66 | } 67 | 68 | if err := rlwe.AddPlaintextToMatrix(rQ, params.RingP(), ptTmp, enc.BuffQ[1], ct.Matrix[1][1], ct.DigitDecomposition); err != nil { 69 | // Sanity check, this error should not happen. 70 | panic(err) 71 | } 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // EncryptZero generates an [rgsw.Ciphertext] encrypting zero. 78 | func (enc Encryptor) EncryptZero(ct *Ciphertext) (err error) { 79 | 80 | if err = enc.Encryptor.EncryptZero(ct.At(0)); err != nil { 81 | return 82 | } 83 | 84 | if err = enc.Encryptor.EncryptZero(ct.At(1)); err != nil { 85 | return 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // ShallowCopy creates a shallow copy of this Encryptor in which all the read-only data-structures are 92 | // shared with the receiver and the temporary buffers are reallocated. The receiver and the returned 93 | // Encryptors can be used concurrently. 94 | func (enc Encryptor) ShallowCopy() *Encryptor { 95 | return &Encryptor{Encryptor: enc.Encryptor.ShallowCopy()} 96 | } 97 | -------------------------------------------------------------------------------- /rgsw/test_parameters.go: -------------------------------------------------------------------------------- 1 | package rgsw 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/rlwe" 5 | ) 6 | 7 | type TestParametersLiteral struct { 8 | rlwe.DigitDecomposition 9 | rlwe.ParametersLiteral 10 | } 11 | 12 | var ( 13 | logN = 10 14 | logQ = []int{45, 35, 35} 15 | logP = []int{50, 50} 16 | 17 | // testInsecure are insecure parameters used for the sole purpose of fast testing. 18 | testInsecure = []TestParametersLiteral{ 19 | // RNS decomposition, no Pw2 decomposition 20 | { 21 | DigitDecomposition: rlwe.DigitDecomposition{}, 22 | 23 | ParametersLiteral: rlwe.ParametersLiteral{ 24 | LogN: logN, 25 | LogQ: logQ, 26 | LogP: logP, 27 | NTTFlag: true, 28 | }, 29 | }, 30 | // RNS decomposition, Pw2 decomposition 31 | { 32 | DigitDecomposition: rlwe.DigitDecomposition{ 33 | Type: rlwe.SignedBalanced, 34 | Log2Basis: 16, 35 | }, 36 | 37 | ParametersLiteral: rlwe.ParametersLiteral{ 38 | LogN: logN, 39 | LogQ: logQ, 40 | LogP: logP[:1], 41 | NTTFlag: true, 42 | }, 43 | }, 44 | // No RNS decomposition, Pw2 decomposition 45 | { 46 | DigitDecomposition: rlwe.DigitDecomposition{ 47 | Type: rlwe.Unsigned, 48 | Log2Basis: 2, 49 | }, 50 | 51 | ParametersLiteral: rlwe.ParametersLiteral{ 52 | LogN: logN, 53 | LogQ: logQ[:1], 54 | LogP: nil, 55 | NTTFlag: true, 56 | }, 57 | }, 58 | } 59 | ) 60 | -------------------------------------------------------------------------------- /rgsw/utils.go: -------------------------------------------------------------------------------- 1 | package rgsw 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/ring" 5 | "github.com/Pro7ech/lattigo/rlwe" 6 | ) 7 | 8 | // NoiseCiphertext returns the base two logarithm of the standard deviation of the noise of each component of an [rgsw.Ciphertext]. 9 | // pt must be in the NTT and Montgomery domain 10 | func NoiseCiphertext(ct *Ciphertext, pt ring.RNSPoly, sk *rlwe.SecretKey, params rlwe.Parameters) (float64, float64) { 11 | ptsk := *pt.Clone() 12 | params.RingQ().AtLevel(ct.LevelQ()).MulCoeffsMontgomery(ptsk, sk.Q, ptsk) 13 | return rlwe.NoiseGadgetCiphertext(ct.At(0), pt, sk, params), rlwe.NoiseGadgetCiphertext(ct.At(1), ptsk, sk, params) 14 | } 15 | -------------------------------------------------------------------------------- /ring/README.md: -------------------------------------------------------------------------------- 1 | ## References 2 | 3 | 1. Faster arithmetic for number-theoretic transforms () 4 | 2. Speeding up the Number Theoretic Transform for Faster Ideal Lattice-Based Cryptography () 5 | 3. Post-quantum key exchange - a new hope () -------------------------------------------------------------------------------- /ring/interpolation_test.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestInterpolation(t *testing.T) { 10 | t.Run("Roots", func(t *testing.T) { 11 | var T uint64 = 65537 12 | roots := make([]uint64, 22) 13 | for i := range roots { 14 | roots[i] = uint64(i) 15 | } 16 | 17 | itp, err := NewInterpolator(len(roots), T) 18 | assert.Nil(t, err) 19 | 20 | coeffs := itp.Interpolate(roots) 21 | for _, alpha := range roots { 22 | assert.Equal(t, uint64(0), EvalPolyModP(alpha, coeffs, T)) 23 | } 24 | }) 25 | 26 | t.Run("Lagrange", func(t *testing.T) { 27 | var T uint64 = 65537 28 | n := 512 29 | x := make([]uint64, n+1) 30 | y := make([]uint64, n+1) 31 | 32 | for i := 0; i < n>>1; i++ { 33 | x[i] = T - uint64(n>>1-i) 34 | y[i] = 0 35 | } 36 | 37 | y[n>>1] = 1 38 | 39 | for i := 1; i < n>>1+1; i++ { 40 | x[i+n>>1] = uint64(i) 41 | y[i+n>>1] = 0 42 | } 43 | 44 | itp, err := NewInterpolator(len(x), T) 45 | assert.Nil(t, err) 46 | 47 | coeffs, err := itp.Lagrange(x, y) 48 | assert.Nil(t, err) 49 | 50 | for i := range x { 51 | assert.Equal(t, y[i], EvalPolyModP(x[i], coeffs, T)) 52 | } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /ring/ntt.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | const ( 4 | // MinimumRingDegreeForLoopUnrolledNTT is the minimum ring degree 5 | // necessary for memory safe loop unrolling 6 | MinimumRingDegreeForLoopUnrolledNTT = 16 7 | ) 8 | 9 | // NumberTheoreticTransformer is an interface to provide 10 | // flexibility on what type of NTT is used by the struct Ring. 11 | type NumberTheoreticTransformer interface { 12 | Forward(p1, p2 []uint64) 13 | ForwardLazy(p1, p2 []uint64) 14 | Backward(p1, p2 []uint64) 15 | BackwardLazy(p1, p2 []uint64) 16 | } 17 | 18 | type numberTheoreticTransformerBase struct { 19 | *NTTTable 20 | N int 21 | Modulus uint64 22 | MRedConstant uint64 23 | BRedConstant [2]uint64 24 | } 25 | 26 | // NumberTheoreticTransformerStandard computes the standard nega-cyclic NTT in the ring Z[X]/(X^N+1). 27 | type NumberTheoreticTransformerStandard struct { 28 | numberTheoreticTransformerBase 29 | } 30 | 31 | // NTTTable store all the constants that are specifically tied to the NTT. 32 | type NTTTable struct { 33 | NthRoot uint64 // Nthroot used for the NTT 34 | PrimitiveRoot uint64 // 2N-th primitive root 35 | RootsForward []uint64 //powers of the 2N-th primitive root in Montgomery form (in bit-reversed order) 36 | RootsBackward []uint64 //powers of the inverse of the 2N-th primitive root in Montgomery form (in bit-reversed order) 37 | NInv uint64 //[N^-1] mod Modulus in Montgomery form 38 | } 39 | 40 | // NTT evaluates p2 = NTT(p1). 41 | func (r Ring) NTT(p1, p2 []uint64) { 42 | r.Forward(p1, p2) 43 | } 44 | 45 | // NTTLazy evaluates p2 = NTT(p1) with p2 in [0, 2*modulus-1]. 46 | func (r Ring) NTTLazy(p1, p2 []uint64) { 47 | r.ForwardLazy(p1, p2) 48 | } 49 | 50 | // INTT evaluates p2 = INTT(p1). 51 | func (r Ring) INTT(p1, p2 []uint64) { 52 | r.Backward(p1, p2) 53 | } 54 | 55 | // INTTLazy evaluates p2 = INTT(p1) with p2 in [0, 2*modulus-1]. 56 | func (r Ring) INTTLazy(p1, p2 []uint64) { 57 | r.BackwardLazy(p1, p2) 58 | } 59 | -------------------------------------------------------------------------------- /ring/ntt_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Pro7ech/lattigo/utils/sampling" 8 | ) 9 | 10 | func BenchmarkNTT(b *testing.B) { 11 | benchNTT(10, 1, b) 12 | benchNTT(11, 1, b) 13 | benchNTT(12, 1, b) 14 | benchNTT(13, 1, b) 15 | benchNTT(14, 1, b) 16 | benchNTT(15, 1, b) 17 | benchNTT(16, 1, b) 18 | benchINTT(10, 1, b) 19 | benchINTT(11, 1, b) 20 | benchINTT(12, 1, b) 21 | benchINTT(13, 1, b) 22 | benchINTT(14, 1, b) 23 | benchINTT(15, 1, b) 24 | benchINTT(16, 1, b) 25 | } 26 | 27 | func benchNTT(LogN, Qi int, b *testing.B) { 28 | b.Run(fmt.Sprintf("Forward/N=%d/Qi=%d", 1< X^{i*gen} on a polynomial in the NTT domain. 9 | // It must be noted that the result cannot be in-place. 10 | func (r RNSRing) AutomorphismNTT(polIn RNSPoly, gen uint64, polOut RNSPoly) { 11 | index, err := AutomorphismNTTIndex(r.N(), r.NthRoot(), gen) 12 | // Sanity check, this error should not happen. 13 | if err != nil { 14 | panic(err) 15 | } 16 | r.AutomorphismNTTWithIndex(polIn, index, polOut) 17 | } 18 | 19 | // AutomorphismNTTWithIndex applies the automorphism X^{i} -> X^{i*gen} on a polynomial in the NTT domain. 20 | // `index` is the lookup table storing the mapping of the automorphism. 21 | // It must be noted that the result cannot be in-place. 22 | func (r RNSRing) AutomorphismNTTWithIndex(polIn RNSPoly, index []uint64, polOut RNSPoly) { 23 | 24 | level := r.Level() 25 | 26 | N := r.N() 27 | 28 | for j := 0; j < N; j = j + 8 { 29 | 30 | /* #nosec G103 -- behavior and consequences well understood, possible buffer overflow if len(index)%8 != 0 */ 31 | x := (*[8]uint64)(unsafe.Pointer(&index[j])) 32 | 33 | for i := 0; i < level+1; i++ { 34 | 35 | /* #nosec G103 -- behavior and consequences well understood, possible buffer overflow if len(polOut.Coeffs)%8 != 0 */ 36 | z := (*[8]uint64)(unsafe.Pointer(&polOut.At(i)[j])) 37 | y := polIn.At(i) 38 | 39 | z[0] = y[x[0]] 40 | z[1] = y[x[1]] 41 | z[2] = y[x[2]] 42 | z[3] = y[x[3]] 43 | z[4] = y[x[4]] 44 | z[5] = y[x[5]] 45 | z[6] = y[x[6]] 46 | z[7] = y[x[7]] 47 | } 48 | } 49 | } 50 | 51 | // AutomorphismNTTWithIndexThenAddLazy applies the automorphism X^{i} -> X^{i*gen} on a polynomial in the NTT domain . 52 | // `index` is the lookup table storing the mapping of the automorphism. 53 | // The result of the automorphism is added on polOut. 54 | func (r RNSRing) AutomorphismNTTWithIndexThenAddLazy(polIn RNSPoly, index []uint64, polOut RNSPoly) { 55 | 56 | level := r.Level() 57 | 58 | N := r.N() 59 | 60 | for j := 0; j < N; j = j + 8 { 61 | 62 | /* #nosec G103 -- behavior and consequences well understood, possible buffer overflow if len(index)%8 != 0 */ 63 | x := (*[8]uint64)(unsafe.Pointer(&index[j])) 64 | 65 | for i := 0; i < level+1; i++ { 66 | 67 | /* #nosec G103 -- behavior and consequences well understood, possible buffer overflow if len(polOut.Coeffs)%8 != 0 */ 68 | z := (*[8]uint64)(unsafe.Pointer(&polOut.At(i)[j])) 69 | y := polIn.At(i) 70 | 71 | z[0] += y[x[0]] 72 | z[1] += y[x[1]] 73 | z[2] += y[x[2]] 74 | z[3] += y[x[3]] 75 | z[4] += y[x[4]] 76 | z[5] += y[x[5]] 77 | z[6] += y[x[6]] 78 | z[7] += y[x[7]] 79 | } 80 | } 81 | } 82 | 83 | // Automorphism applies the automorphism X^{i} -> X^{i*gen} on a polynomial outside of the NTT domain. 84 | // It must be noted that the result cannot be in-place. 85 | func (r RNSRing) Automorphism(polIn RNSPoly, gen uint64, polOut RNSPoly) { 86 | 87 | var mask, index, indexRaw, logN, tmp uint64 88 | 89 | N := uint64(r.N()) 90 | 91 | if r.Type() == ConjugateInvariant { 92 | 93 | mask = 2*N - 1 94 | 95 | logN = uint64(bits.Len64(mask)) 96 | 97 | // TODO: find a more efficient way to do 98 | // the automorphism on Z[X+X^-1] 99 | for i := uint64(0); i < 2*N; i++ { 100 | 101 | indexRaw = i * gen 102 | 103 | index = indexRaw & mask 104 | 105 | tmp = (indexRaw >> logN) & 1 106 | 107 | // Only consider i -> index if within [0, N-1] 108 | if index < N { 109 | 110 | idx := i 111 | 112 | // If the starting index is within [N, 2N-1] 113 | if idx >= N { 114 | idx = 2*N - idx // Wrap back between [0, N-1] 115 | tmp ^= 1 // Negate 116 | } 117 | 118 | for j, s := range r { 119 | polOut.At(j)[index] = polIn.At(j)[idx]*(tmp^1) | (s.Modulus-polIn.At(j)[idx])*tmp 120 | } 121 | } 122 | } 123 | 124 | } else { 125 | 126 | mask = N - 1 127 | 128 | logN = uint64(bits.Len64(mask)) 129 | 130 | for i := uint64(0); i < N; i++ { 131 | 132 | indexRaw = i * gen 133 | 134 | index = indexRaw & mask 135 | 136 | tmp = (indexRaw >> logN) & 1 137 | 138 | for j, s := range r { 139 | polOut.At(j)[index] = polIn.At(j)[i]*(tmp^1) | (s.Modulus-polIn.At(j)[i])*tmp 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ring/rns_conjugate_invariant.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | // UnfoldConjugateInvariantToStandard maps the compressed representation (N/2 coefficients) 4 | // of Z_Q[X+X^-1]/(X^2N + 1) to full representation in Z_Q[X]/(X^2N+1). 5 | // Requires degree(polyConjugateInvariant) = 2*degree(polyStandard). 6 | // Requires that polyStandard and polyConjugateInvariant share the same moduli. 7 | func (r RNSRing) UnfoldConjugateInvariantToStandard(polyConjugateInvariant, polyStandard RNSPoly) { 8 | 9 | // Sanity check 10 | if 2*polyConjugateInvariant.N() != polyStandard.N() { 11 | panic("cannot UnfoldConjugateInvariantToStandard: Ring degree of polyConjugateInvariant must be twice the ring degree of polyStandard") 12 | } 13 | 14 | N := polyConjugateInvariant.N() 15 | 16 | for i := range r { 17 | tmp2, tmp1 := polyStandard.At(i), polyConjugateInvariant.At(i) 18 | copy(tmp2, tmp1) 19 | for idx, jdx := N-1, N; jdx < 2*N; idx, jdx = idx-1, jdx+1 { 20 | tmp2[jdx] = tmp1[idx] 21 | } 22 | } 23 | } 24 | 25 | // FoldStandardToConjugateInvariant folds [X]/(X^N+1) to [X+X^-1]/(X^N+1) in compressed form (N/2 coefficients). 26 | // Requires degree(polyConjugateInvariant) = 2*degree(polyStandard). 27 | // Requires that polyStandard and polyConjugateInvariant share the same moduli. 28 | func (r RNSRing) FoldStandardToConjugateInvariant(polyStandard RNSPoly, permuteNTTIndexInv []uint64, polyConjugateInvariant RNSPoly) { 29 | 30 | // Sanity check 31 | if polyStandard.N() != 2*polyConjugateInvariant.N() { 32 | panic("cannot FoldStandardToConjugateInvariant: Ring degree of polyStandard must be 2N and ring degree of polyConjugateInvariant must be N") 33 | } 34 | 35 | N := r.N() 36 | 37 | r.AutomorphismNTTWithIndex(polyStandard, permuteNTTIndexInv, polyConjugateInvariant) 38 | 39 | for i, s := range r { 40 | s.Add(polyConjugateInvariant.At(i)[:N], polyStandard.At(i)[:N], polyConjugateInvariant.At(i)[:N]) 41 | } 42 | } 43 | 44 | // PadDefaultRingToConjugateInvariant converts a polynomial in Z[X]/(X^N +1) to a polynomial in Z[X+X^-1]/(X^2N+1). 45 | func (r RNSRing) PadDefaultRingToConjugateInvariant(polyStandard RNSPoly, IsNTT bool, polyConjugateInvariant RNSPoly) { 46 | 47 | // Sanity check 48 | if polyConjugateInvariant.N() != 2*polyStandard.N() { 49 | panic("cannot PadDefaultRingToConjugateInvariant: polyConjugateInvariant degree must be twice the one of polyStandard") 50 | } 51 | 52 | N := polyStandard.N() 53 | 54 | for i := range r { 55 | qi := r[i].Modulus 56 | 57 | copy(polyConjugateInvariant.At(i), polyStandard.At(i)) 58 | 59 | tmp := polyConjugateInvariant.At(i) 60 | if IsNTT { 61 | for j := 0; j < N; j++ { 62 | tmp[N-j-1] = tmp[j] 63 | } 64 | } else { 65 | tmp[0] = 0 66 | for j := 1; j < N; j++ { 67 | tmp[N-j] = qi - tmp[j] 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ring/rns_ntt.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | // NTT evaluates p2 = NTT(P1). 4 | func (r RNSRing) NTT(p1, p2 RNSPoly) { 5 | for i, s := range r { 6 | s.NTT(p1.At(i), p2.At(i)) 7 | } 8 | } 9 | 10 | // NTTLazy evaluates p2 = NTT(p1) with p2 in [0, 2*modulus-1]. 11 | func (r RNSRing) NTTLazy(p1, p2 RNSPoly) { 12 | for i, s := range r { 13 | s.NTTLazy(p1.At(i), p2.At(i)) 14 | } 15 | } 16 | 17 | // INTT evaluates p2 = INTT(p1). 18 | func (r RNSRing) INTT(p1, p2 RNSPoly) { 19 | for i, s := range r { 20 | s.INTT(p1.At(i), p2.At(i)) 21 | } 22 | } 23 | 24 | // INTTLazy evaluates p2 = INTT(p1) with p2 in [0, 2*modulus-1]. 25 | func (r RNSRing) INTTLazy(p1, p2 RNSPoly) { 26 | for i, s := range r { 27 | s.INTTLazy(p1.At(i), p2.At(i)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ring/rns_sampler.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Pro7ech/lattigo/utils/sampling" 7 | ) 8 | 9 | // Sampler is an interface for random polynomial samplers. 10 | // It has a single Read method which takes as argument the polynomial to be 11 | // populated according to the Sampler's distribution. 12 | type Sampler interface { 13 | GetSource() *sampling.Source 14 | Read(pol RNSPoly) 15 | ReadNew(N int) (pol RNSPoly) 16 | ReadAndAdd(pol RNSPoly) 17 | AtLevel(level int) Sampler 18 | WithSource(source *sampling.Source) Sampler 19 | } 20 | 21 | // NewSampler instantiates a new [Sampler] interface from the provided [rand.Source], 22 | // modulic chain and [DistributionParameters]. 23 | func NewSampler(source *sampling.Source, moduli []uint64, X DistributionParameters) (Sampler, error) { 24 | switch X := X.(type) { 25 | case *DiscreteGaussian: 26 | return NewGaussianSampler(source, moduli, *X), nil 27 | case *Ternary: 28 | return NewTernarySampler(source, moduli, *X) 29 | case *Uniform: 30 | return NewUniformSampler(source, moduli), nil 31 | default: 32 | return nil, fmt.Errorf("invalid distribution: want ring.DiscreteGaussianDistribution, ring.TernaryDistribution or ring.UniformDistribution but have %T", X) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ring/rns_sampler_uniform.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | import ( 4 | "math/bits" 5 | 6 | "github.com/Pro7ech/lattigo/utils/sampling" 7 | ) 8 | 9 | // UniformSampler wraps a util.PRNG and represents 10 | // the state of a sampler of uniform polynomials. 11 | type UniformSampler struct { 12 | Moduli []uint64 13 | *sampling.Source 14 | } 15 | 16 | // NewUniformSampler creates a new instance of UniformSampler from a 17 | // [sampling.Source] and a list of moduli. 18 | func NewUniformSampler(source *sampling.Source, moduli []uint64) (u *UniformSampler) { 19 | u = new(UniformSampler) 20 | u.Moduli = moduli 21 | u.Source = source 22 | return 23 | } 24 | 25 | // GetSource returns the underlying [sampling.Source] used by the sampler. 26 | func (u UniformSampler) GetSource() *sampling.Source { 27 | return u.Source 28 | } 29 | 30 | // WithSource returns an instance of the underlying sampler with 31 | // a new [sampling.Source]. 32 | // It can be used concurrently with the original sampler. 33 | func (u UniformSampler) WithSource(source *sampling.Source) Sampler { 34 | return &UniformSampler{ 35 | Moduli: u.Moduli, 36 | Source: source, 37 | } 38 | } 39 | 40 | // AtLevel returns an instance of the target UniformSampler to sample at the given level. 41 | // The returned sampler cannot be used concurrently to the original sampler. 42 | func (u UniformSampler) AtLevel(level int) Sampler { 43 | return &UniformSampler{ 44 | Moduli: u.Moduli[:level+1], 45 | Source: u.Source, 46 | } 47 | } 48 | 49 | func (u *UniformSampler) Read(pol RNSPoly) { 50 | u.read(pol, func(a, b, c uint64) uint64 { 51 | return b 52 | }) 53 | } 54 | 55 | func (u *UniformSampler) ReadAndAdd(pol RNSPoly) { 56 | u.read(pol, func(a, b, c uint64) uint64 { 57 | return CRed(a+b, c) 58 | }) 59 | } 60 | 61 | func (u *UniformSampler) read(pol RNSPoly, f func(a, b, c uint64) uint64) { 62 | 63 | var c, mask uint64 64 | 65 | r := u.Source 66 | 67 | for j, qi := range u.Moduli { 68 | 69 | mask = (1 << uint64(bits.Len64(qi-1))) - 1 70 | 71 | coeffs := pol.At(j) 72 | 73 | for i := range coeffs { 74 | 75 | c = r.Uint64() & mask 76 | 77 | for c >= qi { 78 | c = r.Uint64() & mask 79 | } 80 | 81 | coeffs[i] = f(coeffs[i], c, qi) 82 | } 83 | } 84 | } 85 | 86 | // ReadNew generates a new polynomial with coefficients following a uniform distribution over [0, Qi-1]. 87 | // Polynomial is created at the max level. 88 | func (u *UniformSampler) ReadNew(N int) (pol RNSPoly) { 89 | pol = NewRNSPoly(N, len(u.Moduli)-1) 90 | u.Read(pol) 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /ring/rns_scalar.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // RNSScalar represents a scalar value in the Ring (i.e., a degree-0 polynomial) in RNS form. 8 | type RNSScalar []uint64 9 | 10 | // NewRNSScalar creates a new Scalar value. 11 | func (r RNSRing) NewRNSScalar() RNSScalar { 12 | return make(RNSScalar, r.ModuliChainLength()) 13 | } 14 | 15 | // NewRNSScalarFromUInt64 creates a new Scalar initialized with value v. 16 | func (r RNSRing) NewRNSScalarFromUInt64(v uint64) (rns RNSScalar) { 17 | rns = make(RNSScalar, r.ModuliChainLength()) 18 | for i, s := range r { 19 | rns[i] = v % s.Modulus 20 | } 21 | return rns 22 | } 23 | 24 | // NewRNSScalarFromBigint creates a new Scalar initialized with value v. 25 | func (r RNSRing) NewRNSScalarFromBigint(v *big.Int) (rns RNSScalar) { 26 | rns = make(RNSScalar, r.ModuliChainLength()) 27 | tmp0 := new(big.Int) 28 | tmp1 := new(big.Int) 29 | for i, s := range r { 30 | rns[i] = tmp0.Mod(v, tmp1.SetUint64(s.Modulus)).Uint64() 31 | } 32 | return rns 33 | } 34 | 35 | // MFormRNSScalar switches an RNS scalar to the Montgomery domain. 36 | // s2 = s1<<64 mod Q 37 | func (r RNSRing) MFormRNSScalar(s1, s2 RNSScalar) { 38 | for i, s := range r { 39 | s2[i] = MForm(s1[i], s.Modulus, s.BRedConstant) 40 | } 41 | } 42 | 43 | // NegRNSScalar evaluates s2 = -s1. 44 | func (r RNSRing) NegRNSScalar(s1, s2 RNSScalar) { 45 | for i, s := range r { 46 | s2[i] = s.Modulus - s1[i] 47 | } 48 | } 49 | 50 | // SubRNSScalar subtracts s2 to s1 and stores the result in sout. 51 | func (r RNSRing) SubRNSScalar(s1, s2, sout RNSScalar) { 52 | for i, s := range r { 53 | if s2[i] > s1[i] { 54 | sout[i] = s1[i] + s.Modulus - s2[i] 55 | } else { 56 | sout[i] = s1[i] - s2[i] 57 | } 58 | } 59 | } 60 | 61 | // MulRNSScalar multiplies s1 and s2 and stores the result in sout. 62 | // Multiplication is operated with Montgomery. 63 | func (r RNSRing) MulRNSScalar(s1, s2, sout RNSScalar) { 64 | for i, s := range r { 65 | sout[i] = MRedLazy(s1[i], s2[i], s.Modulus, s.MRedConstant) 66 | } 67 | } 68 | 69 | // Inverse computes the modular inverse of a scalar a expressed in a CRT decomposition. 70 | // The inversion is done in-place and assumes that a is in Montgomery form. 71 | func (r RNSRing) Inverse(a RNSScalar) { 72 | for i, s := range r { 73 | a[i] = ModExpMontgomery(a[i], s.Modulus-2, s.Modulus, s.MRedConstant, s.BRedConstant) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ring/test_params.go: -------------------------------------------------------------------------------- 1 | package ring 2 | 3 | // Parameters is a struct storing test parameters for the package Ring. 4 | type Parameters struct { 5 | logN uint64 6 | qi []uint64 7 | pi []uint64 8 | } 9 | 10 | var testParameters = []Parameters{ 11 | {14, Qi60[:12], Pi60[:3]}, 12 | } 13 | 14 | // Qi60 are the first [0:32] 61-bit close to 2^{62} NTT-friendly primes for N up to 2^{17} 15 | var Qi60 = []uint64{0x1fffffffffe00001, 0x1fffffffffc80001, 0x1fffffffffb40001, 0x1fffffffff500001, 16 | 0x1fffffffff380001, 0x1fffffffff000001, 0x1ffffffffef00001, 0x1ffffffffee80001, 17 | 0x1ffffffffeb40001, 0x1ffffffffe780001, 0x1ffffffffe600001, 0x1ffffffffe4c0001, 18 | 0x1ffffffffdf40001, 0x1ffffffffdac0001, 0x1ffffffffda40001, 0x1ffffffffc680001, 19 | 0x1ffffffffc000001, 0x1ffffffffb880001, 0x1ffffffffb7c0001, 0x1ffffffffb300001, 20 | 0x1ffffffffb1c0001, 0x1ffffffffadc0001, 0x1ffffffffa400001, 0x1ffffffffa140001, 21 | 0x1ffffffff9d80001, 0x1ffffffff9140001, 0x1ffffffff8ac0001, 0x1ffffffff8a80001, 22 | 0x1ffffffff81c0001, 0x1ffffffff7800001, 0x1ffffffff7680001, 0x1ffffffff7080001} 23 | 24 | // Pi60 are the next [32:64] 61-bit close to 2^{62} NTT-friendly primes for N up to 2^{17} 25 | var Pi60 = []uint64{0x1ffffffff6c80001, 0x1ffffffff6140001, 0x1ffffffff5f40001, 0x1ffffffff5700001, 26 | 0x1ffffffff4bc0001, 0x1ffffffff4380001, 0x1ffffffff3240001, 0x1ffffffff2dc0001, 27 | 0x1ffffffff1a40001, 0x1ffffffff11c0001, 0x1ffffffff0fc0001, 0x1ffffffff0d80001, 28 | 0x1ffffffff0c80001, 0x1ffffffff08c0001, 0x1fffffffefd00001, 0x1fffffffef9c0001, 29 | 0x1fffffffef600001, 0x1fffffffeef40001, 0x1fffffffeed40001, 0x1fffffffeed00001, 30 | 0x1fffffffeebc0001, 0x1fffffffed540001, 0x1fffffffed440001, 0x1fffffffed2c0001, 31 | 0x1fffffffed200001, 0x1fffffffec940001, 0x1fffffffec6c0001, 0x1fffffffebe80001, 32 | 0x1fffffffebac0001, 0x1fffffffeba40001, 0x1fffffffeb4c0001, 0x1fffffffeb280001} 33 | -------------------------------------------------------------------------------- /rlwe/README.md: -------------------------------------------------------------------------------- 1 | ## References 2 | 3 | 1. Somewhat Practical Fully Homomorphic Encryption () 4 | 2. Fully Homomorphic Encryption without Bootstrapping () 5 | 3. Efficient Homomorphic Conversion Between (Ring) LWE Ciphertexts () 6 | 4. HERMES: Efficient Ring Packing using MLWE Ciphertexts and Application to Transciphering () -------------------------------------------------------------------------------- /rlwe/compression.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | 7 | "github.com/Pro7ech/lattigo/utils/buffer" 8 | "github.com/Pro7ech/lattigo/utils/structs" 9 | ) 10 | 11 | // CompressionInfos is a struct storing information to be able 12 | // to reconstruct public random polynomial from a seed. 13 | // [rlwe.Ciphertext] or [rlwe.GadgetCiphertext] with compression infos 14 | // have a serialization size that is half of their usual size. 15 | type CompressionInfos struct { 16 | Qi structs.Vector[uint64] 17 | Pi structs.Vector[uint64] 18 | Seed [32]byte 19 | } 20 | 21 | // BinarySize returns the serialized size of the object in bytes. 22 | func (c CompressionInfos) BinarySize() (size int) { 23 | return c.Qi.BinarySize() + c.Pi.BinarySize() + 32 24 | } 25 | 26 | // WriteTo writes the object on an io.Writer. It implements the io.WriterTo 27 | // interface, and will write exactly object.BinarySize() bytes on w. 28 | // 29 | // Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), 30 | // it will be wrapped into a bufio.Writer. Since this requires allocations, it 31 | // is preferable to pass a buffer.Writer directly: 32 | // 33 | // - When writing multiple times to a io.Writer, it is preferable to first wrap the 34 | // io.Writer in a pre-allocated bufio.Writer. 35 | // - When writing to a pre-allocated var b []byte, it is preferable to pass 36 | // buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). 37 | func (c CompressionInfos) WriteTo(w io.Writer) (n int64, err error) { 38 | 39 | switch w := w.(type) { 40 | case buffer.Writer: 41 | 42 | var inc int64 43 | 44 | if inc, err = c.Qi.WriteTo(w); err != nil { 45 | return n + inc, err 46 | } 47 | 48 | n += inc 49 | 50 | if inc, err = c.Pi.WriteTo(w); err != nil { 51 | return n + inc, err 52 | } 53 | 54 | n += inc 55 | 56 | if inc, err = buffer.Write(w, c.Seed[:]); err != nil { 57 | return n + inc, err 58 | } 59 | 60 | return n + inc, err 61 | 62 | default: 63 | return c.WriteTo(bufio.NewWriter(w)) 64 | } 65 | } 66 | 67 | // ReadFrom reads on the object from an io.Writer. It implements the 68 | // io.ReaderFrom interface. 69 | // 70 | // Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), 71 | // it will be wrapped into a bufio.Reader. Since this requires allocation, it 72 | // is preferable to pass a buffer.Reader directly: 73 | // 74 | // - When reading multiple values from a io.Reader, it is preferable to first 75 | // first wrap io.Reader in a pre-allocated bufio.Reader. 76 | // - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) 77 | // as w (see lattigo/utils/buffer/buffer.go). 78 | func (c *CompressionInfos) ReadFrom(r io.Reader) (n int64, err error) { 79 | switch r := r.(type) { 80 | case buffer.Reader: 81 | 82 | var inc int64 83 | 84 | if inc, err = c.Qi.ReadFrom(r); err != nil { 85 | return n + inc, err 86 | } 87 | 88 | n += inc 89 | 90 | if inc, err = c.Pi.ReadFrom(r); err != nil { 91 | return n + inc, err 92 | } 93 | 94 | n += inc 95 | 96 | if inc, err = buffer.Read(r, c.Seed[:]); err != nil { 97 | return n + inc, err 98 | } 99 | 100 | return n + inc, err 101 | 102 | default: 103 | return c.ReadFrom(bufio.NewReader(r)) 104 | } 105 | } 106 | 107 | // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. 108 | func (c CompressionInfos) MarshalBinary() (data []byte, err error) { 109 | buf := buffer.NewBufferSize(c.BinarySize()) 110 | _, err = c.WriteTo(buf) 111 | return buf.Bytes(), err 112 | } 113 | 114 | // UnmarshalBinary decodes a slice of bytes generated by 115 | // MarshalBinary or WriteTo on the object. 116 | func (c *CompressionInfos) UnmarshalBinary(p []byte) (err error) { 117 | _, err = c.ReadFrom(buffer.NewBuffer(p)) 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /rlwe/decryptor.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Pro7ech/lattigo/ring" 7 | ) 8 | 9 | // Decryptor is a structure used to decrypt [rlwe.Ciphertext]. 10 | // It stores the secret-key. 11 | type Decryptor struct { 12 | params Parameters 13 | buff ring.RNSPoly 14 | sk *SecretKey 15 | } 16 | 17 | // NewDecryptor instantiates a new [rlwe.Decryptor]. 18 | func NewDecryptor(params ParameterProvider, sk *SecretKey) *Decryptor { 19 | 20 | p := params.GetRLWEParameters() 21 | 22 | if sk != nil && sk.N() != p.N() { 23 | panic(fmt.Errorf("secret_key ring degree does not match parameters ring degree")) 24 | } 25 | 26 | return &Decryptor{ 27 | params: *p, 28 | buff: p.RingQ().NewRNSPoly(), 29 | sk: sk, 30 | } 31 | } 32 | 33 | // GetRLWEParameters returns the underlying [rlwe.Parameters] of the receiver.. 34 | func (d Decryptor) GetRLWEParameters() *Parameters { 35 | return &d.params 36 | } 37 | 38 | // DecryptNew decrypts an [rlwe.Ciphertext] and returns the result in a new [rlwe.Plaintext]. 39 | // Output plaintext [rlwe.MetaData] will match the input ciphertext [rlwe.MetaData]. 40 | func (d Decryptor) DecryptNew(ct *Ciphertext) (pt *Plaintext) { 41 | pt = NewPlaintext(d.params, ct.Level(), -1) 42 | d.Decrypt(ct, pt) 43 | return 44 | } 45 | 46 | // Decrypt decrypts an [rlwe.Ciphertext] and writes the result on an [rlwe.Plaintext]. 47 | // The level of the output plaintext is min(ct.Level(), pt.Level()). 48 | // Output plaintext [rlwe.MetaData] will match the input ciphertext [rlwe.MetaData]. 49 | func (d Decryptor) Decrypt(ct *Ciphertext, pt *Plaintext) { 50 | 51 | if d.sk == nil { 52 | panic(fmt.Errorf("decryption key is nil")) 53 | } 54 | 55 | LevelQ := min(ct.LevelQ(), pt.LevelQ()) 56 | LevelP := min(ct.LevelP(), pt.LevelP()) 57 | 58 | pt.ResizeQ(LevelQ) 59 | pt.ResizeP(LevelP) 60 | 61 | *pt.MetaData = *ct.MetaData 62 | 63 | d.decrypt(d.params.RingQ().AtLevel(LevelQ), ct.Q, pt.Q, d.sk.Q, ct.IsNTT) 64 | 65 | if rP := d.params.RingP(); rP != nil && LevelP > -1 { 66 | d.decrypt(rP.AtLevel(LevelP), ct.P, pt.P, d.sk.P, ct.IsNTT) 67 | } 68 | } 69 | 70 | func (d *Decryptor) decrypt(r ring.RNSRing, ct []ring.RNSPoly, pt, sk ring.RNSPoly, isNTT bool) { 71 | 72 | degree := len(ct) - 1 73 | 74 | if isNTT { 75 | pt.CopyLvl(pt.Level(), &ct[degree]) 76 | } else { 77 | r.NTTLazy(ct[degree], pt) 78 | } 79 | 80 | for i := degree; i > 0; i-- { 81 | 82 | r.MulCoeffsMontgomery(pt, sk, pt) 83 | 84 | if !isNTT { 85 | r.NTTLazy(ct[i-1], d.buff) 86 | r.Add(pt, d.buff, pt) 87 | } else { 88 | r.Add(pt, ct[i-1], pt) 89 | } 90 | 91 | if i&7 == 7 { 92 | r.Reduce(pt, pt) 93 | } 94 | } 95 | 96 | if degree&7 != 7 { 97 | r.Reduce(pt, pt) 98 | } 99 | 100 | if !isNTT { 101 | r.INTT(pt, pt) 102 | } 103 | } 104 | 105 | // ShallowCopy creates a shallow copy of the receiver in which all the read-only data- 106 | // structures are shared with the receiver and the temporary buffers are reallocated. 107 | // The receiver and the returned object can be used concurrently. 108 | func (d Decryptor) ShallowCopy() *Decryptor { 109 | return &Decryptor{ 110 | params: d.params, 111 | buff: d.params.RingQ().NewRNSPoly(), 112 | sk: d.sk, 113 | } 114 | } 115 | 116 | // WithKey returns an instance of the receiver with a new decryption key. 117 | // The returned object cannot be used concurrently with the receiver. 118 | func (d Decryptor) WithKey(sk *SecretKey) *Decryptor { 119 | 120 | if sk == nil { 121 | panic(fmt.Errorf("key is nil")) 122 | } 123 | 124 | if sk.N() != d.params.N() { 125 | panic(fmt.Errorf("key ring degree does not match parameters ring degree")) 126 | } 127 | 128 | return &Decryptor{ 129 | params: d.params, 130 | buff: d.buff, 131 | sk: sk, 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /rlwe/digit_decomposition.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // DigitDecompositionType defines the type of the 8 | // digit decomposition. 9 | type DigitDecompositionType int 10 | 11 | const ( 12 | // 0 = no digit decomposition (i.e. plain RNS) 13 | 14 | // Unsigned: unsigned digit decomposition, Var[X] = 2^{w}/12, E[X] = 2^{w-1} 15 | // Fastest decomposition, but greatest error. 16 | Unsigned = DigitDecompositionType(1) 17 | 18 | // Signed: signed digit decomposition, Var[X] = 2^{w}/12, E[X] = -0.5 19 | // Sligthly slower than unsigned (up to 15%), close to optimal error. 20 | Signed = DigitDecompositionType(2) 21 | 22 | // SignedBalanced: signed balanced digit decomposition, Var[X] = (2^{w}+0.25)/12, E[X] = 0 23 | // Much slower than unsigned (up to 50%), but optimal error. 24 | SignedBalanced = DigitDecompositionType(3) 25 | ) 26 | 27 | // DigitDecomposition is a struct that stores 28 | // the parameters for the digit decomposition. 29 | type DigitDecomposition struct { 30 | Type DigitDecompositionType 31 | Log2Basis int 32 | } 33 | 34 | func (dd DigitDecomposition) ToString() string { 35 | switch dd.Type { 36 | case Unsigned: 37 | return fmt.Sprintf("Unsigned:%d", dd.Log2Basis) 38 | case Signed: 39 | return fmt.Sprintf("Signed:%d", dd.Log2Basis) 40 | case SignedBalanced: 41 | return fmt.Sprintf("SignedBalanced:%d", dd.Log2Basis) 42 | default: 43 | return fmt.Sprintf("None:%d", dd.Log2Basis) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rlwe/distribution.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/Pro7ech/lattigo/ring" 7 | ) 8 | 9 | type Distribution struct { 10 | ring.DistributionParameters 11 | Std float64 12 | AbsBound float64 13 | } 14 | 15 | func NewDistribution(params ring.DistributionParameters, logN int) (d Distribution) { 16 | d.DistributionParameters = params 17 | switch params := params.(type) { 18 | case *ring.DiscreteGaussian: 19 | d.Std = params.Sigma 20 | d.AbsBound = params.Bound 21 | case *ring.Ternary: 22 | if params.P != 0 { 23 | d.Std = math.Sqrt(1 - params.P) 24 | } else { 25 | d.Std = math.Sqrt(float64(params.H) / (math.Exp2(float64(logN)) - 1)) 26 | } 27 | d.AbsBound = 1 28 | default: 29 | // Sanity check 30 | panic("invalid dist") 31 | } 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /rlwe/element.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | import ( 4 | "github.com/Pro7ech/lattigo/ring" 5 | ) 6 | 7 | type Element interface { 8 | N() int 9 | LogN() int 10 | Degree() int 11 | Level() int 12 | LevelQ() int 13 | LevelP() int 14 | AsPoint() *ring.Point 15 | AsVector() *ring.Vector 16 | AsPlaintext() *Plaintext 17 | AsCiphertext() *Ciphertext 18 | } 19 | -------------------------------------------------------------------------------- /rlwe/example_parameters.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | var ( 4 | // ExampleParameterLogN14LogQP438 is an example parameters set with logN=14 and logQP=438 5 | // offering 128-bit of security. 6 | ExampleParametersLogN14LogQP438 = ParametersLiteral{ 7 | LogN: 14, 8 | Q: []uint64{0x200000440001, 0x7fff80001, 0x800280001, 0x7ffd80001, 0x7ffc80001}, 9 | P: []uint64{0x3ffffffb80001, 0x4000000800001}, 10 | NTTFlag: true, 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /rlwe/operand.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | // Operand is an empty interface aimed at 4 | // providing an anchor for documentation. 5 | // 6 | // This interface is deliberately left empty 7 | // for backward and forward compatibility. 8 | // It aims at representing all types of operands 9 | // that can be passed as argument to homomorphic 10 | // evaluators. 11 | type Operand interface { 12 | } 13 | -------------------------------------------------------------------------------- /rlwe/rlwe.go: -------------------------------------------------------------------------------- 1 | // Package rlwe implements the generic cryptographic functionalities and operations that are common to R-LWE schemes. 2 | // The other implemented schemes extend this package with their specific operations and structures. 3 | package rlwe 4 | -------------------------------------------------------------------------------- /rlwe/security.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | import "github.com/Pro7ech/lattigo/ring" 4 | 5 | const ( 6 | // XsUniformTernary is the standard deviation of a ternary key with uniform distribution 7 | XsUniformTernary = 0.816496580927726 //Sqrt(2/3) 8 | 9 | // DefaultNoise is the default standard deviation of the error 10 | DefaultNoise = 3.2 11 | 12 | // DefaultNoiseBound is the default bound (in number of standard deviation) of the noise bound 13 | DefaultNoiseBound = 19.2 // 6*3.2 14 | ) 15 | 16 | // DefaultXe is the default discrete Gaussian distribution. 17 | var DefaultXe = ring.DiscreteGaussian{Sigma: DefaultNoise, Bound: DefaultNoiseBound} 18 | 19 | var DefaultXs = ring.Ternary{P: 2 / 3.0} 20 | -------------------------------------------------------------------------------- /rlwe/test_params_test.go: -------------------------------------------------------------------------------- 1 | package rlwe 2 | 3 | type TestParametersLiteral struct { 4 | DigitDecomposition 5 | ParametersLiteral 6 | } 7 | 8 | var ( 9 | logN = 10 10 | logQ = []int{45, 35, 35} 11 | logP = []int{50, 50} 12 | 13 | // testInsecure are insecure parameters used for the sole purpose of fast testing. 14 | testInsecure = []TestParametersLiteral{ 15 | // RNS decomposition, no Pw2 decomposition 16 | { 17 | DigitDecomposition: DigitDecomposition{}, 18 | 19 | ParametersLiteral: ParametersLiteral{ 20 | LogN: logN, 21 | LogQ: logQ, 22 | LogP: logP, 23 | NTTFlag: true, 24 | }, 25 | }, 26 | // RNS decomposition, Pw2 decomposition 27 | { 28 | DigitDecomposition: DigitDecomposition{ 29 | Type: Unsigned, 30 | Log2Basis: 16, 31 | }, 32 | 33 | ParametersLiteral: ParametersLiteral{ 34 | LogN: logN, 35 | LogQ: logQ, 36 | LogP: logP[:1], 37 | NTTFlag: true, 38 | }, 39 | }, 40 | // No RNS decomposition, Pw2 decomposition 41 | { 42 | DigitDecomposition: DigitDecomposition{ 43 | Type: SignedBalanced, 44 | Log2Basis: 2, 45 | }, 46 | 47 | ParametersLiteral: ParametersLiteral{ 48 | LogN: logN, 49 | LogQ: logQ[:1], 50 | LogP: nil, 51 | NTTFlag: true, 52 | }, 53 | }, 54 | } 55 | ) 56 | -------------------------------------------------------------------------------- /utils/bignum/bignum.go: -------------------------------------------------------------------------------- 1 | // Package bignum implements arbitrary precision arithmetic for integers, reals and complex numbers. 2 | package bignum 3 | -------------------------------------------------------------------------------- /utils/bignum/bignum_test.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | import ( 4 | "math" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | // test vectors for function DivRound 12 | type argDivRound struct { 13 | x, y, want *big.Int 14 | } 15 | 16 | var divRoundVec = []argDivRound{ 17 | {NewInt(0), NewInt(1), NewInt(0)}, 18 | {NewInt(1), NewInt(2), NewInt(1)}, 19 | {NewInt(5), NewInt(2), NewInt(3)}, 20 | {NewInt(5), NewInt(3), NewInt(2)}, 21 | {NewInt(5), NewInt(-2), NewInt(-3)}, 22 | {NewInt(-5), NewInt(2), NewInt(-3)}, 23 | {NewInt(-5), NewInt(-2), NewInt(3)}, 24 | {NewInt(987654321), NewInt(123456789), NewInt(8)}, 25 | {NewInt(-987654320), NewInt(123456789), NewInt(-8)}, 26 | {NewInt(-121932631112635269), NewInt(-987654321), NewInt(123456789)}, 27 | {NewInt("123456789123456789123456789123456789"), NewInt(123456789), NewInt("1000000001000000001000000001")}, 28 | {NewInt("987654321987654321987654321987654321"), NewInt("123456789123456789123456789123456789"), NewInt(8)}, 29 | {NewInt("-987654321987654321987654321987654321"), NewInt("-123456789123456789123456789123456789"), NewInt(8)}, 30 | } 31 | 32 | func TestDivRound(t *testing.T) { 33 | z := new(big.Int) 34 | for i, testPair := range divRoundVec { 35 | DivRound(testPair.x, testPair.y, z) 36 | require.Zerof(t, z.Cmp(testPair.want), "Error DivRound test pair %v", i) 37 | } 38 | } 39 | 40 | func BenchmarkDivRound(b *testing.B) { 41 | z := new(big.Int) 42 | x := NewInt("123456789123456789123456789123456789") 43 | y := NewInt("987654321987654321987654321987654321") 44 | for i := 0; i < b.N; i++ { 45 | DivRound(x, y, z) 46 | } 47 | } 48 | 49 | func BenchmarkDivRoundDebug(b *testing.B) { 50 | y := int64(123456789) 51 | x := int64(987654321) 52 | for i := 0; i < b.N; i++ { 53 | x = int64(math.Round(float64(x / y))) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /utils/bignum/eval.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // MonomialEval evaluates y = sum x^i * poly[i]. 8 | func MonomialEval(x *big.Float, poly []big.Float) (y *big.Float) { 9 | n := len(poly) - 1 10 | y = new(big.Float).Set(&poly[n-1]) 11 | for i := n - 2; i >= 0; i-- { 12 | y.Mul(y, x) 13 | y.Add(y, &poly[i]) 14 | } 15 | return 16 | } 17 | 18 | // ChebyshevEval evaluates y = sum Ti(x) * poly[i], where T0(x) = 1, T1(x) = (2x-a-b)/(b-a) and T{i+j}(x) = 2TiTj(x)- T|i-j|(x). 19 | func ChebyshevEval(x *big.Float, poly []big.Float, inter Interval) (y *big.Float) { 20 | 21 | precision := x.Prec() 22 | 23 | two := NewFloat(2, precision) 24 | var tmp, u = new(big.Float), new(big.Float) 25 | var T, Tprev, Tnext = new(big.Float), new(big.Float), new(big.Float) 26 | 27 | // u = (2*x - (a+b))/(b-a) 28 | u.Set(x) 29 | u.Mul(u, two) 30 | u.Sub(u, &inter.A) 31 | u.Sub(u, &inter.B) 32 | tmp.Set(&inter.B) 33 | tmp.Sub(tmp, &inter.A) 34 | u.Quo(u, tmp) 35 | 36 | Tprev.SetPrec(precision) 37 | Tprev.SetFloat64(1) 38 | T.Set(u) 39 | y = new(big.Float).Set(&poly[0]) 40 | 41 | for i := 1; i < len(poly); i++ { 42 | y.Add(y, tmp.Mul(T, &poly[i])) 43 | Tnext.Mul(two, u) 44 | Tnext.Mul(Tnext, T) 45 | Tnext.Sub(Tnext, Tprev) 46 | Tprev.Set(T) 47 | T.Set(Tnext) 48 | } 49 | 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /utils/bignum/float_test.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | import ( 4 | "math" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestFloat(t *testing.T) { 12 | testFunc1("Sin", 1.4142135623730951, math.Sin, Sin, 1e-15, t) 13 | testFunc1("Cos", 1.4142135623730951, math.Cos, Cos, 1e-15, t) 14 | testFunc1("Log", 1.4142135623730951, math.Log, Log, 1e-15, t) 15 | testFunc1("Exp", 1.4142135623730951, math.Exp, Exp, 1e-15, t) 16 | testFunc2("Pow", 2, 1.4142135623730951, math.Pow, Pow, 1e-15, t) 17 | testFunc1("SinH", 1.4142135623730951, math.Sinh, SinH, 1e-15, t) 18 | testFunc1("TanH", 1.4142135623730951, math.Tanh, TanH, 1e-15, t) 19 | } 20 | 21 | func testFunc1(name string, x float64, f func(x float64) (y float64), g func(x *big.Float) (y *big.Float), delta float64, t *testing.T) { 22 | t.Run(name, func(t *testing.T) { 23 | y, _ := g(NewFloat(x, 53)).Float64() 24 | require.InDelta(t, f(x), y, delta) 25 | }) 26 | } 27 | 28 | func testFunc2(name string, x, e float64, f func(x, e float64) (y float64), g func(x, e *big.Float) (y *big.Float), delta float64, t *testing.T) { 29 | t.Run(name, func(t *testing.T) { 30 | y, _ := g(NewFloat(x, 53), NewFloat(e, 53)).Float64() 31 | require.InDelta(t, f(x, e), y, delta) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /utils/bignum/int.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "io" 7 | "math" 8 | "math/big" 9 | ) 10 | 11 | // NewInt allocates a new *big.Int. 12 | // Accepted types are: string, uint, uint64, int64, int, *big.Float or *big.Int. 13 | func NewInt(x interface{}) (y *big.Int) { 14 | 15 | y = new(big.Int) 16 | 17 | if x == nil { 18 | return 19 | } 20 | 21 | switch x := x.(type) { 22 | case string: 23 | y.SetString(x, 0) 24 | case uint: 25 | y.SetUint64(uint64(x)) 26 | case uint64: 27 | y.SetUint64(x) 28 | case int64: 29 | y.SetInt64(x) 30 | case int: 31 | y.SetInt64(int64(x)) 32 | case *big.Float: 33 | x.Int(y) 34 | case *big.Int: 35 | y.Set(x) 36 | default: 37 | panic(fmt.Sprintf("cannot Newint: accepted types are string, uint, uint64, int, int64, *big.Float, *big.Int, but is %T", x)) 38 | } 39 | 40 | return 41 | } 42 | 43 | // RandInt generates a random Int in [0, max-1]. 44 | func RandInt(reader io.Reader, max *big.Int) (n *big.Int) { 45 | var err error 46 | if n, err = rand.Int(reader, max); err != nil { 47 | panic(fmt.Errorf("rand.Int: %w", err)) 48 | } 49 | return 50 | } 51 | 52 | // DivRound sets the target i to round(a/b). 53 | func DivRound(a, b, i *big.Int) { 54 | _a := new(big.Int).Set(a) 55 | i.Quo(_a, b) 56 | r := new(big.Int).Rem(_a, b) 57 | r2 := new(big.Int).Mul(r, NewInt(2)) 58 | if r2.CmpAbs(b) != -1.0 { 59 | if _a.Sign() == b.Sign() { 60 | i.Add(i, NewInt(1)) 61 | } else { 62 | i.Sub(i, NewInt(1)) 63 | } 64 | } 65 | } 66 | 67 | func Stats(values []big.Int, prec uint) [2]float64 { 68 | 69 | N := len(values) 70 | 71 | mean := NewFloat(0, prec) 72 | tmp := NewFloat(0, prec) 73 | 74 | for i := 0; i < N; i++ { 75 | mean.Add(mean, tmp.SetInt(&values[i])) 76 | } 77 | 78 | mean.Quo(mean, NewFloat(float64(N), prec)) 79 | 80 | stdFloat := NewFloat(0, prec) 81 | 82 | for i := 0; i < N; i++ { 83 | tmp.SetInt(&values[i]) 84 | tmp.Sub(tmp, mean) 85 | tmp.Mul(tmp, tmp) 86 | stdFloat.Add(stdFloat, tmp) 87 | } 88 | 89 | stdFloat.Quo(stdFloat, NewFloat(float64(N-1), prec)) 90 | 91 | stdFloat.Sqrt(stdFloat) 92 | 93 | stdF64, _ := stdFloat.Float64() 94 | meanF64, _ := mean.Float64() 95 | 96 | return [2]float64{math.Log2(stdF64), meanF64} 97 | } 98 | -------------------------------------------------------------------------------- /utils/bignum/interval.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Interval is a struct storing information about interval 8 | // for a polynomial approximation. 9 | // Nodes: the number of points used for the interpolation. 10 | // [A, B]: the domain of the interpolation. 11 | type Interval struct { 12 | Nodes int 13 | A, B big.Float 14 | } 15 | -------------------------------------------------------------------------------- /utils/bignum/metadata.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | // Basis is a type for the polynomials basis 4 | type Basis int 5 | 6 | const ( 7 | // Monomial : x^(a+b) = x^a * x^b 8 | Monomial = Basis(0) 9 | // Chebyshev : T_(a+b) = 2 * T_a * T_b - T_(|a-b|) 10 | Chebyshev = Basis(1) 11 | ) 12 | 13 | type MetaData struct { 14 | Basis 15 | Interval 16 | IsOdd bool 17 | IsEven bool 18 | } 19 | -------------------------------------------------------------------------------- /utils/bignum/remez_test.go: -------------------------------------------------------------------------------- 1 | package bignum 2 | 3 | import ( 4 | //"fmt" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestApproximation(t *testing.T) { 12 | sigmoid := func(x *big.Float) (y *big.Float) { 13 | z := new(big.Float).Set(x) 14 | z.Neg(z) 15 | z = Exp(z) 16 | z.Add(z, NewFloat(1, x.Prec())) 17 | y = NewFloat(1, x.Prec()) 18 | y.Quo(y, z) 19 | return 20 | } 21 | 22 | prec := uint(256) 23 | 24 | t.Run("Chebyshev", func(t *testing.T) { 25 | 26 | interval := Interval{ 27 | Nodes: 47, 28 | A: *NewFloat(-4, prec), 29 | B: *NewFloat(4, prec), 30 | } 31 | 32 | poly := ChebyshevApproximation(sigmoid, interval) 33 | 34 | xBig := NewFloat(1.4142135623730951, prec) 35 | 36 | y0, _ := sigmoid(xBig).Float64() 37 | y1, _ := poly.Evaluate(xBig)[0].Float64() 38 | 39 | require.InDelta(t, y0, y1, 1e-15) 40 | }) 41 | 42 | t.Run("MultiIntervalMinimaxRemez", func(t *testing.T) { 43 | 44 | f := func(x *big.Float) (y *big.Float) { 45 | return Log(x) 46 | } 47 | 48 | intervals := []Interval{ 49 | {A: *NewFloat(1e-4, prec), B: *NewFloat(148, prec), Nodes: 31}, 50 | } 51 | 52 | params := RemezParameters{ 53 | Function: f, 54 | Basis: Chebyshev, 55 | Intervals: intervals, 56 | Prec: prec, 57 | } 58 | 59 | r := NewRemez(params) 60 | r.Approximate(200, 1e-15) 61 | r.ShowCoeffs(50) 62 | r.ShowError(50) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /utils/buffer/buffer.go: -------------------------------------------------------------------------------- 1 | // Package buffer implement methods for efficiently writing and reading values 2 | // to and from io.Writer and io.Reader that also expose their internal buffers. 3 | package buffer 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | // Writer is an interface for writers that expose their internal 11 | // buffers. 12 | // This interface is notably implemented by the bufio.Writer type 13 | // (see https://pkg.go.dev/bufio#Writer) and by the Buffer type. 14 | type Writer interface { 15 | io.Writer 16 | Flush() (err error) 17 | AvailableBuffer() []byte 18 | Available() int 19 | } 20 | 21 | // Reader is an interface for readers that expose their internal 22 | // buffers. 23 | // This interface is notably implemented by the bufio.Reader type 24 | // (see https://pkg.go.dev/bufio#Reader) and by the Buffer type. 25 | type Reader interface { 26 | io.Reader 27 | Size() int 28 | Peek(n int) ([]byte, error) 29 | Discard(n int) (discarded int, err error) 30 | } 31 | 32 | // Buffer is a simple []byte-based buffer that complies to the 33 | // Writer and Reader interfaces. This type assumes that its 34 | // backing slice has a fixed size and won't attempt to extend 35 | // it. Instead, writes beyond capacity will result in an error. 36 | type Buffer struct { 37 | buf []byte 38 | n int 39 | off int 40 | } 41 | 42 | // NewBuffer creates a new Buffer struct with buff as a backing 43 | // []byte. The read and write offset are initialized at buff[0]. 44 | // Hence, writing new data will overwrite the content of buff. 45 | func NewBuffer(buff []byte) *Buffer { 46 | b := new(Buffer) 47 | b.buf = buff 48 | return b 49 | } 50 | 51 | // NewBufferSize creates a new Buffer with size capacity. 52 | func NewBufferSize(size int) *Buffer { 53 | b := new(Buffer) 54 | b.buf = make([]byte, size) 55 | return b 56 | } 57 | 58 | // Write writes p into b. It returns the number of bytes written 59 | // and an error if attempting to write passed the initial capacity 60 | // of the buffer. Note that the case where p shares the same backing 61 | // memory as b is optimized. 62 | func (b *Buffer) Write(p []byte) (n int, err error) { 63 | if len(p)+b.n > cap(b.buf) { 64 | return 0, fmt.Errorf("buffer too small") 65 | } 66 | inc := copy(b.buf[b.n:], p) // This is optimized if &b.buf[b.n:][0] == &p[0] 67 | b.n += inc 68 | return inc, nil 69 | } 70 | 71 | // Flush doesn't do anything on this slice-based buffer. 72 | func (b *Buffer) Flush() (err error) { 73 | return nil 74 | } 75 | 76 | // AvailableBuffer returns an empty buffer with b.Available() capacity, to be 77 | // directly appended to and passed to a Write call. The buffer is only valid 78 | // until the next write operation on b. 79 | func (b *Buffer) AvailableBuffer() []byte { 80 | return b.buf[b.n:][:0] 81 | } 82 | 83 | // Available returns the number of bytes available for writes on the buffer. 84 | func (b *Buffer) Available() int { 85 | return len(b.buf) - b.n 86 | } 87 | 88 | // Bytes returns the backing slice. 89 | func (b *Buffer) Bytes() []byte { 90 | return b.buf 91 | } 92 | 93 | // Reset re-initializes the read and write offsets of b. 94 | func (b *Buffer) Reset() { 95 | b.n = 0 96 | b.off = 0 97 | } 98 | 99 | // Read reads len(p) bytes from the read offset of b into p. It returns the 100 | // number n of bytes read and an error if n < len(p). 101 | func (b *Buffer) Read(p []byte) (n int, err error) { 102 | n = copy(p, b.buf[b.off:]) 103 | b.off += n 104 | if n < len(p) { 105 | return n, io.EOF 106 | } 107 | return n, nil 108 | } 109 | 110 | // Size returns the size of the buffer available for read. 111 | func (b *Buffer) Size() int { 112 | return len(b.buf) - b.off 113 | } 114 | 115 | // Peek returns the next n bytes without advancing the read offset, directly 116 | // as a reslice of the internal buffer. It returns an error if the number of 117 | // returned bytes is smaller than n. 118 | func (b *Buffer) Peek(n int) ([]byte, error) { 119 | if b.off+n > len(b.buf) { 120 | return b.buf[b.off:], io.EOF 121 | } 122 | return b.buf[b.off : b.off+n], nil 123 | } 124 | 125 | // Discard skips the next n bytes, returning the number of bytes discarded. If 126 | // Discard skips fewer than n bytes, it also returns an error. 127 | func (b *Buffer) Discard(n int) (discarded int, err error) { 128 | remain := len(b.buf) - b.off 129 | if n > remain { 130 | b.off = len(b.buf) 131 | return remain, io.EOF 132 | } 133 | b.off += n 134 | return n, nil 135 | } 136 | -------------------------------------------------------------------------------- /utils/concurrency/ressource_manager_test.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestConcurrency(t *testing.T) { 11 | 12 | t.Run("NoError", func(t *testing.T) { 13 | 14 | acc := make([]int, 8) 15 | 16 | ressources := make([]bool, 4) 17 | 18 | rm := NewRessourceManager(ressources) 19 | 20 | for i := range acc { 21 | rm.Run(func(r bool) (err error) { 22 | acc[i]++ 23 | return 24 | }) 25 | } 26 | 27 | require.NoError(t, rm.Wait()) 28 | 29 | for i := range acc { 30 | require.Equal(t, acc[i], 1) 31 | } 32 | }) 33 | 34 | t.Run("WithError", func(t *testing.T) { 35 | acc := make([]int, 8) 36 | 37 | ressources := make([]bool, 4) 38 | 39 | rm := NewRessourceManager(ressources) 40 | 41 | for i := range acc { 42 | rm.Run(func(r bool) (err error) { 43 | acc[i]++ 44 | if i == 2 { 45 | return fmt.Errorf("something bad happened") 46 | } 47 | 48 | return 49 | }) 50 | } 51 | 52 | require.Error(t, rm.Wait()) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /utils/concurrency/ressources_manager.go: -------------------------------------------------------------------------------- 1 | // Package concurrency implements a simple channel based ressource manager for concurrent operations. 2 | package concurrency 3 | 4 | import ( 5 | "sync" 6 | ) 7 | 8 | // ResourceManager is a struct storing a channel of some given ressource (e.g. an [rlwe.Evaluator]) 9 | // meant to be used concurrently and a channel for errors. 10 | type ResourceManager[T any] struct { 11 | sync.WaitGroup 12 | Ressources chan T 13 | Errors chan error 14 | } 15 | 16 | // NewRessourceManager instantiates a new [RessourceManager]. 17 | func NewRessourceManager[T any](ressources []T) *ResourceManager[T] { 18 | Ressources := make(chan T, len(ressources)) 19 | for i := range ressources { 20 | Ressources <- ressources[i] 21 | } 22 | return &ResourceManager[T]{ 23 | Ressources: Ressources, 24 | Errors: make(chan error, len(ressources)), 25 | } 26 | } 27 | 28 | // Task is an abstract templates for a function taking as input 29 | // a ressource of any kind that can be used concurrently. 30 | type Task[T any] func(ressource T) (err error) 31 | 32 | // Run runs a [Task] concurrently. 33 | // If the internal error channel is not empty, does nothing. 34 | // Adds any error returned by [Task] to the internal error channel. 35 | func (r *ResourceManager[T]) Run(f Task[T]) { 36 | r.Add(1) 37 | go func() { 38 | defer r.Done() 39 | if len(r.Errors) != 0 { 40 | return 41 | } 42 | ressource := <-r.Ressources 43 | if err := f(ressource); err != nil { 44 | if len(r.Errors) < cap(r.Errors) { 45 | r.Errors <- err 46 | } 47 | } 48 | r.Ressources <- ressource 49 | }() 50 | } 51 | 52 | // Wait waits until all concurrent [Task] have finished and returns 53 | // the first encountered error, if any. 54 | func (r *ResourceManager[T]) Wait() (err error) { 55 | if len(r.Errors) == 0 { 56 | r.WaitGroup.Wait() 57 | } else { 58 | return <-r.Errors 59 | } 60 | 61 | if len(r.Errors) != 0 { 62 | return <-r.Errors 63 | } 64 | 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /utils/factorization/factorization_test.go: -------------------------------------------------------------------------------- 1 | package factorization_test 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/Pro7ech/lattigo/utils/factorization" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | const ( 12 | prime uint64 = 0x1fffffffffe00001 13 | ) 14 | 15 | func TestIsPrime(t *testing.T) { 16 | // 2^64 - 59 is prime 17 | require.True(t, factorization.IsPrime(new(big.Int).SetUint64(0xffffffffffffffc5))) 18 | // 2^64 + 13 is prime 19 | bigPrime, _ := new(big.Int).SetString("18446744073709551629", 10) 20 | require.True(t, factorization.IsPrime(bigPrime)) 21 | // 2^64 is not prime 22 | require.False(t, factorization.IsPrime(new(big.Int).SetUint64(0xffffffffffffffff))) 23 | } 24 | 25 | func TestGetFactors(t *testing.T) { 26 | 27 | t.Run("GetFactors", func(t *testing.T) { 28 | m := new(big.Int).SetUint64(prime - 1) 29 | require.True(t, checkFactorization(new(big.Int).Set(m), factorization.GetFactors(m))) 30 | }) 31 | 32 | t.Run("ECM", func(t *testing.T) { 33 | m := new(big.Int).SetUint64(prime - 1) 34 | require.True(t, m.Mod(m, factorization.GetFactorECM(m)).Cmp(new(big.Int)) == 0) 35 | }) 36 | 37 | t.Run("PollardRho", func(t *testing.T) { 38 | m := new(big.Int).SetUint64(prime - 1) 39 | require.True(t, m.Mod(m, factorization.GetFactorPollardRho(m)).Cmp(new(big.Int)) == 0) 40 | }) 41 | } 42 | 43 | func checkFactorization(p *big.Int, factors []*big.Int) bool { 44 | zero := new(big.Int) 45 | for _, factor := range factors { 46 | for new(big.Int).Mod(p, factor).Cmp(zero) == 0 { 47 | p.Quo(p, factor) 48 | } 49 | } 50 | 51 | return p.Cmp(new(big.Int).SetUint64(1)) == 0 52 | } 53 | -------------------------------------------------------------------------------- /utils/factorization/weierstrass.go: -------------------------------------------------------------------------------- 1 | package factorization 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | ) 7 | 8 | // Weierstrass is an elliptic curve y^2 = x^3 + ax + b mod N. 9 | type Weierstrass struct { 10 | A, B, N *big.Int 11 | } 12 | 13 | // Point represents an elliptic curve point in standard coordinates. 14 | type Point struct { 15 | X, Y *big.Int 16 | } 17 | 18 | // Add adds two Weierstrass points together with respect 19 | // to the underlying Weierstrass curve. 20 | // This method does not check if the points lie on 21 | // the underlying curve. 22 | func (w *Weierstrass) Add(P, Q Point) Point { 23 | 24 | tmp := new(big.Int) 25 | 26 | xR, yR := new(big.Int), new(big.Int) 27 | 28 | if P.X.Cmp(tmp.SetUint64(0)) == 0 && P.Y.Cmp(tmp.SetUint64(1)) == 0 { 29 | 30 | return Point{xR.Set(Q.X), yR.Set(Q.Y)} 31 | } 32 | 33 | if Q.X.Cmp(tmp.SetUint64(0)) == 0 && Q.Y.Cmp(tmp.SetUint64(1)) == 0 { 34 | return Point{xR.Set(P.X), yR.Set(P.Y)} 35 | } 36 | 37 | xP, yP := P.X, P.Y 38 | xQ, yQ := Q.X, Q.Y 39 | 40 | N := w.N 41 | 42 | if xP.Cmp(xQ) == 0 && yP.Cmp(new(big.Int).Sub(N, yQ)) == 0 { 43 | return Point{xR.SetUint64(0), yR.SetUint64(0)} 44 | } 45 | 46 | S := new(big.Int) // slope 47 | 48 | if xP != xQ { 49 | 50 | // S = (yQ-yP)/(xQ-xP) 51 | S.Sub(yQ, yP) 52 | tmp.Sub(xQ, xP) 53 | tmp.ModInverse(tmp, N) 54 | S.Mul(S, tmp) 55 | S.Mod(S, N) 56 | 57 | } else { 58 | 59 | // S = (3*(xP^2) + a)/(2*yP) 60 | S.Mul(xP, xP) 61 | S.Mod(S, N) 62 | S.Mul(S, new(big.Int).SetUint64(3)) 63 | S.Add(S, w.A) 64 | S.Mod(S, N) 65 | tmp.Add(yP, yP) 66 | tmp.ModInverse(tmp, N) 67 | S.Mul(S, tmp) 68 | S.Mod(S, N) 69 | } 70 | 71 | // s^2 - xP - xQ 72 | xR.Mul(S, S) 73 | xR.Mod(xR, N) 74 | xR.Sub(xR, xP) 75 | xR.Sub(xR, xQ) 76 | xR.Mod(xR, N) 77 | 78 | // s*(xP-xR)-yP 79 | yR.Sub(xP, xR) 80 | yR.Mul(yR, S) 81 | yR.Mod(yR, N) 82 | yR.Sub(yR, yP) 83 | yR.Mod(yR, N) 84 | 85 | return Point{X: xR, Y: yR} 86 | } 87 | 88 | // RandInt generates a random Int in [0, max-1]. 89 | func RandInt(max *big.Int) (n *big.Int) { 90 | var err error 91 | if n, err = rand.Int(rand.Reader, max); err != nil { 92 | panic(err) 93 | } 94 | return 95 | } 96 | 97 | // NewRandomWeierstrassCurve generates a new random Weierstrass curve modulo N, 98 | // along with a random point that lies on the curve. 99 | func NewRandomWeierstrassCurve(N *big.Int) (Weierstrass, Point) { 100 | 101 | var A, B, xG, yG *big.Int 102 | for { 103 | 104 | // Select random values for A, xG and yG 105 | A = RandInt(N) 106 | xG = RandInt(N) 107 | yG = RandInt(N) 108 | 109 | // Deduces B from Y^2 = X^3 + A * X + B evaluated at point (xG, yG) 110 | yGpow2 := new(big.Int).Mul(yG, yG) 111 | yGpow2.Mod(yGpow2, N) 112 | 113 | xGpow3 := new(big.Int).Mul(xG, xG) 114 | xGpow3.Mod(xGpow3, N) 115 | xGpow3.Sub(xGpow3, A) 116 | xGpow3.Mul(xGpow3, xG) 117 | xGpow3.Mod(xGpow3, N) 118 | 119 | B = new(big.Int).Sub(yGpow2, xGpow3) // B = yG^2 - xG*(xG^2 - A) 120 | B.Mod(B, N) 121 | 122 | // Checks that 4A^3 + 27B^2 != 0 123 | fourACube := new(big.Int).Add(A, A) 124 | fourACube.Mul(fourACube, fourACube) 125 | fourACube.Mod(fourACube, N) 126 | fourACube.Mul(fourACube, A) 127 | 128 | twentySevenBSquare := new(big.Int).Mul(B, B) 129 | twentySevenBSquare.Mod(twentySevenBSquare, N) 130 | twentySevenBSquare.Mul(twentySevenBSquare, new(big.Int).SetUint64(27)) 131 | twentySevenBSquare.Mod(twentySevenBSquare, N) 132 | 133 | jInvariantQuotient := new(big.Int).Add(fourACube, twentySevenBSquare) 134 | jInvariantQuotient.Mod(jInvariantQuotient, N) 135 | 136 | if jInvariantQuotient.Cmp(new(big.Int).SetUint64(0)) != 0 && new(big.Int).GCD(nil, nil, N, jInvariantQuotient).Cmp(new(big.Int).SetUint64(1)) == 0 { 137 | return Weierstrass{ 138 | A: A, 139 | B: B, 140 | N: N, 141 | }, Point{X: xG, Y: yG} 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /utils/pointy.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "unsafe" 5 | 6 | cs "golang.org/x/exp/constraints" 7 | ) 8 | 9 | type Number interface { 10 | cs.Complex | cs.Float | cs.Integer | bool 11 | } 12 | 13 | // Pointy creates a new T variable and returns its pointer. 14 | func Pointy[T Number](x T) *T { 15 | return &x 16 | } 17 | 18 | // PointyIntToPointUint64 converts *int to *uint64. 19 | func PointyIntToPointUint64(x *int) *uint64 { 20 | /* #nosec G103 -- behavior and consequences well understood, pointer type cast */ 21 | return (*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(x)))) 22 | } 23 | -------------------------------------------------------------------------------- /utils/sampling/source.go: -------------------------------------------------------------------------------- 1 | // Package sampling implements secure sampling. 2 | package sampling 3 | 4 | import ( 5 | crypto "crypto/rand" 6 | "encoding/binary" 7 | "math/rand/v2" 8 | ) 9 | 10 | // NewSeed returns a 32 byte seed populated with 11 | // cryptographically secure random bytes. 12 | func NewSeed() (seed [32]byte) { 13 | if _, err := crypto.Read(seed[:]); err != nil { 14 | panic(err) 15 | } 16 | return 17 | } 18 | 19 | type Source struct { 20 | buff [8]byte 21 | rand.ChaCha8 22 | } 23 | 24 | func NewSource(seed [32]byte) *Source { 25 | return &Source{ChaCha8: *rand.NewChaCha8(seed)} 26 | } 27 | 28 | // NewSeed returns a new seed populated with bytes 29 | // generated by the receiver. 30 | func (s *Source) NewSeed() (seed [32]byte) { 31 | if _, err := s.Read(seed[:]); err != nil { 32 | panic(err) 33 | } 34 | return 35 | } 36 | 37 | // NewSource returns a new [sampling.Source] seeded 38 | // with bytes generated by the receiver. 39 | func (s *Source) NewSource() *Source { 40 | return NewSource(s.NewSeed()) 41 | } 42 | 43 | // Float64 returns a pseudo random float64 in the interval [min, max). 44 | func (s *Source) Float64(min, max float64) float64 { 45 | return min + float64(s.Uint64()<<11>>11)/(1<<53)*(max-min) 46 | } 47 | 48 | // Complex128 returns a pseudo random complex128 in with 49 | // the real part in the interval [real(min), real(max)), 50 | // the imag part in the interval [imag(min), imag(max)). 51 | func (s *Source) Complex128(min, max complex128) complex128 { 52 | return complex(s.Float64(real(min), real(max)), s.Float64(imag(min), imag(max))) 53 | } 54 | 55 | // Read complies to the io.Reader interface. 56 | func (s *Source) Read(b []byte) (n int, err error) { 57 | 58 | size := len(b) 59 | 60 | if size < 8 { 61 | binary.LittleEndian.PutUint64(s.buff[:], s.Uint64()) 62 | copy(b, s.buff[:]) 63 | return len(b), nil 64 | } 65 | 66 | for i := 0; i < (size>>3)<<3; i += 8 { 67 | binary.LittleEndian.PutUint64(b[i:], s.Uint64()) 68 | } 69 | 70 | // If len(b) is not a multiple of 8, we fill the last 8 bytes 71 | // of b, possibly overwriting previously filled bytes. 72 | if size&7 != 0 { 73 | binary.LittleEndian.PutUint64(b[size-8:], s.Uint64()) 74 | } 75 | 76 | return len(b), nil 77 | } 78 | -------------------------------------------------------------------------------- /utils/slices.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // Alias1D returns true if x and y share the same base array. 4 | // Taken from http://golang.org/src/pkg/math/big/nat.go#L340 . 5 | func Alias1D[V any](x, y []V) bool { 6 | return cap(x) > 0 && cap(y) > 0 && &x[0:cap(x)][cap(x)-1] == &y[0:cap(y)][cap(y)-1] 7 | } 8 | 9 | // Alias2D returns true if x and y share the same base array. 10 | // Taken from http://golang.org/src/pkg/math/big/nat.go#L340 . 11 | func Alias2D[V any](x, y [][]V) bool { 12 | return cap(x) > 0 && cap(y) > 0 && &x[0:cap(x)][cap(x)-1] == &y[0:cap(y)][cap(y)-1] 13 | } 14 | 15 | // GetDistincts returns the list of distinct elements in v. 16 | func GetDistincts[V comparable](v []V) (vd []V) { 17 | m := map[V]bool{} 18 | for _, vi := range v { 19 | m[vi] = true 20 | } 21 | 22 | vd = make([]V, len(m)) 23 | 24 | var i int 25 | for mi := range m { 26 | vd[i] = mi 27 | i++ 28 | } 29 | 30 | return 31 | } 32 | 33 | // RotateSlice returns a new slice corresponding to s rotated by k positions to the left. 34 | func RotateSlice[V any](s []V, k int) []V { 35 | ret := make([]V, len(s)) 36 | RotateSliceAllocFree(s, k, ret) 37 | return ret 38 | } 39 | 40 | // RotateSliceAllocFree rotates slice s by k positions to the left and writes the result in sout. 41 | // without allocating new memory. 42 | func RotateSliceAllocFree[V any](s []V, k int, sout []V) { 43 | 44 | if len(s) != len(sout) { 45 | panic("cannot RotateSliceAllocFree: s and sout of different lengths") 46 | } 47 | 48 | if len(s) == 0 { 49 | return 50 | } 51 | 52 | k = k % len(s) 53 | if k < 0 { 54 | k = k + len(s) 55 | } 56 | 57 | if &s[0] == &sout[0] { // checks if the two slice share the same backing array 58 | RotateSliceInPlace(s, k) 59 | return 60 | } 61 | 62 | copy(sout[:len(s)-k], s[k:]) 63 | copy(sout[len(s)-k:], s[:k]) 64 | } 65 | 66 | // RotateSliceInPlace rotates slice s in place by k positions to the left. 67 | func RotateSliceInPlace[V any](s []V, k int) { 68 | n := len(s) 69 | k = k % len(s) 70 | if k < 0 { 71 | k = k + len(s) 72 | } 73 | gcd := GCD(k, n) 74 | for i := 0; i < gcd; i++ { 75 | tmp := s[i] 76 | j := i 77 | for { 78 | x := j + k 79 | if x >= n { 80 | x = x - n 81 | } 82 | if x == i { 83 | break 84 | } 85 | s[j] = s[x] 86 | j = x 87 | } 88 | s[j] = tmp 89 | } 90 | } 91 | 92 | // RotateSlotsNew returns a new slice where the two half of the 93 | // original slice are rotated each by k positions independently. 94 | func RotateSlotsNew[V any](s []V, k int) (r []V) { 95 | r = make([]V, len(s)) 96 | copy(r, s) 97 | slots := len(s) >> 1 98 | RotateSliceInPlace(r[:slots], k) 99 | RotateSliceInPlace(r[slots:], k) 100 | return 101 | } 102 | 103 | // BitReverseInPlaceSlice applies an in-place bit-reverse permutation on the input slice. 104 | func BitReverseInPlaceSlice[V any](slice []V, N int) { 105 | 106 | var bit, j int 107 | 108 | for i := 1; i < N; i++ { 109 | 110 | bit = N >> 1 111 | 112 | for j >= bit { 113 | j -= bit 114 | bit >>= 1 115 | } 116 | 117 | j += bit 118 | 119 | if i < j { 120 | slice[i], slice[j] = slice[j], slice[i] 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /utils/structs/structs.go: -------------------------------------------------------------------------------- 1 | // Package structs implements helpers to generalize vectors and matrices of structs, as well as their serialization. 2 | package structs 3 | 4 | type Equatable[T any] interface { 5 | Equal(*T) bool 6 | } 7 | 8 | type Cloner[V any] interface { 9 | Clone() *V 10 | } 11 | 12 | type Copyer[V any] interface { 13 | Copy(*V) 14 | } 15 | 16 | type ShallowCopyer[V any] interface { 17 | ShallowCopy() *V 18 | } 19 | 20 | type BinarySizer interface { 21 | BinarySize() int 22 | } 23 | -------------------------------------------------------------------------------- /utils/structs/structs_test.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "github.com/stretchr/testify/require" 8 | "golang.org/x/exp/constraints" 9 | ) 10 | 11 | func TestStructs(t *testing.T) { 12 | t.Run("Vector/W64/Serialization&Equatable", func(t *testing.T) { 13 | testVector[uint64](t) 14 | }) 15 | 16 | t.Run("Vector/W32/Serialization&Equatable", func(t *testing.T) { 17 | testVector[uint32](t) 18 | }) 19 | 20 | t.Run("Vector/W16/Serialization&Equatable", func(t *testing.T) { 21 | testVector[uint16](t) 22 | }) 23 | 24 | t.Run("Vector/W8/Serialization&Equatable", func(t *testing.T) { 25 | testVector[uint8](t) 26 | }) 27 | 28 | t.Run("Matrix/W64/Serialization&Equatable", func(t *testing.T) { 29 | testMatrix[float64](t) 30 | }) 31 | 32 | t.Run("Matrix/W32/Serialization&Equatable", func(t *testing.T) { 33 | testMatrix[float64](t) 34 | }) 35 | 36 | t.Run("Matrix/W16/Serialization&Equatable", func(t *testing.T) { 37 | testMatrix[float64](t) 38 | }) 39 | 40 | t.Run("Matrix/W8/Serialization&Equatable", func(t *testing.T) { 41 | testMatrix[float64](t) 42 | }) 43 | } 44 | 45 | func testVector[T constraints.Float | constraints.Integer](t *testing.T) { 46 | v := Vector[T](make([]T, 64)) 47 | for i := range v { 48 | v[i] = T(i) 49 | } 50 | data, err := v.MarshalBinary() 51 | require.NoError(t, err) 52 | vNew := Vector[T]{} 53 | require.NoError(t, vNew.UnmarshalBinary(data)) 54 | require.True(t, cmp.Equal(v, vNew)) // also tests Equatable 55 | } 56 | 57 | func testMatrix[T constraints.Float | constraints.Integer](t *testing.T) { 58 | v := Matrix[T](make([][]T, 64)) 59 | for i := range v { 60 | vi := make([]T, 64) 61 | for j := range vi { 62 | vi[j] = T(i & j) 63 | } 64 | 65 | v[i] = vi 66 | } 67 | 68 | data, err := v.MarshalBinary() 69 | require.NoError(t, err) 70 | vNew := Matrix[T]{} 71 | require.NoError(t, vNew.UnmarshalBinary(data)) 72 | require.True(t, cmp.Equal(v, vNew)) // also tests Equatable 73 | } 74 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | // Package utils implements various helper functions. 2 | package utils 3 | 4 | import ( 5 | "math/bits" 6 | "reflect" 7 | ) 8 | 9 | // IsNil returns true either type or value are nil. 10 | // Only interfaces or pointers to objects should be passed as argument. 11 | func IsNil(i interface{}) bool { 12 | return i == nil || reflect.ValueOf(i).IsNil() 13 | } 14 | 15 | // BitReverse64 returns the bit-reverse value of the input value, within a context of 2^bitLen. 16 | func BitReverse64[V uint64 | uint32 | int | int64](index V, bitLen int) uint64 { 17 | return bits.Reverse64(uint64(index)) >> (64 - bitLen) 18 | } 19 | 20 | // HammingWeight64 returns the Hamming weight if the input value. 21 | func HammingWeight64[V uint64 | uint32 | int | int64](x V) V { 22 | y := uint64(x) 23 | y -= (y >> 1) & 0x5555555555555555 24 | y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333) 25 | y = (y + (y >> 4)) & 0x0f0f0f0f0f0f0f0f 26 | return V(((y * 0x0101010101010101) & 0xffffffffffffffff) >> 56) 27 | } 28 | 29 | // AllDistinct returns true if all elements in s are distinct, and false otherwise. 30 | func AllDistinct[V comparable](s []V) bool { 31 | m := make(map[V]struct{}, len(s)) 32 | for _, si := range s { 33 | if _, exists := m[si]; exists { 34 | return false 35 | } 36 | m[si] = struct{}{} 37 | } 38 | return true 39 | } 40 | 41 | // GCD computes the greatest common divisor between a and b. 42 | func GCD[V uint64 | uint32 | int | int64](a, b V) V { 43 | if a == 0 || b == 0 { 44 | return 0 45 | } 46 | for b != 0 { 47 | a, b = b, a%b 48 | } 49 | return a 50 | } 51 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestAllDistinct(t *testing.T) { 10 | require.True(t, AllDistinct([]uint64{})) 11 | require.True(t, AllDistinct([]uint64{1})) 12 | require.True(t, AllDistinct([]uint64{1, 2, 3})) 13 | require.False(t, AllDistinct([]uint64{1, 1})) 14 | require.False(t, AllDistinct([]uint64{1, 2, 3, 4, 5, 5})) 15 | } 16 | 17 | func TestRotateUint64(t *testing.T) { 18 | s := []uint64{0, 1, 2, 3, 4, 5, 6, 7} 19 | sout := make([]uint64, len(s)) 20 | 21 | RotateSliceAllocFree(s, 3, sout) 22 | require.Equal(t, []uint64{3, 4, 5, 6, 7, 0, 1, 2}, sout) 23 | require.Equal(t, []uint64{0, 1, 2, 3, 4, 5, 6, 7}, s, "should not modify input slice") 24 | 25 | RotateSliceAllocFree(s, 0, sout) 26 | require.Equal(t, []uint64{0, 1, 2, 3, 4, 5, 6, 7}, sout) 27 | 28 | RotateSliceAllocFree(s, -2, sout) 29 | require.Equal(t, []uint64{6, 7, 0, 1, 2, 3, 4, 5}, sout) 30 | 31 | RotateSliceAllocFree(s, 9, sout) 32 | require.Equal(t, []uint64{1, 2, 3, 4, 5, 6, 7, 0}, sout) 33 | 34 | RotateSliceAllocFree(s, -11, sout) 35 | require.Equal(t, []uint64{5, 6, 7, 0, 1, 2, 3, 4}, sout) 36 | 37 | RotateSliceAllocFree(s, 0, s) 38 | require.Equal(t, []uint64{0, 1, 2, 3, 4, 5, 6, 7}, s) 39 | 40 | RotateSliceAllocFree(s, 1, s) 41 | require.Equal(t, []uint64{1, 2, 3, 4, 5, 6, 7, 0}, s) 42 | 43 | RotateSliceAllocFree(s, -2, s) 44 | require.Equal(t, []uint64{7, 0, 1, 2, 3, 4, 5, 6}, s) 45 | } 46 | --------------------------------------------------------------------------------