secure-fl 2026.3.17.dev1__tar.gz → 2026.8.25.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/.gitignore +20 -5
  2. secure_fl-2026.8.25.dev1/PKG-INFO +336 -0
  3. secure_fl-2026.8.25.dev1/README.md +283 -0
  4. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/pyproject.toml +6 -11
  5. secure_fl-2026.8.25.dev1/src/secure_fl/.build_number +1 -0
  6. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/__init__.py +0 -1
  7. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/__init__.py +8 -0
  8. secure_fl-2026.8.25.dev1/src/secure_fl/benchmarks/attacks.py +445 -0
  9. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/execution.py +180 -17
  10. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/pipeline.py +3 -1
  11. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/reporting.py +17 -1
  12. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/handlers.py +22 -50
  13. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/main.py +0 -5
  14. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/config.py +27 -23
  15. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/types.py +17 -14
  16. secure_fl-2026.8.25.dev1/src/secure_fl/data/__init__.py +23 -0
  17. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/data/benchmark_data.py +57 -21
  18. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/aggregation.py +61 -46
  19. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/client.py +122 -54
  20. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/client_runtime.py +11 -7
  21. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/client_utils.py +21 -6
  22. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/server.py +40 -38
  23. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/server_runtime.py +0 -1
  24. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/stability_monitor.py +80 -40
  25. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/__init__.py +1 -1
  26. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/factory.py +20 -9
  27. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/mnist_cnn.py +1 -1
  28. secure_fl-2026.8.25.dev1/src/secure_fl/proofs/client/delta_bound.circom +56 -0
  29. secure_fl-2026.8.25.dev1/src/secure_fl/proofs/server/aggregation.circom +55 -0
  30. secure_fl-2026.8.25.dev1/src/secure_fl/proofs/server/pot13_final.ptau +0 -0
  31. secure_fl-2026.8.25.dev1/src/secure_fl/py.typed +0 -0
  32. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/utils/helpers.py +32 -20
  33. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/utils/logging_config.py +25 -16
  34. secure_fl-2026.8.25.dev1/src/secure_fl/zkp/client_proof_manager.py +501 -0
  35. secure_fl-2026.8.25.dev1/src/secure_fl/zkp/client_setup.py +205 -0
  36. secure_fl-2026.8.25.dev1/src/secure_fl/zkp/client_snark_io.py +148 -0
  37. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/proof_schema.py +1 -1
  38. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/quantization.py +22 -3
  39. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/server_proof_manager.py +232 -64
  40. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/server_snark_io.py +39 -12
  41. secure_fl-2026.3.17.dev1/PKG-INFO +0 -360
  42. secure_fl-2026.3.17.dev1/README.md +0 -304
  43. secure_fl-2026.3.17.dev1/src/secure_fl/.build_number +0 -1
  44. secure_fl-2026.3.17.dev1/src/secure_fl/data/__init__.py +0 -85
  45. secure_fl-2026.3.17.dev1/src/secure_fl/proofs/client_circuits/sgd_full_trace.cairo +0 -502
  46. secure_fl-2026.3.17.dev1/src/secure_fl/zkp/client_proof_manager.py +0 -293
  47. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/LICENSE +0 -0
  48. {secure_fl-2026.3.17.dev1/docs/research paper → secure_fl-2026.8.25.dev1/docs/paper}/README.md +0 -0
  49. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/experiments/README.md +0 -0
  50. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/__main__.py +0 -0
  51. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/defaults.py +0 -0
  52. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/__init__.py +0 -0
  53. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/__main__.py +0 -0
  54. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/setup.py +0 -0
  55. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/zkp_check.py +0 -0
  56. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/__init__.py +0 -0
  57. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/_version.py +0 -0
  58. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/exceptions.py +0 -0
  59. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/__init__.py +0 -0
  60. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/simple.py +0 -0
  61. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/__init__.py +0 -0
  62. {secure_fl-2026.3.17.dev1/src/secure_fl/proofs/client_circuits → secure_fl-2026.8.25.dev1/src/secure_fl/proofs/client}/__init__.py +0 -0
  63. /secure_fl-2026.3.17.dev1/src/secure_fl/py.typed → /secure_fl-2026.8.25.dev1/src/secure_fl/proofs/client_circuits/__init__.py +0 -0
  64. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/client_circuits/delta_bound.py +0 -0
  65. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/server/pot12_final.ptau +0 -0
  66. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/server/test.txt +0 -0
  67. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/utils/__init__.py +0 -0
  68. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/__init__.py +0 -0
  69. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/base.py +0 -0
  70. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/proof_manager.py +0 -0
  71. {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/server_setup.py +0 -0
@@ -103,21 +103,25 @@ dmypy.json
103
103
  # Runtime logs and results
104
104
  logs/
105
105
  results/*
106
- !results/gpu_convergence_mnist/
107
- !results/gpu_convergence_cifar10/
108
- !results/gpu_convergence_synthetic_small/
109
- !results/gpu_convergence_medmnist_resnet18/
106
+ !results/full_benchmark/
107
+ !results/full_benchmark_50/
108
+ !results/full_benchmark_v2/
110
109
  results_bak/
111
110
  *.log
112
111
 
112
+ # Stray local artifacts (empty dirs from mock leaks / tool runs, not code-created)
113
+ /MagicMock/
114
+ /.benchmarks/
115
+
113
116
  # Data directories
114
117
  datasets/
115
118
 
116
119
  # ZKP artifacts
117
120
  *.r1cs
118
121
  *.wtns
119
- *.circom
122
+ # *.circom
120
123
  output/
124
+ graphify-out/
121
125
  witness/
122
126
  circuit.wasm
123
127
  circuit_js/
@@ -170,10 +174,17 @@ Thumbs.db
170
174
  *.synctex.gz
171
175
  *.out
172
176
  *.toc
177
+ *.lof
178
+ *.lot
173
179
  *.log
174
180
  *.pdf
175
181
  *.dvi
176
182
  *.ps
183
+ # Allow committed figure PDFs in the report
184
+ !docs/final-defense/report/images/figures/*.pdf
185
+ !docs/final-defense/report/diagrams/*.pdf
186
+ !docs/research\ paper/figures/*.pdf
187
+ !docs/paper/figures/*.pdf
177
188
 
178
189
  # ===========================
179
190
  # Development Tools
@@ -200,3 +211,7 @@ yarn-error.log*
200
211
  # Environment variables
201
212
  .env*
202
213
  !.env.example
214
+
215
+
216
+ experiments/data/
217
+ *.pptx
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.5
2
+ Name: secure-fl
3
+ Version: 2026.8.25.dev1
4
+ Summary: Dual-Verifiable Framework for Federated Learning using Zero-Knowledge Proofs
5
+ Project-URL: Homepage, https://github.com/krishantt/secure-fl
6
+ Project-URL: Bug Reports, https://github.com/krishantt/secure-fl/issues
7
+ Project-URL: Source, https://github.com/krishantt/secure-fl
8
+ Project-URL: Documentation, https://github.com/krishantt/secure-fl/blob/main/README.md
9
+ Author-email: Krishant Timilsina <krishtimil@gmail.com>, Bindu Paudel <binduupaudel565@gmail.com>
10
+ Maintainer-email: Krishant Timilsina <krishtimil@gmail.com>, Bindu Paudel <binduupaudel565@gmail.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: cryptography,federated-learning,machine-learning,privacy,zero-knowledge-proofs,zk-snarks,zk-starks
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: click>=8.0.0
25
+ Requires-Dist: flwr>=1.5.0
26
+ Requires-Dist: numpy>=1.24.0
27
+ Requires-Dist: pysnark
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: textual>=6.2.1
31
+ Requires-Dist: torch>=2.0.0
32
+ Requires-Dist: torchvision>=0.15.0
33
+ Provides-Extra: benchmark
34
+ Requires-Dist: memory-profiler>=0.61.0; extra == 'benchmark'
35
+ Requires-Dist: pytest-benchmark>=5.2.3; extra == 'benchmark'
36
+ Provides-Extra: dev
37
+ Requires-Dist: mypy>=1.19.0; extra == 'dev'
38
+ Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
39
+ Requires-Dist: psutil>=5.9.0; extra == 'dev'
40
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
41
+ Requires-Dist: pytest-xdist>=3.3.0; extra == 'dev'
42
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
43
+ Requires-Dist: ruff>=0.14.8; extra == 'dev'
44
+ Requires-Dist: types-psutil; extra == 'dev'
45
+ Requires-Dist: types-pyyaml; extra == 'dev'
46
+ Provides-Extra: medical
47
+ Requires-Dist: medmnist>=2.2.0; extra == 'medical'
48
+ Provides-Extra: viz
49
+ Requires-Dist: matplotlib>=3.7.0; extra == 'viz'
50
+ Requires-Dist: plotly>=5.0.0; extra == 'viz'
51
+ Requires-Dist: seaborn>=0.12.0; extra == 'viz'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # Secure FL: Zero-Knowledge Federated Learning
55
+
56
+ A dual-verifiable framework for federated learning using zero-knowledge proofs to ensure training integrity and aggregation correctness.
57
+
58
+ ## Core Features
59
+
60
+ - **Dual ZKP Verification**: Client-side zk-STARKs (PySNARK) + Server-side zk-SNARKs (Circom/SnarkJS)
61
+ - **FedJSCM Aggregation**: Momentum-based federated optimization with auto-reinit on architecture changes
62
+ - **Dynamic Proof Rigor**: `StabilityMonitor` adapts proof complexity (`low`/`medium`/`high`/`adaptive`) based on training stability
63
+ - **Deterministic Hashing**: SHA-256 over parameter bytes — safe across Python/numpy versions
64
+ - **Attack Detection**: Norm-bound enforcement, hash verification, and ZKP circuit checks block poisoned updates
65
+
66
+ ## Architecture
67
+
68
+ ```
69
+ Client: Local Training → zk-STARK proof (learnable params) → Send full state_dict + proof
70
+ Server: Receive updates → verify proofs → FedJSCM aggregation → zk-SNARK proof → Distribute model
71
+ ```
72
+
73
+ **Dual verification:**
74
+ 1. **Clients** generate zk-STARK proofs of correct local training (delta norm bound, hash commitment)
75
+ 2. **Server** generates zk-SNARK proofs of correct FedJSCM aggregation
76
+
77
+ ## Quick Start
78
+
79
+ ### Installation
80
+
81
+ ```bash
82
+ # From source with uv (recommended)
83
+ git clone https://github.com/krishantt/secure-fl
84
+ cd secure-fl
85
+ uv sync --all-extras
86
+ ```
87
+
88
+ ### ZKP Prerequisites
89
+
90
+ ```bash
91
+ # Automated setup (installs Rust, Circom, SnarkJS)
92
+ make setup-zkp
93
+
94
+ # Verify
95
+ uv run secure-fl check-zkp
96
+ ```
97
+
98
+ ### Run the attack defense demo
99
+
100
+ ```bash
101
+ # Synthetic data — fastest, no download needed
102
+ uv run python demo/simulate_attack.py --dataset synthetic_small --num-rounds 6
103
+
104
+ # MNIST
105
+ uv run python demo/simulate_attack.py --dataset mnist
106
+
107
+ # CIFAR-10 with more malicious clients
108
+ uv run python demo/simulate_attack.py --dataset cifar10 --malicious 2 --proof-rigor low
109
+
110
+ # MedMNIST
111
+ uv run python demo/simulate_attack.py --dataset medmnist --malicious 2 --proof-rigor low
112
+ ```
113
+
114
+ The TUI shows real-time ZKP verification, attack detection, and accuracy comparison (with ZKP vs projected without). Attackers appear in ~65% of rounds; the rest are all-honest.
115
+
116
+ ### Start a federated learning session
117
+
118
+ ```bash
119
+ # Server
120
+ uv run secure-fl-server --config experiments/config.yaml
121
+
122
+ # Client (in separate terminals)
123
+ uv run secure-fl-client --server localhost:8080 --dataset mnist --client-id client_1
124
+ uv run secure-fl-client --server localhost:8080 --dataset mnist --client-id client_2
125
+
126
+ # Or use Docker
127
+ docker compose up -d
128
+ ```
129
+
130
+ ## Python API
131
+
132
+ ```python
133
+ from secure_fl.federation.client_runtime import create_client
134
+ from secure_fl.models.factory import model_fn
135
+
136
+ # Create a secure FL client
137
+ factory = model_fn("mnist") # returns MNISTCNN factory
138
+ client = create_client(
139
+ client_id="client_1",
140
+ model_fn=factory,
141
+ train_data=train_dataset,
142
+ enable_zkp=True,
143
+ proof_rigor="low", # "low" | "medium" | "high"
144
+ local_epochs=1,
145
+ learning_rate=0.01,
146
+ )
147
+
148
+ # client.fit(parameters, config) → (updated_params, num_examples, metrics)
149
+ # metrics["zkp_proof"] contains the JSON proof string when enable_zkp=True
150
+ ```
151
+
152
+ ```python
153
+ from secure_fl.federation.aggregation import FedJSCMAggregator
154
+ from secure_fl.zkp.proof_manager import ServerProofManager
155
+
156
+ aggregator = FedJSCMAggregator(momentum=0.9, learning_rate=1.0)
157
+ server_pm = ServerProofManager()
158
+
159
+ # Verify a client proof
160
+ ok = server_pm.verify_client_proof(proof_json, updated_params, global_params)
161
+
162
+ # Aggregate verified updates
163
+ global_params = aggregator.aggregate(
164
+ client_updates=verified_updates,
165
+ client_weights=normalized_weights,
166
+ server_round=rnd,
167
+ global_params=global_params,
168
+ )
169
+ ```
170
+
171
+ ## Configuration
172
+
173
+ ```yaml
174
+ # experiments/config.yaml
175
+ server:
176
+ host: "localhost"
177
+ port: 8080
178
+ num_rounds: 10
179
+
180
+ strategy:
181
+ min_fit_clients: 2
182
+ fraction_fit: 1.0
183
+ momentum: 0.9
184
+
185
+ zkp:
186
+ enable_zkp: true
187
+ proof_rigor: "high" # "low" | "medium" | "high" | "adaptive"
188
+ quantize_weights: true
189
+ quantization_bits: 8
190
+ ```
191
+
192
+ ## Models and Datasets
193
+
194
+ | Dataset | Model | Notes |
195
+ |---|---|---|
196
+ | `mnist` | `MNISTCNN` | 2-layer CNN, fast |
197
+ | `cifar10` | `ResNet18Model` | Full ResNet18 |
198
+ | `medmnist` | `ResNet18Model` | Medical imaging |
199
+ | `synthetic_small` | `SimpleModel` | MLP, no download needed |
200
+
201
+ ```python
202
+ from secure_fl.models.factory import model_fn
203
+ factory = model_fn("cifar10") # returns callable → ResNet18Model instance
204
+ model = factory()
205
+ ```
206
+
207
+ ```python
208
+ from secure_fl.data.benchmark_data import load_dataset, partition_dataset, subsample_dataset
209
+
210
+ train, test = load_dataset("mnist", seed=42)
211
+ train = subsample_dataset(train, max_samples=2000, seed=42)
212
+ client_subsets = partition_dataset(train, n=6, iid=False, seed=42) # Dirichlet non-IID
213
+ ```
214
+
215
+ ## Technical Details
216
+
217
+ ### FedJSCM Aggregation
218
+
219
+ ```
220
+ m^{t+1} = γ·m^t + (1−γ)·Δ
221
+ w^{t+1} = w^t + η·m^{t+1}
222
+ ```
223
+ where `Δ = weighted_avg(client_updates) − global_params`. Momentum is reinitialized automatically if the model architecture changes between rounds. Integer-dtype buffers (e.g. BatchNorm `num_batches_tracked`) are preserved through aggregation.
224
+
225
+ ### ZKP Proof Schema
226
+
227
+ Each client proof is a JSON object containing:
228
+ - `initial_hash`, `updated_hash`, `delta_hash` — SHA-256 commitments over learnable parameters
229
+ - `delta_norm_l2`, `max_delta_norm_l2` — norm bound enforcement
230
+ - `pysnark` — PySNARK circuit result (`delta_bound_proof`)
231
+
232
+ Server verification order: proof present → valid JSON → hash matches → delta hash matches → norm within bound → circuit ok.
233
+
234
+ ### Parameter Representations
235
+
236
+ Two lists exist and must not be mixed:
237
+
238
+ | | Source | Contents |
239
+ |---|---|---|
240
+ | Full state dict | `model.state_dict().values()` | weights + biases + BN buffers |
241
+ | Learnable only | `model.parameters()` | weights + biases only |
242
+
243
+ `fit()` returns full state dict. ZKP proofs cover learnable-only params. Mixing them with `zip` causes shape-mismatch errors on BN models (ResNet18, MNISTCNN).
244
+
245
+ ### Dynamic Proof Rigor
246
+
247
+ `StabilityMonitor` tracks gradient variance and convergence across rounds and adapts `ProofRigor`:
248
+ - High variance / early rounds → `high` (more proof coverage)
249
+ - Stable training → `low` (faster proofs)
250
+ - `adaptive` mode enables automatic adjustment each round
251
+
252
+ ## Experiments & Benchmarks
253
+
254
+ ```bash
255
+ # Fast smoke run
256
+ uv run python experiments/canonical_benchmark.py --quick
257
+
258
+ # Full reproducible benchmark with repeats
259
+ uv run python experiments/canonical_benchmark.py \
260
+ --datasets mnist synthetic_small \
261
+ --num-repeats 5 \
262
+ --seed 42 \
263
+ --require-real-proofs
264
+
265
+ # Interactive benchmark TUI (supports reattach)
266
+ uv run python experiments/benchmark_tui.py
267
+ ```
268
+
269
+ Outputs: `benchmark_results.json` (per-seed runs) and `tradeoff_report.json` (aggregated trade-off summary).
270
+
271
+ ## Repository Structure
272
+
273
+ ```
274
+ secure-fl/
275
+ ├── src/secure_fl/
276
+ │ ├── core/ # Types, config (thread-safe singleton), exceptions
277
+ │ ├── federation/ # SecureFlowerClient, SecureFlowerServer, FedJSCMAggregator,
278
+ │ │ # StabilityMonitor, client_utils, helpers
279
+ │ ├── zkp/ # ClientProofManager, ServerProofManager, quantization
280
+ │ ├── models/ # SimpleModel, MNISTCNN, ResNet18Model, factory
281
+ │ ├── data/ # benchmark_data: load/partition/subsample datasets
282
+ │ ├── cli/ # Click CLI: secure-fl, secure-fl-server, secure-fl-client
283
+ │ ├── proofs/
284
+ │ │ ├── client_circuits/ # PySNARK delta_bound_proof circuit
285
+ │ │ └── server/ # Circom aggregation SNARK circuits
286
+ │ └── utils/ # helpers (compute_hash, compute_parameter_norm), logging
287
+ ├── demo/
288
+ │ └── simulate_attack.py # Textual TUI: real ZKP attack detection demo
289
+ ├── experiments/
290
+ │ ├── canonical_benchmark.py
291
+ │ ├── benchmark_tui.py
292
+ │ └── config.yaml
293
+ └── tests/
294
+ ├── unit/ # No external dependencies (fast)
295
+ └── integration/ # Requires Circom + SnarkJS
296
+ ```
297
+
298
+ ## Development
299
+
300
+ ```bash
301
+ make dev # Full setup (uv sync + ZKP tools)
302
+ make lint # Ruff lint
303
+ make format # Ruff format ← run before every commit
304
+ make type-check # mypy (strict)
305
+ make test # Full suite
306
+ make test-quick # Fast, --exitfirst
307
+ make test-cov # With coverage (threshold: 34%)
308
+ ```
309
+
310
+ ## Known Limitations
311
+
312
+ - `_submit_to_blockchain()` raises `NotImplementedError` — not yet implemented
313
+ - Server ZKP (Circom/SnarkJS) requires `make setup-zkp`; subprocess calls time out after 300 s
314
+ - Coverage threshold is 34% — being raised incrementally
315
+
316
+ ## License
317
+
318
+ MIT License — see [LICENSE](LICENSE) for details.
319
+
320
+ ## Citation
321
+
322
+ ```bibtex
323
+ @misc{timilsina2026securefl,
324
+ title={Secure-FL: Zero-Knowledge Proofs for Dual-Verifiable Federated Learning},
325
+ author={Timilsina, Krishant and Paudel, Bindu and Timilsina, Arun Kumar},
326
+ year={2026},
327
+ url={https://github.com/krishantt/secure-fl},
328
+ note={Preprint}
329
+ }
330
+ ```
331
+
332
+ ## Acknowledgments
333
+
334
+ - [Flower](https://flower.dev) — federated learning infrastructure
335
+ - [Circom](https://github.com/iden3/circom) / [SnarkJS](https://github.com/iden3/snarkjs) — zk-SNARK toolchain
336
+ - [PySNARK](https://github.com/meilof/pysnark) — Python zk-STARK/SNARK library
@@ -0,0 +1,283 @@
1
+ # Secure FL: Zero-Knowledge Federated Learning
2
+
3
+ A dual-verifiable framework for federated learning using zero-knowledge proofs to ensure training integrity and aggregation correctness.
4
+
5
+ ## Core Features
6
+
7
+ - **Dual ZKP Verification**: Client-side zk-STARKs (PySNARK) + Server-side zk-SNARKs (Circom/SnarkJS)
8
+ - **FedJSCM Aggregation**: Momentum-based federated optimization with auto-reinit on architecture changes
9
+ - **Dynamic Proof Rigor**: `StabilityMonitor` adapts proof complexity (`low`/`medium`/`high`/`adaptive`) based on training stability
10
+ - **Deterministic Hashing**: SHA-256 over parameter bytes — safe across Python/numpy versions
11
+ - **Attack Detection**: Norm-bound enforcement, hash verification, and ZKP circuit checks block poisoned updates
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ Client: Local Training → zk-STARK proof (learnable params) → Send full state_dict + proof
17
+ Server: Receive updates → verify proofs → FedJSCM aggregation → zk-SNARK proof → Distribute model
18
+ ```
19
+
20
+ **Dual verification:**
21
+ 1. **Clients** generate zk-STARK proofs of correct local training (delta norm bound, hash commitment)
22
+ 2. **Server** generates zk-SNARK proofs of correct FedJSCM aggregation
23
+
24
+ ## Quick Start
25
+
26
+ ### Installation
27
+
28
+ ```bash
29
+ # From source with uv (recommended)
30
+ git clone https://github.com/krishantt/secure-fl
31
+ cd secure-fl
32
+ uv sync --all-extras
33
+ ```
34
+
35
+ ### ZKP Prerequisites
36
+
37
+ ```bash
38
+ # Automated setup (installs Rust, Circom, SnarkJS)
39
+ make setup-zkp
40
+
41
+ # Verify
42
+ uv run secure-fl check-zkp
43
+ ```
44
+
45
+ ### Run the attack defense demo
46
+
47
+ ```bash
48
+ # Synthetic data — fastest, no download needed
49
+ uv run python demo/simulate_attack.py --dataset synthetic_small --num-rounds 6
50
+
51
+ # MNIST
52
+ uv run python demo/simulate_attack.py --dataset mnist
53
+
54
+ # CIFAR-10 with more malicious clients
55
+ uv run python demo/simulate_attack.py --dataset cifar10 --malicious 2 --proof-rigor low
56
+
57
+ # MedMNIST
58
+ uv run python demo/simulate_attack.py --dataset medmnist --malicious 2 --proof-rigor low
59
+ ```
60
+
61
+ The TUI shows real-time ZKP verification, attack detection, and accuracy comparison (with ZKP vs projected without). Attackers appear in ~65% of rounds; the rest are all-honest.
62
+
63
+ ### Start a federated learning session
64
+
65
+ ```bash
66
+ # Server
67
+ uv run secure-fl-server --config experiments/config.yaml
68
+
69
+ # Client (in separate terminals)
70
+ uv run secure-fl-client --server localhost:8080 --dataset mnist --client-id client_1
71
+ uv run secure-fl-client --server localhost:8080 --dataset mnist --client-id client_2
72
+
73
+ # Or use Docker
74
+ docker compose up -d
75
+ ```
76
+
77
+ ## Python API
78
+
79
+ ```python
80
+ from secure_fl.federation.client_runtime import create_client
81
+ from secure_fl.models.factory import model_fn
82
+
83
+ # Create a secure FL client
84
+ factory = model_fn("mnist") # returns MNISTCNN factory
85
+ client = create_client(
86
+ client_id="client_1",
87
+ model_fn=factory,
88
+ train_data=train_dataset,
89
+ enable_zkp=True,
90
+ proof_rigor="low", # "low" | "medium" | "high"
91
+ local_epochs=1,
92
+ learning_rate=0.01,
93
+ )
94
+
95
+ # client.fit(parameters, config) → (updated_params, num_examples, metrics)
96
+ # metrics["zkp_proof"] contains the JSON proof string when enable_zkp=True
97
+ ```
98
+
99
+ ```python
100
+ from secure_fl.federation.aggregation import FedJSCMAggregator
101
+ from secure_fl.zkp.proof_manager import ServerProofManager
102
+
103
+ aggregator = FedJSCMAggregator(momentum=0.9, learning_rate=1.0)
104
+ server_pm = ServerProofManager()
105
+
106
+ # Verify a client proof
107
+ ok = server_pm.verify_client_proof(proof_json, updated_params, global_params)
108
+
109
+ # Aggregate verified updates
110
+ global_params = aggregator.aggregate(
111
+ client_updates=verified_updates,
112
+ client_weights=normalized_weights,
113
+ server_round=rnd,
114
+ global_params=global_params,
115
+ )
116
+ ```
117
+
118
+ ## Configuration
119
+
120
+ ```yaml
121
+ # experiments/config.yaml
122
+ server:
123
+ host: "localhost"
124
+ port: 8080
125
+ num_rounds: 10
126
+
127
+ strategy:
128
+ min_fit_clients: 2
129
+ fraction_fit: 1.0
130
+ momentum: 0.9
131
+
132
+ zkp:
133
+ enable_zkp: true
134
+ proof_rigor: "high" # "low" | "medium" | "high" | "adaptive"
135
+ quantize_weights: true
136
+ quantization_bits: 8
137
+ ```
138
+
139
+ ## Models and Datasets
140
+
141
+ | Dataset | Model | Notes |
142
+ |---|---|---|
143
+ | `mnist` | `MNISTCNN` | 2-layer CNN, fast |
144
+ | `cifar10` | `ResNet18Model` | Full ResNet18 |
145
+ | `medmnist` | `ResNet18Model` | Medical imaging |
146
+ | `synthetic_small` | `SimpleModel` | MLP, no download needed |
147
+
148
+ ```python
149
+ from secure_fl.models.factory import model_fn
150
+ factory = model_fn("cifar10") # returns callable → ResNet18Model instance
151
+ model = factory()
152
+ ```
153
+
154
+ ```python
155
+ from secure_fl.data.benchmark_data import load_dataset, partition_dataset, subsample_dataset
156
+
157
+ train, test = load_dataset("mnist", seed=42)
158
+ train = subsample_dataset(train, max_samples=2000, seed=42)
159
+ client_subsets = partition_dataset(train, n=6, iid=False, seed=42) # Dirichlet non-IID
160
+ ```
161
+
162
+ ## Technical Details
163
+
164
+ ### FedJSCM Aggregation
165
+
166
+ ```
167
+ m^{t+1} = γ·m^t + (1−γ)·Δ
168
+ w^{t+1} = w^t + η·m^{t+1}
169
+ ```
170
+ where `Δ = weighted_avg(client_updates) − global_params`. Momentum is reinitialized automatically if the model architecture changes between rounds. Integer-dtype buffers (e.g. BatchNorm `num_batches_tracked`) are preserved through aggregation.
171
+
172
+ ### ZKP Proof Schema
173
+
174
+ Each client proof is a JSON object containing:
175
+ - `initial_hash`, `updated_hash`, `delta_hash` — SHA-256 commitments over learnable parameters
176
+ - `delta_norm_l2`, `max_delta_norm_l2` — norm bound enforcement
177
+ - `pysnark` — PySNARK circuit result (`delta_bound_proof`)
178
+
179
+ Server verification order: proof present → valid JSON → hash matches → delta hash matches → norm within bound → circuit ok.
180
+
181
+ ### Parameter Representations
182
+
183
+ Two lists exist and must not be mixed:
184
+
185
+ | | Source | Contents |
186
+ |---|---|---|
187
+ | Full state dict | `model.state_dict().values()` | weights + biases + BN buffers |
188
+ | Learnable only | `model.parameters()` | weights + biases only |
189
+
190
+ `fit()` returns full state dict. ZKP proofs cover learnable-only params. Mixing them with `zip` causes shape-mismatch errors on BN models (ResNet18, MNISTCNN).
191
+
192
+ ### Dynamic Proof Rigor
193
+
194
+ `StabilityMonitor` tracks gradient variance and convergence across rounds and adapts `ProofRigor`:
195
+ - High variance / early rounds → `high` (more proof coverage)
196
+ - Stable training → `low` (faster proofs)
197
+ - `adaptive` mode enables automatic adjustment each round
198
+
199
+ ## Experiments & Benchmarks
200
+
201
+ ```bash
202
+ # Fast smoke run
203
+ uv run python experiments/canonical_benchmark.py --quick
204
+
205
+ # Full reproducible benchmark with repeats
206
+ uv run python experiments/canonical_benchmark.py \
207
+ --datasets mnist synthetic_small \
208
+ --num-repeats 5 \
209
+ --seed 42 \
210
+ --require-real-proofs
211
+
212
+ # Interactive benchmark TUI (supports reattach)
213
+ uv run python experiments/benchmark_tui.py
214
+ ```
215
+
216
+ Outputs: `benchmark_results.json` (per-seed runs) and `tradeoff_report.json` (aggregated trade-off summary).
217
+
218
+ ## Repository Structure
219
+
220
+ ```
221
+ secure-fl/
222
+ ├── src/secure_fl/
223
+ │ ├── core/ # Types, config (thread-safe singleton), exceptions
224
+ │ ├── federation/ # SecureFlowerClient, SecureFlowerServer, FedJSCMAggregator,
225
+ │ │ # StabilityMonitor, client_utils, helpers
226
+ │ ├── zkp/ # ClientProofManager, ServerProofManager, quantization
227
+ │ ├── models/ # SimpleModel, MNISTCNN, ResNet18Model, factory
228
+ │ ├── data/ # benchmark_data: load/partition/subsample datasets
229
+ │ ├── cli/ # Click CLI: secure-fl, secure-fl-server, secure-fl-client
230
+ │ ├── proofs/
231
+ │ │ ├── client_circuits/ # PySNARK delta_bound_proof circuit
232
+ │ │ └── server/ # Circom aggregation SNARK circuits
233
+ │ └── utils/ # helpers (compute_hash, compute_parameter_norm), logging
234
+ ├── demo/
235
+ │ └── simulate_attack.py # Textual TUI: real ZKP attack detection demo
236
+ ├── experiments/
237
+ │ ├── canonical_benchmark.py
238
+ │ ├── benchmark_tui.py
239
+ │ └── config.yaml
240
+ └── tests/
241
+ ├── unit/ # No external dependencies (fast)
242
+ └── integration/ # Requires Circom + SnarkJS
243
+ ```
244
+
245
+ ## Development
246
+
247
+ ```bash
248
+ make dev # Full setup (uv sync + ZKP tools)
249
+ make lint # Ruff lint
250
+ make format # Ruff format ← run before every commit
251
+ make type-check # mypy (strict)
252
+ make test # Full suite
253
+ make test-quick # Fast, --exitfirst
254
+ make test-cov # With coverage (threshold: 34%)
255
+ ```
256
+
257
+ ## Known Limitations
258
+
259
+ - `_submit_to_blockchain()` raises `NotImplementedError` — not yet implemented
260
+ - Server ZKP (Circom/SnarkJS) requires `make setup-zkp`; subprocess calls time out after 300 s
261
+ - Coverage threshold is 34% — being raised incrementally
262
+
263
+ ## License
264
+
265
+ MIT License — see [LICENSE](LICENSE) for details.
266
+
267
+ ## Citation
268
+
269
+ ```bibtex
270
+ @misc{timilsina2026securefl,
271
+ title={Secure-FL: Zero-Knowledge Proofs for Dual-Verifiable Federated Learning},
272
+ author={Timilsina, Krishant and Paudel, Bindu and Timilsina, Arun Kumar},
273
+ year={2026},
274
+ url={https://github.com/krishantt/secure-fl},
275
+ note={Preprint}
276
+ }
277
+ ```
278
+
279
+ ## Acknowledgments
280
+
281
+ - [Flower](https://flower.dev) — federated learning infrastructure
282
+ - [Circom](https://github.com/iden3/circom) / [SnarkJS](https://github.com/iden3/snarkjs) — zk-SNARK toolchain
283
+ - [PySNARK](https://github.com/meilof/pysnark) — Python zk-STARK/SNARK library