Update package name

This commit is contained in:
2025-08-06 19:52:38 -05:00
parent 7c7123870e
commit bd8c210383
159 changed files with 5716 additions and 7074 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ assignees: ''
Please make sure you have answered all the following questions before submitting your issue!
-->
### What version of Lattigo are you using?
### What version of Lattice are you using?
### Does this issue persist with the latest release?
+2 -2
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Feature Proposal/Request
url: https://tuneinsight.com/lattigo/contact
url: https://lux.network/lattice/contact
about: We do not use GitHub Issues for features proposals or requests. If you have a proposal or would like to make a request for a feature, please contact us directly.
- name: General Question
url: https://github.com/tuneinsight/lattigo/discussions
url: https://github.com/luxdefi/lattice/discussions
about: Ask a question that is not necessarily related to the use or implementation of the library.
+2 -2
View File
@@ -8,7 +8,7 @@ assignees: ''
---
<!--
Make sure that your question is directly related to the use or the implementation of the library before submitting it as we do not use GitHub Issues for general discussion. If your question is general (e.g. "How can I do <xxx> in FHE?"" or "Why don't I get the correct result?"), please use the dedicated https://github.com/tuneinsight/lattigo/discussions instead.
Make sure that your question is directly related to the use or the implementation of the library before submitting it as we do not use GitHub Issues for general discussion. If your question is general (e.g. "How can I do <xxx> in FHE?"" or "Why don't I get the correct result?"), please use the dedicated https://github.com/luxdefi/lattice/discussions instead.
If you want to keep your question private, you can contact us directly using the following email: latti[email protected].
If you want to keep your question private, you can contact us directly using the following email: latti[email protected].
-->
+3 -3
View File
@@ -399,7 +399,7 @@ All notable changes to this library are documented in this file.
- CKKS: encoder is now about 3.5x faster (without taking the NTT into account).
## [3.0.0] - 2022-02-21
- ALL: renamed the module to `github.com/tuneinsight/v3`.
- ALL: renamed the module to `github.com/luxdefi/v3`.
- RING: renamed `FastBasisExtender` to `BasisExtender`.
- RING: `.PolyToBigint[...](*)` now take as input `gap` which defines the multiples of `X^{i*gap}` to reconstruct.
- RLWE: removed `FastEncryptor`. Encryption without rescaling by `P` is now automatically used by `Encryptor` if no `P` is specified in the parameters.
@@ -484,7 +484,7 @@ All notable changes to this library are documented in this file.
- RING: RNS rescaling API is now in place and can take a different poly as output.
- RING: added `ReadFromDistLvl` and `ReadAndAddFromDistLvl` to Gaussian sampler API.
- RING: added `IsNTT` and `IsMForm` flags in the `ring.Poly` type. For now, these flags are never checked or changed by the `ring` package.
- RLWE: added a new `rlwe` package as common implementation base package for the Lattigo RLWE schemes.
- RLWE: added a new `rlwe` package as common implementation base package for the Lattice RLWE schemes.
- RLWE: extracted the `rlwe.Parameters` type as common base struct for BFV and CKKS parameters.
- RLWE: extracted the `rlwe.KeyGenerator` type as common key-generator for BFV and CKKS.
- RLWE: extracted the `rlwe.Ciphertext` type as common base struct for BFV and CKKS ciphertexts.
@@ -497,7 +497,7 @@ All notable changes to this library are documented in this file.
- RLWE: added equality and inclusion check methods for the `rlwe.RotatationKeySet` type.
- RLWE: added tests for encryption, decryption, key-generation and key-switching.
- RLWE: moved keys related marshalling tests of `bfv` and `ckks` packages the `rlwe` package.
- DRLWE: added a new `drlwe` package as a common implementation base for the lattigo multiparty RLWE schemes.
- DRLWE: added a new `drlwe` package as a common implementation base for the lattice multiparty RLWE schemes.
- DRLWE: added tests for the protocols.
- DRLWE: moved keys-related marshalling tests of `dbfv` and `dckks` packages to the `drlwe` package.
- BFV/CKKS: the schemes now use a common implementation for their keys.
+4 -4
View File
@@ -1,12 +1,12 @@
Copyright 2022 EPFL, Tune Insight SA
The Lattigo library was originally exclusively developed by the EPFL Laboratory
The Lattice library was originally exclusively developed by the EPFL Laboratory
for Data Security until its version 2.4.0.
Starting with the release of version 3.0.0, Lattigo is maintained and supported
by [Tune Insight SA](https://tuneinsight.com).
Starting with the release of version 3.0.0, Lattice is maintained and supported
by [Tune Insight SA](https://luxdefi.com).
Active development of new features in Lattigo is still carried out both at the
Active development of new features in Lattice is still carried out both at the
EPFL Laboratory for Data Security and at Tune Insight SA.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+92 -106
View File
@@ -1,12 +1,10 @@
# Lattigo: lattice-based multiparty homomorphic encryption library in Go
# lattice
<p align="center">
<img src="logo.png" />
</p>
## Lattice-based multiparty homomorphic encryption library in Go
![Go tests](https://github.com/tuneinsight/lattigo/actions/workflows/ci.yml/badge.svg)
![Go tests](https://github.com/luxdefi/lattice/actions/workflows/ci.yml/badge.svg)
Lattigo is a Go module that implements full-RNS Ring-Learning-With-Errors-based homomorphic-encryption
Lattice is a Go module that implements full-RNS Ring-Learning-With-Errors-based homomorphic-encryption
primitives and Multiparty-Homomorphic-Encryption-based secure protocols. The library features:
- Optimized arithmetic for power-of-two cyclotomic rings.
@@ -16,126 +14,127 @@ primitives and Multiparty-Homomorphic-Encryption-based secure protocols. The lib
- A pure Go implementation, enabling cross-platform builds, including WASM compilation for
browser clients, with comparable performance to state-of-the-art C++ libraries.
Lattigo is meant to support HE in distributed systems and microservices architectures, for which Go
Lattice is meant to support HE in distributed systems and microservices architectures, for which Go
is a common choice thanks to its natural concurrency model and portability.
## Library overview
<p align="center" width="100%">
<img width=500 height=350 alt="lattigo-hierarchy" src="./lattigo-hierarchy.svg">
</p>
The library exposes the following packages:
Lattigo is a strictly hierarchical library whose packages form a linear dependency chain ranging from
low-level arithmetic functionalities to high-level homomorphic circuits. A graphical depiction of the
Lattigo package organization is given in the Figure above.
- `lattice/he`: The main package of the library which provides scheme-agnostic interfaces
and Homomorphic Encryption for different plaintext domains.
- `lattigo/ring`: At the lowest level resides the `ring` package providing modular arithmetic operations for polynomials
in the RNS basis, including: RNS basis extension; RNS rescaling; number theoretic transform (NTT); uniform,
Gaussian and ternary sampling.
- `lattigo/core`: This package implements the core cryptographic functionalities of the library and builds directly
upon the arithmetic functionalities provided by the `ring` package:
- `hebin`: Homomorphic Encryption for binary arithmetic. It comprises blind rotations (a.k.a Lookup Tables) over RLWE ciphertexts.
- `rlwe`: Common base for generic RLWE-based homomorphic encryption.
It provides all homomorphic functionalities and defines all structs that are not scheme-specific.
This includes plaintext, ciphertext, key-generation, encryption, decryption and key-switching, as
well as other more advanced primitives such as RLWE-repacking.
- `hefloat`: Homomorphic Encryption for fixed-point approximate arithmetic over the complex or real numbers.
- `rgsw`: A Full-RNS variant of Ring-GSW ciphertexts and the external product.
- `lattigo/schemes`: The implementation of RLWE-based homomorphic encryption schemes are found in the `schemes` package:
- `bootstrapper`: Bootstrapping for fixed-point approximate arithmetic over the real
and complex numbers, with support for the Conjugate Invariant ring, batch bootstrapping with automatic
packing/unpacking of sparsely packed/smaller ring degree ciphertexts, arbitrary precision bootstrapping,
and advanced circuit customization/parameterization.
- `heint`: Homomorphic Encryption for modular arithmetic over the integers.
- `lattice/mhe`: Package for multiparty (a.k.a. distributed or threshold) key-generation and
interactive ciphertext bootstrapping with secret-shared secret keys.
- `mhefloat`: Homomorphic decryption and re-encryption from and to Linear-Secret-Sharing-Shares,
as well as interactive ciphertext bootstrapping for the package `he/hefloat`.
- `mheint`: Homomorphic decryption and re-encryption from and to Linear-Secret-Sharing-Shares,
as well as interactive ciphertext bootstrapping for the package `he/heint`.
- `lattice/schemes`: A package implementing RLWE-based homomorphic encryption schemes.
- `bfv`: A Full-RNS variant of the Brakerski-Fan-Vercauteren scale-invariant homomorphic
encryption scheme. This scheme is instantiated via a wrapper of the `bgv` scheme.
encryption scheme. This scheme is instantiated via a wrapper of the `bgv` scheme.
It provides modular arithmetic over the integers.
- `bgv`: A Full-RNS generalization of the Brakerski-Fan-Vercauteren scale-invariant (BFV) and
Brakerski-Gentry-Vaikuntanathan (BGV) homomorphic encryption schemes.
- `bgv`: A Full-RNS generalization of the Brakerski-Fan-Vercauteren scale-invariant (BFV) and
Brakerski-Gentry-Vaikuntanathan (BGV) homomorphic encryption schemes.
It provides modular arithmetic over the integers.
- `ckks`: A Full-RNS Homomorphic Encryption for Arithmetic for Approximate Numbers (HEAAN,
a.k.a. CKKS) scheme. It provides fixed-point approximate arithmetic over the complex numbers (in its classic
variant) and over the real numbers (in its conjugate-invariant variant).
- `lattigo/circuits`: The circuits package provides implementation of a select set of homomorphic circuits for
the `bgv` and `ckks` cryptosystems:
- `bgv/lintrans`, `ckks/lintrans`: Arbitrary linear transformations and slot permutations for both `bgv` and `ckks`.
Scheme-generic objects and functions are part of `common/lintrans`.
- `bgv/polynomial`, `ckks/polynomial`: Polynomial evaluation circuits for `bgv` and `ckks`.
Scheme-generic objects and functions are part of `common/polynomial`.
- `ckks/minimax`: Minimax composite polynomial evaluator for `ckks`.
- `ckks/comparison`: Homomorphic comparison-based circuits such as `sign`, `max` and `step` for the `ckks` scheme.
- `ckks/inverse`: Homomorphic inverse circuit for `ckks`.
- `ckks/mod1`: Homomorphic circuit for the `mod1` function using the `ckks` cryptosystem.
- `ckks/dft`: Homomorphic Discrete Fourier Transform circuits for the `ckks` scheme.
- `ckks/bootstrapping`: Bootstrapping for fixed-point approximate arithmetic over the real
and complex numbers, i.e., the `ckks` scheme, with support for the Conjugate Invariant ring, batch bootstrapping with automatic
packing/unpacking of sparsely packed/smaller ring degree ciphertexts, arbitrary precision bootstrapping,
and advanced circuit customization/parameterization.
- `lattice/core`: A package implementing the core cryptographic functionalities of the library.
- `lattigo/multiparty`: Package for multiparty (a.k.a. distributed or threshold) key-generation and
interactive ciphertext bootstrapping with secret-shared secret keys.
- `rlwe`: Common base for generic RLWE-based homomorphic encryption.
It provides all homomorphic functionalities and defines all structs that are not scheme-specific.
This includes plaintext, ciphertext, key-generation, encryption, decryption and key-switching, as
well as other more advanced primitives such as RLWE-repacking.
- `mpckks`: Homomorphic decryption and re-encryption from and to Linear-Secret-Sharing-Shares,
as well as interactive ciphertext bootstrapping for the `schemes/ckks` package.
- `rgsw`: A Full-RNS variant of Ring-GSW ciphertexts and the external product.
- `mpbgv`: Homomorphic decryption and re-encryption from and to Linear-Secret-Sharing-Shares,
as well as interactive ciphertext bootstrapping for the `schemes/bgv` package.
- `lattice/ring`: Modular arithmetic operations for polynomials in the RNS basis, including: RNS
basis extension; RNS rescaling; number theoretic transform (NTT); uniform, Gaussian and ternary
sampling.
- `lattigo/examples`: Executable Go programs that demonstrate the use of the Lattigo library. Each
subpackage includes test files that further demonstrate the use of Lattigo
- `lattice/examples`: Executable Go programs that demonstrate the use of the Lattice library. Each
subpackage includes test files that further demonstrate the use of Lattice
primitives.
- `lattigo/utils`: Generic utility methods. This package also contains the following sub-packages:
- `lattice/utils`: Generic utility methods. This package also contains the following sub-pacakges:
- `bignum`: Arbitrary precision linear algebra and polynomial approximation.
- `buffer`: Efficient methods to write/read on `io.Writer` and `io.Reader`.
- `factorization`: Various factorization algorithms for medium-sized integers.
- `sampling`: Secure bytes sampling.
- `structs`: Generic structs for maps, vectors and matrices, including serialization.
### Documentation
The full documentation of the individual packages can be browsed as a web page using official
Golang documentation rendering tool `pkgsite` or browsing the [Go doc](https://pkg.go.dev/github.com/tuneinsight/lattigo/v6).
```bash
$ go install golang.org/x/pkgsite/cmd/pkgsite@latest
$ cd lattigo
$ pkgsite -open .
```mermaid
---
title: Packages Dependency & Organization
---
flowchart LR
RING(RING) --> RLWE(RLWE)
RLWE --> RGSW(RGSW)
RLWE --> HE([HE])
RLWE --> CKKS{{CKKS}}
RGSW --> HEBin{HEBin}
HE --> HEFloat{HEFloat}
HE --> HEInt{HEInt}
BFV/BGV --> HEInt
CKKS --> HEFloat
RLWE --> BFV/BGV{{BFV/BGV}}
MHE --> MHEFloat
HEFloat --> MHEFloat((MHEFloat))
HEFloat --> Bootstrapping
HEInt --> MHEInt((MHEInt))
RLWE --> MHE([MHE])
MHE --> MHEInt
```
## Versions and Roadmap
The Lattigo library was originally exclusively developed by the EPFL Laboratory for Data Security
The Lattice library was originally exclusively developed by the EPFL Laboratory for Data Security
until its version 2.4.0.
Starting with the release of version 3.0.0, Lattigo is maintained and supported by [Tune Insight
SA](https://tuneinsight.com).
Starting with the release of version 3.0.0, Lattice is maintained and supported by [Tune Insight
SA](https://luxdefi.com).
Also starting from version 3.0.0, the module name has changed to
`github.com/tuneinsight/lattigo/v[X]`, and the official repository has been moved to
https://github.com/tuneinsight/lattigo. This has the following implications for modules that depend
on Lattigo:
- Modules that require `github.com/ldsec/lattigo/v2` will still build correctly.
- To upgrade to a version X.y.z >= 3.0.0, depending modules must require `github.com/tuneinsight/lattigo/v[X]/`,
for example by changing the imports to `github.com/tuneinsight/lattigo/v[X]/[package]` and by
Also starting with from version 3.0.0, the module name has changed to
`github.com/luxdefi/lattice/v[X]`, and the official repository has been moved to
https://github.com/luxdefi/lattice. This has the following implications for modules that depend
on Lattice:
- Modules that require `github.com/ldsec/lattice/v2` will still build correctly.
- To upgrade to a version X.y.z >= 3.0.0, depending modules must require `github.com/luxdefi/lattice/v[X]/`,
for example by changing the imports to `github.com/luxdefi/lattice/v[X]/[package]` and by
running `go mod tidy`.
The current version of Lattigo (v6.x.x) is fast-evolving and in constant development. Consequently,
The current version of Lattice, (v4.x.x) is fast-evolving and in constant development. Consequently,
there will still be backward-incompatible changes within this major version, in addition to many bug
fixes and new features. Hence, we encourage all Lattigo users to update to the latest Lattigo version.
fixes and new features. Hence, we encourage all Lattice users to update to the latest Lattice version.
See CHANGELOG.md for the current and past versions.
## Stability
To keep a comprehensive history, we prioritize rebases over merges for branches other than `main`.
Branches with the prefix `dev_` are branches in active development and will be frequently rebased.
Hence, we don't recommend depending on them.
## Pull Requests
External pull requests should only be used to propose new functionalities that are substantial and would
@@ -144,41 +143,28 @@ us before doing so to make sure that the proposed changes are aligned with our d
External pull requests only proposing small or trivial changes will be converted to an issue and closed.
External contributions will require the signature of a Contributor License Agreement (CLA).
You can contact us using the following email to request a copy of the CLA: [[email protected]](mailto:[email protected]).
## Vulnerability Reports
See [Report a Vulnerability](SECURITY.md#report-a-vulnerability).
## Bug Reports
Lattigo welcomes bug/regression reports of any kind that conform to the preset template, which is
automatically generated upon creation of a new empty issue. Nonconformity will result in the issue
being closed without acknowledgement.
## License
Lattigo is licensed under the Apache 2.0 License. See [LICENSE](https://github.com/tuneinsight/lattigo/blob/master/LICENSE).
Lattice is licensed under the Apache 2.0 License. See [LICENSE](https://github.com/luxdefi/lattice/blob/master/LICENSE).
## Contact
Before contacting us directly, please make sure that your request cannot be handled through an issue.
If you want to contribute to Lattigo or report a security issue, you have a feature proposal or request, or you simply want to contact us directly, please do so using the following email: [lattigo@tuneinsight.com](mailto:latti[email protected]).
If you want to contribute to Lattice, have a feature proposal or request, to report a security issue or simply want to contact us directly, please do so using the following email: [lattice@luxdefi.com](mailto:latti[email protected]).
## Citing
Please use the following BibTex entry for citing Lattigo:
Please use the following BibTex entry for citing Lattice:
@misc{lattigo,
title = {Lattigo v6},
howpublished = {Online: \url{https://github.com/tuneinsight/lattigo}},
month = Aug,
year = 2024,
@misc{lattice,
title = {Lattice v5},
howpublished = {Online: \url{https://github.com/luxdefi/lattice}},
month = Nov,
year = 2023,
note = {EPFL-LDS, Tune Insight SA}
}
The Lattigo logo is a lattice-based version of the original Golang mascot by [Renee
The Lattice logo is a lattice-based version of the original Golang mascot by [Renee
French](http://reneefrench.blogspot.com/).
+10 -62
View File
@@ -1,79 +1,27 @@
# Report a Vulnerability
To report a vulnerability please contact us directly using the following email: lattigo@tuneinsight.com.
Please include as much detail as possible in your report, including:
- Affected version.
- Steps to reproduce.
- Potential impact of the vulnerability.
# Past Vulnerabilities
### Wrong level for DenseToSparse Evaluation Key (04.2025)
- **Severity:** Low
- **Impact:** A security of 128-bit is not guaranteed if the `EvkDenseToSparse` evaluation key, which is part of the CKKS bootstrapping keys, is used. With the bootstrapping parameters proposed in Lattigo, the security falls to $\approx$ 106 bits of security. This means the vulnerability should not lead to any practical attack. However, anyone using CKKS bootstrapping should update their version of Lattigo and rotate their secret key to go back to 128-bit security.
- **Versions impacted:** `v5.0.0-v6.1.0`
- **Fixed in:** `v6.1.1`
- **Fix:** `EvkDenseToSparse` is generated at the correct level.
- **Reported by:** Noam Kleinburd **@NoamK-CR**
To report a vulnerability please contact us directly using the following email: lattice@luxdefi.com.
# Code Review
Lattigo 2.0.0 was code-reviewed by ELCA in November 2020 and, within the allocated time for the code review, no critical or high-risk issues were found.
Lattice 2.0.0 has been code-reviewed by ELCA in November 2020 and, within the allocated time for the code review, no critical or high-risk issues were found.
# IND-CPA Security
# Security of Approximate Homomorphic Encryption
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).
In IND-CPA security, the adversary has access only to an encryption oracle. This means that to attempt breaking the cryptographic scheme, the adversary can select multiple plaintexts and observe the corresponding well-formed ciphertexts produced by the encryption function. IND-CPA serves as the baseline security standard for cryptosystems with provable security.
Notably, all widely used FHE schemes —such as BFV, BGV, CKKS (implemented in the current version of Lattigo), and TFHE— provide only CPA security, which refers to protection against passive adversaries. In Lattigo, the default parameters provide at least 128-bits of security (according to the [Lattice estimator](https://github.com/malb/lattice-estimator)).
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).
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 CPA<sup>D</sup> secure. The corresponding indistinguishability notion (IND-CPA<sup>D</sup>) is defined as "indistinguishability under chosen plaintext attacks with decryption oracles."
# IND-CPA-D Security
IND-CPA-D security strengthens IND-CPA by granting the attacker access to a decryption oracle for ciphertexts where the plaintext is known. This includes ciphertexts the attacker encrypted legitimately, as well as those derived by evaluating circuits of their choosing.
## Approximate Homomorphic Encryption Scheme (CKKS)
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 $((- a s + m + e ) - ( m + e ) ) \cdot a^{-1} = a s a^{-1} = s$ (the probability of $a$ being invertible is overwhelming, and if $a$ is not invertible, only a few more samples are required).
This attack demonstrates that, when using an approximate homomorphic encryption scheme, the usual CPA security may not be sufficient depending on the application setting. Many applications do not require sharing 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 CPA-D secure. The corresponding indistinguishability notion (IND-CPA-D) is defined as "indistinguishability under chosen plaintext attacks with decryption oracles".
The Lattigo API does _not_ provide automatic IND-CPA-D security. However, Lattigo implements the necessary API 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.
# CPA<sup>D</sup> Security for Approximate Homomorphic Encryption
Lattice 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.
Let $\epsilon$ be the scheme error after the decoding step. We compute the bit precision of the output as $\log_{2}(1/\epsilon)$.
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$.
Estimating $\text{Pr}[\epsilon < x] \leq 2^{-s}$ of the circuit must be done carefully and we suggest the following process to do so:
1. Given an error bound $\varepsilon$ (a user defined security parameter) and a circuit $C$ that takes as inputs $\text{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.
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.
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]$.
3. Use the CDF to select the value $\text{E}[\epsilon]$ such that any given slot will fail with probability $2^{-\varepsilon}$ to reach $\log_{2}(1/\epsilon)$ bits of precision.
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.
4. Use the encoder method $\textsf{DecodePublic}$ with the parameter $\log_{2}(1/\epsilon)$ to decode plaintexts that will be published.
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$.
[Bossuat et al.](https://eprint.iacr.org/2024/853) provide tight bounds on noise to optimize the rounding process, minimizing loss in both precision and efficiency. Integration of the corresponding noise estimator in Lattigo is planned.
## Exact Homomorphic Encryption Scheme
[Checri et al.](https://eprint.iacr.org/2024/116) and [Cheon et al.](https://eprint.iacr.org/2024/127), revealed new passive key-recovery attacks targeting also the exact FHE cryptosystems, including BFV, BGV, and TFHE. They exploit imperfect correctness and show that BFV, BGV and TFHE are not protected against IND-CPA-D attackers.
Achieving IND-CPA-D security for the exact homomorphic encryption schemes requires near-perfect correctness, meaning decryption failures must be exceptionally rare, with a probability lower than $2^{−\lambda}$, where $\lambda$ is a user-defined security parameter. Such failures should be so unlikely that finding one is computationally infeasible.
For exact schemes like BFV and BGV, implemented in Lattigo, near-perfect correctness can be maintained by adjusting scheme parameters to bound decryption noise, though this comes at the cost of performance. The circuit implementation must also control noise growth by limiting the number and type of operations performed at each computation level.
## Multiparty/Threshold Homomorphic Encryption
Multiparty or Threshold Fully Homomorphic Encryption involves secret-sharing the encryption key among multiple users, requiring their collaboration to decrypt data. The scheme maintains security even if a defined threshold number of users are compromised by an attacker. However, when decrypting, a party has access to the raw decrypted value before rounding, even with exact schemes. Since the receiver(s) (i.e. the decrypting party) may be corrupted, the attacker can mount the same kind of attacks, as described by [Checri et al.](https://eprint.iacr.org/2024/116) Fortunately, proper noise flooding as described by [Mouchet et al.](https://eprint.iacr.org/2020/304) (Section IV.E) thwarts these attacks. However, such methods require exponential noise, which will affect the performance and/or the correctness of the protocol.
## Limitations of the IND-CPA-D model
It is important to clarify that, for many applications, IND-CPA is sufficient.The IND-CPA-D model does not always align well with practical use cases. This model fails to address a broad range of real-world adversarial scenarios-for example, an attacker capable of producing or computing ciphertexts is unlikely to be limited to the constraints defined by the IND-CPA-D model. Furthermore, it introduces unnecessary performance drawbacks for FHE. However, depending on the specific application and threat model, additional security guarantees may be necessary. In such cases, these enhanced guarantees can often be achieved through supplementary application-specific countermeasures or protocol modifications.
## Recommendation for applicative countermeasures
1. As already said, the inherent malleability of FHE ciphertexts, along with weaknesses such as circular security and decision-to-search attacks, creates potential avenues for key-recovery attacks. As a foundational principle, it’s crucial that FHE ciphertexts are transmitted only through private and authenticated channels, encapsulated within traditional cryptographic methods.
2. Most IND-CPA-D attacks require hundreds of thousands of queries to the evaluation and decryption oracles. By employing ephemeral keys or key rotations, we can limit the number of available plaintext-ciphertext pairs, effectively reducing the attack surface and mitigating these types of attacks.
3. Augmenting the FHE scheme with some kind of verifiable computation mechanism (e.g. SNARKs) might help protect against stronger adversaries (see e.g., [Canard et al.](https://eprint.iacr.org/2024/812)).
4. Other physical limitations: firewall, rate control, enclave
# IND-CCA Security
Fully Homomorphic Encryption (FHE) ciphertexts are malleable by design. This malleability, when combined with vulnerabilities such as circular security and decision-to-search attacks, can result in trivial key-recovery reaction attacks. This implies that FHE schemes are **not** secure against chosen ciphertext attacks (CCA security). Many intermediate security notions between IND-CPA and IND-CCA exist; an extensive summary can be found in the work of [Canard et al.](https://eprint.iacr.org/2024/812).
# Circuit Privacy
As discussed above, the FHE schemes proposed in Lattigo guarantee some notion of confidentiality (e.g. IND-CPA) but they do not necessarily hide information about the underlying computation. In other words, they do not provide **circuit privacy**. More precisely, schemes like CKKS and BFV/BGV leak information on the computation performed because the homomorphic operations introduce some structure in the ciphertext. Then, an adversary with knowledge of ciphertexts can infer details about the circuit (e.g. its depth, the type of operations, constants…). For instance, given a ciphertext $\text{ct}_1$ and its transformed version $\text{ct}_2$ after a homomorphic multiplication by a constant $C$, an adversary can deduce $C$.
Circuit privacy usually requires techniques such as noise flooding or rerandomization, which in turn implies a significant loss in performance.
@@ -1,23 +1,22 @@
package polynomial
package heint
import (
"fmt"
"math/big"
"math/bits"
"github.com/tuneinsight/lattigo/v6/circuits/common/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/schemes/bgv"
"github.com/luxdefi/lattice/v5/utils"
)
// simEvaluator is a struct used to pre-computed the scaling
// factors of the polynomial coefficients used by the inlined
// polynomial evaluation by running the polynomial evaluation
// with dummy operands.
// This struct implements the interface [polynomial.SimEvaluator].
// This struct implements the interface he.SimEvaluator.
type simEvaluator struct {
params bgv.Parameters
params Parameters
InvariantTensoring bool
}
@@ -26,29 +25,24 @@ func (d simEvaluator) PolynomialDepth(degree int) int {
if d.InvariantTensoring {
return 0
}
if degree <= 0 {
panic(fmt.Errorf("invalid degree: degree=%d should be greater than zero", degree))
}
return bits.Len64(uint64(degree)) - 1
}
// Rescale rescales the target polynomial.SimOperand n times and returns it.
func (d simEvaluator) Rescale(op0 *polynomial.SimOperand) {
// Rescale rescales the target he.SimOperand n times and returns it.
func (d simEvaluator) Rescale(op0 *he.SimOperand) {
if !d.InvariantTensoring {
op0.Scale = op0.Scale.Div(rlwe.NewScale(d.params.Q()[op0.Level]))
op0.Level--
}
}
// MulNew multiplies two polynomial.SimOperand, stores the result the target [polynomial.SimOperand] and returns the result.
func (d simEvaluator) MulNew(op0, op1 *polynomial.SimOperand) (opOut *polynomial.SimOperand) {
opOut = new(polynomial.SimOperand)
// MulNew multiplies two he.SimOperand, stores the result the target he.SimOperand and returns the result.
func (d simEvaluator) MulNew(op0, op1 *he.SimOperand) (opOut *he.SimOperand) {
opOut = new(he.SimOperand)
opOut.Level = utils.Min(op0.Level, op1.Level)
if d.InvariantTensoring {
opOut.Scale = bgv.MulScaleInvariant(d.params, op0.Scale, op1.Scale, opOut.Level)
opOut.Scale = bgv.MulScaleInvariant(d.params.Parameters, op0.Scale, op1.Scale, opOut.Level)
} else {
opOut.Scale = op0.Scale.Mul(op1.Scale)
}
+64 -155
View File
@@ -6,18 +6,17 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var flagLongTest = flag.Bool("long", false, "run the long test suite (all parameters + secure bootstrapping). Overrides -short and requires -timeout=0.")
var printPrecisionStats = flag.Bool("print-precision", false, "print precision stats")
var testPrec45 = ckks.ParametersLiteral{
var testPrec45 = hefloat.ParametersLiteral{
LogN: 10,
LogQ: []int{60, 40},
LogP: []int{61},
@@ -35,7 +34,7 @@ func TestBootstrapping(t *testing.T) {
schemeParamsLit.LogN = 16
}
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
params, err := hefloat.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParamsLit.LogN = utils.Pointy(params.LogN())
@@ -63,7 +62,7 @@ func TestBootstrapping(t *testing.T) {
evaluator, err := NewEvaluator(btpParams, btpKeys)
require.NoError(t, err)
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
enc := rlwe.NewEncryptor(params, sk)
dec := rlwe.NewDecryptor(params, sk)
@@ -81,7 +80,7 @@ func TestBootstrapping(t *testing.T) {
t.Run("Bootstrapping", func(t *testing.T) {
plaintext := ckks.NewPlaintext(params, 0)
plaintext := hefloat.NewPlaintext(params, 0)
ecd.Encode(values, plaintext)
ctQ0, err := enc.EncryptNew(plaintext)
@@ -114,7 +113,7 @@ func TestBootstrapping(t *testing.T) {
schemeParamsLit.LogNthRoot = schemeParamsLit.LogN + 1
schemeParamsLit.LogN--
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
params, err := hefloat.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParamsLit.LogN = utils.Pointy(params.LogN() + 1)
@@ -143,7 +142,7 @@ func TestBootstrapping(t *testing.T) {
evaluator, err := NewEvaluator(btpParams, btpKeys)
require.Nil(t, err)
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
enc := rlwe.NewEncryptor(params, sk)
dec := rlwe.NewDecryptor(params, sk)
@@ -161,7 +160,7 @@ func TestBootstrapping(t *testing.T) {
t.Run("N1ToN2->Bootstrapping->N2ToN1", func(t *testing.T) {
plaintext := ckks.NewPlaintext(params, 0)
plaintext := hefloat.NewPlaintext(params, 0)
ecd.Encode(values, plaintext)
ctQ0, err := enc.EncryptNew(plaintext)
@@ -186,91 +185,6 @@ func TestBootstrapping(t *testing.T) {
})
})
t.Run("BootstrappingPackedWithoutRingDegreeSwitch", func(t *testing.T) {
schemeParamsLit := testPrec45
btpParamsLit := ParametersLiteral{}
if *flagLongTest {
schemeParamsLit.LogN = 16
}
btpParamsLit.LogN = utils.Pointy(schemeParamsLit.LogN)
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
// Insecure params for fast testing only
if !*flagLongTest {
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParamsLit.LogMessageRatio = utils.Pointy(DefaultLogMessageRatio + (16 - params.LogN()))
}
sk := rlwe.NewKeyGenerator(params).GenSecretKeyNew()
ecd := ckks.NewEncoder(params)
enc := rlwe.NewEncryptor(params, sk)
dec := rlwe.NewDecryptor(params, sk)
for _, sparsity := range []int{0, 1, 2} {
btpParamsLit.LogSlots = utils.Pointy(*btpParamsLit.LogN - 1 - sparsity)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(t, err)
t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.ResidualParameters.LogN(), btpParams.ResidualParameters.LogMaxSlots(), btpParams.ResidualParameters.LogQP())
t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.BootstrappingParameters.LogN(), btpParams.BootstrappingParameters.LogMaxSlots(), btpParams.BootstrappingParameters.LogQP())
t.Log("Generating Bootstrapping Keys for LogSlots")
btpKeys, _, err := btpParams.GenEvaluationKeys(sk)
require.Nil(t, err)
evaluator, err := NewEvaluator(btpParams, btpKeys)
require.Nil(t, err)
for _, slotOffset := range []int{0, 1, 2, 3} {
logMaxSlots := params.LogMaxSlots() - slotOffset
logMaxSlots = utils.Min(logMaxSlots, btpParams.LogMaxSlots())
values := make([]complex128, 1<<logMaxSlots)
for i := range values {
values[i] = sampling.RandComplex128(-1, 1)
}
values[0] = complex(0.9238795325112867, 0.3826834323650898)
values[1] = complex(0.9238795325112867, 0.3826834323650898)
if len(values) > 2 {
values[2] = complex(0.9238795325112867, 0.3826834323650898)
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
pt := ckks.NewPlaintext(params, 0)
pt.LogDimensions = ring.Dimensions{Rows: 0, Cols: logMaxSlots}
cts := make([]rlwe.Ciphertext, 11)
for i := range cts {
require.NoError(t, ecd.Encode(utils.RotateSlice(values, i), pt))
ct, err := enc.EncryptNew(pt)
require.NoError(t, err)
cts[i] = *ct
}
if cts, err = evaluator.BootstrapMany(cts); err != nil {
t.Fatal(err)
}
for i := range cts {
// Checks that the output ciphertext is at the max level of paramsN1
require.True(t, cts[i].Level() == params.MaxLevel())
require.True(t, cts[i].Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, utils.RotateSlice(values, i), &cts[i], t)
}
}
}
})
t.Run("BootstrappingPackedWithRingDegreeSwitch", func(t *testing.T) {
schemeParamsLit := testPrec45
@@ -284,77 +198,72 @@ func TestBootstrapping(t *testing.T) {
schemeParamsLit.LogNthRoot = schemeParamsLit.LogN + 1
schemeParamsLit.LogN -= 3
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
params, err := hefloat.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(t, err)
// Insecure params for fast testing only
if !*flagLongTest {
btpParams.SlotsToCoeffsParameters.LogSlots = btpParams.BootstrappingParameters.LogN() - 1
btpParams.CoeffsToSlotsParameters.LogSlots = btpParams.BootstrappingParameters.LogN() - 1
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParamsLit.LogMessageRatio = utils.Pointy(DefaultLogMessageRatio + (16 - params.LogN()))
btpParams.Mod1ParametersLiteral.LogMessageRatio += 16 - params.LogN()
}
t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.ResidualParameters.LogN(), btpParams.ResidualParameters.LogMaxSlots(), btpParams.ResidualParameters.LogQP())
t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.BootstrappingParameters.LogN(), btpParams.BootstrappingParameters.LogMaxSlots(), btpParams.BootstrappingParameters.LogQP())
sk := rlwe.NewKeyGenerator(params).GenSecretKeyNew()
ecd := ckks.NewEncoder(params)
t.Log("Generating Bootstrapping Keys")
btpKeys, _, err := btpParams.GenEvaluationKeys(sk)
require.Nil(t, err)
evaluator, err := NewEvaluator(btpParams, btpKeys)
require.Nil(t, err)
ecd := hefloat.NewEncoder(params)
enc := rlwe.NewEncryptor(params, sk)
dec := rlwe.NewDecryptor(params, sk)
for _, sparsity := range []int{0, 1, 2} {
btpParamsLit.LogSlots = utils.Pointy(*btpParamsLit.LogN - 1 - sparsity)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(t, err)
values := make([]complex128, params.MaxSlots())
for i := range values {
values[i] = sampling.RandComplex128(-1, 1)
}
t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.ResidualParameters.LogN(), btpParams.ResidualParameters.LogMaxSlots(), btpParams.ResidualParameters.LogQP())
t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.BootstrappingParameters.LogN(), btpParams.BootstrappingParameters.LogMaxSlots(), btpParams.BootstrappingParameters.LogQP())
values[0] = complex(0.9238795325112867, 0.3826834323650898)
values[1] = complex(0.9238795325112867, 0.3826834323650898)
if len(values) > 2 {
values[2] = complex(0.9238795325112867, 0.3826834323650898)
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
t.Log("Generating Bootstrapping Keys for LogSlots")
btpKeys, _, err := btpParams.GenEvaluationKeys(sk)
require.Nil(t, err)
pt := hefloat.NewPlaintext(params, 0)
evaluator, err := NewEvaluator(btpParams, btpKeys)
require.Nil(t, err)
cts := make([]rlwe.Ciphertext, 7)
for i := range cts {
for _, slotOffset := range []int{0, 1, 2, 3} {
logMaxSlots := params.LogMaxSlots() - slotOffset
logMaxSlots = utils.Min(logMaxSlots, btpParams.LogMaxSlots())
values := make([]complex128, 1<<logMaxSlots)
for i := range values {
values[i] = sampling.RandComplex128(-1, 1)
}
require.NoError(t, ecd.Encode(utils.RotateSlice(values, i), pt))
values[0] = complex(0.9238795325112867, 0.3826834323650898)
values[1] = complex(0.9238795325112867, 0.3826834323650898)
if len(values) > 2 {
values[2] = complex(0.9238795325112867, 0.3826834323650898)
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
ct, err := enc.EncryptNew(pt)
require.NoError(t, err)
pt := ckks.NewPlaintext(params, 0)
pt.LogDimensions = ring.Dimensions{Rows: 0, Cols: logMaxSlots}
cts[i] = *ct
}
cts := make([]rlwe.Ciphertext, 11)
for i := range cts {
if cts, err = evaluator.BootstrapMany(cts); err != nil {
t.Fatal(err)
}
require.NoError(t, ecd.Encode(utils.RotateSlice(values, i), pt))
for i := range cts {
// Checks that the output ciphertext is at the max level of paramsN1
require.True(t, cts[i].Level() == params.MaxLevel())
require.True(t, cts[i].Scale.Equal(params.DefaultScale()))
ct, err := enc.EncryptNew(pt)
require.NoError(t, err)
cts[i] = *ct
}
if cts, err = evaluator.BootstrapMany(cts); err != nil {
t.Fatal(err)
}
for i := range cts {
// Checks that the output ciphertext is at the max level of paramsN1
require.True(t, cts[i].Level() == params.MaxLevel())
require.True(t, cts[i].Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, utils.RotateSlice(values, i), &cts[i], t)
}
}
verifyTestVectorsBootstrapping(params, ecd, dec, utils.RotateSlice(values, i), &cts[i], t)
}
})
@@ -372,7 +281,7 @@ func TestBootstrapping(t *testing.T) {
schemeParamsLit.LogNthRoot = schemeParamsLit.LogN + 1
schemeParamsLit.LogN--
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
params, err := hefloat.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
@@ -396,7 +305,7 @@ func TestBootstrapping(t *testing.T) {
evaluator, err := NewEvaluator(btpParams, btpKeys)
require.Nil(t, err)
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
enc := rlwe.NewEncryptor(params, sk)
dec := rlwe.NewDecryptor(params, sk)
@@ -414,7 +323,7 @@ func TestBootstrapping(t *testing.T) {
t.Run("ConjugateInvariant->Standard->Bootstrapping->Standard->ConjugateInvariant", func(t *testing.T) {
plaintext := ckks.NewPlaintext(params, 0)
plaintext := hefloat.NewPlaintext(params, 0)
require.NoError(t, ecd.Encode(values, plaintext))
ctLeftQ0, err := enc.EncryptNew(plaintext)
@@ -444,14 +353,14 @@ func TestBootstrapping(t *testing.T) {
})
}
func verifyTestVectorsBootstrapping(params ckks.Parameters, encoder *ckks.Encoder, decryptor *rlwe.Decryptor, valuesWant, element interface{}, t *testing.T) {
precStats := ckks.GetPrecisionStats(params, encoder, decryptor, valuesWant, element, 0, false)
func verifyTestVectorsBootstrapping(params hefloat.Parameters, encoder *hefloat.Encoder, decryptor *rlwe.Decryptor, valuesWant, element interface{}, t *testing.T) {
precStats := hefloat.GetPrecisionStats(params, encoder, decryptor, valuesWant, element, 0, false)
if *printPrecisionStats {
t.Log(precStats.String())
}
rf64 := precStats.AVGLog2Prec.Real
if64 := precStats.AVGLog2Prec.Imag
rf64, _ := precStats.MeanPrecision.Real.Float64()
if64, _ := precStats.MeanPrecision.Imag.Float64()
minPrec := math.Log2(params.DefaultScale().Float64()) - float64(params.LogN()+2)
if minPrec < 0 {
@@ -1,13 +1,13 @@
package bootstrapping
import (
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
type defaultParametersLiteral struct {
SchemeParams ckks.ParametersLiteral
SchemeParams hefloat.ParametersLiteral
BootstrappingParams ParametersLiteral
}
@@ -31,7 +31,7 @@ var (
// Precision : 26.6 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1546H192H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{60, 40, 40, 40, 40, 40, 40, 40, 40, 40},
LogP: []int{61, 61, 61, 61, 61},
@@ -49,7 +49,7 @@ var (
// Precision : 32.1 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1547H192H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{60, 45, 45, 45, 45, 45},
LogP: []int{61, 61, 61, 61},
@@ -72,7 +72,7 @@ var (
// Precision : 19.1 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1553H192H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{55, 60, 60, 60, 60, 60, 60, 60},
LogP: []int{61, 61, 61, 61, 61},
@@ -94,7 +94,7 @@ var (
// Precision : 15.4 bits for 2^{14} slots.
// Failure : 2^{-139.7} for 2^{14} slots.
N15QP768H192H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 15,
LogQ: []int{33, 50, 25},
LogP: []int{51, 51},
@@ -116,7 +116,7 @@ var (
// Precision : 23.8 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1767H32768H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{60, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40},
LogP: []int{61, 61, 61, 61, 61, 61},
@@ -134,7 +134,7 @@ var (
// Precision : 29.8 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1788H32768H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{60, 45, 45, 45, 45, 45, 45, 45, 45, 45},
LogP: []int{61, 61, 61, 61, 61},
@@ -157,7 +157,7 @@ var (
// Precision : 17.8 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1793H32768H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{55, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 30},
LogP: []int{61, 61, 61, 61, 61},
@@ -179,7 +179,7 @@ var (
// Precision : 17.3 bits for 2^{14} slots.
// Failure : 2^{-139.7} for 2^{14} slots.
N15QP880H16384H32 = defaultParametersLiteral{
ckks.ParametersLiteral{
hefloat.ParametersLiteral{
LogN: 15,
LogQ: []int{40, 31, 31, 31, 31},
LogP: []int{56, 56},
@@ -5,49 +5,15 @@ import (
"time"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
)
func BenchmarkConcurrentBootstrap(b *testing.B) {
paramSet := DefaultParametersDense[0]
params, err := ckks.NewParametersFromLiteral(paramSet.SchemeParams)
require.NoError(b, err)
btpParams, err := NewParametersFromLiteral(params, paramSet.BootstrappingParams)
require.Nil(b, err)
kgen := rlwe.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
evk, _, err := btpParams.GenEvaluationKeys(sk)
require.NoError(b, err)
// Benchmark parallel bootstrapping
b.Run(ParamsToString(params, btpParams.LogMaxDimensions().Cols, "Bootstrap/"), func(b *testing.B) {
var err error
eval, err := NewEvaluator(btpParams, evk)
require.NoError(b, err)
ct1 := ckks.NewCiphertext(params, 1, 0)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err = eval.Bootstrap(ct1)
}
})
})
}
func BenchmarkBootstrap(b *testing.B) {
b.Skip()
paramSet := DefaultParametersDense[0]
params, err := ckks.NewParametersFromLiteral(paramSet.SchemeParams)
params, err := hefloat.NewParametersFromLiteral(paramSet.SchemeParams)
require.NoError(b, err)
btpParams, err := NewParametersFromLiteral(params, paramSet.BootstrappingParams)
@@ -59,16 +25,17 @@ func BenchmarkBootstrap(b *testing.B) {
evk, _, err := btpParams.GenEvaluationKeys(sk)
require.NoError(b, err)
eval, err := NewEvaluator(btpParams, evk)
require.NoError(b, err)
b.Run(ParamsToString(params, btpParams.LogMaxDimensions().Cols, "Bootstrap/"), func(b *testing.B) {
var err error
eval, err := NewEvaluator(btpParams, evk)
require.NoError(b, err)
for i := 0; i < b.N; i++ {
b.StopTimer()
ct := ckks.NewCiphertext(params, 1, 0)
ct := hefloat.NewCiphertext(params, 1, 0)
b.StartTimer()
var t time.Time
+23 -21
View File
@@ -7,16 +7,15 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var minPrec float64 = 12.0
func ParamsToString(params ckks.Parameters, LogSlots int, opname string) string {
func ParamsToString(params hefloat.Parameters, LogSlots int, opname string) string {
return fmt.Sprintf("%slogN=%d/LogSlots=%d/logQP=%f/levels=%d/a=%d/b=%d",
opname,
params.LogN(),
@@ -54,7 +53,7 @@ func TestParametersMarshalling(t *testing.T) {
t.Run("Parameters", func(t *testing.T) {
paramSet := DefaultParametersSparse[0]
params, err := ckks.NewParametersFromLiteral(paramSet.SchemeParams)
params, err := hefloat.NewParametersFromLiteral(paramSet.SchemeParams)
require.Nil(t, err)
btpParams, err := NewParametersFromLiteral(params, paramSet.BootstrappingParams)
@@ -94,7 +93,7 @@ func TestCircuitWithEncapsulation(t *testing.T) {
paramsSetCpy.BootstrappingParams.LogSlots = &LogSlots
params, err := ckks.NewParametersFromLiteral(paramsSetCpy.SchemeParams)
params, err := hefloat.NewParametersFromLiteral(paramsSetCpy.SchemeParams)
require.NoError(t, err)
btpParams, err := NewParametersFromLiteral(params, paramsSetCpy.BootstrappingParams)
@@ -137,7 +136,7 @@ func TestCircuitOriginal(t *testing.T) {
paramsSetCpy.BootstrappingParams.LogSlots = &LogSlots
params, err := ckks.NewParametersFromLiteral(paramsSetCpy.SchemeParams)
params, err := hefloat.NewParametersFromLiteral(paramsSetCpy.SchemeParams)
require.NoError(t, err)
btpParams, err := NewParametersFromLiteral(params, paramsSetCpy.BootstrappingParams)
@@ -156,13 +155,13 @@ func TestCircuitOriginal(t *testing.T) {
testRawCircuitHighPrecision(paramSet, t)
}
func testRawCircuit(params ckks.Parameters, btpParams Parameters, level int, t *testing.T) {
func testRawCircuit(params hefloat.Parameters, btpParams Parameters, level int, t *testing.T) {
t.Run(ParamsToString(params, btpParams.LogMaxSlots(), ""), func(t *testing.T) {
kgen := rlwe.NewKeyGenerator(btpParams.BootstrappingParameters)
sk := kgen.GenSecretKeyNew()
encoder := ckks.NewEncoder(params)
encoder := hefloat.NewEncoder(params)
encryptor := rlwe.NewEncryptor(params, sk)
decryptor := rlwe.NewDecryptor(params, sk)
@@ -186,19 +185,22 @@ func testRawCircuit(params ckks.Parameters, btpParams Parameters, level int, t *
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
plaintext := ckks.NewPlaintext(params, 0)
plaintext := hefloat.NewPlaintext(params, 0)
plaintext.Scale = params.DefaultScale()
plaintext.LogDimensions = btpParams.LogMaxDimensions()
encoder.Encode(values, plaintext)
n := 2
n := 1
ciphertexts := make([]*rlwe.Ciphertext, n)
evaluators := make([]*Evaluator, n)
evaluators[0] = eval
ciphertexts[0], err = encryptor.EncryptNew(plaintext)
require.NoError(t, err)
for i := 1; i < len(ciphertexts); i++ {
ciphertexts[i], err = encryptor.EncryptNew(plaintext)
require.NoError(t, err)
evaluators[i] = evaluators[0].ShallowCopy()
}
var wg sync.WaitGroup
@@ -206,7 +208,7 @@ func testRawCircuit(params ckks.Parameters, btpParams Parameters, level int, t *
for i := range ciphertexts {
go func(index int) {
var err error
ciphertexts[index], err = eval.Evaluate(ciphertexts[index])
ciphertexts[index], err = evaluators[index].Evaluate(ciphertexts[index])
require.NoError(t, err)
wg.Done()
}(i)
@@ -238,7 +240,7 @@ func testRawCircuitHighPrecision(paramSet defaultParametersLiteral, t *testing.T
ReservedPrimeBitSize: 28,
}
params, err := ckks.NewParametersFromLiteral(paramSet.SchemeParams)
params, err := hefloat.NewParametersFromLiteral(paramSet.SchemeParams)
if err != nil {
panic(err)
}
@@ -259,7 +261,7 @@ func testRawCircuitHighPrecision(paramSet defaultParametersLiteral, t *testing.T
kgen := rlwe.NewKeyGenerator(btpParams.BootstrappingParameters)
sk := kgen.GenSecretKeyNew()
encoder := ckks.NewEncoder(params)
encoder := hefloat.NewEncoder(params)
encryptor := rlwe.NewEncryptor(params, sk)
decryptor := rlwe.NewDecryptor(params, sk)
@@ -282,7 +284,7 @@ func testRawCircuitHighPrecision(paramSet defaultParametersLiteral, t *testing.T
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
plaintext := ckks.NewPlaintext(params, level)
plaintext := hefloat.NewPlaintext(params, level)
plaintext.Scale = params.DefaultScale()
plaintext.LogDimensions = btpParams.LogMaxDimensions()
@@ -303,14 +305,14 @@ func testRawCircuitHighPrecision(paramSet defaultParametersLiteral, t *testing.T
})
}
func verifyTestVectors(params ckks.Parameters, encoder *ckks.Encoder, decryptor *rlwe.Decryptor, valuesWant, valuesHave interface{}, t *testing.T) {
precStats := ckks.GetPrecisionStats(params, encoder, decryptor, valuesWant, valuesHave, 0, false)
func verifyTestVectors(params hefloat.Parameters, encoder *hefloat.Encoder, decryptor *rlwe.Decryptor, valuesWant, valuesHave interface{}, t *testing.T) {
precStats := hefloat.GetPrecisionStats(params, encoder, decryptor, valuesWant, valuesHave, 0, false)
if *printPrecisionStats {
t.Log(precStats.String())
}
rf64 := precStats.AVGLog2Prec.Real
if64 := precStats.AVGLog2Prec.Imag
rf64, _ := precStats.MeanPrecision.Real.Float64()
if64, _ := precStats.MeanPrecision.Imag.Float64()
require.GreaterOrEqual(t, rf64, minPrec)
require.GreaterOrEqual(t, if64, minPrec)
+42 -53
View File
@@ -6,26 +6,25 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/dft"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/mod1"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/schemes/ckks"
"github.com/luxdefi/lattice/v5/utils"
)
// Parameters is a struct storing the parameters
// of the bootstrapping circuit.
type Parameters struct {
// ResidualParameters: Parameters outside of the bootstrapping circuit
ResidualParameters ckks.Parameters
ResidualParameters hefloat.Parameters
// BootstrappingParameters: Parameters during the bootstrapping circuit
BootstrappingParameters ckks.Parameters
BootstrappingParameters hefloat.Parameters
// SlotsToCoeffsParameters Parameters of the homomorphic decoding linear transformation
SlotsToCoeffsParameters dft.MatrixLiteral
SlotsToCoeffsParameters hefloat.DFTMatrixLiteral
// Mod1ParametersLiteral: Parameters of the homomorphic modular reduction
Mod1ParametersLiteral mod1.ParametersLiteral
Mod1ParametersLiteral hefloat.Mod1ParametersLiteral
// CoeffsToSlotsParameters: Parameters of the homomorphic encoding linear transformation
CoeffsToSlotsParameters dft.MatrixLiteral
CoeffsToSlotsParameters hefloat.DFTMatrixLiteral
// IterationsParameters: Parameters of the bootstrapping iterations (META-BTS)
IterationsParameters *IterationsParameters
// EphemeralSecretWeight: Hamming weight of the ephemeral secret. If 0, no ephemeral secret is used during the bootstrapping.
@@ -34,21 +33,21 @@ type Parameters struct {
CircuitOrder CircuitOrder
}
// NewParametersFromLiteral instantiates a [Parameters] from the residual [ckks.Parameters] and
// a [ParametersLiteral] struct.
// NewParametersFromLiteral instantiates a Parameters from the residual hefloat.Parameters and
// a bootstrapping.ParametersLiteral struct.
//
// The residualParameters corresponds to the [ckks.Parameters] that are left after the bootstrapping circuit is evaluated.
// The residualParameters corresponds to the hefloat.Parameters that are left after the bootstrapping circuit is evaluated.
// These are entirely independent of the bootstrapping parameters with one exception: the ciphertext primes Qi must be
// congruent to 1 mod 2N of the bootstrapping parameters (note that the auxiliary primes Pi do not need to be).
// This is required because the primes Qi of the residual parameters and the bootstrapping parameters are the same between
// the two sets of parameters.
//
// The user can ensure that this condition is met by setting the appropriate LogNThRoot in the [ckks.ParametersLiteral] before
// The user can ensure that this condition is met by setting the appropriate LogNThRoot in the hefloat.ParametersLiteral before
// instantiating them.
//
// The method NewParametersFromLiteral will automatically allocate the [ckks.Parameters] of the bootstrapping circuit based on
// the provided residualParameters and the information given in the [ParametersLiteral].
func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit ParametersLiteral) (Parameters, error) {
// The method NewParametersFromLiteral will automatically allocate the hefloat.Parameters of the bootstrapping circuit based on
// the provided residualParameters and the information given in the bootstrapping.ParametersLiteral.
func NewParametersFromLiteral(residualParameters hefloat.Parameters, btpLit ParametersLiteral) (Parameters, error) {
var err error
@@ -67,7 +66,6 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
}
// Takes the greatest NthRoot between the residualParameters NthRoot and the bootstrapping NthRoot
/* #nosec G115 -- N cannot be negative */
NthRoot = utils.Max(uint64(residualParameters.N()<<2), uint64(2<<LogN))
default:
@@ -78,7 +76,6 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
}
// Takes the greatest NthRoot between the residualParameters NthRoot and the bootstrapping NthRoot
/* #nosec G115 -- N cannot be negative */
NthRoot = utils.Max(uint64(residualParameters.N()<<1), uint64(2<<LogN))
}
@@ -126,11 +123,11 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
}
// SlotsToCoeffs parameters (homomorphic decoding)
S2CParams := dft.MatrixLiteral{
Type: dft.HomomorphicDecode,
S2CParams := hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicDecode,
LogSlots: LogSlots,
Format: dft.RepackImagAsReal,
LevelQ: residualParameters.MaxLevel() + len(SlotsToCoeffsFactorizationDepthAndLogScales) + hasReservedIterationPrime,
Format: hefloat.RepackImagAsReal,
LevelStart: residualParameters.MaxLevel() + len(SlotsToCoeffsFactorizationDepthAndLogScales) + hasReservedIterationPrime,
LogBSGSRatio: 1,
Levels: SlotsToCoeffsLevels,
}
@@ -175,7 +172,7 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
}
// Parameters of the homomorphic modular reduction x mod 1
Mod1ParametersLiteral := mod1.ParametersLiteral{
Mod1ParametersLiteral := hefloat.Mod1ParametersLiteral{
LogScale: EvalMod1LogScale,
Mod1Type: Mod1Type,
Mod1Degree: Mod1Degree,
@@ -193,7 +190,7 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
}
// Coeffs To Slots params
Mod1ParametersLiteral.LevelQ = S2CParams.LevelQ + Mod1ParametersLiteral.Depth()
Mod1ParametersLiteral.LevelStart = S2CParams.LevelStart + Mod1ParametersLiteral.Depth()
CoeffsToSlotsLevels := make([]int, len(CoeffsToSlotsFactorizationDepthAndLogScales))
for i := range CoeffsToSlotsLevels {
@@ -201,11 +198,11 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
}
// Parameters of the CoeffsToSlots (homomorphic encoding)
C2SParams := dft.MatrixLiteral{
Type: dft.HomomorphicEncode,
Format: dft.RepackImagAsReal,
C2SParams := hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicEncode,
Format: hefloat.RepackImagAsReal,
LogSlots: LogSlots,
LevelQ: Mod1ParametersLiteral.LevelQ + len(CoeffsToSlotsFactorizationDepthAndLogScales),
LevelStart: Mod1ParametersLiteral.LevelStart + len(CoeffsToSlotsFactorizationDepthAndLogScales),
LogBSGSRatio: 1,
Levels: CoeffsToSlotsLevels,
}
@@ -261,14 +258,11 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
// Retrieve the number of primes #Pi of the bootstrapping circuit
// and adds them to the list of bit-size
LogP := btpLit.GetLogP(C2SParams.LevelQ + 1)
LogP := btpLit.GetLogP(C2SParams.LevelStart + 1)
for _, logpi := range LogP {
primesBitLenNew[logpi]++
}
S2CParams.LevelP = len(LogP) - 1
C2SParams.LevelP = len(LogP) - 1
// Map to store [bit-size][]primes
primesNew := map[int][]uint64{}
@@ -276,7 +270,6 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
for logqi, k := range primesBitLenNew {
// Creates a new prime generator
/* #nosec G115 -- logqi cannot be negative */
g := ring.NewNTTFriendlyPrimesGenerator(uint64(logqi), NthRoot)
// Populates the list with primes that aren't yet in primesHave
@@ -329,8 +322,8 @@ func NewParametersFromLiteral(residualParameters ckks.Parameters, btpLit Paramet
LogDefaultScale = residualParameters.LogQi()[0] - LogMessageRatio
}
// Instantiates the ckks.Parameters of the bootstrapping circuit.
params, err := ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
// Instantiates the hefloat.Parameters of the bootstrapping circuit.
params, err := hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: LogN,
Q: Q,
P: P,
@@ -396,27 +389,26 @@ func (p Parameters) Depth() (depth int) {
}
// MarshalBinary returns a JSON representation of the Parameters struct.
// See Marshal from the [encoding/json] package.
// See `Marshal` from the `encoding/json` package.
func (p Parameters) MarshalBinary() (data []byte, err error) {
return json.Marshal(p)
}
// UnmarshalBinary reads a JSON representation on the target Parameters struct.
// See Unmarshal from the [encoding/json] package.
// See `Unmarshal` from the `encoding/json` package.
func (p *Parameters) UnmarshalBinary(data []byte) (err error) {
return json.Unmarshal(data, p)
}
func (p Parameters) MarshalJSON() (data []byte, err error) {
return json.Marshal(struct {
ResidualParameters ckks.Parameters
BootstrappingParameters ckks.Parameters
SlotsToCoeffsParameters dft.MatrixLiteral
Mod1ParametersLiteral mod1.ParametersLiteral
CoeffsToSlotsParameters dft.MatrixLiteral
ResidualParameters hefloat.Parameters
BootstrappingParameters hefloat.Parameters
SlotsToCoeffsParameters hefloat.DFTMatrixLiteral
Mod1ParametersLiteral hefloat.Mod1ParametersLiteral
CoeffsToSlotsParameters hefloat.DFTMatrixLiteral
IterationsParameters *IterationsParameters
EphemeralSecretWeight int
CircuitOrder int
}{
ResidualParameters: p.ResidualParameters,
BootstrappingParameters: p.BootstrappingParameters,
@@ -425,20 +417,18 @@ func (p Parameters) MarshalJSON() (data []byte, err error) {
CoeffsToSlotsParameters: p.CoeffsToSlotsParameters,
IterationsParameters: p.IterationsParameters,
EphemeralSecretWeight: p.EphemeralSecretWeight,
CircuitOrder: int(p.CircuitOrder),
})
}
func (p *Parameters) UnmarshalJSON(data []byte) (err error) {
var params struct {
ResidualParameters ckks.Parameters
BootstrappingParameters ckks.Parameters
SlotsToCoeffsParameters dft.MatrixLiteral
Mod1ParametersLiteral mod1.ParametersLiteral
CoeffsToSlotsParameters dft.MatrixLiteral
ResidualParameters hefloat.Parameters
BootstrappingParameters hefloat.Parameters
SlotsToCoeffsParameters hefloat.DFTMatrixLiteral
Mod1ParametersLiteral hefloat.Mod1ParametersLiteral
CoeffsToSlotsParameters hefloat.DFTMatrixLiteral
IterationsParameters *IterationsParameters
EphemeralSecretWeight int
CircuitOrder int
}
if err = json.Unmarshal(data, &params); err != nil {
@@ -452,13 +442,12 @@ func (p *Parameters) UnmarshalJSON(data []byte) (err error) {
p.CoeffsToSlotsParameters = params.CoeffsToSlotsParameters
p.IterationsParameters = params.IterationsParameters
p.EphemeralSecretWeight = params.EphemeralSecretWeight
p.CircuitOrder = CircuitOrder(params.CircuitOrder)
return
}
// GaloisElements returns the list of Galois elements required to evaluate the bootstrapping.
func (p Parameters) GaloisElements(params ckks.Parameters) (galEls []uint64) {
func (p Parameters) GaloisElements(params hefloat.Parameters) (galEls []uint64) {
logN := params.LogN()
@@ -6,15 +6,15 @@ import (
"math"
"math/bits"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/mod1"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
// ParametersLiteral is a struct to parameterize the bootstrapping parameters.
// The ParametersLiteral struct an unchecked struct that is given to the method [NewParametersFromLiteral] to validate them
// and create the bootstrapping [Parameters] struct, which is used to instantiate a Bootstrapper.
// The `ParametersLiteral` struct an unchecked struct that is given to the method `NewParametersFromLiteral` to validate them
// and create the bootstrapping `Parameter` struct, which is used to instantiate a `Bootstrapper`.
// This struct contains only optional fields.
// The default bootstrapping (with no optional field) has
// - Depth 4 for CoeffsToSlots
@@ -22,8 +22,8 @@ import (
// - Depth 3 for SlotsToCoeffs
// for a total depth of 15 and a bit consumption of 821
// A precision, for complex values with both real and imaginary parts uniformly distributed in -1, 1 of
// - 27.9 bits (27.4 L2) for H=192
// - 24.4 bits (23.9 L2) for H=32768,
// - 27.25 bits for H=192
// - 23.8 bits for H=32768,
// And a failure probability of 2^{-138.7} for 2^{15} slots.
//
// =====================================
@@ -67,8 +67,8 @@ import (
// See https://eprint.iacr.org/2022/024 for more information.
//
// IterationsParameters : by treating the bootstrapping as a black box with precision logprec, we can construct a bootstrapping of precision ~k*logprec by iteration (see https://eprint.iacr.org/2022/1167).
// - BootstrappingPrecision: []float64, the list of iterations (after the initial bootstrapping) given by the expected precision of each previous iteration.
// - ReservedPrimeBitSize: the size of the reserved prime for the scaling after the initial bootstrapping.
// - BootstrappingPrecision: []float64, the list of iterations (after the initial bootstrapping) given by the expected precision of each previous iteration.
// - ReservedPrimeBitSize: the size of the reserved prime for the scaling after the initial bootstrapping.
//
// For example: &bootstrapping.IterationsParameters{BootstrappingPrecision: []float64{16}, ReservedPrimeBitSize: 16} will define a two iteration bootstrapping (the first iteration being the initial bootstrapping)
// with a additional prime close to 2^{16} reserved for the scaling of the error during the second iteration.
@@ -76,21 +76,19 @@ import (
// Here is an example for a two iterations bootstrapping of an input message mod [logq0=55, logq1=45] with scaling factor 2^{90}:
//
// INPUT:
// 1. The input is a ciphertext encrypting [2^{90} * M]_{q0, q1}
//
// 1) The input is a ciphertext encrypting [2^{90} * M]_{q0, q1}
// ITERATION N°0
// 2. Rescale [M^{90}]_{q0, q1} to [M^{90}/q1]_{q0} (ensure that M^{90}/q1 ~ q0/message-ratio by additional scaling if necessary)
// 3. Bootstrap [M^{90}/q1]_{q0} to [M^{90}/q1 + e^{90 - logprec}/q1]_{q0, q1, q2, ...}
// 4. Scale up [M^{90}/q1 + e^{90 - logprec}/q1]_{q0, q1, q2, ...} to [M^{d} + e^{d - logprec}]_{q0, q1, q2, ...}
//
// 2) Rescale [M^{90}]_{q0, q1} to [M^{90}/q1]_{q0} (ensure that M^{90}/q1 ~ q0/messageratio by additional scaling if necessary)
// 3) Bootsrap [M^{90}/q1]_{q0} to [M^{90}/q1 + e^{90 - logprec}/q1]_{q0, q1, q2, ...}
// 4) Scale up [M^{90}/q1 + e^{90 - logprec}/q1]_{q0, q1, q2, ...} to [M^{d} + e^{d - logprec}]_{q0, q1, q2, ...}
// ITERATION N°1
// 5. Subtract [M^{d}]_{q0, q1} to [M^{d} + e^{d - logprec}]_{q0, q1, q2, ...} to get [e^{d - logprec}]_{q0, q1}
// 6. Scale up [e^{90 - logprec}]_{q0, q1} by 2^{logprec} to get [e^{d}]_{q0, q1}
// 7. Rescale [e^{90}]_{q0, q1} to [{90}/q1]_{q0}
// 8. Bootstrap [e^{90}/q1]_{q0} to [e^{90}/q1 + e'^{90 - logprec}/q1]_{q0, q1, q2, ...}
// 9. Scale up [e^{90}/q1 + e'^{90 - logprec}/q0]_{q0, q1, q2, ...} by round(q1/2^{logprec}) to get [e^{90-logprec} + e'^{90 - 2logprec}]_{q0, q1, q2, ...}
// 10. Subtract [e^{d - logprec} + e'^{d - 2logprec}]_{q0, q1, q2, ...} to [M^{d} + e^{d - logprec}]_{q0, q1, q2, ...} to get [M^{d} + e'^{d - 2logprec}]_{q0, q1, q2, ...}
// 11. Go back to step 5 for more iterations until 2^{k * logprec} >= 2^{90}
// 5) Subtract [M^{d}]_{q0, q1} to [M^{d} + e^{d - logprec}]_{q0, q1, q2, ...} to get [e^{d - logprec}]_{q0, q1}
// 6) Scale up [e^{90 - logprec}]_{q0, q1} by 2^{logprec} to get [e^{d}]_{q0, q1}
// 7) Rescale [e^{90}]_{q0, q1} to [{90}/q1]_{q0}
// 8) Bootsrap [e^{90}/q1]_{q0} to [e^{90}/q1 + e'^{90 - logprec}/q1]_{q0, q1, q2, ...}
// 9) Scale up [e^{90}/q1 + e'^{90 - logprec}/q0]_{q0, q1, q2, ...} by round(q1/2^{logprec}) to get [e^{90-logprec} + e'^{90 - 2logprec}]_{q0, q1, q2, ...}
// 10) Subtract [e^{d - logprec} + e'^{d - 2logprec}]_{q0, q1, q2, ...} to [M^{d} + e^{d - logprec}]_{q0, q1, q2, ...} to get [M^{d} + e'^{d - 2logprec}]_{q0, q1, q2, ...}
// 11) Go back to step 5 for more iterations until 2^{k * logprec} >= 2^{90}
//
// This example can be generalized to input messages of any scaling factor and desired output precision by increasing the input scaling factor and substituting q1 by a larger product of primes.
//
@@ -113,7 +111,7 @@ import (
// When using a small ratio (i.e. 2^4), for example if ct.PlaintextScale is close to Q[0] is small or if |m| is large, the Mod1InvDegree can be set to
// a non zero value (i.e. 5 or 7). This will greatly improve the precision of the bootstrapping, at the expense of slightly increasing its depth.
//
// Mod1Type: the type of approximation for the modular reduction polynomial. By default set to mod1.CosDiscrete.
// Mod1Type: the type of approximation for the modular reduction polynomial. By default set to hefloat.CosDiscrete.
//
// K: the range of the approximation interval, by default set to 16.
//
@@ -133,7 +131,7 @@ type ParametersLiteral struct {
EvalModLogScale *int // Default: 60
EphemeralSecretWeight *int // Default: 32
IterationsParameters *IterationsParameters // Default: nil (default starting level of 0 and 1 iteration)
Mod1Type mod1.Type // Default: mod1.CosDiscrete
Mod1Type hefloat.Mod1Type // Default: hefloat.CosDiscrete
LogMessageRatio *int // Default: 8
K *int // Default: 16
Mod1Degree *int // Default: 30
@@ -167,7 +165,7 @@ const (
// DefaultIterations is the default number of bootstrapping iterations.
DefaultIterations = 1
// DefaultMod1Type is the default function and approximation technique for the homomorphic modular reduction polynomial.
DefaultMod1Type = mod1.CosDiscrete
DefaultMod1Type = hefloat.CosDiscrete
// DefaultLogMessageRatio is the default ratio between Q[0] and |m|.
DefaultLogMessageRatio = 8
// DefaultK is the default interval [-K+1, K-1] for the polynomial approximation of the homomorphic modular reduction.
@@ -192,19 +190,19 @@ type IterationsParameters struct {
ReservedPrimeBitSize int
}
// MarshalBinary returns a JSON representation of the target ParametersLiteral struct on a slice of bytes.
// See Marshal from the [encoding/json] package.
// MarshalBinary returns a JSON representation of the the target ParametersLiteral struct on a slice of bytes.
// See `Marshal` from the `encoding/json` package.
func (p ParametersLiteral) MarshalBinary() (data []byte, err error) {
return json.Marshal(p)
}
// UnmarshalBinary reads a JSON representation on the target ParametersLiteral struct.
// See Unmarshal from the [encoding/json] package.
// See `Unmarshal` from the `encoding/json` package.
func (p *ParametersLiteral) UnmarshalBinary(data []byte) (err error) {
return json.Unmarshal(data, p)
}
// GetLogN returns the LogN field of the target [ParametersLiteral].
// GetLogN returns the LogN field of the target ParametersLiteral.
// The default value DefaultLogN is returned if the field is nil.
func (p ParametersLiteral) GetLogN() (LogN int) {
if v := p.LogN; v == nil {
@@ -216,7 +214,7 @@ func (p ParametersLiteral) GetLogN() (LogN int) {
return
}
// GetDefaultXs returns the Xs field of the target [ParametersLiteral].
// GetDefaultXs returns the Xs field of the target ParametersLiteral.
// The default value DefaultXs is returned if the field is nil.
func (p ParametersLiteral) GetDefaultXs() (Xs ring.DistributionParameters) {
if v := p.Xs; v == nil {
@@ -228,7 +226,7 @@ func (p ParametersLiteral) GetDefaultXs() (Xs ring.DistributionParameters) {
return
}
// GetDefaultXe returns the Xe field of the target [ParametersLiteral].
// GetDefaultXe returns the Xe field of the target ParametersLiteral.
// The default value DefaultXe is returned if the field is nil.
func (p ParametersLiteral) GetDefaultXe() (Xe ring.DistributionParameters) {
if v := p.Xe; v == nil {
@@ -256,7 +254,7 @@ func (p ParametersLiteral) GetLogP(NumberOfQi int) (LogP []int) {
return
}
// GetLogSlots returns the LogSlots field of the target [ParametersLiteral].
// GetLogSlots returns the LogSlots field of the target ParametersLiteral.
// The default value LogN-1 is returned if the field is nil.
func (p ParametersLiteral) GetLogSlots() (LogSlots int, err error) {
@@ -276,8 +274,8 @@ func (p ParametersLiteral) GetLogSlots() (LogSlots int, err error) {
return
}
// GetCoeffsToSlotsFactorizationDepthAndLogScales returns a copy of the CoeffsToSlotsFactorizationDepthAndLogScales field of the target [ParametersLiteral].
// The default value constructed from [DefaultSlotsToCoeffsFactorizationDepth] and [DefaultSlotsToCoeffsLogScale] is returned if the field is nil.
// GetCoeffsToSlotsFactorizationDepthAndLogScales returns a copy of the CoeffsToSlotsFactorizationDepthAndLogScales field of the target ParametersLiteral.
// The default value constructed from DefaultC2SFactorization and DefaultC2SLogScale is returned if the field is nil.
func (p ParametersLiteral) GetCoeffsToSlotsFactorizationDepthAndLogScales(LogSlots int) (CoeffsToSlotsFactorizationDepthAndLogScales [][]int, err error) {
if p.CoeffsToSlotsFactorizationDepthAndLogScales == nil {
CoeffsToSlotsFactorizationDepthAndLogScales = make([][]int, utils.Min(DefaultCoeffsToSlotsFactorizationDepth, utils.Max(LogSlots, 1)))
@@ -299,8 +297,8 @@ func (p ParametersLiteral) GetCoeffsToSlotsFactorizationDepthAndLogScales(LogSlo
return
}
// GetSlotsToCoeffsFactorizationDepthAndLogScales returns a copy of the SlotsToCoeffsFactorizationDepthAndLogScales field of the target [ParametersLiteral].
// The default value constructed from [DefaultSlotsToCoeffsFactorizationDepth] and [DefaultSlotsToCoeffsLogScale] is returned if the field is nil.
// GetSlotsToCoeffsFactorizationDepthAndLogScales returns a copy of the SlotsToCoeffsFactorizationDepthAndLogScales field of the target ParametersLiteral.
// The default value constructed from DefaultS2CFactorization and DefaultS2CLogScale is returned if the field is nil.
func (p ParametersLiteral) GetSlotsToCoeffsFactorizationDepthAndLogScales(LogSlots int) (SlotsToCoeffsFactorizationDepthAndLogScales [][]int, err error) {
if p.SlotsToCoeffsFactorizationDepthAndLogScales == nil {
SlotsToCoeffsFactorizationDepthAndLogScales = make([][]int, utils.Min(DefaultSlotsToCoeffsFactorizationDepth, utils.Max(LogSlots, 1)))
@@ -322,8 +320,8 @@ func (p ParametersLiteral) GetSlotsToCoeffsFactorizationDepthAndLogScales(LogSlo
return
}
// GetEvalMod1LogScale returns the EvalModLogScale field of the target [ParametersLiteral].
// The default value [DefaultEvalModLogScale] is returned if the field is nil.
// GetEvalMod1LogScale returns the EvalModLogScale field of the target ParametersLiteral.
// The default value DefaultEvalModLogScale is returned if the field is nil.
func (p ParametersLiteral) GetEvalMod1LogScale() (EvalModLogScale int, err error) {
if v := p.EvalModLogScale; v == nil {
EvalModLogScale = DefaultEvalModLogScale
@@ -339,7 +337,7 @@ func (p ParametersLiteral) GetEvalMod1LogScale() (EvalModLogScale int, err error
return
}
// GetIterationsParameters returns the [IterationsParameters] field of the target [ParametersLiteral].
// GetIterationsParameters returns the IterationsParameters field of the target ParametersLiteral.
// The default value is nil.
func (p ParametersLiteral) GetIterationsParameters() (Iterations *IterationsParameters, err error) {
@@ -365,8 +363,8 @@ func (p ParametersLiteral) GetIterationsParameters() (Iterations *IterationsPara
}
}
// GetLogMessageRatio returns the []LogMessageRatio field of the target [ParametersLiteral].
// The default value [DefaultLogMessageRatio] is returned if the field is nil.
// GetLogMessageRatio returns the LogMessageRatio field of the target ParametersLiteral.
// The default value DefaultLogMessageRatio is returned if the field is nil.
func (p ParametersLiteral) GetLogMessageRatio() (LogMessageRatio int, err error) {
if v := p.LogMessageRatio; v == nil {
LogMessageRatio = DefaultLogMessageRatio
@@ -381,8 +379,8 @@ func (p ParametersLiteral) GetLogMessageRatio() (LogMessageRatio int, err error)
return
}
// GetK returns the K field of the target [ParametersLiteral].
// The default value [DefaultK] is returned if the field is nil.
// GetK returns the K field of the target ParametersLiteral.
// The default value DefaultK is returned if the field is nil.
func (p ParametersLiteral) GetK() (K int, err error) {
if v := p.K; v == nil {
K = DefaultK
@@ -399,18 +397,18 @@ func (p ParametersLiteral) GetK() (K int, err error) {
// GetMod1Type returns the Mod1Type field of the target ParametersLiteral.
// The default value DefaultMod1Type is returned if the field is nil.
func (p ParametersLiteral) GetMod1Type() (Mod1Type mod1.Type) {
func (p ParametersLiteral) GetMod1Type() (Mod1Type hefloat.Mod1Type) {
return p.Mod1Type
}
// GetDoubleAngle returns the DoubleAngle field of the target [ParametersLiteral].
// The default value [DefaultDoubleAngle] is returned if the field is nil.
// GetDoubleAngle returns the DoubleAngle field of the target ParametersLiteral.
// The default value DefaultDoubleAngle is returned if the field is nil.
func (p ParametersLiteral) GetDoubleAngle() (DoubleAngle int, err error) {
if v := p.DoubleAngle; v == nil {
switch p.GetMod1Type() {
case mod1.SinContinuous:
case hefloat.SinContinuous:
DoubleAngle = 0
default:
DoubleAngle = DefaultDoubleAngle
@@ -426,8 +424,8 @@ func (p ParametersLiteral) GetDoubleAngle() (DoubleAngle int, err error) {
return
}
// GetMod1Degree returns the Mod1Degree field of the target [ParametersLiteral].
// The default value [DefaultMod1Degree] is returned if the field is nil.
// GetMod1Degree returns the Mod1Degree field of the target ParametersLiteral.
// The default value DefaultMod1Degree is returned if the field is nil.
func (p ParametersLiteral) GetMod1Degree() (Mod1Degree int, err error) {
if v := p.Mod1Degree; v == nil {
Mod1Degree = DefaultMod1Degree
@@ -441,8 +439,8 @@ func (p ParametersLiteral) GetMod1Degree() (Mod1Degree int, err error) {
return
}
// GetMod1InvDegree returns the Mod1InvDegree field of the target [ParametersLiteral].
// The default value [DefaultMod1InvDegree] is returned if the field is nil.
// GetMod1InvDegree returns the Mod1InvDegree field of the target ParametersLiteral.
// The default value DefaultMod1InvDegree is returned if the field is nil.
func (p ParametersLiteral) GetMod1InvDegree() (Mod1InvDegree int, err error) {
if v := p.Mod1InvDegree; v == nil {
Mod1InvDegree = DefaultMod1InvDegree
@@ -457,8 +455,8 @@ func (p ParametersLiteral) GetMod1InvDegree() (Mod1InvDegree int, err error) {
return
}
// GetEphemeralSecretWeight returns the EphemeralSecretWeight field of the target [ParametersLiteral].
// The default value [DefaultEphemeralSecretWeight] is returned if the field is nil.
// GetEphemeralSecretWeight returns the EphemeralSecretWeight field of the target ParametersLiteral.
// The default value DefaultEphemeralSecretWeight is returned if the field is nil.
func (p ParametersLiteral) GetEphemeralSecretWeight() (EphemeralSecretWeight int, err error) {
if v := p.EphemeralSecretWeight; v == nil {
EphemeralSecretWeight = DefaultEphemeralSecretWeight
@@ -473,7 +471,7 @@ func (p ParametersLiteral) GetEphemeralSecretWeight() (EphemeralSecretWeight int
}
// BitConsumption returns the expected consumption in bits of
// bootstrapping circuit of the target [ParametersLiteral].
// bootstrapping circuit of the target ParametersLiteral.
// The value is rounded up and thus will overestimate the value by up to 1 bit.
func (p ParametersLiteral) BitConsumption(LogSlots int) (logQ int, err error) {
@@ -529,7 +527,6 @@ func (p ParametersLiteral) BitConsumption(LogSlots int) (logQ int, err error) {
ReservedPrimeBitSize = Iterations.ReservedPrimeBitSize
}
/* #nosec G115 -- Mod1Degree, Mod1InvDegree cannot be negative */
logQ += 1 + EvalModLogPlaintextScale*(bits.Len64(uint64(Mod1Degree))+DoubleAngle+bits.Len64(uint64(Mod1InvDegree))) + ReservedPrimeBitSize
return
+12 -11
View File
@@ -1,39 +1,40 @@
package bootstrapping
import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// SecretKeyBootstrapper is an implementation of the [rlwe.Bootstrapping] interface that
// SecretKeyBootstrapper is an implementation of the rlwe.Bootstrapping interface that
// uses the secret-key to decrypt and re-encrypt the bootstrapped ciphertext.
type SecretKeyBootstrapper struct {
ckks.Parameters
*ckks.Encoder
hefloat.Parameters
*hefloat.Encoder
*rlwe.Decryptor
*rlwe.Encryptor
sk *rlwe.SecretKey
Values []*bignum.Complex
Counter int // records the number of bootstrapping
MinLevel int
}
func NewSecretKeyBootstrapper(params ckks.Parameters, sk *rlwe.SecretKey) *SecretKeyBootstrapper {
func NewSecretKeyBootstrapper(params hefloat.Parameters, sk *rlwe.SecretKey) *SecretKeyBootstrapper {
return &SecretKeyBootstrapper{
Parameters: params,
Encoder: ckks.NewEncoder(params),
Encoder: hefloat.NewEncoder(params),
Decryptor: rlwe.NewDecryptor(params, sk),
Encryptor: rlwe.NewEncryptor(params, sk),
sk: sk,
}
Values: make([]*bignum.Complex, params.N())}
}
func (d *SecretKeyBootstrapper) Bootstrap(ct *rlwe.Ciphertext) (*rlwe.Ciphertext, error) {
values := make([]*bignum.Complex, 1<<ct.LogDimensions.Cols)
values := d.Values[:1<<ct.LogDimensions.Cols]
if err := d.Decode(d.DecryptNew(ct), values); err != nil {
return nil, err
}
pt := ckks.NewPlaintext(d.Parameters, d.MaxLevel())
pt := hefloat.NewPlaintext(d.Parameters, d.MaxLevel())
pt.MetaData = ct.MetaData
pt.Scale = d.Parameters.DefaultScale()
if err := d.Encode(values, pt); err != nil {
+25 -35
View File
@@ -1,26 +1,24 @@
// Package comparison implements homomorphic comparisons for the CKKS scheme.
package comparison
package hefloat
import (
"math/big"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/minimax"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Evaluator is an evaluator providing an API for homomorphic comparisons.
// ComparisonEvaluator is an evaluator providing an API for homomorphic comparisons.
// All fields of this struct are public, enabling custom instantiations.
type Evaluator struct {
Parameters ckks.Parameters
*minimax.Evaluator
MinimaxCompositeSignPolynomial minimax.Polynomial
type ComparisonEvaluator struct {
MinimaxCompositePolynomialEvaluator
MinimaxCompositeSignPolynomial MinimaxCompositePolynomial
}
// NewEvaluator instantiates a new ComparisonEvaluator.
// The default ckks.Evaluator is compliant with the EvaluatorForMinimaxCompositePolynomial interface.
// NewComparisonEvaluator instantiates a new ComparisonEvaluator.
// The default hefloat.Evaluator is compliant with the EvaluatorForMinimaxCompositePolynomial interface.
// The field he.Bootstrapper[rlwe.Ciphertext] can be nil if the parameters have enough level to support the computation.
//
// Giving a MinimaxCompositePolynomial is optional, but it is highly recommended to provide one that is optimized
// for the circuit requiring the comparisons as this polynomial will define the internal precision of all computations
@@ -34,23 +32,15 @@ type Evaluator struct {
// See the doc of DefaultMinimaxCompositePolynomialForSign for additional information about the performance of this approximation.
//
// This method is allocation free if a MinimaxCompositePolynomial is given.
func NewEvaluator(params ckks.Parameters, eval *minimax.Evaluator, signPoly ...minimax.Polynomial) *Evaluator {
func NewComparisonEvaluator(params Parameters, eval EvaluatorForMinimaxCompositePolynomial, bootstrapper he.Bootstrapper[rlwe.Ciphertext], signPoly ...MinimaxCompositePolynomial) *ComparisonEvaluator {
if len(signPoly) == 1 {
return &Evaluator{
Parameters: params,
Evaluator: eval,
MinimaxCompositeSignPolynomial: signPoly[0],
}
return &ComparisonEvaluator{*NewMinimaxCompositePolynomialEvaluator(params, eval, bootstrapper), signPoly[0]}
} else {
return &Evaluator{
Parameters: params,
Evaluator: eval,
MinimaxCompositeSignPolynomial: minimax.NewPolynomial(DefaultCompositePolynomialForSign),
}
return &ComparisonEvaluator{*NewMinimaxCompositePolynomialEvaluator(params, eval, bootstrapper), NewMinimaxCompositePolynomial(DefaultMinimaxCompositePolynomialForSign)}
}
}
// DefaultCompositePolynomialForSign is an example of composite minimax polynomial
// DefaultMinimaxCompositePolynomialForSign is an example of composite minimax polynomial
// for the sign function that is able to distinguish between value with a delta of up to
// 2^{-alpha=30}, tolerates a scheme error of 2^{-35} and outputs a binary value (-1, or 1)
// of up to 20x4 bits of precision.
@@ -58,7 +48,7 @@ func NewEvaluator(params ckks.Parameters, eval *minimax.Evaluator, signPoly ...m
// It was computed with GenMinimaxCompositePolynomialForSign(256, 30, 35, []int{15, 15, 15, 17, 31, 31, 31, 31})
// which outputs a minimax composite polynomial of precision 21.926741, which is further composed with
// CoeffsSignX4Cheby to bring it to ~80bits of precision.
var DefaultCompositePolynomialForSign = [][]string{
var DefaultMinimaxCompositePolynomialForSign = [][]string{
{"0", "0.6371462957672043333", "0", "-0.2138032460610765328", "0", "0.1300439303835664499", "0", "-0.0948842756566191044", "0", "0.0760417811618939909", "0", "-0.0647714820920817557", "0", "0.0577904411211959048", "0", "-0.5275634328386103792"},
{"0", "0.6371463830322414578", "0", "-0.2138032749880402509", "0", "0.1300439475440832118", "0", "-0.0948842877009570762", "0", "0.0760417903036533484", "0", "-0.0647714893343788749", "0", "0.0577904470018789283", "0", "-0.5275633669027163690"},
{"0", "0.6371474873319408921", "0", "-0.2138036410457105809", "0", "0.1300441647026617059", "0", "-0.0948844401165889295", "0", "0.0760419059884502454", "0", "-0.0647715809823254389", "0", "0.0577905214191996406", "0", "-0.5275625325136631842"},
@@ -67,18 +57,18 @@ var DefaultCompositePolynomialForSign = [][]string{
{"0", "0.6484328404896112084", "0", "-0.2164688471885406655", "0", "0.1302737771018761402", "0", "-0.0934786176742356885", "0", "0.0731553324133884104", "0", "-0.0603252338481440981", "0", "0.0515366139595849853", "0", "-0.0451803385226980999", "0", "0.0404062758116036740", "0", "-0.0367241775307736352", "0", "0.0338327393147257876", "0", "-0.0315379870551266008", "0", "0.0297110181467332488", "0", "-0.0282647625290482803", "0", "0.0271406820054187399", "0", "-0.5041440308249296747"},
{"0", "0.8988231150519633581", "0", "-0.2996064625122592138", "0", "0.1797645789317822353", "0", "-0.1284080039344265678", "0", "0.0998837306152582349", "0", "-0.0817422066647773587", "0", "0.0691963884439569899", "0", "-0.0600136111161848355", "0", "0.0530132660795356506", "0", "-0.0475133961913746909", "0", "0.0430936248086665091", "0", "-0.0394819050695222720", "0", "0.0364958013826412785", "0", "-0.0340100990129699835", "0", "0.0319381346687564699", "0", "-0.3095637759472512887"},
{"0", "1.2654405107323937767", "0", "-0.4015427502443620045", "0", "0.2182109348265640036", "0", "-0.1341692540177466882", "0", "0.0852282854825304735", "0", "-0.0539043807248265057", "0", "0.0332611560159092728", "0", "-0.0197419082926337129", "0", "0.0111368708758574529", "0", "-0.0058990205011466309", "0", "0.0028925861201479251", "0", "-0.0012889673944941461", "0", "0.0005081425552893727", "0", "-0.0001696330470066833", "0", "0.0000440808328172753", "0", "-0.0000071549240608255"},
minimax.CoeffsSignX4Cheby, // Quadruples the output precision (up to the scheme error)
CoeffsSignX4Cheby, // Quadruples the output precision (up to the scheme error)
}
// Sign evaluates f(x) = 1 if x > 0, -1 if x < 0, else 0.
// This will ensure that sign.Scale = params.DefaultScale().
func (eval Evaluator) Sign(op0 *rlwe.Ciphertext) (sign *rlwe.Ciphertext, err error) {
func (eval ComparisonEvaluator) Sign(op0 *rlwe.Ciphertext) (sign *rlwe.Ciphertext, err error) {
return eval.Evaluate(op0, eval.MinimaxCompositeSignPolynomial)
}
// Step evaluates f(x) = 1 if x > 0, 0 if x < 0, else 0.5 (i.e. (sign+1)/2).
// This will ensure that step.Scale = params.DefaultScale().
func (eval Evaluator) Step(op0 *rlwe.Ciphertext) (step *rlwe.Ciphertext, err error) {
func (eval ComparisonEvaluator) Step(op0 *rlwe.Ciphertext) (step *rlwe.Ciphertext, err error) {
n := len(eval.MinimaxCompositeSignPolynomial)
@@ -108,7 +98,7 @@ func (eval Evaluator) Step(op0 *rlwe.Ciphertext) (step *rlwe.Ciphertext, err err
// - op0.Scale = op1.Scale.
//
// This method ensures that max.Scale = params.DefaultScale.
func (eval Evaluator) Max(op0, op1 *rlwe.Ciphertext) (max *rlwe.Ciphertext, err error) {
func (eval ComparisonEvaluator) Max(op0, op1 *rlwe.Ciphertext) (max *rlwe.Ciphertext, err error) {
// step * diff
var stepdiff *rlwe.Ciphertext
@@ -130,7 +120,7 @@ func (eval Evaluator) Max(op0, op1 *rlwe.Ciphertext) (max *rlwe.Ciphertext, err
// - op0.Scale = op1.Scale.
//
// This method ensures that min.Scale = params.DefaultScale.
func (eval Evaluator) Min(op0, op1 *rlwe.Ciphertext) (min *rlwe.Ciphertext, err error) {
func (eval ComparisonEvaluator) Min(op0, op1 *rlwe.Ciphertext) (min *rlwe.Ciphertext, err error) {
// step * diff
var stepdiff *rlwe.Ciphertext
@@ -146,7 +136,7 @@ func (eval Evaluator) Min(op0, op1 *rlwe.Ciphertext) (min *rlwe.Ciphertext, err
return stepdiff, nil
}
func (eval Evaluator) stepdiff(op0, op1 *rlwe.Ciphertext) (stepdiff *rlwe.Ciphertext, err error) {
func (eval ComparisonEvaluator) stepdiff(op0, op1 *rlwe.Ciphertext) (stepdiff *rlwe.Ciphertext, err error) {
params := eval.Parameters
// diff = op0 - op1
@@ -157,7 +147,7 @@ func (eval Evaluator) stepdiff(op0, op1 *rlwe.Ciphertext) (stepdiff *rlwe.Cipher
// Required for the scale matching before the last multiplication.
if diff.Level() < params.LevelsConsumedPerRescaling()*2 {
if diff, err = eval.BtsEval.Bootstrap(diff); err != nil {
if diff, err = eval.Bootstrap(diff); err != nil {
return
}
}
@@ -170,7 +160,7 @@ func (eval Evaluator) stepdiff(op0, op1 *rlwe.Ciphertext) (stepdiff *rlwe.Cipher
// Required for the following multiplication
if step.Level() < params.LevelsConsumedPerRescaling() {
if step, err = eval.BtsEval.Bootstrap(step); err != nil {
if step, err = eval.Bootstrap(step); err != nil {
return
}
}
+104 -94
View File
@@ -1,68 +1,76 @@
// Package dft implements a homomorphic DFT circuit for the CKKS scheme.
package dft
package hefloat
import (
"encoding/json"
"fmt"
"math"
"math/big"
"slices"
ltcommon "github.com/tuneinsight/lattigo/v6/circuits/ckks/lintrans"
"github.com/tuneinsight/lattigo/v6/circuits/common/lintrans"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/schemes/ckks"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Type is a type used to distinguish between different discrete Fourier transformations.
type Type int
// EvaluatorForDFT is an interface defining the set of methods required to instantiate a DFTEvaluator.
// The default hefloat.Evaluator is compliant to this interface.
type EvaluatorForDFT interface {
rlwe.ParameterProvider
he.EvaluatorForLinearTransformation
Add(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error)
Sub(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error)
Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error)
Conjugate(op0 *rlwe.Ciphertext, opOut *rlwe.Ciphertext) (err error)
Rotate(op0 *rlwe.Ciphertext, k int, opOut *rlwe.Ciphertext) (err error)
Rescale(op0 *rlwe.Ciphertext, opOut *rlwe.Ciphertext) (err error)
}
// DFTType is a type used to distinguish between different discrete Fourier transformations.
type DFTType int
// HomomorphicEncode (IDFT) and HomomorphicDecode (DFT) are two available linear transformations for homomorphic encoding and decoding.
const (
HomomorphicEncode = Type(0) // Homomorphic Encoding (IDFT)
HomomorphicDecode = Type(1) // Homomorphic Decoding (DFT)
HomomorphicEncode = DFTType(0) // Homomorphic Encoding (IDFT)
HomomorphicDecode = DFTType(1) // Homomorphic Decoding (DFT)
)
// Format is a type used to distinguish between the
// DFTFormat is a type used to distinguish between the
// different input/output formats of the Homomorphic DFT.
type Format int
type DFTFormat int
const (
// Standard: designates the regular DFT.
// Example: [a+bi, c+di] -> DFT([a+bi, c+di])
Standard = Format(0)
Standard = DFTFormat(0)
// SplitRealAndImag: HomomorphicEncode will return the real and
// imaginary part into separate ciphertexts, both as real vectors.
// Example: [a+bi, c+di] -> DFT([a, c]) and DFT([b, d])
SplitRealAndImag = Format(1)
SplitRealAndImag = DFTFormat(1)
// RepackImagAsReal: behaves the same as SplitRealAndImag except that
// if the ciphertext is sparsely packed (at most N/4 slots), HomomorphicEncode
// will repacks the real part into the left N/2 slots and the imaginary part
// into the right N/2 slots. HomomorphicDecode must be specified with the same
// format for correctness.
// Example: [a+bi, 0, c+di, 0] -> [DFT([a, b]), DFT([b, d])]
RepackImagAsReal = Format(2)
RepackImagAsReal = DFTFormat(2)
)
// Matrix is a struct storing the factorized IDFT, DFT matrices, which are
// DFTMatrix is a struct storing the factorized IDFT, DFT matrices, which are
// used to homomorphically encode and decode a ciphertext respectively.
type Matrix struct {
MatrixLiteral
Matrices []ltcommon.LinearTransformation
type DFTMatrix struct {
DFTMatrixLiteral
Matrices []LinearTransformation
}
// MatrixLiteral is a struct storing the parameters to generate the factorized DFT/IDFT matrices.
// DFTMatrixLiteral is a struct storing the parameters to generate the factorized DFT/IDFT matrices.
// This struct has mandatory and optional fields.
//
// Mandatory:
// - Type: HomomorphicEncode (a.k.a. CoeffsToSlots) or HomomorphicDecode (a.k.a. SlotsToCoeffs)
// - LogSlots: log2(slots)
// - LevelQ: starting level of the linear transformation
// - LevelP: number of auxiliary primes used during the automorphisms. User must ensure that this
// value is the same as the one used to generate the Galois keys.
// - LevelStart: starting level of the linear transformation
// - Levels: depth of the linear transform (i.e. the degree of factorization of the encoding matrix)
//
// Optional:
@@ -70,15 +78,14 @@ type Matrix struct {
// - Scaling: constant by which the matrix is multiplied
// - BitReversed: if true, then applies the transformation bit-reversed and expects bit-reversed inputs
// - LogBSGSRatio: log2 of the ratio between the inner and outer loop of the baby-step giant-step algorithm
type MatrixLiteral struct {
type DFTMatrixLiteral struct {
// Mandatory
Type Type
LogSlots int
LevelQ int
LevelP int
Levels []int
Type DFTType
LogSlots int
LevelStart int
Levels []int
// Optional
Format Format // Default: standard.
Format DFTFormat // Default: standard.
Scaling *big.Float // Default 1.0.
BitReversed bool // Default: False.
LogBSGSRatio int // Default: 0.
@@ -87,7 +94,7 @@ type MatrixLiteral struct {
// Depth returns the number of levels allocated to the linear transform.
// If actual == true then returns the number of moduli consumed, else
// returns the factorization depth.
func (d MatrixLiteral) Depth(actual bool) (depth int) {
func (d DFTMatrixLiteral) Depth(actual bool) (depth int) {
if actual {
depth = len(d.Levels)
} else {
@@ -99,7 +106,7 @@ func (d MatrixLiteral) Depth(actual bool) (depth int) {
}
// GaloisElements returns the list of rotations performed during the CoeffsToSlot operation.
func (d MatrixLiteral) GaloisElements(params ckks.Parameters) (galEls []uint64) {
func (d DFTMatrixLiteral) GaloisElements(params Parameters) (galEls []uint64) {
rotations := []int{}
imgRepack := d.Format == RepackImagAsReal
@@ -119,48 +126,45 @@ func (d MatrixLiteral) GaloisElements(params ckks.Parameters) (galEls []uint64)
// Coeffs to Slots rotations
for i, pVec := range indexCtS {
N1 := lintrans.FindBestBSGSRatio(utils.GetKeys(pVec), dslots, d.LogBSGSRatio)
N1 := he.FindBestBSGSRatio(utils.GetKeys(pVec), dslots, d.LogBSGSRatio)
rotations = addMatrixRotToList(pVec, rotations, N1, slots, d.Type == HomomorphicDecode && logSlots < logN-1 && i == 0 && imgRepack)
}
return params.GaloisElements(rotations)
}
// MarshalBinary returns a JSON representation of the the target [MatrixLiteral] on a slice of bytes.
// MarshalBinary returns a JSON representation of the the target DFTMatrixLiteral on a slice of bytes.
// See `Marshal` from the `encoding/json` package.
func (d MatrixLiteral) MarshalBinary() (data []byte, err error) {
func (d DFTMatrixLiteral) MarshalBinary() (data []byte, err error) {
return json.Marshal(d)
}
// UnmarshalBinary reads a JSON representation on the target [MatrixLiteral] struct.
// UnmarshalBinary reads a JSON representation on the target DFTMatrixLiteral struct.
// See `Unmarshal` from the `encoding/json` package.
func (d *MatrixLiteral) UnmarshalBinary(data []byte) error {
func (d *DFTMatrixLiteral) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, d)
}
// Evaluator is an evaluator providing an API for homomorphic DFT.
// DFTEvaluator is an evaluator providing an API for homomorphic DFT.
// All fields of this struct are public, enabling custom instantiations.
type Evaluator struct {
*ckks.Evaluator
LTEvaluator *ltcommon.Evaluator
parameters ckks.Parameters
pool *rlwe.BufferPool
type DFTEvaluator struct {
EvaluatorForDFT
*LinearTransformationEvaluator
parameters Parameters
}
// NewEvaluator instantiates a new [Evaluator] from a [ckks.Evaluator].
func NewEvaluator(params ckks.Parameters, eval *ckks.Evaluator) *Evaluator {
dfteval := new(Evaluator)
dfteval.Evaluator = eval
dfteval.LTEvaluator = ltcommon.NewEvaluator(eval)
// NewDFTEvaluator instantiates a new DFTEvaluator.
// The default hefloat.Evaluator is compliant to the EvaluatorForDFT interface.
func NewDFTEvaluator(params Parameters, eval EvaluatorForDFT) *DFTEvaluator {
dfteval := new(DFTEvaluator)
dfteval.EvaluatorForDFT = eval
dfteval.LinearTransformationEvaluator = NewLinearTransformationEvaluator(eval)
dfteval.parameters = params
dfteval.pool = rlwe.NewPool(params.RingQP())
return dfteval
}
// NewMatrixFromLiteral generates the factorized DFT/IDFT matrices for the homomorphic encoding/decoding.
func NewMatrixFromLiteral(params ckks.Parameters, d MatrixLiteral, encoder *ckks.Encoder) (Matrix, error) {
// NewDFTMatrixFromLiteral generates the factorized DFT/IDFT matrices for the homomorphic encoding/decoding.
func NewDFTMatrixFromLiteral(params Parameters, d DFTMatrixLiteral, encoder *Encoder) (DFTMatrix, error) {
logSlots := d.LogSlots
logdSlots := logSlots
@@ -169,12 +173,12 @@ func NewMatrixFromLiteral(params ckks.Parameters, d MatrixLiteral, encoder *ckks
}
// CoeffsToSlots vectors
matrices := []ltcommon.LinearTransformation{}
matrices := []LinearTransformation{}
pVecDFT := d.GenMatrices(params.LogN(), params.EncodingPrecision())
nbModuliPerRescale := params.LevelsConsumedPerRescaling()
level := d.LevelQ
level := d.LevelStart
var idx int
for i := range d.Levels {
@@ -193,19 +197,18 @@ func NewMatrixFromLiteral(params ckks.Parameters, d MatrixLiteral, encoder *ckks
for j := 0; j < d.Levels[i]; j++ {
ltparams := ltcommon.Parameters{
DiagonalsIndexList: pVecDFT[idx].DiagonalsIndexList(),
LevelQ: d.LevelQ,
LevelP: d.LevelP,
Scale: scale,
LogDimensions: ring.Dimensions{Rows: 0, Cols: logdSlots},
LogBabyStepGiantStepRatio: d.LogBSGSRatio,
ltparams := LinearTransformationParameters{
DiagonalsIndexList: pVecDFT[idx].DiagonalsIndexList(),
Level: level,
Scale: scale,
LogDimensions: ring.Dimensions{Rows: 0, Cols: logdSlots},
LogBabyStepGianStepRatio: d.LogBSGSRatio,
}
mat := ltcommon.NewTransformation(params, ltparams)
mat := NewLinearTransformation(params, ltparams)
if err := ltcommon.Encode(encoder, pVecDFT[idx], mat); err != nil {
return Matrix{}, fmt.Errorf("cannot NewDFTMatrixFromLiteral: %w", err)
if err := EncodeLinearTransformation[*bignum.Complex](encoder, pVecDFT[idx], mat); err != nil {
return DFTMatrix{}, fmt.Errorf("cannot NewDFTMatrixFromLiteral: %w", err)
}
matrices = append(matrices, mat)
@@ -215,7 +218,7 @@ func NewMatrixFromLiteral(params ckks.Parameters, d MatrixLiteral, encoder *ckks
level -= nbModuliPerRescale
}
return Matrix{MatrixLiteral: d, Matrices: matrices}, nil
return DFTMatrix{DFTMatrixLiteral: d, Matrices: matrices}, nil
}
// CoeffsToSlotsNew applies the homomorphic encoding and returns the result on new ciphertexts.
@@ -223,11 +226,11 @@ func NewMatrixFromLiteral(params ckks.Parameters, d MatrixLiteral, encoder *ckks
// Given n = current number of slots and N/2 max number of slots (half the ring degree):
// If the packing is sparse (n < N/2), then returns ctReal = Ecd(vReal || vImag) and ctImag = nil.
// If the packing is dense (n == N/2), then returns ctReal = Ecd(vReal) and ctImag = Ecd(vImag).
func (eval *Evaluator) CoeffsToSlotsNew(ctIn *rlwe.Ciphertext, ctsMatrices Matrix) (ctReal, ctImag *rlwe.Ciphertext, err error) {
ctReal = ckks.NewCiphertext(eval.parameters, 1, ctsMatrices.LevelQ)
func (eval *DFTEvaluator) CoeffsToSlotsNew(ctIn *rlwe.Ciphertext, ctsMatrices DFTMatrix) (ctReal, ctImag *rlwe.Ciphertext, err error) {
ctReal = NewCiphertext(eval.parameters, 1, ctsMatrices.LevelStart)
if ctsMatrices.LogSlots == eval.parameters.LogMaxSlots() {
ctImag = ckks.NewCiphertext(eval.parameters, 1, ctsMatrices.LevelQ)
ctImag = NewCiphertext(eval.parameters, 1, ctsMatrices.LevelStart)
}
return ctReal, ctImag, eval.CoeffsToSlots(ctIn, ctsMatrices, ctReal, ctImag)
@@ -237,7 +240,7 @@ func (eval *Evaluator) CoeffsToSlotsNew(ctIn *rlwe.Ciphertext, ctsMatrices Matri
// Homomorphically encodes a complex vector vReal + i*vImag of size n on a real vector of size 2n.
// If the packing is sparse (n < N/2), then returns ctReal = Ecd(vReal || vImag) and ctImag = nil.
// If the packing is dense (n == N/2), then returns ctReal = Ecd(vReal) and ctImag = Ecd(vImag).
func (eval *Evaluator) CoeffsToSlots(ctIn *rlwe.Ciphertext, ctsMatrices Matrix, ctReal, ctImag *rlwe.Ciphertext) (err error) {
func (eval *DFTEvaluator) CoeffsToSlots(ctIn *rlwe.Ciphertext, ctsMatrices DFTMatrix, ctReal, ctImag *rlwe.Ciphertext) (err error) {
if ctsMatrices.Format == RepackImagAsReal || ctsMatrices.Format == SplitRealAndImag {
@@ -255,8 +258,14 @@ func (eval *Evaluator) CoeffsToSlots(ctIn *rlwe.Ciphertext, ctsMatrices Matrix,
if ctImag != nil {
tmp = ctImag
} else {
tmp = eval.pool.GetBuffCt(1, ctReal.Level())
defer eval.pool.RecycleBuffCt(tmp)
tmp, err = rlwe.NewCiphertextAtLevelFromPoly(ctReal.Level(), eval.GetBuffCt().Value[:2])
// This error cannot happen unless the user improperly tempered the evaluators
// buffer. If it were to happen in that case, there is no way to recover from
// it, hence the panic.
if err != nil {
panic(err)
}
tmp.IsNTT = true
}
@@ -301,21 +310,22 @@ func (eval *Evaluator) CoeffsToSlots(ctIn *rlwe.Ciphertext, ctsMatrices Matrix,
// Homomorphically decodes a real vector of size 2n on a complex vector vReal + i*vImag of size n.
// If the packing is sparse (n < N/2) then ctReal = Ecd(vReal || vImag) and ctImag = nil.
// If the packing is dense (n == N/2), then ctReal = Ecd(vReal) and ctImag = Ecd(vImag).
func (eval *Evaluator) SlotsToCoeffsNew(ctReal, ctImag *rlwe.Ciphertext, stcMatrices Matrix) (opOut *rlwe.Ciphertext, err error) {
func (eval *DFTEvaluator) SlotsToCoeffsNew(ctReal, ctImag *rlwe.Ciphertext, stcMatrices DFTMatrix) (opOut *rlwe.Ciphertext, err error) {
if ctReal.Level() < stcMatrices.LevelQ || (ctImag != nil && ctImag.Level() < stcMatrices.LevelQ) {
return nil, fmt.Errorf("ctReal.Level() or ctImag.Level() < DFTMatrix.LevelQ")
if ctReal.Level() < stcMatrices.LevelStart || (ctImag != nil && ctImag.Level() < stcMatrices.LevelStart) {
return nil, fmt.Errorf("ctReal.Level() or ctImag.Level() < DFTMatrix.LevelStart")
}
opOut = ckks.NewCiphertext(eval.parameters, 1, stcMatrices.LevelQ)
opOut = NewCiphertext(eval.parameters, 1, stcMatrices.LevelStart)
return opOut, eval.SlotsToCoeffs(ctReal, ctImag, stcMatrices, opOut)
}
// SlotsToCoeffs applies the homomorphic decoding and returns the result on the provided ciphertext.
// Homomorphically decodes a real vector of size 2n on a complex vector vReal + i*vImag of size n.
// If the packing is sparse (n < N/2) then ctReal = Ecd(vReal || vImag) and ctImag = nil.
// If the packing is dense (n == N/2), then ctReal = Ecd(vReal) and ctImag = Ecd(vImag).
func (eval *Evaluator) SlotsToCoeffs(ctReal, ctImag *rlwe.Ciphertext, stcMatrices Matrix, opOut *rlwe.Ciphertext) (err error) {
func (eval *DFTEvaluator) SlotsToCoeffs(ctReal, ctImag *rlwe.Ciphertext, stcMatrices DFTMatrix, opOut *rlwe.Ciphertext) (err error) {
// If full packing, the repacking can be done directly using ct0 and ct1.
if ctImag != nil {
@@ -339,13 +349,13 @@ func (eval *Evaluator) SlotsToCoeffs(ctReal, ctImag *rlwe.Ciphertext, stcMatrice
return
}
// dft evaluates a series of [lintrans.LinearTransformation] sequentially on the ctIn and stores the result in opOut.
func (eval *Evaluator) dft(ctIn *rlwe.Ciphertext, matrices []ltcommon.LinearTransformation, opOut *rlwe.Ciphertext) (err error) {
// dft evaluates a series of LinearTransformation sequentially on the ctIn and stores the result in opOut.
func (eval *DFTEvaluator) dft(ctIn *rlwe.Ciphertext, matrices []LinearTransformation, opOut *rlwe.Ciphertext) (err error) {
inputLogSlots := ctIn.LogDimensions
// Sequentially multiplies w with the provided dft matrices.
if err = eval.LTEvaluator.EvaluateSequential(ctIn, matrices, opOut); err != nil {
if err = eval.LinearTransformationEvaluator.EvaluateSequential(ctIn, matrices, opOut); err != nil {
return
}
@@ -491,7 +501,7 @@ func addMatrixRotToList(pVec map[int]bool, rotations []int, N1, slots int, repac
if len(pVec) < 3 {
for j := range pVec {
if !slices.Contains(rotations, j) {
if !utils.IsInSlice(j, rotations) {
rotations = append(rotations, j)
}
}
@@ -509,13 +519,13 @@ func addMatrixRotToList(pVec map[int]bool, rotations []int, N1, slots int, repac
index &= (slots - 1)
}
if index != 0 && !slices.Contains(rotations, index) {
if index != 0 && !utils.IsInSlice(index, rotations) {
rotations = append(rotations, index)
}
index = j & (N1 - 1)
if index != 0 && !slices.Contains(rotations, index) {
if index != 0 && !utils.IsInSlice(index, rotations) {
rotations = append(rotations, index)
}
}
@@ -524,7 +534,7 @@ func addMatrixRotToList(pVec map[int]bool, rotations []int, N1, slots int, repac
return rotations
}
func (d MatrixLiteral) computeBootstrappingDFTIndexMap(logN int) (rotationMap []map[int]bool) {
func (d DFTMatrixLiteral) computeBootstrappingDFTIndexMap(logN int) (rotationMap []map[int]bool) {
logSlots := d.LogSlots
ltType := d.Type
@@ -594,7 +604,7 @@ func (d MatrixLiteral) computeBootstrappingDFTIndexMap(logN int) (rotationMap []
return
}
func genWfftIndexMap(logL, level int, ltType Type, bitreversed bool) (vectors map[int]bool) {
func genWfftIndexMap(logL, level int, ltType DFTType, bitreversed bool) (vectors map[int]bool) {
var rot int
@@ -618,7 +628,7 @@ func genWfftRepackIndexMap(logL, level int) (vectors map[int]bool) {
return
}
func nextLevelfftIndexMap(vec map[int]bool, logL, N, nextLevel int, ltType Type, bitreversed bool) (newVec map[int]bool) {
func nextLevelfftIndexMap(vec map[int]bool, logL, N, nextLevel int, ltType DFTType, bitreversed bool) (newVec map[int]bool) {
var rot int
@@ -640,7 +650,7 @@ func nextLevelfftIndexMap(vec map[int]bool, logL, N, nextLevel int, ltType Type,
}
// GenMatrices returns the ordered list of factors of the non-zero diagonals of the IDFT (encoding) or DFT (decoding) matrix.
func (d MatrixLiteral) GenMatrices(LogN int, prec uint) (plainVector []ltcommon.Diagonals[*bignum.Complex]) {
func (d DFTMatrixLiteral) GenMatrices(LogN int, prec uint) (plainVector []Diagonals[*bignum.Complex]) {
logSlots := d.LogSlots
slots := 1 << logSlots
@@ -673,7 +683,7 @@ func (d MatrixLiteral) GenMatrices(LogN int, prec uint) (plainVector []ltcommon.
a, b, c = fftPlainVec(logSlots, 1<<logdSlots, roots, pow5)
}
plainVector = make([]ltcommon.Diagonals[*bignum.Complex], maxDepth)
plainVector = make([]Diagonals[*bignum.Complex], maxDepth)
// We compute the chain of merge in order or reverse order depending if its DFT or InvDFT because
// the way the levels are collapsed has an impact on the total number of rotations and keys to be
@@ -770,7 +780,7 @@ func (d MatrixLiteral) GenMatrices(LogN int, prec uint) (plainVector []ltcommon.
return
}
func genFFTDiagMatrix(logL, fftLevel int, a, b, c []*bignum.Complex, ltType Type, bitreversed bool) (vectors map[int][]*bignum.Complex) {
func genFFTDiagMatrix(logL, fftLevel int, a, b, c []*bignum.Complex, ltType DFTType, bitreversed bool) (vectors map[int][]*bignum.Complex) {
var rot int
@@ -826,7 +836,7 @@ func genRepackMatrix(logL int, prec uint, bitreversed bool) (vectors map[int][]*
return
}
func multiplyFFTMatrixWithNextFFTLevel(vec map[int][]*bignum.Complex, logL, N, nextLevel int, a, b, c []*bignum.Complex, ltType Type, bitreversed bool) (newVec map[int][]*bignum.Complex) {
func multiplyFFTMatrixWithNextFFTLevel(vec map[int][]*bignum.Complex, logL, N, nextLevel int, a, b, c []*bignum.Complex, ltType DFTType, bitreversed bool) (newVec map[int][]*bignum.Complex) {
var rot int
+46 -70
View File
@@ -1,4 +1,4 @@
package dft
package hefloat_test
import (
"math/big"
@@ -7,13 +7,12 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func TestHomomorphicDFT(t *testing.T) {
@@ -27,13 +26,13 @@ func TestHomomorphicDFT(t *testing.T) {
for _, paramsLiteral := range testParametersLiteral {
var params ckks.Parameters
if params, err = ckks.NewParametersFromLiteral(paramsLiteral); err != nil {
var params hefloat.Parameters
if params, err = hefloat.NewParametersFromLiteral(paramsLiteral); err != nil {
t.Fatal(err)
}
for _, logSlots := range []int{params.LogMaxDimensions().Cols - 1, params.LogMaxDimensions().Cols} {
for _, testSet := range []func(params ckks.Parameters, logSlots int, t *testing.T){
for _, testSet := range []func(params hefloat.Parameters, logSlots int, t *testing.T){
testHomomorphicEncoding,
testHomomorphicDecoding,
} {
@@ -46,12 +45,11 @@ func TestHomomorphicDFT(t *testing.T) {
func testDFTMatrixLiteralMarshalling(t *testing.T) {
t.Run("Marshalling", func(t *testing.T) {
m := MatrixLiteral{
m := hefloat.DFTMatrixLiteral{
LogSlots: 15,
Type: HomomorphicDecode,
Format: RepackImagAsReal,
LevelQ: 12,
LevelP: 1,
Type: hefloat.HomomorphicDecode,
Format: hefloat.RepackImagAsReal,
LevelStart: 12,
LogBSGSRatio: 2,
Levels: []int{1, 1, 1},
BitReversed: true,
@@ -60,7 +58,7 @@ func testDFTMatrixLiteralMarshalling(t *testing.T) {
data, err := m.MarshalBinary()
require.Nil(t, err)
mNew := new(MatrixLiteral)
mNew := new(hefloat.DFTMatrixLiteral)
if err := mNew.UnmarshalBinary(data); err != nil {
require.Nil(t, err)
}
@@ -68,7 +66,7 @@ func testDFTMatrixLiteralMarshalling(t *testing.T) {
})
}
func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T) {
func testHomomorphicEncoding(params hefloat.Parameters, LogSlots int, t *testing.T) {
slots := 1 << LogSlots
@@ -79,9 +77,9 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
packing = "SparsePacking"
}
var params2N ckks.Parameters
var params2N hefloat.Parameters
var err error
if params2N, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
if params2N, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: params.LogN() + 1,
LogQ: []int{60},
LogP: []int{61},
@@ -90,7 +88,7 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
t.Fatal(err)
}
ecd2N := ckks.NewEncoder(params2N)
ecd2N := hefloat.NewEncoder(params2N)
t.Run("Encode/"+packing, func(t *testing.T) {
@@ -118,23 +116,22 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
Levels[i] = 1
}
CoeffsToSlotsParametersLiteral := MatrixLiteral{
LogSlots: LogSlots,
Type: HomomorphicEncode,
Format: RepackImagAsReal,
LevelQ: params.MaxLevelQ(),
LevelP: params.MaxLevelP(),
Levels: Levels,
CoeffsToSlotsParametersLiteral := hefloat.DFTMatrixLiteral{
LogSlots: LogSlots,
Type: hefloat.HomomorphicEncode,
Format: hefloat.RepackImagAsReal,
LevelStart: params.MaxLevel(),
Levels: Levels,
}
kgen := rlwe.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
encoder := ckks.NewEncoder(params, 90) // Required to force roots.(type) to be []*bignum.Complex instead of []complex128
encoder := hefloat.NewEncoder(params, 90) // Required to force roots.(type) to be []*bignum.Complex instead of []complex128
encryptor := rlwe.NewEncryptor(params, sk)
decryptor := rlwe.NewDecryptor(params, sk)
// Generates the encoding matrices
CoeffsToSlotMatrices, err := NewMatrixFromLiteral(params, CoeffsToSlotsParametersLiteral, encoder)
CoeffsToSlotMatrices, err := hefloat.NewDFTMatrixFromLiteral(params, CoeffsToSlotsParametersLiteral, encoder)
require.NoError(t, err)
// Gets Galois elements
@@ -145,8 +142,8 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
evk := rlwe.NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(galEls, sk)...)
// Creates an evaluator with the rotation keys
eval := ckks.NewEvaluator(params, evk)
hdftEval := NewEvaluator(params, eval)
eval := hefloat.NewEvaluator(params, evk)
hdftEval := hefloat.NewDFTEvaluator(params, eval)
prec := params.EncodingPrecision()
@@ -183,7 +180,7 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
}
// Encodes coefficient-wise and encrypts the test vector
pt := ckks.NewPlaintext(params, params.MaxLevel())
pt := hefloat.NewPlaintext(params, params.MaxLevel())
pt.LogDimensions = ring.Dimensions{Rows: 0, Cols: LogSlots}
pt.IsBatched = false
@@ -234,7 +231,7 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
}
// Compares
ckks.VerifyTestVectors(params, ecd2N, nil, want, have, params.LogDefaultScale(), 0, true, t)
hefloat.VerifyTestVectors(params, ecd2N, nil, want, have, params.LogDefaultScale(), 0, *printPrecisionStats, t)
} else {
@@ -278,13 +275,13 @@ func testHomomorphicEncoding(params ckks.Parameters, LogSlots int, t *testing.T)
wantImag[i], wantImag[j] = vec1[i][0], vec1[i][1]
}
ckks.VerifyTestVectors(params, ecd2N, nil, wantReal, haveReal, params.LogDefaultScale(), 0, true, t)
ckks.VerifyTestVectors(params, ecd2N, nil, wantImag, haveImag, params.LogDefaultScale(), 0, true, t)
hefloat.VerifyTestVectors(params, ecd2N, nil, wantReal, haveReal, params.LogDefaultScale(), 0, *printPrecisionStats, t)
hefloat.VerifyTestVectors(params, ecd2N, nil, wantImag, haveImag, params.LogDefaultScale(), 0, *printPrecisionStats, t)
}
})
}
func testHomomorphicDecoding(params ckks.Parameters, LogSlots int, t *testing.T) {
func testHomomorphicDecoding(params hefloat.Parameters, LogSlots int, t *testing.T) {
slots := 1 << LogSlots
@@ -323,23 +320,22 @@ func testHomomorphicDecoding(params ckks.Parameters, LogSlots int, t *testing.T)
Levels[i] = 1
}
SlotsToCoeffsParametersLiteral := MatrixLiteral{
LogSlots: LogSlots,
Type: HomomorphicDecode,
Format: RepackImagAsReal,
LevelQ: params.MaxLevelQ(),
LevelP: params.MaxLevelP(),
Levels: Levels,
SlotsToCoeffsParametersLiteral := hefloat.DFTMatrixLiteral{
LogSlots: LogSlots,
Type: hefloat.HomomorphicDecode,
Format: hefloat.RepackImagAsReal,
LevelStart: params.MaxLevel(),
Levels: Levels,
}
kgen := rlwe.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
encoder := ckks.NewEncoder(params)
encoder := hefloat.NewEncoder(params)
encryptor := rlwe.NewEncryptor(params, sk)
decryptor := rlwe.NewDecryptor(params, sk)
// Generates the encoding matrices
SlotsToCoeffsMatrix, err := NewMatrixFromLiteral(params, SlotsToCoeffsParametersLiteral, encoder)
SlotsToCoeffsMatrix, err := hefloat.NewDFTMatrixFromLiteral(params, SlotsToCoeffsParametersLiteral, encoder)
require.NoError(t, err)
// Gets the Galois elements
@@ -350,8 +346,8 @@ func testHomomorphicDecoding(params ckks.Parameters, LogSlots int, t *testing.T)
evk := rlwe.NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(galEls, sk)...)
// Creates an evaluator with the rotation keys
eval := ckks.NewEvaluator(params, evk)
hdftEval := NewEvaluator(params, eval)
eval := hefloat.NewEvaluator(params, evk)
hdftEval := hefloat.NewDFTEvaluator(params, eval)
prec := params.EncodingPrecision()
@@ -378,7 +374,7 @@ func testHomomorphicDecoding(params ckks.Parameters, LogSlots int, t *testing.T)
}
// Encodes and encrypts the test vectors
plaintext := ckks.NewPlaintext(params, params.MaxLevel())
plaintext := hefloat.NewPlaintext(params, params.MaxLevel())
plaintext.LogDimensions = ring.Dimensions{Rows: 0, Cols: LogSlots}
if err = encoder.Encode(valuesReal, plaintext); err != nil {
t.Fatal(err)
@@ -427,26 +423,6 @@ func testHomomorphicDecoding(params ckks.Parameters, LogSlots int, t *testing.T)
// Result is bit-reversed, so applies the bit-reverse permutation on the reference vector
utils.BitReverseInPlaceSlice(valuesReal, slots)
ckks.VerifyTestVectors(params, encoder, decryptor, valuesReal, valuesTest, params.LogDefaultScale(), 0, true, t)
hefloat.VerifyTestVectors(params, encoder, decryptor, valuesReal, valuesTest, params.LogDefaultScale(), 0, *printPrecisionStats, t)
})
}
var (
// testInsecurePrec45 are insecure parameters used for the sole purpose of fast testing.
testInsecurePrec45 = ckks.ParametersLiteral{
LogN: 10,
LogQ: []int{55, 45, 45, 45, 45, 45, 45},
LogP: []int{60},
LogDefaultScale: 45,
}
// testInsecurePrec90 are insecure parameters used for the sole purpose of fast testing.
testInsecurePrec90 = ckks.ParametersLiteral{
LogN: 10,
LogQ: []int{55, 55, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45},
LogP: []int{60, 60},
LogDefaultScale: 90,
}
testParametersLiteral = []ckks.ParametersLiteral{testInsecurePrec45, testInsecurePrec90}
)
+41 -31
View File
@@ -1,31 +1,41 @@
// Package inverse implements a homomorphic inversion circuit for the CKKS scheme.
package inverse
package hefloat
import (
"fmt"
"math"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/bootstrapping"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/comparison"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/minimax"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/utils"
)
// Evaluator is an evaluator used to evaluate the inverses of ciphertexts.
// All fields of this struct are public, enabling custom instantiations.
type Evaluator struct {
Parameters ckks.Parameters
*minimax.Evaluator
// EvaluatorForInverse defines a set of common and scheme agnostic
// methods that are necessary to instantiate an InverseEvaluator.
// The default hefloat.Evaluator is compliant to this interface.
type EvaluatorForInverse interface {
EvaluatorForMinimaxCompositePolynomial
SetScale(ct *rlwe.Ciphertext, scale rlwe.Scale) (err error)
}
// NewEvaluator instantiates a new InverseEvaluator.
// InverseEvaluator is an evaluator used to evaluate the inverses of ciphertexts.
// All fields of this struct are public, enabling custom instantiations.
type InverseEvaluator struct {
EvaluatorForInverse
*MinimaxCompositePolynomialEvaluator
he.Bootstrapper[rlwe.Ciphertext]
Parameters Parameters
}
// NewInverseEvaluator instantiates a new InverseEvaluator.
// The default hefloat.Evaluator is compliant to the EvaluatorForInverse interface.
// The field he.Bootstrapper[rlwe.Ciphertext] can be nil if the parameters have enough levels to support the computation.
// This method is allocation free.
func NewEvaluator(params ckks.Parameters, eval *minimax.Evaluator) Evaluator {
return Evaluator{
Parameters: params,
Evaluator: eval,
func NewInverseEvaluator(params Parameters, eval EvaluatorForInverse, btp he.Bootstrapper[rlwe.Ciphertext]) InverseEvaluator {
return InverseEvaluator{
EvaluatorForInverse: eval,
MinimaxCompositePolynomialEvaluator: NewMinimaxCompositePolynomialEvaluator(params, eval, btp),
Bootstrapper: btp,
Parameters: params,
}
}
@@ -39,16 +49,16 @@ func NewEvaluator(params ckks.Parameters, eval *minimax.Evaluator) Evaluator {
//
// The user can provide a minimax composite polynomial (signMinimaxPoly) for the sign function in the interval
// [-1-e, -2^{log2min}] U [2^{log2min}, 1+e] (where e is an upperbound on the scheme error).
// If no such polynomial is provided, then the [DefaultMinimaxCompositePolynomialForSign] is used by default.
// If no such polynomial is provided, then the DefaultMinimaxCompositePolynomialForSign is used by default.
// Note that the precision of the output of sign(x * c) does not impact the circuit precision since this value ends up being both at
// the numerator and denominator, thus cancelling itself.
func (eval Evaluator) EvaluateFullDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64, signMinimaxPoly ...minimax.Polynomial) (cInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) EvaluateFullDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64, signMinimaxPoly ...MinimaxCompositePolynomial) (cInv *rlwe.Ciphertext, err error) {
var poly minimax.Polynomial
var poly MinimaxCompositePolynomial
if len(signMinimaxPoly) == 1 {
poly = signMinimaxPoly[0]
} else {
poly = minimax.NewPolynomial(comparison.DefaultCompositePolynomialForSign)
poly = NewMinimaxCompositePolynomial(DefaultMinimaxCompositePolynomialForSign)
}
return eval.evaluateNew(ct, log2min, log2max, true, poly)
@@ -60,7 +70,7 @@ func (eval Evaluator) EvaluateFullDomainNew(ct *rlwe.Ciphertext, log2min, log2ma
// This is done by using the work Efficient Homomorphic Evaluation on Large Intervals (https://eprint.iacr.org/2022/280.pdf).
// 2. Compute y' = 1/(c * x) with the iterative Goldschmidt division algorithm.
// 3. Compute y = y' * c
func (eval Evaluator) EvaluatePositiveDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64) (cInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) EvaluatePositiveDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64) (cInv *rlwe.Ciphertext, err error) {
return eval.evaluateNew(ct, log2min, log2max, false, nil)
}
@@ -70,7 +80,7 @@ func (eval Evaluator) EvaluatePositiveDomainNew(ct *rlwe.Ciphertext, log2min, lo
// This is done by using the work Efficient Homomorphic Evaluation on Large Intervals (https://eprint.iacr.org/2022/280.pdf).
// 2. Compute y' = 1/(c * x) with the iterative Goldschmidt division algorithm.
// 3. Compute y = y' * c
func (eval Evaluator) EvaluateNegativeDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64) (cInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) EvaluateNegativeDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64) (cInv *rlwe.Ciphertext, err error) {
var ctNeg *rlwe.Ciphertext
if ctNeg, err = eval.MulNew(ct, -1); err != nil {
@@ -84,13 +94,13 @@ func (eval Evaluator) EvaluateNegativeDomainNew(ct *rlwe.Ciphertext, log2min, lo
return cInv, eval.Mul(cInv, -1, cInv)
}
func (eval Evaluator) evaluateNew(ct *rlwe.Ciphertext, log2min, log2max float64, fulldomain bool, signMinimaxPoly minimax.Polynomial) (cInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, log2min, log2max float64, fulldomain bool, signMinimaxPoly MinimaxCompositePolynomial) (cInv *rlwe.Ciphertext, err error) {
params := eval.Parameters
levelsPerRescaling := params.LevelsConsumedPerRescaling()
btp := eval.BtsEval
btp := eval.Bootstrapper
var normalizationfactor *rlwe.Ciphertext
@@ -110,12 +120,12 @@ func (eval Evaluator) evaluateNew(ct *rlwe.Ciphertext, log2min, log2max float64,
if fulldomain {
if eval.Evaluator == nil {
if eval.MinimaxCompositePolynomialEvaluator == nil {
return nil, fmt.Errorf("preprocessing: cannot EvaluateNew: MinimaxCompositePolynomialEvaluator is nil but fulldomain is set to true")
}
// Computes the sign with precision [-1, -2^-a] U [2^-a, 1]
if sign, err = eval.Evaluator.Evaluate(cInv, signMinimaxPoly); err != nil {
if sign, err = eval.MinimaxCompositePolynomialEvaluator.Evaluate(cInv, signMinimaxPoly); err != nil {
return nil, fmt.Errorf("preprocessing: fulldomain: true -> sign: %w", err)
}
@@ -205,9 +215,9 @@ func (eval Evaluator) evaluateNew(ct *rlwe.Ciphertext, log2min, log2max float64,
// This method will return an error if the input ciphertext does not have enough
// remaining level and if the InverseEvaluator was instantiated with no bootstrapper.
// This method will return an error if something goes wrong with the bootstrapping or the rescaling operations.
func (eval Evaluator) GoldschmidtDivisionNew(ct *rlwe.Ciphertext, log2min float64) (ctInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) GoldschmidtDivisionNew(ct *rlwe.Ciphertext, log2min float64) (ctInv *rlwe.Ciphertext, err error) {
btp := eval.BtsEval
btp := eval.Bootstrapper
params := eval.Parameters
@@ -309,7 +319,7 @@ func (eval Evaluator) GoldschmidtDivisionNew(ct *rlwe.Ciphertext, log2min float6
// The normalization factor is independant to each slot:
// - values smaller than 1 will have a normalization factor that tends to 1
// - values greater than 1 will have a normalization factor that tends to 1/x
func (eval Evaluator) IntervalNormalization(ct *rlwe.Ciphertext, log2Max float64, btp bootstrapping.Bootstrapper) (ctNorm, ctNormFac *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) IntervalNormalization(ct *rlwe.Ciphertext, log2Max float64, btp he.Bootstrapper[rlwe.Ciphertext]) (ctNorm, ctNormFac *rlwe.Ciphertext, err error) {
ctNorm = ct.CopyNew()
@@ -1,21 +1,20 @@
// Package minimax implements a homomorphic minimax circuit for the CKKS scheme.
package minimax
package hefloat
import (
"fmt"
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Polynomial is a struct storing P(x) = pk(x) o pk-1(x) o ... o p1(x) o p0(x).
type Polynomial []bignum.Polynomial
// MinimaxCompositePolynomial is a struct storing P(x) = pk(x) o pk-1(x) o ... o p1(x) o p0(x).
type MinimaxCompositePolynomial []bignum.Polynomial
// NewPolynomial creates a new Polynomial from a list of coefficients.
// NewMinimaxCompositePolynomial creates a new MinimaxCompositePolynomial from a list of coefficients.
// Coefficients are expected to be given in the Chebyshev basis.
func NewPolynomial(coeffsStr [][]string) Polynomial {
func NewMinimaxCompositePolynomial(coeffsStr [][]string) MinimaxCompositePolynomial {
polys := make([]bignum.Polynomial, len(coeffsStr))
for i := range coeffsStr {
@@ -34,17 +33,17 @@ func NewPolynomial(coeffsStr [][]string) Polynomial {
polys[i] = poly
}
return Polynomial(polys)
return MinimaxCompositePolynomial(polys)
}
func (mcp Polynomial) MaxDepth() (depth int) {
func (mcp MinimaxCompositePolynomial) MaxDepth() (depth int) {
for i := range mcp {
depth = utils.Max(depth, mcp[i].Depth())
}
return
}
func (mcp Polynomial) Evaluate(x interface{}) (y *bignum.Complex) {
func (mcp MinimaxCompositePolynomial) Evaluate(x interface{}) (y *bignum.Complex) {
y = mcp[0].Evaluate(x)
for _, p := range mcp[1:] {
@@ -79,7 +78,7 @@ var CoeffsSignX4Cheby = []string{"0", "1.1962890625", "0", "-0.2392578125", "0",
//
// The sign function is defined as: -1 if -1 <= x < 0, 0 if x = 0, 1 if 0 < x <= 1.
//
// See [GenMinimaxCompositePolynomial] for information about how to instantiate and
// See GenMinimaxCompositePolynomial for information about how to instantiate and
// parameterize each input value of the algorithm.
func GenMinimaxCompositePolynomialForSign(prec uint, logalpha, logerr int, deg []int) {
@@ -212,10 +211,19 @@ func GenMinimaxCompositePolynomial(prec uint, logalpha, logerr int, deg []int, f
fmt.Println()
}
// Since this is the last polynomial, we can skip the interval scaling.
maxInterval := bignum.NewFloat(1, prec)
maxInterval.Add(maxInterval, r.MaxErr)
minInterval := bignum.NewFloat(1, prec)
minInterval.Sub(minInterval, r.MinErr)
maxInterval.Add(maxInterval, e)
minInterval.Sub(minInterval, e)
coeffs[len(deg)-1] = make([]*big.Float, deg[len(deg)-1]+1)
for j := range coeffs[len(deg)-1] {
coeffs[len(deg)-1][j] = new(big.Float).Set(r.Coeffs[j])
coeffs[len(deg)-1][j].Quo(coeffs[len(deg)-1][j], maxInterval) // Interval normalization
}
f64, _ := r.MaxErr.Float64()
@@ -1,36 +1,41 @@
package minimax
package hefloat
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/bootstrapping"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/ring"
)
// Evaluator is an evaluator used to evaluate composite polynomials on ciphertexts.
// All fields of this struct are publics, enabling custom instantiations.
type Evaluator struct {
*ckks.Evaluator
PolyEval *polynomial.Evaluator
BtsEval bootstrapping.Bootstrapper
Parameters ckks.Parameters
// EvaluatorForMinimaxCompositePolynomial defines a set of common and scheme agnostic method that are necessary to instantiate a MinimaxCompositePolynomialEvaluator.
type EvaluatorForMinimaxCompositePolynomial interface {
he.Evaluator
ConjugateNew(ct *rlwe.Ciphertext) (ctConj *rlwe.Ciphertext, err error)
}
// NewEvaluator instantiates a new Evaluator.
// MinimaxCompositePolynomialEvaluator is an evaluator used to evaluate composite polynomials on ciphertexts.
// All fields of this struct are publics, enabling custom instantiations.
type MinimaxCompositePolynomialEvaluator struct {
EvaluatorForMinimaxCompositePolynomial
PolynomialEvaluator
he.Bootstrapper[rlwe.Ciphertext]
Parameters Parameters
}
// NewMinimaxCompositePolynomialEvaluator instantiates a new MinimaxCompositePolynomialEvaluator.
// The default hefloat.Evaluator is compliant to the EvaluatorForMinimaxCompositePolynomial interface.
// This method is allocation free.
func NewEvaluator(params ckks.Parameters, eval *ckks.Evaluator, btsEval bootstrapping.Bootstrapper) *Evaluator {
return &Evaluator{eval, polynomial.NewEvaluator(params, eval), btsEval, params}
func NewMinimaxCompositePolynomialEvaluator(params Parameters, eval EvaluatorForMinimaxCompositePolynomial, bootstrapper he.Bootstrapper[rlwe.Ciphertext]) *MinimaxCompositePolynomialEvaluator {
return &MinimaxCompositePolynomialEvaluator{eval, *NewPolynomialEvaluator(params, eval), bootstrapper, params}
}
// Evaluate evaluates the provided MinimaxCompositePolynomial on the input ciphertext.
func (eval Evaluator) Evaluate(ct *rlwe.Ciphertext, mcp Polynomial) (res *rlwe.Ciphertext, err error) {
func (eval MinimaxCompositePolynomialEvaluator) Evaluate(ct *rlwe.Ciphertext, mcp MinimaxCompositePolynomial) (res *rlwe.Ciphertext, err error) {
params := eval.Parameters
btp := eval.BtsEval
btp := eval.Bootstrapper
levelsConsumedPerRescaling := params.LevelsConsumedPerRescaling()
@@ -61,7 +66,7 @@ func (eval Evaluator) Evaluate(ct *rlwe.Ciphertext, mcp Polynomial) (res *rlwe.C
}
// Evaluate the polynomial
if res, err = eval.PolyEval.Evaluate(res, poly, targetScale); err != nil {
if res, err = eval.PolynomialEvaluator.Evaluate(res, poly, targetScale); err != nil {
return nil, fmt.Errorf("evaluate polynomial: %w", err)
}
+47 -47
View File
@@ -1,4 +1,4 @@
package mod1
package hefloat_test
import (
"math"
@@ -7,12 +7,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func TestMod1(t *testing.T) {
@@ -22,7 +20,7 @@ func TestMod1(t *testing.T) {
t.Skip("skipping homomorphic mod tests for GOARCH=wasm")
}
ParametersLiteral := ckks.ParametersLiteral{
ParametersLiteral := hefloat.ParametersLiteral{
LogN: 10,
LogQ: []int{55, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 53},
LogP: []int{61, 61, 61, 61, 61},
@@ -30,14 +28,14 @@ func TestMod1(t *testing.T) {
LogDefaultScale: 45,
}
testMod1Marshalling(t)
testMod1Marhsalling(t)
var params ckks.Parameters
if params, err = ckks.NewParametersFromLiteral(ParametersLiteral); err != nil {
var params hefloat.Parameters
if params, err = hefloat.NewParametersFromLiteral(ParametersLiteral); err != nil {
t.Fatal(err)
}
for _, testSet := range []func(params ckks.Parameters, t *testing.T){
for _, testSet := range []func(params hefloat.Parameters, t *testing.T){
testMod1,
} {
testSet(params, t)
@@ -45,12 +43,12 @@ func TestMod1(t *testing.T) {
}
}
func testMod1Marshalling(t *testing.T) {
func testMod1Marhsalling(t *testing.T) {
t.Run("Marshalling", func(t *testing.T) {
evm := ParametersLiteral{
LevelQ: 12,
Mod1Type: SinContinuous,
evm := hefloat.Mod1ParametersLiteral{
LevelStart: 12,
Mod1Type: hefloat.SinContinuous,
LogMessageRatio: 8,
K: 14,
Mod1Degree: 127,
@@ -61,7 +59,7 @@ func testMod1Marshalling(t *testing.T) {
data, err := evm.MarshalBinary()
assert.Nil(t, err)
evmNew := new(ParametersLiteral)
evmNew := new(hefloat.Mod1ParametersLiteral)
if err := evmNew.UnmarshalBinary(data); err != nil {
assert.Nil(t, err)
}
@@ -69,20 +67,20 @@ func testMod1Marshalling(t *testing.T) {
})
}
func testMod1(params ckks.Parameters, t *testing.T) {
func testMod1(params hefloat.Parameters, t *testing.T) {
kgen := rlwe.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
enc := rlwe.NewEncryptor(params, sk)
dec := rlwe.NewDecryptor(params, sk)
eval := ckks.NewEvaluator(params, rlwe.NewMemEvaluationKeySet(kgen.GenRelinearizationKeyNew(sk)))
eval := hefloat.NewEvaluator(params, rlwe.NewMemEvaluationKeySet(kgen.GenRelinearizationKeyNew(sk)))
t.Run("SineContinuousWithArcSine", func(t *testing.T) {
evm := ParametersLiteral{
LevelQ: 12,
Mod1Type: SinContinuous,
evm := hefloat.Mod1ParametersLiteral{
LevelStart: 12,
Mod1Type: hefloat.SinContinuous,
LogMessageRatio: 8,
K: 14,
Mod1Degree: 127,
@@ -90,16 +88,16 @@ func testMod1(params ckks.Parameters, t *testing.T) {
LogScale: 60,
}
values, ciphertext := evaluateMod1(evm, params, ecd, enc, dec, eval, t)
values, ciphertext := evaluateMod1(evm, params, ecd, enc, eval, t)
ckks.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), 0, true, t)
hefloat.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), 0, *printPrecisionStats, t)
})
t.Run("CosDiscrete", func(t *testing.T) {
evm := ParametersLiteral{
LevelQ: 12,
Mod1Type: CosDiscrete,
evm := hefloat.Mod1ParametersLiteral{
LevelStart: 12,
Mod1Type: hefloat.CosDiscrete,
LogMessageRatio: 8,
K: 12,
Mod1Degree: 30,
@@ -107,16 +105,16 @@ func testMod1(params ckks.Parameters, t *testing.T) {
LogScale: 60,
}
values, ciphertext := evaluateMod1(evm, params, ecd, enc, dec, eval, t)
values, ciphertext := evaluateMod1(evm, params, ecd, enc, eval, t)
ckks.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), 0, true, t)
hefloat.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), 0, *printPrecisionStats, t)
})
t.Run("CosContinuous", func(t *testing.T) {
evm := ParametersLiteral{
LevelQ: 12,
Mod1Type: CosContinuous,
evm := hefloat.Mod1ParametersLiteral{
LevelStart: 12,
Mod1Type: hefloat.CosContinuous,
LogMessageRatio: 4,
K: 325,
Mod1Degree: 177,
@@ -124,15 +122,15 @@ func testMod1(params ckks.Parameters, t *testing.T) {
LogScale: 60,
}
values, ciphertext := evaluateMod1(evm, params, ecd, enc, dec, eval, t)
values, ciphertext := evaluateMod1(evm, params, ecd, enc, eval, t)
ckks.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), 0, true, t)
hefloat.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), 0, *printPrecisionStats, t)
})
}
func evaluateMod1(evm ParametersLiteral, params ckks.Parameters, ecd *ckks.Encoder, enc *rlwe.Encryptor, dec *rlwe.Decryptor, eval *ckks.Evaluator, t *testing.T) ([]float64, *rlwe.Ciphertext) {
func evaluateMod1(evm hefloat.Mod1ParametersLiteral, params hefloat.Parameters, ecd *hefloat.Encoder, enc *rlwe.Encryptor, eval *hefloat.Evaluator, t *testing.T) ([]float64, *rlwe.Ciphertext) {
mod1Parameters, err := NewParametersFromLiteral(params, evm)
mod1Parameters, err := hefloat.NewMod1ParametersFromLiteral(params, evm)
require.NoError(t, err)
values, _, ciphertext := newTestVectorsMod1(params, enc, ecd, mod1Parameters, t)
@@ -148,11 +146,11 @@ func evaluateMod1(evm ParametersLiteral, params ckks.Parameters, ecd *ckks.Encod
eval.ScaleUp(ciphertext, rlwe.NewScale(math.Round(scale.Float64())), ciphertext)
// Normalization
require.NoError(t, eval.Mul(ciphertext, 1/(mod1Parameters.K*mod1Parameters.QDiff), ciphertext))
require.NoError(t, eval.Mul(ciphertext, 1/(float64(mod1Parameters.K())*mod1Parameters.QDiff()), ciphertext))
require.NoError(t, eval.Rescale(ciphertext, ciphertext))
// EvalMod
ciphertext, err = NewEvaluator(eval, polynomial.NewEvaluator(params, eval), mod1Parameters).EvaluateNew(ciphertext)
ciphertext, err = hefloat.NewMod1Evaluator(eval, hefloat.NewPolynomialEvaluator(params, eval), mod1Parameters).EvaluateNew(ciphertext)
require.NoError(t, err)
// PlaintextCircuit
@@ -160,7 +158,7 @@ func evaluateMod1(evm ParametersLiteral, params ckks.Parameters, ecd *ckks.Encod
x := values[i]
x /= mod1Parameters.MessageRatio()
x /= mod1Parameters.QDiff
x /= mod1Parameters.QDiff()
x = math.Sin(6.28318530717958 * x)
if evm.Mod1InvDegree > 0 {
@@ -168,7 +166,7 @@ func evaluateMod1(evm ParametersLiteral, params ckks.Parameters, ecd *ckks.Encod
}
x *= mod1Parameters.MessageRatio()
x *= mod1Parameters.QDiff
x *= mod1Parameters.QDiff()
x /= 6.28318530717958
values[i] = x
@@ -177,20 +175,22 @@ func evaluateMod1(evm ParametersLiteral, params ckks.Parameters, ecd *ckks.Encod
return values, ciphertext
}
func newTestVectorsMod1(params ckks.Parameters, encryptor *rlwe.Encryptor, encoder *ckks.Encoder, evm Parameters, t *testing.T) (values []float64, plaintext *rlwe.Plaintext, ciphertext *rlwe.Ciphertext) {
func newTestVectorsMod1(params hefloat.Parameters, encryptor *rlwe.Encryptor, encoder *hefloat.Encoder, evm hefloat.Mod1Parameters, t *testing.T) (values []float64, plaintext *rlwe.Plaintext, ciphertext *rlwe.Ciphertext) {
values = make([]float64, params.MaxSlots())
logSlots := params.LogMaxDimensions().Cols
K := evm.K - 1
Q := evm.QDiff * evm.MessageRatio()
values = make([]float64, 1<<logSlots)
for i := range values {
K := float64(evm.K() - 1)
Q := float64(params.Q()[0]) / math.Exp2(math.Round(math.Log2(float64(params.Q()[0])))) * evm.MessageRatio()
for i := uint64(0); i < 1<<logSlots; i++ {
values[i] = math.Round(sampling.RandFloat64(-K, K))*Q + sampling.RandFloat64(-1, 1)
}
values[0] = K*Q + 0.5
plaintext = ckks.NewPlaintext(params, params.MaxLevel())
plaintext = hefloat.NewPlaintext(params, params.MaxLevel())
encoder.Encode(values, plaintext)
+74 -65
View File
@@ -1,4 +1,4 @@
package mod1
package hefloat
import (
"encoding/json"
@@ -7,60 +7,57 @@ import (
"math/big"
"math/bits"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/cosine"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat/cosine"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Type is the type of function/approximation used to evaluate x mod 1.
type Type uint64
// Mod1Type is the type of function/approximation used to evaluate x mod 1.
type Mod1Type uint64
// Sin and Cos are the two proposed functions for [Type].
// Sin and Cos are the two proposed functions for Mod1Type.
// These trigonometric functions offer a good approximation of the function x mod 1 when the values are close to the origin.
const (
CosDiscrete = Type(0) // Special approximation (Han and Ki) of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r); this method requires a minimum degree of 2*(K-1).
SinContinuous = Type(1) // Standard Chebyshev approximation of (1/2pi) * sin(2pix) on the full interval
CosContinuous = Type(2) // Standard Chebyshev approximation of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r) on the full interval
CosDiscrete = Mod1Type(0) // Special approximation (Han and Ki) of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r); this method requires a minimum degree of 2*(K-1).
SinContinuous = Mod1Type(1) // Standard Chebyshev approximation of (1/2pi) * sin(2pix) on the full interval
CosContinuous = Mod1Type(2) // Standard Chebyshev approximation of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r) on the full interval
)
// ParametersLiteral a struct for the parameters of the mod 1 procedure.
// Mod1ParametersLiteral a struct for the parameters of the mod 1 procedure.
// The x mod 1 procedure goal is to homomorphically evaluate a modular reduction by Q[0] (the first prime of the moduli chain) on the encrypted plaintext.
// This struct is consumed by [NewParametersFromLiteral] to generate the [ParametersLiteral] struct, which notably stores
// This struct is consumed by `NewMod1ParametersLiteralFromLiteral` to generate the `Mod1ParametersLiteral` struct, which notably stores
// the coefficient of the polynomial approximating the function x mod Q[0].
type ParametersLiteral struct {
LevelQ int // Starting level of x mod 1
LogScale int // Log2 of the scaling factor used during x mod 1
Mod1Type Type // Chose between [Sin(2*pi*x)] or [cos(2*pi*x/r) with double angle formula]
Scaling float64 // Value by which the output is scaled by
LogMessageRatio int // Log2 of the ratio between Q0 and m, i.e. Q[0]/|m|
K int // K parameter (interpolation in the range -K to K)
Mod1Degree int // Degree of f: x mod 1
DoubleAngle int // Number of rescale and double angle formula (only applies for cos and is ignored if sin is used)
Mod1InvDegree int // Degree of f^-1: (x mod 1)^-1
type Mod1ParametersLiteral struct {
LevelStart int // Starting level of x mod 1
LogScale int // Log2 of the scaling factor used during x mod 1
Mod1Type Mod1Type // Chose between [Sin(2*pi*x)] or [cos(2*pi*x/r) with double angle formula]
Scaling float64 // Value by which the output is scaled by
LogMessageRatio int // Log2 of the ratio between Q0 and m, i.e. Q[0]/|m|
K int // K parameter (interpolation in the range -K to K)
Mod1Degree int // Degree of f: x mod 1
DoubleAngle int // Number of rescale and double angle formula (only applies for cos and is ignored if sin is used)
Mod1InvDegree int // Degree of f^-1: (x mod 1)^-1
}
// MarshalBinary returns a JSON representation of the the target Mod1ParametersLiteral struct on a slice of bytes.
// See Marshal from the [encoding/json] package.
func (evm ParametersLiteral) MarshalBinary() (data []byte, err error) {
// See `Marshal` from the `encoding/json` package.
func (evm Mod1ParametersLiteral) MarshalBinary() (data []byte, err error) {
return json.Marshal(evm)
}
// UnmarshalBinary reads a JSON representation on the target Mod1ParametersLiteral struct.
// See Unmarshal from the [encoding/json] package.
func (evm *ParametersLiteral) UnmarshalBinary(data []byte) (err error) {
// See `Unmarshal` from the `encoding/json` package.
func (evm *Mod1ParametersLiteral) UnmarshalBinary(data []byte) (err error) {
return json.Unmarshal(data, evm)
}
// Depth returns the depth required to evaluate x mod 1.
func (evm ParametersLiteral) Depth() (depth int) {
func (evm Mod1ParametersLiteral) Depth() (depth int) {
if evm.Mod1Type == CosDiscrete { // this method requires a minimum degree of 2*K-1.
/* #nosec G115 -- Mod1Degree cannot be negative */
depth += int(bits.Len64(uint64(utils.Max(evm.Mod1Degree, 2*evm.K-1))))
} else {
/* #nosec G115 -- Mod1Degree cannot be negative */
depth += int(bits.Len64(uint64(evm.Mod1Degree)))
}
@@ -68,48 +65,60 @@ func (evm ParametersLiteral) Depth() (depth int) {
depth += evm.DoubleAngle
}
/* #nosec G115 -- Mod1InvDegree cannot be negative */
depth += int(bits.Len64(uint64(evm.Mod1InvDegree)))
return depth
}
// Parameters is a struct storing the parameters and polynomials approximating the function x mod Q[0] (the first prime of the moduli chain).
type Parameters struct {
LevelQ int // starting level of the operation
// Mod1Parameters is a struct storing the parameters and polynomials approximating the function x mod Q[0] (the first prime of the moduli chain).
type Mod1Parameters struct {
levelStart int // starting level of the operation
LogDefaultScale int // log2 of the default scaling factor
Mod1Type Type // type of approximation for the f: x mod 1 function
Mod1Type Mod1Type // type of approximation for the f: x mod 1 function
LogMessageRatio int // Log2 of the ratio between Q0 and m, i.e. Q[0]/|m|
DoubleAngle int // Number of rescale and double angle formula (only applies for cos and is ignored if sin is used)
QDiff float64 // Q / 2^round(Log2(Q))
Sqrt2Pi float64 // (1/2pi)^(1.0/scFac)
Mod1Poly bignum.Polynomial // Polynomial for f: x mod 1
Mod1InvPoly *bignum.Polynomial // Polynomial for f^-1: (x mod 1)^-1
K float64 // interval [-K, K]
doubleAngle int // Number of rescale and double angle formula (only applies for cos and is ignored if sin is used)
qDiff float64 // Q / 2^round(Log2(Q))
scFac float64 // 2^doubleAngle
sqrt2Pi float64 // (1/2pi)^(1.0/scFac)
mod1Poly bignum.Polynomial // Polynomial for f: x mod 1
mod1InvPoly *bignum.Polynomial // Polynomial for f^-1: (x mod 1)^-1
k float64 // interval [-k, k]
}
// IntervalShrinkFactor returns 2^{DoubleAngle}
func (evp Parameters) IntervalShrinkFactor() float64 {
return math.Exp2(float64(evp.DoubleAngle))
}
// KShrinked returns K / IntervalShrinkFactor()
func (evp Parameters) KShrinked() float64 {
return evp.K / evp.IntervalShrinkFactor()
// LevelStart returns the starting level of the x mod 1.
func (evp Mod1Parameters) LevelStart() int {
return evp.levelStart
}
// ScalingFactor returns scaling factor used during the x mod 1.
func (evp Parameters) ScalingFactor() rlwe.Scale {
func (evp Mod1Parameters) ScalingFactor() rlwe.Scale {
return rlwe.NewScale(math.Exp2(float64(evp.LogDefaultScale)))
}
// ScFac returns 1/2^r where r is the number of double angle evaluation.
func (evp Mod1Parameters) ScFac() float64 {
return evp.scFac
}
// MessageRatio returns the pre-set ratio Q[0]/|m|.
func (evp Parameters) MessageRatio() float64 {
func (evp Mod1Parameters) MessageRatio() float64 {
return float64(uint(1 << evp.LogMessageRatio))
}
// NewParametersFromLiteral generates an [Parameters] struct from the [ParametersLiteral] struct.
// The [Parameters] struct is to instantiates a [Mod1Evaluator], which homomorphically evaluates x mod 1.
func NewParametersFromLiteral(params ckks.Parameters, evm ParametersLiteral) (Parameters, error) {
// K return the sine approximation range.
func (evp Mod1Parameters) K() float64 {
return evp.k * evp.scFac
}
// QDiff return Q[0]/ClosetPow2
// This is the error introduced by the approximate division by Q[0].
func (evp Mod1Parameters) QDiff() float64 {
return evp.qDiff
}
// NewMod1ParametersFromLiteral generates an Mod1Parameters struct from the Mod1ParametersLiteral struct.
// The Mod1Parameters struct is to instantiates a Mod1Evaluator, which homomorphically evaluates x mod 1.
func NewMod1ParametersFromLiteral(params Parameters, evm Mod1ParametersLiteral) (Mod1Parameters, error) {
var mod1InvPoly *bignum.Polynomial
var mod1Poly bignum.Polynomial
var sqrt2pi float64
@@ -199,7 +208,7 @@ func NewParametersFromLiteral(params ckks.Parameters, evm ParametersLiteral) (Pa
}
default:
return Parameters{}, fmt.Errorf("invalid Mod1Type")
return Mod1Parameters{}, fmt.Errorf("invalid Mod1Type")
}
sqrt2piBig := new(big.Float).SetFloat64(sqrt2pi)
@@ -210,19 +219,19 @@ func NewParametersFromLiteral(params ckks.Parameters, evm ParametersLiteral) (Pa
}
}
return Parameters{
LevelQ: evm.LevelQ,
return Mod1Parameters{
levelStart: evm.LevelStart,
LogDefaultScale: evm.LogScale,
Mod1Type: evm.Mod1Type,
LogMessageRatio: evm.LogMessageRatio,
DoubleAngle: doubleAngle,
QDiff: qDiff,
Sqrt2Pi: sqrt2pi,
Mod1Poly: mod1Poly,
Mod1InvPoly: mod1InvPoly,
K: float64(evm.K),
doubleAngle: doubleAngle,
qDiff: qDiff,
scFac: scFac,
sqrt2Pi: sqrt2pi,
mod1Poly: mod1Poly,
mod1InvPoly: mod1InvPoly,
k: K,
}, nil
}
func sin2pi(x *big.Float) (y *big.Float) {
+12 -13
View File
@@ -1,35 +1,34 @@
// Package polynomial implements a homomorphic polynomial evaluator for the CKKS scheme.
package polynomial
package hefloat
import (
"math/big"
"github.com/tuneinsight/lattigo/v6/circuits/common/polynomial"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Polynomial is a type wrapping the type [polynomial.Polynomial].
type Polynomial polynomial.Polynomial
// Polynomial is a type wrapping the type he.Polynomial.
type Polynomial he.Polynomial
// NewPolynomial creates a new Polynomial from a [bignum.Polynomial].
// NewPolynomial creates a new Polynomial from a bignum.Polynomial.
func NewPolynomial(poly bignum.Polynomial) Polynomial {
return Polynomial(polynomial.NewPolynomial(poly))
return Polynomial(he.NewPolynomial(poly))
}
// PolynomialVector is a type wrapping the type [polynomial.PolynomialVector].
type PolynomialVector polynomial.PolynomialVector
// PolynomialVector is a type wrapping the type he.PolynomialVector.
type PolynomialVector he.PolynomialVector
// Depth returns the depth of the target [PolynomialVector].
// Depth returns the depth of the target PolynomialVector.
func (p PolynomialVector) Depth() int {
return p.Value[0].Depth()
}
// NewPolynomialVector creates a new PolynomialVector from a list of [bignum.Polynomial] and a mapping
// NewPolynomialVector creates a new PolynomialVector from a list of bignum.Polynomial and a mapping
// map[poly_index][slots_index] which stores which polynomial has to be evaluated on which slot.
// Slots that are not referenced in this mapping will be evaluated to zero.
// User must ensure that a same slot is not referenced twice.
func NewPolynomialVector(polys []bignum.Polynomial, mapping map[int][]int) (PolynomialVector, error) {
p, err := polynomial.NewPolynomialVector(polys, mapping)
p, err := he.NewPolynomialVector(polys, mapping)
return PolynomialVector(p), err
}
@@ -1,48 +1,41 @@
package polynomial
package hefloat
import (
"fmt"
"math/big"
"math/bits"
"github.com/tuneinsight/lattigo/v6/circuits/common/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// simEvaluator is a struct used to pre-compute the scaling
// simEvaluator is a struct used to pre-computed the scaling
// factors of the polynomial coefficients used by the inlined
// polynomial evaluation by running the polynomial evaluation
// with dummy operands.
// This struct implements the interface polynomial.SimEvaluator.
// This struct implements the interface he.SimEvaluator.
type simEvaluator struct {
params ckks.Parameters
params Parameters
levelsConsumedPerRescaling int
}
// PolynomialDepth returns the depth of the polynomial.
func (d simEvaluator) PolynomialDepth(degree int) int {
if degree <= 0 {
panic(fmt.Errorf("invalid degree: degree=%d should be greater than zero", degree))
}
return d.levelsConsumedPerRescaling * (bits.Len64(uint64(degree)) - 1)
}
// Rescale rescales the target polynomial.SimOperand n times and returns it.
func (d simEvaluator) Rescale(op0 *polynomial.SimOperand) {
// Rescale rescales the target he.SimOperand n times and returns it.
func (d simEvaluator) Rescale(op0 *he.SimOperand) {
for i := 0; i < d.levelsConsumedPerRescaling; i++ {
op0.Scale = op0.Scale.Div(rlwe.NewScale(d.params.Q()[op0.Level]))
op0.Level--
}
}
// MulNew multiplies two polynomial.SimOperand, stores the result the target polynomial.SimOperand and returns the result.
func (d simEvaluator) MulNew(op0, op1 *polynomial.SimOperand) (opOut *polynomial.SimOperand) {
opOut = new(polynomial.SimOperand)
// MulNew multiplies two he.SimOperand, stores the result the target he.SimOperand and returns the result.
func (d simEvaluator) MulNew(op0, op1 *he.SimOperand) (opOut *he.SimOperand) {
opOut = new(he.SimOperand)
opOut.Level = utils.Min(op0.Level, op1.Level)
opOut.Scale = op0.Scale.Mul(op1.Scale)
return
+40 -52
View File
@@ -1,18 +1,16 @@
// Package lintrans bundles generic parts of the homomorphic linear transformation circuit.
package lintrans
package he
import (
"fmt"
"sort"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/schemes"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// Parameters is a struct storing the parameterization of a
// LinearTransformationParameters is a struct storing the parameterization of a
// linear transformation.
//
// A homomorphic linear transformations on a ciphertext acts as evaluating:
@@ -46,20 +44,15 @@ import (
// than their matrix representation by being able to only store the non-zero diagonals.
//
// Finally, some metrics about the time and storage complexity of homomorphic linear transformations:
// - Storage: #diagonals polynomials mod Q * P
// - Storage: #diagonals polynomials mod Q_level * P
// - Evaluation: #diagonals multiplications and 2sqrt(#diagonals) ciphertexts rotations.
type Parameters struct {
type LinearTransformationParameters struct {
// DiagonalsIndexList is the list of the non-zero diagonals of the square matrix.
// A non zero diagonals is a diagonal with a least one non-zero element.
DiagonalsIndexList []int
// LevelQ is the level at which to encode the linear transformation.
LevelQ int
// LevelP is the level of the auxliary prime used during the automorphisms
// User must ensure that this value is the same as the one used to generate
// the evaluation keys used to perform the automorphisms.
LevelP int
// Level is the level at which to encode the linear transformation.
Level int
// Scale is the plaintext scale at which to encode the linear transformation.
Scale rlwe.Scale
@@ -71,13 +64,13 @@ type Parameters struct {
// the SIMD packed matrix.
LogDimensions ring.Dimensions
// LogBabyStepGiantStepRatio is the log2 of the ratio n1/n2 for n = n1 * n2 and
// LogBabyStepGianStepRatio is the log2 of the ratio n1/n2 for n = n1 * n2 and
// n is the dimension of the linear transformation. The number of Galois keys required
// is minimized when this value is 0 but the overall complexity of the homomorphic evaluation
// can be reduced by increasing the ratio (at the expanse of increasing the number of keys required).
// If the value returned is negative, then the baby-step giant-step algorithm is not used
// and the evaluation complexity (as well as the number of keys) becomes O(n) instead of O(sqrt(n)).
LogBabyStepGiantStepRatio int
LogBabyStepGianStepRatio int
}
type Diagonals[T any] map[int][]T
@@ -123,19 +116,18 @@ func (m Diagonals[T]) At(i, slots int) ([]T, error) {
// LinearTransformation is a type for linear transformations on ciphertexts.
// It stores a plaintext matrix in diagonal form and can be evaluated on a
// ciphertext using a [Evaluator].
// ciphertext using a LinearTransformationEvaluator.
type LinearTransformation struct {
*rlwe.MetaData
LogBabyStepGiantStepRatio int
N1 int
LevelQ int
LevelP int
Vec map[int]ringqp.Poly
LogBabyStepGianStepRatio int
N1 int
Level int
Vec map[int]ringqp.Poly
}
// GaloisElements returns the list of Galois elements needed for the evaluation of the linear transformation.
func (lt LinearTransformation) GaloisElements(params rlwe.ParameterProvider) (galEls []uint64) {
return GaloisElements(params, utils.GetKeys(lt.Vec), 1<<lt.LogDimensions.Cols, lt.LogBabyStepGiantStepRatio)
return GaloisElementsForLinearTransformation(params, utils.GetKeys(lt.Vec), 1<<lt.LogDimensions.Cols, lt.LogBabyStepGianStepRatio)
}
// BSGSIndex returns the BSGSIndex of the target linear transformation.
@@ -143,23 +135,22 @@ func (lt LinearTransformation) BSGSIndex() (index map[int][]int, n1, n2 []int) {
return BSGSIndex(utils.GetKeys(lt.Vec), 1<<lt.LogDimensions.Cols, lt.N1)
}
// NewLinearTransformation allocates a new LinearTransformation with zero values according to the parameters specified
// by the [Parameters].
func NewLinearTransformation(params rlwe.ParameterProvider, ltparams Parameters) LinearTransformation {
// NewLinearTransformation allocates a new LinearTransformation with zero values according to the parameters specified by the LinearTransformationParameters.
func NewLinearTransformation(params rlwe.ParameterProvider, ltparams LinearTransformationParameters) LinearTransformation {
p := params.GetRLWEParameters()
vec := make(map[int]ringqp.Poly)
cols := 1 << ltparams.LogDimensions.Cols
logBabyStepGiantStepRatio := ltparams.LogBabyStepGiantStepRatio
levelQ := ltparams.LevelQ
levelP := ltparams.LevelP
logBabyStepGianStepRatio := ltparams.LogBabyStepGianStepRatio
levelQ := ltparams.Level
levelP := p.MaxLevelP()
ringQP := p.RingQP().AtLevel(levelQ, levelP)
diagslislt := ltparams.DiagonalsIndexList
var N1 int
if logBabyStepGiantStepRatio < 0 {
if logBabyStepGianStepRatio < 0 {
N1 = 0
for _, i := range diagslislt {
idx := i
@@ -169,7 +160,7 @@ func NewLinearTransformation(params rlwe.ParameterProvider, ltparams Parameters)
vec[idx] = ringQP.NewPoly()
}
} else {
N1 = FindBestBSGSRatio(diagslislt, cols, logBabyStepGiantStepRatio)
N1 = FindBestBSGSRatio(diagslislt, cols, logBabyStepGianStepRatio)
index, _, _ := BSGSIndex(diagslislt, cols, N1)
for j := range index {
for _, i := range index[j] {
@@ -191,18 +182,16 @@ func NewLinearTransformation(params rlwe.ParameterProvider, ltparams Parameters)
}
return LinearTransformation{
MetaData: metadata,
LogBabyStepGiantStepRatio: logBabyStepGiantStepRatio,
N1: N1,
LevelQ: levelQ,
LevelP: levelP,
Vec: vec,
MetaData: metadata,
LogBabyStepGianStepRatio: logBabyStepGianStepRatio,
N1: N1,
Level: levelQ,
Vec: vec,
}
}
// Encode encodes on a pre-allocated LinearTransformation a set of non-zero diagonals of a matrix representing
// a linear transformation.
func Encode[T any](encoder schemes.Encoder, diagonals Diagonals[T], allocated LinearTransformation) (err error) {
// EncodeLinearTransformation encodes on a pre-allocated LinearTransformation a set of non-zero diagonaes of a matrix representing a linear transformation.
func EncodeLinearTransformation[T any](encoder Encoder[T, ringqp.Poly], diagonals Diagonals[T], allocated LinearTransformation) (err error) {
rows := 1 << allocated.LogDimensions.Rows
cols := 1 << allocated.LogDimensions.Cols
@@ -227,11 +216,11 @@ func Encode[T any](encoder schemes.Encoder, diagonals Diagonals[T], allocated Li
}
if vec, ok := allocated.Vec[idx]; !ok {
return fmt.Errorf("cannot Encode: error encoding on LinearTransformation: plaintext diagonal [%d] does not exist", idx)
return fmt.Errorf("cannot EncodeLinearTransformation: error encoding on LinearTransformation: plaintext diagonal [%d] does not exist", idx)
} else {
if v, err = diagonals.At(i, cols); err != nil {
return fmt.Errorf("cannot Encode: %w", err)
return fmt.Errorf("cannot EncodeLinearTransformation: %w", err)
}
if err = rotateAndEncodeDiagonal(v, encoder, 0, metaData, buf, vec); err != nil {
@@ -254,7 +243,7 @@ func Encode[T any](encoder schemes.Encoder, diagonals Diagonals[T], allocated Li
} else {
if v, err = diagonals.At(i+j, cols); err != nil {
return fmt.Errorf("cannot Encode: %w", err)
return fmt.Errorf("cannot EncodeLinearTransformation: %w", err)
}
if err = rotateAndEncodeDiagonal(v, encoder, rot, metaData, buf, vec); err != nil {
@@ -268,7 +257,7 @@ func Encode[T any](encoder schemes.Encoder, diagonals Diagonals[T], allocated Li
return
}
func rotateAndEncodeDiagonal[T any](v []T, encoder schemes.Encoder, rot int, metaData *rlwe.MetaData, buf []T, poly ringqp.Poly) (err error) {
func rotateAndEncodeDiagonal[T any](v []T, encoder Encoder[T, ringqp.Poly], rot int, metaData *rlwe.MetaData, buf []T, poly ringqp.Poly) (err error) {
rows := 1 << metaData.LogDimensions.Rows
cols := 1 << metaData.LogDimensions.Cols
@@ -288,13 +277,12 @@ func rotateAndEncodeDiagonal[T any](v []T, encoder schemes.Encoder, rot int, met
values = v
}
return encoder.Embed(values, metaData, poly)
return encoder.Encode(values, metaData, poly)
}
// GaloisElements returns the list of Galois elements needed for the evaluation of a linear transformation
// given the index of its non-zero diagonals, the number of slots in the plaintext and the LogBabyStepGiantStepRatio,
// see [Parameters].
func GaloisElements(params rlwe.ParameterProvider, diags []int, slots, logBabyStepGianStepRatio int) (galEls []uint64) {
// GaloisElementsForLinearTransformation returns the list of Galois elements needed for the evaluation of a linear transformation
// given the index of its non-zero diagonals, the number of slots in the plaintext and the LogBabyStepGianStepRatio (see LinearTransformationParameters).
func GaloisElementsForLinearTransformation(params rlwe.ParameterProvider, diags []int, slots, logBabyStepGianStepRatio int) (galEls []uint64) {
p := params.GetRLWEParameters()
+79 -117
View File
@@ -1,30 +1,40 @@
package lintrans
package he
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/schemes"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
type Evaluator struct {
schemes.Evaluator
pool *rlwe.BufferPool
// EvaluatorForLinearTransformation defines a set of common and scheme agnostic method necessary to instantiate an LinearTransformationEvaluator.
type EvaluatorForLinearTransformation interface {
rlwe.ParameterProvider
Rescale(op1, op2 *rlwe.Ciphertext) (err error)
GetBuffQP() [6]ringqp.Poly
GetBuffCt() *rlwe.Ciphertext
GetBuffDecompQP() []ringqp.Poly
DecomposeNTT(level, levelP, pCount int, c1 ring.Poly, isNTT bool, BuffDecompQP []ringqp.Poly)
CheckAndGetGaloisKey(galEl uint64) (evk *rlwe.GaloisKey, err error)
GadgetProductLazy(levelQ int, cx ring.Poly, gadgetCt *rlwe.GadgetCiphertext, ct *rlwe.Element[ringqp.Poly])
GadgetProductHoistedLazy(levelQ int, BuffQPDecompQP []ringqp.Poly, gadgetCt *rlwe.GadgetCiphertext, ct *rlwe.Element[ringqp.Poly])
AutomorphismHoistedLazy(levelQ int, ctIn *rlwe.Ciphertext, c1DecompQP []ringqp.Poly, galEl uint64, ctQP *rlwe.Element[ringqp.Poly]) (err error)
ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q ring.Poly)
AutomorphismIndex(uint64) []uint64
}
func NewEvaluator(eval schemes.Evaluator) *Evaluator {
return &Evaluator{
Evaluator: eval,
pool: rlwe.NewPool(eval.GetRLWEParameters().RingQP()),
}
type EvaluatorForDiagonalMatrix interface {
Decompose(levelQ int, ctIn *rlwe.Ciphertext, BuffDecompQP []ringqp.Poly)
GetPreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ int, ctIn *rlwe.Ciphertext, BuffDecompQP []ringqp.Poly, rots []int, ctPreRot map[int]*rlwe.Element[ringqp.Poly]) (err error)
MultiplyByDiagMatrix(ctIn *rlwe.Ciphertext, matrix LinearTransformation, BuffDecompQP []ringqp.Poly, opOut *rlwe.Ciphertext) (err error)
MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix LinearTransformation, ctInPreRot map[int]*rlwe.Element[ringqp.Poly], opOut *rlwe.Ciphertext) (err error)
}
// EvaluateMany takes as input a ciphertext ctIn, a list of linear transformations [M0, M1, M2, ...] and a list of pre-allocated receiver opOut
// EvaluateLinearTransformationsMany takes as input a ciphertext ctIn, a list of linear transformations [M0, M1, M2, ...] and a list of pre-allocated receiver opOut
// and evaluates opOut: [M0(ctIn), M1(ctIn), M2(ctIn), ...]
func (eval Evaluator) EvaluateMany(ctIn *rlwe.Ciphertext, linearTransformations []LinearTransformation, opOut []*rlwe.Ciphertext) (err error) {
func EvaluateLinearTransformationsMany(evalLT EvaluatorForLinearTransformation, evalDiag EvaluatorForDiagonalMatrix, ctIn *rlwe.Ciphertext, linearTransformations []LinearTransformation, opOut []*rlwe.Ciphertext) (err error) {
if len(opOut) < len(linearTransformations) {
return fmt.Errorf("output *rlwe.Ciphertext slice is too small")
@@ -35,41 +45,33 @@ func (eval Evaluator) EvaluateMany(ctIn *rlwe.Ciphertext, linearTransformations
}
}
var levelQ int
levelP := linearTransformations[0].LevelP
var level int
for _, lt := range linearTransformations {
levelQ = utils.Max(levelQ, lt.LevelQ)
if levelP != lt.LevelP {
return fmt.Errorf("all linearTransformations must have the same levelP")
}
level = utils.Max(level, lt.Level)
}
level = utils.Min(level, ctIn.Level())
levelQ = utils.Min(levelQ, ctIn.Level())
BuffDecompQP := evalLT.GetBuffDecompQP()
poolQP := eval.pool.AtLevel(levelQ, levelP)
buffDecompQP := poolQP.GetBuffDecompQP(*eval.GetRLWEParameters(), levelQ, levelP)
defer eval.pool.RecycleBuffDecompQP(buffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctIn.Value[1], ctIn.IsNTT, buffDecompQP)
evalDiag.Decompose(level, ctIn, BuffDecompQP)
ctPreRot := map[int]*rlwe.Element[ringqp.Poly]{}
for i, lt := range linearTransformations {
if lt.N1 == 0 {
if err = eval.MultiplyByDiagMatrix(ctIn, lt, buffDecompQP, opOut[i]); err != nil {
if err = evalDiag.MultiplyByDiagMatrix(ctIn, lt, BuffDecompQP, opOut[i]); err != nil {
return
}
} else {
_, _, rotN2 := lt.BSGSIndex()
if err = eval.PreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ, levelP, ctIn, buffDecompQP, rotN2, ctPreRot); err != nil {
if err = evalDiag.GetPreRotatedCiphertextForDiagonalMatrixMultiplication(level, ctIn, BuffDecompQP, rotN2, ctPreRot); err != nil {
return
}
if err = eval.MultiplyByDiagMatrixBSGS(ctIn, lt, ctPreRot, opOut[i]); err != nil {
if err = evalDiag.MultiplyByDiagMatrixBSGS(ctIn, lt, ctPreRot, opOut[i]); err != nil {
return
}
}
@@ -78,10 +80,11 @@ func (eval Evaluator) EvaluateMany(ctIn *rlwe.Ciphertext, linearTransformations
return
}
// PreRotatedCiphertextForDiagonalMatrixMultiplication populates ctPreRot with the pre-rotated ciphertext for the rotations rots and deletes rotated ciphertexts that are not in rots.
func (eval Evaluator) PreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ, levelP int, ctIn *rlwe.Ciphertext, BuffDecompQP []ringqp.Poly, rots []int, ctPreRot map[int]*rlwe.Element[ringqp.Poly]) (err error) {
// GetPreRotatedCiphertextForDiagonalMatrixMultiplication populates ctPreRot with the pre-rotated ciphertext for the rotations rots and deletes rotated ciphertexts that are not in rots.
func GetPreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ int, eval EvaluatorForLinearTransformation, ctIn *rlwe.Ciphertext, BuffDecompQP []ringqp.Poly, rots []int, ctPreRot map[int]*rlwe.Element[ringqp.Poly]) (err error) {
params := eval.GetRLWEParameters()
levelP := params.MaxLevelP()
rotsMap := map[int]bool{}
@@ -101,7 +104,7 @@ func (eval Evaluator) PreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ
if _, ok := ctPreRot[i]; i != 0 && !ok {
ctPreRot[i] = rlwe.NewElementExtended(params, 1, levelQ, levelP)
if err = eval.AutomorphismHoistedLazy(levelQ, ctIn, BuffDecompQP, params.GaloisElement(i), ctPreRot[i]); err != nil {
return fmt.Errorf("eval.AutomorphismHoistedLazy: %w", err)
return
}
}
}
@@ -109,24 +112,24 @@ func (eval Evaluator) PreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ
return
}
// EvaluateSequential takes as input a ciphertext ctIn and a list of linear transformations [M0, M1, M2, ...] and evaluates opOut:...M2(M1(M0(ctIn))
func (eval Evaluator) EvaluateSequential(ctIn *rlwe.Ciphertext, linearTransformations []LinearTransformation, opOut *rlwe.Ciphertext) (err error) {
// EvaluateLinearTranformationSequential takes as input a ciphertext ctIn and a list of linear transformations [M0, M1, M2, ...] and evaluates opOut:...M2(M1(M0(ctIn))
func EvaluateLinearTranformationSequential(evalLT EvaluatorForLinearTransformation, evalDiag EvaluatorForDiagonalMatrix, ctIn *rlwe.Ciphertext, linearTransformations []LinearTransformation, opOut *rlwe.Ciphertext) (err error) {
if err = eval.EvaluateMany(ctIn, linearTransformations[:1], []*rlwe.Ciphertext{opOut}); err != nil {
if err = EvaluateLinearTransformationsMany(evalLT, evalDiag, ctIn, linearTransformations[:1], []*rlwe.Ciphertext{opOut}); err != nil {
return
}
if err = eval.Rescale(opOut, opOut); err != nil {
if err = evalLT.Rescale(opOut, opOut); err != nil {
return
}
for i := 1; i < len(linearTransformations); i++ {
if err = eval.EvaluateMany(opOut, linearTransformations[i:i+1], []*rlwe.Ciphertext{opOut}); err != nil {
if err = EvaluateLinearTransformationsMany(evalLT, evalDiag, opOut, linearTransformations[i:i+1], []*rlwe.Ciphertext{opOut}); err != nil {
return
}
if err = eval.Rescale(opOut, opOut); err != nil {
if err = evalLT.Rescale(opOut, opOut); err != nil {
return
}
}
@@ -134,62 +137,49 @@ func (eval Evaluator) EvaluateSequential(ctIn *rlwe.Ciphertext, linearTransforma
return
}
// MultiplyByDiagMatrix multiplies the Ciphertext ctIn by the plaintext matrix and returns the result on the Ciphertext
// opOut. Memory buffers for the decomposed ciphertext BuffDecompQP, BuffDecompQP must be provided, those are list of poly of ringQ and ringP
// MultiplyByDiagMatrix multiplies the Ciphertext "ctIn" by the plaintext matrix "matrix" and returns the result on the Ciphertext
// "opOut". Memory buffers for the decomposed ciphertext BuffDecompQP, BuffDecompQP must be provided, those are list of poly of ringQ and ringP
// respectively, each of size params.Beta().
// The naive approach is used (single hoisting and no baby-step giant-step), which is faster than MultiplyByDiagMatrixBSGS
// for matrix of only a few non-zero diagonals but uses more keys.
func (eval Evaluator) MultiplyByDiagMatrix(ctIn *rlwe.Ciphertext, matrix LinearTransformation, BuffDecompQP []ringqp.Poly, opOut *rlwe.Ciphertext) (err error) {
func MultiplyByDiagMatrix(eval EvaluatorForLinearTransformation, ctIn *rlwe.Ciphertext, matrix LinearTransformation, BuffDecompQP []ringqp.Poly, opOut *rlwe.Ciphertext) (err error) {
BuffQP := eval.GetBuffQP()
BuffCt := eval.GetBuffCt()
*opOut.MetaData = *ctIn.MetaData
opOut.Scale = opOut.Scale.Mul(matrix.Scale)
params := eval.GetRLWEParameters()
levelQ := utils.Min(opOut.Level(), utils.Min(ctIn.Level(), matrix.LevelQ))
levelP := matrix.LevelP
levelQ := utils.Min(opOut.Level(), utils.Min(ctIn.Level(), matrix.Level))
levelP := params.RingP().MaxLevel()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
buffQP0 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP0)
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
buffQP3 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP3)
buffQP4 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP4)
buffQP5 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP5)
buffCt := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(buffCt)
opOut.Resize(opOut.Degree(), levelQ)
QiOverF := params.QiOverflowMargin(levelQ)
PiOverF := params.PiOverflowMargin(levelP)
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: (*buffQP5).Q}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: (*buffQP5).P}
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: BuffQP[5].Q}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: BuffQP[5].P}
ct0TimesP := (*buffQP0).Q // ct0 * P mod Q
tmp0QP := *buffQP1
tmp1QP := *buffQP2
ct0TimesP := BuffQP[0].Q // ct0 * P mod Q
tmp0QP := BuffQP[1]
tmp1QP := BuffQP[2]
cQP := &rlwe.Element[ringqp.Poly]{}
cQP.Value = []ringqp.Poly{*buffQP3, *buffQP4}
cQP.Value = []ringqp.Poly{BuffQP[3], BuffQP[4]}
cQP.MetaData = &rlwe.MetaData{}
cQP.MetaData.IsNTT = true
buffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
buffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
BuffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
BuffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
ctInTmp0, ctInTmp1 := buffCt.Value[0], buffCt.Value[1]
ctInTmp0, ctInTmp1 := BuffCt.Value[0], BuffCt.Value[1]
ringQ.MulScalarBigint(ctInTmp0, ringP.ModulusAtLevel[levelP], ct0TimesP) // P*c0
@@ -215,16 +205,9 @@ func (eval Evaluator) MultiplyByDiagMatrix(ctIn *rlwe.Ciphertext, matrix LinearT
return fmt.Errorf("cannot MultiplyByDiagMatrix: Automorphism: CheckAndGetGaloisKey: %w", err)
}
if evk.LevelP() != levelP {
return fmt.Errorf("LinearTransformation.LevelP = %d != GaloiKey[%d].LevelP() = %d: ensure that the levelP of the linear transformation is the same as the levelP of the GaloisKeys", levelP, galEl, evk.LevelP())
}
index := eval.AutomorphismIndex(galEl)
if err = eval.GadgetProductHoistedLazy(levelQ, BuffDecompQP, &evk.GadgetCiphertext, cQP); err != nil {
return fmt.Errorf("eval.GadgetProductHoistedLazy: %w", err)
}
eval.GadgetProductHoistedLazy(levelQ, BuffDecompQP, &evk.GadgetCiphertext, cQP)
ringQ.Add(cQP.Value[0].Q, ct0TimesP, cQP.Value[0].Q)
ringQP.AutomorphismNTTWithIndex(cQP.Value[0], index, tmp0QP)
ringQP.AutomorphismNTTWithIndex(cQP.Value[1], index, tmp1QP)
@@ -274,39 +257,26 @@ func (eval Evaluator) MultiplyByDiagMatrix(ctIn *rlwe.Ciphertext, matrix LinearT
}
// MultiplyByDiagMatrixBSGS multiplies the Ciphertext "ctIn" by the plaintext matrix "matrix" and returns the result on the Ciphertext "opOut".
// ctInPreRotated can be obtained with [PreRotatedCiphertextForDiagonalMatrixMultiplication].
// ctInPreRotated can be obtained with GetPreRotatedCiphertextForDiagonalMatrixMultiplication.
// The BSGS approach is used (double hoisting with baby-step giant-step), which is faster than MultiplyByDiagMatrix
// for matrix with more than a few non-zero diagonals and uses significantly less keys.
func (eval Evaluator) MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix LinearTransformation, ctInPreRot map[int]*rlwe.Element[ringqp.Poly], opOut *rlwe.Ciphertext) (err error) {
func MultiplyByDiagMatrixBSGS(eval EvaluatorForLinearTransformation, ctIn *rlwe.Ciphertext, matrix LinearTransformation, ctInPreRot map[int]*rlwe.Element[ringqp.Poly], opOut *rlwe.Ciphertext) (err error) {
params := eval.GetRLWEParameters()
BuffQP := eval.GetBuffQP()
BuffCt := eval.GetBuffCt()
*opOut.MetaData = *ctIn.MetaData
opOut.Scale = opOut.Scale.Mul(matrix.Scale)
levelQ := utils.Min(opOut.Level(), utils.Min(ctIn.Level(), matrix.LevelQ))
levelP := matrix.LevelP
levelQ := utils.Min(opOut.Level(), utils.Min(ctIn.Level(), matrix.Level))
levelP := params.MaxLevelP()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
buffQP3 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP3)
buffQP4 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP4)
buffP1 := poolQP.PoolP.GetBuffPoly()
defer poolQP.PoolP.RecycleBuffPoly(buffP1)
buffP2 := poolQP.PoolP.GetBuffPoly()
defer poolQP.PoolP.RecycleBuffPoly(buffP2)
buffCt := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(buffCt)
opOut.Resize(opOut.Degree(), levelQ)
QiOverF := params.QiOverflowMargin(levelQ) >> 1
@@ -315,24 +285,24 @@ func (eval Evaluator) MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix Lin
// Computes the N2 rotations indexes of the non-zero rows of the diagonalized DFT matrix for the baby-step giant-step algorithm
index, _, _ := matrix.BSGSIndex()
buffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
buffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
BuffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
BuffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
ctInTmp0, ctInTmp1 := buffCt.Value[0], buffCt.Value[1]
ctInTmp0, ctInTmp1 := BuffCt.Value[0], BuffCt.Value[1]
// Accumulator inner loop
tmp0QP := (*buffQP1)
tmp1QP := (*buffQP2)
tmp0QP := BuffQP[1]
tmp1QP := BuffQP[2]
// Accumulator outer loop
cQP := &rlwe.Element[ringqp.Poly]{}
cQP.Value = []ringqp.Poly{*buffQP3, *buffQP4}
cQP.Value = []ringqp.Poly{BuffQP[3], BuffQP[4]}
cQP.MetaData = &rlwe.MetaData{}
cQP.IsNTT = true
// Result in QP
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: *buffP1}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: *buffP2}
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: BuffQP[5].Q}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: BuffQP[5].P}
ringQ.MulScalarBigint(ctInTmp0, ringP.ModulusAtLevel[levelP], ctInTmp0) // P*c0
ringQ.MulScalarBigint(ctInTmp1, ringP.ModulusAtLevel[levelP], ctInTmp1) // P*c1
@@ -404,20 +374,12 @@ func (eval Evaluator) MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix Lin
var evk *rlwe.GaloisKey
var err error
if evk, err = eval.CheckAndGetGaloisKey(galEl); err != nil {
return fmt.Errorf("eval.CheckAndGetGaloisKey(%d): CheckAndGetGaloisKey: %w", galEl, err)
}
if evk.LevelP() != levelP {
return fmt.Errorf("LinearTransformation.LevelP = %d != GaloiKey[%d].LevelP() = %d: ensure that the levelP of the linear transformation is the same as the levelP of the GaloisKeys", levelP, galEl, evk.LevelP())
return fmt.Errorf("cannot MultiplyByDiagMatrixBSGS: Automorphism: CheckAndGetGaloisKey: %w", err)
}
rotIndex := eval.AutomorphismIndex(galEl)
// EvaluationKey(P*phi(tmpRes_1)) = (d0, d1) in base QP
if err = eval.GadgetProductLazy(levelQ, tmp1QP.Q, &evk.GadgetCiphertext, cQP); err != nil {
return fmt.Errorf("eval.GadgetProductLazy: %w", err)
}
eval.GadgetProductLazy(levelQ, tmp1QP.Q, &evk.GadgetCiphertext, cQP) // EvaluationKey(P*phi(tmpRes_1)) = (d0, d1) in base QP
ringQP.Add(cQP.Value[0], tmp0QP, cQP.Value[0])
// Outer loop rotations
+14 -18
View File
@@ -1,18 +1,17 @@
// Package polynomial bundles generic parts of the homomorphic polynomial evaluation circuit.
package polynomial
package he
import (
"fmt"
"math/bits"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Polynomial is a struct for representing plaintext polynomials
// for their homomorphic evaluation in an encrypted point. The
// type wraps a [bignum.Polynomial] along with several evaluation-
// type wraps a bignum.Polynomial along with several evaluation-
// related parameters.
type Polynomial struct {
bignum.Polynomial
@@ -24,7 +23,7 @@ type Polynomial struct {
}
// NewPolynomial returns an instantiated Polynomial for the
// provided [bignum.Polynomial].
// provided bignum.Polynomial.
func NewPolynomial(poly bignum.Polynomial) Polynomial {
return Polynomial{
Polynomial: poly,
@@ -69,12 +68,11 @@ type PatersonStockmeyerPolynomial struct {
Value []Polynomial
}
// PatersonStockmeyerPolynomial returns the Paterson Stockmeyer polynomial decomposition of the target polynomial.
// GetPatersonStockmeyerPolynomial returns the Paterson Stockmeyer polynomial decomposition of the target polynomial.
// The decomposition is done with the power of two basis.
func (p Polynomial) PatersonStockmeyerPolynomial(params rlwe.ParameterProvider, inputLevel int, inputScale, outputScale rlwe.Scale, eval SimEvaluator) PatersonStockmeyerPolynomial {
func (p Polynomial) GetPatersonStockmeyerPolynomial(params rlwe.ParameterProvider, inputLevel int, inputScale, outputScale rlwe.Scale, eval SimEvaluator) PatersonStockmeyerPolynomial {
// ceil(log2(degree))
/* #nosec G115 -- degree cannot be negative */
logDegree := bits.Len64(uint64(p.Degree()))
// optimal ratio between degree(pi(X)) et degree(P(X))
@@ -110,10 +108,8 @@ func recursePS(params rlwe.ParameterProvider, logSplit, targetLevel int, p Polyn
if p.Degree() < (1 << logSplit) {
/* #nosec G115 -- MaxDeg cannot be negative */
if p.Lead && logSplit > 1 && p.MaxDeg > (1<<bits.Len64(uint64(p.MaxDeg)))-(1<<(logSplit-1)) {
/* #nosec G115 -- Degree cannot be negative */
logDegree := int(bits.Len64(uint64(p.Degree())))
logSplit := bignum.OptimalSplit(logDegree)
@@ -161,9 +157,9 @@ type PolynomialVector struct {
Mapping map[int][]int
}
// NewPolynomialVector instantiates a new [PolynomialVector] from a set of [bignum.Polynomial] and a mapping indicating
// NewPolynomialVector instantiates a new PolynomialVector from a set of bignum.Polynomial and a mapping indicating
// which polynomial has to be evaluated on which slot.
// For example, if we are given two polynomials P0(X) and P1(X) and the following mapping: map[int][]int{0:[0, 1, 2], 1:[3, 4, 5]},
// For example, if we are given two polynomials P0(X) and P1(X) and the folling mapping: map[int][]int{0:[0, 1, 2], 1:[3, 4, 5]},
// then the polynomial evaluation on a vector [a, b, c, d, e, f, g, h] will evaluate to [P0(a), P0(b), P0(c), P1(d), P1(e), P1(f), 0, 0]
func NewPolynomialVector(polys []bignum.Polynomial, mapping map[int][]int) (PolynomialVector, error) {
var maxDeg int
@@ -230,18 +226,18 @@ func (p PolynomialVector) Factorize(n int) (polyq, polyr PolynomialVector) {
// PatersonStockmeyerPolynomialVector is a struct implementing the
// Paterson Stockmeyer decomposition of a PolynomialVector.
// See [PatersonStockmeyerPolynomial] for additional information.
// See PatersonStockmeyerPolynomial for additional information.
type PatersonStockmeyerPolynomialVector struct {
Value []PatersonStockmeyerPolynomial
Mapping map[int][]int
}
// PatersonStockmeyerPolynomial returns the Paterson Stockmeyer polynomial decomposition of the target PolynomialVector.
// GetPatersonStockmeyerPolynomial returns the Paterson Stockmeyer polynomial decomposition of the target PolynomialVector.
// The decomposition is done with the power of two basis
func (p PolynomialVector) PatersonStockmeyerPolynomial(params rlwe.Parameters, inputLevel int, inputScale, outputScale rlwe.Scale, eval SimEvaluator) PatersonStockmeyerPolynomialVector {
func (p PolynomialVector) GetPatersonStockmeyerPolynomial(params rlwe.Parameters, inputLevel int, inputScale, outputScale rlwe.Scale, eval SimEvaluator) PatersonStockmeyerPolynomialVector {
Value := make([]PatersonStockmeyerPolynomial, len(p.Value))
for i := range Value {
Value[i] = p.Value[i].PatersonStockmeyerPolynomial(params, inputLevel, inputScale, outputScale, eval)
Value[i] = p.Value[i].GetPatersonStockmeyerPolynomial(params, inputLevel, inputScale, outputScale, eval)
}
return PatersonStockmeyerPolynomialVector{
@@ -1,32 +1,37 @@
package polynomial
package he
import (
"fmt"
"math/bits"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// EvaluatorForPolynomial defines a set of common and scheme agnostic method that are necessary to instantiate a PolynomialVectorEvaluator.
type EvaluatorForPolynomial interface {
Evaluator
EvaluatePatersonStockmeyerPolynomialVector(poly PatersonStockmeyerPolynomialVector, pb PowerBasis) (res *rlwe.Ciphertext, err error)
}
// CoefficientGetter defines an interface to get the coefficients of a Polynomial.
type CoefficientGetter[T uint64 | *bignum.Complex] interface {
type CoefficientGetter[T any] interface {
// GetVectorCoefficient should return a slice []T containing the k-th coefficient
// of each polynomial of PolynomialVector indexed by its Mapping.
// See PolynomialVector for additional information about the Mapping.
GetVectorCoefficient(pol PolynomialVector, k int) (values []T)
// GetSingleCoefficient should return the k-th coefficient of Polynomial as the type T.
GetSingleCoefficient(pol Polynomial, k int) (value T)
// ShallowCopy should return a thread-safe copy of the original CoefficientGetter.
ShallowCopy() CoefficientGetter[T]
}
type Evaluator[T uint64 | *bignum.Complex] struct {
schemes.Evaluator
CoefficientGetter[T]
}
// Evaluate is a generic and scheme agnostic method to evaluate polynomials on rlwe.Ciphertexts.
func (eval Evaluator[T]) Evaluate(input interface{}, p interface{}, targetScale rlwe.Scale, levelsConsumedPerRescaling int, SimEval SimEvaluator) (opOut *rlwe.Ciphertext, err error) {
// EvaluatePolynomial is a generic and scheme agnostic method to evaluate polynomials on rlwe.Ciphertexts.
func EvaluatePolynomial(eval EvaluatorForPolynomial, input interface{}, p interface{}, targetScale rlwe.Scale, levelsConsumedPerRescaling int, SimEval SimEvaluator) (opOut *rlwe.Ciphertext, err error) {
var polyVec PolynomialVector
switch p := p.(type) {
@@ -37,7 +42,7 @@ func (eval Evaluator[T]) Evaluate(input interface{}, p interface{}, targetScale
case PolynomialVector:
polyVec = p
default:
return nil, fmt.Errorf("cannot Polynomial: invalid polynomial type, must be either bignum.Polynomial, polynomial.Polynomial or polynomial.PolynomialVector, but is %T", p)
return nil, fmt.Errorf("cannot Polynomial: invalid polynomial type, must be either bignum.Polynomial, he.Polynomial or he.PolynomialVector, but is %T", p)
}
var powerbasis PowerBasis
@@ -57,7 +62,6 @@ func (eval Evaluator[T]) Evaluate(input interface{}, p interface{}, targetScale
return nil, fmt.Errorf("%d levels < %d log(d) -> cannot evaluate poly", level, depth)
}
/* #nosec G115 -- Degree cannot be negative */
logDegree := bits.Len64(uint64(polyVec.Value[0].Degree()))
logSplit := bignum.OptimalSplit(logDegree)
@@ -81,7 +85,7 @@ func (eval Evaluator[T]) Evaluate(input interface{}, p interface{}, targetScale
}
}
PS := polyVec.PatersonStockmeyerPolynomial(*eval.GetRLWEParameters(), powerbasis.Value[1].Level(), powerbasis.Value[1].Scale, targetScale, SimEval)
PS := polyVec.GetPatersonStockmeyerPolynomial(*eval.GetRLWEParameters(), powerbasis.Value[1].Level(), powerbasis.Value[1].Scale, targetScale, SimEval)
if opOut, err = eval.EvaluatePatersonStockmeyerPolynomialVector(PS, powerbasis); err != nil {
return nil, err
@@ -98,7 +102,7 @@ type BabyStep struct {
}
// EvaluatePatersonStockmeyerPolynomialVector evaluates a pre-decomposed PatersonStockmeyerPolynomialVector on a pre-computed power basis [1, X^{1}, X^{2}, ..., X^{2^{n}}, X^{2^{n+1}}, ..., X^{2^{m}}]
func (eval Evaluator[T]) EvaluatePatersonStockmeyerPolynomialVector(poly PatersonStockmeyerPolynomialVector, pb PowerBasis) (res *rlwe.Ciphertext, err error) {
func EvaluatePatersonStockmeyerPolynomialVector[T any](eval Evaluator, poly PatersonStockmeyerPolynomialVector, cg CoefficientGetter[T], pb PowerBasis) (res *rlwe.Ciphertext, err error) {
split := len(poly.Value[0].Value)
@@ -108,7 +112,7 @@ func (eval Evaluator[T]) EvaluatePatersonStockmeyerPolynomialVector(poly Paterso
for i := range babySteps {
// eval & cg are not thread-safe
if babySteps[split-i-1], err = eval.EvaluateBabyStep(i, poly, pb); err != nil {
if babySteps[split-i-1], err = EvaluateBabyStep(i, eval, poly, cg, pb); err != nil {
return nil, fmt.Errorf("cannot EvaluateBabyStep: %w", err)
}
}
@@ -130,7 +134,7 @@ func (eval Evaluator[T]) EvaluatePatersonStockmeyerPolynomialVector(poly Paterso
for i := 0; i < len(babySteps); i++ {
// eval is not thread-safe
if err = eval.EvaluateGiantStep(i, giantsteps, babySteps, pb); err != nil {
if err = EvaluateGianStep(i, giantsteps, babySteps, eval, pb); err != nil {
return nil, err
}
}
@@ -162,7 +166,7 @@ func (eval Evaluator[T]) EvaluatePatersonStockmeyerPolynomialVector(poly Paterso
// EvaluateBabyStep evaluates a baby-step of the PatersonStockmeyer polynomial evaluation algorithm, i.e. the inner-product between the precomputed
// powers [1, T, T^2, ..., T^{n-1}] and the coefficients [ci0, ci1, ci2, ..., ci{n-1}].
func (eval Evaluator[T]) EvaluateBabyStep(i int, poly PatersonStockmeyerPolynomialVector, pb PowerBasis) (ct *BabyStep, err error) {
func EvaluateBabyStep[T any](i int, eval Evaluator, poly PatersonStockmeyerPolynomialVector, cg CoefficientGetter[T], pb PowerBasis) (ct *BabyStep, err error) {
nbPoly := len(poly.Value)
@@ -181,16 +185,16 @@ func (eval Evaluator[T]) EvaluateBabyStep(i int, poly PatersonStockmeyerPolynomi
ct = new(BabyStep)
ct.Degree = poly.Value[0].Value[i].Degree()
if ct.Value, err = eval.EvaluatePolynomialVectorFromPowerBasis(level, polyVec, pb, scale); err != nil {
if ct.Value, err = EvaluatePolynomialVectorFromPowerBasis(eval, level, polyVec, cg, pb, scale); err != nil {
return ct, fmt.Errorf("cannot EvaluatePolynomialVectorFromPowerBasis: polynomial[%d]: %w", i, err)
}
return ct, nil
}
// EvaluateGiantStep evaluates a giant-step of the PatersonStockmeyer polynomial evaluation algorithm, which consists
// EvaluateGianStep evaluates a giant-step of the PatersonStockmeyer polynomial evaluation algorithm, which consists
// in combining the baby-steps <[1, T, T^2, ..., T^{n-1}], [ci0, ci1, ci2, ..., ci{n-1}]> together with powers T^{2^k}.
func (eval Evaluator[T]) EvaluateGiantStep(i int, giantSteps []int, babySteps []*BabyStep, pb PowerBasis) (err error) {
func EvaluateGianStep(i int, giantSteps []int, babySteps []*BabyStep, eval Evaluator, pb PowerBasis) (err error) {
// If we reach the end of the list it means we weren't able to combine
// the last two sub-polynomials which necessarily implies that that the
@@ -206,10 +210,9 @@ func (eval Evaluator[T]) EvaluateGiantStep(i int, giantSteps []int, babySteps []
even, odd := babySteps[i], babySteps[i+1]
/* #nosec G115 -- Degree cannot be negative */
deg := 1 << bits.Len64(uint64(babySteps[i].Degree))
if err = eval.EvaluateMonomial(even.Value, odd.Value, pb.Value[deg]); err != nil {
if err = EvaluateMonomial(even.Value, odd.Value, pb.Value[deg], eval); err != nil {
return
}
@@ -223,7 +226,7 @@ func (eval Evaluator[T]) EvaluateGiantStep(i int, giantSteps []int, babySteps []
}
// EvaluateMonomial evaluates a monomial of the form a + b * X^{pow} and writes the results in b.
func (eval Evaluator[T]) EvaluateMonomial(a, b, xpow *rlwe.Ciphertext) (err error) {
func EvaluateMonomial(a, b, xpow *rlwe.Ciphertext, eval Evaluator) (err error) {
if b.Degree() == 2 {
if err = eval.Relinearize(b, b); err != nil {
@@ -250,8 +253,8 @@ func (eval Evaluator[T]) EvaluateMonomial(a, b, xpow *rlwe.Ciphertext) (err erro
return
}
// EvaluatePolynomialVectorFromPowerBasis evaluates P(ct) = sum c_i * ct^{i}.
func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int, pol PolynomialVector, pb PowerBasis, targetScale rlwe.Scale) (res *rlwe.Ciphertext, err error) {
// EvaluatePolynomialVectorFromPowerBasis a method that complies to the interface he.PolynomialVectorEvaluator. This method evaluates P(ct) = sum c_i * ct^{i}.
func EvaluatePolynomialVectorFromPowerBasis[T any](eval Evaluator, targetLevel int, pol PolynomialVector, cg CoefficientGetter[T], pb PowerBasis, targetScale rlwe.Scale) (res *rlwe.Ciphertext, err error) {
// Map[int] of the powers [X^{0}, X^{1}, X^{2}, ...]
X := pb.Value
@@ -290,7 +293,7 @@ func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int,
if even {
if err = eval.Add(res, eval.GetVectorCoefficient(pol, 0), res); err != nil {
if err = eval.Add(res, cg.GetVectorCoefficient(pol, 0), res); err != nil {
return nil, err
}
}
@@ -304,7 +307,7 @@ func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int,
res.Scale = targetScale
if even {
if err = eval.Add(res, eval.GetVectorCoefficient(pol, 0), res); err != nil {
if err = eval.Add(res, cg.GetVectorCoefficient(pol, 0), res); err != nil {
return nil, err
}
}
@@ -312,7 +315,7 @@ func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int,
// Loops starting from the highest degree coefficient
for key := pol.Value[0].Degree(); key > 0; key-- {
if !(even || odd) || (key&1 == 0 && even) || (key&1 == 1 && odd) {
if err = eval.MulThenAdd(X[key], eval.GetVectorCoefficient(pol, key), res); err != nil {
if err = eval.MulThenAdd(X[key], cg.GetVectorCoefficient(pol, key), res); err != nil {
return
}
}
@@ -327,7 +330,7 @@ func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int,
res.Scale = targetScale
if even {
if err = eval.Add(res, eval.GetSingleCoefficient(pol.Value[0], 0), res); err != nil {
if err = eval.Add(res, cg.GetSingleCoefficient(pol.Value[0], 0), res); err != nil {
return
}
}
@@ -340,7 +343,7 @@ func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int,
res.Scale = targetScale
if even {
if err = eval.Add(res, eval.GetSingleCoefficient(pol.Value[0], 0), res); err != nil {
if err = eval.Add(res, cg.GetSingleCoefficient(pol.Value[0], 0), res); err != nil {
return
}
}
@@ -348,7 +351,7 @@ func (eval Evaluator[T]) EvaluatePolynomialVectorFromPowerBasis(targetLevel int,
for key := pol.Value[0].Degree(); key > 0; key-- {
if key != 0 && (!(even || odd) || (key&1 == 0 && even) || (key&1 == 1 && odd)) {
// MulScalarAndAdd automatically scales c to match the scale of res.
if err = eval.MulThenAdd(X[key], eval.GetSingleCoefficient(pol.Value[0], key), res); err != nil {
if err = eval.MulThenAdd(X[key], cg.GetSingleCoefficient(pol.Value[0], key), res); err != nil {
return
}
}
@@ -1,6 +1,8 @@
package polynomial
package he
import "github.com/tuneinsight/lattigo/v6/core/rlwe"
import (
"github.com/luxdefi/lattice/v5/core/rlwe"
)
// SimOperand is a dummy operand that
// only stores its level and scale.
+25 -32
View File
@@ -1,4 +1,4 @@
package polynomial
package he
import (
"bufio"
@@ -6,11 +6,10 @@ import (
"io"
"math/bits"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/luxdefi/lattice/v5/utils/structs"
)
// PowerBasis is a struct storing powers of a ciphertext.
@@ -19,9 +18,9 @@ type PowerBasis struct {
Value structs.Map[int, rlwe.Ciphertext]
}
// NewPowerBasis creates a new [PowerBasis]. It takes as input a ciphertext
// NewPowerBasis creates a new PowerBasis. It takes as input a ciphertext
// and a basis type. The struct treats the input ciphertext as a monomial X and
// can be used to generates power of this monomial X^{n} in the given [BasisType].
// can be used to generates power of this monomial X^{n} in the given BasisType.
func NewPowerBasis(ct *rlwe.Ciphertext, basis bignum.Basis) (p PowerBasis) {
return PowerBasis{
Value: map[int]*rlwe.Ciphertext{1: ct.CopyNew()},
@@ -33,16 +32,11 @@ func NewPowerBasis(ct *rlwe.Ciphertext, basis bignum.Basis) (p PowerBasis) {
// with a and/or b odd if possible.
func SplitDegree(n int) (a, b int) {
if n <= 0 {
panic(fmt.Errorf("invalid n: n=%d should be greater than zero", n))
}
if n&(n-1) == 0 {
a, b = n/2, n/2 //Necessary for optimal depth
} else {
// [Lee et al. 2020] : High-Precision and Low-Complexity Approximate Homomorphic Encryption by Error Variance Minimization
// Maximize the number of odd terms of Chebyshev basis
/* #nosec G115 -- previous check ensures n is greater than 0 */
k := bits.Len64(uint64(n-1)) - 1
a = (1 << k) - 1
b = n + 1 - (1 << k)
@@ -54,7 +48,7 @@ func SplitDegree(n int) (a, b int) {
// GenPower recursively computes X^{n}.
// If lazy = true, the final X^{n} will not be relinearized.
// Previous non-relinearized X^{n} that are required to compute the target X^{n} are automatically relinearized.
func (p *PowerBasis) GenPower(n int, lazy bool, eval schemes.Evaluator) (err error) {
func (p *PowerBasis) GenPower(n int, lazy bool, eval Evaluator) (err error) {
if eval == nil {
return fmt.Errorf("cannot GenPower: EvaluatorInterface is nil")
@@ -77,7 +71,7 @@ func (p *PowerBasis) GenPower(n int, lazy bool, eval schemes.Evaluator) (err err
return nil
}
func (p *PowerBasis) genPower(n int, lazy, rescale bool, eval schemes.Evaluator) (rescaltOut bool, err error) {
func (p *PowerBasis) genPower(n int, lazy, rescale bool, eval Evaluator) (rescaltOut bool, err error) {
if p.Value[n] == nil {
@@ -186,17 +180,17 @@ func (p PowerBasis) BinarySize() (size int) {
return 1 + p.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// [io.Writer] in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (p PowerBasis) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
@@ -204,7 +198,6 @@ func (p PowerBasis) WriteTo(w io.Writer) (n int64, err error) {
var inc int64
/* #nosec G115 -- Basis cannot be negative if receiver is valid */
if inc, err = buffer.WriteUint8(w, uint8(p.Basis)); err != nil {
return n + inc, err
}
@@ -220,17 +213,17 @@ func (p PowerBasis) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (p *PowerBasis) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
@@ -267,7 +260,7 @@ func (p PowerBasis) MarshalBinary() (data []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [PowerBasis.MarshalBinary] or [PowerBasis.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (p *PowerBasis) UnmarshalBinary(data []byte) (err error) {
_, err = p.ReadFrom(buffer.NewBuffer(data))
return
@@ -1,12 +1,12 @@
package polynomial
package he
import (
"testing"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func TestPowerBasis(t *testing.T) {
+56
View File
@@ -0,0 +1,56 @@
func (n *Network) validateBlock(block *bfv.Ciphertext, sampleSize int) bool {
// 1. Sample a subset of nodes
sampleNodes := n.sampleNodes(sampleSize)
// 2. Ask each node in the sample to vote on the block
encryptedVotes := make(map[*Node]*bfv.Ciphertext)
for _, node := range sampleNodes {
encryptedVotes[node] = node.voteOnBlock(block)
}
// 3. Aggregate the encrypted votes
aggregatedVote := n.aggregateVotes(encryptedVotes)
// 4. Check if the aggregated vote exceeds the threshold
isValid := n.checkThreshold(aggregatedVote)
// 5. Return the decision
return isValid
}
func (n *Network) sampleNodes(sampleSize int) []*Node {
// Randomly select a subset of nodes from the network.
return n.randomNodes(sampleSize)
}
func (n *Network) aggregateVotes(encryptedVotes map[*Node]*bfv.Ciphertext) *bfv.Ciphertext {
// Use the homomorphic properties of BFV to aggregate the votes.
aggregatedVote := new(bfv.Ciphertext)
for _, vote := range encryptedVotes {
aggregatedVote.Add(aggregatedVote, vote)
}
return aggregatedVote
}
func (n *Network) checkThreshold(aggregatedVote *bfv.Ciphertext) bool {
// Use the homomorphic properties of BFV to check if the aggregated vote exceeds the threshold.
// Compare the encrypted aggregated vote to an encrypted representation of the threshold.
return n.isAboveThreshold(aggregatedVote)
}
func main() {
// Initialize network
// ...
// For each block:
for _, block := range blocks {
// Validate block using Avalanche consensus protocol
if network.validateBlock(block, sampleSize) {
// If the block is valid, add it to the blockchain
network.addBlock(block)
}
}
}
I have removed the block.hash%2==0 condition and replaced it with a random vote. I have also removed the mention of CKKS.
+4 -4
View File
@@ -1,9 +1,9 @@
// Package blindrot implements blind rotations evaluation for RLWE schemes.
package blindrot
// Package hebin implements blind rotations evaluation for RLWE schemes.
package hebin
import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
)
// InitTestPolynomial takes a function g, and creates a test polynomial polynomial for the function in the interval [a, b].
@@ -1,12 +1,13 @@
package blindrot
package hebin
import (
"testing"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
)
func BenchmarkHEBin(b *testing.B) {
+4 -4
View File
@@ -1,4 +1,4 @@
package blindrot
package hebin
import (
"fmt"
@@ -7,9 +7,9 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
func testString(params rlwe.Parameters, opname string) string {
+27 -15
View File
@@ -1,13 +1,13 @@
package blindrot
package hebin
import (
"fmt"
"math/big"
"github.com/tuneinsight/lattigo/v6/core/rgsw"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rgsw"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// Evaluator is a struct that stores the necessary
@@ -25,7 +25,7 @@ type Evaluator struct {
galoisGenDiscreteLog map[uint64]int
}
// NewEvaluator instantiates a new [Evaluator].
// NewEvaluator instantiates a new Evaluator.
func NewEvaluator(paramsBR, paramsLWE rlwe.ParameterProvider) (eval *Evaluator) {
eval = new(Evaluator)
eval.Evaluator = rgsw.NewEvaluator(paramsBR, nil)
@@ -43,6 +43,27 @@ func NewEvaluator(paramsBR, paramsLWE rlwe.ParameterProvider) (eval *Evaluator)
return
}
// EvaluateAndRepack extracts on the fly LWE samples, evaluates the provided blind rotations on the LWE and repacks everything into a single rlwe.Ciphertext.
// testPolyWithSlotIndex : a map with [slot_index] -> blind rotation
// repackIndex : a map with [slot_index_have] -> slot_index_want
func (eval *Evaluator) EvaluateAndRepack(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[int]*ring.Poly, repackIndex map[int]int, key BlindRotationEvaluationKeySet, repackKey rlwe.EvaluationKeySet) (res *rlwe.Ciphertext, err error) {
cts, err := eval.Evaluate(ct, testPolyWithSlotIndex, key)
if err != nil {
return nil, err
}
ciphertexts := make(map[int]*rlwe.Ciphertext)
for i := range cts {
ciphertexts[repackIndex[i]] = cts[i]
}
eval.Evaluator = eval.Evaluator.WithKey(repackKey)
return eval.Pack(ciphertexts, eval.paramsBR.LogN(), true)
}
// Evaluate extracts on the fly LWE samples and evaluates the provided blind rotation on the LWE.
// testPolyWithSlotIndex : a map with [slot_index] -> blind rotation
// Returns a map[slot_index] -> BlindRotate(ct[slot_index])
@@ -80,7 +101,6 @@ func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[i
tmp1 := acc.Value[1].Coeffs[0]
tmp0[0] = tmp1[0]
NLWE := ringQLWE.N()
/* #nosec G115 -- N cannot be negative */
mask := uint64(ringQBR.N()<<1) - 1
for j := 1; j < NLWE; j++ {
tmp0[j] = -tmp1[ringQLWE.N()-j] & mask
@@ -104,7 +124,6 @@ func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[i
// Line 2 of Algorithm 7 of https://eprint.iacr.org/2022/198
// Acc = (f(X^{-g}) * X^{-g * b}, 0)
/* #nosec G115 -- b is ensured to be small enough */
Xb := ringQBR.NewMonomialXi(int(b))
ringQBR.NTT(Xb, Xb)
ringQBR.MForm(Xb, Xb)
@@ -231,13 +250,8 @@ func (eval *Evaluator) evaluateFromDiscreteLogSets(GaloisElement func(k int) (ga
// getGaloisElementInverseMap generates a map [(+/-) g^{k} mod 2N] = +/- k
func getGaloisElementInverseMap(GaloisGen uint64, N int) (GaloisGenDiscreteLog map[uint64]int) {
if N <= 0 {
panic(fmt.Errorf("invalid N: N=%d should be greater than zero", N))
}
twoN := N << 1
NHalf := N >> 1
/* #nosec G115 -- previous check ensures twoN is greater than zero */
mask := uint64(twoN - 1)
GaloisGenDiscreteLog = map[uint64]int{}
@@ -245,7 +259,6 @@ func getGaloisElementInverseMap(GaloisGen uint64, N int) (GaloisGenDiscreteLog m
var pow uint64 = 1
for i := 0; i < NHalf; i++ {
GaloisGenDiscreteLog[pow] = i
/* #nosec G115 -- twoN cannot be negative */
GaloisGenDiscreteLog[uint64(twoN)-pow] = -i
pow *= GaloisGen
pow &= mask
@@ -290,7 +303,6 @@ func (eval *Evaluator) modSwitchRLWETo2NLvl(level int, polQ, pol2N ring.Poly, ma
QBig := ringQ.ModulusAtLevel[level]
/* #nosec G115 -- N cannot be negative */
twoN := uint64(eval.paramsBR.N() << 1)
twoNBig := bignum.NewInt(twoN)
tmp := pol2N.Coeffs[0]
+8 -8
View File
@@ -1,12 +1,12 @@
package blindrot
package hebin
import (
"math/big"
"github.com/tuneinsight/lattigo/v6/core/rgsw"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rgsw"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
const (
@@ -16,7 +16,7 @@ const (
// BlindRotationEvaluationKeySet is a interface implementing methods
// to load the blind rotation keys (RGSW) and automorphism keys
// (via the [rlwe.EvaluationKeySet] interface).
// (via the rlwe.EvaluationKeySet interface).
// Implementation of this interface must be safe for concurrent use.
type BlindRotationEvaluationKeySet interface {
@@ -28,7 +28,7 @@ type BlindRotationEvaluationKeySet interface {
GetEvaluationKeySet() (evk rlwe.EvaluationKeySet, err error)
}
// MemBlindRotationEvaluationKeySet is a basic in-memory implementation of the [BlindRotationEvaluationKeySet] interface.
// MemBlindRotationEvaluationKeySet is a basic in-memory implementation of the BlindRotationEvaluationKeySet interface.
type MemBlindRotationEvaluationKeySet struct {
BlindRotationKeys []*rgsw.Ciphertext
AutomorphismKeys []*rlwe.GaloisKey
@@ -59,7 +59,7 @@ func GenEvaluationKeyNew(paramsRLWE rlwe.ParameterProvider, skRLWE *rlwe.SecretK
encryptor := rgsw.NewEncryptor(pRLWE, skRLWE)
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(pRLWE, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(pRLWE, evkParams)
skiRGSW := make([]*rgsw.Ciphertext, pLWE.N())
+3 -3
View File
@@ -1,10 +1,10 @@
package blindrot
package hebin
import (
"math/big"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// MulBySmallMonomialMod2N multiplies pol by x^n, with 0 <= n < N
+20 -20
View File
@@ -4,8 +4,8 @@ import (
"bufio"
"io"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils/buffer"
)
// Ciphertext is a generic type for RGSW ciphertext.
@@ -23,7 +23,7 @@ func (ct Ciphertext) LevelP() int {
return ct.Value[0].LevelP()
}
// NewCiphertext allocates a new RGSW [Ciphertext] in the NTT domain.
// NewCiphertext allocates a new RGSW ciphertext in the NTT domain.
func NewCiphertext(params rlwe.Parameters, levelQ, levelP, BaseTwoDecomposition int) (ct *Ciphertext) {
return &Ciphertext{
Value: [2]rlwe.GadgetCiphertext{
@@ -38,17 +38,17 @@ func (ct Ciphertext) BinarySize() int {
return ct.Value[0].BinarySize() + ct.Value[1].BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// [io.Writer] in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (ct Ciphertext) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
case buffer.Writer:
@@ -66,17 +66,17 @@ func (ct Ciphertext) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (ct *Ciphertext) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
@@ -102,7 +102,7 @@ func (ct Ciphertext) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [Ciphertext.MarshalBinary] or [Ciphertext.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (ct *Ciphertext) UnmarshalBinary(p []byte) (err error) {
_, err = ct.ReadFrom(buffer.NewBuffer(p))
return
@@ -111,7 +111,7 @@ func (ct *Ciphertext) UnmarshalBinary(p []byte) (err error) {
// Plaintext stores an RGSW plaintext value.
type Plaintext rlwe.GadgetPlaintext
// NewPlaintext creates a new RGSW plaintext from value, which can be either uint64, int64 or *[ring.Poly].
// NewPlaintext creates a new RGSW plaintext from value, which can be either uint64, int64 or *ring.Poly.
// Plaintext is returned in the NTT and Montgomery domain.
func NewPlaintext(params rlwe.Parameters, value interface{}, levelQ, levelP, BaseTwoDecomposition int) (*Plaintext, error) {
gct, err := rlwe.NewGadgetPlaintext(params, value, levelQ, levelP, BaseTwoDecomposition)
+27 -41
View File
@@ -1,26 +1,25 @@
package rgsw
import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring/ringqp"
)
// Encryptor is a type for encrypting RGSW ciphertexts. It implements the [rlwe.Encryptor]
// interface overriding the [rlwe.Encryptor.Encrypt] and [rlwe.Encryptor.EncryptZero] methods to accept [rgsw.Ciphertext]
// Encryptor is a type for encrypting RGSW ciphertexts. It implements the rlwe.Encryptor
// interface overriding the `Encrypt` and `EncryptZero` methods to accept rgsw.Ciphertext
// types in addition to ciphertexts types in the rlwe package.
type Encryptor struct {
*rlwe.Encryptor
pool *rlwe.BufferPool
buffQP ringqp.Poly
}
// NewEncryptor creates a new Encryptor type. Note that only secret-key encryption is
// supported at the moment.
func NewEncryptor(params rlwe.ParameterProvider, key rlwe.EncryptionKey) *Encryptor {
return &Encryptor{rlwe.NewEncryptor(params, key), rlwe.NewPool(params.GetRLWEParameters().RingQP())}
return &Encryptor{rlwe.NewEncryptor(params, key), params.GetRLWEParameters().RingQP().NewPoly()}
}
// Encrypt encrypts a plaintext pt into a ciphertext ct, which can be a [rgsw.Ciphertext]
// Encrypt encrypts a plaintext pt into a ciphertext ct, which can be a rgsw.Ciphertext
// or any of the `rlwe` cipheretxt types.
func (enc Encryptor) Encrypt(pt *rlwe.Plaintext, ct interface{}) (err error) {
@@ -41,29 +40,26 @@ func (enc Encryptor) Encrypt(pt *rlwe.Plaintext, ct interface{}) (err error) {
if pt != nil {
buffQP := enc.pool.GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffQP)
if !pt.IsNTT {
ringQ.NTT(pt.Value, buffQP.Q)
ringQ.NTT(pt.Value, enc.buffQP.Q)
if !pt.IsMontgomery {
ringQ.MForm(buffQP.Q, buffQP.Q)
ringQ.MForm(enc.buffQP.Q, enc.buffQP.Q)
}
} else {
if !pt.IsMontgomery {
ringQ.MForm(pt.Value, buffQP.Q)
ringQ.MForm(pt.Value, enc.buffQP.Q)
} else {
pt.Value.CopyLvl(levelQ, buffQP.Q)
pt.Value.CopyLvl(levelQ, enc.buffQP.Q)
}
}
if err := rlwe.AddPolyTimesGadgetVectorToGadgetCiphertext(
buffQP.Q,
enc.buffQP.Q,
[]rlwe.GadgetCiphertext{rgswCt.Value[0], rgswCt.Value[1]},
*params.RingQP(),
buffQP.Q); err != nil {
enc.buffQP.Q); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
@@ -72,8 +68,8 @@ func (enc Encryptor) Encrypt(pt *rlwe.Plaintext, ct interface{}) (err error) {
return nil
}
// EncryptZero generates an encryption of zero into a ciphertext ct, which can be a [rgsw.Ciphertext]
// or any of the `rlwe` ciphertext types.
// EncryptZero generates an encryption of zero into a ciphertext ct, which can be a rgsw.Ciphertext
// or any of the `rlwe` cipheretxt types.
func (enc Encryptor) EncryptZero(ct interface{}) (err error) {
var rgswCt *Ciphertext
@@ -91,31 +87,21 @@ func (enc Encryptor) EncryptZero(ct interface{}) (err error) {
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
// extract the RingQ polynomial in case not P moduli have been provided
if rgswCt.LevelP() == -1 {
q0 := make([]ring.Poly, len(rgswCt.Value[0].Value[i][j]))
q1 := make([]ring.Poly, len(rgswCt.Value[1].Value[i][j]))
for k := range q0 {
q0[k] = rgswCt.Value[0].Value[i][j][k].Q
q1[k] = rgswCt.Value[1].Value[i][j][k].Q
}
if err = enc.Encryptor.EncryptZero(&rlwe.Ciphertext{Element: rlwe.Element[ring.Poly]{MetaData: metadata, Value: q0}}); err != nil {
return
}
if err = enc.Encryptor.EncryptZero(&rlwe.Ciphertext{Element: rlwe.Element[ring.Poly]{MetaData: metadata, Value: q1}}); err != nil {
return
}
} else {
if err = enc.Encryptor.EncryptZero(rlwe.Element[ringqp.Poly]{MetaData: metadata, Value: []ringqp.Poly(rgswCt.Value[0].Value[i][j])}); err != nil {
return
}
if err = enc.Encryptor.EncryptZero(rlwe.Element[ringqp.Poly]{MetaData: metadata, Value: []ringqp.Poly(rgswCt.Value[1].Value[i][j])}); err != nil {
return
}
if err = enc.Encryptor.EncryptZero(rlwe.Element[ringqp.Poly]{MetaData: metadata, Value: []ringqp.Poly(rgswCt.Value[0].Value[i][j])}); err != nil {
return
}
if err = enc.Encryptor.EncryptZero(rlwe.Element[ringqp.Poly]{MetaData: metadata, Value: []ringqp.Poly(rgswCt.Value[1].Value[i][j])}); err != nil {
return
}
}
}
return nil
}
// ShallowCopy creates a shallow copy of this Encryptor in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Encryptors can be used concurrently.
func (enc Encryptor) ShallowCopy() *Encryptor {
return &Encryptor{Encryptor: enc.Encryptor.ShallowCopy(), buffQP: enc.GetRLWEParameters().RingQP().NewPoly()}
}
+30 -54
View File
@@ -1,32 +1,35 @@
package rgsw
import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
)
// Evaluator is a type for evaluating homomorphic operations involving RGSW ciphertexts.
// It currently supports the external product between a RLWE and a RGSW ciphertext (see
// [Evaluator.ExternalProduct]).
// Evaluator.ExternalProduct).
type Evaluator struct {
rlwe.Evaluator
pool *rlwe.BufferPool
}
// NewEvaluator creates a new [Evaluator] type supporting RGSW operations in addition
// to [rlwe.Evaluator] operations.
// NewEvaluator creates a new Evaluator type supporting RGSW operations in addition
// to rlwe.Evaluator operations.
func NewEvaluator(params rlwe.ParameterProvider, evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{
*rlwe.NewEvaluator(params, evk),
rlwe.NewPool(params.GetRLWEParameters().RingQP()),
}
return &Evaluator{*rlwe.NewEvaluator(params, evk)}
}
// WithKey creates a shallow copy of the receiver [Evaluator] for which the evaluation key is set to the provided [rlwe.EvaluationKeySet]
// ShallowCopy creates a shallow copy of this Evaluator in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Evaluators can be used concurrently.
func (eval Evaluator) ShallowCopy() *Evaluator {
return &Evaluator{*eval.Evaluator.ShallowCopy()}
}
// WithKey creates a shallow copy of the receiver Evaluator for which the new EvaluationKey is evaluationKey
// and where the temporary buffers are shared. The receiver and the returned Evaluators cannot be used concurrently.
func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{*eval.Evaluator.WithKey(evk), eval.pool}
return &Evaluator{*eval.Evaluator.WithKey(evk)}
}
// ExternalProduct computes RLWE x RGSW -> RLWE
@@ -39,17 +42,12 @@ func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
func (eval Evaluator) ExternalProduct(op0 *rlwe.Ciphertext, op1 *Ciphertext, opOut *rlwe.Ciphertext) {
levelQ, levelP := op1.LevelQ(), op1.LevelP()
poolQP := eval.pool.AtLevel(levelQ, levelP)
var c0QP, c1QP ringqp.Poly
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
if op0 == opOut {
c0QP, c1QP = *buffQP1, *buffQP2
c0QP, c1QP = eval.BuffQP[1], eval.BuffQP[2]
} else {
c0QP, c1QP = ringqp.Poly{Q: opOut.Value[0], P: (*buffQP1).P}, ringqp.Poly{Q: opOut.Value[1], P: (*buffQP2).P}
c0QP, c1QP = ringqp.Poly{Q: opOut.Value[0], P: eval.BuffQP[1].P}, ringqp.Poly{Q: opOut.Value[1], P: eval.BuffQP[2].P}
}
if levelP < 1 {
@@ -74,7 +72,7 @@ func (eval Evaluator) ExternalProduct(op0 *rlwe.Ciphertext, op1 *Ciphertext, opO
}
}
} else {
eval.externalProductInPlaceMultipleP(levelQ, levelP, op0, op1, (*buffQP1).Q, (*buffQP1).P, (*buffQP2).Q, (*buffQP2).P)
eval.externalProductInPlaceMultipleP(levelQ, levelP, op0, op1, eval.BuffQP[1].Q, eval.BuffQP[1].P, eval.BuffQP[2].Q, eval.BuffQP[2].P)
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c0QP.Q, c0QP.P, opOut.Value[0])
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c1QP.Q, c1QP.P, opOut.Value[1])
@@ -88,32 +86,23 @@ func (eval Evaluator) externalProduct32Bit(ct0 *rlwe.Ciphertext, rgsw *Ciphertex
// opOut = [<ct, rgsw[0]>, <ct, rgsw[1]>] = [ct[0] * rgsw[0][0] + ct[1] * rgsw[0][1], ct[0] * rgsw[1][0] + ct[1] * rgsw[1][1]]
params := eval.GetRLWEParameters()
ringQ := params.RingQ().AtLevel(0)
poolQ := eval.pool.AtLevel(0)
subRing := ringQ.SubRings[0]
pw2 := rgsw.Value[0].BaseTwoDecomposition
mask := uint64(((1 << pw2) - 1))
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
cw := (*buffQ1).Coeffs[0]
buffBitDecomp := eval.pool.GetBuffUintArray()
defer eval.pool.RecycleBuffUintArray(buffBitDecomp)
cwNTT := *buffBitDecomp
cw := eval.BuffQP[0].Q.Coeffs[0]
cwNTT := eval.BuffBitDecomp
acc0 := c0.Coeffs[0]
acc1 := c1.Coeffs[0]
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
// (a, b) + (c0 * rgsw[0][0], c0 * rgsw[0][1])
// (a, b) + (c1 * rgsw[1][0], c1 * rgsw[1][1])
for i, el := range rgsw.Value {
ringQ.INTT(ct0.Value[i], *buffQ)
ringQ.INTT(ct0.Value[i], eval.BuffInvNTT)
for j := range el.Value[0] {
// TODO: center values if mask = 0
ring.MaskVec(buffQ.Coeffs[0], j*pw2, mask, cw)
ring.MaskVec(eval.BuffInvNTT.Coeffs[0], j*pw2, mask, cw)
if j == 0 && i == 0 {
subRing.NTTLazy(cw, cwNTT)
subRing.MulCoeffsLazy(el.Value[0][j][0].Q.Coeffs[0], cwNTT, acc0)
@@ -137,7 +126,6 @@ func (eval Evaluator) externalProductInPlaceSinglePAndBitDecomp(ct0 *rlwe.Cipher
params := eval.GetRLWEParameters()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
@@ -151,21 +139,15 @@ func (eval Evaluator) externalProductInPlaceSinglePAndBitDecomp(ct0 *rlwe.Cipher
BaseRNSDecompositionVectorSize := rgsw.Value[0].BaseRNSDecompositionVectorSize()
BaseTwoDecompositionVectorSize := rgsw.Value[0].BaseTwoDecompositionVectorSize()
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
buffQ1 := poolQP.BufferPool.AtLevel(0).GetBuffPoly()
defer poolQP.BufferPool.AtLevel(0).RecycleBuffPoly(buffQ1)
buffBitDecomp := eval.pool.GetBuffUintArray()
defer eval.pool.RecycleBuffUintArray(buffBitDecomp)
// (a, b) + (c0 * rgsw[k][0], c0 * rgsw[k][1])
for k, el := range rgsw.Value {
ringQ.INTT(ct0.Value[k], *buffQ)
cw := (*buffQ1).Coeffs[0]
cwNTT := *buffBitDecomp
ringQ.INTT(ct0.Value[k], eval.BuffInvNTT)
cw := eval.BuffQP[0].Q.Coeffs[0]
cwNTT := eval.BuffBitDecomp
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
// TODO: center values if mask == 0
ring.MaskVec(buffQ.Coeffs[i], j*pw2, mask, cw)
ring.MaskVec(eval.BuffInvNTT.Coeffs[i], j*pw2, mask, cw)
if k == 0 && i == 0 && j == 0 {
for u, s := range ringQ.SubRings[:levelQ+1] {
@@ -208,13 +190,10 @@ func (eval Evaluator) externalProductInPlaceMultipleP(levelQ, levelP int, ct0 *r
params := eval.GetRLWEParameters()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
c2QP := (*buffQP1)
c2QP := eval.BuffQP[0]
c0QP := ringqp.Poly{Q: c0OutQ, P: c0OutP}
c1QP := ringqp.Poly{Q: c1OutQ, P: c1OutP}
@@ -226,17 +205,14 @@ func (eval Evaluator) externalProductInPlaceMultipleP(levelQ, levelP int, ct0 *r
var c2NTT, c2InvNTT ring.Poly
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
for k, el := range rgsw.Value {
if ct0.IsNTT {
c2NTT = ct0.Value[k]
c2InvNTT = *buffQ
c2InvNTT = eval.BuffInvNTT
ringQ.INTT(c2NTT, c2InvNTT)
} else {
c2NTT = *buffQ
c2NTT = eval.BuffInvNTT
c2InvNTT = ct0.Value[k]
ringQ.NTT(c2InvNTT, c2NTT)
}
+4 -4
View File
@@ -4,10 +4,10 @@ import (
"math/big"
"testing"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/stretchr/testify/require"
)
+2 -2
View File
@@ -1,8 +1,8 @@
package rgsw
import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
)
// NoiseRGSWCiphertext returns the log2 of the standard deviation of the noise of each component of the RGSW ciphertext.
+7 -19
View File
@@ -3,8 +3,8 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// Ciphertext is a generic type for RLWE ciphertexts.
@@ -12,17 +12,17 @@ type Ciphertext struct {
Element[ring.Poly]
}
// NewCiphertext returns a new [Ciphertext] with zero values and an associated
// NewCiphertext returns a new Ciphertext with zero values and an associated
// MetaData set to the Parameters default value.
func NewCiphertext(params ParameterProvider, degree int, level ...int) (ct *Ciphertext) {
op := *NewElement(params, degree, level...)
return &Ciphertext{op}
}
// NewCiphertextAtLevelFromPoly constructs a new [Ciphertext] at a specific level
// NewCiphertextAtLevelFromPoly constructs a new Ciphertext at a specific level
// where the message is set to the passed poly. No checks are performed on poly and
// the returned [Ciphertext] will share its backing array of coefficients.
// Returned [Ciphertext]'s MetaData is allocated but empty.
// the returned Ciphertext will share its backing array of coefficients.
// Returned Ciphertext's MetaData is allocated but empty .
func NewCiphertextAtLevelFromPoly(level int, poly []ring.Poly) (*Ciphertext, error) {
operand, err := NewElementAtLevelFromPoly(level, poly)
@@ -36,25 +36,13 @@ func NewCiphertextAtLevelFromPoly(level int, poly []ring.Poly) (*Ciphertext, err
return &Ciphertext{*operand}, nil
}
// NewCiphertextRandom generates a new uniformly distributed [Ciphertext] of degree, level.
// NewCiphertextRandom generates a new uniformly distributed Ciphertext of degree, level.
func NewCiphertextRandom(prng sampling.PRNG, params ParameterProvider, degree, level int) (ciphertext *Ciphertext) {
ciphertext = NewCiphertext(params, degree, level)
PopulateElementRandom(prng, params, ciphertext.El())
return
}
// Plaintext casts the target ciphertext into a plaintext type.
// This method is allocation free.
func (ct Ciphertext) Plaintext() *Plaintext {
return &Plaintext{
Element: Element[ring.Poly]{
Value: ct.Element.Value[:1],
MetaData: ct.MetaData,
},
Value: ct.Element.Value[0],
}
}
// CopyNew creates a new element as a copy of the target element.
func (ct Ciphertext) CopyNew() *Ciphertext {
return &Ciphertext{Element: *ct.Element.CopyNew()}
+30 -20
View File
@@ -3,19 +3,19 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
// Decryptor is a structure used to decrypt [Ciphertext]. It stores the secret-key.
// Decryptor is a structure used to decrypt Ciphertext. It stores the secret-key.
type Decryptor struct {
params Parameters
ringQ *ring.Ring
poolQ *ring.BufferPool
buff ring.Poly
sk *SecretKey
}
// NewDecryptor instantiates a new generic RLWE [Decryptor].
// NewDecryptor instantiates a new generic RLWE Decryptor.
func NewDecryptor(params ParameterProvider, sk *SecretKey) *Decryptor {
p := params.GetRLWEParameters()
@@ -27,27 +27,27 @@ func NewDecryptor(params ParameterProvider, sk *SecretKey) *Decryptor {
return &Decryptor{
params: *p,
ringQ: p.RingQ(),
buff: p.RingQ().NewPoly(),
sk: sk,
poolQ: ring.NewPool(p.ringQ),
}
}
// GetRLWEParameters returns the underlying [Parameters].
// GetRLWEParameters returns the underlying rlwe.Parameters.
func (d Decryptor) GetRLWEParameters() *Parameters {
return &d.params
}
// DecryptNew decrypts the [Ciphertext] and returns the result in a new [Plaintext].
// Output pt [MetaData] will match the input ct [MetaData].
// DecryptNew decrypts the Ciphertext and returns the result in a new Plaintext.
// Output pt MetaData will match the input ct MetaData.
func (d Decryptor) DecryptNew(ct *Ciphertext) (pt *Plaintext) {
pt = NewPlaintext(d.params, ct.Level())
d.Decrypt(ct, pt)
return
}
// Decrypt decrypts the [Ciphertext] and writes the result in pt.
// The level of the output [Plaintext] is min(ct.Level(), pt.Level())
// Output pt [MetaData] will match the input ct [MetaData].
// Decrypt decrypts the Ciphertext and writes the result in pt.
// The level of the output Plaintext is min(ct.Level(), pt.Level())
// Output pt MetaData will match the input ct MetaData.
func (d Decryptor) Decrypt(ct *Ciphertext, pt *Plaintext) {
level := utils.Min(ct.Level(), pt.Level())
@@ -69,11 +69,8 @@ func (d Decryptor) Decrypt(ct *Ciphertext, pt *Plaintext) {
ringQ.MulCoeffsMontgomery(pt.Value, d.sk.Value.Q, pt.Value)
if !ct.IsNTT {
poolQ := d.poolQ.AtLevel(level)
buff := poolQ.GetBuffPoly()
ringQ.NTTLazy(ct.Value[i-1], *buff)
ringQ.Add(pt.Value, *buff, pt.Value)
poolQ.RecycleBuffPoly(buff)
ringQ.NTTLazy(ct.Value[i-1], d.buff)
ringQ.Add(pt.Value, d.buff, pt.Value)
} else {
ringQ.Add(pt.Value, ct.Value[i-1], pt.Value)
}
@@ -92,13 +89,26 @@ func (d Decryptor) Decrypt(ct *Ciphertext, pt *Plaintext) {
}
}
// WithKey creates a shallow copy of [Decryptor] with a new decryption key, in which all the
// data-structures are shared with the receiver.
// The receiver and the returned [Decryptor] can be used concurrently.
// ShallowCopy creates a shallow copy of Decryptor in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Decryptor can be used concurrently.
func (d Decryptor) ShallowCopy() *Decryptor {
return &Decryptor{
params: d.params,
ringQ: d.ringQ,
buff: d.ringQ.NewPoly(),
sk: d.sk,
}
}
// WithKey creates a shallow copy of Decryptor with a new decryption key, in which all the
// read-only data-structures are shared with the receiver and the temporary buffers
// are reallocated. The receiver and the returned Decryptor can be used concurrently.
func (d Decryptor) WithKey(sk *SecretKey) *Decryptor {
return &Decryptor{
params: d.params,
ringQ: d.ringQ,
buff: d.ringQ.NewPoly(),
sk: sk,
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ package rlwe
import (
"math"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/luxdefi/lattice/v5/ring"
)
type Distribution struct {
+26 -48
View File
@@ -4,20 +4,17 @@ import (
"bufio"
"fmt"
"io"
"math/bits"
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/luxdefi/lattice/v5/utils/sampling"
"github.com/luxdefi/lattice/v5/utils/structs"
)
// ElementInterface is a common interface for [Ciphertext] and [Plaintext] types.
// ElementInterface is a common interface for Ciphertext and Plaintext types.
type ElementInterface[T ring.Poly | ringqp.Poly] interface {
N() int
LogN() int
El() *Element[T]
Degree() int
Level() int
@@ -74,10 +71,10 @@ func NewElementExtended(params ParameterProvider, degree, levelQ, levelP int) *E
}
}
// NewElementAtLevelFromPoly constructs a new [Element] at a specific level
// NewElementAtLevelFromPoly constructs a new Element at a specific level
// where the message is set to the passed poly. No checks are performed on poly and
// the returned [Element] will share its backing array of coefficients.
// Returned [Element]'s [MetaData] is nil.
// the returned Element will share its backing array of coefficients.
// Returned Element's MetaData is nil.
func NewElementAtLevelFromPoly(level int, poly []ring.Poly) (*Element[ring.Poly], error) {
Value := make([]ring.Poly, len(poly))
for i := range Value {
@@ -92,36 +89,17 @@ func NewElementAtLevelFromPoly(level int, poly []ring.Poly) (*Element[ring.Poly]
return &Element[ring.Poly]{Value: Value}, nil
}
// N returns the ring degree used by the target element.
func (op Element[T]) N() int {
switch el := any(op.Value[0]).(type) {
case ring.Poly:
return el.N()
case ringqp.Poly:
return el.Q.N()
default:
// Sanity check
panic("invalid Element[type]")
}
}
// LogN returns the log2 of the ring degree used by the target element.
func (op Element[T]) LogN() int {
/* #nosec G115 -- N is ensured to be greater than 0 */
return bits.Len64(uint64(op.N() - 1))
}
// Equal performs a deep equal.
func (op Element[T]) Equal(other *Element[T]) bool {
return cmp.Equal(op.MetaData, other.MetaData) && cmp.Equal(op.Value, other.Value)
}
// Degree returns the degree of the target element.
// Degree returns the degree of the target Element.
func (op Element[T]) Degree() int {
return len(op.Value) - 1
}
// Level returns the level of the target element.
// Level returns the level of the target Element.
func (op Element[T]) Level() int {
return op.LevelQ()
}
@@ -234,7 +212,7 @@ func GetSmallestLargest[T ring.Poly | ringqp.Poly](el0, el1 *Element[T]) (smalle
return el0, el1, true
}
// PopulateElementRandom creates a new [Element] with random coefficients.
// PopulateElementRandom creates a new rlwe.Element with random coefficients.
func PopulateElementRandom(prng sampling.PRNG, params ParameterProvider, ct *Element[ring.Poly]) {
sampler := ring.NewUniformSampler(prng, params.GetRLWEParameters().RingQ()).AtLevel(ct.Level())
for i := range ct.Value {
@@ -321,17 +299,17 @@ func (op Element[T]) BinarySize() (size int) {
return size + op.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// [io.Writer] in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (op Element[T]) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
@@ -370,17 +348,17 @@ func (op Element[T]) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (op *Element[T]) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
@@ -430,7 +408,7 @@ func (op Element[T]) MarshalBinary() (data []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [Element.MarshalBinary] or [Element.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (op *Element[T]) UnmarshalBinary(p []byte) (err error) {
_, err = op.ReadFrom(buffer.NewBuffer(p))
return
+91 -96
View File
@@ -4,19 +4,19 @@ import (
"fmt"
"reflect"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// EncryptionKey is an interface for encryption keys. Valid encryption
// keys are the [SecretKey] and [PublicKey] types.
// keys are the SecretKey and PublicKey types.
type EncryptionKey interface {
isEncryptionKey()
}
// NewEncryptor creates a new [Encryptor] from either a public key or a private key.
// NewEncryptor creates a new Encryptor from either a public key or a private key.
func NewEncryptor(params ParameterProvider, key EncryptionKey) *Encryptor {
p := *params.GetRLWEParameters()
@@ -46,6 +46,7 @@ func NewEncryptor(params ParameterProvider, key EncryptionKey) *Encryptor {
type Encryptor struct {
params Parameters
*encryptorBuffers
encKey EncryptionKey
prng sampling.PRNG
@@ -53,10 +54,9 @@ type Encryptor struct {
xsSampler ring.Sampler
basisextender *ring.BasisExtender
uniformSampler ringqp.UniformSampler
pool *BufferPool
}
// GetRLWEParameters returns the underlying [Parameters].
// GetRLWEParameters returns the underlying rlwe.Parameters.
func (enc Encryptor) GetRLWEParameters() *Parameters {
return &enc.params
}
@@ -65,7 +65,8 @@ func newEncryptor(params Parameters) *Encryptor {
prng, err := sampling.NewPRNG()
if err != nil {
panic(fmt.Errorf("newEncryptor: %w", err))
// Sanity check, this error should not happen.
panic(err)
}
var bc *ring.BasisExtender
@@ -88,45 +89,44 @@ func newEncryptor(params Parameters) *Encryptor {
}
return &Encryptor{
params: params,
prng: prng,
xeSampler: xeSampler,
xsSampler: xsSampler,
uniformSampler: ringqp.NewUniformSampler(prng, *params.RingQP()),
basisextender: bc,
pool: NewPool(params.RingQP()),
params: params,
prng: prng,
xeSampler: xeSampler,
xsSampler: xsSampler,
encryptorBuffers: newEncryptorBuffers(params),
uniformSampler: ringqp.NewUniformSampler(prng, *params.RingQP()),
basisextender: bc,
}
}
// newTestEncryptorWithKeyedPRNG creates a new [Encryptor] that uses the provided prng for randomness.
// CAUTION: THIS FUNCTION SHOULD BE USED FOR TESTING PURPOSES ONLY.
// WARNING: The resulting encryptor is not meant to be used concurrently.
func newTestEncryptorWithKeyedPRNG(params ParameterProvider, key EncryptionKey, prng *sampling.KeyedPRNG) *Encryptor {
p := *params.GetRLWEParameters()
type encryptorBuffers struct {
buffQ [2]ring.Poly
buffP [3]ring.Poly
buffQP ringqp.Poly
}
enc := NewEncryptor(params, key)
xeSampler, err := ring.NewSampler(prng, p.RingQ(), p.Xe(), false)
if err != nil {
panic(fmt.Errorf("NewEncryptorWithPRNG: cannot create xeSampler %w", err))
}
xsSampler, err := ring.NewSampler(prng, p.RingQ(), p.Xs(), false)
if err != nil {
panic(fmt.Errorf("NewEncryptorWithPRNG: cannot create xsSampler %w", err))
}
uniformSampler := ringqp.NewUniformSampler(prng, *p.RingQP())
enc.prng = prng
enc.xeSampler = xeSampler
enc.xsSampler = xsSampler
enc.uniformSampler = uniformSampler
func newEncryptorBuffers(params Parameters) *encryptorBuffers {
return enc
ringQ := params.RingQ()
ringP := params.RingP()
var buffP [3]ring.Poly
if params.PCount() != 0 {
buffP = [3]ring.Poly{ringP.NewPoly(), ringP.NewPoly(), ringP.NewPoly()}
}
return &encryptorBuffers{
buffQ: [2]ring.Poly{ringQ.NewPoly(), ringQ.NewPoly()},
buffP: buffP,
buffQP: params.RingQP().NewPoly(),
}
}
// Encrypt encrypts the input plaintext using the stored encryption key and writes the result on ct.
// The method currently accepts only *[Ciphertext] as ct.
// If a [Plaintext] is given, then the output [Ciphertext] [MetaData] will match the [Plaintext] [MetaData].
// The method currently accepts only *rlwe.Ciphertext as ct.
// If a Plaintext is given, then the output Ciphertext MetaData will match the Plaintext MetaData.
// The method returns an error if the ct has an unsupported type or if no encryption key is stored
// in the [Encryptor].
// in the Encryptor.
//
// The encryption procedure masks the plaintext by adding a fresh encryption of zero.
// The encryption procedure depends on the parameters: If the auxiliary modulus P is defined, the
@@ -152,10 +152,10 @@ func (enc Encryptor) Encrypt(pt *Plaintext, ct interface{}) (err error) {
}
// EncryptNew encrypts the input plaintext using the stored encryption key and returns a newly
// allocated [Ciphertext] containing the result.
// If a [Plaintext] is provided, then the output [Ciphertext] [MetaData] will match the [Plaintext] [MetaData].
// allocated Ciphertext containing the result.
// If a Plaintext is provided, then the output ciphertext MetaData will match the Plaintext MetaData.
// The method returns an error if the ct has an unsupported type or if no encryption key is stored
// in the [Encryptor].
// in the Encryptor.
//
// The encryption procedure masks the plaintext by adding a fresh encryption of zero.
// The encryption procedure depends on the parameters: If the auxiliary modulus P is defined, the
@@ -166,13 +166,13 @@ func (enc Encryptor) EncryptNew(pt *Plaintext) (ct *Ciphertext, err error) {
}
// EncryptZero generates an encryption of zero under the stored encryption key and writes the result on ct.
// The method accepts only *[Ciphertext] as input.
// The method accepts only *rlwe.Ciphertext as input.
// The method returns an error if the ct has an unsupported type or if no encryption key is stored
// in the [Encryptor].
// in the Encryptor.
//
// The encryption procedure depends on the parameters: If the auxiliary modulus P is defined, the
// encryption of zero is sampled in QP before being rescaled by P; otherwise, it is directly sampled in Q.
// The zero encryption is generated according to the given [Ciphertext] [MetaData].
// The zero encryption is generated according to the given Ciphertext MetaData.
func (enc Encryptor) EncryptZero(ct interface{}) (err error) {
switch key := enc.encKey.(type) {
case *SecretKey:
@@ -188,8 +188,8 @@ func (enc Encryptor) EncryptZero(ct interface{}) (err error) {
}
// EncryptZeroNew generates an encryption of zero under the stored encryption key and returns a newly
// allocated [Ciphertext] containing the result.
// The method returns an error if no encryption key is stored in the [Encryptor].
// allocated Ciphertext containing the result.
// The method returns an error if no encryption key is stored in the Encryptor.
// The encryption procedure depends on the parameters: If the auxiliary modulus P is defined, the
// encryption of zero is sampled in QP before being rescaled by P; otherwise, it is directly sampled in Q.
func (enc Encryptor) EncryptZeroNew(level int) (ct *Ciphertext) {
@@ -215,11 +215,9 @@ func (enc Encryptor) encryptZeroPk(pk *PublicKey, ct interface{}) (err error) {
levelQ = ct.Level()
levelP = 0
buffQP := enc.pool.GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffQP)
ct0QP = ringqp.Poly{Q: ct.Value[0], P: buffQP.Q}
ct1QP = ringqp.Poly{Q: ct.Value[1], P: buffQP.P}
ct0QP = ringqp.Poly{Q: ct.Value[0], P: enc.buffP[0]}
ct1QP = ringqp.Poly{Q: ct.Value[1], P: enc.buffP[1]}
case Element[ringqp.Poly]:
levelQ = ct.LevelQ()
@@ -233,10 +231,7 @@ func (enc Encryptor) encryptZeroPk(pk *PublicKey, ct interface{}) (err error) {
ringQP := enc.params.RingQP().AtLevel(levelQ, levelP)
buffU := enc.pool.GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffU)
u := *buffU
u := ringqp.Poly{Q: enc.buffQ[0], P: enc.buffP[2]}
// We sample a RLWE instance (encryption of zero) over the extended ring (ciphertext ring + special prime)
enc.xsSampler.AtLevel(levelQ).Read(u.Q)
@@ -304,24 +299,23 @@ func (enc Encryptor) encryptZeroPkNoP(pk *PublicKey, ct Element[ring.Poly]) (err
ringQ := enc.params.RingQ().AtLevel(levelQ)
buffQ0 := enc.pool.GetBuffPoly()
defer enc.pool.RecycleBuffPoly(buffQ0)
buffQ0 := enc.buffQ[0]
enc.xsSampler.AtLevel(levelQ).Read(*buffQ0)
ringQ.NTT(*buffQ0, *buffQ0)
enc.xsSampler.AtLevel(levelQ).Read(buffQ0)
ringQ.NTT(buffQ0, buffQ0)
c0, c1 := ct.Value[0], ct.Value[1]
// ct0 = NTT(u*pk0)
ringQ.MulCoeffsMontgomery(*buffQ0, pk.Value[0].Q, c0)
ringQ.MulCoeffsMontgomery(buffQ0, pk.Value[0].Q, c0)
// ct1 = NTT(u*pk1)
ringQ.MulCoeffsMontgomery(*buffQ0, pk.Value[1].Q, c1)
ringQ.MulCoeffsMontgomery(buffQ0, pk.Value[1].Q, c1)
// c0
if ct.IsNTT {
enc.xeSampler.AtLevel(levelQ).Read(*buffQ0)
ringQ.NTT(*buffQ0, *buffQ0)
ringQ.Add(c0, *buffQ0, c0)
enc.xeSampler.AtLevel(levelQ).Read(buffQ0)
ringQ.NTT(buffQ0, buffQ0)
ringQ.Add(c0, buffQ0, c0)
} else {
ringQ.INTT(c0, c0)
enc.xeSampler.AtLevel(levelQ).ReadAndAdd(c0)
@@ -329,9 +323,9 @@ func (enc Encryptor) encryptZeroPkNoP(pk *PublicKey, ct Element[ring.Poly]) (err
// c1
if ct.IsNTT {
enc.xeSampler.AtLevel(levelQ).Read(*buffQ0)
ringQ.NTT(*buffQ0, *buffQ0)
ringQ.Add(c1, *buffQ0, c1)
enc.xeSampler.AtLevel(levelQ).Read(buffQ0)
ringQ.NTT(buffQ0, buffQ0)
ringQ.Add(c1, buffQ0, c1)
} else {
ringQ.INTT(c1, c1)
@@ -345,7 +339,6 @@ func (enc Encryptor) encryptZeroPkNoP(pk *PublicKey, ct Element[ring.Poly]) (err
// The method accepts only *rlwe.Ciphertext or *rgsw.Ciphertext as input and will return an error otherwise.
// The zero encryption is generated according to the given Ciphertext MetaData.
func (enc Encryptor) encryptZeroSk(sk *SecretKey, ct interface{}) (err error) {
switch ct := ct.(type) {
case *Ciphertext:
@@ -353,9 +346,7 @@ func (enc Encryptor) encryptZeroSk(sk *SecretKey, ct interface{}) (err error) {
if ct.Degree() == 1 {
c1 = ct.Value[1]
} else {
buffPolyQ := enc.pool.BufferPool.AtLevel(ct.Level()).GetBuffPoly()
defer enc.pool.RecycleBuffPoly(buffPolyQ)
c1 = *buffPolyQ
c1 = enc.buffQ[1]
}
enc.uniformSampler.AtLevel(ct.Level(), -1).Read(ringqp.Poly{Q: c1})
@@ -373,9 +364,7 @@ func (enc Encryptor) encryptZeroSk(sk *SecretKey, ct interface{}) (err error) {
if ct.Degree() == 1 {
c1 = ct.Value[1]
} else {
buffPolyQP := enc.pool.AtLevel(ct.LevelQ(), ct.LevelP()).GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffPolyQP)
c1 = *buffPolyQP
c1 = enc.buffQP
}
// ct = (e, a)
@@ -386,6 +375,7 @@ func (enc Encryptor) encryptZeroSk(sk *SecretKey, ct interface{}) (err error) {
}
return enc.encryptZeroSkFromC1QP(sk, ct, c1)
default:
return fmt.Errorf("cannot EncryptZero: input ciphertext type %T is not supported", ct)
}
@@ -399,22 +389,20 @@ func (enc Encryptor) encryptZeroSkFromC1(sk *SecretKey, ct Element[ring.Poly], c
c0 := ct.Value[0]
ringQ.MulCoeffsMontgomery(c1, sk.Value.Q, c0)
ringQ.Neg(c0, c0)
ringQ.MulCoeffsMontgomery(c1, sk.Value.Q, c0) // c0 = NTT(sc1)
ringQ.Neg(c0, c0) // c0 = NTT(-sc1)
if ct.IsNTT {
e := enc.pool.GetBuffPoly()
defer enc.pool.RecycleBuffPoly(e)
enc.xeSampler.AtLevel(levelQ).Read(*e)
ringQ.NTT(*e, *e)
ringQ.Add(c0, *e, c0)
enc.xeSampler.AtLevel(levelQ).Read(enc.buffQ[0]) // e
ringQ.NTT(enc.buffQ[0], enc.buffQ[0]) // NTT(e)
ringQ.Add(c0, enc.buffQ[0], c0) // c0 = NTT(-sc1 + e)
} else {
ringQ.INTT(c0, c0)
ringQ.INTT(c0, c0) // c0 = -sc1
if ct.Degree() == 1 {
ringQ.INTT(c1, c1)
ringQ.INTT(c1, c1) // c1 = c1
}
enc.xeSampler.AtLevel(levelQ).ReadAndAdd(c0)
enc.xeSampler.AtLevel(levelQ).ReadAndAdd(c0) // c0 = -sc1 + e
}
return
@@ -456,12 +444,24 @@ func (enc Encryptor) encryptZeroSkFromC1QP(sk *SecretKey, ct Element[ringqp.Poly
return
}
// withKeyedUniformSampling returns this encryptor with a keyed prng as its source of randomness for the uniform
// WithPRNG returns this encryptor with prng as its source of randomness for the uniform
// element c1.
// The returned encryptor is not thread safe (sampling will not be deterministic).
func (enc Encryptor) withKeyedUniformSampling(prng *sampling.KeyedPRNG) *Encryptor {
enc.uniformSampler = ringqp.NewUniformSampler(prng, *enc.params.RingQP())
return &enc
// The returned encryptor isn't safe to use concurrently with the original encryptor.
func (enc *Encryptor) WithPRNG(prng sampling.PRNG) *Encryptor {
return &Encryptor{
params: enc.params,
encryptorBuffers: enc.encryptorBuffers,
encKey: enc.encKey,
prng: enc.prng,
xeSampler: enc.xeSampler,
xsSampler: enc.xsSampler,
basisextender: enc.basisextender,
uniformSampler: ringqp.NewUniformSampler(prng, *enc.params.RingQP()),
}
}
func (enc Encryptor) ShallowCopy() *Encryptor {
return NewEncryptor(enc.params, enc.encKey)
}
func (enc Encryptor) WithKey(key EncryptionKey) *Encryptor {
@@ -505,22 +505,17 @@ func (enc Encryptor) checkSk(sk *SecretKey) (err error) {
func (enc Encryptor) addPtToCt(level int, pt *Plaintext, ct *Ciphertext) {
ringQ := enc.params.RingQ().AtLevel(level)
poolQ := enc.pool.AtLevel(level)
var buff ring.Poly
if pt.IsNTT {
if ct.IsNTT {
buff = pt.Value
} else {
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
buff = *buffQ
buff = enc.buffQ[0]
ringQ.NTT(pt.Value, buff)
}
} else {
if ct.IsNTT {
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
buff = *buffQ
buff = enc.buffQ[0]
ringQ.INTT(pt.Value, buff)
} else {
buff = pt.Value
+90 -21
View File
@@ -3,8 +3,9 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// Evaluator is a struct that holds the necessary elements to execute general homomorphic
@@ -12,21 +13,59 @@ import (
type Evaluator struct {
params Parameters
EvaluationKeySet
*EvaluatorBuffers
automorphismIndex map[uint64][]uint64
BasisExtender *ring.BasisExtender
Decomposer *ring.Decomposer
pool *BufferPool
}
// NewEvaluator creates a new [Evaluator].
type EvaluatorBuffers struct {
BuffCt *Ciphertext
// BuffQP[0-1]: Key-Switch output Key-Switch on the fly decomp(c2)
// BuffQP[2-5]: Available
BuffQP [6]ringqp.Poly
BuffInvNTT ring.Poly
BuffDecompQP []ringqp.Poly // Memory Buff for the basis extension in hoisting
BuffBitDecomp []uint64
}
func NewEvaluatorBuffers(params Parameters) *EvaluatorBuffers {
buff := new(EvaluatorBuffers)
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(params.MaxLevelQ(), 0)
ringQP := params.RingQP()
buff.BuffCt = NewCiphertext(params, 2, params.MaxLevel())
buff.BuffQP = [6]ringqp.Poly{
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
}
buff.BuffInvNTT = params.RingQ().NewPoly()
buff.BuffDecompQP = make([]ringqp.Poly, BaseRNSDecompositionVectorSize)
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
buff.BuffDecompQP[i] = ringQP.NewPoly()
}
buff.BuffBitDecomp = make([]uint64, params.RingQ().N())
return buff
}
// NewEvaluator creates a new Evaluator.
func NewEvaluator(params ParameterProvider, evk EvaluationKeySet) (eval *Evaluator) {
eval = new(Evaluator)
p := params.GetRLWEParameters()
eval.params = *p
eval.pool = NewPool(p.RingQP())
eval.EvaluatorBuffers = NewEvaluatorBuffers(eval.params)
if p.RingP() != nil {
eval.BasisExtender = ring.NewBasisExtender(p.RingQ(), p.RingP())
@@ -64,7 +103,7 @@ func (eval *Evaluator) GetRLWEParameters() *Parameters {
return &eval.params
}
// CheckAndGetGaloisKey returns an error if the [GaloisKey] for the given Galois element is missing or the [EvaluationKey] interface is nil.
// CheckAndGetGaloisKey returns an error if the GaloisKey for the given Galois element is missing or the EvaluationKey interface is nil.
func (eval Evaluator) CheckAndGetGaloisKey(galEl uint64) (evk *GaloisKey, err error) {
if eval.EvaluationKeySet != nil {
if evk, err = eval.GetGaloisKey(galEl); err != nil {
@@ -88,7 +127,7 @@ func (eval Evaluator) CheckAndGetGaloisKey(galEl uint64) (evk *GaloisKey, err er
return
}
// CheckAndGetRelinearizationKey returns an error if the [RelinearizationKey] is missing or the [EvaluationKey] interface is nil.
// CheckAndGetRelinearizationKey returns an error if the RelinearizationKey is missing or the EvaluationKey interface is nil.
func (eval Evaluator) CheckAndGetRelinearizationKey() (evk *RelinearizationKey, err error) {
if eval.EvaluationKeySet != nil {
if evk, err = eval.GetRelinearizationKey(); err != nil {
@@ -101,14 +140,14 @@ func (eval Evaluator) CheckAndGetRelinearizationKey() (evk *RelinearizationKey,
return
}
// InitOutputBinaryOp initializes the output [Element] opOut for receiving the result of a binary operation over
// InitOutputBinaryOp initializes the output Element opOut for receiving the result of a binary operation over
// op0 and op1. The method also performs the following checks:
//
// 1. Inputs are not nil
// 2. MetaData are not nil
// 3. op0.Degree() + op1.Degree() != 0 (i.e at least one [Element] is a ciphertext)
// 4. op0.IsNTT == op1.IsNTT == DefaultNTTFlag
// 5. op0.IsBatched == op1.IsBatched
// 1. Inputs are not nil
// 2. MetaData are not nil
// 3. op0.Degree() + op1.Degree() != 0 (i.e at least one Element is a ciphertext)
// 4. op0.IsNTT == op1.IsNTT == DefaultNTTFlag
// 5. op0.IsBatched == op1.IsBatched
//
// The opOut metadata are initilized as:
// IsNTT <- DefaultNTTFlag
@@ -159,12 +198,12 @@ func (eval Evaluator) InitOutputBinaryOp(op0, op1 *Element[ring.Poly], opInTotal
return
}
// InitOutputUnaryOp initializes the output [Element] opOut for receiving the result of a unary operation over
// InitOutputUnaryOp initializes the output Element opOut for receiving the result of a unary operation over
// op0. The method also performs the following checks:
//
// 1. Input and output are not nil
// 2. Inoutp and output Metadata are not nil
// 3. op0.IsNTT == DefaultNTTFlag
// 1. Input and output are not nil
// 2. Inoutp and output Metadata are not nil
// 2. op0.IsNTT == DefaultNTTFlag
//
// The method will also update the metadata of opOut:
//
@@ -195,8 +234,22 @@ func (eval Evaluator) InitOutputUnaryOp(op0, opOut *Element[ring.Poly]) (degree,
return utils.Max(op0.Degree(), opOut.Degree()), utils.Min(op0.Level(), opOut.Level()), nil
}
// WithKey creates a shallow copy of the receiver [Evaluator] for which the new [EvaluationKey] is evaluationKey
// and where the temporary buffers are shared. The receiver and the returned evaluators cannot be used concurrently.
// ShallowCopy creates a shallow copy of this Evaluator in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Evaluators can be used concurrently.
func (eval Evaluator) ShallowCopy() *Evaluator {
return &Evaluator{
params: eval.params,
Decomposer: eval.Decomposer,
BasisExtender: eval.BasisExtender.ShallowCopy(),
EvaluatorBuffers: NewEvaluatorBuffers(eval.params),
EvaluationKeySet: eval.EvaluationKeySet,
automorphismIndex: eval.automorphismIndex,
}
}
// WithKey creates a shallow copy of the receiver Evaluator for which the new EvaluationKey is evaluationKey
// and where the temporary buffers are shared. The receiver and the returned Evaluators cannot be used concurrently.
func (eval Evaluator) WithKey(evk EvaluationKeySet) *Evaluator {
var AutomorphismIndex map[uint64][]uint64
@@ -218,11 +271,11 @@ func (eval Evaluator) WithKey(evk EvaluationKeySet) *Evaluator {
return &Evaluator{
params: eval.params,
EvaluatorBuffers: eval.EvaluatorBuffers,
Decomposer: eval.Decomposer,
BasisExtender: eval.BasisExtender,
EvaluationKeySet: evk,
automorphismIndex: AutomorphismIndex,
pool: eval.pool,
}
}
@@ -230,6 +283,22 @@ func (eval Evaluator) AutomorphismIndex(galEl uint64) []uint64 {
return eval.automorphismIndex[galEl]
}
func (eval Evaluator) GetEvaluatorBuffer() *EvaluatorBuffers {
return eval.EvaluatorBuffers
}
func (eval Evaluator) GetBuffQP() [6]ringqp.Poly {
return eval.BuffQP
}
func (eval Evaluator) GetBuffCt() *Ciphertext {
return eval.BuffCt
}
func (eval Evaluator) GetBuffDecompQP() []ringqp.Poly {
return eval.BuffDecompQP
}
func (eval Evaluator) ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q ring.Poly) {
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, p1Q, p1P, p2Q)
}
+16 -27
View File
@@ -3,12 +3,13 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// Automorphism computes phi(ct), where phi is the map X -> X^galEl. The method requires
// that the corresponding RotationKey has been added to the [Evaluator]. The method will
// that the corresponding RotationKey has been added to the Evaluator. The method will
// return an error if either ctIn or opOut degree is not equal to 1.
func (eval Evaluator) Automorphism(ctIn *Ciphertext, galEl uint64, opOut *Ciphertext) (err error) {
@@ -34,8 +35,7 @@ func (eval Evaluator) Automorphism(ctIn *Ciphertext, galEl uint64, opOut *Cipher
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp := &Ciphertext{Element: Element[ring.Poly]{Value: []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}}}
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[1], &evk.GadgetCiphertext, ctTmp)
@@ -55,9 +55,9 @@ func (eval Evaluator) Automorphism(ctIn *Ciphertext, galEl uint64, opOut *Cipher
return
}
// AutomorphismHoisted is similar to [Evaluator.Automorphism], except that it takes as input ctIn and c1DecompQP, where c1DecompQP is the RNS
// decomposition of its element of degree 1. This decomposition can be obtained with [Evaluator.DecomposeNTT].
// The method requires that the corresponding RotationKey has been added to the [Evaluator].
// AutomorphismHoisted is similar to Automorphism, except that it takes as input ctIn and c1DecompQP, where c1DecompQP is the RNS
// decomposition of its element of degree 1. This decomposition can be obtained with DecomposeNTT.
// The method requires that the corresponding RotationKey has been added to the Evaluator.
// The method will return an error if either ctIn or opOut degree is not equal to 1.
func (eval Evaluator) AutomorphismHoisted(level int, ctIn *Ciphertext, c1DecompQP []ringqp.Poly, galEl uint64, opOut *Ciphertext) (err error) {
@@ -81,8 +81,8 @@ func (eval Evaluator) AutomorphismHoisted(level int, ctIn *Ciphertext, c1DecompQ
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q} // GadgetProductHoisted uses the same buffers for its ciphertext QP
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProductHoisted(level, c1DecompQP, &evk.EvaluationKey.GadgetCiphertext, ctTmp)
@@ -101,8 +101,8 @@ func (eval Evaluator) AutomorphismHoisted(level int, ctIn *Ciphertext, c1DecompQ
return
}
// AutomorphismHoistedLazy is similar to [Evaluator.AutomorphismHoisted], except that it returns a ciphertext modulo QP and scaled by P.
// The method requires that the corresponding RotationKey has been added to the [Evaluator].
// AutomorphismHoistedLazy is similar to AutomorphismHoisted, except that it returns a ciphertext modulo QP and scaled by P.
// The method requires that the corresponding RotationKey has been added to the Evaluator.
// Result NTT domain is returned according to the NTT flag of ctQP.
func (eval Evaluator) AutomorphismHoistedLazy(levelQ int, ctIn *Ciphertext, c1DecompQP []ringqp.Poly, galEl uint64, ctQP *Element[ringqp.Poly]) (err error) {
@@ -113,24 +113,13 @@ func (eval Evaluator) AutomorphismHoistedLazy(levelQ int, ctIn *Ciphertext, c1De
levelP := evk.LevelP()
if ctQP.LevelP() < levelP {
return fmt.Errorf("ctQP.LevelP()=%d < GaloisKey[%d].LevelP()=%d", ctQP.LevelP(), galEl, levelP)
}
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
ctTmp := &Element[ringqp.Poly]{}
ctTmp.Value = []ringqp.Poly{*buffQP1, *buffQP2}
ctTmp.Value = []ringqp.Poly{eval.BuffQP[0], eval.BuffQP[1]}
ctTmp.MetaData = ctIn.MetaData
if err = eval.GadgetProductHoistedLazy(levelQ, c1DecompQP, &evk.GadgetCiphertext, ctTmp); err != nil {
panic(fmt.Errorf("eval.GadgetProductHoistedLazy: %w", err))
}
eval.GadgetProductHoistedLazy(levelQ, c1DecompQP, &evk.GadgetCiphertext, ctTmp)
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
+16 -11
View File
@@ -3,15 +3,16 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
// ApplyEvaluationKey is a generic method to apply an [EvaluationKey] on a ciphertext.
// ApplyEvaluationKey is a generic method to apply an EvaluationKey on a ciphertext.
// An EvaluationKey is a type of public key that is be used during the evaluation of
// a homomorphic circuit to provide additional functionalities, like relinearization
// or rotations.
//
// In a nutshell, an [EvaluationKey] encrypts a secret skIn under a secret skOut and
// In a nutshell, an Evaluationkey encrypts a secret skIn under a secret skOut and
// enables the public and non interactive re-encryption of any ciphertext encrypted
// under skIn to a new ciphertext encrypted under skOut.
//
@@ -21,7 +22,7 @@ import (
// Note that the parameters of the smaller ring degree must be the same or a subset of the
// moduli Q and P of the one for the larger ring degree.
//
// To do so, it must be provided with the appropriate [EvaluationKey], and have the operands
// To do so, it must be provided with the appropriate EvaluationKey, and have the operands
// matching the target ring degrees.
//
// To switch a ciphertext to a smaller ring degree:
@@ -71,8 +72,13 @@ func (eval Evaluator) ApplyEvaluationKey(ctIn *Ciphertext, evk *EvaluationKey, o
level := utils.Min(ctIn.Level(), opOut.Level())
ctTmp := eval.pool.GetBuffCt(ctIn.Degree(), level)
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp, err := NewCiphertextAtLevelFromPoly(level, eval.BuffCt.Value)
// Sanity check, this error should not happen unless the
// evaluator's buffer have been improperly tempered with.
if err != nil {
panic(err)
}
ctTmp.MetaData = ctIn.MetaData
@@ -97,10 +103,9 @@ func (eval Evaluator) ApplyEvaluationKey(ctIn *Ciphertext, evk *EvaluationKey, o
}
func (eval Evaluator) applyEvaluationKey(level int, ctIn *Ciphertext, evk *EvaluationKey, opOut *Ciphertext) {
ctTmp := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[1], &evk.GadgetCiphertext, ctTmp)
eval.params.RingQ().AtLevel(level).Add(ctIn.Value[0], ctTmp.Value[0], opOut.Value[0])
opOut.Value[1].CopyLvl(level, ctTmp.Value[1])
@@ -132,8 +137,8 @@ func (eval Evaluator) Relinearize(ctIn *Ciphertext, opOut *Ciphertext) (err erro
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[2], &rlk.GadgetCiphertext, ctTmp)
+69 -124
View File
@@ -3,9 +3,9 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// GadgetProduct evaluates poly x Gadget -> RLWE where
@@ -18,19 +18,11 @@ func (eval Evaluator) GadgetProduct(levelQ int, cx ring.Poly, gadgetCt *GadgetCi
levelQ = utils.Min(levelQ, gadgetCt.LevelQ())
levelP := gadgetCt.LevelP()
poolQP := eval.pool.AtLevel(levelQ, levelP)
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
ctTmp := &Element[ringqp.Poly]{}
ctTmp.Value = []ringqp.Poly{{Q: ct.Value[0], P: (*buffQP1).P}, {Q: ct.Value[1], P: (*buffQP2).P}}
ctTmp.Value = []ringqp.Poly{{Q: ct.Value[0], P: eval.BuffQP[0].P}, {Q: ct.Value[1], P: eval.BuffQP[1].P}}
ctTmp.MetaData = ct.MetaData
if err := eval.GadgetProductLazy(levelQ, cx, gadgetCt, ctTmp); err != nil {
panic(fmt.Errorf("eval.GadgetProductLazy: %w", err))
}
eval.GadgetProductLazy(levelQ, cx, gadgetCt, ctTmp)
eval.ModDown(levelQ, levelP, ctTmp, ct)
}
@@ -98,59 +90,43 @@ func (eval Evaluator) ModDown(levelQ, levelP int, ctQP *Element[ringqp.Poly], ct
// GadgetProductLazy evaluates poly x Gadget -> RLWE where
//
// ctQP = [<decomp(cx), gadget[0]>, <decomp(cx), gadget[1]>] mod QP
// ct = [<decomp(cx), gadget[0]>, <decomp(cx), gadget[1]>] mod QP
//
// Expects the flag IsNTT of ctQP to correctly reflect the domain of cx.
// Expects the flag IsNTT of ct to correctly reflect the domain of cx.
//
// Result NTT domain is returned according to the NTT flag of ctQP.
//
// The method will return an error if ctQP.Level() < gadgetCt.Level().
func (eval Evaluator) GadgetProductLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ctQP *Element[ringqp.Poly]) (err error) {
if ctQP.LevelP() < gadgetCt.LevelP() {
return fmt.Errorf("ctQP.LevelP()=%d < gadgetCt.LevelP()=%d", ctQP.LevelP(), gadgetCt.LevelP())
}
// Result NTT domain is returned according to the NTT flag of ct.
func (eval Evaluator) GadgetProductLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ct *Element[ringqp.Poly]) {
if gadgetCt.LevelP() > 0 {
eval.gadgetProductMultiplePLazy(levelQ, cx, gadgetCt, ctQP)
eval.gadgetProductMultiplePLazy(levelQ, cx, gadgetCt, ct)
} else {
eval.gadgetProductSinglePAndBitDecompLazy(levelQ, cx, gadgetCt, ctQP)
eval.gadgetProductSinglePAndBitDecompLazy(levelQ, cx, gadgetCt, ct)
}
if !ctQP.IsNTT {
if !ct.IsNTT {
ringQP := eval.params.RingQP().AtLevel(levelQ, gadgetCt.LevelP())
ringQP.INTT(ctQP.Value[0], ctQP.Value[0])
ringQP.INTT(ctQP.Value[1], ctQP.Value[1])
ringQP.INTT(ct.Value[0], ct.Value[0])
ringQP.INTT(ct.Value[1], ct.Value[1])
}
return
}
func (eval Evaluator) gadgetProductMultiplePLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ctQP *Element[ringqp.Poly]) {
func (eval Evaluator) gadgetProductMultiplePLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ct *Element[ringqp.Poly]) {
levelP := gadgetCt.LevelP()
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
buff := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buff)
c2QP := *buff
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
c2QP := eval.BuffDecompQP[0]
var cxNTT, cxInvNTT ring.Poly
if ctQP.IsNTT {
if ct.IsNTT {
cxNTT = cx
cxInvNTT = *buffQ
cxInvNTT = eval.BuffInvNTT
ringQ.INTT(cxNTT, cxInvNTT)
} else {
cxNTT = *buffQ
cxNTT = eval.BuffInvNTT
cxInvNTT = cx
ringQ.NTT(cxInvNTT, cxNTT)
}
@@ -169,52 +145,49 @@ func (eval Evaluator) gadgetProductMultiplePLazy(levelQ int, cx ring.Poly, gadge
eval.DecomposeSingleNTT(levelQ, levelP, levelP+1, i, cxNTT, cxInvNTT, c2QP.Q, c2QP.P)
if i == 0 {
ringQP.MulCoeffsMontgomeryLazy(el[i][0][0], c2QP, ctQP.Value[0])
ringQP.MulCoeffsMontgomeryLazy(el[i][0][1], c2QP, ctQP.Value[1])
ringQP.MulCoeffsMontgomeryLazy(el[i][0][0], c2QP, ct.Value[0])
ringQP.MulCoeffsMontgomeryLazy(el[i][0][1], c2QP, ct.Value[1])
} else {
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(el[i][0][0], c2QP, ctQP.Value[0])
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(el[i][0][1], c2QP, ctQP.Value[1])
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(el[i][0][0], c2QP, ct.Value[0])
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(el[i][0][1], c2QP, ct.Value[1])
}
if reduce%QiOverF == QiOverF-1 {
ringQ.Reduce(ctQP.Value[0].Q, ctQP.Value[0].Q)
ringQ.Reduce(ctQP.Value[1].Q, ctQP.Value[1].Q)
ringQ.Reduce(ct.Value[0].Q, ct.Value[0].Q)
ringQ.Reduce(ct.Value[1].Q, ct.Value[1].Q)
}
if reduce%PiOverF == PiOverF-1 {
ringP.Reduce(ctQP.Value[0].P, ctQP.Value[0].P)
ringP.Reduce(ctQP.Value[1].P, ctQP.Value[1].P)
ringP.Reduce(ct.Value[0].P, ct.Value[0].P)
ringP.Reduce(ct.Value[1].P, ct.Value[1].P)
}
reduce++
}
if reduce%QiOverF != 0 {
ringQ.Reduce(ctQP.Value[0].Q, ctQP.Value[0].Q)
ringQ.Reduce(ctQP.Value[1].Q, ctQP.Value[1].Q)
ringQ.Reduce(ct.Value[0].Q, ct.Value[0].Q)
ringQ.Reduce(ct.Value[1].Q, ct.Value[1].Q)
}
if reduce%PiOverF != 0 {
ringP.Reduce(ctQP.Value[0].P, ctQP.Value[0].P)
ringP.Reduce(ctQP.Value[1].P, ctQP.Value[1].P)
ringP.Reduce(ct.Value[0].P, ct.Value[0].P)
ringP.Reduce(ct.Value[1].P, ct.Value[1].P)
}
}
func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ctQP *Element[ringqp.Poly]) {
func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ct *Element[ringqp.Poly]) {
levelP := gadgetCt.LevelP()
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
var cxInvNTT ring.Poly
if ctQP.IsNTT {
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
cxInvNTT = *buffQ
if ct.IsNTT {
cxInvNTT = eval.BuffInvNTT
ringQ.INTT(cx, cxInvNTT)
} else {
cxInvNTT = cx
@@ -227,21 +200,15 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
mask := uint64(((1 << pw2) - 1))
buff := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buff)
cw := buff.Q.Coeffs[0]
buffBitDecomp := eval.pool.GetBuffUintArray()
defer eval.pool.RecycleBuffUintArray(buffBitDecomp)
cwNTT := *buffBitDecomp
cw := eval.BuffDecompQP[0].Q.Coeffs[0]
cwNTT := eval.BuffBitDecomp
QiOverF := eval.params.QiOverflowMargin(levelQ) >> 1
PiOverF := eval.params.PiOverflowMargin(levelP) >> 1
el := gadgetCt.Value
c2QP := buff
c2QP := eval.BuffDecompQP[0]
// Re-encryption with CRT decomposition for the Qi
var reduce int
@@ -257,21 +224,16 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
if mask != 0 {
ring.MaskVec(cxInvNTT.Coeffs[i], j*pw2, mask, cw)
ring.MaskVec(cxInvNTT.Coeffs[i], j*pw2, mask, cw)
}
if i == 0 && j == 0 {
for u, s := range ringQ.SubRings[:levelQ+1] {
if mask == 0 {
s.NTTLazy(c2QP.Q.Coeffs[u], cwNTT)
} else {
ring.MaskVec(cxInvNTT.Coeffs[i], j*pw2, mask, cw)
s.NTTLazy(cw, cwNTT)
}
s.MulCoeffsMontgomeryLazy(el[i][j][0].Q.Coeffs[u], cwNTT, ctQP.Value[0].Q.Coeffs[u])
s.MulCoeffsMontgomeryLazy(el[i][j][1].Q.Coeffs[u], cwNTT, ctQP.Value[1].Q.Coeffs[u])
s.MulCoeffsMontgomeryLazy(el[i][j][0].Q.Coeffs[u], cwNTT, ct.Value[0].Q.Coeffs[u])
s.MulCoeffsMontgomeryLazy(el[i][j][1].Q.Coeffs[u], cwNTT, ct.Value[1].Q.Coeffs[u])
}
if ringP != nil {
@@ -279,10 +241,11 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
if mask == 0 {
s.NTTLazy(c2QP.P.Coeffs[u], cwNTT)
} else {
ring.MaskVec(cxInvNTT.Coeffs[i], j*pw2, mask, cw)
s.NTTLazy(cw, cwNTT)
}
s.MulCoeffsMontgomeryLazy(el[i][j][0].P.Coeffs[u], cwNTT, ctQP.Value[0].P.Coeffs[u])
s.MulCoeffsMontgomeryLazy(el[i][j][1].P.Coeffs[u], cwNTT, ctQP.Value[1].P.Coeffs[u])
s.MulCoeffsMontgomeryLazy(el[i][j][0].P.Coeffs[u], cwNTT, ct.Value[0].P.Coeffs[u])
s.MulCoeffsMontgomeryLazy(el[i][j][1].P.Coeffs[u], cwNTT, ct.Value[1].P.Coeffs[u])
}
}
@@ -291,10 +254,11 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
if mask == 0 {
s.NTTLazy(c2QP.Q.Coeffs[u], cwNTT)
} else {
ring.MaskVec(cxInvNTT.Coeffs[i], j*pw2, mask, cw)
s.NTTLazy(cw, cwNTT)
}
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][0].Q.Coeffs[u], cwNTT, ctQP.Value[0].Q.Coeffs[u])
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][1].Q.Coeffs[u], cwNTT, ctQP.Value[1].Q.Coeffs[u])
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][0].Q.Coeffs[u], cwNTT, ct.Value[0].Q.Coeffs[u])
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][1].Q.Coeffs[u], cwNTT, ct.Value[1].Q.Coeffs[u])
}
if ringP != nil {
@@ -302,22 +266,23 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
if mask == 0 {
s.NTTLazy(c2QP.P.Coeffs[u], cwNTT)
} else {
ring.MaskVec(cxInvNTT.Coeffs[i], j*pw2, mask, cw)
s.NTTLazy(cw, cwNTT)
}
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][0].P.Coeffs[u], cwNTT, ctQP.Value[0].P.Coeffs[u])
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][1].P.Coeffs[u], cwNTT, ctQP.Value[1].P.Coeffs[u])
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][0].P.Coeffs[u], cwNTT, ct.Value[0].P.Coeffs[u])
s.MulCoeffsMontgomeryLazyThenAddLazy(el[i][j][1].P.Coeffs[u], cwNTT, ct.Value[1].P.Coeffs[u])
}
}
}
if reduce%QiOverF == QiOverF-1 {
ringQ.Reduce(ctQP.Value[0].Q, ctQP.Value[0].Q)
ringQ.Reduce(ctQP.Value[1].Q, ctQP.Value[1].Q)
ringQ.Reduce(ct.Value[0].Q, ct.Value[0].Q)
ringQ.Reduce(ct.Value[1].Q, ct.Value[1].Q)
}
if reduce%PiOverF == PiOverF-1 {
ringP.Reduce(ctQP.Value[0].P, ctQP.Value[0].P)
ringP.Reduce(ctQP.Value[1].P, ctQP.Value[1].P)
ringP.Reduce(ct.Value[0].P, ct.Value[0].P)
ringP.Reduce(ct.Value[1].P, ct.Value[1].P)
}
reduce++
@@ -325,14 +290,14 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
}
if reduce%QiOverF != 0 {
ringQ.Reduce(ctQP.Value[0].Q, ctQP.Value[0].Q)
ringQ.Reduce(ctQP.Value[1].Q, ctQP.Value[1].Q)
ringQ.Reduce(ct.Value[0].Q, ct.Value[0].Q)
ringQ.Reduce(ct.Value[1].Q, ct.Value[1].Q)
}
if ringP != nil {
if reduce%PiOverF != 0 {
ringP.Reduce(ctQP.Value[0].P, ctQP.Value[0].P)
ringP.Reduce(ctQP.Value[1].P, ctQP.Value[1].P)
ringP.Reduce(ct.Value[0].P, ct.Value[0].P)
ringP.Reduce(ct.Value[1].P, ct.Value[1].P)
}
}
}
@@ -347,22 +312,14 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
// Result NTT domain is returned according to the NTT flag of ct.
func (eval Evaluator) GadgetProductHoisted(levelQ int, BuffQPDecompQP []ringqp.Poly, gadgetCt *GadgetCiphertext, ct *Ciphertext) {
pool := eval.pool.PoolP.AtLevel(gadgetCt.LevelP())
buffP1 := pool.GetBuffPoly()
defer pool.RecycleBuffPoly(buffP1)
buffP2 := pool.GetBuffPoly()
defer pool.RecycleBuffPoly(buffP2)
ctQP := &Element[ringqp.Poly]{}
ctQP.Value = []ringqp.Poly{
{Q: ct.Value[0], P: *buffP1},
{Q: ct.Value[1], P: *buffP2},
{Q: ct.Value[0], P: eval.BuffQP[0].P},
{Q: ct.Value[1], P: eval.BuffQP[1].P},
}
ctQP.MetaData = ct.MetaData
if err := eval.GadgetProductHoistedLazy(levelQ, BuffQPDecompQP, gadgetCt, ctQP); err != nil {
panic(fmt.Errorf("GadgetProductHoistedLazy: %w", err))
}
eval.GadgetProductHoistedLazy(levelQ, BuffQPDecompQP, gadgetCt, ctQP)
eval.ModDown(levelQ, gadgetCt.LevelP(), ctQP, ct)
}
@@ -373,29 +330,21 @@ func (eval Evaluator) GadgetProductHoisted(levelQ int, BuffQPDecompQP []ringqp.P
//
// BuffQPDecompQP is expected to be in the NTT domain.
//
// Result NTT domain is returned according to the NTT flag of ctQP.
//
// The method will return an error if ctQP.Level() < gadgetCt.Level().
func (eval Evaluator) GadgetProductHoistedLazy(levelQ int, BuffQPDecompQP []ringqp.Poly, gadgetCt *GadgetCiphertext, ctQP *Element[ringqp.Poly]) (err error) {
// Result NTT domain is returned according to the NTT flag of ct.
func (eval Evaluator) GadgetProductHoistedLazy(levelQ int, BuffQPDecompQP []ringqp.Poly, gadgetCt *GadgetCiphertext, ct *Element[ringqp.Poly]) {
// Sanity check for invalid parameters.
if gadgetCt.BaseTwoDecomposition != 0 {
return fmt.Errorf("method is unsupported for BaseTwoDecomposition != 0")
panic(fmt.Errorf("cannot GadgetProductHoistedLazy: method is unsupported for BaseTwoDecomposition != 0"))
}
if ctQP.LevelP() < gadgetCt.LevelP() {
return fmt.Errorf("ctQP.LevelP()=%d < gadgetCt.LevelP()=%d", ctQP.Level(), gadgetCt.LevelP())
}
eval.gadgetProductMultiplePLazyHoisted(levelQ, BuffQPDecompQP, gadgetCt, ct)
eval.gadgetProductMultiplePLazyHoisted(levelQ, BuffQPDecompQP, gadgetCt, ctQP)
if !ctQP.IsNTT {
if !ct.IsNTT {
ringQP := eval.params.RingQP().AtLevel(levelQ, gadgetCt.LevelP())
ringQP.INTT(ctQP.Value[0], ctQP.Value[0])
ringQP.INTT(ctQP.Value[1], ctQP.Value[1])
ringQP.INTT(ct.Value[0], ct.Value[0])
ringQP.INTT(ct.Value[1], ct.Value[1])
}
return
}
func (eval Evaluator) gadgetProductMultiplePLazyHoisted(levelQ int, BuffQPDecompQP []ringqp.Poly, gadgetCt *GadgetCiphertext, ct *Element[ringqp.Poly]) {
@@ -459,19 +408,15 @@ func (eval Evaluator) gadgetProductMultiplePLazyHoisted(levelQ int, BuffQPDecomp
func (eval Evaluator) DecomposeNTT(levelQ, levelP, nbPi int, c2 ring.Poly, c2IsNTT bool, decompQP []ringqp.Poly) {
ringQ := eval.params.RingQ().AtLevel(levelQ)
poolQ := eval.pool.AtLevel(levelQ)
var polyNTT, polyInvNTT ring.Poly
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
if c2IsNTT {
polyNTT = c2
polyInvNTT = *buffQ
polyInvNTT = eval.BuffInvNTT
ringQ.INTT(polyNTT, polyInvNTT)
} else {
polyNTT = *buffQ
polyNTT = eval.BuffInvNTT
polyInvNTT = c2
ringQ.NTT(polyInvNTT, polyNTT)
}
+36 -42
View File
@@ -4,14 +4,13 @@ import (
"bufio"
"fmt"
"io"
"slices"
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/luxdefi/lattice/v5/utils/structs"
)
// GadgetCiphertext is a struct for storing an encrypted
@@ -21,9 +20,9 @@ type GadgetCiphertext struct {
Value structs.Matrix[VectorQP]
}
// NewGadgetCiphertext returns a new [GadgetCiphertext] key with pre-allocated zero-value.
// [GadgetCiphertext] is always in the NTT domain.
// A [GadgetCiphertext] is created by default at degree 1 with the the maximum levelQ and levelP and with no base 2 decomposition.
// NewGadgetCiphertext returns a new Ciphertext key with pre-allocated zero-value.
// Ciphertext is always in the NTT domain.
// A GadgetCiphertext is created by default at degree 1 with the the maximum levelQ and levelP and with no base 2 decomposition.
// Give the optional GadgetCiphertextParameters struct to create a GadgetCiphertext with at a specific degree, levelQ, levelP and/or base 2 decomposition.
func NewGadgetCiphertext(params ParameterProvider, Degree, LevelQ, LevelP, BaseTwoDecomposition int) *GadgetCiphertext {
@@ -43,17 +42,12 @@ func NewGadgetCiphertext(params ParameterProvider, Degree, LevelQ, LevelP, BaseT
return &GadgetCiphertext{BaseTwoDecomposition: BaseTwoDecomposition, Value: m}
}
// Degree returns the degree of the target ciphertext.
func (ct GadgetCiphertext) Degree() int {
return len(ct.Value[0][0]) - 1
}
// LevelQ returns the level of the modulus Q of the target ciphertext.
// LevelQ returns the level of the modulus Q of the target Ciphertext.
func (ct GadgetCiphertext) LevelQ() int {
return ct.Value[0][0][0].LevelQ()
}
// LevelP returns the level of the modulus P of the target ciphertext.
// LevelP returns the level of the modulus P of the target Ciphertext.
func (ct GadgetCiphertext) LevelP() int {
return ct.Value[0][0][0].LevelP()
}
@@ -63,7 +57,7 @@ func (ct GadgetCiphertext) BaseRNSDecompositionVectorSize() int {
return len(ct.Value)
}
// BaseTwoDecompositionVectorSize returns the number of element in the power of two decomposition basis for each prime of Q.
// BaseTwoDecompositionVectorSize returns the number of element in the Power of two decomposition basis for each prime of Q.
func (ct GadgetCiphertext) BaseTwoDecompositionVectorSize() (base []int) {
base = make([]int, len(ct.Value))
for i := range ct.Value {
@@ -72,12 +66,12 @@ func (ct GadgetCiphertext) BaseTwoDecompositionVectorSize() (base []int) {
return
}
// Equal checks two ciphertexts for equality.
// Equal checks two Ciphertexts for equality.
func (ct GadgetCiphertext) Equal(other *GadgetCiphertext) bool {
return (ct.BaseTwoDecomposition == other.BaseTwoDecomposition) && cmp.Equal(ct.Value, other.Value)
}
// CopyNew creates a deep copy of the receiver ciphertext and returns it.
// CopyNew creates a deep copy of the receiver Ciphertext and returns it.
func (ct GadgetCiphertext) CopyNew() (ctCopy *GadgetCiphertext) {
return &GadgetCiphertext{BaseTwoDecomposition: ct.BaseTwoDecomposition, Value: ct.Value.CopyNew()}
}
@@ -87,17 +81,17 @@ func (ct GadgetCiphertext) BinarySize() (dataLen int) {
return 8 + ct.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (ct GadgetCiphertext) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
@@ -105,7 +99,7 @@ func (ct GadgetCiphertext) WriteTo(w io.Writer) (n int64, err error) {
var inc int64
if inc, err = buffer.WriteAsUint64(w, ct.BaseTwoDecomposition); err != nil {
if inc, err = buffer.WriteAsUint64[int](w, ct.BaseTwoDecomposition); err != nil {
return n + inc, err
}
@@ -120,24 +114,24 @@ func (ct GadgetCiphertext) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (ct *GadgetCiphertext) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
var inc int64
if inc, err = buffer.ReadAsUint64(r, &ct.BaseTwoDecomposition); err != nil {
if inc, err = buffer.ReadAsUint64[int](r, &ct.BaseTwoDecomposition); err != nil {
return n + inc, err
}
@@ -160,14 +154,14 @@ func (ct GadgetCiphertext) MarshalBinary() (data []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [GadgetCiphertext.MarshalBinary] or [GadgetCiphertext.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (ct *GadgetCiphertext) UnmarshalBinary(p []byte) (err error) {
_, err = ct.ReadFrom(buffer.NewBuffer(p))
return
}
// AddPolyTimesGadgetVectorToGadgetCiphertext takes a plaintext polynomial and a list of [GadgetCiphertext] and adds the
// plaintext times the RNS and BIT decomposition to the i-th element of the i-th ciphertext. This method returns
// AddPolyTimesGadgetVectorToGadgetCiphertext takes a plaintext polynomial and a list of Ciphertexts and adds the
// plaintext times the RNS and BIT decomposition to the i-th element of the i-th Ciphertexts. This method return
// an error if len(cts) > 2.
func AddPolyTimesGadgetVectorToGadgetCiphertext(pt ring.Poly, cts []GadgetCiphertext, ringQP ringqp.Ring, buff ring.Poly) (err error) {
@@ -197,7 +191,7 @@ func AddPolyTimesGadgetVectorToGadgetCiphertext(pt ring.Poly, cts []GadgetCipher
N := ringQ.N()
var index int
for j := 0; j < slices.Max(BaseTwoDecompositionVectorSize); j++ {
for j := 0; j < utils.MaxSlice(BaseTwoDecompositionVectorSize); j++ {
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
@@ -245,13 +239,13 @@ type GadgetPlaintext struct {
Value structs.Vector[ring.Poly]
}
// NewGadgetPlaintext creates a new [GadgetPlaintext] from value, which can be either uint64, int64 or *[ring.Poly].
// NewGadgetPlaintext creates a new gadget plaintext from value, which can be either uint64, int64 or *ring.Poly.
// Plaintext is returned in the NTT and Montgomery domain.
func NewGadgetPlaintext(params Parameters, value interface{}, levelQ, levelP, baseTwoDecomposition int) (pt *GadgetPlaintext, err error) {
ringQ := params.RingQP().RingQ.AtLevel(levelQ)
BaseTwoDecompositionVectorSize := slices.Max(params.BaseTwoDecompositionVectorSize(levelQ, levelP, baseTwoDecomposition))
BaseTwoDecompositionVectorSize := utils.MaxSlice(params.BaseTwoDecompositionVectorSize(levelQ, levelP, baseTwoDecomposition))
pt = new(GadgetPlaintext)
pt.Value = make([]ring.Poly, BaseTwoDecompositionVectorSize)
+56 -182
View File
@@ -1,153 +1,31 @@
package rlwe
import (
"fmt"
"math/big"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// Trace maps X -> sum((-1)^i * X^{i*n+1}) for n <= i < N
// Monomial X^k vanishes if k is not divisible by (N/n), otherwise it is multiplied by (N/n).
// Ciphertext is pre-multiplied by (N/n)^-1 to remove the (N/n) factor.
// Examples of full Trace for [0 + 1X + 2X^2 + 3X^3 + 4X^4 + 5X^5 + 6X^6 + 7X^7]
// InnerSum applies an optimized inner sum on the Ciphertext (log2(n) + HW(n) rotations with double hoisting).
// The operation assumes that `ctIn` encrypts Slots/`batchSize` sub-vectors of size `batchSize` and will add them together (in parallel) in groups of `n`.
// It outputs in opOut a Ciphertext for which the "leftmost" sub-vector of each group is equal to the sum of the group.
//
// 1.
// The inner sum is computed in a tree fashion. Example for batchSize=2 & n=4 (garbage slots are marked by 'x'):
//
// [1 + 2X + 3X^2 + 4X^3 + 5X^4 + 6X^5 + 7X^6 + 8X^7]
// + [1 - 6X - 3X^2 + 8X^3 + 5X^4 + 2X^5 - 7X^6 - 4X^7] {X-> X^(i * 5^1)}
// = [2 - 4X + 0X^2 +12X^3 +10X^4 + 8X^5 - 0X^6 + 4X^7]
// 1) [{a, b}, {c, d}, {e, f}, {g, h}, {a, b}, {c, d}, {e, f}, {g, h}]
//
// 2.
// 2. [{a, b}, {c, d}, {e, f}, {g, h}, {a, b}, {c, d}, {e, f}, {g, h}]
// +
// [{c, d}, {e, f}, {g, h}, {x, x}, {c, d}, {e, f}, {g, h}, {x, x}] (rotate batchSize * 2^{0})
// =
// [{a+c, b+d}, {x, x}, {e+g, f+h}, {x, x}, {a+c, b+d}, {x, x}, {e+g, f+h}, {x, x}]
//
// [2 - 4X + 0X^2 +12X^3 +10X^4 + 8X^5 - 0X^6 + 4X^7]
// + [2 + 4X + 0X^2 -12X^3 +10X^4 - 8X^5 + 0X^6 - 4X^7] {X-> X^(i * 5^2)}
// = [4 + 0X + 0X^2 - 0X^3 +20X^4 + 0X^5 + 0X^6 - 0X^7]
//
// 3.
//
// [4 + 0X + 0X^2 - 0X^3 +20X^4 + 0X^5 + 0X^6 - 0X^7]
// + [4 + 0X + 0X^2 - 0X^3 -20X^4 + 0X^5 + 0X^6 - 0X^7] {X-> X^(i * -1)}
// = [8 + 0X + 0X^2 - 0X^3 + 0X^4 + 0X^5 + 0X^6 - 0X^7]
//
// The method will return an error if the input and output ciphertexts degree is not one.
func (eval Evaluator) Trace(ctIn *Ciphertext, logN int, opOut *Ciphertext) (err error) {
if ctIn.Degree() != 1 || opOut.Degree() != 1 {
return fmt.Errorf("ctIn.Degree() != 1 or opOut.Degree() != 1")
}
params := eval.GetRLWEParameters()
level := utils.Min(ctIn.Level(), opOut.Level())
opOut.Resize(opOut.Degree(), level)
*opOut.MetaData = *ctIn.MetaData
gap := 1 << (params.LogN() - logN - 1)
if logN == 0 {
gap <<= 1
}
if gap > 1 {
ringQ := params.RingQ().AtLevel(level)
if ringQ.Type() == ring.ConjugateInvariant {
gap >>= 1 // We skip the last step that applies phi(5^{-1})
}
/* #nosec G115 -- gap cannot be negative */
NInv := new(big.Int).SetUint64(uint64(gap))
NInv.ModInverse(NInv, ringQ.ModulusAtLevel[level])
// pre-multiplication by (N/n)^-1
ringQ.MulScalarBigint(ctIn.Value[0], NInv, opOut.Value[0])
ringQ.MulScalarBigint(ctIn.Value[1], NInv, opOut.Value[1])
if !ctIn.IsNTT {
ringQ.NTT(opOut.Value[0], opOut.Value[0])
ringQ.NTT(opOut.Value[1], opOut.Value[1])
opOut.IsNTT = true
}
buff := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(buff)
buff.IsNTT = true
for i := logN; i < params.LogN()-1; i++ {
if err = eval.Automorphism(opOut, params.GaloisElement(1<<i), buff); err != nil {
return err
}
ringQ.Add(opOut.Value[0], buff.Value[0], opOut.Value[0])
ringQ.Add(opOut.Value[1], buff.Value[1], opOut.Value[1])
}
if logN == 0 && ringQ.Type() == ring.Standard {
if err = eval.Automorphism(opOut, ringQ.NthRoot()-1, buff); err != nil {
return err
}
ringQ.Add(opOut.Value[0], buff.Value[0], opOut.Value[0])
ringQ.Add(opOut.Value[1], buff.Value[1], opOut.Value[1])
}
if !ctIn.IsNTT {
ringQ.INTT(opOut.Value[0], opOut.Value[0])
ringQ.INTT(opOut.Value[1], opOut.Value[1])
opOut.IsNTT = false
}
} else {
if ctIn != opOut {
opOut.Copy(ctIn)
}
}
return
}
// GaloisElementsForTrace returns the list of Galois elements required for the for the `Trace` operation.
// Trace maps X -> sum((-1)^i * X^{i*n+1}) for 2^{LogN} <= i < N.
func GaloisElementsForTrace(params ParameterProvider, logN int) (galEls []uint64) {
p := params.GetRLWEParameters()
galEls = []uint64{}
for i, j := logN, 0; i < p.LogN()-1; i, j = i+1, j+1 {
galEls = append(galEls, p.GaloisElement(1<<i))
}
if logN == 0 {
switch p.RingType() {
case ring.Standard:
galEls = append(galEls, p.GaloisElementOrderTwoOrthogonalSubgroup())
case ring.ConjugateInvariant:
panic("cannot GaloisElementsForTrace: Galois element GaloisGen^-1 is undefined in ConjugateInvariant Ring")
default:
panic("cannot GaloisElementsForTrace: invalid ring type")
}
}
return
}
// PartialTracesSum applies a set of automorphisms on the input ciphertext and sum the results.
// The automorphisms are of the form phi(i*offset, X), 0 <= i < n, where phi(k, X): X -> X^{5^k}
// i.e. opOut = \sum_{i = 0}^{n-1} phi(i*offset, ctIn).
// At the scheme level, this function is used to perform inner sums or efficiently replicate slots.
func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *Ciphertext) (err error) {
if n == 0 || offset == 0 {
return fmt.Errorf("partialtrace: invalid parameter (n = 0 or batchSize = 0)")
}
// 3. [{a+c, b+d}, {x, x}, {e+g, f+h}, {x, x}, {a+c, b+d}, {x, x}, {e+g, f+h}, {x, x}] (rotate batchSize * 2^{1})
// +
// [{e+g, f+h}, {x, x}, {x, x}, {x, x}, {e+g, f+h}, {x, x}, {x, x}, {x, x}] =
// =
// [{a+c+e+g, b+d+f+h}, {x, x}, {x, x}, {x, x}, {a+c+e+g, b+d+f+h}, {x, x}, {x, x}, {x, x}]
func (eval Evaluator) InnerSum(ctIn *Ciphertext, batchSize, n int, opOut *Ciphertext) (err error) {
params := eval.GetRLWEParameters()
@@ -155,15 +33,19 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
levelP := params.PCount() - 1
ringQP := params.RingQP().AtLevel(ctIn.Level(), levelP)
poolQP := eval.pool.AtLevel(ctIn.Level(), levelP)
ringQ := ringQP.RingQ
opOut.Resize(opOut.Degree(), levelQ)
*opOut.MetaData = *ctIn.MetaData
ctInNTT := eval.pool.GetBuffCt(1, levelQ)
defer eval.pool.RecycleBuffCt(ctInNTT)
ctInNTT, err := NewCiphertextAtLevelFromPoly(levelQ, eval.BuffCt.Value[:2])
// Sanity check, this error should not happen unless the
// evaluator's buffer thave been improperly tempered with.
if err != nil {
panic(err)
}
ctInNTT.MetaData = &MetaData{}
ctInNTT.IsNTT = true
@@ -183,50 +65,40 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
}
} else {
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
// BuffQP[0:2] are used by AutomorphismHoistedLazy
// Accumulator mod QP (i.e. opOut Mod QP)
accQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{*buffQP1, *buffQP2}}
accQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{eval.BuffQP[2], eval.BuffQP[3]}}
accQP.MetaData = ctInNTT.MetaData
// Buffer mod QP (i.e. to store the result of lazy gadget products)
buffQP3 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP3)
buffQP4 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP4)
cQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{*buffQP3, *buffQP4}}
cQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{eval.BuffQP[4], eval.BuffQP[5]}}
cQP.MetaData = ctInNTT.MetaData
// Buffer mod Q (i.e. to store the result of gadget products)
cQ, err := NewCiphertextAtLevelFromPoly(levelQ, []ring.Poly{cQP.Value[0].Q, cQP.Value[1].Q})
// Sanity check, this error should not happen unless the
// evaluator's buffer has been improperly tempered with.
// evaluator's buffer thave been improperly tempered with.
if err != nil {
panic(err)
}
cQ.MetaData = ctInNTT.MetaData
buffDecompQP := poolQP.GetBuffDecompQP(eval.params, levelQ, levelP)
defer eval.pool.RecycleBuffDecompQP(buffDecompQP)
state := false
copy := true
// Binary reading of the input n
for i, j := 0, n; j > 0; i, j = i+1, j>>1 {
// Starts by decomposing the input ciphertext
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctInNTT.Value[1], true, buffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctInNTT.Value[1], true, eval.BuffDecompQP)
// If the binary reading scans a 1 (j is odd)
if j&1 == 1 {
k := n - (n & ((2 << i) - 1))
k *= offset
k *= batchSize
// If the rotation is not zero
if k != 0 {
@@ -235,12 +107,12 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
// opOutQP = opOutQP + Rotate(ctInNTT, k)
if copy {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, buffDecompQP, rot, accQP); err != nil {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, eval.BuffDecompQP, rot, accQP); err != nil {
return err
}
copy = false
} else {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, buffDecompQP, rot, cQP); err != nil {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, eval.BuffDecompQP, rot, cQP); err != nil {
return err
}
ringQP.Add(accQP.Value[0], cQP.Value[0], accQP.Value[0])
@@ -271,10 +143,10 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
if !state {
rot := params.GaloisElement((1 << i) * offset)
rot := params.GaloisElement((1 << i) * batchSize)
// ctInNTT = ctInNTT + Rotate(ctInNTT, 2^i)
if err = eval.AutomorphismHoisted(levelQ, ctInNTT, buffDecompQP, rot, cQ); err != nil {
if err = eval.AutomorphismHoisted(levelQ, ctInNTT, eval.BuffDecompQP, rot, cQ); err != nil {
return err
}
ringQ.Add(ctInNTT.Value[0], cQ.Value[0], ctInNTT.Value[0])
@@ -291,17 +163,16 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
return
}
// InnerFunction applies an user defined function on the [Ciphertext] with a tree-like combination requiring log2(n) + HW(n) rotations.
// InnerFunction applies an user defined function on the Ciphertext with a tree-like combination requiring log2(n) + HW(n) rotations.
//
// InnerFunction with f = eval.Add(a, b, c) is equivalent to [Evaluator.InnerSum] (although slightly slower).
// InnerFunction with f = eval.Add(a, b, c) is equivalent to InnerSum (although slightly slower).
//
// The operation assumes that `ctIn` encrypts Slots/`batchSize` sub-vectors of size `batchSize` and will add them together (in parallel) in groups of `n`.
// It outputs in opOut a [Ciphertext] for which the "leftmost" sub-vector of each group is equal to the pair-wise recursive evaluation of
// function over the group.
// It outputs in opOut a Ciphertext for which the "leftmost" sub-vector of each group is equal to the pair-wise recursive evaluation of function over the group.
//
// The inner function is computed in a tree fashion. Example for batchSize=2 & n=4 (garbage slots are marked by 'x'):
//
// 1. [{a, b}, {c, d}, {e, f}, {g, h}, {a, b}, {c, d}, {e, f}, {g, h}]
// 1) [{a, b}, {c, d}, {e, f}, {g, h}, {a, b}, {c, d}, {e, f}, {g, h}]
//
// 2. [{a, b}, {c, d}, {e, f}, {g, h}, {a, b}, {c, d}, {e, f}, {g, h}]
// f
@@ -325,6 +196,11 @@ func (eval Evaluator) InnerFunction(ctIn *Ciphertext, batchSize, n int, f func(a
opOut.Resize(opOut.Degree(), levelQ)
*opOut.MetaData = *ctIn.MetaData
P0 := params.RingQ().NewPoly()
P1 := params.RingQ().NewPoly()
P2 := params.RingQ().NewPoly()
P3 := params.RingQ().NewPoly()
ctInNTT := NewCiphertext(params, 1, levelQ)
*ctInNTT.MetaData = *ctIn.MetaData
@@ -342,23 +218,21 @@ func (eval Evaluator) InnerFunction(ctIn *Ciphertext, batchSize, n int, f func(a
} else {
// Accumulator mod Q
accQ := eval.pool.GetBuffCt(1, levelQ)
defer eval.pool.RecycleBuffCt(accQ)
accQ, err := NewCiphertextAtLevelFromPoly(levelQ, []ring.Poly{P0, P1})
*accQ.MetaData = *ctInNTT.MetaData
// Sanity check, this error should not happen unless the
// evaluator's buffer has been improperly tempered with.
// evaluator's buffer thave been improperly tempered with.
if err != nil {
panic(err)
}
// Buffer mod Q
cQ := eval.pool.GetBuffCt(1, levelQ)
defer eval.pool.RecycleBuffCt(cQ)
cQ, err := NewCiphertextAtLevelFromPoly(levelQ, []ring.Poly{P2, P3})
*cQ.MetaData = *ctInNTT.MetaData
// Sanity check, this error should not happen unless the
// evaluator's buffer has been improperly tempered with.
// evaluator's buffer thave been improperly tempered with.
if err != nil {
panic(err)
}
@@ -438,7 +312,7 @@ func (eval Evaluator) InnerFunction(ctIn *Ciphertext, batchSize, n int, f func(a
}
// GaloisElementsForInnerSum returns the list of Galois elements necessary to apply the method
// [Evaluator.InnerSum] operation with parameters batch and n.
// `InnerSum` operation with parameters `batch` and `n`.
func GaloisElementsForInnerSum(params ParameterProvider, batch, n int) (galEls []uint64) {
rotIndex := make(map[int]bool)
@@ -465,19 +339,19 @@ func GaloisElementsForInnerSum(params ParameterProvider, batch, n int) (galEls [
return params.GetRLWEParameters().GaloisElements(rotations)
}
// Replicate applies an optimized replication on the [Ciphertext] (log2(n) + HW(n) rotations with double hoisting).
// Replicate applies an optimized replication on the Ciphertext (log2(n) + HW(n) rotations with double hoisting).
// It acts as the inverse of a inner sum (summing elements from left to right).
// The replication is parameterized by the size of the sub-vectors to replicate batchSize and
// the number of times n they need to be replicated.
// The replication is parameterized by the size of the sub-vectors to replicate "batchSize" and
// the number of times 'n' they need to be replicated.
// To ensure correctness, a gap of zero values of size batchSize * (n-1) must exist between
// two consecutive sub-vectors to replicate.
// This method is faster than Replicate when the number of rotations is large and it uses log2(n) + HW(n) instead of n.
// This method is faster than Replicate when the number of rotations is large and it uses log2(n) + HW(n) instead of 'n'.
func (eval Evaluator) Replicate(ctIn *Ciphertext, batchSize, n int, opOut *Ciphertext) (err error) {
return eval.PartialTracesSum(ctIn, -batchSize, n, opOut)
return eval.InnerSum(ctIn, -batchSize, n, opOut)
}
// GaloisElementsForReplicate returns the list of Galois elements necessary to perform the
// [Evaluator.Replicate] operation with parameters batch and n.
// `Replicate` operation with parameters `batch` and `n`.
func GaloisElementsForReplicate(params ParameterProvider, batch, n int) (galEls []uint64) {
return GaloisElementsForInnerSum(params, -batch, n)
}
+58 -112
View File
@@ -3,49 +3,46 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// KeyGenerator is a structure that stores the elements required to create new keys,
// as well as a memory buffer for intermediate values.
type KeyGenerator struct {
*Encryptor
pool *BufferPool
}
// NewKeyGenerator creates a new KeyGenerator, from which the secret and public keys, as well as [EvaluationKey].
// NewKeyGenerator creates a new KeyGenerator, from which the secret and public keys, as well as EvaluationKeys.
func NewKeyGenerator(params ParameterProvider) *KeyGenerator {
return &KeyGenerator{
Encryptor: NewEncryptor(params, nil),
pool: NewPool(params.GetRLWEParameters().RingQP()),
}
}
// GenSecretKeyNew generates a new [SecretKey].
// Distribution is set according to [rlwe.Parameters.HammingWeight].
// GenSecretKeyNew generates a new SecretKey.
// Distribution is set according to `rlwe.Parameters.HammingWeight()`.
func (kgen KeyGenerator) GenSecretKeyNew() (sk *SecretKey) {
sk = NewSecretKey(kgen.params)
kgen.GenSecretKey(sk)
return
}
// GenSecretKey generates a [SecretKey].
// Distribution is set according to [rlwe.Parameters.HammingWeight].
// GenSecretKey generates a SecretKey.
// Distribution is set according to `rlwe.Parameters.HammingWeight()`.
func (kgen KeyGenerator) GenSecretKey(sk *SecretKey) {
kgen.genSecretKeyFromSampler(kgen.xsSampler, sk)
}
// GenSecretKeyWithHammingWeightNew generates a new [SecretKey] with exactly hw non-zero coefficients.
// GenSecretKeyWithHammingWeightNew generates a new SecretKey with exactly hw non-zero coefficients.
func (kgen *KeyGenerator) GenSecretKeyWithHammingWeightNew(hw int) (sk *SecretKey) {
sk = NewSecretKey(kgen.params)
kgen.GenSecretKeyWithHammingWeight(hw, sk)
return
}
// GenSecretKeyWithHammingWeight generates a [SecretKey] with exactly hw non-zero coefficients.
// GenSecretKeyWithHammingWeight generates a SecretKey with exactly hw non-zero coefficients.
func (kgen KeyGenerator) GenSecretKeyWithHammingWeight(hw int, sk *SecretKey) {
Xs, err := ring.NewSampler(kgen.prng, kgen.params.RingQ(), ring.Ternary{H: hw}, false)
// Sanity check, this error should not happen.
@@ -69,14 +66,14 @@ func (kgen KeyGenerator) genSecretKeyFromSampler(sampler ring.Sampler, sk *Secre
ringQP.MForm(sk.Value, sk.Value)
}
// GenPublicKeyNew generates a new [PublicKey] from the provided [SecretKey].
// GenPublicKeyNew generates a new public key from the provided SecretKey.
func (kgen KeyGenerator) GenPublicKeyNew(sk *SecretKey) (pk *PublicKey) {
pk = NewPublicKey(kgen.params)
kgen.GenPublicKey(sk, pk)
return
}
// GenPublicKey generates a [PublicKey] from the provided [SecretKey].
// GenPublicKey generates a public key from the provided SecretKey.
func (kgen KeyGenerator) GenPublicKey(sk *SecretKey, pk *PublicKey) {
if err := kgen.WithKey(sk).EncryptZero(Element[ringqp.Poly]{
MetaData: &MetaData{CiphertextMetaData: CiphertextMetaData{IsNTT: true, IsMontgomery: true}},
@@ -87,49 +84,34 @@ func (kgen KeyGenerator) GenPublicKey(sk *SecretKey, pk *PublicKey) {
}
}
// GenKeyPairNew generates a new [SecretKey] and a corresponding [PublicKey].
// Distribution of the [SecretKey] set according to [rlwe.Parameters.HammingWeight].
// GenKeyPairNew generates a new SecretKey and a corresponding public key.
// Distribution is of the SecretKey set according to `rlwe.Parameters.HammingWeight()`.
func (kgen KeyGenerator) GenKeyPairNew() (sk *SecretKey, pk *PublicKey) {
sk = kgen.GenSecretKeyNew()
pk = kgen.GenPublicKeyNew(sk)
return
}
// GenRelinearizationKeyNew generates a new [EvaluationKey] that will be used to relinearize [Ciphertexts] during multiplication.
// GenRelinearizationKeyNew generates a new EvaluationKey that will be used to relinearize Ciphertexts during multiplication.
func (kgen KeyGenerator) GenRelinearizationKeyNew(sk *SecretKey, evkParams ...EvaluationKeyParameters) (rlk *RelinearizationKey) {
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(kgen.params, evkParams)
degree := 1
if compressed {
degree = 0
}
rlk = &RelinearizationKey{EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(kgen.params, degree, levelQ, levelP, BaseTwoDecomposition)}}
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
rlk = &RelinearizationKey{EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(kgen.params, 1, levelQ, levelP, BaseTwoDecomposition)}}
kgen.GenRelinearizationKey(sk, rlk)
return
}
// GenRelinearizationKey generates an [EvaluationKey] that will be used to relinearize [Ciphertexts] during multiplication.
// GenRelinearizationKey generates an EvaluationKey that will be used to relinearize Ciphertexts during multiplication.
func (kgen KeyGenerator) GenRelinearizationKey(sk *SecretKey, rlk *RelinearizationKey) {
sk2 := kgen.pool.AtLevel(rlk.LevelQ()).GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(sk2)
sk2.CopyLvl(rlk.LevelQ(), sk.Value.Q)
kgen.params.RingQ().AtLevel(rlk.LevelQ()).MulCoeffsMontgomery(*sk2, sk.Value.Q, *sk2)
kgen.genEvaluationKey(*sk2, sk.Value, &rlk.EvaluationKey)
kgen.buffQP.Q.CopyLvl(rlk.LevelQ(), sk.Value.Q)
kgen.params.RingQ().AtLevel(rlk.LevelQ()).MulCoeffsMontgomery(kgen.buffQP.Q, sk.Value.Q, kgen.buffQP.Q)
kgen.genEvaluationKey(kgen.buffQP.Q, sk.Value, &rlk.EvaluationKey)
}
// GenGaloisKeyNew generates a new [GaloisKey], enabling the automorphism X^{i} -> X^{i * galEl}.
// GenGaloisKeyNew generates a new GaloisKey, enabling the automorphism X^{i} -> X^{i * galEl}.
func (kgen KeyGenerator) GenGaloisKeyNew(galEl uint64, sk *SecretKey, evkParams ...EvaluationKeyParameters) (gk *GaloisKey) {
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(kgen.params, evkParams)
degree := 1
if compressed {
degree = 0
}
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
gk = &GaloisKey{
EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(kgen.params, degree, levelQ, levelP, BaseTwoDecomposition)},
EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(kgen.params, 1, levelQ, levelP, BaseTwoDecomposition)},
NthRoot: kgen.params.GetRLWEParameters().RingQ().NthRoot(),
}
kgen.GenGaloisKey(galEl, sk, gk)
@@ -140,12 +122,9 @@ func (kgen KeyGenerator) GenGaloisKeyNew(galEl uint64, sk *SecretKey, evkParams
func (kgen KeyGenerator) GenGaloisKey(galEl uint64, sk *SecretKey, gk *GaloisKey) {
skIn := sk.Value
skOut := kgen.buffQP
ringQP := kgen.params.RingQP().AtLevel(gk.LevelQ(), gk.LevelP())
polyQP := kgen.pool.AtLevel(gk.LevelQ(), gk.LevelP())
skOut := polyQP.GetBuffPolyQP()
defer polyQP.RecycleBuffPolyQP(skOut)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
@@ -170,13 +149,13 @@ func (kgen KeyGenerator) GenGaloisKey(galEl uint64, sk *SecretKey, gk *GaloisKey
ringP.AutomorphismNTTWithIndex(skIn.P, index, skOut.P)
}
kgen.genEvaluationKey(skIn.Q, *skOut, &gk.EvaluationKey)
kgen.genEvaluationKey(skIn.Q, skOut, &gk.EvaluationKey)
gk.GaloisElement = galEl
gk.NthRoot = ringQ.NthRoot()
}
// GenGaloisKeys generates the [GaloisKey] objects for all galois elements in galEls, and stores
// GenGaloisKeys generates the GaloisKey objects for all galois elements in galEls, and stores
// the resulting key for galois element i in gks[i].
// The galEls and gks parameters must have the same length.
func (kgen KeyGenerator) GenGaloisKeys(galEls []uint64, sk *SecretKey, gks []*GaloisKey) {
@@ -195,19 +174,19 @@ func (kgen KeyGenerator) GenGaloisKeys(galEls []uint64, sk *SecretKey, gks []*Ga
}
}
// GenGaloisKeysNew generates the [GaloisKey] objects for all galois elements in galEls, and
// returns the resulting keys in a newly allocated []*[GaloisKey].
// GenGaloisKeysNew generates the GaloisKey objects for all galois elements in galEls, and
// returns the resulting keys in a newly allocated []*GaloisKey.
func (kgen KeyGenerator) GenGaloisKeysNew(galEls []uint64, sk *SecretKey, evkParams ...EvaluationKeyParameters) (gks []*GaloisKey) {
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(kgen.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
gks = make([]*GaloisKey, len(galEls))
for i, galEl := range galEls {
gks[i] = newGaloisKey(kgen.params, levelQ, levelP, BaseTwoDecomposition, compressed)
gks[i] = newGaloisKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
kgen.GenGaloisKey(galEl, sk, gks[i])
}
return
}
// GenEvaluationKeysForRingSwapNew generates the necessary evaluation keys to switch from a standard ring to to a conjugate invariant ring and vice-versa.
// GenEvaluationKeysForRingSwapNew generates the necessary EvaluationKeys to switch from a standard ring to to a conjugate invariant ring and vice-versa.
func (kgen KeyGenerator) GenEvaluationKeysForRingSwapNew(skStd, skConjugateInvariant *SecretKey, evkParams ...EvaluationKeyParameters) (stdToci, ciToStd *EvaluationKey) {
levelQ := utils.Min(skStd.Value.Q.Level(), skConjugateInvariant.Value.Q.Level())
@@ -216,114 +195,81 @@ func (kgen KeyGenerator) GenEvaluationKeysForRingSwapNew(skStd, skConjugateInvar
kgen.params.RingQ().AtLevel(levelQ).UnfoldConjugateInvariantToStandard(skConjugateInvariant.Value.Q, skCIMappedToStandard.Value.Q)
if kgen.params.PCount() != 0 {
buffQ := kgen.pool.GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffQ)
ExtendBasisSmallNormAndCenterNTTMontgomery(kgen.params.RingQ(), kgen.params.RingP(), skCIMappedToStandard.Value.Q, *buffQ, skCIMappedToStandard.Value.P)
ExtendBasisSmallNormAndCenterNTTMontgomery(kgen.params.RingQ(), kgen.params.RingP(), skCIMappedToStandard.Value.Q, kgen.buffQ[1], skCIMappedToStandard.Value.P)
}
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(kgen.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
stdToci = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition, compressed)
stdToci = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
kgen.GenEvaluationKey(skStd, skCIMappedToStandard, stdToci)
ciToStd = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition, compressed)
ciToStd = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
kgen.GenEvaluationKey(skCIMappedToStandard, skStd, ciToStd)
return
}
// GenEvaluationKeyNew generates a new [EvaluationKey], that will re-encrypt a [Ciphertext] encrypted under the input key into the output key.
// GenEvaluationKeyNew generates a new EvaluationKey, that will re-encrypt a Ciphertext encrypted under the input key into the output key.
// If the ringDegree(skOutput) > ringDegree(skInput), generates [-a*SkOut + w*P*skIn_{Y^{N/n}} + e, a] in X^{N}.
// If the ringDegree(skOutput) < ringDegree(skInput), generates [-a*skOut_{Y^{N/n}} + w*P*skIn + e_{N}, a_{N}] in X^{N}.
// Else generates [-a*skOut + w*P*skIn + e, a] in X^{N}.
// The output [EvaluationKey] is always given in max(N, n).
// If evkParams is void, the output [EvaluationKey] will be in the moduli given by the parameters stored in the [KeyGenerator] kgen.
// When re-encrypting a [Ciphertext] from Y^{N/n} to X^{N}, the Ciphertext must first be mapped to X^{N}
// using [SwitchCiphertextRingDegreeNTT](ctSmallDim, nil, ctLargeDim).
// When re-encrypting a [Ciphertext] from X^{N} to Y^{N/n}, the output of the re-encryption is in still X^{N} and
// must be mapped Y^{N/n} using [SwitchCiphertextRingDegreeNTT](ctLargeDim, ringQLargeDim, ctSmallDim).
// The output EvaluationKey is always given in max(N, n) and in the moduli of the output EvaluationKey.
// When re-encrypting a Ciphertext from Y^{N/n} to X^{N}, the Ciphertext must first be mapped to X^{N}
// using SwitchCiphertextRingDegreeNTT(ctSmallDim, nil, ctLargeDim).
// When re-encrypting a Ciphertext from X^{N} to Y^{N/n}, the output of the re-encryption is in still X^{N} and
// must be mapped Y^{N/n} using SwitchCiphertextRingDegreeNTT(ctLargeDim, ringQLargeDim, ctSmallDim).
func (kgen KeyGenerator) GenEvaluationKeyNew(skInput, skOutput *SecretKey, evkParams ...EvaluationKeyParameters) (evk *EvaluationKey) {
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(kgen.params, evkParams)
evk = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition, compressed)
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
evk = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
kgen.GenEvaluationKey(skInput, skOutput, evk)
return
}
// GenEvaluationKey generates an [EvaluationKey], that will re-encrypt a [Ciphertext] encrypted under the input key into the output key.
// GenEvaluationKey generates an EvaluationKey, that will re-encrypt a Ciphertext encrypted under the input key into the output key.
// If the ringDegree(skOutput) > ringDegree(skInput), generates [-a*SkOut + w*P*skIn_{Y^{N/n}} + e, a] in X^{N}.
// If the ringDegree(skOutput) < ringDegree(skInput), generates [-a*skOut_{Y^{N/n}} + w*P*skIn + e_{N}, a_{N}] in X^{N}.
// Else generates [-a*skOut + w*P*skIn + e, a] in X^{N}.
// The output [EvaluationKey] is always given in max(N, n) and in the moduli of the output [EvaluationKey].
// When re-encrypting a [Ciphertext] from Y^{N/n} to X^{N}, the [Ciphertext] must first be mapped to X^{N}
// using [SwitchCiphertextRingDegreeNTT](ctSmallDim, nil, ctLargeDim).
// When re-encrypting a [Ciphertext] from X^{N} to Y^{N/n}, the output of the re-encryption is in still X^{N} and
// must be mapped Y^{N/n} using [SwitchCiphertextRingDegreeNTT](ctLargeDim, ringQLargeDim, ctSmallDim).
// The output EvaluationKey is always given in max(N, n) and in the moduli of the output EvaluationKey.
// When re-encrypting a Ciphertext from Y^{N/n} to X^{N}, the Ciphertext must first be mapped to X^{N}
// using SwitchCiphertextRingDegreeNTT(ctSmallDim, nil, ctLargeDim).
// When re-encrypting a Ciphertext from X^{N} to Y^{N/n}, the output of the re-encryption is in still X^{N} and
// must be mapped Y^{N/n} using SwitchCiphertextRingDegreeNTT(ctLargeDim, ringQLargeDim, ctSmallDim).
func (kgen KeyGenerator) GenEvaluationKey(skInput, skOutput *SecretKey, evk *EvaluationKey) {
ringQ := kgen.params.RingQ()
ringP := kgen.params.RingP()
buffSkOut := kgen.pool.GetBuffPolyQP()
defer kgen.pool.RecycleBuffPolyQP(buffSkOut)
buffSkIn := kgen.pool.GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffSkIn)
// Maps the smaller key to the largest with Y = X^{N/n}.
ring.MapSmallDimensionToLargerDimensionNTT(skOutput.Value.Q, buffSkOut.Q)
ring.MapSmallDimensionToLargerDimensionNTT(skOutput.Value.Q, kgen.buffQP.Q)
// Extends the modulus P of skOutput to the one of skInput
buffQ := kgen.pool.AtLevel(0).GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffQ)
if levelP := evk.LevelP(); levelP != -1 {
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringP.AtLevel(levelP), buffSkOut.Q, *buffQ, buffSkOut.P)
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringP.AtLevel(levelP), kgen.buffQP.Q, kgen.buffQ[0], kgen.buffQP.P)
}
// Maps the smaller key to the largest dimension with Y = X^{N/n}.
ring.MapSmallDimensionToLargerDimensionNTT(skInput.Value.Q, *buffSkIn)
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringQ.AtLevel(skOutput.Value.Q.Level()), *buffSkIn, *buffQ, *buffSkIn)
ring.MapSmallDimensionToLargerDimensionNTT(skInput.Value.Q, kgen.buffQ[0])
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringQ.AtLevel(skOutput.Value.Q.Level()), kgen.buffQ[0], kgen.buffQ[1], kgen.buffQ[0])
kgen.genEvaluationKey(*buffSkIn, *buffSkOut, evk)
kgen.genEvaluationKey(kgen.buffQ[0], kgen.buffQP, evk)
}
func (kgen KeyGenerator) genEvaluationKey(skIn ring.Poly, skOut ringqp.Poly, evk *EvaluationKey) {
enc := kgen.WithKey(&SecretKey{Value: skOut})
// For a compressed evaluation key, a seed is created and stored in the EvaluationKey struct
// struct while an uncompressed key uses an ephemeral seed.
if evk.IsCompressed() {
var seed [32]byte
if n, err := kgen.prng.Read(seed[:]); n != 32 || err != nil {
panic(fmt.Errorf("unable to sample evaluation key seed"))
}
evk.Seed = &seed
sampler, err := sampling.NewKeyedPRNG(seed[:])
if err != nil {
panic(fmt.Errorf("sampling.NewKeyedPRNG: %w", err))
}
enc = enc.withKeyedUniformSampling(sampler)
}
// Samples an encryption of zero for each element of the EvaluationKey.
for i := 0; i < len(evk.Value); i++ {
for j := 0; j < len(evk.Value[i]); j++ {
// evk[i][j] = (-a*sk + e, a) if the degree of degree of the GadgetCiphertext is 1
// evk[i][j] = (-a*sk + e) if the degree is 0
if err := enc.EncryptZero(Element[ringqp.Poly]{
MetaData: &MetaData{CiphertextMetaData: CiphertextMetaData{IsNTT: true, IsMontgomery: true}},
Value: []ringqp.Poly(evk.Value[i][j]),
}); err != nil {
if err := enc.EncryptZero(Element[ringqp.Poly]{MetaData: &MetaData{CiphertextMetaData: CiphertextMetaData{IsNTT: true, IsMontgomery: true}}, Value: []ringqp.Poly(evk.Value[i][j])}); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
}
}
// Adds the plaintext (input-key) to the EvaluationKey.
buffQ := kgen.pool.AtLevel(evk.GadgetCiphertext.LevelQ()).GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffQ)
if err := AddPolyTimesGadgetVectorToGadgetCiphertext(skIn, []GadgetCiphertext{evk.GadgetCiphertext}, *kgen.params.RingQP(), *buffQ); err != nil {
if err := AddPolyTimesGadgetVectorToGadgetCiphertext(skIn, []GadgetCiphertext{evk.GadgetCiphertext}, *kgen.params.RingQP(), kgen.buffQ[0]); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
+134 -325
View File
@@ -4,13 +4,11 @@ import (
"bufio"
"fmt"
"io"
"slices"
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/luxdefi/lattice/v5/utils/structs"
)
// SecretKey is a type for generic RLWE secret keys.
@@ -19,7 +17,7 @@ type SecretKey struct {
Value ringqp.Poly
}
// NewSecretKey generates a new [SecretKey] with zero values.
// NewSecretKey generates a new SecretKey with zero values.
func NewSecretKey(params ParameterProvider) *SecretKey {
return &SecretKey{Value: params.GetRLWEParameters().RingQP().NewPoly()}
}
@@ -39,7 +37,7 @@ func (sk SecretKey) LevelP() int {
return sk.Value.P.Level()
}
// CopyNew creates a deep copy of the receiver [SecretKey] and returns it.
// CopyNew creates a deep copy of the receiver secret key and returns it.
func (sk SecretKey) CopyNew() *SecretKey {
return &SecretKey{*sk.Value.CopyNew()}
}
@@ -49,32 +47,32 @@ func (sk SecretKey) BinarySize() (dataLen int) {
return sk.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (sk SecretKey) WriteTo(w io.Writer) (n int64, err error) {
return sk.Value.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (sk *SecretKey) ReadFrom(r io.Reader) (n int64, err error) {
return sk.Value.ReadFrom(r)
}
@@ -85,7 +83,7 @@ func (sk SecretKey) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [SecretKey.MarshalBinary] or [SecretKey.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (sk *SecretKey) UnmarshalBinary(p []byte) (err error) {
return sk.Value.UnmarshalBinary(p)
}
@@ -94,7 +92,7 @@ func (sk *SecretKey) isEncryptionKey() {}
type VectorQP []ringqp.Poly
// NewVectorQP returns a new [PublicKey] with zero values.
// NewVectorQP returns a new PublicKey with zero values.
func NewVectorQP(params ParameterProvider, size, levelQ, levelP int) (v VectorQP) {
rqp := params.GetRLWEParameters().RingQP().AtLevel(levelQ, levelP)
@@ -107,7 +105,7 @@ func NewVectorQP(params ParameterProvider, size, levelQ, levelP int) (v VectorQP
return
}
// LevelQ returns the level of the modulus Q of the first element of the [VectorQP].
// LevelQ returns the level of the modulus Q of the first element of the VectorQP.
// Returns -1 if the size of the vector is zero or has no modulus Q.
func (p VectorQP) LevelQ() int {
if len(p) == 0 {
@@ -116,7 +114,7 @@ func (p VectorQP) LevelQ() int {
return p[0].LevelQ()
}
// LevelP returns the level of the modulus P of the first element of the [VectorQP].
// LevelP returns the level of the modulus P of the first element of the VectorQP.
// Returns -1 if the size of the vector is zero or has no modulus P.
func (p VectorQP) LevelP() int {
if len(p) == 0 {
@@ -125,7 +123,7 @@ func (p VectorQP) LevelP() int {
return p[0].LevelP()
}
// CopyNew creates a deep copy of the target [PublicKey] and returns it.
// CopyNew creates a deep copy of the target PublicKey and returns it.
func (p VectorQP) CopyNew() *VectorQP {
m := make([]ringqp.Poly, len(p))
for i := range p {
@@ -154,33 +152,33 @@ func (p VectorQP) BinarySize() int {
return structs.Vector[ringqp.Poly](p[:]).BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (p VectorQP) WriteTo(w io.Writer) (n int64, err error) {
v := structs.Vector[ringqp.Poly](p[:])
return v.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (p *VectorQP) ReadFrom(r io.Reader) (n int64, err error) {
v := structs.Vector[ringqp.Poly](*p)
n, err = v.ReadFrom(r)
@@ -196,7 +194,7 @@ func (p VectorQP) MarshalBinary() ([]byte, error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [VectorQP.MarshalBinary] or [VectorQP.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (p *VectorQP) UnmarshalBinary(b []byte) error {
_, err := p.ReadFrom(buffer.NewBuffer(b))
return err
@@ -208,7 +206,7 @@ type PublicKey struct {
Value VectorQP
}
// NewPublicKey returns a new [PublicKey] with zero values.
// NewPublicKey returns a new PublicKey with zero values.
func NewPublicKey(params ParameterProvider) (pk *PublicKey) {
p := params.GetRLWEParameters()
return &PublicKey{Value: NewVectorQP(params, 2, p.MaxLevelQ(), p.MaxLevelP())}
@@ -222,7 +220,7 @@ func (p PublicKey) LevelP() int {
return p.Value.LevelP()
}
// CopyNew creates a deep copy of the target [PublicKey] and returns it.
// CopyNew creates a deep copy of the target PublicKey and returns it.
func (p PublicKey) CopyNew() *PublicKey {
return &PublicKey{Value: *p.Value.CopyNew()}
}
@@ -236,32 +234,32 @@ func (p PublicKey) BinarySize() int {
return p.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (p PublicKey) WriteTo(w io.Writer) (n int64, err error) {
return p.Value.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (p *PublicKey) ReadFrom(r io.Reader) (n int64, err error) {
return p.Value.ReadFrom(r)
}
@@ -272,7 +270,7 @@ func (p PublicKey) MarshalBinary() ([]byte, error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [PublicKey.MarshalBinary] or [PublicKey.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (p *PublicKey) UnmarshalBinary(b []byte) error {
return p.Value.UnmarshalBinary(b)
}
@@ -280,8 +278,8 @@ func (p *PublicKey) UnmarshalBinary(b []byte) error {
func (p *PublicKey) isEncryptionKey() {}
// EvaluationKey is a public key indented to be used during the evaluation phase of a homomorphic circuit.
// It provides a one way public and non-interactive re-encryption from a ciphertext encrypted under skIn
// to a ciphertext encrypted under skOut.
// It provides a one way public and non-interactive re-encryption from a ciphertext encrypted under `skIn`
// to a ciphertext encrypted under `skOut`.
//
// Such re-encryption is for example used for:
// - Homomorphic relinearization: re-encryption of a quadratic ciphertext (that requires (1, sk sk^2) to be decrypted)
@@ -292,17 +290,15 @@ func (p *PublicKey) isEncryptionKey() {}
// is used to bring it back to its original key.
type EvaluationKey struct {
GadgetCiphertext
Seed *[32]byte // Must be != nil iff EvaluationKey.IsCompressed() = true
}
type EvaluationKeyParameters struct {
LevelQ *int
LevelP *int
BaseTwoDecomposition *int
Compressed bool
}
func ResolveEvaluationKeyParameters(params Parameters, evkParams []EvaluationKeyParameters) (levelQ, levelP, BaseTwoDecomposition int, compressed bool) {
func ResolveEvaluationKeyParameters(params Parameters, evkParams []EvaluationKeyParameters) (levelQ, levelP, BaseTwoDecomposition int) {
if len(evkParams) != 0 {
if evkParams[0].LevelQ == nil {
levelQ = params.MaxLevelQ()
@@ -319,7 +315,6 @@ func ResolveEvaluationKeyParameters(params Parameters, evkParams []EvaluationKey
if evkParams[0].BaseTwoDecomposition != nil {
BaseTwoDecomposition = *evkParams[0].BaseTwoDecomposition
}
compressed = evkParams[0].Compressed
} else {
levelQ = params.MaxLevelQ()
levelP = params.MaxLevelP()
@@ -328,214 +323,28 @@ func ResolveEvaluationKeyParameters(params Parameters, evkParams []EvaluationKey
return
}
// NewEvaluationKey returns a new [EvaluationKey] with pre-allocated zero-value.
// NewEvaluationKey returns a new EvaluationKey with pre-allocated zero-value.
func NewEvaluationKey(params ParameterProvider, evkParams ...EvaluationKeyParameters) *EvaluationKey {
p := *params.GetRLWEParameters()
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(p, evkParams)
return newEvaluationKey(p, levelQ, levelP, BaseTwoDecomposition, compressed)
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(p, evkParams)
return newEvaluationKey(p, levelQ, levelP, BaseTwoDecomposition)
}
func newEvaluationKey(params Parameters, levelQ, levelP, BaseTwoDecomposition int, compressed bool) *EvaluationKey {
degree := 1
if compressed {
degree = 0
}
return &EvaluationKey{
GadgetCiphertext: *NewGadgetCiphertext(params, degree, levelQ, levelP, BaseTwoDecomposition),
}
func newEvaluationKey(params Parameters, levelQ, levelP, BaseTwoDecomposition int) *EvaluationKey {
return &EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(params, 1, levelQ, levelP, BaseTwoDecomposition)}
}
// IsCompressed indicates whether the [EvaluationKey] is compressed or not.
func (evk EvaluationKey) IsCompressed() bool {
return evk.Degree() == 0
}
// Expand decompresses a compressed [EvaluationKey] of the form (-a*sk + w*P*s' + e) to (-a*sk + w*P*s' + e, a).
// The user can provide a buffer GadgetCiphertext of degree 0 matching the size of the [EvaluationKey].
// If no buffer is provided, the second component will be allocated.
// The method will return an error if:
// - The [EvaluationKey] is not compressed
// - The provided buffer is invalid.
func (evk EvaluationKey) Expand(params ParameterProvider, buffer *GadgetCiphertext) error {
if !evk.IsCompressed() {
return fmt.Errorf("evaluation key is not compressed")
}
if evk.Seed == nil {
return fmt.Errorf("seed is missing")
}
prng, err := sampling.NewKeyedPRNG((*evk.Seed)[:])
if err != nil {
panic(fmt.Errorf("sampling.NewKeyedPRNG: %s", err))
}
levelQ := evk.LevelQ()
levelP := evk.LevelP()
uniformRingQPSampler := ringqp.NewUniformSampler(prng, *params.GetRLWEParameters().RingQP()).AtLevel(levelQ, levelP)
BaseRNSDecompositionVectorSize := evk.BaseRNSDecompositionVectorSize()
BaseTwoDecompositionVectorSize := evk.BaseTwoDecompositionVectorSize()
// Check that the provided buffer is of the correct size
if buffer != nil {
if have := buffer.Degree(); have != 0 {
return fmt.Errorf("invalid buffer, degree should be 0 but is %d", have)
}
if have := buffer.BaseRNSDecompositionVectorSize(); have != BaseRNSDecompositionVectorSize {
return fmt.Errorf("invalid buffer BaseRNSDecompositionVectorSize, should be %d but is %d", have, BaseRNSDecompositionVectorSize)
}
if have := buffer.BaseTwoDecompositionVectorSize(); !slices.Equal(have, BaseTwoDecompositionVectorSize) {
return fmt.Errorf("invalid buffer BaseTwoDecompositionVectorSize, should be %v but is %v", have, BaseTwoDecompositionVectorSize)
}
if have := buffer.LevelQ(); have != levelQ {
return fmt.Errorf("invalid buffer levelQ, should be %d but is %d", levelQ, have)
}
if have := buffer.LevelP(); have != levelP {
return fmt.Errorf("invalid buffer levelP, should be %d but is %d", levelP, have)
}
} else {
buffer = NewGadgetCiphertext(params, 0, levelQ, levelP, evk.BaseTwoDecomposition)
}
// This works because the uniform RingQP sampler is only used to sample 'a'
// during the creation of the compressed evaluation key with no other call to
// the sampler. Hence, both PRNG invocation sequences are equal.
value := make(structs.Matrix[VectorQP], BaseRNSDecompositionVectorSize)
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
value[i] = make([]VectorQP, BaseTwoDecompositionVectorSize[i])
for j := range value[i] {
// Sample 'a' and create the full evaluation key (-a*sk + w*P*s' + e, a).
uniformRingQPSampler.Read(buffer.Value[i][j][0])
evk.Value[i][j] = VectorQP{evk.Value[i][j][0], buffer.Value[i][j][0]}
}
}
return nil
}
// BinarySize returns the serialized size of the object in bytes.
func (evk EvaluationKey) BinarySize() (size int) {
if evk.Seed != nil {
return evk.GadgetCiphertext.BinarySize() + len(*evk.Seed)
}
return evk.GadgetCiphertext.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
func (evk EvaluationKey) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
case buffer.Writer:
var inc int64
if inc, err = evk.GadgetCiphertext.WriteTo(w); err != nil {
return n + inc, err
}
n += inc
if evk.IsCompressed() {
// Sanity check, should not happen unless evk has been manually modified
if evk.Seed == nil {
return n + inc, fmt.Errorf("writing compressed evaluation key: the seed is nil")
}
if inc, err = buffer.Write(w, (*evk.Seed)[:]); err != nil {
return n + inc, err
}
n += inc
}
if err = w.Flush(); err != nil {
return n, err
}
return
default:
return evk.WriteTo(bufio.NewWriter(w))
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
func (evk *EvaluationKey) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
var inc int64
if inc, err = evk.GadgetCiphertext.ReadFrom(r); err != nil {
return n + inc, err
}
n += inc
if evk.IsCompressed() {
var seed [32]byte
if inc, err = buffer.Read(r, seed[:]); err != nil {
return n + inc, err
}
evk.Seed = &seed
n += inc
}
return
default:
return evk.ReadFrom(bufio.NewReader(r))
}
}
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
func (evk EvaluationKey) MarshalBinary() (p []byte, err error) {
buf := buffer.NewBufferSize(evk.BinarySize())
_, err = evk.WriteTo(buf)
return buf.Bytes(), err
}
// UnmarshalBinary decodes a slice of bytes generated by
// [EvaluationKey.MarshalBinary] or [EvaluationKey.WriteTo] on the object.
func (evk *EvaluationKey) UnmarshalBinary(p []byte) (err error) {
_, err = evk.ReadFrom(buffer.NewBuffer(p))
return
}
// CopyNew creates a deep copy of the target [EvaluationKey] and returns it.
// CopyNew creates a deep copy of the target EvaluationKey and returns it.
func (evk EvaluationKey) CopyNew() *EvaluationKey {
return &EvaluationKey{GadgetCiphertext: *evk.GadgetCiphertext.CopyNew()}
}
// RelinearizationKey is type of [EvaluationKey] used for ciphertext multiplication compactness.
// Equal performs a deep equal.
func (evk EvaluationKey) Equal(other *EvaluationKey) bool {
return evk.GadgetCiphertext.Equal(&other.GadgetCiphertext)
}
// RelinearizationKey is type of evaluation key used for ciphertext multiplication compactness.
// The Relinearization key encrypts s^{2} under s and is used to homomorphically re-encrypt the
// degree 2 term of a ciphertext (the term that decrypt with s^{2}) into a degree 1 term
// (a term that decrypts with s).
@@ -543,19 +352,15 @@ type RelinearizationKey struct {
EvaluationKey
}
// NewRelinearizationKey allocates a new [RelinearizationKey] with zero coefficients.
// NewRelinearizationKey allocates a new RelinearizationKey with zero coefficients.
func NewRelinearizationKey(params ParameterProvider, evkParams ...EvaluationKeyParameters) *RelinearizationKey {
p := *params.GetRLWEParameters()
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(p, evkParams)
return newRelinearizationKey(p, levelQ, levelP, BaseTwoDecomposition, compressed)
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(p, evkParams)
return newRelinearizationKey(p, levelQ, levelP, BaseTwoDecomposition)
}
func newRelinearizationKey(params Parameters, levelQ, levelP, BaseTwoDecomposition int, compressed bool) *RelinearizationKey {
degree := 1
if compressed {
degree = 0
}
return &RelinearizationKey{EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(params, degree, levelQ, levelP, BaseTwoDecomposition)}}
func newRelinearizationKey(params Parameters, levelQ, levelP, BaseTwoDecomposition int) *RelinearizationKey {
return &RelinearizationKey{EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(params, 1, levelQ, levelP, BaseTwoDecomposition)}}
}
// CopyNew creates a deep copy of the object and returns it.
@@ -563,12 +368,17 @@ func (rlk RelinearizationKey) CopyNew() *RelinearizationKey {
return &RelinearizationKey{EvaluationKey: *rlk.EvaluationKey.CopyNew()}
}
// GaloisKey is a type of [EvaluationKey] used to evaluate automorphisms on ciphertext.
// Equal performs a deep equal.
func (rlk RelinearizationKey) Equal(other *RelinearizationKey) bool {
return rlk.EvaluationKey.Equal(&other.EvaluationKey)
}
// GaloisKey is a type of evaluation key used to evaluate automorphisms on ciphertext.
// An automorphism pi: X^{i} -> X^{i*GaloisElement} changes the key under which the
// ciphertext is encrypted from s to pi(s). Thus, the ciphertext must be re-encrypted
// from pi(s) to s to ensure correctness, which is done with the corresponding GaloisKey.
//
// Lattigo implements automorphisms differently than the usual way (which is to first
// Lattice implements automorphisms differently than the usual way (which is to first
// apply the automorphism and then the evaluation key). Instead the order of operations
// is reversed, the GaloisKey for pi^{-1} is evaluated on the ciphertext, outputting a
// ciphertext encrypted under pi^{-1}(s), and then the automorphism pi is applied. This
@@ -580,26 +390,25 @@ type GaloisKey struct {
EvaluationKey
}
// NewGaloisKey allocates a new [GaloisKey] with zero coefficients and GaloisElement set to zero.
// NewGaloisKey allocates a new GaloisKey with zero coefficients and GaloisElement set to zero.
func NewGaloisKey(params ParameterProvider, evkParams ...EvaluationKeyParameters) *GaloisKey {
p := *params.GetRLWEParameters()
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(p, evkParams)
return newGaloisKey(p, levelQ, levelP, BaseTwoDecomposition, compressed)
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(p, evkParams)
return newGaloisKey(p, levelQ, levelP, BaseTwoDecomposition)
}
func newGaloisKey(params Parameters, levelQ, levelP, BaseTwoDecomposition int, compressed bool) *GaloisKey {
degree := 1
if compressed {
degree = 0
}
func newGaloisKey(params Parameters, levelQ, levelP, BaseTwoDecomposition int) *GaloisKey {
return &GaloisKey{
EvaluationKey: EvaluationKey{
GadgetCiphertext: *NewGadgetCiphertext(params, degree, levelQ, levelP, BaseTwoDecomposition),
},
NthRoot: params.GetRLWEParameters().RingQ().NthRoot(),
EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(params, 1, levelQ, levelP, BaseTwoDecomposition)},
NthRoot: params.GetRLWEParameters().RingQ().NthRoot(),
}
}
// Equal returns true if the two objects are equal.
func (gk GaloisKey) Equal(other *GaloisKey) bool {
return gk.GaloisElement == other.GaloisElement && gk.NthRoot == other.NthRoot && cmp.Equal(gk.EvaluationKey, other.EvaluationKey)
}
// CopyNew creates a deep copy of the object and returns it
func (gk GaloisKey) CopyNew() *GaloisKey {
return &GaloisKey{
@@ -614,17 +423,17 @@ func (gk GaloisKey) BinarySize() (size int) {
return gk.EvaluationKey.BinarySize() + 16
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (gk GaloisKey) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
case buffer.Writer:
@@ -656,17 +465,17 @@ func (gk GaloisKey) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (gk *GaloisKey) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
@@ -705,14 +514,14 @@ func (gk GaloisKey) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [GaloisKey.MarshalBinary] or [GaloisKey.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (gk *GaloisKey) UnmarshalBinary(p []byte) (err error) {
_, err = gk.ReadFrom(buffer.NewBuffer(p))
return
}
// EvaluationKeySet is an interface implementing methods
// to load the [RelinearizationKey] and the [GaloisKey] in the [Evaluator].
// to load the RelinearizationKey and GaloisKeys in the Evaluator.
// Implementations of this interface must be safe for concurrent use.
type EvaluationKeySet interface {
@@ -727,13 +536,13 @@ type EvaluationKeySet interface {
GetRelinearizationKey() (evk *RelinearizationKey, err error)
}
// MemEvaluationKeySet is a basic in-memory implementation of the [EvaluationKeySet] interface.
// MemEvaluationKeySet is a basic in-memory implementation of the EvaluationKeySet interface.
type MemEvaluationKeySet struct {
RelinearizationKey *RelinearizationKey
GaloisKeys structs.Map[uint64, GaloisKey]
}
// NewMemEvaluationKeySet returns a new [EvaluationKeySet] with the provided [RelinearizationKey] and the [GaloisKey].
// NewMemEvaluationKeySet returns a new EvaluationKeySet with the provided RelinearizationKey and GaloisKeys.
func NewMemEvaluationKeySet(relinKey *RelinearizationKey, galoisKeys ...*GaloisKey) (eks *MemEvaluationKeySet) {
eks = &MemEvaluationKeySet{GaloisKeys: map[uint64]*GaloisKey{}}
eks.RelinearizationKey = relinKey
@@ -743,7 +552,7 @@ func NewMemEvaluationKeySet(relinKey *RelinearizationKey, galoisKeys ...*GaloisK
return eks
}
// GetGaloisKey retrieves the [GaloisKey] for the automorphism X^{i} -> X^{i*galEl}.
// GetGaloisKey retrieves the Galois key for the automorphism X^{i} -> X^{i*galEl}.
func (evk MemEvaluationKeySet) GetGaloisKey(galEl uint64) (gk *GaloisKey, err error) {
var ok bool
if gk, ok = evk.GaloisKeys[galEl]; !ok {
@@ -772,7 +581,7 @@ func (evk MemEvaluationKeySet) GetGaloisKeysList() (galEls []uint64) {
return
}
// GetRelinearizationKey retrieves the [RelinearizationKey].
// GetRelinearizationKey retrieves the RelinearizationKey.
func (evk MemEvaluationKeySet) GetRelinearizationKey() (rk *RelinearizationKey, err error) {
if evk.RelinearizationKey != nil {
return evk.RelinearizationKey, nil
@@ -796,17 +605,17 @@ func (evk MemEvaluationKeySet) BinarySize() (size int) {
return
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (evk MemEvaluationKeySet) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
case buffer.Writer:
@@ -860,17 +669,17 @@ func (evk MemEvaluationKeySet) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (evk *MemEvaluationKeySet) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
@@ -932,7 +741,7 @@ func (evk MemEvaluationKeySet) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [MemEvaluationKeySet.MarshalBinary] or [MemEvaluationKeySet.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (evk *MemEvaluationKeySet) UnmarshalBinary(p []byte) (err error) {
_, err = evk.ReadFrom(buffer.NewBuffer(p))
return
+59 -81
View File
@@ -7,8 +7,8 @@ import (
"math/big"
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
// MetaData is a struct storing metadata.
@@ -31,7 +31,7 @@ func (m MetaData) BinarySize() int {
return 44 + m.PlaintextMetaData.BinarySize() + m.CiphertextMetaData.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
func (m MetaData) WriteTo(w io.Writer) (int64, error) {
if p, err := m.MarshalBinary(); err != nil {
@@ -45,17 +45,17 @@ func (m MetaData) WriteTo(w io.Writer) (int64, error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (m *MetaData) ReadFrom(r io.Reader) (int64, error) {
p := make([]byte, m.BinarySize())
if n, err := r.Read(p); err != nil {
@@ -115,10 +115,14 @@ type PlaintextMetaData struct {
// in such a way that product in R[X]/(X^N+1) acts as a point-wise multiplication
// in the plaintext space.
IsBatched bool
}
// IsBitReversed is a flag indicating if the underlying plaintext is
// bit-reversed. This can be true for both batch and non-batched plaintexts.
IsBitReversed bool
// CiphertextMetaData is a struct storing metadata related to the ciphertext.
type CiphertextMetaData struct {
// IsNTT is a flag indicating if the ciphertext is in the NTT domain.
IsNTT bool
// IsMontgomery is a flag indicating if the ciphertext is in the Montgomery domain.
IsMontgomery bool
}
// Slots returns the total number of slots that the plaintext holds.
@@ -142,27 +146,33 @@ func (m PlaintextMetaData) LogScale() float64 {
func (m *PlaintextMetaData) Equal(other *PlaintextMetaData) (res bool) {
res = cmp.Equal(&m.Scale, &other.Scale)
res = res && m.IsBatched == other.IsBatched
res = res && m.IsBitReversed == other.IsBitReversed
res = res && m.LogDimensions == other.LogDimensions
return
}
// Equal returns true if two MetaData structs are identical.
func (m *CiphertextMetaData) Equal(other *CiphertextMetaData) (res bool) {
res = m.IsNTT == other.IsNTT
res = res && m.IsMontgomery == other.IsMontgomery
return
}
// BinarySize returns the size in bytes that the object once marshalled into a binary form.
func (m PlaintextMetaData) BinarySize() int {
return 84 + m.Scale.BinarySize()
return 61 + m.Scale.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (m PlaintextMetaData) WriteTo(w io.Writer) (int64, error) {
if p, err := m.MarshalBinary(); err != nil {
return 0, err
@@ -175,17 +185,17 @@ func (m PlaintextMetaData) WriteTo(w io.Writer) (int64, error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (m *PlaintextMetaData) ReadFrom(r io.Reader) (int64, error) {
p := make([]byte, m.BinarySize())
if n, err := r.Read(p); err != nil {
@@ -198,25 +208,18 @@ func (m *PlaintextMetaData) ReadFrom(r io.Reader) (int64, error) {
func (m PlaintextMetaData) MarshalJSON() (p []byte, err error) {
var IsBatched uint8
if m.IsBatched {
IsBatched = 1
}
var IsBitReversed uint8
if m.IsBitReversed {
IsBitReversed = 1
}
aux := &struct {
Scale Scale
IsBatched string
IsBitReversed string
LogDimensions [2]string
}{
Scale: m.Scale,
IsBatched: fmt.Sprintf("0x%02x", IsBatched),
IsBitReversed: fmt.Sprintf("0x%02x", IsBitReversed),
/* #nosec G115 -- Rows and Cols cannot be negative if valid */
LogDimensions: [2]string{fmt.Sprintf("0x%02x", uint8(m.LogDimensions.Rows)), fmt.Sprintf("0x%02x", uint8(m.LogDimensions.Cols))},
}
@@ -234,7 +237,6 @@ func (m *PlaintextMetaData) UnmarshalJSON(p []byte) (err error) {
aux := &struct {
Scale Scale
IsBatched string
IsBitReversed string
LogDimensions [2]string
}{}
@@ -252,14 +254,6 @@ func (m *PlaintextMetaData) UnmarshalJSON(p []byte) (err error) {
m.IsBatched = false
}
if y, err := hexconv(aux.IsBitReversed); err != nil {
return err
} else if y == 1 {
m.IsBitReversed = true
} else {
m.IsBitReversed = false
}
logRows, err := hexconv(aux.LogDimensions[0])
if err != nil {
@@ -272,49 +266,33 @@ func (m *PlaintextMetaData) UnmarshalJSON(p []byte) (err error) {
return err
}
/* #nosec G115 -- logRows and logCols are < 256 if valid */
m.LogDimensions = ring.Dimensions{Rows: int(int8(logRows)), Cols: int(int8(logCols))}
return
}
// UnmarshalBinary decodes a slice of bytes generated by
// [PlaintextMetaData.MarshalBinary] or [PlaintextMetaData.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (m *PlaintextMetaData) UnmarshalBinary(p []byte) (err error) {
return m.UnmarshalJSON(p)
}
// CiphertextMetaData is a struct storing metadata related to the ciphertext.
type CiphertextMetaData struct {
// IsNTT is a flag indicating if the ciphertext is in the NTT domain.
IsNTT bool
// IsMontgomery is a flag indicating if the ciphertext is in the Montgomery domain.
IsMontgomery bool
}
// Equal returns true if two MetaData structs are identical.
func (m *CiphertextMetaData) Equal(other *CiphertextMetaData) (res bool) {
res = m.IsNTT == other.IsNTT
res = res && m.IsMontgomery == other.IsMontgomery
return
}
// BinarySize returns the size in bytes that the object once marshalled into a binary form.
func (m *CiphertextMetaData) BinarySize() int {
return 38
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (m *CiphertextMetaData) WriteTo(w io.Writer) (int64, error) {
if p, err := m.MarshalBinary(); err != nil {
return 0, err
@@ -327,17 +305,17 @@ func (m *CiphertextMetaData) WriteTo(w io.Writer) (int64, error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (m *CiphertextMetaData) ReadFrom(r io.Reader) (int64, error) {
p := make([]byte, m.BinarySize())
if n, err := r.Read(p); err != nil {
@@ -400,7 +378,7 @@ func (m *CiphertextMetaData) UnmarshalJSON(p []byte) (err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [CiphertextMetaData.MarshalBinary] or [CiphertextMetaData.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (m *CiphertextMetaData) UnmarshalBinary(p []byte) (err error) {
return m.UnmarshalJSON(p)
}
+44 -147
View File
@@ -1,20 +1,16 @@
package rlwe
import (
"bufio"
"encoding/json"
"fmt"
"io"
"math"
"math/big"
"math/bits"
"slices"
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
)
// MaxLogN is the log2 of the largest supported polynomial modulus degree.
@@ -27,7 +23,7 @@ const MinLogN = 4
const MaxModuliSize = 60
// GaloisGen is an integer of order N=2^d modulo M=2N and that spans Z_M with the integer -1.
// The j-th ring automorphism takes the root zeta to zeta^(5^j).
// The j-th ring automorphism takes the root zeta to zeta^(5j).
const GaloisGen uint64 = ring.GaloisGen
type DistributionLiteral interface{}
@@ -38,7 +34,7 @@ type ParameterProvider interface {
// ParametersLiteral is a literal representation of RLWE parameters. It has public fields and
// is used to express unchecked user-defined parameters literally into Go programs.
// The [NewParametersFromLiteral] function is used to generate the actual checked parameters
// The NewParametersFromLiteral function is used to generate the actual checked parameters
// from the literal representation.
//
// Users must set the polynomial degree (LogN) and the coefficient modulus, by either setting
@@ -50,7 +46,7 @@ type ParameterProvider interface {
// - the error variance (Sigma) and secrets' density (H) and the ring type (RingType).
//
// If left unset, standard default values for these field are substituted at
// parameter creation (see [NewParametersFromLiteral]).
// parameter creation (see NewParametersFromLiteral).
type ParametersLiteral struct {
LogN int
LogNthRoot int `json:",omitempty"`
@@ -66,7 +62,7 @@ type ParametersLiteral struct {
}
// Parameters represents a set of generic RLWE parameters. Its fields are private and
// immutable. See [ParametersLiteral] for user-specified parameters.
// immutable. See ParametersLiteral for user-specified parameters.
type Parameters struct {
logN int
qi []uint64
@@ -81,7 +77,7 @@ type Parameters struct {
}
// NewParameters returns a new set of generic RLWE parameters from the given ring degree logn, moduli q and p, and
// error distribution Xs (secret) and Xe (error). It returns the empty parameters [Parameters]{} and a non-nil error if the
// error distribution Xs (secret) and Xe (error). It returns the empty parameters Parameters{} and a non-nil error if the
// specified parameters are invalid.
func NewParameters(logn int, q, p []uint64, xs, xe DistributionLiteral, ringType ring.Type, defaultScale Scale, NTTFlag bool) (params Parameters, err error) {
@@ -90,7 +86,7 @@ func NewParameters(logn int, q, p []uint64, xs, xe DistributionLiteral, ringType
lenP = len(p)
}
if err = checkSizeParams(logn); err != nil {
if err = checkSizeParams(logn, len(q), lenP); err != nil {
return Parameters{}, err
}
@@ -125,6 +121,9 @@ func NewParameters(logn int, q, p []uint64, xs, xe DistributionLiteral, ringType
default:
return Parameters{}, fmt.Errorf("secret distribution type must be Ternary or DiscretGaussian but is %T", xs)
}
if err != nil {
return Parameters{}, err
}
switch xe := xe.(type) {
case ring.Ternary, ring.DiscreteGaussian:
@@ -132,6 +131,9 @@ func NewParameters(logn int, q, p []uint64, xs, xe DistributionLiteral, ringType
default:
return Parameters{}, fmt.Errorf("error distribution type must be Ternary or DiscretGaussian but is %T", xe)
}
if err != nil {
return Parameters{}, err
}
var warning error
if params.XsHammingWeight() == 0 {
@@ -149,16 +151,16 @@ func NewParameters(logn int, q, p []uint64, xs, xe DistributionLiteral, ringType
return params, warning
}
// NewParametersFromLiteral instantiate a set of generic RLWE parameters from a [ParametersLiteral] specification.
// NewParametersFromLiteral instantiate a set of generic RLWE parameters from a ParametersLiteral specification.
// It returns the empty parameters Parameters{} and a non-nil error if the specified parameters are invalid.
//
// If the moduli chain is specified through the LogQ and LogP fields, the method generates a moduli chain matching
// the specified sizes (see [GenModuli]).
// the specified sizes (see `GenModuli`).
//
// If the secrets' density parameter (H) is left unset, its value is set to 2^(paramDef.LogN-1) to match
// the standard ternary distribution.
//
// If the error variance is left unset, its value is set to [DefaultError].
// If the error variance is left unset, its value is set to `DefaultError`.
//
// If the RingType is left unset, the default value is ring.Standard.
func NewParametersFromLiteral(paramDef ParametersLiteral) (params Parameters, err error) {
@@ -169,7 +171,7 @@ func NewParametersFromLiteral(paramDef ParametersLiteral) (params Parameters, er
if paramDef.Xe == nil {
// prevents the zero value of ParameterLiteral to result in a noise-less parameter instance.
// Users should use the NewParameters method to explicitly create noiseless instances.
// Users should use the NewParameters method to explicitely create noiseless instances.
paramDef.Xe = DefaultXe
}
@@ -178,48 +180,28 @@ func NewParametersFromLiteral(paramDef ParametersLiteral) (params Parameters, er
paramDef.DefaultScale = s
}
// Invalid moduli configurations: do not allow empty Q and LogQ as well double-set log and non-log fields.
if paramDef.Q == nil && paramDef.LogQ == nil {
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: both Q and LogQ fields are empty")
}
if paramDef.Q != nil && paramDef.LogQ != nil {
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: both Q and LogQ fields are set")
}
if paramDef.P != nil && paramDef.LogP != nil {
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: both P and LogP fields are set")
}
var (
q []uint64 = nil
p []uint64 = nil
)
// In case a log prime field is set for either Q or P, the corresponding primes need to be generated.
// Note that GenModuli returns nil for Q if logQ == nil, and nil for P if logP == nil.
if paramDef.LogQ != nil || paramDef.LogP != nil {
switch {
case paramDef.Q != nil && paramDef.LogQ == nil:
return NewParameters(paramDef.LogN, paramDef.Q, paramDef.P, paramDef.Xs, paramDef.Xe, paramDef.RingType, paramDef.DefaultScale, paramDef.NTTFlag)
case paramDef.LogQ != nil && paramDef.Q == nil:
var q, p []uint64
switch paramDef.RingType {
case ring.Standard:
LogNthRoot := utils.Max(paramDef.LogN+1, paramDef.LogNthRoot)
q, p, err = GenModuli(LogNthRoot, paramDef.LogQ, paramDef.LogP) //2NthRoot
if err != nil {
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: unable to generate standard ring moduli: %w", err)
}
case ring.ConjugateInvariant:
LogNthRoot := utils.Max(paramDef.LogN+2, paramDef.LogNthRoot)
q, p, err = GenModuli(LogNthRoot, paramDef.LogQ, paramDef.LogP) //4NthRoot
if err != nil {
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: unable to generate conjugate invariant ring moduli: %w", err)
}
default:
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: invalid ring.Type, must be ring.ConjugateInvariant or ring.Standard")
}
if err != nil {
return Parameters{}, err
}
return NewParameters(paramDef.LogN, q, p, paramDef.Xs, paramDef.Xe, paramDef.RingType, paramDef.DefaultScale, paramDef.NTTFlag)
default:
return Parameters{}, fmt.Errorf("rlwe.NewParametersFromLiteral: invalid parameter literal")
}
// Use the user-provided primes if specified.
if q == nil {
q = paramDef.Q
}
if p == nil {
p = paramDef.P
}
return NewParameters(paramDef.LogN, q, p, paramDef.Xs, paramDef.Xe, paramDef.RingType, paramDef.DefaultScale, paramDef.NTTFlag)
}
// StandardParameters returns a RLWE parameter set that corresponds to the
@@ -288,7 +270,6 @@ func (p Parameters) LogN() int {
// NthRoot returns the NthRoot of the ring.
func (p Parameters) NthRoot() int {
if p.RingQ() != nil {
/* #nosec G115 -- NthRoot of valid [ring.Ring] is positive */
return int(p.RingQ().NthRoot())
}
@@ -297,7 +278,6 @@ func (p Parameters) NthRoot() int {
// LogNthRoot returns the log2(NthRoot) of the ring.
func (p Parameters) LogNthRoot() int {
/* #nosec G115 -- NthRoot is ensured to be greater than 0 */
return bits.Len64(uint64(p.NthRoot() - 1))
}
@@ -490,7 +470,7 @@ func (p Parameters) LogP() (logp float64) {
// LogPi returns the round(log2) of each primes of the modulus P.
func (p Parameters) LogPi() (logpi []int) {
pi := p.P()
pi := p.Q()
logpi = make([]int, len(pi))
for i := range pi {
logpi[i] = int(math.Round(math.Log2(float64(pi[i]))))
@@ -550,21 +530,15 @@ func (p Parameters) BaseRNSDecompositionVectorSize(levelQ, levelP int) int {
}
// QiOverflowMargin returns floor(2^64 / max(Qi)), i.e. the number of times elements of Z_max{Qi} can
// be added together before overflowing 2^64. The function returns -1 if the moduli array is empty.
// be added together before overflowing 2^64.
func (p Parameters) QiOverflowMargin(level int) int {
if len(p.qi) == 0 {
return -1
}
return int(math.Exp2(64) / float64(slices.Max(p.qi[:level+1])))
return int(math.Exp2(64) / float64(utils.MaxSlice(p.qi[:level+1])))
}
// PiOverflowMargin returns floor(2^64 / max(Pi)), i.e. the number of times elements of Z_max{Pi} can
// be added together before overflowing 2^64. The function returns -1 if the moduli array is empty.
// be added together before overflowing 2^64.
func (p Parameters) PiOverflowMargin(level int) int {
if len(p.pi) == 0 {
return -1
}
return int(math.Exp2(64) / float64(slices.Max(p.pi[:level+1])))
return int(math.Exp2(64) / float64(utils.MaxSlice(p.pi[:level+1])))
}
// GaloisElements takes a list of integers k and returns the list [GaloisGen^{k[i]} mod NthRoot, ...].
@@ -578,7 +552,6 @@ func (p Parameters) GaloisElements(k []int) (galEls []uint64) {
// GaloisElement takes an integer k and returns GaloisGen^{k} mod NthRoot.
func (p Parameters) GaloisElement(k int) uint64 {
/* #nosec G115 -- implicit reduction modulo 2^64 */
return ring.ModExp(GaloisGen, uint64(k)&(p.ringQ.NthRoot()-1), p.ringQ.NthRoot())
}
@@ -612,7 +585,6 @@ func (p Parameters) SolveDiscreteLogGaloisElement(galEl uint64) (k int) {
}
if x == 1 {
/* #nosec G115 -- kuint is ensured to be smaller than NthRoot */
return int(kuint)
}
@@ -635,25 +607,22 @@ func (p Parameters) Equal(other *Parameters) (res bool) {
}
// MarshalBinary returns a []byte representation of the parameter set.
// This representation corresponds to the [Parameters.MarshalJSON] representation.
// This representation corresponds to the MarshalJSON representation.
func (p Parameters) MarshalBinary() ([]byte, error) {
buf := buffer.NewBufferSize(p.BinarySize())
_, err := p.WriteTo(buf)
return buf.Bytes(), err
return p.MarshalJSON()
}
// UnmarshalBinary decodes a slice of bytes on the target Parameters.
func (p *Parameters) UnmarshalBinary(data []byte) (err error) {
_, err = p.ReadFrom(buffer.NewBuffer(data))
return
return p.UnmarshalJSON(data)
}
// MarshalJSON returns a JSON representation of this parameter set. See Marshal from the [encoding/json] package.
// MarshalJSON returns a JSON representation of this parameter set. See `Marshal` from the `encoding/json` package.
func (p Parameters) MarshalJSON() ([]byte, error) {
return json.Marshal(p.ParametersLiteral())
}
// UnmarshalJSON reads a JSON representation of a parameter set into the receiver Parameter. See Unmarshal from the [encoding/json] package.
// UnmarshalJSON reads a JSON representation of a parameter set into the receiver Parameter. See `Unmarshal` from the `encoding/json` package.
func (p *Parameters) UnmarshalJSON(data []byte) (err error) {
var params ParametersLiteral
if err = json.Unmarshal(data, &params); err != nil {
@@ -663,80 +632,10 @@ func (p *Parameters) UnmarshalJSON(data []byte) (err error) {
return
}
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
func (p Parameters) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
case buffer.Writer:
bytes, err := p.MarshalJSON()
if err != nil {
return 0, err
}
if n, err = buffer.WriteAsUint32(w, len(bytes)); err != nil {
return n, fmt.Errorf("buffer.WriteAsUint32[int]: %w", err)
}
var inc int
if inc, err = w.Write(bytes); err != nil {
return int64(n), fmt.Errorf("io.Write.Write: %w", err)
}
n += int64(inc)
return n, w.Flush()
default:
return p.WriteTo(bufio.NewWriter(w))
}
}
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
func (p *Parameters) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
var size int
if n, err = buffer.ReadAsUint32(r, &size); err != nil {
return int64(n), fmt.Errorf("buffer.ReadAsUint64[int]: %w", err)
}
bytes := make([]byte, size)
var inc int
if inc, err = r.Read(bytes); err != nil {
return n + int64(inc), fmt.Errorf("io.Reader.Read: %w", err)
}
return n + int64(inc), p.UnmarshalJSON(bytes)
default:
return p.ReadFrom(bufio.NewReader(r))
}
}
// BinarySize returns size in bytes of the marshalled [Parameters] object.
func (p Parameters) BinarySize() int {
// XXX: Byte size is hard to predict without marshalling.
b, _ := p.MarshalJSON()
return 4 + len(b)
}
// CheckModuli checks that the provided q and p correspond to a valid moduli chain.
func CheckModuli(q, p []uint64) error {
for i, qi := range q {
/* #nosec G115 -- error is returned if integer overflow conversion */
if uint64(bits.Len64(qi)-1) > MaxModuliSize+1 {
return fmt.Errorf("a Qi bit-size (i=%d) is larger than %d", i, MaxModuliSize)
}
@@ -751,7 +650,6 @@ func CheckModuli(q, p []uint64) error {
if p != nil {
for i, pi := range p {
/* #nosec G115 -- error is triggered if integer overflow conversion */
if uint64(bits.Len64(pi)-1) > MaxModuliSize+2 {
return fmt.Errorf("a Pi bit-size (i=%d) is larger than %d", i, MaxModuliSize)
}
@@ -780,7 +678,7 @@ func (p Parameters) UnpackLevelParams(args []int) (levelQ, levelP int) {
}
}
func checkSizeParams(logN int) error {
func checkSizeParams(logN int, lenQ, lenP int) error {
if logN > MaxLogN {
return fmt.Errorf("logN=%d is larger than MaxLogN=%d", logN, MaxLogN)
}
@@ -810,7 +708,7 @@ func checkModuliLogSize(logQ, logP []int) error {
// GenModuli generates a valid moduli chain from the provided moduli sizes.
func GenModuli(LogNthRoot int, logQ, logP []int) (q, p []uint64, err error) {
if err = checkSizeParams(logN); err != nil {
if err = checkSizeParams(logN, len(logQ), len(logP)); err != nil {
return
}
@@ -832,7 +730,6 @@ func GenModuli(LogNthRoot int, logQ, logP []int) (q, p []uint64, err error) {
primes := make(map[int][]uint64)
for bitsize, value := range primesbitlen {
/* #nosec G115 -- bitsize cannot be negative */
g := ring.NewNTTFriendlyPrimesGenerator(uint64(bitsize), uint64(1<<LogNthRoot))
if bitsize == 61 {
+15 -15
View File
@@ -3,8 +3,8 @@ package rlwe
import (
"io"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// Plaintext is a common base type for RLWE plaintexts.
@@ -13,16 +13,16 @@ type Plaintext struct {
Value ring.Poly
}
// NewPlaintext creates a new [Plaintext] at the specified level from the parameters.
// NewPlaintext creates a new Plaintext at level `level` from the parameters.
func NewPlaintext(params ParameterProvider, level ...int) (pt *Plaintext) {
op := *NewElement(params, 0, level...)
return &Plaintext{Element: op, Value: op.Value[0]}
}
// NewPlaintextAtLevelFromPoly constructs a new [Plaintext] at a specific level
// NewPlaintextAtLevelFromPoly constructs a new Plaintext at a specific level
// where the message is set to the passed poly. No checks are performed on poly and
// the returned [Plaintext] will share its backing array of coefficients.
// Returned plaintext's [MetaData] is allocated but empty.
// the returned Plaintext will share its backing array of coefficients.
// Returned plaintext's MetaData is allocated but empty.
func NewPlaintextAtLevelFromPoly(level int, poly ring.Poly) (pt *Plaintext, err error) {
Element, err := NewElementAtLevelFromPoly(level, []ring.Poly{poly})
if err != nil {
@@ -34,7 +34,7 @@ func NewPlaintextAtLevelFromPoly(level int, poly ring.Poly) (pt *Plaintext, err
return &Plaintext{Element: *Element, Value: Element.Value[0]}, nil
}
// Copy copies the supplied plaintext value into the receiver plaintext.
// Copy copies the `other` plaintext value into the receiver plaintext.
func (pt Plaintext) Copy(other *Plaintext) {
pt.Element.Copy(&other.Element)
pt.Value = other.Element.Value[0]
@@ -52,24 +52,24 @@ func (pt Plaintext) Equal(other *Plaintext) bool {
return pt.Element.Equal(&other.Element) && pt.Value.Equal(&other.Value)
}
// NewPlaintextRandom generates a new uniformly distributed [Plaintext].
// NewPlaintextRandom generates a new uniformly distributed Plaintext.
func NewPlaintextRandom(prng sampling.PRNG, params ParameterProvider, level int) (pt *Plaintext) {
pt = NewPlaintext(params, level)
PopulateElementRandom(prng, params, pt.El())
return
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap io.Reader in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (pt *Plaintext) ReadFrom(r io.Reader) (n int64, err error) {
if n, err = pt.Element.ReadFrom(r); err != nil {
return
+1 -10
View File
@@ -6,7 +6,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/utils"
)
func BenchmarkRLWE(b *testing.B) {
@@ -143,13 +143,4 @@ func benchEvaluator(tc *TestContext, bpw2 int, b *testing.B) {
}
})
}
b.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), bpw2, "Evaluator/BuffQPPool"), func(b *testing.B) {
poolQP := NewPool(params.RingQP())
for i := 0; i < b.N; i++ {
pol := poolQP.GetBuffPolyQP()
poolQP.RecycleBuffPolyQP(pol)
}
})
}
+243 -264
View File
@@ -1,7 +1,6 @@
package rlwe
import (
"encoding/base64"
"encoding/json"
"flag"
"fmt"
@@ -9,16 +8,14 @@ import (
"runtime"
"testing"
"golang.org/x/crypto/blake2b"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/buffer"
"github.com/luxdefi/lattice/v5/utils/sampling"
"github.com/luxdefi/lattice/v5/utils/structs"
)
var flagParamString = flag.String("params", "", "specify the test cryptographic parameters as a JSON string. Overrides -short and -long.")
@@ -34,90 +31,6 @@ func testString(params Parameters, levelQ, levelP, bpw2 int, opname string) stri
params.RingType())
}
// TestRLWEConstSerialization test detects (fails) if the serialization of
// [PublicKey], [SecretKey], [Ciphertext], [GaloisKey], [MemEvaluationKeySet] has changed.
// If such a modification is intended, this test must be updated and users notified s.t.
// old serialized objects can be converted to the new format.
func TestRLWEConstSerialization(t *testing.T) {
// Note: changing nbIteration will change the expected value
const nbIteration = 10
const expected = "1MTZP69YciXe+YjFnQbiUJIz7/d/h78atA/jaGKQhhc="
var err error
defaultParamsLiteral := testInsecure
seedKeyGen := []byte{'l', 'a', 't'}
seedEnc := []byte{'t', 'i', 'g', 'o'}
hash, err := blake2b.New(32, nil)
require.Nil(t, err)
for _, paramsLit := range defaultParamsLiteral[:] {
for _, NTTFlag := range []bool{true, false}[:] {
for _, RingType := range []ring.Type{ring.Standard, ring.ConjugateInvariant}[:] {
paramsLit.NTTFlag = NTTFlag
paramsLit.RingType = RingType
var params Parameters
if params, err = NewParametersFromLiteral(paramsLit.ParametersLiteral); err != nil {
t.Fatal(err)
}
detTC, err := NewDeterministicTestContext(params, seedKeyGen, seedEnc)
require.Nil(t, err)
for i := 0; i < nbIteration; i++ {
// Add marshalled (sk, pk) to the hash input
sk, pk := detTC.kgen.GenKeyPairNew()
skBytes, err := sk.MarshalBinary()
hash.Write(skBytes)
require.Nil(t, err)
pkBytes, err := pk.MarshalBinary()
require.Nil(t, err)
hash.Write(pkBytes)
// Add marshalled GaloisKey to the hash input
galEl1 := params.GaloisElement(-1)
galEl2 := params.GaloisElement(3)
galKey1 := detTC.kgen.GenGaloisKeyNew(galEl1, sk)
galKey2 := detTC.kgen.GenGaloisKeyNew(galEl2, sk, EvaluationKeyParameters{Compressed: true})
galKeyBytes, err := galKey1.MarshalBinary()
require.Nil(t, err)
hash.Write(galKeyBytes)
galKeyBytes, err = galKey2.MarshalBinary()
require.Nil(t, err)
hash.Write(galKeyBytes)
// Add marshalled MemEvaluationKeySet to the hash input
relinKey := detTC.kgen.GenRelinearizationKeyNew(sk)
evk := NewMemEvaluationKeySet(relinKey, galKey1, galKey2)
evkBytes, err := evk.MarshalBinary()
require.Nil(t, err)
hash.Write(evkBytes)
// Add marshalled MemEvaluationKeySet to the hash input
ct := NewCiphertext(params, 1, params.MaxLevel())
pt := genPlaintext(params, params.MaxLevel(), (1<<int(params.LogQ()))-1)
detTC.enc.Encrypt(pt, ct)
ctBytes, err := ct.MarshalBinary()
require.Nil(t, err)
hash.Write(ctBytes)
ctJSON, err := ct.MarshalJSON()
require.Nil(t, err)
hash.Write(ctJSON)
}
}
}
}
digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))
// In case the value expected must be updated, uncomment to print the new expected value:
// fmt.Println(digest)
require.Equal(t, expected, digest)
}
func TestRLWE(t *testing.T) {
var err error
@@ -236,108 +149,6 @@ func testUserDefinedParameters(t *testing.T) {
require.Equal(t, paramsWithBadDist, Parameters{})
})
// test valid/invalid configurations of prime fields
t.Run("Parameters/NewParametersFromLiteral", func(t *testing.T) {
Q := []uint64{0x200000440001, 0x7fff80001, 0x800280001, 0x7ffd80001, 0x7ffc80001}
P := []uint64{0x3ffffffb80001, 0x4000000800001}
logQ := []int{55, 40, 40, 40, 40}
logP := []int{55, 55}
// both Q and P given (good)
params, err := NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: Q, P: P, LogQ: nil, LogP: nil,
})
require.NoError(t, err)
require.Equal(t, params.qi, Q)
require.Equal(t, params.pi, P)
// only Q given (good)
params, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: Q, P: nil, LogQ: nil, LogP: nil,
})
require.NoError(t, err)
require.Equal(t, params.qi, Q)
require.Empty(t, params.pi)
// Q and logP given (good)
params, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: Q, P: nil, LogQ: nil, LogP: logP,
})
require.NoError(t, err)
require.Equal(t, params.qi, Q)
require.Equal(t, len(params.pi), len(logP))
// logQ and P given (good)
params, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: nil, P: P, LogQ: logQ, LogP: nil,
})
require.NoError(t, err)
require.Equal(t, len(params.qi), len(logQ))
require.Equal(t, params.pi, P)
// both LogQ and LogP given (good)
params, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: nil, P: nil, LogQ: logQ, LogP: logP,
})
require.NoError(t, err)
require.Equal(t, len(params.qi), len(logQ))
require.Equal(t, len(params.pi), len(logP))
// only LogQ given (good)
params, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: nil, P: nil, LogQ: logQ, LogP: nil,
})
require.NoError(t, err)
require.Equal(t, len(params.qi), len(logQ))
require.Empty(t, params.pi)
// empty primes (bad)
_, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: nil, P: nil, LogQ: nil, LogP: nil,
})
require.Error(t, err)
// double set log/non-prime (bad)
_, err = NewParametersFromLiteral(ParametersLiteral{
LogN: logN, Q: Q, P: nil, LogQ: logQ, LogP: nil,
})
require.Error(t, err)
})
}
func NewDeterministicTestContext(params Parameters, seedKeyGen, seedEnc []byte) (tc *TestContext, err error) {
prngKGen, err := sampling.NewKeyedPRNG(seedKeyGen)
if err != nil {
panic(fmt.Errorf("NewDeterministicTestContext: failed to make prngKGen %w", err))
}
prngEnc, err := sampling.NewKeyedPRNG(seedEnc)
if err != nil {
panic(fmt.Errorf("NewDeterministicTestContext: failed to make prngEnc %w", err))
}
kgen := NewKeyGenerator(params)
kgenEncryptor := newTestEncryptorWithKeyedPRNG(params, nil, prngKGen)
kgen.Encryptor = kgenEncryptor
sk := kgen.GenSecretKeyNew()
pk := kgen.GenPublicKeyNew(sk)
eval := NewEvaluator(params, nil)
enc := newTestEncryptorWithKeyedPRNG(params, sk, prngEnc)
dec := NewDecryptor(params, sk)
return &TestContext{
params: params,
kgen: kgen,
sk: sk,
pk: pk,
enc: enc,
dec: dec,
eval: eval,
}, nil
}
func NewTestContext(params Parameters) (tc *TestContext, err error) {
@@ -379,12 +190,6 @@ func testParameters(tc *TestContext, t *testing.T) {
require.Equal(t, uint64(1), res)
}
})
t.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), 0, "Elements"), func(t *testing.T) {
ct := NewCiphertext(tc.params, 1, 0)
require.Equal(t, ct.N(), params.N())
require.Equal(t, ct.LogN(), params.LogN())
})
}
func testKeyGenerator(tc *TestContext, bpw2 int, t *testing.T) {
@@ -489,7 +294,7 @@ func testKeyGenerator(tc *TestContext, bpw2 int, t *testing.T) {
// 1) Decrypting the RNS decomposed input key
// 2) Reconstructing the key
// 3) Checking that the difference with the input key has a small norm
t.Run(testString(params, levelQ, levelP, bpw2, "KeyGenerator/GenEvaluationKey/Compressed=False"), func(t *testing.T) {
t.Run(testString(params, levelQ, levelP, bpw2, "KeyGenerator/GenEvaluationKey"), func(t *testing.T) {
skOut := kgen.GenSecretKeyNew()
@@ -509,37 +314,6 @@ func testKeyGenerator(tc *TestContext, bpw2 int, t *testing.T) {
require.GreaterOrEqual(t, math.Log2(math.Sqrt(float64(BaseRNSDecompositionVectorSize))*params.NoiseFreshSK())+1, NoiseEvaluationKey(evk, sk, skOut, params))
})
t.Run(testString(params, levelQ, levelP, bpw2, "KeyGenerator/GenEvaluationKey/Compressed=True"), func(t *testing.T) {
skOut := kgen.GenSecretKeyNew()
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(levelQ, levelP)
BaseTwoDecompositionVectorSize := params.BaseTwoDecompositionVectorSize(levelQ, levelP, bpw2)
evkParamsCompressed := evkParams
evkParamsCompressed.Compressed = true
evk := NewEvaluationKey(params, evkParamsCompressed)
// Generates Decomp([-asIn + w*P*sOut + e, a])
kgen.GenEvaluationKey(sk, skOut, evk)
require.Equal(t, BaseRNSDecompositionVectorSize, len(evk.Value))
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
require.Equal(t, BaseTwoDecompositionVectorSize[i], len(evk.Value[i]))
}
require.True(t, evk.Degree() == 0)
require.True(t, evk.IsCompressed())
require.NoError(t, evk.Expand(tc.params, nil))
require.True(t, evk.Degree() == 1)
require.False(t, evk.IsCompressed())
require.GreaterOrEqual(t, math.Log2(math.Sqrt(float64(BaseRNSDecompositionVectorSize))*params.NoiseFreshSK())+1, NoiseEvaluationKey(evk, sk, skOut, params))
})
t.Run(testString(params, levelQ, levelP, bpw2, "KeyGenerator/GenRelinearizationKey"), func(t *testing.T) {
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(levelQ, levelP)
@@ -608,6 +382,17 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
require.GreaterOrEqual(t, math.Log2(params.NoiseFreshPK())+1, ringQ.Log2OfStandardDeviation(pt.Value))
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encryptor/Encrypt/Pk/ShallowCopy"), func(t *testing.T) {
pkEnc1 := enc.WithKey(pk)
pkEnc2 := pkEnc1.ShallowCopy()
require.True(t, pkEnc1.params.Equal(&pkEnc2.params))
require.True(t, pkEnc1.encKey == pkEnc2.encKey)
require.False(t, (pkEnc1.basisextender == pkEnc2.basisextender) && (pkEnc1.basisextender != nil) && (pkEnc2.basisextender != nil))
require.False(t, pkEnc1.encryptorBuffers == pkEnc2.encryptorBuffers)
require.False(t, pkEnc1.xsSampler == pkEnc2.xsSampler)
require.False(t, pkEnc1.xeSampler == pkEnc2.xeSampler)
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encryptor/Encrypt/Sk"), func(t *testing.T) {
ringQ := params.RingQ().AtLevel(level)
@@ -635,7 +420,7 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
prng1, _ := sampling.NewKeyedPRNG([]byte{'a', 'b', 'c'})
prng2, _ := sampling.NewKeyedPRNG([]byte{'a', 'b', 'c'})
enc.withKeyedUniformSampling(prng1).Encrypt(pt, ct)
enc.WithPRNG(prng1).Encrypt(pt, ct)
samplerQ := ring.NewUniformSampler(prng2, ringQ)
@@ -650,6 +435,18 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
require.GreaterOrEqual(t, math.Log2(params.NoiseFreshSK())+1, ringQ.Log2OfStandardDeviation(pt.Value))
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encrypt/Sk/ShallowCopy"), func(t *testing.T) {
skEnc1 := NewEncryptor(params, sk)
skEnc2 := skEnc1.ShallowCopy()
require.True(t, skEnc1.params.Equal(&skEnc2.params))
require.True(t, skEnc1.encKey == skEnc2.encKey)
require.False(t, (skEnc1.basisextender == skEnc2.basisextender) && (skEnc1.basisextender != nil) && (skEnc2.basisextender != nil))
require.False(t, skEnc1.encryptorBuffers == skEnc2.encryptorBuffers)
require.False(t, skEnc1.xsSampler == skEnc2.xsSampler)
require.False(t, skEnc1.xeSampler == skEnc2.xeSampler)
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encrypt/WithKey/Sk->Sk"), func(t *testing.T) {
sk2 := kgen.GenSecretKeyNew()
skEnc1 := NewEncryptor(params, sk)
@@ -658,6 +455,7 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
require.True(t, skEnc1.encKey == sk)
require.True(t, skEnc2.encKey == sk2)
require.True(t, skEnc1.basisextender == skEnc2.basisextender)
require.True(t, skEnc1.encryptorBuffers == skEnc2.encryptorBuffers)
require.True(t, skEnc1.xsSampler == skEnc2.xsSampler)
require.True(t, skEnc1.xeSampler == skEnc2.xeSampler)
})
@@ -727,11 +525,6 @@ func testGadgetProduct(tc *TestContext, levelQ, bpw2 int, t *testing.T) {
t.Run(testString(params, levelQ, levelP, bpw2, "Evaluator/GadgetProductHoisted"), func(t *testing.T) {
// Setup temporary buffer for decomposition
poolQP := tc.eval.pool.AtLevel(levelQ, levelP)
buffDecompQP := poolQP.GetBuffDecompQP(params, levelQ, levelP)
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
if bpw2 != 0 {
t.Skip("method is unsupported for BaseTwoDecomposition != 0")
}
@@ -754,10 +547,10 @@ func testGadgetProduct(tc *TestContext, levelQ, bpw2 int, t *testing.T) {
kgen.GenEvaluationKey(sk, skOut, evk)
//Decompose the ciphertext
eval.DecomposeNTT(levelQ, levelP, levelP+1, a, ct.IsNTT, buffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, a, ct.IsNTT, eval.BuffDecompQP)
// Gadget product: ct = [-cs1 + as0 , c]
eval.GadgetProductHoisted(levelQ, buffDecompQP, &evk.GadgetCiphertext, ct)
eval.GadgetProductHoisted(levelQ, eval.BuffDecompQP, &evk.GadgetCiphertext, ct)
// pt = as0
pt := NewDecryptor(params, skOut).DecryptNew(ct)
@@ -960,11 +753,6 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
t.Skip("method is not supported if BaseTwoDecomposition != 0")
}
// Setup temporary buffer for decomposition
poolQP := tc.eval.pool.AtLevel(level, params.MaxLevelP())
buffDecompQP := poolQP.GetBuffDecompQP(params, level, 0)
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
// Generate a plaintext with values up to 2^30
pt := genPlaintext(params, level, 1<<30)
@@ -979,10 +767,10 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeyNew(galEl, sk, evkParams))
//Decompose the ciphertext
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, buffDecompQP)
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, eval.BuffDecompQP)
// Evaluate the automorphism
eval.WithKey(evk).AutomorphismHoisted(level, ct, buffDecompQP, galEl, ct)
eval.WithKey(evk).AutomorphismHoisted(level, ct, eval.BuffDecompQP, galEl, ct)
// Apply the same automorphism on the plaintext
ringQ := params.RingQ().AtLevel(level)
@@ -1015,11 +803,6 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
t.Skip("method is not supported if BaseTwoDecomposition != 0")
}
// Setup temporary buffer for decomposition
poolQP := tc.eval.pool.AtLevel(level, params.MaxLevelP())
buffDecompQP := poolQP.GetBuffDecompQP(params, level, 0)
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
// Generate a plaintext with values up to 2^30
pt := genPlaintext(params, level, 1<<30)
@@ -1034,12 +817,12 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeyNew(galEl, sk, evkParams))
//Decompose the ciphertext
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, buffDecompQP)
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, eval.BuffDecompQP)
ctQP := NewElementExtended(params, 1, level, params.MaxLevelP())
// Evaluate the automorphism
eval.WithKey(evk).AutomorphismHoistedLazy(level, ct, buffDecompQP, galEl, ctQP)
eval.WithKey(evk).AutomorphismHoistedLazy(level, ct, eval.BuffDecompQP, galEl, ctQP)
eval.ModDown(level, params.MaxLevelP(), ctQP, ct)
@@ -1078,7 +861,205 @@ func testSlotOperations(tc *TestContext, level, bpw2 int, t *testing.T) {
enc := tc.enc
dec := tc.dec
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Evaluator/PartialTrace"), func(t *testing.T) {
evkParams := EvaluationKeyParameters{LevelQ: utils.Pointy(level), BaseTwoDecomposition: utils.Pointy(bpw2)}
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Evaluator/Expand"), func(t *testing.T) {
if params.RingType() != ring.Standard {
t.Skip("Expand not supported for ring.Type = ring.ConjugateInvariant")
}
pt := NewPlaintext(params, level)
ringQ := params.RingQ().AtLevel(level)
logN := 4
logGap := 0
gap := 1 << logGap
values := make([]uint64, params.N())
scale := 1 << 22
for i := 0; i < 1<<logN; i++ { // embeds even coefficients only
values[i] = uint64(i * scale)
}
for i := 0; i < pt.Level()+1; i++ {
copy(pt.Value.Coeffs[i], values)
}
if pt.IsNTT {
ringQ.NTT(pt.Value, pt.Value)
}
ctIn := NewCiphertext(params, 1, level)
enc.Encrypt(pt, ctIn)
// GaloisKeys
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(GaloisElementsForExpand(params, logN), sk, evkParams)...)
eval := NewEvaluator(params, evk)
ciphertexts, err := eval.WithKey(evk).Expand(ctIn, logN, logGap)
require.NoError(t, err)
Q := ringQ.ModuliChain()
NoiseBound := float64(params.LogN() - logN + bpw2)
if bpw2 != 0 {
NoiseBound += float64(level + 5)
}
for i := range ciphertexts {
dec.Decrypt(ciphertexts[i], pt)
if pt.IsNTT {
ringQ.INTT(pt.Value, pt.Value)
}
for j := 0; j < level+1; j++ {
pt.Value.Coeffs[j][0] = ring.CRed(pt.Value.Coeffs[j][0]+Q[j]-values[i*gap], Q[j])
}
// Logs the noise
require.GreaterOrEqual(t, NoiseBound, ringQ.Log2OfStandardDeviation(pt.Value))
}
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Evaluator/Pack/LogGap=LogN"), func(t *testing.T) {
if params.RingType() != ring.Standard {
t.Skip("Pack not supported for ring.Type = ring.ConjugateInvariant")
}
pt := NewPlaintext(params, level)
N := params.N()
ringQ := tc.params.RingQ().AtLevel(level)
gap := params.N() / 16
ptPacked := NewPlaintext(params, level)
ciphertexts := make(map[int]*Ciphertext)
slotIndex := make(map[int]bool)
for i := 0; i < N; i += gap {
ciphertexts[i] = enc.EncryptZeroNew(level)
scalar := (1 << 30) + uint64(i)*(1<<20)
if ciphertexts[i].IsNTT {
ringQ.AddScalar(ciphertexts[i].Value[0], scalar, ciphertexts[i].Value[0])
} else {
for j := 0; j < level+1; j++ {
ciphertexts[i].Value[0].Coeffs[j][0] = ring.CRed(ciphertexts[i].Value[0].Coeffs[j][0]+scalar, ringQ.SubRings[j].Modulus)
}
}
slotIndex[i] = true
for j := 0; j < level+1; j++ {
ptPacked.Value.Coeffs[j][i] = scalar
}
}
// Galois Keys
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(GaloisElementsForPack(params, params.LogN()), sk, evkParams)...)
ct, err := eval.WithKey(evk).Pack(ciphertexts, params.LogN(), false)
require.NoError(t, err)
dec.Decrypt(ct, pt)
if pt.IsNTT {
ringQ.INTT(pt.Value, pt.Value)
}
ringQ.Sub(pt.Value, ptPacked.Value, pt.Value)
for i := 0; i < N; i++ {
if i%gap != 0 {
for j := 0; j < level+1; j++ {
pt.Value.Coeffs[j][i] = 0
}
}
}
NoiseBound := 15.0 + float64(bpw2)
if bpw2 != 0 {
NoiseBound += math.Log2(float64(level)+1.0) + 1.0
}
// Logs the noise
require.GreaterOrEqual(t, NoiseBound, ringQ.Log2OfStandardDeviation(pt.Value))
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Evaluator/Pack/LogGap=LogN-1"), func(t *testing.T) {
if params.RingType() != ring.Standard {
t.Skip("Pack not supported for ring.Type = ring.ConjugateInvariant")
}
pt := NewPlaintext(params, level)
N := params.N()
ringQ := tc.params.RingQ().AtLevel(level)
ptPacked := NewPlaintext(params, level)
ciphertexts := make(map[int]*Ciphertext)
slotIndex := make(map[int]bool)
for i := 0; i < N/2; i += params.N() / 16 {
ciphertexts[i] = enc.EncryptZeroNew(level)
scalar := (1 << 30) + uint64(i)*(1<<20)
if ciphertexts[i].IsNTT {
ringQ.INTT(ciphertexts[i].Value[0], ciphertexts[i].Value[0])
}
for j := 0; j < level+1; j++ {
ciphertexts[i].Value[0].Coeffs[j][0] = ring.CRed(ciphertexts[i].Value[0].Coeffs[j][0]+scalar, ringQ.SubRings[j].Modulus)
ciphertexts[i].Value[0].Coeffs[j][N/2] = ring.CRed(ciphertexts[i].Value[0].Coeffs[j][N/2]+scalar, ringQ.SubRings[j].Modulus)
}
if ciphertexts[i].IsNTT {
ringQ.NTT(ciphertexts[i].Value[0], ciphertexts[i].Value[0])
}
slotIndex[i] = true
for j := 0; j < level+1; j++ {
ptPacked.Value.Coeffs[j][i] = scalar
ptPacked.Value.Coeffs[j][i+N/2] = scalar
}
}
// Galois Keys
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(GaloisElementsForPack(params, params.LogN()-1), sk, evkParams)...)
ct, err := eval.WithKey(evk).Pack(ciphertexts, params.LogN()-1, true)
require.NoError(t, err)
dec.Decrypt(ct, pt)
if pt.IsNTT {
ringQ.INTT(pt.Value, pt.Value)
}
ringQ.Sub(pt.Value, ptPacked.Value, pt.Value)
NoiseBound := 15.0 + float64(bpw2)
if bpw2 != 0 {
NoiseBound += math.Log2(float64(level)+1.0) + 1.0
}
// Logs the noise
require.GreaterOrEqual(t, NoiseBound, ringQ.Log2OfStandardDeviation(pt.Value))
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Evaluator/InnerSum"), func(t *testing.T) {
if params.MaxLevelP() == -1 {
t.Skip("test requires #P > 0")
@@ -1090,7 +1071,6 @@ func testSlotOperations(tc *TestContext, level, bpw2 int, t *testing.T) {
ringQ := tc.params.RingQ().AtLevel(level)
pt := genPlaintext(params, level, 1<<30)
pt.LogDimensions = ring.Dimensions{Rows: 1, Cols: params.logN - 1}
ptInnerSum := *pt.Value.CopyNew()
ct, err := enc.EncryptNew(pt)
require.NoError(t, err)
@@ -1098,7 +1078,7 @@ func testSlotOperations(tc *TestContext, level, bpw2 int, t *testing.T) {
// Galois Keys
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(GaloisElementsForInnerSum(params, batch, n), sk)...)
require.NoError(t, eval.WithKey(evk).PartialTracesSum(ct, batch, n, ct))
require.NoError(t, eval.WithKey(evk).InnerSum(ct, batch, n, ct))
dec.Decrypt(ct, pt)
@@ -1217,8 +1197,11 @@ func testWriteAndRead(tc *TestContext, bpw2 int, t *testing.T) {
})
t.Run(testString(params, levelQ, levelP, bpw2, "WriteAndRead/GadgetCiphertext"), func(t *testing.T) {
rlk := NewRelinearizationKey(params, EvaluationKeyParameters{BaseTwoDecomposition: utils.Pointy(bpw2)})
tc.kgen.GenRelinearizationKey(tc.sk, rlk)
buffer.RequireSerializerCorrect(t, &rlk.GadgetCiphertext)
})
@@ -1230,14 +1213,10 @@ func testWriteAndRead(tc *TestContext, bpw2 int, t *testing.T) {
buffer.RequireSerializerCorrect(t, pk)
})
t.Run(testString(params, levelQ, levelP, bpw2, "WriteAndRead/EvaluationKey/Compressed=False"), func(t *testing.T) {
t.Run(testString(params, levelQ, levelP, bpw2, "WriteAndRead/EvaluationKey"), func(t *testing.T) {
buffer.RequireSerializerCorrect(t, tc.kgen.GenEvaluationKeyNew(sk, sk))
})
t.Run(testString(params, levelQ, levelP, bpw2, "WriteAndRead/EvaluationKey/Compressed=True"), func(t *testing.T) {
buffer.RequireSerializerCorrect(t, tc.kgen.GenEvaluationKeyNew(sk, sk, EvaluationKeyParameters{Compressed: true}))
})
t.Run(testString(params, levelQ, levelP, bpw2, "WriteAndRead/RelinearizationKey"), func(t *testing.T) {
buffer.RequireSerializerCorrect(t, tc.kgen.GenRelinearizationKeyNew(tc.sk))
})
@@ -1264,7 +1243,7 @@ func testMarshaller(tc *TestContext, t *testing.T) {
require.Nil(t, err)
var p Parameters
require.Nil(t, p.UnmarshalBinary(bytes))
require.Equal(t, params.ParametersLiteral(), p.ParametersLiteral())
require.Equal(t, params, p)
})
t.Run("Marshaller/MetaData", func(t *testing.T) {
+23 -28
View File
@@ -6,7 +6,7 @@ import (
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
const (
@@ -17,7 +17,7 @@ const (
var ScalePrecisionLog10 = int(math.Ceil(float64(ScalePrecision) / math.Log2(10)))
// Scale is a struct used to track the scaling factor
// of [Plaintext] and [Ciphertext] structs.
// of Plaintext and Ciphertext structs.
// The scale is managed as an 128-bit precision real and can
// be either a floating point value or a mod T
// prime integer, which is determined at instantiation.
@@ -26,16 +26,16 @@ type Scale struct {
Mod *big.Int //`json:",omitempty"`
}
// NewScale instantiates a new floating point [Scale].
// Accepted types for s are int, int64, uint64, float64, *[big.Int], *[big.Float] and *[Scale].
// NewScale instantiates a new floating point Scale.
// Accepted types for s are int, int64, uint64, float64, *big.Int, *big.Float and *Scale.
// If the input type is not an accepted type, returns an error.
func NewScale(s interface{}) Scale {
v := scaleToBigFloat(s)
return Scale{Value: *v}
}
// NewScaleModT instantiates a new integer mod T [Scale].
// Accepted types for s are int, int64, uint64, float64, *[big.Int], *[big.Float] and *[Scale].
// NewScaleModT instantiates a new integer mod T Scale.
// Accepted types for s are int, int64, uint64, float64, *big.Int, *big.Float and *Scale.
// If the input type is not an accepted type, returns an error.
func NewScaleModT(s interface{}, mod uint64) Scale {
scale := NewScale(s)
@@ -59,12 +59,6 @@ func (s Scale) Float64() float64 {
return f64
}
func (s Scale) Log2() float64 {
res := new(big.Float).Quo(bignum.Log(&s.Value), bignum.Log2(s.Value.Prec()))
f64, _ := res.Float64()
return f64
}
// Uint64 returns the underlying scale as an uint64 value.
func (s Scale) Uint64() uint64 {
u64, _ := s.Value.Uint64()
@@ -72,7 +66,7 @@ func (s Scale) Uint64() uint64 {
}
// Mul multiplies the target s with s1, returning the result in
// a new [Scale] struct. If mod is specified, performs the multiplication
// a new Scale struct. If mod is specified, performs the multiplication
// modulo mod.
func (s Scale) Mul(s1 Scale) Scale {
@@ -93,7 +87,7 @@ func (s Scale) Mul(s1 Scale) Scale {
}
// Div multiplies the target s with s1^-1, returning the result in
// a new [Scale] struct. If mod is specified, performs the multiplication
// a new Scale struct. If mod is specified, performs the multiplication
// modulo t with the multiplicative inverse of s1. Otherwise, performs
// the quotient operation.
func (s Scale) Div(s1 Scale) Scale {
@@ -171,10 +165,9 @@ func (s Scale) Min(s1 Scale) (max Scale) {
}
// BinarySize returns the serialized size of the object in bytes.
// Each value is encoded with .Text('e', ceil(ScalePrecision / log2(10))).
// Each value is encoded with .Text('x', ceil(ScalePrecision / log2(10))).
func (s Scale) BinarySize() int {
// 21 for JSON formatting plus 2*(6 + ScalePrecisionLog10) for the scales encoding.
return 21 + (ScalePrecisionLog10+6)<<1
return 21 + (ScalePrecisionLog10+8)<<1 // 21 for JSON formatting and 2*(8 + ScalePrecisionLog10)
}
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
@@ -183,17 +176,18 @@ func (s Scale) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [Scale.MarshalBinary] on the object.
// MarshalBinary or WriteTo on the object.
func (s Scale) UnmarshalBinary(p []byte) (err error) {
return s.UnmarshalJSON(p)
}
// MarshalJSON encodes the object into a binary form on a newly allocated slice of bytes.
func (s Scale) MarshalJSON() (p []byte, err error) {
var mod string
if s.Mod != nil {
mod = new(big.Float).SetPrec(ScalePrecision).SetInt(s.Mod).Text('e', ScalePrecisionLog10)
mod = new(big.Float).SetPrec(ScalePrecision).SetInt(s.Mod).Text('x', ScalePrecisionLog10)
} else {
var m string
@@ -201,14 +195,14 @@ func (s Scale) MarshalJSON() (p []byte, err error) {
m += "0"
}
mod = "0." + m + "e+00"
mod = "0x0." + m + "p+00"
}
aux := &struct {
Value string
Mod string
}{
Value: s.Value.Text('e', ScalePrecisionLog10),
Value: s.Value.Text('x', ScalePrecisionLog10),
Mod: mod,
}
@@ -229,7 +223,9 @@ func (s *Scale) UnmarshalJSON(p []byte) (err error) {
}
s.Value.SetPrec(ScalePrecision)
s.Value.SetString(aux.Value)
f, _ := new(big.Float).SetString(aux.Value)
s.Value.Set(f)
mod, bool := new(big.Float).SetString(aux.Mod)
@@ -259,30 +255,35 @@ func scaleToBigFloat(scale interface{}) (s *big.Float) {
s = new(big.Float).SetPrec(ScalePrecision)
s.SetFloat64(scale)
return
case *big.Float:
if scale.Cmp(new(big.Float).SetFloat64(0)) < 0 || scale.IsInf() {
panic(fmt.Errorf("scale cannot be negative, but is %f", scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.Set(scale)
return
case big.Float:
if scale.Cmp(new(big.Float).SetFloat64(0)) < 0 || scale.IsInf() {
panic(fmt.Errorf("scale cannot be negative, but is %f", &scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.Set(&scale)
return
case *big.Int:
if scale.Cmp(new(big.Int).SetInt64(0)) < 0 {
panic(fmt.Errorf("scale cannot be negative, but is %f", scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.SetInt(scale)
return
case big.Int:
if scale.Cmp(new(big.Int).SetInt64(0)) < 0 {
panic(fmt.Errorf("scale cannot be negative, but is %f", &scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.SetInt(&scale)
return
case int:
return scaleToBigFloat(new(big.Int).SetInt64(int64(scale)))
case int64:
@@ -294,10 +295,4 @@ func scaleToBigFloat(scale interface{}) (s *big.Float) {
default:
panic(fmt.Errorf("invalid scale.(type): must be int, int64, uint64, float64, *big.Int, *big.Float or *Scale but is %T", scale))
}
// Although the big.Float has 128 bits of precision, it will be
// initialized with mant:big.nat{0x0}, i.e. only one mantissa word.
// This forces two mantissa words: mant:big.nat{0x0, 0x0}.
s.SetString(s.Text('x', ScalePrecisionLog10))
return
}
+1 -1
View File
@@ -1,6 +1,6 @@
package rlwe
import "github.com/tuneinsight/lattigo/v6/ring"
import "github.com/luxdefi/lattice/v5/ring"
const (
// XsUniformTernary is the standard deviation of a ternary key with uniform distribution
+10 -11
View File
@@ -3,13 +3,12 @@ package rlwe
import (
"math"
"math/big"
"slices"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
// NoisePublicKey returns the log2 of the standard deviation of the input [PublicKey] with respect to the given [Secret] and parameters.
// NoisePublicKey returns the log2 of the standard deviation of the input public-key with respect to the given secret-key and parameters.
func NoisePublicKey(pk *PublicKey, sk *SecretKey, params Parameters) float64 {
pk = pk.CopyNew()
@@ -24,14 +23,14 @@ func NoisePublicKey(pk *PublicKey, sk *SecretKey, params Parameters) float64 {
return ringQP.Log2OfStandardDeviation(pk.Value[0])
}
// NoiseRelinearizationKey the log2 of the standard deviation of the noise of the input [RelinearizationKey] with respect to the given [Secret] and paramters.
// NoiseRelinearizationKey the log2 of the standard deviation of the noise of the input relinearization key with respect to the given secret-key and paramters.
func NoiseRelinearizationKey(rlk *RelinearizationKey, sk *SecretKey, params Parameters) float64 {
sk2 := sk.CopyNew()
params.RingQP().AtLevel(rlk.LevelQ(), rlk.LevelP()).MulCoeffsMontgomery(sk2.Value, sk2.Value, sk2.Value)
return NoiseEvaluationKey(&rlk.EvaluationKey, sk2, sk, params)
}
// NoiseGaloisKey the log2 of the standard deviation of the noise of the input [GaloisKey] key with respect to the given [SecretKey] and paramters.
// NoiseGaloisKey the log2 of the standard deviation of the noise of the input Galois key key with respect to the given secret-key and paramters.
func NoiseGaloisKey(gk *GaloisKey, sk *SecretKey, params Parameters) float64 {
skIn := sk.CopyNew()
@@ -46,7 +45,7 @@ func NoiseGaloisKey(gk *GaloisKey, sk *SecretKey, params Parameters) float64 {
return NoiseEvaluationKey(&gk.EvaluationKey, skIn, skOut, params)
}
// NoiseGadgetCiphertext returns the log2 of the standard deviation of the noise of the input [GadgetCiphertext] with respect to the given [Plaintext], [SecretKey] and [Parameters].
// NoiseGadgetCiphertext returns the log2 of the standard devaition of the noise of the input gadget ciphertext with respect to the given plaintext, secret-key and parameters.
// The polynomial pt is expected to be in the NTT and Montgomery domain.
func NoiseGadgetCiphertext(gct *GadgetCiphertext, pt ring.Poly, sk *SecretKey, params Parameters) float64 {
@@ -55,7 +54,7 @@ func NoiseGadgetCiphertext(gct *GadgetCiphertext, pt ring.Poly, sk *SecretKey, p
levelQ, levelP := gct.LevelQ(), gct.LevelP()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
ringQ, ringP := ringQP.RingQ, ringQP.RingP
BaseTwoDecompositionVectorSize := slices.Min(gct.BaseTwoDecompositionVectorSize()) // required else the check becomes very complicated
BaseTwoDecompositionVectorSize := utils.MinSlice(gct.BaseTwoDecompositionVectorSize()) // required else the check becomes very complicated
// Decrypts
// [-asIn + w*P*sOut + e, a] + [asIn]
@@ -101,13 +100,13 @@ func NoiseGadgetCiphertext(gct *GadgetCiphertext, pt ring.Poly, sk *SecretKey, p
return maxLog2Std
}
// NoiseEvaluationKey the log2 of the standard deviation of the noise of the input [GaloisKey] with respect to the given [SecretKey] and [Parameters].
// NoiseEvaluationKey the log2 of the standard deviation of the noise of the input Galois key key with respect to the given secret-key and paramters.
func NoiseEvaluationKey(evk *EvaluationKey, skIn, skOut *SecretKey, params Parameters) float64 {
return NoiseGadgetCiphertext(&evk.GadgetCiphertext, skIn.Value.Q, skOut, params)
}
// Norm returns the log2 of the standard deviation, minimum and maximum absolute norm of
// the decrypted [Ciphertext], before the decoding (i.e. including the error).
// the decrypted Ciphertext, before the decoding (i.e. including the error).
func Norm(ct *Ciphertext, dec *Decryptor) (std, min, max float64) {
params := dec.params
@@ -199,7 +198,7 @@ func NTTSparseAndMontgomery(r *ring.Ring, metadata *MetaData, pol ring.Poly) {
} else {
var n int
var NTT func(p1, p2 []uint64, N int, Q, QInv uint64, BRedConstant [2]uint64, nttPsi []uint64)
var NTT func(p1, p2 []uint64, N int, Q, QInv uint64, BRedConstant, nttPsi []uint64)
switch r.Type() {
case ring.Standard:
n = 2 << metadata.LogDimensions.Cols
+14 -14
View File
@@ -3,44 +3,44 @@ package examples
import (
"testing"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/he/heint"
)
func TestExampleParams(t *testing.T) {
for _, pl := range BGVParams {
p, err := bgv.NewParametersFromLiteral(pl)
for _, pl := range HEIntParams {
p, err := heint.NewParametersFromLiteral(pl)
if err != nil {
t.Fatal(err)
}
p.RingQ()
t.Logf("BGVParams: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
t.Logf("HEIntParams: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
}
for _, pl := range BGVScaleInvariantParams {
p, err := bgv.NewParametersFromLiteral(pl)
for _, pl := range HEIntScaleInvariantParams {
p, err := heint.NewParametersFromLiteral(pl)
if err != nil {
t.Fatal(err)
}
p.RingQ()
t.Logf("BGVScaleInvariantParams: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
t.Logf("HEIntScaleInvariantParams: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
}
for _, pl := range CKKSComplexParams {
p, err := ckks.NewParametersFromLiteral(pl)
for _, pl := range HEFloatComplexParams {
p, err := hefloat.NewParametersFromLiteral(pl)
if err != nil {
t.Fatal(err)
}
p.RingQ()
t.Logf("CKKSComplex: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
t.Logf("HEFloatComplex: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
}
for _, pl := range CKKSRealParams {
p, err := ckks.NewParametersFromLiteral(pl)
for _, pl := range HEFloatRealParams {
p, err := hefloat.NewParametersFromLiteral(pl)
if err != nil {
t.Fatal(err)
}
p.RingQ()
t.Logf("CKKSReal: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
t.Logf("HEFloatReal: LogN: %d - LogQP: %12.7f - LogSlots: %d", p.LogN(), p.LogQP(), p.LogMaxSlots())
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Package examples contains several example Go applications that use Lattigo in both the single- and multiparty settings,
// Package examples contains several example Go applications that use Lattice in both the single- and multiparty settings,
// as well as several example parameter sets. See examples/README.md for more information about the examples.
//
// 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.
+26 -27
View File
@@ -8,12 +8,12 @@ import (
"sync"
"time"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// This example showcases the use of the multiparty package to generate an evaluation key in a multiparty setting.
// This example showcases the use of the mhe package to generate an evaluation key in a multiparty setting.
// It simulate multiple parties and their interactions within a single Go program using multiple goroutines.
// The parties use the t-out-of-N-threshold RLWE encryption scheme as described in "An Efficient Threshold
// Access-Structure for RLWE-Based Multiparty Homomorphic Encryption" (2022) by Mouchet, C., Bertrand, E. and
@@ -33,28 +33,28 @@ import (
// party represents a party in the scenario.
type party struct {
multiparty.GaloisKeyGenProtocol
multiparty.Thresholdizer
multiparty.Combiner
mhe.GaloisKeyGenProtocol
mhe.Thresholdizer
mhe.Combiner
i int
sk *rlwe.SecretKey
tsk multiparty.ShamirSecretShare
ssp multiparty.ShamirPolynomial
shamirPk multiparty.ShamirPublicPoint
tsk mhe.ShamirSecretShare
ssp mhe.ShamirPolynomial
shamirPk mhe.ShamirPublicPoint
genTaskQueue chan genTask
}
// cloud represents the cloud server assisting the parties.
type cloud struct {
multiparty.GaloisKeyGenProtocol
mhe.GaloisKeyGenProtocol
aggTaskQueue chan genTaskResult
finDone chan rlwe.GaloisKey
}
var crp map[uint64]multiparty.GaloisKeyGenCRP
var crp map[uint64]mhe.GaloisKeyGenCRP
// Run simulate the behavior of a party during the key generation protocol. The parties process
// a queue of share-generation tasks which is attributed to them by a protocol orchestrator
@@ -73,7 +73,7 @@ func (p *party) Run(wg *sync.WaitGroup, params rlwe.Parameters, N int, P []*part
if t == N {
sk = p.sk
} else {
activePk := make([]multiparty.ShamirPublicPoint, 0)
activePk := make([]mhe.ShamirPublicPoint, 0)
for _, pi := range task.group {
activePk = append(activePk, pi.shamirPk)
}
@@ -111,12 +111,12 @@ func (p *party) String() string {
func (c *cloud) Run(galEls []uint64, params rlwe.Parameters, t int) {
shares := make(map[uint64]*struct {
share multiparty.GaloisKeyGenShare
share mhe.GaloisKeyGenShare
needed int
}, len(galEls))
for _, galEl := range galEls {
shares[galEl] = &struct {
share multiparty.GaloisKeyGenShare
share mhe.GaloisKeyGenShare
needed int
}{c.AllocateShare(), t}
shares[galEl].share.GaloisElement = galEl
@@ -210,7 +210,7 @@ func main() {
wg := new(sync.WaitGroup)
C := &cloud{
GaloisKeyGenProtocol: multiparty.NewGaloisKeyGenProtocol(params),
GaloisKeyGenProtocol: mhe.NewGaloisKeyGenProtocol(params),
aggTaskQueue: make(chan genTaskResult, len(galEls)*N),
finDone: make(chan rlwe.GaloisKey, len(galEls)),
}
@@ -218,25 +218,24 @@ func main() {
// Initialize the parties' state
P := make([]*party, N)
skIdeal := rlwe.NewSecretKey(params)
shamirPks := make([]multiparty.ShamirPublicPoint, 0)
shamirPks := make([]mhe.ShamirPublicPoint, 0)
for i := range P {
pi := new(party)
pi.GaloisKeyGenProtocol = multiparty.NewGaloisKeyGenProtocol(params)
pi.GaloisKeyGenProtocol = mhe.NewGaloisKeyGenProtocol(params)
pi.i = i
pi.sk = kg.GenSecretKeyNew()
pi.genTaskQueue = make(chan genTask, k)
if t != N {
pi.Thresholdizer = multiparty.NewThresholdizer(params)
pi.Thresholdizer = mhe.NewThresholdizer(params)
pi.tsk = pi.AllocateThresholdSecretShare()
var err error
pi.ssp, err = pi.GenShamirPolynomial(t, pi.sk)
if err != nil {
panic(err)
}
/* #nosec G115 -- i cannot be negative */
pi.shamirPk = multiparty.ShamirPublicPoint(i + 1)
pi.shamirPk = mhe.ShamirPublicPoint(i + 1)
}
P[i] = pi
@@ -250,14 +249,14 @@ func main() {
// if t < N, use the t-out-of-N scheme and performs the share-resharing procedure.
if t != N {
for _, pi := range P {
pi.Combiner = multiparty.NewCombiner(params, pi.shamirPk, shamirPks, t)
pi.Combiner = mhe.NewCombiner(params, pi.shamirPk, shamirPks, t)
}
fmt.Println("Performing threshold setup")
shares := make(map[*party]map[*party]multiparty.ShamirSecretShare, len(P))
shares := make(map[*party]map[*party]mhe.ShamirSecretShare, len(P))
for _, pi := range P {
shares[pi] = make(map[*party]multiparty.ShamirSecretShare)
shares[pi] = make(map[*party]mhe.ShamirSecretShare)
for _, pj := range P {
share := pi.AllocateThresholdSecretShare()
@@ -284,7 +283,7 @@ func main() {
// Sample the common random polynomials from the CRS.
// For the scenario, we consider it is provided as-is to the parties.
crp = make(map[uint64]multiparty.GaloisKeyGenCRP)
crp = make(map[uint64]mhe.GaloisKeyGenCRP)
for _, galEl := range galEls {
crp[galEl] = P[0].SampleCRP(crs)
}
@@ -324,7 +323,7 @@ func main() {
fmt.Printf("Checking the keys... ")
noise := multiparty.NoiseGaloisKey(params, t)
noise := mhe.NoiseGaloisKey(params, t)
for _, galEl := range galEls {
@@ -348,7 +347,7 @@ type genTask struct {
type genTaskResult struct {
galEl uint64
rtgShare multiparty.GaloisKeyGenShare
rtgShare mhe.GaloisKeyGenShare
}
func getTasks(galEls []uint64, groups [][]*party) []genTask {
+53 -53
View File
@@ -1,135 +1,135 @@
package examples
import (
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/ring"
)
var (
// BGVParamsN12QP109 is an example parameter set for the `bgv` package logN=12 and logQP=109.
// HEIntParamsN12QP109 is an example parameter set for the `heint` package logN=12 and logQP=109.
// These parameters expect the user to use the regular tensoring (i.e. Evaluator.Mul) followed
// by the rescaling (i.e. Evaluator.Rescale).
BGVParamsN12QP109 = bgv.ParametersLiteral{
HEIntParamsN12QP109 = heint.ParametersLiteral{
LogN: 12,
LogQ: []int{39, 31},
LogP: []int{39},
PlaintextModulus: 0x10001,
}
// BGVParamsN13QP218 is an example parameter set for the `bgv` package with logN=13 and logQP=218.
// HEIntParamsN13QP218 is an example parameter set for the `heint` package with logN=13 and logQP=218.
// These parameters expect the user to use the regular tensoring (i.e. Evaluator.Mul) followed
// by the rescaling (i.e. Evaluator.Rescale).
BGVParamsN13QP218 = bgv.ParametersLiteral{
HEIntParamsN13QP218 = heint.ParametersLiteral{
LogN: 13,
LogQ: []int{42, 33, 33, 33, 33},
LogP: []int{44},
PlaintextModulus: 0x10001,
}
// BGVParamsN14QP438 is an example parameter set for the `bgv` package with logN=14 and logQP=438.
// HEIntParamsN14QP438 is an example parameter set for the `heint` package with logN=14 and logQP=438.
// These parameters expect the user to use the regular tensoring (i.e. Evaluator.Mul) followed
// by the rescaling (i.e. Evaluator.Rescale).
BGVParamsN14QP438 = bgv.ParametersLiteral{
HEIntParamsN14QP438 = heint.ParametersLiteral{
LogN: 14,
LogQ: []int{44, 34, 34, 34, 34, 34, 34, 34, 34, 34},
LogP: []int{44, 44},
PlaintextModulus: 0x10001,
}
// BGVParamsN15QP880 is an example parameter set for the `bgv` package with logN=15 and logQP=881.
// HEIntParamsN15QP880 is an example parameter set for the `heint` package with logN=15 and logQP=881.
// These parameters expect the user to use the regular tensoring (i.e. Evaluator.Mul) followed
// by the rescaling (i.e. Evaluator.Rescale).
BGVParamsN15QP880 = bgv.ParametersLiteral{
HEIntParamsN15QP880 = heint.ParametersLiteral{
LogN: 15,
LogQ: []int{47, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34},
LogP: []int{47, 47, 47, 47},
PlaintextModulus: 0x10001,
}
// BGVScaleInvariantParamsN12QP109 is an example parameter set for the `bgv` package logN=12 and logQP=109.
// HEIntScaleInvariantParamsN12QP109 is an example parameter set for the `heint` package logN=12 and logQP=109.
// These parameters expect the user to use the scale invariant tensoring (i.e. Evaluator.MulScaleInvariant).
BGVScaleInvariantParamsN12QP109 = bgv.ParametersLiteral{
HEIntScaleInvariantParamsN12QP109 = heint.ParametersLiteral{
LogN: 12,
LogQ: []int{39, 39},
LogP: []int{31},
PlaintextModulus: 0x10001,
}
// BGVScaleInvariantParamsN13QP218 is an example parameter set for the `bgv` package with logN=13 and logQP=218.
// HEIntScaleInvariantParamsN13QP218 is an example parameter set for the `heint` package with logN=13 and logQP=218.
// These parameters expect the user to use the scale invariant tensoring (i.e. Evaluator.MulScaleInvariant).
BGVScaleInvariantParamsN13QP218 = bgv.ParametersLiteral{
HEIntScaleInvariantParamsN13QP218 = heint.ParametersLiteral{
LogN: 13,
LogQ: []int{55, 54, 54},
LogP: []int{55},
PlaintextModulus: 0x10001,
}
// BGVScaleInvariantParamsN14QP438 is an example parameter set for the `bgv` package with logN=14 and logQP=438.
// HEIntScaleInvariantParamsN14QP438 is an example parameter set for the `heint` package with logN=14 and logQP=438.
// These parameters expect the user to use the scale invariant tensoring (i.e. Evaluator.MulScaleInvariant).
BGVScaleInvariantParamsN14QP438 = bgv.ParametersLiteral{
HEIntScaleInvariantParamsN14QP438 = heint.ParametersLiteral{
LogN: 14,
LogQ: []int{55, 55, 55, 54, 54, 54},
LogP: []int{56, 55},
PlaintextModulus: 0x10001,
}
// BGVScaleInvariantParamsN15QP880 is an example parameter set for the `bgv` package with logN=15 and logQP=881.
// HEIntScaleInvariantParamsN15QP880 is an example parameter set for the `heint` package with logN=15 and logQP=881.
// These parameters expect the user to use the scale invariant tensoring (i.e. Evaluator.MulScaleInvariant).
BGVScaleInvariantParamsN15QP880 = bgv.ParametersLiteral{
HEIntScaleInvariantParamsN15QP880 = heint.ParametersLiteral{
LogN: 15,
LogQ: []int{60, 60, 59, 58, 58, 58, 58, 58, 58, 58, 58, 58},
LogP: []int{60, 60, 60},
PlaintextModulus: 0x10001,
}
// CKKSComplexParamsN12QP109 is an example parameter set for the `ckks` package with logN=12 and logQP=109.
// These parameters instantiate `ckks` over the complex field with N/2 SIMD slots.
CKKSComplexParamsN12QP109 = ckks.ParametersLiteral{
// HEFloatComplexParamsN12QP109 is an example parameter set for the `hefloat` package with logN=12 and logQP=109.
// These parameters instantiate `hefloat` over the complex field with N/2 SIMD slots.
HEFloatComplexParamsN12QP109 = hefloat.ParametersLiteral{
LogN: 12,
LogQ: []int{38, 32},
LogP: []int{39},
LogDefaultScale: 32,
}
// CKKSComplexParamsN13QP218 is an example parameter set for the `ckks` package with logN=13 and logQP=218.
// These parameters instantiate `ckks` over the complex field with N/2 SIMD slots.
CKKSComplexParamsN13QP218 = ckks.ParametersLiteral{
// HEFloatComplexParamsN13QP218 is an example parameter set for the `hefloat` package with logN=13 and logQP=218.
// These parameters instantiate `hefloat` over the complex field with N/2 SIMD slots.
HEFloatComplexParamsN13QP218 = hefloat.ParametersLiteral{
LogN: 13,
LogQ: []int{33, 30, 30, 30, 30, 30},
LogP: []int{35},
LogDefaultScale: 30,
}
// CKKSComplexParamsN14QP438 is an example parameter set for the `ckks` package with logN=14 and logQP=438.
// These parameters instantiate `ckks` over the complex field with N/2 SIMD slots.
CKKSComplexParamsN14QP438 = ckks.ParametersLiteral{
// HEFloatComplexParamsN14QP438 is an example parameter set for the `hefloat` package with logN=14 and logQP=438.
// These parameters instantiate `hefloat` over the complex field with N/2 SIMD slots.
HEFloatComplexParamsN14QP438 = hefloat.ParametersLiteral{
LogN: 14,
LogQ: []int{45, 34, 34, 34, 34, 34, 34, 34, 34, 34},
LogP: []int{44, 43},
LogDefaultScale: 34,
}
// CKKSComplexParamsN15QP880 is an example parameter set for the `ckks` package with logN=15 and logQP=881.
// These parameters instantiate `ckks` over the complex field with N/2 SIMD slots.
CKKSComplexParamsN15QP881 = ckks.ParametersLiteral{
// HEFloatComplexParamsN15QP880 is an example parameter set for the `hefloat` package with logN=15 and logQP=881.
// These parameters instantiate `hefloat` over the complex field with N/2 SIMD slots.
HEFloatComplexParamsN15QP881 = hefloat.ParametersLiteral{
LogN: 15,
LogQ: []int{51, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40},
LogP: []int{50, 50, 50},
LogDefaultScale: 40,
}
// CKKSComplexParamsPN16QP1761 is an example parameter set for the `ckks` package with logN=16 and logQP = 1761.
// These parameters instantiate `ckks` over the complex field with N/2 SIMD slots.
CKKSComplexParamsPN16QP1761 = ckks.ParametersLiteral{
// HEFloatComplexParamsPN16QP1761 is an example parameter set for the `hefloat` package with logN=16 and logQP = 1761.
// These parameters instantiate `hefloat` over the complex field with N/2 SIMD slots.
HEFloatComplexParamsPN16QP1761 = hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{56, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45},
LogP: []int{55, 55, 55, 55},
LogDefaultScale: 45,
}
// CKKSRealParamsN12QP109 is an example parameter set for the `ckks` package with conjugate-invariant CKKS and logN=12 and logQP=109.
// These parameters instantiate `ckks` over the real field with N SIMD slots.
CKKSRealParamsN12QP109 = ckks.ParametersLiteral{
// HEFloatRealParamsN12QP109 is an example parameter set for the `hefloat` package with conjugate-invariant CKKS and logN=12 and logQP=109.
// These parameters instantiate `hefloat` over the real field with N SIMD slots.
HEFloatRealParamsN12QP109 = hefloat.ParametersLiteral{
LogN: 12,
LogQ: []int{38, 32},
LogP: []int{39},
@@ -137,9 +137,9 @@ var (
RingType: ring.ConjugateInvariant,
}
// CKKSRealParamsN13QP218 is an example parameter set for the `ckks` package with conjugate-invariant CKKS and logN=13 and logQP=218
// These parameters instantiate `ckks` over the real field with N SIMD slots.
CKKSRealParamsN13QP218 = ckks.ParametersLiteral{
// HEFloatRealParamsN13QP218 is an example parameter set for the `hefloat` package with conjugate-invariant CKKS and logN=13 and logQP=218
// These parameters instantiate `hefloat` over the real field with N SIMD slots.
HEFloatRealParamsN13QP218 = hefloat.ParametersLiteral{
LogN: 13,
LogQ: []int{33, 30, 30, 30, 30, 30},
LogP: []int{35},
@@ -147,9 +147,9 @@ var (
RingType: ring.ConjugateInvariant,
}
// CKKSRealParamsN14QP438 is an example parameter set for the `ckks` package with logN=14 and logQP=438.
// These parameters instantiate `ckks` over the real field with N SIMD slots.
CKKSRealParamsN14QP438 = ckks.ParametersLiteral{
// HEFloatRealParamsN14QP438 is an example parameter set for the `hefloat` package with logN=14 and logQP=438.
// These parameters instantiate `hefloat` over the real field with N SIMD slots.
HEFloatRealParamsN14QP438 = hefloat.ParametersLiteral{
LogN: 14,
LogQ: []int{46, 34, 34, 34, 34, 34, 34, 34, 34, 34},
LogP: []int{43, 43},
@@ -157,9 +157,9 @@ var (
RingType: ring.ConjugateInvariant,
}
// CKKSRealParamsN15QP880 is an example parameter set for the `ckks` package with logN=15 and logQP=881.
// These parameters instantiate `ckks` over the real field with N SIMD slots.
CKKSRealParamsN15QP881 = ckks.ParametersLiteral{
// HEFloatRealParamsN15QP880 is an example parameter set for the `hefloat` package with logN=15 and logQP=881.
// These parameters instantiate `hefloat` over the real field with N SIMD slots.
HEFloatRealParamsN15QP881 = hefloat.ParametersLiteral{
LogN: 15,
LogQ: []int{51, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40},
LogP: []int{50, 50, 50},
@@ -167,9 +167,9 @@ var (
RingType: ring.ConjugateInvariant,
}
// CKKSRealParamsPN16QP1761 is an example parameter set for the `ckks` package with logN=16 and logQP = 1761
// These parameters instantiate `ckks` over the real field with N SIMD slots.
CKKSRealParamsPN16QP1761 = ckks.ParametersLiteral{
// HEFloatRealParamsPN16QP1761 is an example parameter set for the `hefloat` package with logN=16 and logQP = 1761
// These parameters instantiate `hefloat` over the real field with N SIMD slots.
HEFloatRealParamsPN16QP1761 = hefloat.ParametersLiteral{
LogN: 16,
LogQ: []int{56, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45},
LogP: []int{55, 55, 55, 55},
@@ -178,10 +178,10 @@ var (
}
)
var BGVParams = []bgv.ParametersLiteral{BGVParamsN12QP109, BGVParamsN13QP218, BGVParamsN14QP438, BGVParamsN15QP880}
var HEIntParams = []heint.ParametersLiteral{HEIntParamsN12QP109, HEIntParamsN13QP218, HEIntParamsN14QP438, HEIntParamsN15QP880}
var BGVScaleInvariantParams = []bgv.ParametersLiteral{BGVScaleInvariantParamsN12QP109, BGVScaleInvariantParamsN13QP218, BGVScaleInvariantParamsN14QP438, BGVScaleInvariantParamsN15QP880}
var HEIntScaleInvariantParams = []heint.ParametersLiteral{HEIntScaleInvariantParamsN12QP109, HEIntScaleInvariantParamsN13QP218, HEIntScaleInvariantParamsN14QP438, HEIntScaleInvariantParamsN15QP880}
var CKKSComplexParams = []ckks.ParametersLiteral{CKKSComplexParamsN12QP109, CKKSComplexParamsN13QP218, CKKSComplexParamsN14QP438, CKKSComplexParamsN15QP881, CKKSComplexParamsPN16QP1761}
var HEFloatComplexParams = []hefloat.ParametersLiteral{HEFloatComplexParamsN12QP109, HEFloatComplexParamsN13QP218, HEFloatComplexParamsN14QP438, HEFloatComplexParamsN15QP881, HEFloatComplexParamsPN16QP1761}
var CKKSRealParams = []ckks.ParametersLiteral{CKKSRealParamsN12QP109, CKKSRealParamsN13QP218, CKKSRealParamsN14QP438, CKKSRealParamsN15QP881, CKKSRealParamsPN16QP1761}
var HEFloatRealParams = []hefloat.ParametersLiteral{HEFloatRealParamsN12QP109, HEFloatRealParamsN13QP218, HEFloatRealParamsN14QP438, HEFloatRealParamsN15QP881, HEFloatRealParamsPN16QP1761}
+10 -9
View File
@@ -6,10 +6,11 @@ import (
"math"
"math/bits"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils/sampling"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/ring"
)
var flagShort = flag.Bool("short", false, "run the example with a smaller and insecure ring degree.")
@@ -57,7 +58,7 @@ func obliviousRiding() {
// Parameters (128 bit security) with plaintext modulus 65929217
// Creating encryption parameters from a default params with logN=14, logQP=438 with a plaintext modulus T=65929217
params, err := bgv.NewParametersFromLiteral(bgv.ParametersLiteral{
params, err := heint.NewParametersFromLiteral(heint.ParametersLiteral{
LogN: 14,
LogQ: []int{56, 55, 55, 54, 54, 54},
LogP: []int{55, 55},
@@ -67,7 +68,7 @@ func obliviousRiding() {
panic(err)
}
encoder := bgv.NewEncoder(params)
encoder := heint.NewEncoder(params)
// Rider's keygen
kgen := rlwe.NewKeyGenerator(params)
@@ -78,7 +79,7 @@ func obliviousRiding() {
encryptorRiderPk := rlwe.NewEncryptor(params, riderPk)
encryptorRiderSk := rlwe.NewEncryptor(params, riderSk)
evaluator := bgv.NewEvaluator(params, nil)
evaluator := heint.NewEvaluator(params, nil)
fmt.Println("============================================")
fmt.Println("Homomorphic computations on batched integers")
@@ -108,7 +109,7 @@ func obliviousRiding() {
Rider[(i<<1)+1] = riderPosY
}
riderPlaintext := bgv.NewPlaintext(params, params.MaxLevel())
riderPlaintext := heint.NewPlaintext(params, params.MaxLevel())
if err := encoder.Encode(Rider, riderPlaintext); err != nil {
panic(err)
}
@@ -121,7 +122,7 @@ func obliviousRiding() {
driversData[i] = make([]uint64, 1<<params.LogN())
driversData[i][(i << 1)] = ring.RandUniform(prng, maxvalue, mask)
driversData[i][(i<<1)+1] = ring.RandUniform(prng, maxvalue, mask)
driversPlaintexts[i] = bgv.NewPlaintext(params, params.MaxLevel())
driversPlaintexts[i] = heint.NewPlaintext(params, params.MaxLevel())
if err := encoder.Encode(driversData[i], driversPlaintexts[i]); err != nil {
panic(err)
}
@@ -6,9 +6,9 @@ import (
"math/big"
"time"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// Vectorized oblivious evaluation is a two-party protocol for the function f(x) = ax + b where a sender
@@ -90,7 +90,6 @@ func newvOLErings(params parameters) *vOLErings {
rings := new(vOLErings)
/* #nosec G115 -- logQ and N expected to not be negative */
g := ring.NewNTTFriendlyPrimesGenerator(uint64(params.logQ[1]), uint64(2*N))
// Generate logQ[0] NTT-friendly primes each close to 2^logQ[1]
@@ -10,12 +10,12 @@ import (
"fmt"
"math"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/bootstrapping"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/he/hefloat/bootstrapping"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var flagShort = flag.Bool("short", false, "run the example with a smaller and insecure ring degree.")
@@ -40,7 +40,7 @@ func main() {
// The residual parameters are the parameters used outside of the bootstrapping circuit.
// For this example, we have a LogN=16, logQ = 55 + 10*40 and logP = 3*61, so LogQP = 638.
// With LogN=16, LogQP=638 and H=192, these parameters achieve well over 128-bit of security.
params, err := ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
params, err := hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: LogN, // Log2 of the ring degree
LogQ: []int{55, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40}, // Log2 of the ciphertext prime moduli
LogP: []int{61, 61, 61}, // Log2 of the key-switch auxiliary prime moduli
@@ -66,7 +66,7 @@ func main() {
// which provides parameters which are at least 128-bit if their LogQP <= 1550.
// For this first example, we do not specify any circuit specific optional field in the bootstrapping parameters literal.
// Thus we expect the bootstrapping to give a average precision of 27.9 bits with H=192 (and 24.4 with H=N/2)
// Thus we expect the bootstrapping to give a precision of 27.25 bits with H=192 (and 23.8 with H=N/2)
// if the plaintext values are uniformly distributed in [-1, 1] for both the real and imaginary part.
// See `he/float/bootstrapping/parameters_literal.go` for detailed information about the optional fields.
btpParametersLit := bootstrapping.ParametersLiteral{
@@ -89,10 +89,10 @@ func main() {
// Now that the residual parameters and the bootstrapping parameters literals are defined, we can instantiate
// the bootstrapping parameters.
// The instantiated bootstrapping parameters store their own ckks.Parameter, which are the parameters of the
// The instantiated bootstrapping parameters store their own hefloat.Parameter, which are the parameters of the
// ring used by the bootstrapping circuit.
// The bootstrapping parameters are a wrapper of ckks.Parameters, with additional information.
// They therefore has the same API as the ckks.Parameters and we can use this API to print some information.
// The bootstrapping parameters are a wrapper of hefloat.Parameters, with additional information.
// They therefore has the same API as the hefloat.Parameters and we can use this API to print some information.
btpParams, err := bootstrapping.NewParametersFromLiteral(params, btpParametersLit)
if err != nil {
panic(err)
@@ -137,7 +137,7 @@ func main() {
sk, pk := kgen.GenKeyPairNew()
encoder := ckks.NewEncoder(params)
encoder := hefloat.NewEncoder(params)
decryptor := rlwe.NewDecryptor(params, sk)
encryptor := rlwe.NewEncryptor(params, pk)
@@ -166,7 +166,7 @@ func main() {
}
// We encrypt at level 0
plaintext := ckks.NewPlaintext(params, 0)
plaintext := hefloat.NewPlaintext(params, 0)
if err := encoder.Encode(valuesWant, plaintext); err != nil {
panic(err)
}
@@ -176,10 +176,6 @@ func main() {
if err != nil {
panic(err)
}
ciphertext2, err := encryptor.EncryptNew(plaintext)
if err != nil {
panic(err)
}
// Decrypt, print and compare with the plaintext values
fmt.Println()
@@ -191,25 +187,12 @@ func main() {
// and returns a ciphertext with the max level of `floatParamsResidualLit`.
// CAUTION: the scale of the ciphertext MUST be equal (or very close) to params.DefaultScale()
// To equalize the scale, the function evaluator.SetScale(ciphertext, parameters.DefaultScale()) can be used at the expense of one level.
// If the ciphertext is at level one or greater when given to the bootstrapper, this equalization is automatically done.
// Here we bootstrap two ciphertexts in parallel to demonstrate that evaluators are thread-safe.
resChan := make(chan *rlwe.Ciphertext, 1)
// If the ciphertext is is at level one or greater when given to the bootstrapper, this equalization is automatically done.
fmt.Println("Bootstrapping...")
go func() {
res, err := eval.Bootstrap(ciphertext2)
if err != nil {
panic(err)
}
resChan <- res
}()
res1, err := eval.Bootstrap(ciphertext1)
ciphertext2, err := eval.Bootstrap(ciphertext1)
if err != nil {
panic(err)
}
res2 := <-resChan
fmt.Println("Done")
//==================
@@ -219,11 +202,10 @@ func main() {
// Decrypt, print and compare with the plaintext values
fmt.Println()
fmt.Println("Precision of ciphertext vs. Bootstrap(ciphertext)")
printDebug(params, res1, valuesTest1, decryptor, encoder)
printDebug(params, res2, valuesTest1, decryptor, encoder)
printDebug(params, ciphertext2, valuesTest1, decryptor, encoder)
}
func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []complex128, decryptor *rlwe.Decryptor, encoder *ckks.Encoder) (valuesTest []complex128) {
func printDebug(params hefloat.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []complex128, decryptor *rlwe.Decryptor, encoder *hefloat.Encoder) (valuesTest []complex128) {
valuesTest = make([]complex128, ciphertext.Slots())
@@ -238,7 +220,7 @@ func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant
fmt.Printf("ValuesTest: %6.10f %6.10f %6.10f %6.10f...\n", valuesTest[0], valuesTest[1], valuesTest[2], valuesTest[3])
fmt.Printf("ValuesWant: %6.10f %6.10f %6.10f %6.10f...\n", valuesWant[0], valuesWant[1], valuesWant[2], valuesWant[3])
precStats := ckks.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
precStats := hefloat.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
fmt.Println(precStats.String())
fmt.Println()
@@ -14,7 +14,7 @@
// The method is described in details by Bae et al. in META-BTS: Bootstrapping Precision Beyond the Limit (https://eprint.iacr.org/2022/1167).
//
// This example assumes that the user is already familiar with the bootstrapping and its different steps.
// See the basic example `lattigo/single_party/applications/reals_bootstrapping/basics` for an introduction into the
// See the basic example `lattice/examples/he/hefloat/bootstrapping/basic` for an introduction into the
// bootstrapping.
// Use the flag -short to run the examples fast but with insecure parameters.
package main
@@ -24,13 +24,13 @@ import (
"fmt"
"math"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/bootstrapping"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/he/hefloat/bootstrapping"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var flagShort = flag.Bool("short", false, "run the example with a smaller and insecure ring degree.")
@@ -55,7 +55,7 @@ func main() {
// The residual parameters are the parameters used outside of the bootstrapping circuit.
// For this example, we have a LogN=16, logQ = (55+45) + 5*(45+45) and logP = 3*61, so LogQP = 638.
// With LogN=16, LogQP=638 and H=192, these parameters achieve well over 128-bit of security.
params, err := ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
params, err := hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: LogN, // Log2 of the ring degree
LogQ: []int{60, 45}, // Log2 of the ciphertext prime moduli
LogP: []int{61, 61, 61}, // Log2 of the key-switch auxiliary prime moduli
@@ -83,9 +83,9 @@ func main() {
// which provides parameters which are at least 128-bit if their LogQP <= 1550.
// For this first example, we do not specify any circuit specific optional field in the bootstrapping parameters literal.
// Thus we expect the bootstrapping to give an average precision of 27.9 bits with H=192 (and 24.4 with H=N/2)
// Thus we expect the bootstrapping to give a precision of 27.25 bits with H=192 (and 23.8 with H=N/2)
// if the plaintext values are uniformly distributed in [-1, 1] for both the real and imaginary part.
// See `circuits/bootstrapping/parameters_literal.go` for detailed information about the optional fields.
// See `he/float/bootstrapping/parameters_literal.go` for detailed information about the optional fields.
btpParametersLit := bootstrapping.ParametersLiteral{
// We specify LogN to ensure that both the residual parameters and the bootstrapping parameters
// have the same LogN. This is not required, but we want it for this example.
@@ -96,13 +96,13 @@ func main() {
LogP: []int{61, 61, 61, 61},
// Sets the IterationsParameters.
// The default bootstrapping parameters have 27.9 bits of average precision and
// The default bootstrapping parameters have 27.5 bits of average precision and
// ~25 bits of minimum precision, and the maximum precision that can be theoretically
// achieved is LogScale - LogN/2.
// Therefore we start with 27.9 bits and each can in theory increase the precision an additional 25 bits.
// Therefore we start with 27.5 bits and each can in theory increase the precision an additional 25 bits.
// However, to achieve the best possible precision, we must carefully adjust each iteration by hand so
// that the sum of all the minimum precision is as close as possible
// to LogScale - LogN/2. Here 27.9+25+25+5 ~= 82.5 (for the insecure parameters with LogN=13, with
// to LogScale - LogN/2. Here 27.5+25+25+5 = 82.5 (for the insecure parameters with LogN=13, with
// the secure parameters using LogN=16 achieve 82.5 - (16-13)/2 = 81 bits of precision).
IterationsParameters: &bootstrapping.IterationsParameters{
BootstrappingPrecision: []float64{25, 25, 5},
@@ -120,10 +120,10 @@ func main() {
// Now that the residual parameters and the bootstrapping parameters literals are defined, we can instantiate
// the bootstrapping parameters.
// The instantiated bootstrapping parameters store their own ckks.Parameter, which are the parameters of the
// The instantiated bootstrapping parameters store their own hefloat.Parameter, which are the parameters of the
// ring used by the bootstrapping circuit.
// The bootstrapping parameters are a wrapper of ckks.Parameters, with additional information.
// They therefore has the same API as the ckks.Parameters and we can use this API to print some information.
// The bootstrapping parameters are a wrapper of hefloat.Parameters, with additional information.
// They therefore has the same API as the hefloat.Parameters and we can use this API to print some information.
btpParams, err := bootstrapping.NewParametersFromLiteral(params, btpParametersLit)
if err != nil {
panic(err)
@@ -168,7 +168,7 @@ func main() {
sk, pk := kgen.GenKeyPairNew()
encoder := ckks.NewEncoder(params)
encoder := hefloat.NewEncoder(params)
decryptor := rlwe.NewDecryptor(params, sk)
encryptor := rlwe.NewEncryptor(params, pk)
@@ -200,7 +200,7 @@ func main() {
}
// We encrypt at level=LevelsConsumedPerRescaling-1
plaintext := ckks.NewPlaintext(params, params.LevelsConsumedPerRescaling()-1)
plaintext := hefloat.NewPlaintext(params, params.LevelsConsumedPerRescaling()-1)
if err := encoder.Encode(valuesWant, plaintext); err != nil {
panic(err)
}
@@ -240,7 +240,7 @@ func main() {
printDebug(params, ciphertext2, valuesTest1, decryptor, encoder)
}
func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []*bignum.Complex, decryptor *rlwe.Decryptor, encoder *ckks.Encoder) (valuesTest []*bignum.Complex) {
func printDebug(params hefloat.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []*bignum.Complex, decryptor *rlwe.Decryptor, encoder *hefloat.Encoder) (valuesTest []*bignum.Complex) {
valuesTest = make([]*bignum.Complex, ciphertext.Slots())
@@ -255,7 +255,7 @@ func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant
fmt.Printf("ValuesTest: %6.27f %6.27f...\n", valuesTest[0], valuesTest[1])
fmt.Printf("ValuesWant: %6.27f %6.27f...\n", valuesWant[0], valuesWant[1])
precStats := ckks.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
precStats := hefloat.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
fmt.Println(precStats.String())
fmt.Println()
@@ -7,7 +7,7 @@
// the coefficient domain.
//
// This example assumes that the user is already familiar with the bootstrapping and its different steps.
// See the basic example `lattigo/single_party/applications/reals_bootstrapping/basics` for an introduction into the
// See the basic example `lattice/examples/he/hefloat/bootstrapping/basic` for an introduction into the
// bootstrapping.
//
// The usual order of the bootstrapping operations is:
@@ -36,15 +36,14 @@ import (
"flag"
"fmt"
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/bootstrapping"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/dft"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/mod1"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/he/hefloat/bootstrapping"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var flagShort = flag.Bool("short", false, "run the example with a smaller and insecure ring degree.")
@@ -72,7 +71,7 @@ func main() {
// For the purpose of the example, only one prime is allocated to the circuit in the slots domain
// and no prime is allocated to the circuit in the coeffs domain.
LogDefaultScale := 45
LogDefaultScale := 40
q0 := []int{55} // 3) ScaleDown & 4) ModUp
qiSlotsToCoeffs := []int{39, 39, 39} // 1) SlotsToCoeffs
@@ -85,7 +84,7 @@ func main() {
LogQ = append(LogQ, qiEvalMod...)
LogQ = append(LogQ, qiCoeffsToSlots...)
params, err := ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
params, err := hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: LogN, // Log2 of the ring degree
LogQ: LogQ, // Log2 of the ciphertext modulus
LogP: []int{61, 61, 61, 61, 61}, // Log2 of the key-switch auxiliary prime moduli
@@ -102,38 +101,42 @@ func main() {
//====================================
// CoeffsToSlots parameters (homomorphic encoding)
CoeffsToSlotsParameters := dft.MatrixLiteral{
Type: dft.HomomorphicEncode,
Format: dft.RepackImagAsReal, // Returns the real and imaginary part into separate ciphertexts
CoeffsToSlotsParameters := hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicEncode,
Format: hefloat.RepackImagAsReal, // Returns the real and imaginary part into separate ciphertexts
LogSlots: params.LogMaxSlots(),
LevelQ: params.MaxLevelQ(),
LevelP: params.MaxLevelP(),
LevelStart: params.MaxLevel(),
LogBSGSRatio: 1,
Levels: []int{1, 1, 1, 1}, //qiCoeffsToSlots
}
// Parameters of the homomorphic modular reduction x mod 1
Mod1ParametersLiteral := mod1.ParametersLiteral{
LevelQ: params.MaxLevel() - CoeffsToSlotsParameters.Depth(true),
LogScale: 60, // Matches qiEvalMod
Mod1Type: mod1.CosDiscrete, // Multi-interval Chebyshev interpolation
Mod1Degree: 30, // Depth 5
DoubleAngle: 3, // Depth 3
K: 16, // With EphemeralSecretWeight = 32 and 2^{15} slots, ensures < 2^{-138.7} failure probability
LogMessageRatio: 10, // q/|m| = 2^10
Mod1InvDegree: 0, // Depth 0
Mod1ParametersLiteral := hefloat.Mod1ParametersLiteral{
LogScale: 60, // Matches qiEvalMod
Mod1Type: hefloat.CosDiscrete, // Multi-interval Chebyshev interpolation
Mod1Degree: 30, // Depth 5
DoubleAngle: 3, // Depth 3
K: 16, // With EphemeralSecretWeight = 32 and 2^{15} slots, ensures < 2^{-138.7} failure probability
LogMessageRatio: 5, // q/|m| = 2^5
Mod1InvDegree: 0, // Depth 0
LevelStart: params.MaxLevel() - len(CoeffsToSlotsParameters.Levels),
}
// Since we scale the values by 1/2^{LogMessageRatio} during CoeffsToSlots,
// we must scale them back by 2^{LogMessageRatio} after EvalMod.
// This is done by scaling the EvalMod polynomial coefficients by 2^{LogMessageRatio}.
Mod1ParametersLiteral.Scaling = math.Exp2(-float64(Mod1ParametersLiteral.LogMessageRatio))
// SlotsToCoeffs parameters (homomorphic decoding)
SlotsToCoeffsParameters := dft.MatrixLiteral{
Type: dft.HomomorphicDecode,
SlotsToCoeffsParameters := hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicDecode,
LogSlots: params.LogMaxSlots(),
Scaling: new(big.Float).SetFloat64(math.Exp2(float64(Mod1ParametersLiteral.LogMessageRatio))),
LogBSGSRatio: 1,
LevelP: params.MaxLevelP(),
Levels: []int{1, 1, 1}, // qiSlotsToCoeffs
}
SlotsToCoeffsParameters.LevelQ = len(SlotsToCoeffsParameters.Levels)
SlotsToCoeffsParameters.LevelStart = len(SlotsToCoeffsParameters.Levels)
// Custom bootstrapping.Parameters.
// All fields are public and can be manually instantiated.
@@ -147,6 +150,11 @@ func main() {
CircuitOrder: bootstrapping.DecodeThenModUp,
}
if *flagShort {
// Corrects the message ratio Q0/|m(X)| to take into account the smaller number of slots and keep the same precision
btpParams.Mod1ParametersLiteral.LogMessageRatio += 16 - params.LogN()
}
// We pring some information about the bootstrapping parameters (which are identical to the residual parameters in this example).
// We can notably check that the LogQP of the bootstrapping parameters is smaller than 1550, which ensures
// 128-bit of security as explained above.
@@ -172,7 +180,7 @@ func main() {
sk, pk := kgen.GenKeyPairNew()
encoder := ckks.NewEncoder(params)
encoder := hefloat.NewEncoder(params)
decryptor := rlwe.NewDecryptor(params, sk)
encryptor := rlwe.NewEncryptor(params, pk)
@@ -194,14 +202,14 @@ func main() {
panic(err)
}
// Generate a random plaintext with values uniformly distributed in [-1, 1] for the real and imaginary part.
// Generate a random plaintext with values uniformely distributed in [-1, 1] for the real and imaginary part.
valuesWant := make([]complex128, params.MaxSlots())
for i := range valuesWant {
valuesWant[i] = sampling.RandComplex128(-1, 1)
}
// We encrypt at level 0
plaintext := ckks.NewPlaintext(params, SlotsToCoeffsParameters.LevelQ)
plaintext := hefloat.NewPlaintext(params, SlotsToCoeffsParameters.LevelStart)
if err := encoder.Encode(valuesWant, plaintext); err != nil {
panic(err)
}
@@ -227,7 +235,7 @@ func main() {
}
// Step 2: Some circuit in the coefficient domain
// Note: the result of SlotsToCoeffs is naturally given in bit-reversed order
// Note: the result of SlotsToCoeffs is naturaly given in bit-reversed order
// In this example, we multiply by the monomial X^{N/2} (which is the imaginary
// unit in the slots domain)
if err = eval.Evaluator.Mul(ciphertext, 1i, ciphertext); err != nil {
@@ -308,7 +316,7 @@ func main() {
printDebug(params, ciphertext, valuesTest, decryptor, encoder)
}
func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []complex128, decryptor *rlwe.Decryptor, encoder *ckks.Encoder) (valuesTest []complex128) {
func printDebug(params hefloat.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []complex128, decryptor *rlwe.Decryptor, encoder *hefloat.Encoder) (valuesTest []complex128) {
slots := ciphertext.Slots()
@@ -329,7 +337,7 @@ func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant
fmt.Printf("ValuesTest: %10.14f %10.14f %10.14f %10.14f...\n", valuesTest[0], valuesTest[1], valuesTest[2], valuesTest[3])
fmt.Printf("ValuesWant: %10.14f %10.14f %10.14f %10.14f...\n", valuesWant[0], valuesWant[1], valuesWant[2], valuesWant[3])
precStats := ckks.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
precStats := hefloat.GetPrecisionStats(params, encoder, nil, valuesWant, valuesTest, 0, false)
fmt.Println(precStats.String())
fmt.Println()
@@ -13,12 +13,11 @@ import (
"math/big"
"time"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/dft"
"github.com/tuneinsight/lattigo/v6/core/rgsw/blindrot"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hebin"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
// ========================================
@@ -61,8 +60,8 @@ func main() {
// determine the complexity of the BlindRotation:
// each BlindRotation takes ~N RGSW ciphertext-ciphertext mul.
// LogN = 12 & LogQP = ~103 -> >128-bit secure.
var paramsN12 ckks.Parameters
if paramsN12, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
var paramsN12 hefloat.Parameters
if paramsN12, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: LogN,
Q: Q,
P: P,
@@ -74,8 +73,8 @@ func main() {
// BlindRotation RLWE params, N of these params determine
// the test poly degree and therefore precision.
// LogN = 11 & LogQP = ~54 -> 128-bit secure.
var paramsN11 ckks.Parameters
if paramsN11, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
var paramsN11 hefloat.Parameters
if paramsN11, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: LogN - 1,
Q: Q[:1],
P: []uint64{0x42001},
@@ -97,28 +96,26 @@ func main() {
normalization := 2.0 / (b - a) // all inputs are normalized before the BlindRotation evaluation.
// SlotsToCoeffsParameters homomorphic encoding parameters
var SlotsToCoeffsParameters = dft.MatrixLiteral{
Type: dft.HomomorphicDecode,
LogSlots: LogSlots,
Scaling: new(big.Float).SetFloat64(normalization * diffScale),
LevelQ: 1, // starting level
LevelP: 0,
Levels: []int{1}, // Decomposition levels of the encoding matrix (this will use one one matrix in one level)
var SlotsToCoeffsParameters = hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicDecode,
LogSlots: LogSlots,
Scaling: new(big.Float).SetFloat64(normalization * diffScale),
LevelStart: 1, // starting level
Levels: []int{1}, // Decomposition levels of the encoding matrix (this will use one one matrix in one level)
}
// CoeffsToSlotsParameters homomorphic decoding parameters
var CoeffsToSlotsParameters = dft.MatrixLiteral{
Type: dft.HomomorphicEncode,
LogSlots: LogSlots,
LevelQ: 1, // starting level
LevelP: 0,
Levels: []int{1}, // Decomposition levels of the encoding matrix (this will use one one matrix in one level)
var CoeffsToSlotsParameters = hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicEncode,
LogSlots: LogSlots,
LevelStart: 1, // starting level
Levels: []int{1}, // Decomposition levels of the encoding matrix (this will use one one matrix in one level)
}
fmt.Printf("Generating Test Poly... ")
now := time.Now()
// Generate test polynomial, provide function, outputscale, ring and interval.
testPoly := blindrot.InitTestPolynomial(sign, paramsN12.DefaultScale(), paramsN12.RingQ(), a, b)
testPoly := hebin.InitTestPolynomial(sign, paramsN12.DefaultScale(), paramsN12.RingQ(), a, b)
fmt.Printf("Done (%s)\n", time.Since(now))
// Index of the test poly and repacking after evaluating the BlindRotation.
@@ -134,7 +131,7 @@ func main() {
kgenN12 := rlwe.NewKeyGenerator(paramsN12)
skN12 := kgenN12.GenSecretKeyNew()
encoderN12 := ckks.NewEncoder(paramsN12)
encoderN12 := hefloat.NewEncoder(paramsN12)
encryptorN12 := rlwe.NewEncryptor(paramsN12, skN12)
decryptorN12 := rlwe.NewDecryptor(paramsN12, skN12)
@@ -146,11 +143,11 @@ func main() {
fmt.Printf("Gen SlotsToCoeffs Matrices... ")
now = time.Now()
SlotsToCoeffsMatrix, err := dft.NewMatrixFromLiteral(paramsN12, SlotsToCoeffsParameters, encoderN12)
SlotsToCoeffsMatrix, err := hefloat.NewDFTMatrixFromLiteral(paramsN12, SlotsToCoeffsParameters, encoderN12)
if err != nil {
panic(err)
}
CoeffsToSlotsMatrix, err := dft.NewMatrixFromLiteral(paramsN12, CoeffsToSlotsParameters, encoderN12)
CoeffsToSlotsMatrix, err := hefloat.NewDFTMatrixFromLiteral(paramsN12, CoeffsToSlotsParameters, encoderN12)
if err != nil {
panic(err)
}
@@ -165,15 +162,15 @@ func main() {
evk := rlwe.NewMemEvaluationKeySet(nil, kgenN12.GenGaloisKeysNew(galEls, skN12)...)
// BlindRotation Evaluator
evalBR := blindrot.NewEvaluator(paramsN12, paramsN11)
evalBR := hebin.NewEvaluator(paramsN12, paramsN11)
// Evaluator
eval := ckks.NewEvaluator(paramsN12, evk)
evalHDFT := dft.NewEvaluator(paramsN12, eval)
eval := hefloat.NewEvaluator(paramsN12, evk)
evalHDFT := hefloat.NewDFTEvaluator(paramsN12, eval)
fmt.Printf("Encrypting bits of skLWE in RGSW... ")
now = time.Now()
blindRotateKey := blindrot.GenEvaluationKeyNew(paramsN12, skN12, paramsN11, skN11, evkParams) // Generate RGSW(sk_i) for all coefficients of sk
blindRotateKey := hebin.GenEvaluationKeyNew(paramsN12, skN12, paramsN11, skN11, evkParams) // Generate RGSW(sk_i) for all coefficients of sk
fmt.Printf("Done (%s)\n", time.Since(now))
// Generates the starting plaintext values.
@@ -183,7 +180,7 @@ func main() {
values[i] = a + float64(i)*interval
}
pt := ckks.NewPlaintext(paramsN12, paramsN12.MaxLevel())
pt := hefloat.NewPlaintext(paramsN12, paramsN12.MaxLevel())
pt.LogDimensions.Cols = LogSlots
if err := encoderN12.Encode(values, pt); err != nil {
panic(err)
@@ -205,7 +202,7 @@ func main() {
ctN12.IsBatched = false
// Key-Switch from LogN = 12 to LogN = 11
ctN11 := ckks.NewCiphertext(paramsN11, 1, paramsN11.MaxLevel())
ctN11 := hefloat.NewCiphertext(paramsN11, 1, paramsN11.MaxLevel())
// key-switch to LWE degree
if err := eval.ApplyEvaluationKey(ctN12, evkN12ToN11, ctN11); err != nil {
panic(err)
@@ -214,35 +211,12 @@ func main() {
fmt.Printf("Evaluating BlindRotations... ")
now = time.Now()
// Extracts & EvalBR(LWEs, indexTestPoly)
var ctsN12 = map[int]*rlwe.Ciphertext{}
if ctsN12, err = evalBR.Evaluate(ctN11, testPolyMap, blindRotateKey); err != nil {
// Extracts & EvalBR(LWEs, indexTestPoly) on the fly -> Repack(LWEs, indexRepack) -> RLWE
ctN12, err = evalBR.EvaluateAndRepack(ctN11, testPolyMap, repackIndex, blindRotateKey, evk)
if err != nil {
panic(err)
}
fmt.Printf("Done (%s)\n", time.Since(now))
// Instantiate the repacking keys
evkRepacking := &rlwe.RingPackingEvaluationKey{
Parameters: map[int]rlwe.ParameterProvider{paramsN12.LogN(): &paramsN12},
RepackKeys: map[int]rlwe.EvaluationKeySet{paramsN12.LogN(): evk},
}
// Instantiate the repacking evaluator from the repacking keys
evalRepack := rlwe.NewRingPackingEvaluator(evkRepacking)
fmt.Printf("Evaluating Ring-Packing... ")
now = time.Now()
// Permutes the ciphertexts according to the repacking map
var ctsN12Permuted = map[int]*rlwe.Ciphertext{}
for i := range ctsN12 {
ctsN12Permuted[repackIndex[i]] = ctsN12[i]
}
// Repacks the ciphertexts
if ctN12, err = evalRepack.Repack(ctsN12Permuted); err != nil {
panic(err)
}
fmt.Printf("Done (%s)\n", time.Since(now))
ctN12.IsBatched = false
ctN12.LogDimensions = paramsN12.LogMaxDimensions()
ctN12.Scale = paramsN12.DefaultScale()
@@ -6,22 +6,21 @@ import (
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func main() {
var err error
var params ckks.Parameters
var params hefloat.Parameters
// 128-bit secure parameters enabling depth-7 circuits.
// LogN:14, LogQP: 431.
if params, err = ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
if params, err = hefloat.NewParametersFromLiteral(
hefloat.ParametersLiteral{
LogN: 14, // log2(ring degree)
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus)
LogP: []int{61}, // log2(primes P) (auxiliary modulus)
@@ -38,7 +37,7 @@ func main() {
sk := kgen.GenSecretKeyNew()
// Encoder
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
// Encryptor
enc := rlwe.NewEncryptor(params, sk)
@@ -53,13 +52,13 @@ func main() {
evk := rlwe.NewMemEvaluationKeySet(rlk)
// Evaluator
eval := ckks.NewEvaluator(params, evk)
eval := hefloat.NewEvaluator(params, evk)
// Samples values in [-K, K]
K := 25.0
// Allocates a plaintext at the max level.
pt := ckks.NewPlaintext(params, params.MaxLevel())
pt := hefloat.NewPlaintext(params, params.MaxLevel())
// Vector of plaintext values
values := make([]float64, pt.Slots())
@@ -85,10 +84,10 @@ func main() {
}
// Chebyhsev approximation of the sigmoid in the domain [-K, K] of degree 63.
poly := polynomial.NewPolynomial(GetChebyshevPoly(K, 63, sigmoid))
poly := hefloat.NewPolynomial(GetChebyshevPoly(K, 63, sigmoid))
// Instantiates the polynomial evaluator
polyEval := polynomial.NewEvaluator(params, eval)
polyEval := hefloat.NewPolynomialEvaluator(params, eval)
// Retrieves the change of basis y = scalar * x + constant
scalar, constant := poly.ChangeOfBasis()
@@ -145,7 +144,7 @@ func GetChebyshevPoly(K float64, degree int, f64 func(x float64) (y float64)) bi
}
// PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext.
func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *ckks.Encoder, dec *rlwe.Decryptor) {
func PrintPrecisionStats(params hefloat.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *hefloat.Encoder, dec *rlwe.Decryptor) {
var err error
@@ -172,5 +171,5 @@ func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []flo
fmt.Printf("...\n")
// Pretty prints the precision stats
fmt.Println(ckks.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
fmt.Println(hefloat.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
}
@@ -6,22 +6,21 @@ import (
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func main() {
var err error
var params ckks.Parameters
var params hefloat.Parameters
// 128-bit secure parameters enabling depth-7 circuits.
// LogN:14, LogQP: 431.
if params, err = ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
if params, err = hefloat.NewParametersFromLiteral(
hefloat.ParametersLiteral{
LogN: 14, // log2(ring degree)
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus)
LogP: []int{61}, // log2(primes P) (auxiliary modulus)
@@ -38,7 +37,7 @@ func main() {
sk := kgen.GenSecretKeyNew()
// Encoder
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
// Encryptor
enc := rlwe.NewEncryptor(params, sk)
@@ -53,13 +52,13 @@ func main() {
evk := rlwe.NewMemEvaluationKeySet(rlk)
// Evaluator
eval := ckks.NewEvaluator(params, evk)
eval := hefloat.NewEvaluator(params, evk)
// Samples values in [-K, K]
K := 25.0
// Allocates a plaintext at the max level.
pt := ckks.NewPlaintext(params, params.MaxLevel())
pt := hefloat.NewPlaintext(params, params.MaxLevel())
// Vector of plaintext values
values := make([]float64, pt.Slots())
@@ -85,10 +84,10 @@ func main() {
}
// Minimax approximation of the sigmoid in the domain [-K, K] of degree 63.
poly := polynomial.NewPolynomial(GetMinimaxPoly(K, 63, sigmoid))
poly := hefloat.NewPolynomial(GetMinimaxPoly(K, 63, sigmoid))
// Instantiates the polynomial evaluator
polyEval := polynomial.NewEvaluator(params, eval)
polyEval := hefloat.NewPolynomialEvaluator(params, eval)
// Retrieves the change of basis y = scalar * x + constant
scalar, constant := poly.ChangeOfBasis()
@@ -181,7 +180,7 @@ func GetMinimaxPoly(K float64, degree int, f64 func(x float64) (y float64)) bign
}
// PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext.
func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *ckks.Encoder, dec *rlwe.Decryptor) {
func PrintPrecisionStats(params hefloat.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *hefloat.Encoder, dec *rlwe.Decryptor) {
var err error
@@ -208,5 +207,5 @@ func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []flo
fmt.Printf("...\n")
// Pretty prints the precision stats
fmt.Println(ckks.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
fmt.Println(hefloat.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
}
@@ -6,22 +6,21 @@ import (
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func main() {
var err error
var params ckks.Parameters
var params hefloat.Parameters
// 128-bit secure parameters enabling depth-7 circuits.
// LogN:14, LogQP: 431.
if params, err = ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
if params, err = hefloat.NewParametersFromLiteral(
hefloat.ParametersLiteral{
LogN: 14, // log2(ring degree)
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus)
LogP: []int{61}, // log2(primes P) (auxiliary modulus)
@@ -38,7 +37,7 @@ func main() {
sk := kgen.GenSecretKeyNew()
// Encoder
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
// Encryptor
enc := rlwe.NewEncryptor(params, sk)
@@ -53,13 +52,13 @@ func main() {
evk := rlwe.NewMemEvaluationKeySet(rlk)
// Evaluator
eval := ckks.NewEvaluator(params, evk)
eval := hefloat.NewEvaluator(params, evk)
// Samples values in [-K, K]
K := 25.0
// Allocates a plaintext at the max level.
pt := ckks.NewPlaintext(params, params.MaxLevel())
pt := hefloat.NewPlaintext(params, params.MaxLevel())
// Vector of plaintext values
values := make([]float64, pt.Slots())
@@ -111,8 +110,8 @@ func main() {
}
// Vectorized Chebyhsev approximation of g0(x) and g1(x) in the domain [-K, K] of degree 63.
var polys polynomial.PolynomialVector
if polys, err = polynomial.NewPolynomialVector([]bignum.Polynomial{
var polys hefloat.PolynomialVector
if polys, err = hefloat.NewPolynomialVector([]bignum.Polynomial{
GetChebyshevPoly(K, 63, g0),
GetChebyshevPoly(K, 63, g1),
}, mapping); err != nil {
@@ -120,7 +119,7 @@ func main() {
}
// Instantiates the polynomial evaluator
polyEval := polynomial.NewEvaluator(params, eval)
polyEval := hefloat.NewPolynomialEvaluator(params, eval)
// Retrieves the vectorized change of basis y = scalar * x + constant
scalar, constant := polys.ChangeOfBasis(ct.Slots())
@@ -178,7 +177,7 @@ func GetChebyshevPoly(K float64, degree int, f64 func(x float64) (y float64)) bi
}
// PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext.
func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *ckks.Encoder, dec *rlwe.Decryptor) {
func PrintPrecisionStats(params hefloat.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *hefloat.Encoder, dec *rlwe.Decryptor) {
var err error
@@ -205,5 +204,5 @@ func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []flo
fmt.Printf("...\n")
// Pretty prints the precision stats
fmt.Println(ckks.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
fmt.Println(hefloat.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
}
@@ -6,10 +6,10 @@ import (
"fmt"
"time"
"github.com/tuneinsight/lattigo/v6/core/rgsw/blindrot"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hebin"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
)
// Function to evaluate
@@ -59,7 +59,7 @@ func main() {
slots := 32
// Test poly
testPoly := blindrot.InitTestPolynomial(sign, rlwe.NewScale(scaleBR), paramsBR.RingQ(), -1, 1)
testPoly := hebin.InitTestPolynomial(sign, rlwe.NewScale(scaleBR), paramsBR.RingQ(), -1, 1)
// Index map of which test poly to evaluate on which slot
testPolyMap := make(map[int]*ring.Poly)
@@ -98,13 +98,13 @@ func main() {
}
// Evaluator for the Blind Rotations
eval := blindrot.NewEvaluator(paramsBR, paramsLWE)
eval := hebin.NewEvaluator(paramsBR, paramsLWE)
// Secret of the RGSW ciphertexts encrypting the bits of skLWE
skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew()
// Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR
blindeRotateKey := blindrot.GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams)
blindeRotateKey := hebin.GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams)
// Evaluation of BlindRotate(ctLWE) = testPoly(X) * X^{dec{ctLWE}}
// Returns one RLWE sample per slot in ctLWE
+10 -10
View File
@@ -4,20 +4,20 @@ package main
import (
"fmt"
"math/rand"
"slices"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/utils"
)
func main() {
var err error
var params bgv.Parameters
var params heint.Parameters
// 128-bit secure parameters enabling depth-7 circuits.
// LogN:14, LogQP: 431.
if params, err = bgv.NewParametersFromLiteral(
bgv.ParametersLiteral{
if params, err = heint.NewParametersFromLiteral(
heint.ParametersLiteral{
LogN: 14, // log2(ring degree)
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus)
LogP: []int{61}, // log2(primes P) (auxiliary modulus)
@@ -33,7 +33,7 @@ func main() {
sk := kgen.GenSecretKeyNew()
// Encoder
ecd := bgv.NewEncoder(params)
ecd := heint.NewEncoder(params)
// Encryptor
enc := rlwe.NewEncryptor(params, sk)
@@ -58,7 +58,7 @@ func main() {
// Default rlwe.MetaData:
// - IsBatched = true (slots encoding)
// - Scale = params.DefaultScale()
pt := bgv.NewPlaintext(params, params.MaxLevel())
pt := heint.NewPlaintext(params, params.MaxLevel())
// Encodes the vector of plaintext values
if err = ecd.Encode(values, pt); err != nil {
@@ -79,7 +79,7 @@ func main() {
}
// PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext.
func PrintPrecisionStats(params bgv.Parameters, ct *rlwe.Ciphertext, want []uint64, ecd *bgv.Encoder, dec *rlwe.Decryptor) {
func PrintPrecisionStats(params heint.Parameters, ct *rlwe.Ciphertext, want []uint64, ecd *heint.Encoder, dec *rlwe.Decryptor) {
var err error
@@ -105,7 +105,7 @@ func PrintPrecisionStats(params bgv.Parameters, ct *rlwe.Ciphertext, want []uint
}
fmt.Printf("...\n")
if !slices.Equal(want, have) {
if !utils.EqualSlice(want, have) {
panic("wrong result: bad decryption or encrypted/plaintext circuits do not match")
}
}
+9 -9
View File
@@ -5,18 +5,18 @@ import (
"fmt"
"math/rand"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
)
func main() {
var err error
var params ckks.Parameters
var params hefloat.Parameters
// 128-bit secure parameters enabling depth-7 circuits.
// LogN:14, LogQP: 431.
if params, err = ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
if params, err = hefloat.NewParametersFromLiteral(
hefloat.ParametersLiteral{
LogN: 14, // log2(ring degree)
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus)
LogP: []int{61}, // log2(primes P) (auxiliary modulus)
@@ -32,7 +32,7 @@ func main() {
sk := kgen.GenSecretKeyNew()
// Encoder
ecd := ckks.NewEncoder(params)
ecd := hefloat.NewEncoder(params)
// Encryptor
enc := rlwe.NewEncryptor(params, sk)
@@ -56,7 +56,7 @@ func main() {
// Default rlwe.MetaData:
// - IsBatched = true (slots encoding)
// - Scale = params.DefaultScale()
pt := ckks.NewPlaintext(params, params.MaxLevel())
pt := hefloat.NewPlaintext(params, params.MaxLevel())
// Encodes the vector of plaintext values
if err = ecd.Encode(values, pt); err != nil {
@@ -77,7 +77,7 @@ func main() {
}
// PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext.
func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *ckks.Encoder, dec *rlwe.Decryptor) {
func PrintPrecisionStats(params hefloat.Parameters, ct *rlwe.Ciphertext, want []float64, ecd *hefloat.Encoder, dec *rlwe.Decryptor) {
var err error
@@ -104,5 +104,5 @@ func PrintPrecisionStats(params ckks.Parameters, ct *rlwe.Ciphertext, want []flo
fmt.Printf("...\n")
// Pretty prints the precision stats
fmt.Println(ckks.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
fmt.Println(hefloat.GetPrecisionStats(params, ecd, dec, have, want, 0, false).String())
}
+153 -146
View File
@@ -5,12 +5,11 @@ import (
"math/cmplx"
"math/rand"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/lintrans"
"github.com/tuneinsight/lattigo/v6/circuits/ckks/polynomial"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
func main() {
@@ -18,13 +17,12 @@ func main() {
// Introduction
// ============
//
// This example showcase the capabilities of encrypted fixed-point approximate arithmetic over the reals/complexes
// using the implementation of the CKKS scheme as part of the `schemes/ckks` package.
// This example showcase the capabilities of encrypted fixed-point approximate arithmetic over the reals/complexes.
//
// The Lattigo library is a library designed around layers.
// The Lattice library is a library designed around layers.
// Each layer is a package that provides functionalities for the layers above it.
//
// The `ckks` package relies on the `rlwe` package, which themselves relies on the `ring` package: `ring` -> `rlwe` -> `ckks`.
// The `he/float` package relies on the `ckks` and `rlwe` packages, which themselves relies on the `ring` package: `ring` -> `rlwe` -> `ckks` -> `he/float`.
//
// The lowest layer is the `ring` package.
// The `ring` package provides optimized arithmetic in rings `Z_{Q}[X]/(X^{N}+1)` for `N` a power of two and
@@ -36,33 +34,35 @@ func main() {
// All objects that are not specific to the CKKS scheme will be imported from the `rlwe` package.
// Such objects notably `rlwe.Plaintext`, `rlwe.Ciphertext`, `rlwe.SecretKey`, `rlwe.PublicKey` and `rlwe.EvaluationKey`.
// But also an `rlwe.Evaluator` for all operations that are not scheme specific, such as gadget product and automorphisms,
// but also more advanced operations such as the `Trace`.
// but also more advanced operations such as the `Trace`.
//
// The top layer is the `ckks` package.
// This package implements the CKKS scheme, and mostly consists in defining the encoding and scheme specific homomorphic operations.
// This package implements the CKKS scheme, and mostly consist in defining the encoding and scheme specific homomorphic operations.
//
// The user facing layer is the `he/float` package which implements high level functionalities and provides the a user with a
// friendly API for the homomorphic operations.
// =======================================================
// `rlwe.Ciphertext`, `rlwe.Plaintext` and `rlwe.MetaData`
// `rlwe.Ciphertert`, `rlwe.Plaintext` and `rlwe.MetaData`
// =======================================================
//
// Before talking about the capabilities of the `ckks` package, we have to give some information about the `rlwe.Ciphertext` and `rlwe.Plaintext` objects.
// Before talking about the capabilities of the `he/float` package, we have to give some information about the `rlwe.Ciphertext` and `rlwe.Plaintext` objects.
//
// Both contain the `rlwe.MetaData` struct, which notably holds the following fields:
// - `Scale`: the scaling factor. This field is updated dynamically during computations.
// - `EncodingDomain`:
// - `SlotsDomain`: the usual encoding that provides SIMD operations over the slots.
// - `CoefficientDomain`: plain encoding in the RING. Addition behaves as usual, but multiplication will result in a negacyclic convolution over the slots.
// - `CoefficientDomain`: plain encoding in the RING. Addition behaves as usual, but multiplication will result in negacyclic convolution over the slots.
// - `LogSlots`: the log2 of the number of slots. Note that if a ciphertext with n slots is multiplied with a ciphertext of 2n slots, the resulting ciphertext
// will have 2n slots. Because a message `m` of n slots is identical to the message `m|m` of 2n slots.
//
// These are all public fields which can be manually edited by advanced users if needed.
//
// ======================================================
// Capabilities of the CKKS Package in the Lattigo Library
// Capabilities of the HE/FLOAT Package in the Lattice Library
// ======================================================
//
// The current capabilities of the `schemes/ckks` package are the following:
// The current capabilities of the `he/float` package are the following:
//
// - Encoding: encode vectors of type `[]complex128`, `[]float64`, `[]*big.Float` or `[]*bignum.Complex` on `rlwe.Plaintext`
//
@@ -86,9 +86,6 @@ func main() {
//
// - Rotations & Conjugation
//
// On top of the `ckks` package resides the `circuits` module in which specific circuits based on the schemes in `schemes` are implemented.
// The most salient for the CKKS cryptosystem being:
//
// - Polynomial Evaluation:
// - `Single polynomial`: evaluate the same polynomial on all slots of a `rlwe.Ciphertext`
// - `Vector polynomial`: evaluate different polynomials on different slots of a `rlwe.Ciphertext`
@@ -102,26 +99,26 @@ func main() {
//
// - All methods of the `rlwe.Evaluator`, which are not described here.
//
// The `circuits/bootstrapping` package also contains the sub-packages: `bootstrapper` which implements bootstrapping to refresh ciphertexts, enabling arbitrary depth circuits.
// The `he/float` package also contains the sub-packages: `bootstrapper` which implements bootstrapping to refresh ciphertexts, enabling arbitrary depth circuits.
//
// Note that the package `he/float` also supports a real variant, i.e. plaintext vector of R^{N} (instead of complex vectors C^{N/2}).
// A homomorphic bridge between the two schemes is also available.
// This variant can be activated by specifying the `ring.Type` to `ring.ConjugateInvariant` (i.e the ring Z[X + X^{-1}]/(X^{N}+1)) in the `ckks.Parameters` struct.
// This variant can be activated by specifying the `ring.Type` to `ring.ConjugateInvariant` (i.e the ring Z[X + X^{-1}]/(X^{N}+1)) in the `hefloat.Parameters` struct.
// =================================
// Instantiating the ckks.Parameters
// Instantiating the hefloat.Parameters
// =================================
//
// We will instantiate a `ckks.Parameters` struct.
// Unlike other libraries, `Lattigo` doesn't have, yet, a quick constructor.
// We will instantiate a `hefloat.Parameters` struct.
// Unlike other libraries, `Lattice` doesn't have, yet, a quick constructor.
// Users must specify all parameters, up to each individual prime size.
//
// We will create parameters that are 128-bit secure and allow a depth 7 computation with a scaling factor of 2^{45}.
var err error
var params ckks.Parameters
if params, err = ckks.NewParametersFromLiteral(
ckks.ParametersLiteral{
var params hefloat.Parameters
if params, err = hefloat.NewParametersFromLiteral(
hefloat.ParametersLiteral{
LogN: 14, // A ring degree of 2^{14}
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // An initial prime of 55 bits and 7 primes of 45 bits
LogP: []int{61}, // The log2 size of the key-switching prime
@@ -131,17 +128,17 @@ func main() {
}
// The ratio between the first prime of size ~2^{55} and the scaling factor 2^{45} is ~2^{10}.
// This means that these parameters can accommodate for values as large as 2^{9} (signed values).
// This means that these parameter can accommodate for values as large as 2^{9} (signed values).
// To be able to store larger values, either the scale has to be reduced or the first prime increased.
// Because the maximum size for the primes of the modulus Q is 60, if we want to store larger values
// with precision, we will need to reserve the first two primes.
// We get the encoding precision of the parameters in bits, which is max(53, log2(DefaultScale)).
// We get the encoding precision of the parameters in bits, which is min(53, log2(DefaultScale)).
// It is always at least 53 (double float precision).
// This precision is notably the precision used by the encoder to encode/decode values.
prec := params.EncodingPrecision() // we will need this value later
// Note that the following fields in the `ckks.ParametersLiteral`are optional, but can be manually specified by advanced users:
// Note that the following fields in the `hefloat.ParametersLiteral`are optional, but can be manually specified by advanced users:
// - `Xs`: the secret distribution (default uniform ternary)
// - `Xe`: the error distribution (default discrete Gaussian with standard deviation of 3.2 and truncated to 19)
// - `PowBase`: the log2 of the binary decomposition (default 0, i.e. infinity, i.e. no decomposition)
@@ -175,11 +172,11 @@ func main() {
// ====================
//
// We use the default number of slots, which is N/2.
// It is possible to use less slots, however in most situations, there is no reason to do so.
// It is possible to use less slots, however it most situations, there is no reason to do so.
LogSlots := params.LogMaxSlots()
Slots := 1 << LogSlots
// We generate a vector of `[]complex128` with both the real and imaginary parts uniformly distributed in [-1, 1]
// We generate a vector of `[]complex128` with both the real and imaginary part uniformly distributed in [-1, 1]
/* #nosec G404 -- this is a plaintext vector */
r := rand.New(rand.NewSource(0))
values1 := make([]complex128, Slots)
@@ -194,14 +191,14 @@ func main() {
// - `EncodingDomain`: `rlwe.SlotsDomain` (this is the default value)
// - `LogSlots`: `params.MaxLogSlots` (which is LogN-1=13 in this example)
// We can check that the plaintext was created at the maximum level with pt1.Level().
pt1 := ckks.NewPlaintext(params, params.MaxLevel())
pt1 := hefloat.NewPlaintext(params, params.MaxLevel())
// Then we need to instantiate the encoder, which will enable us to embed our `values` of type `[]complex128` on a `rlwe.Plaintext`.
// By default the encoder will use the params.DefaultPrecision(), but a user can specify a custom precision as an optional argument,
// for example `ckks.NewEncoder(params, 256)`.
ecd := ckks.NewEncoder(params)
// for example `hefloat.NewEncoder(params, 256)`.
ecd := hefloat.NewEncoder(params)
ecd2 := ckks.NewEncoder(ckks.Parameters(params))
ecd2 := hefloat.NewEncoder(hefloat.Parameters(params))
// And we encode our `values` on the plaintext.
// Note that the encoder will check the metadata of the plaintext and adapt the encoding accordingly.
@@ -227,7 +224,7 @@ func main() {
}
// It is also possible to first allocate the ciphertext the same way it was done
// for the plaintext with `ct := ckks.NewCiphertext(params, 1, pt.Level())`,
// for the plaintext with with `ct := hefloat.NewCiphertext(params, 1, pt.Level())`,
// enabling allocation free encryptions (for example if the ciphertext has to be
// serialized right away).
@@ -245,7 +242,7 @@ func main() {
// ================
//
// Before anything, we must instantiate the evaluator, and we provide the evaluation key struct.
eval := ckks.NewEvaluator(params, evk)
eval := hefloat.NewEvaluator(params, evk)
// For the purpose of the example, we will create a second vector of random values.
values2 := make([]complex128, Slots)
@@ -253,7 +250,7 @@ func main() {
values2[i] = complex(2*r.Float64()-1, 2*r.Float64()-1)
}
pt2 := ckks.NewPlaintext(params, params.MaxLevel())
pt2 := hefloat.NewPlaintext(params, params.MaxLevel())
// ===========================
// Managing the Scaling Factor
@@ -276,11 +273,11 @@ func main() {
// The rescaling operation divides a ciphertext by the prime of its current level and
// returns a new ciphertext with one less level and scaling factor divided by this prime.
//
// The main difficulty arises from the primes used for rescaling as they do not
// The main difficulty arises from the primes used for the rescaling, since they do not
// divide the scaling factor.
//
// Throughout this example we will show ways to properly manage this scaling factor to
// keep it as close as possible to the default scaling factor (in this example 2^{45}) while
// Throughout this example we will show ways to properly manage this scaling factor to both
// keep it as close as possible to the default scaling factor (in this example 2^{45}) and
// minimizing the error.
// In fact we will show that it is usually possible to keep the scaling factor always at 2^{45},
// even though the primes are not powers of two.
@@ -289,10 +286,10 @@ func main() {
fmt.Printf("ADDITION\n")
fmt.Printf("========\n")
fmt.Printf("\n")
// Addition is often seen as a trivial operation.
// Additions are often seen as a trivial operation.
// However in the case of the full-RNS implementation we have to be careful.
// Indeed, we must ensure that when adding two ciphertexts, those ciphertexts have the same exact scale,
// else an error proportional to the difference of the scales will be introduced.
// else an error proportional to the difference of the scale will be introduced.
//
// The evaluator will try to compensate if the ciphertexts do not have the same scale,
// but only up to an integer multiplication (which is "free").
@@ -300,7 +297,7 @@ func main() {
// then the evaluator will take that into account and properly operate the addition.
//
// However, if one of the scales is a fraction of the other (e.g. 2^{45} * q0 and 2^{45} * q1),
// the evaluator isn't able to reconcile the scales and will treat the ciphertext with the
// the evaluator isn't able to reconciliate the scales and will treat the ciphertext with the
// smallest scale as being at the scale of the largest one.
//
// This will introduce an approximation error proportional to q^{45} * q0 / 2^{45} * q1 = q0/q1 in the addition.
@@ -325,20 +322,20 @@ func main() {
// A small comment about the precision stats.
// Theses stats show the -log2 of the matching bits on the right side of the decimal point.
// Because values are not normalized, large values will show as having a low precision, even if left side of the decimal point (integer part) is correct.
// Because values are not normalized, large values will show as having a low precision, even if left side of of the decimal point (integer part) is correct.
// Eventually this will be fixed, by normalizing with the maximum value decrypted.
ct3, err := eval.AddNew(ct1, ct2)
if err != nil {
panic(err)
}
fmt.Printf("Addition - ct + ct%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Addition - ct + ct%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// ciphertext + plaintext
ct3, err = eval.AddNew(ct1, pt2)
if err != nil {
panic(err)
}
fmt.Printf("Addition - ct + pt%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Addition - ct + pt%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// ciphertext + vector
// Note that the evaluator will encode this vector at the scale of the input ciphertext to ensure a noiseless addition.
@@ -346,7 +343,7 @@ func main() {
if err != nil {
panic(err)
}
fmt.Printf("Addition - ct + vector%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Addition - ct + vector%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// ciphertext + scalar
scalar := 3.141592653589793 + 1.4142135623730951i
@@ -359,7 +356,7 @@ func main() {
if err != nil {
panic(err)
}
fmt.Printf("Addition - ct + scalar%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Addition - ct + scalar%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("==============\n")
fmt.Printf("MULTIPLICATION\n")
@@ -370,7 +367,7 @@ func main() {
want[i] = values1[i] * values2[i]
}
// We could simply call the multiplication on ct1 and ct2, however since a rescaling is needed afterwards,
// We could simply call the multiplication on ct1 and ct2, however since a rescaling is needed afterward,
// we also want to properly control the scale of the result.
// Our goal is to keep the scale to the default one, i.e. 2^{45} in this example.
// However, the rescaling operation divides by one (or multiple) primes qi,
@@ -391,7 +388,7 @@ func main() {
panic(err)
}
// and we encrypt (recall that the metadata of the plaintext are copied on the created ciphertext).
// and we encrypt (recall that the metadata of the plaintext are copied on the created ciphertext)
if err := enc.Encrypt(pt2, ct2); err != nil {
panic(err)
}
@@ -401,8 +398,8 @@ func main() {
panic(err)
}
// The scaling factor of res should be equal to ct1.Scale * ct2.Scale.
ctScale := &res.Scale.Value // We need to access the pointer in order for it to display correctly in the command line.
// The scaling factor of res should be equal to ct1.Scale * ct2.Scale
ctScale := &res.Scale.Value // We need to access the pointer to have it display correctly in the command line
fmt.Printf("Scale before rescaling: %f\n", ctScale)
// To control the growth of the scaling factor, we call the rescaling operation.
@@ -416,30 +413,30 @@ func main() {
Scale := params.DefaultScale().Value
// And we check that we are back on our feet with a scale of 2^{45} but with one less level.
// And we check that we are back on our feet with a scale of 2^{45} but with one less level
fmt.Printf("Scale after rescaling: %f == %f: %t and %d == %d+1: %t\n", ctScale, &Scale, ctScale.Cmp(&Scale) == 0, ct1.Level(), res.Level(), ct1.Level() == res.Level()+1)
fmt.Printf("\n")
// For the sake of conciseness, we will not rescale the output for the other multiplication example.
// But this maintenance operation should usually be called (either before of after the multiplication depending on the choice of noise management)
// to control the magnitude of the plaintext scale.
fmt.Printf("Multiplication - ct * ct%s", ckks.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
fmt.Printf("Multiplication - ct * ct%s", hefloat.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
// ciphertext + plaintext
ct3, err = eval.MulRelinNew(ct1, pt2)
if err != nil {
panic(err)
}
fmt.Printf("Multiplication - ct * pt%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Multiplication - ct * pt%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// ciphertext + vector
// Note that when giving non-encoded vectors, the evaluator will internally encode this vector with the appropriate scale that ensure that
// the following rescaling operation will make the resulting ciphertext fall back on its previous scale.
// the following rescaling operation will make the resulting ciphertext fall back on it's previous scale.
ct3, err = eval.MulRelinNew(ct1, values2)
if err != nil {
panic(err)
}
fmt.Printf("Multiplication - ct * vector%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Multiplication - ct * vector%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// ciphertext + scalar (scalar = pi + sqrt(2) * i)
for i := 0; i < Slots; i++ {
@@ -448,26 +445,26 @@ func main() {
// Similarly, when giving a scalar, the scalar is encoded with the appropriate scale to get back to the original ciphertext scale after the rescaling.
// Additionally, the multiplication with a Gaussian integer does not increase the scale of the ciphertext, thus does not require rescaling and does not consume a level.
// For example, multiplication/division by the imaginary unit `i` is free in terms of level consumption and can be used without moderation.
// For example, multiplication/division by the imaginary unit `i` is free in term of level consumption and can be used without moderation.
ct3, err = eval.MulRelinNew(ct1, scalar)
if err != nil {
panic(err)
}
fmt.Printf("Multiplication - ct * scalar%s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Multiplication - ct * scalar%s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("======================\n")
fmt.Printf("ROTATION & CONJUGATION\n")
fmt.Printf("======================\n")
fmt.Printf("\n")
// Before being able to do any rotation, we need to generate the corresponding Galois keys.
// A Galois key is a special type of `rlwe.EvaluationKey` that enables automorphisms X^{i} -> X^{i*k mod 2N} mod X^{N} + 1 on ciphertexts.
// Before being able to do any rotations, we need to generate the corresponding Galois keys.
// A Galois key is a special type of `rlwe.EvaluationKey` that enables automorphisms X^{i} -> X^{i*k mod 2N} mod X^{N} + 1 on ciphertext
// Some of these automorphisms act like cyclic rotations on plaintext encoded in the `SlotsDomain`.
//
// Galois keys can be large depending on the parameters, and one Galois key is needed per automorphism.
// Therefore it is important to design circuits that minimize the numbers of these keys.
//
// In this example we will rotate a ciphertext by 5 positions to the left and get its complex conjugate as well.
// In this example we will rotate a ciphertext by 5 positions to the left, as well as get the complex conjugate.
// This corresponds to the following values for k which we call "galois elements":
rot := 5
galEls := []uint64{
@@ -490,7 +487,7 @@ func main() {
if err != nil {
panic(err)
}
fmt.Printf("Rotation by k=%d %s", rot, ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Rotation by k=%d %s", rot, hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// Conjugation
for i := 0; i < Slots; i++ {
@@ -501,11 +498,11 @@ func main() {
if err != nil {
panic(err)
}
fmt.Printf("Conjugation %s", ckks.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
fmt.Printf("Conjugation %s", hefloat.GetPrecisionStats(params, ecd, dec, want, ct3, 0, false).String())
// Note that rotations and conjugation only add a fixed additive noise independent of the ciphertext noise.
// If the parameters are set correctly, this noise can be rounding error (thus negligible).
// It is recommended to apply the rescaling operation after such operations rather than before.
// It is recommended apply the rescaling operation after such operations rather than before.
// This way, the noise is added in the lower bits of the ciphertext and gets erased by the rescaling.
fmt.Printf("=====================\n")
@@ -530,7 +527,7 @@ func main() {
// With 7 levels, we can evaluate a polynomial of degree up to 127.
// However, since we will be in the Chebyshev basis, we must also take into consideration the change of basis
// y = (2*x - a - b)/(b-a), which will usually consume a level.
// Often it is however possible to include this linear transformation in a previous step of the circuit, to save a level.
// Often it is however possible to include this linear transformation in previous step of a circuit, to save a level.
// Since we do not have any previous operation in this example, we will have to operate the change of basis, thus
// the maximum polynomial degree for depth 6 is 63.
@@ -540,16 +537,16 @@ func main() {
B: *bignum.NewFloat(8, prec),
}
// We generate the `bignum.Polynomial` which stores the degree 63 Chevyshev approximation of the SiLU function in the interval [-8, 8].
// We generate the `bignum.Polynomial` which stores the degree 63 Chevyshev approximation of the SiLU function in the interval [-8, 8]
poly := bignum.ChebyshevApproximation(SiLU, interval)
// The struct `bignum.Polynomial` comes with an handy evaluation method.
// The struct `bignum.Polynomial` comes with an handy evaluation method
tmp := bignum.NewComplex().SetPrec(prec)
for i := 0; i < Slots; i++ {
want[i] = poly.Evaluate(tmp.SetComplex128(values1[i])).Complex128()
}
// First, we must operate the change of basis for the Chebyshev evaluation y = (2*x-a-b)/(b-a) = scalarmul * x + scalaradd.
// First, we must operate the change of basis for the Chebyshev evaluation y = (2*x-a-b)/(b-a) = scalarmul * x + scalaradd
scalarmul, scalaradd := poly.ChangeOfBasis()
res, err = eval.MulNew(ct1, scalarmul)
@@ -565,9 +562,9 @@ func main() {
panic(err)
}
polyEval := polynomial.NewEvaluator(params, eval)
polyEval := hefloat.NewPolynomialEvaluator(params, eval)
// And we evaluate this polynomial on the ciphertext.
// And we evaluate this polynomial on the ciphertext
// The last argument, `params.DefaultScale()` is the scale that we want the ciphertext
// to have after the evaluation, which is usually the default scale, 2^{45} in this example.
// Other values can be specified, but they should be close to the default scale, else the
@@ -576,29 +573,30 @@ func main() {
panic(err)
}
fmt.Printf("Polynomial Evaluation %s", ckks.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
fmt.Printf("Polynomial Evaluation %s", hefloat.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
// =============================
// Vector Polynomials Evaluation
// =============================
//
// See `examples/hefloat/polyeval`
fmt.Printf("======================\n")
fmt.Printf("LINEAR TRANSFORMATIONS\n")
fmt.Printf("======================\n")
fmt.Printf("\n")
// The `circuits/lintrans` package provides several handy linear transformations.
// The `he/float` package provides a multiple handy linear transformations.
// We will start with the inner sum.
// This method allows to aggregate `n` sub-vectors of size `batch` and it stores the result in the leftmost sub-vector of each "group".
// For example given a vector [x0, x1, x2, x3, x4, x5, x6, x7], batch = 2 and n = 4
// it will return the vector [x0+x2+x4+x6, x1+x3+x5+x7, X, X, X, X, X, X], where X marks garbage slots.
// Note that n*batch must divide the length of the vector (i.e. the number of slots).
// Thus method allows to aggregate `n` sub-vectors of size `batch`.
// For example given a vector [x0, x1, x2, x3, x4, x5, x6, x7], batch = 2 and n = 3
// it will return the vector [x0+x2+x4, x1+x3+x5, x2+x4+x6, x3+x5+x7, x4+x6+x0, x5+x7+x1, x6+x0+x2, x7+x1+x3]
// Observe that the inner sum wraps around the vector, this behavior must be taken into account.
batch := 32
n := 128
batch := 37
n := 127
// The innersum operation is carried out with log2(n) + HW(n) automorphisms and we need to
// The innersum operations is carried out with log2(n) + HW(n) automorphisms and we need to
// generate the corresponding Galois keys and provide them to the `Evaluator`.
eval = eval.WithKey(rlwe.NewMemEvaluationKeySet(rlk, kgen.GenGaloisKeysNew(params.GaloisElementsForInnerSum(batch, n), sk)...))
@@ -617,37 +615,9 @@ func main() {
// Note that this method can obviously be used to average values.
// For a good noise management, it is recommended to first multiply the values by 1/n, then
// apply the innersum and then only apply the rescaling.
fmt.Printf("Innersum %s", ckks.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
fmt.Printf("Innersum %s", hefloat.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
// Sometimes we wish to compute an inner sum on the first values of the vector only.
// In this case, n*batch does not necessarily divide the length of the vector and the RotateAndAdd function must be used instead.
// This method allows to repeatedly shift the vector by batch values and add (i.e. \sum_{i=0}^{n-1} v << (i*batch), where v is the input vector).
// For example given a vector [x0, x1, x2, x3, x4, x5, x6, x7], batch = 2 and n = 3
// it will return the vector [x0+x2+x4, x1+x3+x5, x2+x4+x6, x3+x5+x7, x4+x6+x0, x5+x7+x1, x6+x0+x2, x7+x1+x3].
// Observe that the inner sum wraps around the vector, this behavior must be taken into account.
batch = 37
n = 127
eval = eval.WithKey(rlwe.NewMemEvaluationKeySet(rlk, kgen.GenGaloisKeysNew(params.GaloisElementsForInnerSum(batch, n), sk)...))
// Plaintext circuit
copy(want, values1)
for i := 1; i < n; i++ {
for j, vi := range utils.RotateSlice(values1, i*batch) {
want[j] += vi
}
}
if err := eval.RotateAndAdd(ct1, batch, n, res); err != nil {
panic(err)
}
// Note that this method can obviously be used to average values.
// For a good noise management, it is recommended to first multiply the values by 1/n, then
// apply the inner sum and then only apply the rescaling.
fmt.Printf("RotateAndAdd %s", ckks.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
// The replicate operation is exactly the same as the rotate and add operation, but in reverse
// The replicate operation is exactly the same as the innersum operation, but in reverse
eval = eval.WithKey(rlwe.NewMemEvaluationKeySet(rlk, kgen.GenGaloisKeysNew(params.GaloisElementsForReplicate(batch, n), sk)...))
// Plaintext circuit
@@ -662,15 +632,15 @@ func main() {
panic(err)
}
fmt.Printf("Replicate %s", ckks.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
fmt.Printf("Replicate %s", hefloat.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
// And we arrive to the linear transformation.
// This method enables to evaluate arbitrary Slots x Slots matrices on a ciphertext.
// What matters is not the size of the matrix, but the number of non-zero diagonals, as
// the complexity of this operation is 2sqrt(#non-zero-diags).
//
// First, let's explain what we mean by non-zero diagonal.
// As an example, let's take the following 4x4 matrix:
// First lets explain what we mean by non-zero diagonal.
// As an example, lets take the following 4x4 matrix:
// 0 1 2 3 (diagonal index)
// | 1 2 3 0 |
// | 0 1 2 3 |
@@ -686,7 +656,7 @@ func main() {
nonZeroDiagonals := []int{-15, -4, -1, 0, 1, 2, 3, 4, 15}
// We allocate the non-zero diagonals and populate them
diagonals := make(lintrans.Diagonals[complex128])
diagonals := make(hefloat.Diagonals[complex128])
for _, i := range nonZeroDiagonals {
tmp := make([]complex128, Slots)
@@ -702,80 +672,117 @@ func main() {
// Here we use the default structs of the rlwe package, which is compliant to the rlwe.LinearTransformationParameters interface
// But a user is free to use any struct compliant to this interface.
// See the definition of the interface for more information about the parameters.
ltparams := lintrans.Parameters{
DiagonalsIndexList: diagonals.DiagonalsIndexList(),
LevelQ: ct1.Level(),
LevelP: params.MaxLevelP(),
Scale: rlwe.NewScale(params.Q()[ct1.Level()]),
LogDimensions: ct1.LogDimensions,
LogBabyStepGiantStepRatio: 1,
ltparams := hefloat.LinearTransformationParameters{
DiagonalsIndexList: diagonals.DiagonalsIndexList(),
Level: ct1.Level(),
Scale: rlwe.NewScale(params.Q()[ct1.Level()]),
LogDimensions: ct1.LogDimensions,
LogBabyStepGianStepRatio: 1,
}
// We allocated the rlwe.LinearTransformation.
// The allocation takes into account the parameters of the linear transformation.
lt := lintrans.NewTransformation(params, ltparams)
lt := hefloat.NewLinearTransformation(params, ltparams)
// We encode our linear transformation on the allocated rlwe.LinearTransformation.
// Note that trying to encode a linear transformation with different non-zero diagonals,
// Not that trying to encode a linear transformation with different non-zero diagonals,
// plaintext dimensions or baby-step giant-step ratio than the one used to allocate the
// rlwe.LinearTransformation will return an error.
if err := lintrans.Encode(ecd, diagonals, lt); err != nil {
if err := hefloat.EncodeLinearTransformation[complex128](ecd, diagonals, lt); err != nil {
panic(err)
}
// Then we generate the corresponding Galois keys.
// The list of Galois elements can also be obtained with `lt.GaloisElements`
// but this requires to have it pre-allocated, which is not always desirable.
galEls = lintrans.GaloisElements(params, ltparams)
galEls = hefloat.GaloisElementsForLinearTransformation(params, ltparams)
ltEval := lintrans.NewEvaluator(eval.WithKey(rlwe.NewMemEvaluationKeySet(rlk, kgen.GenGaloisKeysNew(galEls, sk)...)))
ltEval := hefloat.NewLinearTransformationEvaluator(eval.WithKey(rlwe.NewMemEvaluationKeySet(rlk, kgen.GenGaloisKeysNew(galEls, sk)...)))
// And we evaluate the linear transform
// And we valuate the linear transform
if err := ltEval.Evaluate(ct1, lt, res); err != nil {
panic(err)
}
// Result is not returned rescaled.
// Result is not returned rescaled
if err = eval.Rescale(res, res); err != nil {
panic(err)
}
// We evaluate the same circuit in plaintext.
want = diagonals.Evaluate(values1, newVec, add, muladd)
// We evaluate the same circuit in plaintext
want = EvaluateLinearTransform(values1, diagonals)
fmt.Printf("vector x matrix %s", ckks.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
fmt.Printf("vector x matrix %s", hefloat.GetPrecisionStats(params, ecd, dec, want, res, 0, false).String())
// =============================
// Homomorphic Encoding/Decoding
// =============================
//
// See `examples/hefloat/advanced/lut`
// ============
// Bootstrapping
// ============
//
// See `examples/hefloat/bootstrapping`
// ==========
// CONCURRENCY
// ==========
//
// In lattigo, a type's methods are thread-safe and can be called concurrently. E.g. one can use an evaluator's methods concurrently.
// Lattice does not implement low level concurrency yet.
// Currently concurrency must be done at the circuit level.
//
// By design, structs outside of the parameters are not thread safe.
// For example, one cannot use an encoder to encode concurrently on different plaintexts.
// However, all structs (for which it makes sens) have the method `ShallowCopy`, which creates
// a copy of the original struct with new internal buffers, that is safe to use concurrently.
}
func newVec(size int) []complex128 {
return make([]complex128, size)
}
// EvaluateLinearTransform evaluates a linear transform (i.e. matrix) on the input vector.
// values: the input vector
// diags: the non-zero diagonals of the linear transform
func EvaluateLinearTransform(values []complex128, diags map[int][]complex128) (res []complex128) {
func add(a, b, c []complex128) {
for i := range a {
c[i] = a[i] + b[i]
slots := len(values)
keys := utils.GetKeys(diags)
N1 := he.FindBestBSGSRatio(keys, len(values), 1)
index, _, _ := he.BSGSIndex(keys, slots, N1)
res = make([]complex128, slots)
for j := range index {
rot := -j & (slots - 1)
tmp := make([]complex128, slots)
for _, i := range index[j] {
v, ok := diags[j+i]
if !ok {
v = diags[j+i-slots]
}
a := utils.RotateSlice(values, i)
b := utils.RotateSlice(v, rot)
for i := 0; i < slots; i++ {
tmp[i] += a[i] * b[i]
}
}
tmp = utils.RotateSlice(tmp, j)
for i := 0; i < slots; i++ {
res[i] += tmp[i]
}
}
}
func muladd(a, b, c []complex128) {
for i := range a {
c[i] = c[i] + a[i]*b[i]
}
return
}
+4 -4
View File
@@ -1,11 +1,11 @@
module github.com/tuneinsight/lattigo/v6
module github.com/luxdefi/lattice/v5
go 1.21
go 1.18
require (
github.com/google/go-cmp v0.5.8
github.com/stretchr/testify v1.8.0
golang.org/x/crypto v0.31.0
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
)
@@ -15,7 +15,7 @@ require (
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+2 -2
View File
@@ -1,7 +1,7 @@
/*
Package lattigo is the open-source community-version of Tune Insight's Homomorphic Encryption library.
Package lattice is the open-source community-version of Tune Insight's Homomorphic Encryption library.
It provides a pure Go implementation of state-of-the-art Homomorphic Encryption (HE) and Multiparty Homomorphic
Encryption (MHE) schemes, enabling code-simplicity, cross-platform compatibility and easy builds, while retaining
the same performance as C++ libraries.
*/
package lattigo
package lattice
+3 -3
View File
@@ -1,9 +1,9 @@
package multiparty
package mhe
import (
"math/big"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/luxdefi/lattice/v5/ring"
)
// AdditiveShare is a type for storing additively shared values in Z_Q[X] (RNS domain).
@@ -23,7 +23,7 @@ func NewAdditiveShare(r *ring.Ring) AdditiveShare {
return AdditiveShare{Value: r.NewPoly()}
}
// NewAdditiveShareBigint instantiates a new additive share struct composed of n [big.Int] elements.
// NewAdditiveShareBigint instantiates a new additive share struct composed of n big.Int elements.
func NewAdditiveShareBigint(n int) AdditiveShareBigint {
v := make([]*big.Int, n)
for i := range v {
+2 -2
View File
@@ -1,7 +1,7 @@
package multiparty
package mhe
import (
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// CRS is an interface for Common Reference Strings.
+42 -21
View File
@@ -1,12 +1,12 @@
package multiparty
package mhe
import (
"io"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// PublicKeyGenProtocol is the structure storing the parameters and and precomputations for
@@ -26,7 +26,7 @@ type PublicKeyGenCRP struct {
Value ringqp.Poly
}
// NewPublicKeyGenProtocol creates a new [PublicKeyGenProtocol] instance
// NewPublicKeyGenProtocol creates a new PublicKeyGenProtocol instance
func NewPublicKeyGenProtocol(params rlwe.ParameterProvider) PublicKeyGenProtocol {
ckg := PublicKeyGenProtocol{}
ckg.params = *params.GetRLWEParameters()
@@ -87,43 +87,64 @@ func (ckg PublicKeyGenProtocol) AggregateShares(share1, share2 PublicKeyGenShare
ckg.params.RingQP().Add(share1.Value, share2.Value, shareOut.Value)
}
// GenPublicKey return the current aggregation of the received shares as a [rlwe.PublicKey].
// GenPublicKey return the current aggregation of the received shares as a bfv.PublicKey.
func (ckg PublicKeyGenProtocol) GenPublicKey(roundShare PublicKeyGenShare, crp PublicKeyGenCRP, pubkey *rlwe.PublicKey) {
pubkey.Value[0].Copy(roundShare.Value)
pubkey.Value[1].Copy(crp.Value)
}
// ShallowCopy creates a shallow copy of PublicKeyGenProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// PublicKeyGenProtocol can be used concurrently.
func (ckg PublicKeyGenProtocol) ShallowCopy() PublicKeyGenProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
sampler, err := ring.NewSampler(prng, ckg.params.RingQ(), ckg.params.Xe(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return PublicKeyGenProtocol{ckg.params, sampler}
}
// BinarySize returns the serialized size of the object in bytes.
func (share PublicKeyGenShare) BinarySize() int {
return share.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (share PublicKeyGenShare) WriteTo(w io.Writer) (n int64, err error) {
return share.Value.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap [io.Reader] in a pre-allocated bufio.Reader.
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (share *PublicKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) {
return share.Value.ReadFrom(r)
}
@@ -134,7 +155,7 @@ func (share PublicKeyGenShare) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [PublicKeyGenShare.MarshalBinary] or [PublicKeyGenShare.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (share *PublicKeyGenShare) UnmarshalBinary(p []byte) (err error) {
return share.Value.UnmarshalBinary(p)
}
+59 -33
View File
@@ -1,25 +1,52 @@
package multiparty
package mhe
import (
"fmt"
"io"
"slices"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
"github.com/luxdefi/lattice/v5/utils/structs"
)
// EvaluationKeyGenProtocol is the structure storing the parameters for the collective EvaluationKey generation.
type EvaluationKeyGenProtocol struct {
params rlwe.Parameters
buff [2]ringqp.Poly
gaussianSamplerQ ring.Sampler
}
// NewEvaluationKeyGenProtocol creates a [EvaluationKeyGenProtocol] instance.
// ShallowCopy creates a shallow copy of EvaluationKeyGenProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// EvaluationKeyGenProtocol can be used concurrently.
func (evkg EvaluationKeyGenProtocol) ShallowCopy() EvaluationKeyGenProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := evkg.params
Xe, err := ring.NewSampler(prng, evkg.params.RingQ(), evkg.params.Xe(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return EvaluationKeyGenProtocol{
params: evkg.params,
buff: [2]ringqp.Poly{params.RingQP().NewPoly(), params.RingQP().NewPoly()},
gaussianSamplerQ: Xe,
}
}
// NewEvaluationKeyGenProtocol creates a EvaluationKeyGenProtocol instance.
func NewEvaluationKeyGenProtocol(params rlwe.ParameterProvider) (evkg EvaluationKeyGenProtocol) {
prng, err := sampling.NewPRNG()
@@ -41,12 +68,13 @@ func NewEvaluationKeyGenProtocol(params rlwe.ParameterProvider) (evkg Evaluation
return EvaluationKeyGenProtocol{
params: pRLWE,
gaussianSamplerQ: Xe,
buff: [2]ringqp.Poly{pRLWE.RingQP().NewPoly(), pRLWE.RingQP().NewPoly()},
}
}
// AllocateShare allocates a party's share in the EvaluationKey Generation.
func (evkg EvaluationKeyGenProtocol) AllocateShare(evkParams ...rlwe.EvaluationKeyParameters) EvaluationKeyGenShare {
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(evkg.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(evkg.params, evkParams)
return evkg.allocateShare(levelQ, levelP, BaseTwoDecomposition)
}
@@ -57,7 +85,7 @@ func (evkg EvaluationKeyGenProtocol) allocateShare(levelQ, levelP, BaseTwoDecomp
// SampleCRP samples a common random polynomial to be used in the EvaluationKey Generation from the provided
// common reference string.
func (evkg EvaluationKeyGenProtocol) SampleCRP(crs CRS, evkParams ...rlwe.EvaluationKeyParameters) EvaluationKeyGenCRP {
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(evkg.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(evkg.params, evkParams)
return evkg.sampleCRP(crs, levelQ, levelP, BaseTwoDecomposition)
}
@@ -100,7 +128,7 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
return fmt.Errorf("cannot GenShare: crp.BaseRNSDecompositionVectorSize() != shareOut.BaseRNSDecompositionVectorSize()")
}
if !slices.Equal(shareOut.BaseTwoDecompositionVectorSize(), crp.BaseTwoDecompositionVectorSize()) {
if !utils.EqualSlice(shareOut.BaseTwoDecompositionVectorSize(), crp.BaseTwoDecompositionVectorSize()) {
return fmt.Errorf("cannot GenShare: crp.BaseTwoDecompositionVectorSize() != shareOut.BaseTwoDecompositionVectorSize()")
}
@@ -109,14 +137,12 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
var hasModulusP bool
buffQ := ringQ.NewPoly()
if levelP > -1 {
hasModulusP = true
ringQ.MulScalarBigint(skIn.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], buffQ)
ringQ.MulScalarBigint(skIn.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], evkg.buff[0].Q)
} else {
levelP = 0
buffQ.CopyLvl(levelQ, skIn.Value.Q)
evkg.buff[0].Q.CopyLvl(levelQ, skIn.Value.Q)
}
m := shareOut.Value
@@ -131,7 +157,7 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
var index int
for j := 0; j < slices.Max(BaseTwoDecompositionVectorSize); j++ {
for j := 0; j < utils.MaxSlice(BaseTwoDecompositionVectorSize); j++ {
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
@@ -163,7 +189,7 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
}
qi := ringQ.SubRings[index].Modulus
tmp0 := buffQ.Coeffs[index]
tmp0 := evkg.buff[0].Q.Coeffs[index]
tmp1 := mij.Q.Coeffs[index]
for w := 0; w < N; w++ {
@@ -176,7 +202,7 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
}
}
ringQ.MulScalar(buffQ, 1<<shareOut.BaseTwoDecomposition, buffQ)
ringQ.MulScalar(evkg.buff[0].Q, 1<<shareOut.BaseTwoDecomposition, evkg.buff[0].Q)
}
return
@@ -279,32 +305,32 @@ func (share EvaluationKeyGenShare) BinarySize() int {
return share.GadgetCiphertext.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (share EvaluationKeyGenShare) WriteTo(w io.Writer) (n int64, err error) {
return share.GadgetCiphertext.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap [io.Reader] in a pre-allocated bufio.Reader.
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (share *EvaluationKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) {
return share.GadgetCiphertext.ReadFrom(r)
}
@@ -315,7 +341,7 @@ func (share EvaluationKeyGenShare) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [EvaluationKeyGenShare.MarshalBinary] or [EvaluationKeyGenShare.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (share *EvaluationKeyGenShare) UnmarshalBinary(p []byte) (err error) {
return share.GadgetCiphertext.UnmarshalBinary(p)
}
+36 -29
View File
@@ -1,17 +1,19 @@
package multiparty
package mhe
import (
"bufio"
"fmt"
"io"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils/buffer"
)
// GaloisKeyGenProtocol is the structure storing the parameters for the collective GaloisKeys generation.
type GaloisKeyGenProtocol struct {
skOut ringqp.Poly
EvaluationKeyGenProtocol
}
@@ -26,21 +28,28 @@ type GaloisKeyGenCRP struct {
EvaluationKeyGenCRP
}
// NewGaloisKeyGenProtocol creates a [GaloisKeyGenProtocol] instance.
// ShallowCopy creates a shallow copy of GaloisKeyGenProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// GaloisKeyGenProtocol can be used concurrently.
func (gkg GaloisKeyGenProtocol) ShallowCopy() GaloisKeyGenProtocol {
return GaloisKeyGenProtocol{EvaluationKeyGenProtocol: gkg.EvaluationKeyGenProtocol.ShallowCopy(), skOut: gkg.params.RingQP().NewPoly()}
}
// NewGaloisKeyGenProtocol creates a GaloisKeyGenProtocol instance.
func NewGaloisKeyGenProtocol(params rlwe.ParameterProvider) (gkg GaloisKeyGenProtocol) {
return GaloisKeyGenProtocol{EvaluationKeyGenProtocol: NewEvaluationKeyGenProtocol(params)}
return GaloisKeyGenProtocol{EvaluationKeyGenProtocol: NewEvaluationKeyGenProtocol(params), skOut: params.GetRLWEParameters().RingQP().NewPoly()}
}
// AllocateShare allocates a party's share in the GaloisKey Generation.
func (gkg GaloisKeyGenProtocol) AllocateShare(evkParams ...rlwe.EvaluationKeyParameters) (gkgShare GaloisKeyGenShare) {
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(gkg.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(gkg.params, evkParams)
return GaloisKeyGenShare{EvaluationKeyGenShare: gkg.EvaluationKeyGenProtocol.allocateShare(levelQ, levelP, BaseTwoDecomposition)}
}
// SampleCRP samples a common random polynomial to be used in the GaloisKey Generation from the provided
// common reference string.
func (gkg GaloisKeyGenProtocol) SampleCRP(crs CRS, evkParams ...rlwe.EvaluationKeyParameters) GaloisKeyGenCRP {
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(gkg.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(gkg.params, evkParams)
return GaloisKeyGenCRP{gkg.EvaluationKeyGenProtocol.sampleCRP(crs, levelQ, levelP, BaseTwoDecomposition)}
}
@@ -50,23 +59,21 @@ func (gkg GaloisKeyGenProtocol) GenShare(sk *rlwe.SecretKey, galEl uint64, crp G
levelQ := shareOut.LevelQ()
levelP := shareOut.LevelP()
ringQP := gkg.params.RingQP().AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
ringQ := gkg.params.RingQ().AtLevel(levelQ)
ringP := gkg.params.RingP().AtLevel(levelP)
galElInv := ring.ModExp(galEl, ringQ.NthRoot()-1, ringQ.NthRoot())
// Important
shareOut.GaloisElement = galEl
skOut := ringQP.NewPoly()
ringQ.AutomorphismNTT(sk.Value.Q, galElInv, skOut.Q)
ringQ.AutomorphismNTT(sk.Value.Q, galElInv, gkg.skOut.Q)
if levelP > -1 {
ringP.AutomorphismNTT(sk.Value.P, galElInv, skOut.P)
ringP.AutomorphismNTT(sk.Value.P, galElInv, gkg.skOut.P)
}
return gkg.EvaluationKeyGenProtocol.GenShare(sk, &rlwe.SecretKey{Value: skOut}, crp.EvaluationKeyGenCRP, &shareOut.EvaluationKeyGenShare)
return gkg.EvaluationKeyGenProtocol.GenShare(sk, &rlwe.SecretKey{Value: gkg.skOut}, crp.EvaluationKeyGenCRP, &shareOut.EvaluationKeyGenShare)
}
@@ -97,17 +104,17 @@ func (share GaloisKeyGenShare) BinarySize() int {
return 8 + share.EvaluationKeyGenShare.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (share GaloisKeyGenShare) WriteTo(w io.Writer) (n int64, err error) {
switch w := w.(type) {
case buffer.Writer:
@@ -132,17 +139,17 @@ func (share GaloisKeyGenShare) WriteTo(w io.Writer) (n int64, err error) {
}
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap [io.Reader] in a pre-allocated bufio.Reader.
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (share *GaloisKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) {
switch r := r.(type) {
case buffer.Reader:
@@ -171,7 +178,7 @@ func (share GaloisKeyGenShare) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [GaloisKeyGenShare.MarshalBinary] or [GaloisKeyGenShare.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (share *GaloisKeyGenShare) UnmarshalBinary(p []byte) (err error) {
_, err = share.ReadFrom(buffer.NewBuffer(p))
return
+106 -54
View File
@@ -1,14 +1,14 @@
package multiparty
package mhe
import (
"io"
"slices"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/tuneinsight/lattigo/v6/utils/structs"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/ring/ringqp"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
"github.com/luxdefi/lattice/v5/utils/structs"
)
// RelinearizationKeyGenProtocol is the structure storing the parameters and and precomputations for the collective relinearization key generation protocol.
@@ -17,6 +17,8 @@ type RelinearizationKeyGenProtocol struct {
gaussianSamplerQ ring.Sampler
ternarySamplerQ ring.Sampler
buf [2]ringqp.Poly
}
// RelinearizationKeyGenShare is a share in the RelinearizationKeyGen protocol.
@@ -29,6 +31,42 @@ type RelinearizationKeyGenCRP struct {
Value structs.Matrix[ringqp.Poly]
}
// ShallowCopy creates a shallow copy of RelinearizationKeyGenProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// RelinearizationKeyGenProtocol can be used concurrently.
func (ekg *RelinearizationKeyGenProtocol) ShallowCopy() RelinearizationKeyGenProtocol {
var err error
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := ekg.params
Xe, err := ring.NewSampler(prng, ekg.params.RingQ(), ekg.params.Xe(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
Xs, err := ring.NewSampler(prng, ekg.params.RingQ(), ekg.params.Xs(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return RelinearizationKeyGenProtocol{
params: ekg.params,
buf: [2]ringqp.Poly{params.RingQP().NewPoly(), params.RingQP().NewPoly()},
gaussianSamplerQ: Xe,
ternarySamplerQ: Xs,
}
}
// NewRelinearizationKeyGenProtocol creates a new RelinearizationKeyGen protocol struct.
func NewRelinearizationKeyGenProtocol(params rlwe.ParameterProvider) RelinearizationKeyGenProtocol {
rkg := RelinearizationKeyGenProtocol{}
@@ -56,6 +94,7 @@ func NewRelinearizationKeyGenProtocol(params rlwe.ParameterProvider) Relineariza
panic(err)
}
rkg.buf = [2]ringqp.Poly{rkg.params.RingQP().NewPoly(), rkg.params.RingQP().NewPoly()}
return rkg
}
@@ -64,7 +103,7 @@ func NewRelinearizationKeyGenProtocol(params rlwe.ParameterProvider) Relineariza
func (ekg RelinearizationKeyGenProtocol) SampleCRP(crs CRS, evkParams ...rlwe.EvaluationKeyParameters) RelinearizationKeyGenCRP {
params := ekg.params
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(ekg.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(ekg.params, evkParams)
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(levelQ, levelP)
BaseTwoDecompositionVectorSize := params.BaseTwoDecompositionVectorSize(levelQ, levelP, BaseTwoDecomposition)
@@ -83,7 +122,7 @@ func (ekg RelinearizationKeyGenProtocol) SampleCRP(crs CRS, evkParams ...rlwe.Ev
return RelinearizationKeyGenCRP{Value: structs.Matrix[ringqp.Poly](m)}
}
// GenShareRoundOne is the first of three rounds of the [RelinearizationKeyGenProtocol] protocol. Each party generates a pseudo encryption of
// GenShareRoundOne is the first of three rounds of the RelinearizationKeyGenProtocol protocol. Each party generates a pseudo encryption of
// its secret share of the key s_i under its ephemeral key u_i : [-u_i*a + s_i*w + e_i] and broadcasts it to the other
// j-1 parties.
//
@@ -101,16 +140,15 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
hasModulusP := levelP > -1
buffQ := ringQ.NewPoly()
if hasModulusP {
// Computes P * sk
ringQ.MulScalarBigint(sk.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], buffQ)
ringQ.MulScalarBigint(sk.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], ekg.buf[0].Q)
} else {
levelP = 0
buffQ.CopyLvl(levelQ, sk.Value.Q)
ekg.buf[0].Q.CopyLvl(levelQ, sk.Value.Q)
}
ringQ.IMForm(buffQ, buffQ)
ringQ.IMForm(ekg.buf[0].Q, ekg.buf[0].Q)
// u
ekg.ternarySamplerQ.Read(ephSkOut.Value.Q)
@@ -130,7 +168,7 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
sampler := ekg.gaussianSamplerQ.AtLevel(levelQ)
var index int
for j := 0; j < slices.Max(BaseTwoDecompositionVectorSize); j++ {
for j := 0; j < utils.MaxSlice(BaseTwoDecompositionVectorSize); j++ {
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
if j < BaseTwoDecompositionVectorSize[i] {
@@ -154,7 +192,7 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
}
qi := ringQ.SubRings[index].Modulus
skP := buffQ.Coeffs[index]
skP := ekg.buf[0].Q.Coeffs[index]
h := shareOut.Value[i][j][0].Q.Coeffs[index]
for w := 0; w < N; w++ {
@@ -179,15 +217,19 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
}
}
ringQ.MulScalar(buffQ, 1<<shareOut.BaseTwoDecomposition, buffQ)
ringQ.MulScalar(ekg.buf[0].Q, 1<<shareOut.BaseTwoDecomposition, ekg.buf[0].Q)
}
}
// GenShareRoundTwo is the second of three rounds of the [RelinearizationKeyGenProtocol] protocol. Upon receiving the j-1 shares, each party computes :
// GenShareRoundTwo is the second of three rounds of the RelinearizationKeyGenProtocol protocol. Upon receiving the j-1 shares, each party computes :
//
// - round1 = sum([-u_i * a + s_i * P + e_0i, s_i* a + e_i1]) = [-ua + sP + e0, sa + e1]
// round1 = sum([-u_i * a + s_i * P + e_0i, s_i* a + e_i1])
//
// - round2 = [s_i * round1[0] + (u_i - s_i) * round1[1] + e_i2] = [s_i * {-ua + s * P + e0} + (u_i - s_i) * {sa + e1} + e_i2]
// = [u * a + s * P + e0, s * a + e1]
//
// round2 = [s_i * round1[0] + e_i2, (u_i - s_i) * round1[1] + e_i3]
//
// = [s_i * {u * a + s * P + e0} + e_i2, (u_i - s_i) * {s * a + e1} + e_i3]
//
// and broadcasts both values to the other j-1 parties.
func (ekg RelinearizationKeyGenProtocol) GenShareRoundTwo(ephSk, sk *rlwe.SecretKey, round1 RelinearizationKeyGenShare, shareOut *RelinearizationKeyGenShare) {
@@ -200,9 +242,7 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundTwo(ephSk, sk *rlwe.Secret
ringQP := ekg.params.RingQP().AtLevel(levelQ, levelP)
// (u_i - s_i)
buffQP0 := ringQP.NewPoly()
buffQP1 := ekg.params.RingQP().NewPoly()
ringQP.Sub(ephSk.Value, sk.Value, buffQP0)
ringQP.Sub(ephSk.Value, sk.Value, ekg.buf[0])
sampler := ekg.gaussianSamplerQ.AtLevel(levelQ)
@@ -217,18 +257,25 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundTwo(ephSk, sk *rlwe.Secret
ringQP.MulCoeffsMontgomeryLazy(round1.Value[i][j][0], sk.Value, shareOut.Value[i][j][0])
// (AggregateShareRoundTwo samples) * sk + e_1i
sampler.Read(buffQP1.Q)
sampler.Read(ekg.buf[1].Q)
if levelP > -1 {
ringQP.ExtendBasisSmallNormAndCenter(buffQP1.Q, levelP, buffQP1.Q, buffQP1.P)
ringQP.ExtendBasisSmallNormAndCenter(ekg.buf[1].Q, levelP, ekg.buf[1].Q, ekg.buf[1].P)
}
ringQP.NTT(buffQP1, buffQP1)
ringQP.Add(shareOut.Value[i][j][0], buffQP1, shareOut.Value[i][j][0])
ringQP.NTT(ekg.buf[1], ekg.buf[1])
ringQP.Add(shareOut.Value[i][j][0], ekg.buf[1], shareOut.Value[i][j][0])
// second part
// (AggRound1Samples[0])*sk + (u_i - s_i) * (AggRound1Samples[1]) + e_1
ringQP.MulCoeffsMontgomeryThenAdd(buffQP0, round1.Value[i][j][1], shareOut.Value[i][j][0])
// (u_i - s_i) * (sum [x][s*a_i + e_2i]) + e3i
sampler.Read(shareOut.Value[i][j][1].Q)
if levelP > -1 {
ringQP.ExtendBasisSmallNormAndCenter(shareOut.Value[i][j][1].Q, levelP, shareOut.Value[i][j][1].Q, shareOut.Value[i][j][1].P)
}
ringQP.NTT(shareOut.Value[i][j][1], shareOut.Value[i][j][1])
ringQP.MulCoeffsMontgomeryThenAdd(ekg.buf[0], round1.Value[i][j][1], shareOut.Value[i][j][1])
}
}
}
@@ -245,19 +292,23 @@ func (ekg RelinearizationKeyGenProtocol) AggregateShares(share1, share2 Relinear
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
// deg(round 1 shares) = 1, deg(round 2 shares) = 0
for k := 0; k <= share1.Degree(); k++ {
ringQP.Add(share1.Value[i][j][k], share2.Value[i][j][k], shareOut.Value[i][j][k])
}
ringQP.Add(share1.Value[i][j][0], share2.Value[i][j][0], shareOut.Value[i][j][0])
ringQP.Add(share1.Value[i][j][1], share2.Value[i][j][1], shareOut.Value[i][j][1])
}
}
}
// GenRelinearizationKey computes the generated RLK from the public shares and write the result in evalKeyOut.
//
// - round1 = [-ua + sP + e0, sa + e1]
// - round2 = sum([s_i * {-ua + sP + e0} + (u_i - s_i) * {sa + e1} + e_i2]) = [-sua + Ps^2 + se0 + e2, sua + ue1 - s^2a -se1]
// - [round2[0] + round2[1], round1[1]] = [-{s^2a + se1} + Ps^2 + {se0 + ue1 + e2}, sa + e1] = [sb + Ps^2 + e, b]
// round1 = [u * a + s * P + e0, s * a + e1]
//
// round2 = sum([s_i * {u * a + s * P + e0} + e_i2, (u_i - s_i) * {s * a + e1} + e_i3])
//
// = [-sua + P*s^2 + s*e0 + e2, sua + ue1 - s^2a -s*e1 + e3]
//
// [round2[0] + round2[1], round1[1]] = [- s^2a - s*e1 + P*s^2 + s*e0 + u*e1 + e2 + e3, s * a + e1]
//
// = [s * b + P * s^2 + s*e0 + u*e1 + e2 + e3, b]
func (ekg RelinearizationKeyGenProtocol) GenRelinearizationKey(round1 RelinearizationKeyGenShare, round2 RelinearizationKeyGenShare, evalKeyOut *rlwe.RelinearizationKey) {
levelQ := round1.LevelQ()
@@ -269,22 +320,23 @@ func (ekg RelinearizationKeyGenProtocol) GenRelinearizationKey(round1 Relineariz
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
ringQP.MForm(round2.Value[i][j][0], evalKeyOut.Value[i][j][0])
ringQP.MForm(round1.Value[i][j][1], evalKeyOut.Value[i][j][1])
ringQP.Add(round2.Value[i][j][0], round2.Value[i][j][1], evalKeyOut.Value[i][j][0])
evalKeyOut.Value[i][j][1].Copy(round1.Value[i][j][1])
ringQP.MForm(evalKeyOut.Value[i][j][0], evalKeyOut.Value[i][j][0])
ringQP.MForm(evalKeyOut.Value[i][j][1], evalKeyOut.Value[i][j][1])
}
}
}
// AllocateShare allocates the share of the EKG protocol.
// To satisfy the correctness of the multi-party protocol, linearization keys shares cannot be allocated in the compressed format.
func (ekg RelinearizationKeyGenProtocol) AllocateShare(evkParams ...rlwe.EvaluationKeyParameters) (ephSk *rlwe.SecretKey, r1 RelinearizationKeyGenShare, r2 RelinearizationKeyGenShare) {
params := ekg.params
ephSk = rlwe.NewSecretKey(params)
levelQ, levelP, BaseTwoDecomposition, _ := rlwe.ResolveEvaluationKeyParameters(ekg.params, evkParams)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(ekg.params, evkParams)
r1 = RelinearizationKeyGenShare{GadgetCiphertext: *rlwe.NewGadgetCiphertext(params, 1, levelQ, levelP, BaseTwoDecomposition)}
r2 = RelinearizationKeyGenShare{GadgetCiphertext: *rlwe.NewGadgetCiphertext(params, 0, levelQ, levelP, BaseTwoDecomposition)}
r2 = RelinearizationKeyGenShare{GadgetCiphertext: *rlwe.NewGadgetCiphertext(params, 1, levelQ, levelP, BaseTwoDecomposition)}
return
}
@@ -294,32 +346,32 @@ func (share RelinearizationKeyGenShare) BinarySize() int {
return share.GadgetCiphertext.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated [bufio.Writer].
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (share RelinearizationKeyGenShare) WriteTo(w io.Writer) (n int64, err error) {
return share.GadgetCiphertext.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap [io.Reader] in a pre-allocated bufio.Reader.
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (share *RelinearizationKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) {
return share.GadgetCiphertext.ReadFrom(r)
}
@@ -330,7 +382,7 @@ func (share RelinearizationKeyGenShare) MarshalBinary() (data []byte, err error)
}
// UnmarshalBinary decodes a slice of bytes generated by
// [RelinearizationKeyGenShare.MarshalBinary] or [RelinearizationKeyGenShare.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (share *RelinearizationKeyGenShare) UnmarshalBinary(data []byte) (err error) {
return share.GadgetCiphertext.UnmarshalBinary(data)
}
+62 -30
View File
@@ -1,14 +1,14 @@
package multiparty
package mhe
import (
"fmt"
"io"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/luxdefi/lattice/v5/ring"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// PublicKeySwitchProtocol is the structure storing the parameters for the collective public key-switching.
@@ -16,6 +16,8 @@ type PublicKeySwitchProtocol struct {
params rlwe.Parameters
noise ring.DistributionParameters
buf ring.Poly
*rlwe.Encryptor
noiseSampler ring.Sampler
}
@@ -32,6 +34,8 @@ func NewPublicKeySwitchProtocol(params rlwe.ParameterProvider, noiseFlooding rin
pcks.params = *params.GetRLWEParameters()
pcks.noise = noiseFlooding
pcks.buf = pcks.params.RingQ().NewPoly()
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
@@ -88,19 +92,18 @@ func (pcks PublicKeySwitchProtocol) GenShare(sk *rlwe.SecretKey, pk *rlwe.Public
panic(err)
}
buffQ := pcks.params.RingQ().NewPoly()
// Add ct[1] * s and noise
if ct.IsNTT {
ringQ.MulCoeffsMontgomeryThenAdd(ct.Value[1], sk.Value.Q, shareOut.Value[0])
pcks.noiseSampler.Read(buffQ)
ringQ.NTT(buffQ, buffQ)
ringQ.Add(shareOut.Value[0], buffQ, shareOut.Value[0])
pcks.noiseSampler.Read(pcks.buf)
ringQ.NTT(pcks.buf, pcks.buf)
ringQ.Add(shareOut.Value[0], pcks.buf, shareOut.Value[0])
} else {
ringQ.NTTLazy(ct.Value[1], buffQ)
ringQ.MulCoeffsMontgomeryLazy(buffQ, sk.Value.Q, buffQ)
ringQ.INTT(buffQ, buffQ)
pcks.noiseSampler.ReadAndAdd(buffQ)
ringQ.Add(shareOut.Value[0], buffQ, shareOut.Value[0])
ringQ.NTTLazy(ct.Value[1], pcks.buf)
ringQ.MulCoeffsMontgomeryLazy(pcks.buf, sk.Value.Q, pcks.buf)
ringQ.INTT(pcks.buf, pcks.buf)
pcks.noiseSampler.ReadAndAdd(pcks.buf)
ringQ.Add(shareOut.Value[0], pcks.buf, shareOut.Value[0])
}
}
@@ -133,37 +136,66 @@ func (pcks PublicKeySwitchProtocol) KeySwitch(ctIn *rlwe.Ciphertext, combined Pu
opOut.Value[1].CopyLvl(level, combined.Value[1])
}
// ShallowCopy creates a shallow copy of PublicKeySwitchProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary bufers are reallocated. The receiver and the returned
// PublicKeySwitchProtocol can be used concurrently.
func (pcks PublicKeySwitchProtocol) ShallowCopy() PublicKeySwitchProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := pcks.params
Xe, err := ring.NewSampler(prng, params.RingQ(), pcks.noise, false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return PublicKeySwitchProtocol{
noiseSampler: Xe,
noise: pcks.noise,
Encryptor: pcks.Encryptor.ShallowCopy(),
params: params,
buf: params.RingQ().NewPoly(),
}
}
// BinarySize returns the serialized size of the object in bytes.
func (share PublicKeySwitchShare) BinarySize() int {
return share.Element.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// [io.Writer] in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (share PublicKeySwitchShare) WriteTo(w io.Writer) (n int64, err error) {
return share.Element.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (share *PublicKeySwitchShare) ReadFrom(r io.Reader) (n int64, err error) {
return share.Element.ReadFrom(r)
}
@@ -174,7 +206,7 @@ func (share PublicKeySwitchShare) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [PublicKeySwitchShare.MarshalBinary] or [PublicKeySwitchShare.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (share *PublicKeySwitchShare) UnmarshalBinary(p []byte) (err error) {
return share.Element.UnmarshalBinary(p)
}
+65 -34
View File
@@ -1,22 +1,24 @@
package multiparty
package mhe
import (
"fmt"
"io"
"math"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/luxdefi/lattice/v5/ring"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// KeySwitchProtocol is the structure storing the parameters and precomputations for the collective key-switching protocol.
// KeySwitchProtocol is the structure storing the parameters and and precomputations for the collective key-switching protocol.
type KeySwitchProtocol struct {
params rlwe.Parameters
noise ring.DistributionParameters
noiseSampler ring.Sampler
buf ring.Poly
bufDelta ring.Poly
}
// KeySwitchShare is a type for the KeySwitch protocol shares.
@@ -24,12 +26,41 @@ type KeySwitchShare struct {
Value ring.Poly
}
// ShallowCopy creates a shallow copy of KeySwitchProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary bufers are reallocated. The receiver and the returned
// KeySwitchProtocol can be used concurrently.
func (cks KeySwitchProtocol) ShallowCopy() KeySwitchProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := cks.params
Xe, err := ring.NewSampler(prng, cks.params.RingQ(), cks.noise, false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return KeySwitchProtocol{
params: params,
noiseSampler: Xe,
buf: params.RingQ().NewPoly(),
bufDelta: params.RingQ().NewPoly(),
noise: cks.noise,
}
}
// KeySwitchCRP is a type for common reference polynomials in the KeySwitch protocol.
type KeySwitchCRP struct {
Value ring.Poly
}
// NewKeySwitchProtocol creates a new [KeySwitchProtocol] that will be used to perform a collective key-switching on a ciphertext encrypted under a collective public-key, whose
// NewKeySwitchProtocol creates a new KeySwitchProtocol that will be used to perform a collective key-switching on a ciphertext encrypted under a collective public-key, whose
// secret-shares are distributed among j parties, re-encrypting the ciphertext under another public-key, whose secret-shares are also known to the
// parties.
func NewKeySwitchProtocol(params rlwe.ParameterProvider, noiseFlooding ring.DistributionParameters) (KeySwitchProtocol, error) {
@@ -61,6 +92,8 @@ func NewKeySwitchProtocol(params rlwe.ParameterProvider, noiseFlooding ring.Dist
panic(err)
}
cks.buf = cks.params.RingQ().NewPoly()
cks.bufDelta = cks.params.RingQ().NewPoly()
return cks, nil
}
@@ -79,7 +112,7 @@ func (cks KeySwitchProtocol) SampleCRP(level int, crs CRS) KeySwitchCRP {
}
// GenShare computes a party's share in the KeySwitchcol from secret-key skInput to secret-key skOutput.
// ct is the [rlwe.Ciphertext] to keyswitch. Note that ct.Value[0] is not used by the function and can be nil/zero.
// ct is the rlwe.Ciphertext to keyswitch. Note that ct.Value[0] is not used by the function and can be nil/zero.
//
// Expected noise: ctNoise + encFreshSk + smudging
func (cks KeySwitchProtocol) GenShare(skInput, skOutput *rlwe.SecretKey, ct *rlwe.Ciphertext, shareOut *KeySwitchShare) {
@@ -89,21 +122,19 @@ func (cks KeySwitchProtocol) GenShare(skInput, skOutput *rlwe.SecretKey, ct *rlw
shareOut.Value.Resize(levelQ)
ringQ := cks.params.RingQ().AtLevel(levelQ)
buffDelta := ringQ.NewPoly()
buffQ := cks.params.RingQ().NewPoly()
ringQ.Sub(skInput.Value.Q, skOutput.Value.Q, buffDelta)
ringQ.Sub(skInput.Value.Q, skOutput.Value.Q, cks.bufDelta)
var c1NTT ring.Poly
if !ct.IsNTT {
ringQ.NTTLazy(ct.Value[1], buffQ)
c1NTT = buffQ
ringQ.NTTLazy(ct.Value[1], cks.buf)
c1NTT = cks.buf
} else {
c1NTT = ct.Value[1]
}
// c1NTT * (skIn - skOut)
ringQ.MulCoeffsMontgomeryLazy(c1NTT, buffDelta, shareOut.Value)
ringQ.MulCoeffsMontgomeryLazy(c1NTT, cks.bufDelta, shareOut.Value)
if !ct.IsNTT {
// InvNTT(c1NTT * (skIn - skOut)) + e
@@ -111,13 +142,13 @@ func (cks KeySwitchProtocol) GenShare(skInput, skOutput *rlwe.SecretKey, ct *rlw
cks.noiseSampler.AtLevel(levelQ).ReadAndAdd(shareOut.Value)
} else {
// c1NTT * (skIn - skOut) + e
cks.noiseSampler.AtLevel(levelQ).Read(buffQ)
ringQ.NTT(buffQ, buffQ)
ringQ.Add(shareOut.Value, buffQ, shareOut.Value)
cks.noiseSampler.AtLevel(levelQ).Read(cks.buf)
ringQ.NTT(cks.buf, cks.buf)
ringQ.Add(shareOut.Value, cks.buf, shareOut.Value)
}
}
// AggregateShares is the second part of the unique round of the [KeySwitchProtocol] protocol. Upon receiving the j-1 elements each party computes:
// AggregateShares is the second part of the unique round of the KeySwitchProtocol protocol. Upon receiving the j-1 elements each party computes :
//
// [ctx[0] + sum((skInput_i - skOutput_i) * ctx[0] + e_i), ctx[1]]
func (cks KeySwitchProtocol) AggregateShares(share1, share2 KeySwitchShare, shareOut *KeySwitchShare) (err error) {
@@ -156,32 +187,32 @@ func (ckss KeySwitchShare) BinarySize() int {
return ckss.Value.BinarySize()
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the [buffer.Writer] interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a [bufio.Writer]. Since this requires allocations, it
// is preferable to pass a [buffer.Writer] directly:
// Unless w implements the buffer.Writer interface (see lattice/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a [io.Writer], it is preferable to first wrap the
// [io.Writer] in a pre-allocated [bufio.Writer].
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
// buffer.NewBuffer(b) as w (see lattice/utils/buffer/buffer.go).
func (ckss KeySwitchShare) WriteTo(w io.Writer) (n int64, err error) {
return ckss.Value.WriteTo(w)
}
// ReadFrom reads on the object from an [io.Writer]. It implements the
// [io.ReaderFrom] interface.
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the [buffer.Reader] interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a [bufio.Reader]. Since this requires allocation, it
// is preferable to pass a [buffer.Reader] directly:
// Unless r implements the buffer.Reader interface (see see lattice/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a [io.Reader], it is preferable to first
// first wrap [io.Reader] in a pre-allocated [bufio.Reader].
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
// as w (see lattice/utils/buffer/buffer.go).
func (ckss *KeySwitchShare) ReadFrom(r io.Reader) (n int64, err error) {
return ckss.Value.ReadFrom(r)
}
@@ -192,7 +223,7 @@ func (ckss KeySwitchShare) MarshalBinary() (p []byte, err error) {
}
// UnmarshalBinary decodes a slice of bytes generated by
// [KeySwitchShare.MarshalBinary] or[KeySwitchShare.WriteTo] on the object.
// MarshalBinary or WriteTo on the object.
func (ckss *KeySwitchShare) UnmarshalBinary(p []byte) (err error) {
return ckss.Value.UnmarshalBinary(p)
}
+12 -13
View File
@@ -1,14 +1,13 @@
package mpbgv
package mheint
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/mhe"
)
func BenchmarkInteger(b *testing.B) {
@@ -18,11 +17,11 @@ func BenchmarkInteger(b *testing.B) {
paramsLiterals := testParams
if *flagParamString != "" {
var jsonParams bgv.ParametersLiteral
var jsonParams heint.ParametersLiteral
if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil {
b.Fatal(err)
}
paramsLiterals = []bgv.ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag
paramsLiterals = []heint.ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag
}
for _, p := range paramsLiterals {
@@ -31,8 +30,8 @@ func BenchmarkInteger(b *testing.B) {
p.PlaintextModulus = plaintextModulus
var params bgv.Parameters
if params, err = bgv.NewParametersFromLiteral(p); err != nil {
var params heint.Parameters
if params, err = heint.NewParametersFromLiteral(p); err != nil {
b.Fatal(err)
}
@@ -58,7 +57,7 @@ func benchRefresh(tc *testContext, b *testing.B) {
type Party struct {
RefreshProtocol
s *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
p := new(Party)
@@ -68,14 +67,14 @@ func benchRefresh(tc *testContext, b *testing.B) {
p.s = sk0Shards[0]
p.share = p.AllocateShare(minLevel, maxLevel)
ciphertext := bgv.NewCiphertext(tc.params, 1, minLevel)
ciphertext := heint.NewCiphertext(tc.params, 1, minLevel)
crp := p.SampleCRP(maxLevel, tc.crs)
b.Run(GetTestName("Refresh/Round1/Gen", tc.params, tc.NParties), func(b *testing.B) {
for i := 0; i < b.N; i++ {
p.GenShare(p.s, ciphertext, crp, &p.share)
p.GenShare(p.s, ciphertext, ciphertext.Scale, crp, &p.share)
}
})
@@ -87,7 +86,7 @@ func benchRefresh(tc *testContext, b *testing.B) {
})
b.Run(GetTestName("Refresh/Finalize", tc.params, tc.NParties), func(b *testing.B) {
opOut := bgv.NewCiphertext(tc.params, 1, maxLevel)
opOut := heint.NewCiphertext(tc.params, 1, maxLevel)
for i := 0; i < b.N; i++ {
p.Finalize(ciphertext, crp, p.share, opOut)
}
+40 -41
View File
@@ -1,4 +1,4 @@
package mpbgv
package mheint
import (
"encoding/json"
@@ -6,22 +6,21 @@ import (
"fmt"
"math"
"runtime"
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var flagParamString = flag.String("params", "", "specify the test cryptographic parameters as a JSON string. Overrides -short and -long.")
func GetTestName(opname string, p bgv.Parameters, parties int) string {
func GetTestName(opname string, p heint.Parameters, parties int) string {
return fmt.Sprintf("%s/LogN=%d/logQ=%d/logP=%d/LogSlots=%dx%d/logT=%d/Qi=%d/Pi=%d/parties=%d",
opname,
p.LogN(),
@@ -36,7 +35,7 @@ func GetTestName(opname string, p bgv.Parameters, parties int) string {
}
type testContext struct {
params bgv.Parameters
params heint.Parameters
// Number of parties
NParties int
@@ -49,7 +48,7 @@ type testContext struct {
ringQ *ring.Ring
ringP *ring.Ring
encoder *bgv.Encoder
encoder *heint.Encoder
sk0Shards []*rlwe.SecretKey
sk0 *rlwe.SecretKey
@@ -63,9 +62,9 @@ type testContext struct {
encryptorPk0 *rlwe.Encryptor
decryptorSk0 *rlwe.Decryptor
decryptorSk1 *rlwe.Decryptor
evaluator *bgv.Evaluator
evaluator *heint.Evaluator
crs multiparty.CRS
crs mhe.CRS
uniformSampler *ring.UniformSampler
}
@@ -76,11 +75,11 @@ func TestInteger(t *testing.T) {
paramsLiterals := testParams
if *flagParamString != "" {
var jsonParams bgv.ParametersLiteral
var jsonParams heint.ParametersLiteral
if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil {
t.Fatal(err)
}
paramsLiterals = []bgv.ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag
paramsLiterals = []heint.ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag
}
for _, p := range paramsLiterals {
@@ -89,8 +88,8 @@ func TestInteger(t *testing.T) {
p.PlaintextModulus = plaintextModulus
var params bgv.Parameters
if params, err = bgv.NewParametersFromLiteral(p); err != nil {
var params heint.Parameters
if params, err = heint.NewParametersFromLiteral(p); err != nil {
t.Fatal(err)
}
@@ -113,7 +112,7 @@ func TestInteger(t *testing.T) {
}
}
func gentestContext(nParties int, params bgv.Parameters) (tc *testContext, err error) {
func gentestContext(nParties int, params heint.Parameters) (tc *testContext, err error) {
tc = new(testContext)
@@ -131,8 +130,8 @@ func gentestContext(nParties int, params bgv.Parameters) (tc *testContext, err e
tc.crs = prng
tc.uniformSampler = ring.NewUniformSampler(prng, params.RingQ())
tc.encoder = bgv.NewEncoder(tc.params)
tc.evaluator = bgv.NewEvaluator(tc.params, nil)
tc.encoder = heint.NewEncoder(tc.params)
tc.evaluator = heint.NewEvaluator(tc.params, nil)
kgen := rlwe.NewKeyGenerator(tc.params)
@@ -169,8 +168,8 @@ func testEncToShares(tc *testContext, t *testing.T) {
e2s EncToShareProtocol
s2e ShareToEncProtocol
sk *rlwe.SecretKey
publicShare multiparty.KeySwitchShare
secretShare multiparty.AdditiveShare
publicShare mhe.KeySwitchShare
secretShare mhe.AdditiveShare
}
params := tc.params
@@ -184,8 +183,8 @@ func testEncToShares(tc *testContext, t *testing.T) {
P[i].s2e, err = NewShareToEncProtocol(params, params.Xe())
require.NoError(t, err)
} else {
P[i].e2s = P[0].e2s
P[i].s2e = P[0].s2e
P[i].e2s = P[0].e2s.ShallowCopy()
P[i].s2e = P[0].s2e.ShallowCopy()
}
P[i].sk = tc.sk0Shards[i]
@@ -216,7 +215,7 @@ func testEncToShares(tc *testContext, t *testing.T) {
tc.encoder.DecodeRingT(ptRt, ciphertext.Scale, values)
assert.True(t, slices.Equal(coeffs, values))
assert.True(t, utils.EqualSlice(coeffs, values))
})
crp := P[0].e2s.SampleCRP(params.MaxLevel(), tc.crs)
@@ -230,7 +229,7 @@ func testEncToShares(tc *testContext, t *testing.T) {
}
}
ctRec := bgv.NewCiphertext(tc.params, 1, tc.params.MaxLevel())
ctRec := heint.NewCiphertext(tc.params, 1, tc.params.MaxLevel())
*ctRec.MetaData = *ciphertext.MetaData
P[0].s2e.GetEncryption(P[0].publicShare, crp, ctRec)
@@ -253,7 +252,7 @@ func testRefresh(tc *testContext, t *testing.T) {
type Party struct {
RefreshProtocol
s *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
RefreshParties := make([]*Party, tc.NParties)
@@ -264,7 +263,7 @@ func testRefresh(tc *testContext, t *testing.T) {
p.RefreshProtocol, err = NewRefreshProtocol(tc.params, tc.params.Xe())
require.NoError(t, err)
} else {
p.RefreshProtocol = RefreshParties[0].RefreshProtocol
p.RefreshProtocol = RefreshParties[0].RefreshProtocol.ShallowCopy()
}
p.s = sk0Shards[i]
@@ -280,7 +279,7 @@ func testRefresh(tc *testContext, t *testing.T) {
ciphertext.Resize(ciphertext.Degree(), minLevel)
for i, p := range RefreshParties {
p.GenShare(p.s, ciphertext, crp, &p.share)
p.GenShare(p.s, ciphertext, ciphertext.Scale, crp, &p.share)
if i > 0 {
P0.AggregateShares(p.share, P0.share, &P0.share)
}
@@ -293,7 +292,7 @@ func testRefresh(tc *testContext, t *testing.T) {
require.True(t, ciphertext.Level() == maxLevel)
have := make([]uint64, tc.params.MaxSlots())
encoder.Decode(decryptorSk0.DecryptNew(ciphertext), have)
require.True(t, slices.Equal(coeffs, have))
require.True(t, utils.EqualSlice(coeffs, have))
})
}
@@ -313,7 +312,7 @@ func testRefreshAndPermutation(tc *testContext, t *testing.T) {
type Party struct {
MaskedTransformProtocol
s *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
RefreshParties := make([]*Party, tc.NParties)
@@ -363,7 +362,7 @@ func testRefreshAndPermutation(tc *testContext, t *testing.T) {
}
for i, p := range RefreshParties {
p.GenShare(p.s, p.s, ciphertext, crp, maskedTransform, &p.share)
p.GenShare(p.s, p.s, ciphertext, ciphertext.Scale, crp, maskedTransform, &p.share)
if i > 0 {
P0.AggregateShares(P0.share, p.share, &P0.share)
}
@@ -381,7 +380,7 @@ func testRefreshAndPermutation(tc *testContext, t *testing.T) {
//Decrypts and compares
require.True(t, ciphertext.Level() == maxLevel)
require.True(t, slices.Equal(coeffsPermute, coeffsHave))
require.True(t, utils.EqualSlice(coeffsPermute, coeffsHave))
})
}
@@ -393,9 +392,9 @@ func testRefreshAndTransformSwitchParams(tc *testContext, t *testing.T) {
t.Run(GetTestName("RefreshAndTransformSwitchparams", tc.params, tc.NParties), func(t *testing.T) {
var paramsOut bgv.Parameters
var paramsOut heint.Parameters
var err error
paramsOut, err = bgv.NewParametersFromLiteral(bgv.ParametersLiteral{
paramsOut, err = heint.NewParametersFromLiteral(heint.ParametersLiteral{
LogN: paramsIn.LogN(),
LogQ: []int{54, 49, 49, 49},
LogP: []int{52, 52},
@@ -411,7 +410,7 @@ func testRefreshAndTransformSwitchParams(tc *testContext, t *testing.T) {
MaskedTransformProtocol
sIn *rlwe.SecretKey
sOut *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
RefreshParties := make([]*Party, tc.NParties)
@@ -465,7 +464,7 @@ func testRefreshAndTransformSwitchParams(tc *testContext, t *testing.T) {
}
for i, p := range RefreshParties {
p.GenShare(p.sIn, p.sOut, ciphertext, crp, transform, &p.share)
p.GenShare(p.sIn, p.sOut, ciphertext, ciphertext.Scale, crp, transform, &p.share)
if i > 0 {
P0.AggregateShares(P0.share, p.share, &P0.share)
}
@@ -477,11 +476,11 @@ func testRefreshAndTransformSwitchParams(tc *testContext, t *testing.T) {
coeffsHave := make([]uint64, tc.params.MaxSlots())
dec := rlwe.NewDecryptor(paramsOut.Parameters, skIdealOut)
bgv.NewEncoder(paramsOut).Decode(dec.DecryptNew(ciphertext), coeffsHave)
heint.NewEncoder(paramsOut).Decode(dec.DecryptNew(ciphertext), coeffsHave)
//Decrypts and compares
require.True(t, ciphertext.Level() == maxLevel)
require.True(t, slices.Equal(coeffs, coeffsHave))
require.True(t, utils.EqualSlice(coeffs, coeffsHave))
})
}
@@ -496,7 +495,7 @@ func newTestVectors(tc *testContext, encryptor *rlwe.Encryptor, t *testing.T) (c
coeffsPol.Coeffs[0][i] = uint64(1)
}
plaintext = bgv.NewPlaintext(tc.params, tc.params.MaxLevel())
plaintext = heint.NewPlaintext(tc.params, tc.params.MaxLevel())
plaintext.Scale = tc.params.NewScale(2)
require.NoError(t, tc.encoder.Encode(coeffsPol.Coeffs[0], plaintext))
ciphertext, err = encryptor.EncryptNew(plaintext)
@@ -509,5 +508,5 @@ func newTestVectors(tc *testContext, encryptor *rlwe.Encryptor, t *testing.T) (c
func verifyTestVectors(tc *testContext, decryptor *rlwe.Decryptor, coeffs []uint64, ciphertext *rlwe.Ciphertext, t *testing.T) {
have := make([]uint64, tc.params.MaxSlots())
tc.encoder.Decode(decryptor.DecryptNew(ciphertext), have)
require.True(t, slices.Equal(coeffs, have))
require.True(t, utils.EqualSlice(coeffs, have))
}
+19 -11
View File
@@ -1,10 +1,11 @@
package mpbgv
package mheint
import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/core/rlwe"
)
// RefreshProtocol is a struct storing the relevant parameters for the Refresh protocol.
@@ -12,8 +13,15 @@ type RefreshProtocol struct {
MaskedTransformProtocol
}
// ShallowCopy creates a shallow copy of RefreshProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// RefreshProtocol can be used concurrently.
func (rfp *RefreshProtocol) ShallowCopy() RefreshProtocol {
return RefreshProtocol{rfp.MaskedTransformProtocol.ShallowCopy()}
}
// NewRefreshProtocol creates a new Refresh protocol instance.
func NewRefreshProtocol(params bgv.Parameters, noiseFlooding ring.DistributionParameters) (rfp RefreshProtocol, err error) {
func NewRefreshProtocol(params heint.Parameters, noiseFlooding ring.DistributionParameters) (rfp RefreshProtocol, err error) {
rfp = RefreshProtocol{}
mt, err := NewMaskedTransformProtocol(params, params, noiseFlooding)
rfp.MaskedTransformProtocol = mt
@@ -21,22 +29,22 @@ func NewRefreshProtocol(params bgv.Parameters, noiseFlooding ring.DistributionPa
}
// AllocateShare allocates the shares of the PermuteProtocol
func (rfp RefreshProtocol) AllocateShare(inputLevel, outputLevel int) multiparty.RefreshShare {
func (rfp RefreshProtocol) AllocateShare(inputLevel, outputLevel int) mhe.RefreshShare {
return rfp.MaskedTransformProtocol.AllocateShare(inputLevel, outputLevel)
}
// GenShare generates a share for the Refresh protocol.
// ct1 is degree 1 element of a rlwe.Ciphertext, i.e. rlwe.Ciphertext.Value[1].
func (rfp RefreshProtocol) GenShare(sk *rlwe.SecretKey, ct *rlwe.Ciphertext, crp multiparty.KeySwitchCRP, shareOut *multiparty.RefreshShare) (err error) {
return rfp.MaskedTransformProtocol.GenShare(sk, sk, ct, crp, nil, shareOut)
func (rfp RefreshProtocol) GenShare(sk *rlwe.SecretKey, ct *rlwe.Ciphertext, scale rlwe.Scale, crp mhe.KeySwitchCRP, shareOut *mhe.RefreshShare) (err error) {
return rfp.MaskedTransformProtocol.GenShare(sk, sk, ct, scale, crp, nil, shareOut)
}
// AggregateShares aggregates two parties' shares in the Refresh protocol.
func (rfp RefreshProtocol) AggregateShares(share1, share2 multiparty.RefreshShare, shareOut *multiparty.RefreshShare) (err error) {
func (rfp RefreshProtocol) AggregateShares(share1, share2 mhe.RefreshShare, shareOut *mhe.RefreshShare) (err error) {
return rfp.MaskedTransformProtocol.AggregateShares(share1, share2, shareOut)
}
// Finalize applies Decrypt, Recode and Recrypt on the input ciphertext.
func (rfp RefreshProtocol) Finalize(ctIn *rlwe.Ciphertext, crp multiparty.KeySwitchCRP, share multiparty.RefreshShare, opOut *rlwe.Ciphertext) (err error) {
func (rfp RefreshProtocol) Finalize(ctIn *rlwe.Ciphertext, crp mhe.KeySwitchCRP, share mhe.RefreshShare, opOut *rlwe.Ciphertext) (err error) {
return rfp.MaskedTransformProtocol.Transform(ctIn, nil, crp, share, opOut)
}
+6 -4
View File
@@ -1,11 +1,13 @@
package mpbgv
package mheint
import "github.com/tuneinsight/lattigo/v6/schemes/bgv"
import (
"github.com/luxdefi/lattice/v5/he/heint"
)
var (
// testInsecure are insecure parameters used for the sole purpose of fast testing.
testInsecure = bgv.ParametersLiteral{
testInsecure = heint.ParametersLiteral{
LogN: 10,
Q: []uint64{0x3fffffa8001, 0x1000090001, 0x10000c8001, 0x10000f0001, 0xffff00001},
P: []uint64{0x7fffffd8001},
@@ -13,5 +15,5 @@ var (
testPlaintextModulus = []uint64{0x101, 0xffc001}
testParams = []bgv.ParametersLiteral{testInsecure}
testParams = []heint.ParametersLiteral{testInsecure}
)
+57 -48
View File
@@ -1,31 +1,49 @@
package mpbgv
package mheint
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/bgv"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// MaskedTransformProtocol is a struct storing the parameters for the [MaskedTransformProtocol] protocol.
// MaskedTransformProtocol is a struct storing the parameters for the MaskedTransformProtocol protocol.
type MaskedTransformProtocol struct {
e2s EncToShareProtocol
s2e ShareToEncProtocol
tmpPt ring.Poly
tmpMask ring.Poly
tmpMaskPerm ring.Poly
}
// ShallowCopy creates a shallow copy of MaskedTransformProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// MaskedTransformProtocol can be used concurrently.
func (rfp MaskedTransformProtocol) ShallowCopy() MaskedTransformProtocol {
params := rfp.e2s.params
return MaskedTransformProtocol{
e2s: rfp.e2s.ShallowCopy(),
s2e: rfp.s2e.ShallowCopy(),
tmpPt: params.RingQ().NewPoly(),
tmpMask: params.RingT().NewPoly(),
tmpMaskPerm: params.RingT().NewPoly(),
}
}
// MaskedTransformFunc is a struct containing a user-defined in-place function that can be applied to masked integer plaintexts, as a part of the
// Masked Transform Protocol.
// The function is called with a vector of integers modulo bgv.Parameters.PlaintextModulus() of size bgv.Parameters.N() as input, and must write
// The function is called with a vector of integers modulo heint.Parameters.PlaintextModulus() of size heint.Parameters.N() as input, and must write
// its output on the same buffer.
// Transform can be the identity.
//
// - Decode: if true, then the masked BFV plaintext will be decoded before applying Transform.
// - Recode: if true, then the masked BFV plaintext will be recoded after applying Transform.
//
// Decode (true/false) -> Transform -> Recode (true/false).
// Decode: if true, then the masked BFV plaintext will be decoded before applying Transform.
// Recode: if true, then the masked BFV plaintext will be recoded after applying Transform.
// i.e. : Decode (true/false) -> Transform -> Recode (true/false).
type MaskedTransformFunc struct {
Decode bool
Func func(coeffs []uint64)
@@ -33,7 +51,7 @@ type MaskedTransformFunc struct {
}
// NewMaskedTransformProtocol creates a new instance of the PermuteProtocol.
func NewMaskedTransformProtocol(paramsIn, paramsOut bgv.Parameters, noiseFlooding ring.DistributionParameters) (rfp MaskedTransformProtocol, err error) {
func NewMaskedTransformProtocol(paramsIn, paramsOut heint.Parameters, noiseFlooding ring.DistributionParameters) (rfp MaskedTransformProtocol, err error) {
if paramsIn.N() > paramsOut.N() {
return MaskedTransformProtocol{}, fmt.Errorf("newMaskedTransformProtocol: paramsIn.N() != paramsOut.N()")
@@ -48,23 +66,26 @@ func NewMaskedTransformProtocol(paramsIn, paramsOut bgv.Parameters, noiseFloodin
return
}
rfp.tmpPt = paramsOut.RingQ().NewPoly()
rfp.tmpMask = paramsIn.RingT().NewPoly()
rfp.tmpMaskPerm = paramsIn.RingT().NewPoly()
return
}
// SampleCRP samples a common random polynomial to be used in the Masked-Transform protocol from the provided
// common reference string.
func (rfp *MaskedTransformProtocol) SampleCRP(level int, crs sampling.PRNG) multiparty.KeySwitchCRP {
func (rfp *MaskedTransformProtocol) SampleCRP(level int, crs sampling.PRNG) mhe.KeySwitchCRP {
return rfp.s2e.SampleCRP(level, crs)
}
// AllocateShare allocates the shares of the PermuteProtocol
func (rfp MaskedTransformProtocol) AllocateShare(levelDecrypt, levelRecrypt int) multiparty.RefreshShare {
return multiparty.RefreshShare{EncToShareShare: rfp.e2s.AllocateShare(levelDecrypt), ShareToEncShare: rfp.s2e.AllocateShare(levelRecrypt)}
func (rfp MaskedTransformProtocol) AllocateShare(levelDecrypt, levelRecrypt int) mhe.RefreshShare {
return mhe.RefreshShare{EncToShareShare: rfp.e2s.AllocateShare(levelDecrypt), ShareToEncShare: rfp.s2e.AllocateShare(levelRecrypt)}
}
// GenShare generates the shares of the PermuteProtocol.
// ct1 is the degree 1 element of a rlwe.Ciphertext, i.e. rlwe.Ciphertext.Value[1].
func (rfp MaskedTransformProtocol) GenShare(skIn, skOut *rlwe.SecretKey, ct *rlwe.Ciphertext, crs multiparty.KeySwitchCRP, transform *MaskedTransformFunc, shareOut *multiparty.RefreshShare) (err error) {
func (rfp MaskedTransformProtocol) GenShare(skIn, skOut *rlwe.SecretKey, ct *rlwe.Ciphertext, scale rlwe.Scale, crs mhe.KeySwitchCRP, transform *MaskedTransformFunc, shareOut *mhe.RefreshShare) (err error) {
if ct.Level() < shareOut.EncToShareShare.Value.Level() {
return fmt.Errorf("cannot GenShare: ct[1] level must be at least equal to EncToShareShare level")
@@ -74,14 +95,13 @@ func (rfp MaskedTransformProtocol) GenShare(skIn, skOut *rlwe.SecretKey, ct *rlw
return fmt.Errorf("cannot GenShare: crs level must be equal to ShareToEncShare")
}
tmpMask := rfp.e2s.params.RingT().NewPoly()
rfp.e2s.GenShare(skIn, ct, &multiparty.AdditiveShare{Value: tmpMask}, &shareOut.EncToShareShare)
mask := tmpMask
rfp.e2s.GenShare(skIn, ct, &mhe.AdditiveShare{Value: rfp.tmpMask}, &shareOut.EncToShareShare)
mask := rfp.tmpMask
if transform != nil {
coeffs := make([]uint64, len(mask.Coeffs[0]))
if transform.Decode {
if err := rfp.e2s.encoder.DecodeRingT(mask, ct.Scale, coeffs); err != nil {
if err := rfp.e2s.encoder.DecodeRingT(mask, scale, coeffs); err != nil {
return fmt.Errorf("cannot GenShare: %w", err)
}
} else {
@@ -90,26 +110,22 @@ func (rfp MaskedTransformProtocol) GenShare(skIn, skOut *rlwe.SecretKey, ct *rlw
transform.Func(coeffs)
tmpMaskPerm := rfp.e2s.params.RingT().NewPoly()
if transform.Encode {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ct.Scale, tmpMaskPerm); err != nil {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, scale, rfp.tmpMaskPerm); err != nil {
return fmt.Errorf("cannot GenShare: %w", err)
}
} else {
copy(tmpMaskPerm.Coeffs[0], coeffs)
copy(rfp.tmpMaskPerm.Coeffs[0], coeffs)
}
mask = tmpMaskPerm
mask = rfp.tmpMaskPerm
}
// Stores the ciphertext metadata on the public share
shareOut.MetaData = *ct.MetaData
return rfp.s2e.GenShare(skOut, crs, multiparty.AdditiveShare{Value: mask}, &shareOut.ShareToEncShare)
return rfp.s2e.GenShare(skOut, crs, mhe.AdditiveShare{Value: mask}, &shareOut.ShareToEncShare)
}
// AggregateShares sums share1 and share2 on shareOut.
func (rfp MaskedTransformProtocol) AggregateShares(share1, share2 multiparty.RefreshShare, shareOut *multiparty.RefreshShare) (err error) {
func (rfp MaskedTransformProtocol) AggregateShares(share1, share2 mhe.RefreshShare, shareOut *mhe.RefreshShare) (err error) {
if share1.EncToShareShare.Value.Level() != share2.EncToShareShare.Value.Level() || share1.EncToShareShare.Value.Level() != shareOut.EncToShareShare.Value.Level() {
return fmt.Errorf("cannot AggregateShares: all e2s shares must be at the same level")
@@ -126,11 +142,7 @@ func (rfp MaskedTransformProtocol) AggregateShares(share1, share2 multiparty.Ref
}
// Transform applies Decrypt, Recode and Recrypt on the input ciphertext.
func (rfp MaskedTransformProtocol) Transform(ct *rlwe.Ciphertext, transform *MaskedTransformFunc, crs multiparty.KeySwitchCRP, share multiparty.RefreshShare, ciphertextOut *rlwe.Ciphertext) (err error) {
if !ct.MetaData.Equal(&share.MetaData) {
return fmt.Errorf("cannot Transform: input ct.MetaData != share.MetaData")
}
func (rfp MaskedTransformProtocol) Transform(ct *rlwe.Ciphertext, transform *MaskedTransformFunc, crs mhe.KeySwitchCRP, share mhe.RefreshShare, ciphertextOut *rlwe.Ciphertext) (err error) {
if ct.Level() < share.EncToShareShare.Value.Level() {
return fmt.Errorf("cannot Transform: input ciphertext level must be at least equal to e2s level")
@@ -142,9 +154,8 @@ func (rfp MaskedTransformProtocol) Transform(ct *rlwe.Ciphertext, transform *Mas
return fmt.Errorf("cannot Transform: crs level and s2e level must be the same")
}
tmpMask := rfp.e2s.params.RingT().NewPoly()
rfp.e2s.GetShare(nil, share.EncToShareShare, ct, &multiparty.AdditiveShare{Value: tmpMask}) // tmpMask RingT(m - sum M_i)
mask := tmpMask
rfp.e2s.GetShare(nil, share.EncToShareShare, ct, &mhe.AdditiveShare{Value: rfp.tmpMask}) // tmpMask RingT(m - sum M_i)
mask := rfp.tmpMask
if transform != nil {
coeffs := make([]uint64, len(mask.Coeffs[0]))
@@ -158,24 +169,22 @@ func (rfp MaskedTransformProtocol) Transform(ct *rlwe.Ciphertext, transform *Mas
transform.Func(coeffs)
tmpMaskPerm := rfp.e2s.params.RingT().NewPoly()
if transform.Encode {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ciphertextOut.Scale, tmpMaskPerm); err != nil {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ciphertextOut.Scale, rfp.tmpMaskPerm); err != nil {
return fmt.Errorf("cannot Transform: %w", err)
}
} else {
copy(tmpMaskPerm.Coeffs[0], coeffs)
copy(rfp.tmpMaskPerm.Coeffs[0], coeffs)
}
mask = tmpMaskPerm
mask = rfp.tmpMaskPerm
}
ciphertextOut.Resize(ciphertextOut.Degree(), maxLevel)
tmpPt := rfp.s2e.params.RingQ().AtLevel(maxLevel).NewPoly()
rfp.s2e.encoder.RingT2Q(maxLevel, true, mask, tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).NTT(tmpPt, tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).Add(tmpPt, share.ShareToEncShare.Value, ciphertextOut.Value[0])
rfp.s2e.encoder.RingT2Q(maxLevel, true, mask, rfp.tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).NTT(rfp.tmpPt, rfp.tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).Add(rfp.tmpPt, share.ShareToEncShare.Value, ciphertextOut.Value[0])
return rfp.s2e.GetEncryption(multiparty.KeySwitchShare{Value: ciphertextOut.Value[0]}, crs, ciphertextOut)
return rfp.s2e.GetEncryption(mhe.KeySwitchShare{Value: ciphertextOut.Value[0]}, crs, ciphertextOut)
}
+17 -18
View File
@@ -1,26 +1,25 @@
package mpckks
package mhefloat
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/bignum"
)
func BenchmarkMultiPartyCKKS(b *testing.B) {
func BenchmarkMHEFloat(b *testing.B) {
var err error
var testParams []ckks.ParametersLiteral
var testParams []hefloat.ParametersLiteral
switch {
case *flagParamString != "": // the custom test suite reads the parameters from the -params flag
testParams = append(testParams, ckks.ParametersLiteral{})
testParams = append(testParams, hefloat.ParametersLiteral{})
if err = json.Unmarshal([]byte(*flagParamString), &testParams[0]); err != nil {
b.Fatal(err)
}
@@ -34,8 +33,8 @@ func BenchmarkMultiPartyCKKS(b *testing.B) {
paramsLiteral.RingType = ringType
var params ckks.Parameters
if params, err = ckks.NewParametersFromLiteral(paramsLiteral); err != nil {
var params hefloat.Parameters
if params, err = hefloat.NewParametersFromLiteral(paramsLiteral); err != nil {
b.Fatal(err)
}
N := 3
@@ -63,7 +62,7 @@ func benchRefresh(tc *testContext, b *testing.B) {
type Party struct {
RefreshProtocol
s *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
p := new(Party)
@@ -73,7 +72,7 @@ func benchRefresh(tc *testContext, b *testing.B) {
p.s = sk0Shards[0]
p.share = p.AllocateShare(minLevel, params.MaxLevel())
ciphertext := ckks.NewCiphertext(params, 1, minLevel)
ciphertext := hefloat.NewCiphertext(params, 1, minLevel)
crp := p.SampleCRP(params.MaxLevel(), tc.crs)
@@ -92,7 +91,7 @@ func benchRefresh(tc *testContext, b *testing.B) {
})
b.Run(GetTestName("Refresh/Finalize", tc.NParties, params), func(b *testing.B) {
opOut := ckks.NewCiphertext(params, 1, params.MaxLevel())
opOut := hefloat.NewCiphertext(params, 1, params.MaxLevel())
for i := 0; i < b.N; i++ {
p.Finalize(ciphertext, crp, p.share, opOut)
}
@@ -116,10 +115,10 @@ func benchMaskedTransform(tc *testContext, b *testing.B) {
type Party struct {
MaskedLinearTransformationProtocol
s *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
ciphertext := ckks.NewCiphertext(params, 1, minLevel)
ciphertext := hefloat.NewCiphertext(params, 1, minLevel)
p := new(Party)
p.MaskedLinearTransformationProtocol, _ = NewMaskedLinearTransformationProtocol(params, params, logBound, params.Xe())
@@ -154,7 +153,7 @@ func benchMaskedTransform(tc *testContext, b *testing.B) {
})
b.Run(GetTestName("Refresh&Transform/Transform", tc.NParties, params), func(b *testing.B) {
opOut := ckks.NewCiphertext(params, 1, params.MaxLevel())
opOut := hefloat.NewCiphertext(params, 1, params.MaxLevel())
for i := 0; i < b.N; i++ {
p.Transform(ciphertext, transform, crp, p.share, opOut)
}
+41 -41
View File
@@ -1,4 +1,4 @@
package mpckks
package mhefloat
import (
"encoding/json"
@@ -11,19 +11,19 @@ import (
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
var flagParamString = flag.String("params", "", "specify the test cryptographic parameters as a JSON string. Overrides -short and -long.")
var printPrecisionStats = flag.Bool("print-precision", false, "print precision stats")
func GetTestName(opname string, parties int, params ckks.Parameters) string {
func GetTestName(opname string, parties int, params hefloat.Parameters) string {
return fmt.Sprintf("%s/RingType=%s/logN=%d/logQP=%d/Qi=%d/Pi=%d/LogDefaultScale=%d/Parties=%d",
opname,
params.RingType(),
@@ -36,14 +36,14 @@ func GetTestName(opname string, parties int, params ckks.Parameters) string {
}
type testContext struct {
params ckks.Parameters
params hefloat.Parameters
NParties int
ringQ *ring.Ring
ringP *ring.Ring
encoder *ckks.Encoder
evaluator *ckks.Evaluator
encoder *hefloat.Encoder
evaluator *hefloat.Evaluator
encryptorPk0 *rlwe.Encryptor
decryptorSk0 *rlwe.Decryptor
@@ -58,18 +58,18 @@ type testContext struct {
sk0Shards []*rlwe.SecretKey
sk1Shards []*rlwe.SecretKey
crs multiparty.CRS
crs mhe.CRS
uniformSampler *ring.UniformSampler
}
func TestMULTIPARTYFloat(t *testing.T) {
func TestMHEFloat(t *testing.T) {
var err error
var testParams []ckks.ParametersLiteral
var testParams []hefloat.ParametersLiteral
switch {
case *flagParamString != "": // the custom test suite reads the parameters from the -params flag
testParams = append(testParams, ckks.ParametersLiteral{})
testParams = append(testParams, hefloat.ParametersLiteral{})
if err = json.Unmarshal([]byte(*flagParamString), &testParams[0]); err != nil {
t.Fatal(err)
}
@@ -83,8 +83,8 @@ func TestMULTIPARTYFloat(t *testing.T) {
paramsLiteral.RingType = ringType
var params ckks.Parameters
if params, err = ckks.NewParametersFromLiteral(paramsLiteral); err != nil {
var params hefloat.Parameters
if params, err = hefloat.NewParametersFromLiteral(paramsLiteral); err != nil {
t.Fatal(err)
}
N := 3
@@ -104,7 +104,7 @@ func TestMULTIPARTYFloat(t *testing.T) {
}
}
func genTestParams(params ckks.Parameters, NParties int) (tc *testContext, err error) {
func genTestParams(params hefloat.Parameters, NParties int) (tc *testContext, err error) {
tc = new(testContext)
@@ -119,8 +119,8 @@ func genTestParams(params ckks.Parameters, NParties int) (tc *testContext, err e
tc.crs = prng
tc.uniformSampler = ring.NewUniformSampler(prng, params.RingQ())
tc.encoder = ckks.NewEncoder(tc.params)
tc.evaluator = ckks.NewEvaluator(tc.params, nil)
tc.encoder = hefloat.NewEncoder(tc.params)
tc.evaluator = hefloat.NewEvaluator(tc.params, nil)
kgen := rlwe.NewKeyGenerator(tc.params)
@@ -165,9 +165,9 @@ func testEncToShareProtocol(tc *testContext, t *testing.T) {
e2s EncToShareProtocol
s2e ShareToEncProtocol
sk *rlwe.SecretKey
publicShareE2S multiparty.KeySwitchShare
publicShareS2E multiparty.KeySwitchShare
secretShare multiparty.AdditiveShareBigint
publicShareE2S mhe.KeySwitchShare
publicShareS2E mhe.KeySwitchShare
secretShare mhe.AdditiveShareBigint
}
params := tc.params
@@ -216,12 +216,12 @@ func testEncToShareProtocol(tc *testContext, t *testing.T) {
}
}
pt := ckks.NewPlaintext(params, ciphertext.Level())
pt := hefloat.NewPlaintext(params, ciphertext.Level())
pt.IsNTT = false
pt.Scale = ciphertext.Scale
tc.ringQ.AtLevel(pt.Level()).SetCoefficientsBigint(rec.Value, pt.Value)
ckks.VerifyTestVectors(params, tc.encoder, nil, coeffs, pt, params.LogDefaultScale(), 0, *printPrecisionStats, t)
hefloat.VerifyTestVectors(params, tc.encoder, nil, coeffs, pt, params.LogDefaultScale(), 0, *printPrecisionStats, t)
crp := P[0].s2e.SampleCRP(params.MaxLevel(), tc.crs)
@@ -232,11 +232,11 @@ func testEncToShareProtocol(tc *testContext, t *testing.T) {
}
}
ctRec := ckks.NewCiphertext(params, 1, params.MaxLevel())
ctRec := hefloat.NewCiphertext(params, 1, params.MaxLevel())
ctRec.Scale = params.DefaultScale()
P[0].s2e.GetEncryption(P[0].publicShareS2E, crp, ctRec)
ckks.VerifyTestVectors(params, tc.encoder, tc.decryptorSk0, coeffs, ctRec, params.LogDefaultScale(), 0, *printPrecisionStats, t)
hefloat.VerifyTestVectors(params, tc.encoder, tc.decryptorSk0, coeffs, ctRec, params.LogDefaultScale(), 0, *printPrecisionStats, t)
})
}
@@ -253,9 +253,9 @@ func testRefresh(tc *testContext, t *testing.T) {
t.Run(GetTestName("N->2N/Transform=nil", tc.NParties, paramsIn), func(t *testing.T) {
var paramsOut ckks.Parameters
var paramsOut hefloat.Parameters
var err error
paramsOut, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
paramsOut, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: paramsIn.LogN() + 1,
LogQ: []int{54, 54, 54, 49, 49, 49, 49, 49, 49},
LogP: []int{52, 52},
@@ -277,9 +277,9 @@ func testRefresh(tc *testContext, t *testing.T) {
t.Run(GetTestName("2N->N/Transform=nil", tc.NParties, tc.params), func(t *testing.T) {
var paramsOut ckks.Parameters
var paramsOut hefloat.Parameters
var err error
paramsOut, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
paramsOut, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: paramsIn.LogN() - 1,
LogQ: []int{54, 54, 54, 49, 49, 49, 49, 49, 49},
LogP: []int{52, 52},
@@ -317,9 +317,9 @@ func testRefresh(tc *testContext, t *testing.T) {
t.Run(GetTestName("N->2N/Transform=true", tc.NParties, paramsIn), func(t *testing.T) {
var paramsOut ckks.Parameters
var paramsOut hefloat.Parameters
var err error
paramsOut, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
paramsOut, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: paramsIn.LogN() + 1,
LogQ: []int{54, 54, 54, 49, 49, 49, 49, 49, 49},
LogP: []int{52, 52},
@@ -352,9 +352,9 @@ func testRefresh(tc *testContext, t *testing.T) {
t.Run(GetTestName("2N->N/Transform=true", tc.NParties, tc.params), func(t *testing.T) {
var paramsOut ckks.Parameters
var paramsOut hefloat.Parameters
var err error
paramsOut, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
paramsOut, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
LogN: paramsIn.LogN() - 1,
LogQ: []int{54, 54, 54, 49, 49, 49, 49, 49, 49},
LogP: []int{52, 52},
@@ -386,7 +386,7 @@ func testRefresh(tc *testContext, t *testing.T) {
})
}
func testRefreshParameterized(tc *testContext, paramsOut ckks.Parameters, skOut []*rlwe.SecretKey, transform *MaskedLinearTransformationFunc, t *testing.T) {
func testRefreshParameterized(tc *testContext, paramsOut hefloat.Parameters, skOut []*rlwe.SecretKey, transform *MaskedLinearTransformationFunc, t *testing.T) {
var err error
@@ -410,7 +410,7 @@ func testRefreshParameterized(tc *testContext, paramsOut ckks.Parameters, skOut
MaskedLinearTransformationProtocol
sIn *rlwe.SecretKey
sOut *rlwe.SecretKey
share multiparty.RefreshShare
share mhe.RefreshShare
}
coeffs, _, ciphertext := newTestVectors(tc, encIn, -1, 1, utils.Min(paramsIn.LogMaxSlots(), paramsOut.LogMaxSlots()))
@@ -435,7 +435,7 @@ func testRefreshParameterized(tc *testContext, paramsOut ckks.Parameters, skOut
t.Fail()
}
} else {
p.MaskedLinearTransformationProtocol = RefreshParties[0].MaskedLinearTransformationProtocol
p.MaskedLinearTransformationProtocol = RefreshParties[0].MaskedLinearTransformationProtocol.ShallowCopy()
}
p.sIn = tc.sk0Shards[i]
@@ -464,7 +464,7 @@ func testRefreshParameterized(tc *testContext, paramsOut ckks.Parameters, skOut
transform.Func(coeffs)
}
ckks.VerifyTestVectors(paramsOut, ckks.NewEncoder(paramsOut), rlwe.NewDecryptor(paramsOut, skIdealOut), coeffs, ciphertext, paramsOut.LogDefaultScale(), 0, *printPrecisionStats, t)
hefloat.VerifyTestVectors(paramsOut, hefloat.NewEncoder(paramsOut), rlwe.NewDecryptor(paramsOut, skIdealOut), coeffs, ciphertext, paramsOut.LogDefaultScale(), 0, *printPrecisionStats, t)
}
func newTestVectors(tc *testContext, encryptor *rlwe.Encryptor, a, b complex128, logSlots int) (values []*bignum.Complex, plaintext *rlwe.Plaintext, ciphertext *rlwe.Ciphertext) {
@@ -475,7 +475,7 @@ func newTestVectorsAtScale(tc *testContext, encryptor *rlwe.Encryptor, a, b comp
prec := tc.encoder.Prec()
pt = ckks.NewPlaintext(tc.params, tc.params.MaxLevel())
pt = hefloat.NewPlaintext(tc.params, tc.params.MaxLevel())
pt.Scale = scale
pt.LogDimensions.Cols = logSlots
+91 -56
View File
@@ -1,67 +1,92 @@
package mpckks
package mhefloat
import (
"fmt"
"math/big"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/multiparty"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/sampling"
"github.com/luxdefi/lattice/v5/he/hefloat"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/utils"
"github.com/luxdefi/lattice/v5/utils/bignum"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
// EncToShareProtocol is the structure storing the parameters and temporary buffers
// required by the encryption-to-shares protocol.
type EncToShareProtocol struct {
multiparty.KeySwitchProtocol
mhe.KeySwitchProtocol
params ckks.Parameters
zero *rlwe.SecretKey
params hefloat.Parameters
zero *rlwe.SecretKey
maskBigint []*big.Int
buff ring.Poly
}
func NewAdditiveShare(params ckks.Parameters, logSlots int) multiparty.AdditiveShareBigint {
func NewAdditiveShare(params hefloat.Parameters, logSlots int) mhe.AdditiveShareBigint {
nValues := 1 << logSlots
if params.RingType() == ring.Standard {
nValues <<= 1
}
return multiparty.NewAdditiveShareBigint(nValues)
return mhe.NewAdditiveShareBigint(nValues)
}
// ShallowCopy creates a shallow copy of EncToShareProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// EncToShareProtocol can be used concurrently.
func (e2s EncToShareProtocol) ShallowCopy() EncToShareProtocol {
maskBigint := make([]*big.Int, len(e2s.maskBigint))
for i := range maskBigint {
maskBigint[i] = new(big.Int)
}
return EncToShareProtocol{
KeySwitchProtocol: e2s.KeySwitchProtocol.ShallowCopy(),
params: e2s.params,
zero: e2s.zero,
maskBigint: maskBigint,
buff: e2s.params.RingQ().NewPoly(),
}
}
// NewEncToShareProtocol creates a new EncToShareProtocol struct from the passed parameters.
func NewEncToShareProtocol(params ckks.Parameters, noise ring.DistributionParameters) (EncToShareProtocol, error) {
func NewEncToShareProtocol(params hefloat.Parameters, noise ring.DistributionParameters) (EncToShareProtocol, error) {
e2s := EncToShareProtocol{}
var err error
if e2s.KeySwitchProtocol, err = multiparty.NewKeySwitchProtocol(params.Parameters, noise); err != nil {
if e2s.KeySwitchProtocol, err = mhe.NewKeySwitchProtocol(params.Parameters, noise); err != nil {
return EncToShareProtocol{}, err
}
e2s.params = params
e2s.zero = rlwe.NewSecretKey(params.Parameters)
e2s.maskBigint = make([]*big.Int, params.N())
for i := range e2s.maskBigint {
e2s.maskBigint[i] = new(big.Int)
}
e2s.buff = e2s.params.RingQ().NewPoly()
return e2s, nil
}
// AllocateShare allocates a share of the EncToShare protocol
func (e2s EncToShareProtocol) AllocateShare(level int) (share multiparty.KeySwitchShare) {
func (e2s EncToShareProtocol) AllocateShare(level int) (share mhe.KeySwitchShare) {
return e2s.KeySwitchProtocol.AllocateShare(level)
}
// GenShare generates a party's share in the encryption-to-shares protocol. This share consist in the additive secret-share of the party
// which is written in secretShareOut and in the public masked-decryption share written in publicShareOut.
// This protocol requires additional inputs which are:
//
// - logBound : the bit length of the masks
// - ct: the ciphertext to share
//
// This protocol requires additional inputs which are :
// logBound : the bit length of the masks
// ct: the ciphertext to share
// publicShareOut is always returned in the NTT domain.
// The method [GetMinimumLevelForRefresh] should be used to get the minimum level at which EncToShare can be called while still ensure 128-bits of security, as well as the
// The method "GetMinimumLevelForBootstrapping" should be used to get the minimum level at which EncToShare can be called while still ensure 128-bits of security, as well as the
// value for logBound.
func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, logBound uint, ct *rlwe.Ciphertext, secretShareOut *multiparty.AdditiveShareBigint, publicShareOut *multiparty.KeySwitchShare) (err error) {
func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, logBound uint, ct *rlwe.Ciphertext, secretShareOut *mhe.AdditiveShareBigint, publicShareOut *mhe.KeySwitchShare) (err error) {
levelQ := utils.Min(ct.Value[1].Level(), publicShareOut.Value.Level())
@@ -91,16 +116,15 @@ func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, logBound uint, ct *rl
dslots *= 2
}
maskBigint := make([]*big.Int, dslots)
// Generate the mask in Z[Y] for Y = X^{N/(2*slots)}
for i := 0; i < dslots; i++ {
maskBigint[i] = bignum.RandInt(prng, bound)
sign = maskBigint[i].Cmp(boundHalf)
e2s.maskBigint[i] = bignum.RandInt(prng, bound)
sign = e2s.maskBigint[i].Cmp(boundHalf)
if sign == 1 || sign == 0 {
maskBigint[i].Sub(maskBigint[i], bound)
e2s.maskBigint[i].Sub(e2s.maskBigint[i], bound)
}
secretShareOut.Value[i].Set(maskBigint[i])
secretShareOut.Value[i].Set(e2s.maskBigint[i])
}
// Encrypt the mask
@@ -108,33 +132,31 @@ func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, logBound uint, ct *rl
e2s.KeySwitchProtocol.GenShare(sk, e2s.zero, ct, publicShareOut)
// Positional -> RNS -> NTT
buffQ := ringQ.NewPoly()
ringQ.SetCoefficientsBigint(secretShareOut.Value[:dslots], buffQ)
rlwe.NTTSparseAndMontgomery(ringQ, ct.MetaData, buffQ)
ringQ.SetCoefficientsBigint(secretShareOut.Value[:dslots], e2s.buff)
rlwe.NTTSparseAndMontgomery(ringQ, ct.MetaData, e2s.buff)
// Subtracts the mask to the encryption of zero
ringQ.Sub(publicShareOut.Value, buffQ, publicShareOut.Value)
ringQ.Sub(publicShareOut.Value, e2s.buff, publicShareOut.Value)
return
}
// GetShare is the final step of the encryption-to-share protocol. It performs the masked decryption of the target ciphertext followed by a
// the removal of the caller's secretShare as generated in the GenShare method.
// If the caller is not secret-key-share holder (i.e., didn't generate a decryption share), secretShare can be set to nil.
// If the caller is not secret-key-share holder (i.e., didn't generate a decryption share), `secretShare` can be set to nil.
// Therefore, in order to obtain an additive sharing of the message, only one party should call this method, and the other parties should use
// the secretShareOut output of the GenShare method.
func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShareBigint, aggregatePublicShare multiparty.KeySwitchShare, ct *rlwe.Ciphertext, secretShareOut *multiparty.AdditiveShareBigint) {
func (e2s EncToShareProtocol) GetShare(secretShare *mhe.AdditiveShareBigint, aggregatePublicShare mhe.KeySwitchShare, ct *rlwe.Ciphertext, secretShareOut *mhe.AdditiveShareBigint) {
levelQ := utils.Min(ct.Level(), aggregatePublicShare.Value.Level())
ringQ := e2s.params.RingQ().AtLevel(levelQ)
buffQ := ringQ.NewPoly()
// Adds the decryption share on the ciphertext and stores the result in a buff
ringQ.Add(aggregatePublicShare.Value, ct.Value[0], buffQ)
ringQ.Add(aggregatePublicShare.Value, ct.Value[0], e2s.buff)
// INTT -> RNS -> Positional
ringQ.INTT(buffQ, buffQ)
ringQ.INTT(e2s.buff, e2s.buff)
dslots := ct.Slots()
if ringQ.Type() == ring.Standard {
@@ -143,23 +165,19 @@ func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShareBigi
gap := ringQ.N() / dslots
maskBigint := make([]*big.Int, ringQ.N())
for i := range maskBigint {
maskBigint[i] = new(big.Int)
}
ringQ.PolyToBigintCentered(buffQ, gap, maskBigint)
ringQ.PolyToBigintCentered(e2s.buff, gap, e2s.maskBigint)
// Subtracts the last mask
if secretShare != nil {
a := secretShareOut.Value
b := maskBigint
b := e2s.maskBigint
c := secretShare.Value
for i := range secretShareOut.Value[:dslots] {
a[i].Add(c[i], b[i])
}
} else {
a := secretShareOut.Value
b := maskBigint
b := e2s.maskBigint
for i := range secretShareOut.Value[:dslots] {
a[i].Set(b[i])
}
@@ -169,33 +187,50 @@ func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShareBigi
// ShareToEncProtocol is the structure storing the parameters and temporary buffers
// required by the shares-to-encryption protocol.
type ShareToEncProtocol struct {
multiparty.KeySwitchProtocol
params ckks.Parameters
zero *rlwe.SecretKey
mhe.KeySwitchProtocol
params hefloat.Parameters
tmp ring.Poly
ssBigint []*big.Int
zero *rlwe.SecretKey
}
// ShallowCopy creates a shallow copy of ShareToEncProtocol in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// ShareToEncProtocol can be used concurrently.
func (s2e ShareToEncProtocol) ShallowCopy() ShareToEncProtocol {
return ShareToEncProtocol{
KeySwitchProtocol: s2e.KeySwitchProtocol.ShallowCopy(),
params: s2e.params,
tmp: s2e.params.RingQ().NewPoly(),
ssBigint: make([]*big.Int, s2e.params.N()),
zero: s2e.zero,
}
}
// NewShareToEncProtocol creates a new ShareToEncProtocol struct from the passed parameters.
func NewShareToEncProtocol(params ckks.Parameters, noise ring.DistributionParameters) (ShareToEncProtocol, error) {
func NewShareToEncProtocol(params hefloat.Parameters, noise ring.DistributionParameters) (ShareToEncProtocol, error) {
s2e := ShareToEncProtocol{}
var err error
if s2e.KeySwitchProtocol, err = multiparty.NewKeySwitchProtocol(params.Parameters, noise); err != nil {
if s2e.KeySwitchProtocol, err = mhe.NewKeySwitchProtocol(params.Parameters, noise); err != nil {
return ShareToEncProtocol{}, err
}
s2e.params = params
s2e.tmp = s2e.params.RingQ().NewPoly()
s2e.ssBigint = make([]*big.Int, s2e.params.N())
s2e.zero = rlwe.NewSecretKey(params.Parameters)
return s2e, nil
}
// AllocateShare allocates a share of the ShareToEnc protocol
func (s2e ShareToEncProtocol) AllocateShare(level int) (share multiparty.KeySwitchShare) {
func (s2e ShareToEncProtocol) AllocateShare(level int) (share mhe.KeySwitchShare) {
return s2e.KeySwitchProtocol.AllocateShare(level)
}
// GenShare generates a party's in the shares-to-encryption protocol given the party's secret-key share `sk`, a common
// polynomial sampled from the CRS `crs` and the party's secret share of the message.
func (s2e ShareToEncProtocol) GenShare(sk *rlwe.SecretKey, crs multiparty.KeySwitchCRP, metadata *rlwe.MetaData, secretShare multiparty.AdditiveShareBigint, c0ShareOut *multiparty.KeySwitchShare) (err error) {
func (s2e ShareToEncProtocol) GenShare(sk *rlwe.SecretKey, crs mhe.KeySwitchCRP, metadata *rlwe.MetaData, secretShare mhe.AdditiveShareBigint, c0ShareOut *mhe.KeySwitchShare) (err error) {
if crs.Value.Level() != c0ShareOut.Value.Level() {
return fmt.Errorf("cannot GenShare: crs and c0ShareOut level must be equal")
@@ -215,20 +250,20 @@ func (s2e ShareToEncProtocol) GenShare(sk *rlwe.SecretKey, crs multiparty.KeySwi
dslots *= 2
}
buffQ := ringQ.NewPoly()
// Positional -> RNS -> NTT
ringQ.SetCoefficientsBigint(secretShare.Value[:dslots], buffQ)
ringQ.SetCoefficientsBigint(secretShare.Value[:dslots], s2e.tmp)
rlwe.NTTSparseAndMontgomery(ringQ, metadata, buffQ)
rlwe.NTTSparseAndMontgomery(ringQ, metadata, s2e.tmp)
ringQ.Add(c0ShareOut.Value, buffQ, c0ShareOut.Value)
ringQ.Add(c0ShareOut.Value, s2e.tmp, c0ShareOut.Value)
return
}
// GetEncryption computes the final encryption of the secret-shared message when provided with the aggregation `c0Agg` of the parties'
// share in the protocol and with the common, CRS-sampled polynomial `crs`.
func (s2e ShareToEncProtocol) GetEncryption(c0Agg multiparty.KeySwitchShare, crs multiparty.KeySwitchCRP, opOut *rlwe.Ciphertext) (err error) {
func (s2e ShareToEncProtocol) GetEncryption(c0Agg mhe.KeySwitchShare, crs mhe.KeySwitchCRP, opOut *rlwe.Ciphertext) (err error) {
if opOut.Degree() != 1 {
return fmt.Errorf("cannot GetEncryption: opOut must have degree 1")
}

Some files were not shown because too many files have changed in this diff Show More