Alpha.
cheon-2024-127runs as a real live-oracle Replay against three 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.lattigo(whenfhe-replay-lattigo-helperis on PATH) — polynomial-domain bisection against real Lattigo BFV/BGV via a Go helper binary that drives the unifiedschemes/bgvcontext.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.
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.
pip install fhe-attack-replayTo 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.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')"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.svgFor 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-127fhe-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| 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 | Replay + RiskCheck + ArtifactCheck | implemented (in-tree Pearson-correlation analyzer over user-supplied traces; live decrypt-timing distinguisher on live-oracle adapters; SEAL/OpenFHE Harvey-butterfly fingerprint verdicts elsewhere) |
reveal-2023-1128 |
Aydin, Karabulut et al. — IACR ePrint 2023/1128 | ArtifactCheck | implemented (in-tree Pearson-correlation analyzer over user-supplied JSON traces) |
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 (in-tree differential analyzer over user-supplied JSON/JSONL fault logs) |
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-127For 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.
For Lattigo BFV/BGV, the Go helper binary owns the keys and ciphertexts.
Perturbation is a per-tower constant addition on Value[0].Coeffs while the
ring is in evaluation form (Lattigo BGV defaults to NTT) — equivalent to
adding a constant polynomial in the time domain. The Python adapter
exchanges line-delimited JSON over stdin/stdout, with offsets sent as
decimal strings when delta exceeds int64. Replay evidence records
serialization_backend=lattigo-bgv,
polynomial_domain="RNS evaluation form (NTT)", the plaintext modulus, and
the per-tower DCRT moduli sizes.
Mitigated configs (params with a recognized noise_flooding label like
lattigo-noise-flooding, openfhe-noise-flooding-decrypt,
eprint-2024-424) drive software flooding on the helper side: every
decrypt samples a fresh Gaussian-distributed integer offset (default
std-dev = delta/4, override via noise_flooding_sigma) and adds it
as a constant polynomial to c0 before native decryption. Cheon's
across-trial boundary variance then drives a real SAFE verdict via
Replay, not via the RiskCheck fallback. Evidence carries
software_flooding_active=true and the active sigma.
# Build the lattigo helper from source
cd vendor/lattigo-helper && go build -o "$HOME/.local/bin/fhe-replay-lattigo-helper" .
# Or download a pre-built binary from the GitHub Releases page and rename it
# to `fhe-replay-lattigo-helper` on PATH.
fhe-replay run --lib lattigo --params examples/bfv-128-vulnerable.json --attacks cheon-2024-127Inspects 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"}
JSONBoth 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.npySet 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.
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-127When 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.
| 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) |
lattigo |
fhe-replay-lattigo-helper (Go binary on PATH; build from vendor/lattigo-helper/ or grab a release asset) |
✅ Replay (BFV/BGV polynomial-domain bisection via per-tower constant addition in evaluation form) |
seal |
tenseal (microsoft/SEAL backend) |
❌ scaffold |
tfhe-rs |
fhe-replay-tfhe-rs-helper (Rust binary, PATH; helper crate scaffold) |
❌ scaffold |
The tfhe-rs helper is still a scaffold (only
hello/shutdownops); its adapter falls back to RiskCheck or NOT_IMPLEMENTED until the Rust-side encrypt/decrypt ops land. The lattigo helper covers BFV/BGV; CKKS Replay is not yet wired (thebgvpackage handles exact-integer schemes only). Lattigo configs with a recognizednoise_floodingmitigation label fall back to the static RiskCheck because the helper does not yet implement software flooding.
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.
- uses: BAder82t/fhe-attack-replay@v0
with:
library: openfhe
params: configs/bfv-128.json
attacks: all
min-coverage: "1.0"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))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)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/*- 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