Context
The repo has been repurposed from the XTrace vector-DB SDK into cuhepy, a
homomorphic-encryption library. Full history is preserved at the tags
archive/xtrace-sdk (old staging, including all of your BFV work) and
archive/xtrace-sdk-main.
Since main and staging were rewritten: push anything local, then re-clone
rather than pulling.
The code now separates primitives from the application built on them:
cuhepy/paillier/ scheme.py lookup.py primitives
cuhepy/bfv/ scheme.py evaluator.py rns.py native.py private.py
cuhepy/hamming/ base.py paillier.py paillier_lookup.py bfv.py
bfv_{security,assurance,guarded,verified,attested,nitro}.py
Imports run application → primitive only. Your BFV modules moved but were not
otherwise changed; git log --follow works, and the commits are in the archive
tags.
The gap
Today the GPU accelerates only Hamming-specialised Paillier. There is no entry
point that encrypts an arbitrary integer on the GPU — encrypt_kernel takes
const uint8_t *all_bits and builds the plaintext from the bit-pair padding
on-device. For most people evaluating an HE library, general encrypt/decrypt
is the feature; encrypted Hamming search is the demo.
|
general encrypt/decrypt over arbitrary ints |
state |
| Paillier CPU |
paillier/scheme.py |
works; no batching wrapper, no scalar multiply |
| Paillier GPU |
— |
❌ encrypt is bits-only; decrypt_only_kernel exists but unexposed |
| Lookup CPU |
paillier/lookup.py |
works |
| Lookup GPU |
encrypt_cts / decrypt_cts |
⚠️ already written in C++, not surfaced in Python |
| BFV CPU |
bfv/scheme.py |
works |
| BFV GPU |
— |
❌ your in-progress work |
Suggested order
- Surface Lookup GPU general enc/dec.
PaillierGPULookupClient::encrypt_cts
(accepts py::int_, range-checks against n) and decrypt_cts already do
the right thing. This is a Python wrapper, not a CUDA change — cheapest win.
- Paillier GPU. Needs an encrypt entry taking limbs rather than bits;
decrypt_only_kernel and modmul_pointwise_kernel are already general
(modmul_pointwise is homomorphic addition).
- BFV GPU — yours.
Design note
The Hamming specialisation can stay; it is a real contribution. But it should
sit on top of the general kernels rather than beside them:
- Evaluate needs nothing —
modmul_pointwise_kernel is already general.
- Encrypt should move the bit-pair interleave host-side. The general path
uploads the packed plaintext (2·embed_len bits = 128 B/vector) instead of raw
bits (512 B/vector), so it is ~4× less PCIe traffic, not more.
- Decode is the only place the specialisation clearly earns its keep: the
fused decode-and-popcount returns ~4 B/vector instead of ~128 B. Worth
measuring fused vs unfused before assuming it matters — for 10k vectors the
difference is a few hundred µs of transfer plus ~10 ms of host popcount,
which is small next to the modexp work. If it is negligible, the
Hamming-specific CUDA surface can go to zero.
Smaller items
encrypt_cts converts each py::int_ via a decimal string
(py::str(obj) → mpz(s, 10)). Base-10 parsing of a 1024-bit integer;
mpz_import from int.to_bytes() would be much faster.
paillier/scheme.py has no plaintext-scalar multiply (E(m)^k = E(k·m)).
multiply correctly raises for ct×ct, but the scalar case is the most-used
Paillier operation in federated learning and is a few lines.
alpha_len default is now 280 (was 50), matching the compiled
ALPHA_LEN, with a 256-bit floor enforced at key generation. At 50 bits the
secret exponent was recoverable from the public key in ~3.5 min on a laptop —
see docs/research/paillier-security.md (PL-01) and
attacks/pl01_alpha_recovery.py.
Before any release
- CI/CD was removed and needs redesigning. Two findings from its last run:
auditwheel cannot repair the BFV extension to manylinux_2_31 ("too-recent
versioned symbols") — needs an older toolchain or a newer manylinux target;
and msgpack had been dropped from dependencies while hamming/bfv_security
imports it (fixed).
- The GPU and C++ backends have not been built since the restructure. Paths
in build_gpu_binaries.sh, both Makefiles, hatch_build.py and the
Dockerfile.dockerignore were corrected by inspection, not by running them.
A CUDA build and make -C src/cuhepy/bfv/_cpu_ext are the real test.
- B023/E731 in
benchmarks/bfv_nitro.py are false positives (every lambda is
invoked immediately inside time_call); benchmarks/ and experiments/ are
outside ruff's default scope. Noting so nobody re-litigates it.
Context
The repo has been repurposed from the XTrace vector-DB SDK into
cuhepy, ahomomorphic-encryption library. Full history is preserved at the tags
archive/xtrace-sdk(oldstaging, including all of your BFV work) andarchive/xtrace-sdk-main.Since
mainandstagingwere rewritten: push anything local, then re-clonerather than pulling.
The code now separates primitives from the application built on them:
Imports run application → primitive only. Your BFV modules moved but were not
otherwise changed;
git log --followworks, and the commits are in the archivetags.
The gap
Today the GPU accelerates only Hamming-specialised Paillier. There is no entry
point that encrypts an arbitrary integer on the GPU —
encrypt_kerneltakesconst uint8_t *all_bitsand builds the plaintext from the bit-pair paddingon-device. For most people evaluating an HE library, general
encrypt/decryptis the feature; encrypted Hamming search is the demo.
paillier/scheme.pyencryptis bits-only;decrypt_only_kernelexists but unexposedpaillier/lookup.pyencrypt_cts/decrypt_ctsbfv/scheme.pySuggested order
PaillierGPULookupClient::encrypt_cts(accepts
py::int_, range-checks againstn) anddecrypt_ctsalready dothe right thing. This is a Python wrapper, not a CUDA change — cheapest win.
decrypt_only_kernelandmodmul_pointwise_kernelare already general(
modmul_pointwiseis homomorphic addition).Design note
The Hamming specialisation can stay; it is a real contribution. But it should
sit on top of the general kernels rather than beside them:
modmul_pointwise_kernelis already general.uploads the packed plaintext (2·embed_len bits = 128 B/vector) instead of raw
bits (512 B/vector), so it is ~4× less PCIe traffic, not more.
fused decode-and-popcount returns ~4 B/vector instead of ~128 B. Worth
measuring fused vs unfused before assuming it matters — for 10k vectors the
difference is a few hundred µs of transfer plus ~10 ms of host popcount,
which is small next to the modexp work. If it is negligible, the
Hamming-specific CUDA surface can go to zero.
Smaller items
encrypt_ctsconverts eachpy::int_via a decimal string(
py::str(obj)→mpz(s, 10)). Base-10 parsing of a 1024-bit integer;mpz_importfromint.to_bytes()would be much faster.paillier/scheme.pyhas no plaintext-scalar multiply (E(m)^k = E(k·m)).multiplycorrectly raises for ct×ct, but the scalar case is the most-usedPaillier operation in federated learning and is a few lines.
alpha_lendefault is now 280 (was 50), matching the compiledALPHA_LEN, with a 256-bit floor enforced at key generation. At 50 bits thesecret exponent was recoverable from the public key in ~3.5 min on a laptop —
see
docs/research/paillier-security.md(PL-01) andattacks/pl01_alpha_recovery.py.Before any release
auditwheelcannot repair the BFV extension tomanylinux_2_31("too-recentversioned symbols") — needs an older toolchain or a newer manylinux target;
and
msgpackhad been dropped from dependencies whilehamming/bfv_securityimports it (fixed).
in
build_gpu_binaries.sh, bothMakefiles,hatch_build.pyand theDockerfile.dockerignorewere corrected by inspection, not by running them.A CUDA build and
make -C src/cuhepy/bfv/_cpu_extare the real test.benchmarks/bfv_nitro.pyare false positives (every lambda isinvoked immediately inside
time_call);benchmarks/andexperiments/areoutside ruff's default scope. Noting so nobody re-litigates it.