This library implements the fully homomorphic encryption algorithm from TFHE
using CUDA and OpenCL. Unlike TFHE
, where FFT is used internally to speed up polynomial multiplication, nuFHE
can use either FFT or purely integer NTT (DFT-like transform on a finite field). The latter is based on the arithmetic operations and NTT scheme from cuFHE
.
Platform | Library | Performance (ms/bit) | |
Binary Gate | MUX Gate | ||
Single Core/Single GPU - FFT | TFHE (CPU) | 13 | 26 |
nuFHE | 0.13 | 0.22 | |
Speedup | 100.9 | 117.7 | |
Single Core/Single GPU - NTT | cuFHE | 0.35 | N/A |
nuFHE | 0.35 | 0.67 | |
Speedup | 1.0 | - |
The code from this example can be found in examples/gate_nand.py
.
nufhe
uses reikna
as a backend for GPU operations, and all the nufhe
calls require a reikna
Thread
object, encapsulating a GPU context and a serialization queue for GPU kernel calls. It can be created interactively:
from reikna.cluda import cuda_api
thr = cuda_api().Thread.create(interactive=True)
where the user will be offered to choose between available platforms and videocards. Alternatively, one can pick an arbitrary available platform/device:
thr = cuda_api().Thread.create()
It is also possible to create a Thread
using a known device, or existing PyCUDA
or PyOpenCL
context. This is advanced usage, for those who plan to integrate nuFHE
into a larger GPU-based program. See the documentation for Thread
and Thread.create()
for details.
If one wants to use OpenCL instead of CUDA, cuda_api
should be replaced with ocl_api
. Alternatively, one can use any_api
to select an arbitrary available one.
The next step is the creation of a private and a public key. The former is used to encrypt plaintexts or decrypt cyphertexts; the latter is required to apply gates to encrypted data. Note that the public key can be rather large (on the order of 100Mb).
import numpy
import nufhe
rng = numpy.random.RandomState()
private_key, public_key = nufhe.make_key_pair(thr, rng)
make_key_pair
takes some keyword parameters that affect the security of the algorithm; the default values correspond to about 110 bits of security.
Using the private key we can encrypt some data. nuFHE
gates operate on bit arrays:
size = 32
bits1 = rng.randint(0, 2, size=size).astype(numpy.bool)
bits2 = rng.randint(0, 2, size=size).astype(numpy.bool)
ciphertext1 = nufhe.encrypt(thr, rng, private_key, bits1)
ciphertext2 = nufhe.encrypt(thr, rng, private_key, bits2)
In this example we will test the NAND gate, so the reference result would be
reference = ~(bits1 * bits2)
On the server side, where only the public key is known, one can use it to apply a gate:
result = nufhe.empty_ciphertext(thr, public_key.params, ciphertext1.shape)
nufhe.gate_nand(thr, public_key, result, ciphertext1, ciphertext2)
After the processing, the person in possession of the private key can decrypt the result and verify that the gate was applied correctly:
result_bits = nufhe.decrypt(thr, private_key, result)
assert (result_bits == reference).all()