This repository contains the code for the bachelor thesis "Privacy-Preserving Malicious Email Detection and Filtering with Zero-Knowledge Proofs".
It implements proof-carrying classification for end-to-end encrypted email. A sender can attach a zero-knowledge proof that a body-level spam policy holds without decryption, and the proof is tied to the ciphertext bundle produced by the in-circuit encryption. The repo includes a Python pipeline that trains and evaluates multinomial naive Bayes, linear SVM, and XGBoost models, called GBDT in the thesis, and then quantizes them for in-circuit inference. The export step writes integer model artifacts into Noir constants. On the circuit side, there are Noir implementations for shared feature extraction, inference, and a wrapper that combines classification with ECIES-style encryption. The repo also includes a small UI for generating and verifying proofs. Separate tooling covers benchmarking and analysis.
At the time of writing, the demo UI is hosted at:
- https://ba-demo-ui.6a67.de/ (served with COOP/COEP headers for SharedArrayBuffer/multithreading)
- https://6a67.github.io/ba-demo-ui/ (served without COOP/COEP headers)
- In-Circuit Spam Filter Design:
- Noir feature extraction + inference circuits:
circuits/noir/classifier_infer/ - Python training/evaluation/export:
packages/spam-classifier-pipeline/ - Generated Noir model constants (from the export step):
circuits/noir/classifier_infer/src/consts.nr
- Noir feature extraction + inference circuits:
- Cryptographic Construction:
- Noir ECIES + tag construction:
circuits/noir/ecies/ - Combined circuit (classification + encryption):
circuits/noir/spam-encryption/ - TS counterparts used by the demo UI:
packages/ecies-zk-ts/,packages/poseidon-ts/
- Noir ECIES + tag construction:
- Proof-of-Concept Application:
apps/demo-ui/ - Evaluation / Benchmarking:
- Classifier evaluation is done in the training pipeline:
packages/spam-classifier-pipeline/ - Performance benchmark runner + reporting:
tools/zkp-bench/ - Circuit variant generator (policy/project variants):
circuits/noir/spam-encryption/build.py
- Classifier evaluation is done in the training pipeline:
- Thesis documents:
- Final thesis:
documents/thesis/ - Concept paper:
documents/concept-paper/ - Introduction presentation:
documents/intro-presentation/(live view, navigable with arrow keys) - Intermediate presentation:
documents/inter-presentation/(live view, navigable with arrow keys) - Final presentation:
documents/final-presentation/(live view, navigable with arrow keys)
- Final thesis:
- Datasets live under
data/. attic/contains archived experiments that are not part of the canonical workflow
The thesis and the default circuit variants use the Poseidon-based keystream.
This repository also includes gmimc and hydra keystream variants, which are not discussed in the thesis, but can be selected as optional alternatives.
apps/demo-ui/- Proof-of-concept UI (SvelteKit) + Tauri wrapperpackages/spam-classifier-pipeline/- Python training/eval/calibration/export pipelinepackages/ecies-zk-ts/,packages/poseidon-ts/,packages/gmimc-ts/,packages/hydra-ts/- TS crypto libs for UI (Poseidon is the thesis default; GMiMC/Hydra are optional alternatives)circuits/noir/classifier_infer/- Noir NB/SVM/XGB inference circuit (GBDT in the thesis terminology)circuits/noir/spam-encryption/- Noir classifier+encryption wrapper circuitcircuits/noir/ecies/- Noir ECIES librarycircuits/noir/hydra/- Noir Hydra library; based on TaceoLabs/noir-hydrav0.4.0, with a small local fix so it compiles against the targeted Noir versiontools/zkp-bench/- Benchmark runner and TCR summary toolingdocuments/- Thesis material and the documents that were presentedattic/- Archived/unused experiments and prototypes kept for reference only (not part of canonical reproduction workflow)data/- Datasetsdata/enron/enron_spam_data.csvdata/artificial-email/email_dataset.csv
Convention: command blocks in READMEs assume the current working directory is the directory that contains the README file.
This repo uses:
uvfor Python environments and locked dependenciesbunfor TypeScript packages and the demo UI
Alternatively, Python modules can be executed with python in an activated virtualenv and Node tooling (npm/pnpm/yarn) can be used instead of bun.
Versions used in this repo:
nargo:1.0.0-beta.12noirc:1.0.0-beta.12+9a5b3695b42e391fa27c48e87b9bbb07523d664dbb:0.84.0
Install Noir (pinned):
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bashnoirup -v 1.0.0-beta.12
Install Barretenberg bb (pinned):
curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/refs/heads/next/barretenberg/bbup/install | bashbbup -v 0.84.0
Verify:
nargo --versionbb --version
cd packages/spam-classifier-pipeline
uv sync
uv run -m email_spam_pipeline train --with-meta --models all --config email_spam_pipeline/configs/default.jsonOutputs:
packages/spam-classifier-pipeline/artifacts_combine_enron/: trained artifacts for NB/SVM/XGB (notably*_int_model.jsonand*_calibration.json; plus optionalstack_meta_*.jsonwhen--with-metais enabled).packages/spam-classifier-pipeline/artifacts_combine_enron_no_ratio/: dedicated no-ratio SVM/XGB variants used by no-ratio circuit variants.
To also export XGB decision tree visualizations as PNGs, enable tree plots in the config:
cd packages/spam-classifier-pipeline
uv sync
uv run -m email_spam_pipeline train --with-meta --models all --config email_spam_pipeline/configs/default.json \
--set xgb.enable_tree_plots=true \
--set xgb.tree_plot_format="png" \
--set xgb.tree_plot_limit=20PNG files are written to packages/spam-classifier-pipeline/artifacts_combine_enron/tree_plots/ (and to the sibling no-ratio directory when the no-ratio variant is trained).
Or train models on the artificial dataset with a dedicated output folder:
cd packages/spam-classifier-pipeline
uv sync
uv run -m email_spam_pipeline train --with-meta --models all --config email_spam_pipeline/configs/default.json \
--set dataset.csv="../../data/artificial-email/email_dataset.csv" \
--set svm.out_dir="artifacts_combine_artificial" \
--set nb.out_dir="artifacts_combine_artificial" \
--set xgb.out_dir="artifacts_combine_artificial" \
--set meta.out_dir="artifacts_combine_artificial" \
--set calibrate.out_dir="artifacts_combine_artificial"This writes artifacts to packages/spam-classifier-pipeline/artifacts_combine_artificial/ and the sibling directory packages/spam-classifier-pipeline/artifacts_combine_artificial_no_ratio/.
cd packages/spam-classifier-pipeline
uv run -m email_spam_pipeline export-consts-combined \
--nb artifacts_combine_enron/nb_int_model.json \
--svm artifacts_combine_enron/svm_int_model.json \
--xgb artifacts_combine_enron/xgb_int_model.json \
--out ../../circuits/noir/classifier_infer/src/consts.nrThis consumes the quantized integer model artifacts (*_int_model.json) from step 1 and exports them as an auto-generated Noir source file (circuits/noir/classifier_infer/src/consts.nr) that is compiled into the Noir inference circuits.
Or for artifacts trained on the artificial dataset:
cd packages/spam-classifier-pipeline
uv run -m email_spam_pipeline export-consts-combined \
--nb artifacts_combine_artificial/nb_int_model.json \
--svm artifacts_combine_artificial/svm_int_model.json \
--xgb artifacts_combine_artificial/xgb_int_model.json \
--svm-no-ratio artifacts_combine_artificial_no_ratio/svm_int_model.json \
--xgb-no-ratio artifacts_combine_artificial_no_ratio/xgb_int_model.json \
--out ../../circuits/noir/classifier_infer/src/consts.nrThe policy variants are compiled circuits used for the proof-of-concept application, while the project variants are used for performance benchmarking.
cd circuits/noir/spam-encryption
uv run build.py --artifacts policies
uv run build.py --artifacts projectsGenerated outputs:
- Policy JSONs (for the demo UI):
circuits/noir/spam-encryption/output/policies.jsonandcircuits/noir/spam-encryption/output/policy/*.json(these embed the compiled circuit ABI + bytecode for each variant). - Project variants (for benchmarking):
circuits/noir/spam-encryption/output/projects/*.
To use freshly generated policies in the demo UI, copy them into apps/demo-ui/static/:
cp circuits/noir/spam-encryption/output/policies.json apps/demo-ui/static/policies.json
mkdir -p apps/demo-ui/static/policy
cp circuits/noir/spam-encryption/output/policy/*.json apps/demo-ui/static/policy/For artifacts trained on the artificial dataset:
cd circuits/noir/spam-encryption
uv run build.py --artifacts policies \
--model-artifacts-dir ../../../packages/spam-classifier-pipeline/artifacts_combine_artificial \
--model-artifacts-no-ratio-dir ../../../packages/spam-classifier-pipeline/artifacts_combine_artificial_no_ratio
uv run build.py --artifacts projects \
--model-artifacts-dir ../../../packages/spam-classifier-pipeline/artifacts_combine_artificial \
--model-artifacts-no-ratio-dir ../../../packages/spam-classifier-pipeline/artifacts_combine_artificial_no_ratiocd tools/zkp-bench
uv sync
uv run zkp-bench --help
uv run zkp-tcr-summary --helpcd apps/demo-ui
bun install
bun run devVerify the main commands in a smoke runner in a Docker container.
docker build -t smoke-test .
docker run --rm smoke-test