Unified attack-replay regression harness for FHE libraries (SEAL, OpenFHE, Lattigo, tfhe-rs).
Project description
fhe-attack-replay
Alpha.
cheon-2024-127runs as a real live-oracle Replay against two adapters:
toy-lwe(always available) — bisection-based encryption-noise recovery across 8 trials of 20 rounds each.openfhe(whenopenfhe-pythonis built locally) — polynomial-domain bisection against real OpenFHE BFV/BGV using serialized DCRT ciphertext mutation.Unmitigated configs report
VULNERABLE(exit 2); noise-flooded configs reportSAFE(exit 0). The same module also runs as a RiskCheck on adapters without a live oracle. See DISCLAIMER.md for whatSAFEdoes and does not mean.
Framework for a unified attack-replay regression harness for FHE libraries.
Modules land in three intent levels — Replay (end-to-end exploit),
RiskCheck (static analysis of (library, params) against a known threat
model), and ArtifactCheck (validates user-supplied traces or evidence).
See docs/status-semantics.md.
License: Apache-2.0. See LICENSE and NOTICE.
Why
Hexens awesome-fhe-attacks curates
attacks but does not run them. Every library and downstream user re-implements
attack PoCs ad-hoc to verify a fix. fhe-attack-replay is the framework that —
once the attack modules land — will let you answer the question "is my CKKS
config still vulnerable to Cheon 2024/127?" in seconds.
Install
pip install fhe-attack-replay
To target a specific library, install the matching native dependency:
pip install openfhe # OpenFHE python bindings (Linux x86_64 only via PyPI)
pip install tenseal # SEAL via TenSEAL
# Lattigo / tfhe-rs require helper binaries on PATH.
Building openfhe-python from source (macOS / Windows / arm64)
The PyPI openfhe wheel only ships a Linux x86_64 .so. Build from
source if you want the live OpenFHE Replay path on other platforms:
brew install cmake gmp ntl libomp pybind11 # or your distro's equivalents
git clone https://github.com/openfheorg/openfhe-development
cd openfhe-development
cmake -B build -DCMAKE_INSTALL_PREFIX="$HOME/.local/openfhe" \
-DBUILD_UNITTESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_BENCHMARKS=OFF
cmake --build build -j
cmake --install build
git clone https://github.com/openfheorg/openfhe-python && cd openfhe-python
cmake -B build \
-DOpenFHE_DIR="$HOME/.local/openfhe/lib/OpenFHE" \
-DPython_EXECUTABLE="$(which python3)" \
-DCMAKE_PREFIX_PATH="$(python3 -c 'import pybind11; print(pybind11.get_cmake_dir())')"
cmake --build build -j
cp build/openfhe.cpython-*.so "$(python3 -c 'import site; print(site.getsitepackages()[0])')/"
mkdir -p "$(python3 -c 'import site; print(site.getsitepackages()[0])')/lib"
cp $HOME/.local/openfhe/lib/libOPENFHE*.dylib \
"$(python3 -c 'import site; print(site.getsitepackages()[0])')/lib/"
python3 -c "import openfhe; print('OK')"
Quick start
fhe-replay list all
fhe-replay doctor
fhe-replay run --lib openfhe --params examples/bfv-128.json --attacks all \
--output-json report.json --badge badge.svg
For a dependency-free first run, use the in-tree toy LWE adapter:
fhe-replay run --lib toy-lwe --params examples/toy-lwe-vulnerable.json \
--attacks cheon-2024-127
fhe-replay doctor reports which native adapters are available on the
current machine and prints the dependency note for each missing backend.
Exit codes:
| Code | Meaning |
|---|---|
| 0 | At least one attack ran and every result was SAFE (or SKIPPED if allowed) |
| 2 | At least one attack reported VULNERABLE |
| 3 | Internal error during replay |
| 4 | One or more selected attacks were NOT_IMPLEMENTED (override: --allow-not-implemented) |
| 5 | Every selected attack was SKIPPED and no attack ran (override: --allow-skipped) |
| 64 | Usage error |
NOT_IMPLEMENTED never silently passes by default — green CI requires real
results. See docs/status-semantics.md.
For CI gates that require a minimum implemented-attack ratio:
fhe-replay run --lib openfhe --params examples/bfv-128.json \
--attacks cheon-2024-127 --min-coverage 1.0
Attack modules
| ID | Source | Intent | Status |
|---|---|---|---|
cheon-2024-127 |
Cheon, Hong, Kim — IACR ePrint 2024/127 | Replay + RiskCheck | implemented (Replay against toy-lwe and OpenFHE BFV/BGV; RiskCheck elsewhere) |
eprint-2025-867 |
Side-Channel Analysis in HE — IACR ePrint 2025/867 | RiskCheck | implemented (SEAL/TenSEAL and OpenFHE Harvey-butterfly fingerprint verdicts) |
reveal-2023-1128 |
Aydin, Karabulut et al. — IACR ePrint 2023/1128 | ArtifactCheck | implemented (records caller-supplied trace verdict; in-tree analyzer pending) |
guo-qian-usenix24 |
Guo et al. — USENIX Security 2024 | RiskCheck | implemented (average-case vs worst-case noise-flooding decision rule) |
glitchfhe-usenix25 |
Mankali et al. — USENIX Security 2025 | ArtifactCheck | implemented (records caller-supplied fault-log verdict; in-tree analyzer pending) |
cheon-2024-127 — IND-CPA-D Replay (live oracle)
Generates keys, encrypts 0, perturbs the ciphertext polynomial toward the
rounding boundary, then runs a binary search on the decryption oracle to
recover the encryption-noise boundary. Repeats over N trials and inspects
the variance of the recovered boundary:
trials := 8 bisection runs
rounds := 20 for toy-lwe; max(20, bit_length(delta)) for OpenFHE
delta := q / t (encoding scale)
threshold := max(1, 0.05 * delta)
deterministic := std(boundaries) < threshold
if deterministic: VULNERABLE (oracle leaks; published key recovery applies)
else: SAFE (oracle randomized; noise-recovery primitive does not converge)
Try it:
fhe-replay run --lib toy-lwe --params examples/toy-lwe-vulnerable.json --attacks cheon-2024-127
fhe-replay run --lib toy-lwe --params examples/toy-lwe-mitigated.json --attacks cheon-2024-127
fhe-replay run --lib openfhe --params examples/bfv-128-vulnerable.json --attacks cheon-2024-127
For OpenFHE BFV/BGV, openfhe-python does not expose mutable DCRTPoly
coefficient APIs. The adapter therefore mutates the serialized OpenFHE JSON
ciphertext directly: it adds a constant polynomial to ciphertext component
c0 across all DCRT towers, deserializes the ciphertext, and queries the
native decrypt oracle. Replay evidence records
test=polynomial_domain_bisection, serialization_backend=openfhe-json, the
plaintext modulus, and DCRT tower metadata.
guo-qian-usenix24 — Non-worst-case noise-flooding RiskCheck (CKKS)
Inspects noise_flooding_strategy (or noise_flooding) against the
Guo-Qian USENIX'24 threat model. Average-case-bound flooding constructions
(li-micciancio, eprint-2020-1533, …) are reported VULNERABLE;
worst-case-bound constructions (openfhe-noise-flooding-decrypt,
eprint-2024-424, modulus-switching-2025-1627, …) report SAFE. Configs
without an oracle exposure or without a recognized flooding label are
SKIPPED.
fhe-replay run --lib seal --attacks guo-qian-usenix24 \
--params /dev/stdin <<'JSON'
{"scheme":"CKKS","adversary_model":"ind-cpa-d","noise_flooding_strategy":"li-micciancio"}
JSON
reveal-2023-1128 / glitchfhe-usenix25 — ArtifactCheck
Both modules consume user-supplied evidence files via the CLI
--evidence KEY=PATH flag and record the analyst's declared outcome:
fhe-replay run --lib seal --attacks reveal-2023-1128 \
--params examples/bfv-128.json \
--evidence trace=runs/seal-ntt.npy
Set params['hamming_weight_signature'] = 'recovered' (or 'clean') to
record the result of an external single-trace correlation analysis;
glitchfhe-usenix25 reads params['differential_outcome'] similarly.
Without an outcome declaration the verdict is NOT_IMPLEMENTED —
the in-tree distinguishers are pending.
cheon-2024-127 — IND-CPA-D RiskCheck (static, all libs)
When the adapter cannot drive a live oracle, the same module runs as a
RiskCheck and inspects (scheme, adversary_model, decryption_oracle, noise_flooding) against the threat model:
oracle_access := decryption_oracle == True
OR adversary_model in {ind-cpa-d, threshold, multi-party}
mitigated := noise_flooding in {openfhe-NOISE_FLOODING_DECRYPT,
eprint-2024-424,
eprint-2025-1627,
eprint-2025-1618,
noise-flooding}
if not oracle_access: SKIPPED (threat model n/a)
if mitigated: SAFE
else: VULNERABLE
Try it:
fhe-replay run --lib seal --params examples/bfv-128-vulnerable.json --attacks cheon-2024-127
fhe-replay run --lib seal --params examples/bfv-128-mitigated.json --attacks cheon-2024-127
When a live adapter is available, Replay supersedes this static declaration
check. For example, OpenFHE BFV/BGV only reports SAFE if the native decrypt
oracle is actually randomized enough for boundary recovery not to converge.
Each module cites its source and (where applicable) the reference PoC. Replay implementations are written from public descriptions and ship under Apache-2.0; no upstream PoC source is redistributed.
Supported libraries
| Adapter | Native dependency | Live oracle? |
|---|---|---|
toy-lwe |
none — pure Python, in-tree (CI validation only, not secure) | ✅ Replay |
openfhe |
openfhe-python (PyPI wheel = Linux x86_64 only; build from source on macOS/Windows) |
✅ Replay (BFV/BGV polynomial-domain bisection via serialized DCRT mutation) |
seal |
tenseal (microsoft/SEAL backend) |
❌ scaffold |
lattigo |
fhe-replay-lattigo-helper (Go binary, PATH; helper crate planned for v0.1) |
❌ scaffold |
tfhe-rs |
fhe-replay-tfhe-rs-helper (Rust binary, PATH; helper crate planned for v0.1) |
❌ scaffold |
The Lattigo and tfhe-rs helper binaries are not yet shipped in this repo; the
vendor/lattigo-helper/andvendor/tfhe-rs-helper/projects are tracked for v0.1. Until then, those adapters report their attacks as RiskCheck-only or NOT_IMPLEMENTED.
When building openfhe-python from source for the live OpenFHE replay, pin a release that emits big DCRT moduli as JSON strings during serialization. The adapter's precision guard fails fast on JSON-float moduli >2^53 to avoid silent truncation.
GitHub Action
- uses: BAder82t/fhe-attack-replay@v0
with:
library: openfhe
params: configs/bfv-128.json
attacks: all
min-coverage: "1.0"
Python API
from fhe_attack_replay import run
from fhe_attack_replay.report import to_svg_badge, write_json
report = run(library="openfhe", params={"scheme": "BFV"}, attacks=None)
write_json(report, "report.json")
print(to_svg_badge(report))
Extending
Register a new adapter or attack via register_adapter / register_attack:
from fhe_attack_replay import register_attack
from fhe_attack_replay.attacks.base import Attack, AttackResult, AttackStatus, Citation
class MyAttack(Attack):
id = "my-attack-2026"
title = "..."
citation = Citation(...)
def run(self, adapter, ctx):
return AttackResult(...)
register_attack(MyAttack)
Development
git clone https://github.com/BAder82t/fhe-attack-replay
cd fhe-attack-replay
python -m pip install -e ".[dev]"
ruff check .
pytest -ra --cov=fhe_attack_replay
python -m build
python -m twine check dist/*
Project docs
- DISCLAIMER.md — what
SAFEdoes and does not mean - SECURITY.md — vulnerability reporting policy
- CONTRIBUTING.md — module checklist and module-intent levels
- docs/status-semantics.md — per-attack status, intent levels, exit codes
- docs/pr-gates.md — using replay reports as PR gates
- CHANGELOG.md
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fhe_attack_replay-0.1.2.tar.gz.
File metadata
- Download URL: fhe_attack_replay-0.1.2.tar.gz
- Upload date:
- Size: 88.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
400a265e0d28e46228264ca50452ca2a075f0bf081a5bf3a9f1d20c66f7929f6
|
|
| MD5 |
44414d876ae94c32be3213a0ee4ff7b5
|
|
| BLAKE2b-256 |
4725a97a5f48f6e3c0bac50a220a5e0b55ea5a88e6e4d344bdf9c8421665db52
|
Provenance
The following attestation bundles were made for fhe_attack_replay-0.1.2.tar.gz:
Publisher:
publish.yml on BAder82t/fhe-attack-replay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fhe_attack_replay-0.1.2.tar.gz -
Subject digest:
400a265e0d28e46228264ca50452ca2a075f0bf081a5bf3a9f1d20c66f7929f6 - Sigstore transparency entry: 1393783702
- Sigstore integration time:
-
Permalink:
BAder82t/fhe-attack-replay@7f43ac2fe30550124e6105bb58c707730a925696 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/BAder82t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7f43ac2fe30550124e6105bb58c707730a925696 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fhe_attack_replay-0.1.2-py3-none-any.whl.
File metadata
- Download URL: fhe_attack_replay-0.1.2-py3-none-any.whl
- Upload date:
- Size: 61.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0906bcdcfab3e91338bcaed599a6966f05820535693b57089c844f98bd4c45fe
|
|
| MD5 |
494bf54926acfd76845d2328cb3b21e9
|
|
| BLAKE2b-256 |
d0c22e51d989b53ed906907ec8ed2b7f7eae591fe218bf56b18db62b9250ecc5
|
Provenance
The following attestation bundles were made for fhe_attack_replay-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on BAder82t/fhe-attack-replay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fhe_attack_replay-0.1.2-py3-none-any.whl -
Subject digest:
0906bcdcfab3e91338bcaed599a6966f05820535693b57089c844f98bd4c45fe - Sigstore transparency entry: 1393783762
- Sigstore integration time:
-
Permalink:
BAder82t/fhe-attack-replay@7f43ac2fe30550124e6105bb58c707730a925696 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/BAder82t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7f43ac2fe30550124e6105bb58c707730a925696 -
Trigger Event:
release
-
Statement type: