├── .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 |
--------------------------------------------------------------------------------