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.
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/.gitignore +20 -5
- secure_fl-2026.8.25.dev1/PKG-INFO +336 -0
- secure_fl-2026.8.25.dev1/README.md +283 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/pyproject.toml +6 -11
- secure_fl-2026.8.25.dev1/src/secure_fl/.build_number +1 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/__init__.py +0 -1
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/__init__.py +8 -0
- secure_fl-2026.8.25.dev1/src/secure_fl/benchmarks/attacks.py +445 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/execution.py +180 -17
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/pipeline.py +3 -1
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/reporting.py +17 -1
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/handlers.py +22 -50
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/main.py +0 -5
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/config.py +27 -23
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/types.py +17 -14
- secure_fl-2026.8.25.dev1/src/secure_fl/data/__init__.py +23 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/data/benchmark_data.py +57 -21
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/aggregation.py +61 -46
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/client.py +122 -54
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/client_runtime.py +11 -7
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/client_utils.py +21 -6
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/server.py +40 -38
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/server_runtime.py +0 -1
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/stability_monitor.py +80 -40
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/__init__.py +1 -1
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/factory.py +20 -9
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/mnist_cnn.py +1 -1
- secure_fl-2026.8.25.dev1/src/secure_fl/proofs/client/delta_bound.circom +56 -0
- secure_fl-2026.8.25.dev1/src/secure_fl/proofs/server/aggregation.circom +55 -0
- secure_fl-2026.8.25.dev1/src/secure_fl/proofs/server/pot13_final.ptau +0 -0
- secure_fl-2026.8.25.dev1/src/secure_fl/py.typed +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/utils/helpers.py +32 -20
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/utils/logging_config.py +25 -16
- secure_fl-2026.8.25.dev1/src/secure_fl/zkp/client_proof_manager.py +501 -0
- secure_fl-2026.8.25.dev1/src/secure_fl/zkp/client_setup.py +205 -0
- secure_fl-2026.8.25.dev1/src/secure_fl/zkp/client_snark_io.py +148 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/proof_schema.py +1 -1
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/quantization.py +22 -3
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/server_proof_manager.py +232 -64
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/server_snark_io.py +39 -12
- secure_fl-2026.3.17.dev1/PKG-INFO +0 -360
- secure_fl-2026.3.17.dev1/README.md +0 -304
- secure_fl-2026.3.17.dev1/src/secure_fl/.build_number +0 -1
- secure_fl-2026.3.17.dev1/src/secure_fl/data/__init__.py +0 -85
- secure_fl-2026.3.17.dev1/src/secure_fl/proofs/client_circuits/sgd_full_trace.cairo +0 -502
- secure_fl-2026.3.17.dev1/src/secure_fl/zkp/client_proof_manager.py +0 -293
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/LICENSE +0 -0
- {secure_fl-2026.3.17.dev1/docs/research paper → secure_fl-2026.8.25.dev1/docs/paper}/README.md +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/experiments/README.md +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/__main__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/benchmarks/defaults.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/__init__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/__main__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/setup.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/cli/zkp_check.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/__init__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/_version.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/core/exceptions.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/federation/__init__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/models/simple.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/__init__.py +0 -0
- {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
- /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
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/client_circuits/delta_bound.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/server/pot12_final.ptau +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/proofs/server/test.txt +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/utils/__init__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/__init__.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/base.py +0 -0
- {secure_fl-2026.3.17.dev1 → secure_fl-2026.8.25.dev1}/src/secure_fl/zkp/proof_manager.py +0 -0
- {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/
|
|
107
|
-
!results/
|
|
108
|
-
!results/
|
|
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
|