secure-fl 2026.2.20.dev4__tar.gz → 2026.2.22.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 (59) hide show
  1. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/PKG-INFO +17 -4
  2. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/README.md +16 -3
  3. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/pyproject.toml +0 -1
  4. secure_fl-2026.2.22.dev1/src/secure_fl/.build_number +1 -0
  5. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/cli/__init__.py +2 -2
  6. secure_fl-2026.2.22.dev1/src/secure_fl/cli/handlers.py +270 -0
  7. secure_fl-2026.2.22.dev1/src/secure_fl/cli/main.py +258 -0
  8. secure_fl-2026.2.22.dev1/src/secure_fl/cli/zkp_check.py +78 -0
  9. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/__init__.py +2 -2
  10. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/client.py +36 -172
  11. secure_fl-2026.2.22.dev1/src/secure_fl/federation/client_runtime.py +91 -0
  12. secure_fl-2026.2.22.dev1/src/secure_fl/federation/client_utils.py +117 -0
  13. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/server.py +9 -102
  14. secure_fl-2026.2.22.dev1/src/secure_fl/federation/server_runtime.py +104 -0
  15. secure_fl-2026.2.22.dev1/src/secure_fl/proofs/server/pot12_final.ptau +0 -0
  16. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/zkp/__init__.py +2 -2
  17. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/base.py +33 -0
  18. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/client_proof_manager.py +242 -0
  19. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/proof_manager.py +11 -0
  20. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/proof_schema.py +69 -0
  21. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/server_proof_manager.py +459 -0
  22. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/server_setup.py +127 -0
  23. secure_fl-2026.2.22.dev1/src/secure_fl/zkp/server_snark_io.py +115 -0
  24. secure_fl-2026.2.20.dev4/examples/README.md +0 -275
  25. secure_fl-2026.2.20.dev4/proofs/.gitkeep +0 -0
  26. secure_fl-2026.2.20.dev4/proofs/server/pot12_final.ptau +0 -0
  27. secure_fl-2026.2.20.dev4/scripts/README.md +0 -322
  28. secure_fl-2026.2.20.dev4/src/secure_fl/.build_number +0 -1
  29. secure_fl-2026.2.20.dev4/src/secure_fl/cli/main.py +0 -611
  30. secure_fl-2026.2.20.dev4/src/secure_fl/zkp/proof_manager.py +0 -951
  31. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/.gitignore +0 -0
  32. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/LICENSE +0 -0
  33. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/experiments/README.md +0 -0
  34. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/__init__.py +0 -0
  35. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/__main__.py +0 -0
  36. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/cli/setup.py +0 -0
  37. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/__init__.py +0 -0
  38. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/_version.py +0 -0
  39. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/config.py +0 -0
  40. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/exceptions.py +0 -0
  41. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/types.py +0 -0
  42. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/aggregation.py +0 -0
  43. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/stability_monitor.py +0 -0
  44. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/__init__.py +0 -0
  45. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/cifar10.py +0 -0
  46. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/flexible_mlp.py +0 -0
  47. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/mnist.py +0 -0
  48. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/resnet.py +0 -0
  49. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/simple.py +0 -0
  50. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/__init__.py +0 -0
  51. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/client_circuits/__init__.py +0 -0
  52. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/client_circuits/delta_bound.py +0 -0
  53. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/client_circuits/sgd_full_trace.cairo +0 -0
  54. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/server/test.txt +0 -0
  55. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/py.typed +0 -0
  56. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/utils/__init__.py +0 -0
  57. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/utils/helpers.py +0 -0
  58. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/utils/logging_config.py +0 -0
  59. {secure_fl-2026.2.20.dev4 → secure_fl-2026.2.22.dev1}/src/secure_fl/zkp/quantization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secure-fl
3
- Version: 2026.2.20.dev4
3
+ Version: 2026.2.22.dev1
4
4
  Summary: Dual-Verifiable Framework for Federated Learning using Zero-Knowledge Proofs
5
5
  Project-URL: Homepage, https://github.com/krishantt/secure-fl
6
6
  Project-URL: Bug Reports, https://github.com/krishantt/secure-fl/issues
@@ -268,6 +268,19 @@ make demo # Run demonstration
268
268
  make clean # Clean artifacts
269
269
  ```
270
270
 
271
+ ### Docker (Minimal)
272
+
273
+ ```bash
274
+ # Build image
275
+ docker build -t secure-fl:local .
276
+
277
+ # Start local FL stack
278
+ docker compose up -d
279
+
280
+ # Stop services
281
+ docker compose down
282
+ ```
283
+
271
284
  ## 📈 Experiments
272
285
 
273
286
  Run benchmarks and experiments:
@@ -298,9 +311,9 @@ secure-fl/
298
311
  │ ├── core/ # Types, config, exceptions, versioning
299
312
  │ ├── cli/ # CLI commands and setup helpers
300
313
  │ └── utils/ # Logging and helper utilities
301
- ├── proofs/ # ZKP circuits
302
- │ ├── client_circuits/ # zk-STARK (Cairo)
303
- │ └── server/ # zk-SNARK (Circom)
314
+ │ ├── proofs/ # ZKP circuits and proving assets
315
+ │ │ ├── client_circuits/ # zk-STARK/PySNARK client circuits
316
+ │ │ └── server/ # zk-SNARK (Circom) server circuits
304
317
  ├── experiments/ # Research experiments
305
318
  ├── tests/ # Test suite
306
319
  └── docs/ # Documentation
@@ -212,6 +212,19 @@ make demo # Run demonstration
212
212
  make clean # Clean artifacts
213
213
  ```
214
214
 
215
+ ### Docker (Minimal)
216
+
217
+ ```bash
218
+ # Build image
219
+ docker build -t secure-fl:local .
220
+
221
+ # Start local FL stack
222
+ docker compose up -d
223
+
224
+ # Stop services
225
+ docker compose down
226
+ ```
227
+
215
228
  ## 📈 Experiments
216
229
 
217
230
  Run benchmarks and experiments:
@@ -242,9 +255,9 @@ secure-fl/
242
255
  │ ├── core/ # Types, config, exceptions, versioning
243
256
  │ ├── cli/ # CLI commands and setup helpers
244
257
  │ └── utils/ # Logging and helper utilities
245
- ├── proofs/ # ZKP circuits
246
- │ ├── client_circuits/ # zk-STARK (Cairo)
247
- │ └── server/ # zk-SNARK (Circom)
258
+ │ ├── proofs/ # ZKP circuits and proving assets
259
+ │ │ ├── client_circuits/ # zk-STARK/PySNARK client circuits
260
+ │ │ └── server/ # zk-SNARK (Circom) server circuits
248
261
  ├── experiments/ # Research experiments
249
262
  ├── tests/ # Test suite
250
263
  └── docs/ # Documentation
@@ -108,7 +108,6 @@ packages = ["src/secure_fl"]
108
108
  [tool.hatch.build]
109
109
  include = [
110
110
  "src/",
111
- "proofs/",
112
111
  "README.md",
113
112
  "LICENSE",
114
113
  ]
@@ -0,0 +1 @@
1
+ 2026.02.22:1
@@ -6,12 +6,12 @@ with the Secure FL framework, including commands for running servers,
6
6
  clients, configuration management, and system validation.
7
7
  """
8
8
 
9
- from .main import check_zkp
10
- from .main import check_zkp_tools
11
9
  from .main import client
12
10
  from .main import create_config
13
11
  from .main import main
14
12
  from .main import server
13
+ from .zkp_check import check_zkp
14
+ from .zkp_check import check_zkp_tools
15
15
 
16
16
 
17
17
  def print_system_info() -> None:
@@ -0,0 +1,270 @@
1
+ """Command handlers for the Secure FL CLI."""
2
+
3
+ import logging
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ from typing import TYPE_CHECKING
8
+ from typing import Union
9
+
10
+ import click
11
+ from rich import print as rprint
12
+ from rich.console import Console
13
+ from rich.progress import Progress
14
+ from rich.progress import SpinnerColumn
15
+ from rich.progress import TextColumn
16
+ from rich.table import Table
17
+
18
+ from secure_fl.core.config import create_example_config
19
+ from secure_fl.models import CIFAR10Model
20
+ from secure_fl.models import MNISTModel
21
+ from secure_fl.models import SimpleModel
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Callable
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ def run_server(
30
+ *,
31
+ config: str | None,
32
+ host: str,
33
+ port: int,
34
+ rounds: int,
35
+ min_clients: int,
36
+ enable_zkp: bool,
37
+ proof_rigor: str,
38
+ momentum: float,
39
+ blockchain: bool,
40
+ model: str,
41
+ console: Console,
42
+ ) -> None:
43
+ from secure_fl.core import get_default_config
44
+ from secure_fl.federation.server import SecureFlowerServer
45
+ from secure_fl.federation.server import create_server_strategy
46
+
47
+ if config:
48
+ import yaml
49
+
50
+ with open(config) as f:
51
+ cfg = yaml.safe_load(f)
52
+ else:
53
+ cfg = get_default_config()
54
+
55
+ cfg["server"].update({"host": host, "port": port, "num_rounds": rounds})
56
+
57
+ strategy_config = {**cfg["strategy"], **cfg["aggregation"], **cfg["zkp"]}
58
+ strategy_config.update(
59
+ {
60
+ "min_available_clients": min_clients,
61
+ "min_fit_clients": min_clients,
62
+ "min_evaluate_clients": min_clients,
63
+ "momentum": momentum,
64
+ "enable_zkp": enable_zkp,
65
+ "proof_rigor": proof_rigor,
66
+ "blockchain_verification": blockchain,
67
+ }
68
+ )
69
+
70
+ model_fn: Callable[[], Union[MNISTModel, CIFAR10Model, SimpleModel]]
71
+ if model == "mnist":
72
+
73
+ def model_fn() -> MNISTModel:
74
+ return MNISTModel()
75
+ elif model == "cifar10":
76
+
77
+ def model_fn() -> CIFAR10Model:
78
+ return CIFAR10Model()
79
+ elif model == "simple":
80
+
81
+ def model_fn() -> SimpleModel:
82
+ return SimpleModel(input_dim=784, output_dim=10)
83
+ else:
84
+ raise click.ClickException(
85
+ f"Unknown model '{model}'. Available: mnist, cifar10, simple"
86
+ )
87
+
88
+ strategy = create_server_strategy(model_fn=model_fn, **strategy_config)
89
+
90
+ with Progress(
91
+ SpinnerColumn(),
92
+ TextColumn("[progress.description]{task.description}"),
93
+ console=console,
94
+ ) as progress:
95
+ task = progress.add_task("Starting Secure FL Server...", total=None)
96
+ server = SecureFlowerServer(strategy=strategy, **cfg["server"])
97
+ progress.update(task, description="Server ready! Waiting for clients...")
98
+
99
+ table = Table(title="Server Configuration")
100
+ table.add_column("Parameter", style="cyan")
101
+ table.add_column("Value", style="green")
102
+ table.add_row("Host", f"{host}:{port}")
103
+ table.add_row("Rounds", str(rounds))
104
+ table.add_row("Min Clients", str(min_clients))
105
+ table.add_row("ZKP Enabled", "✓" if enable_zkp else "✗")
106
+ table.add_row("Proof Rigor", proof_rigor)
107
+ table.add_row("Momentum", str(momentum))
108
+ table.add_row("Blockchain", "✓" if blockchain else "✗")
109
+ console.print(table)
110
+ server.start()
111
+
112
+
113
+ def run_client(
114
+ *,
115
+ server_address: str,
116
+ client_id: str,
117
+ dataset: str,
118
+ data_path: str | None,
119
+ partition: int | None,
120
+ enable_zkp: bool,
121
+ epochs: int,
122
+ batch_size: int,
123
+ learning_rate: float,
124
+ proof_rigor: str,
125
+ console: Console,
126
+ ) -> None:
127
+ from secure_fl.data import FederatedDataLoader
128
+ from secure_fl.federation.client import create_client
129
+ from secure_fl.federation.client import start_client
130
+
131
+ if data_path:
132
+ data_loader = FederatedDataLoader(
133
+ dataset_name="synthetic",
134
+ num_clients=1,
135
+ batch_size=batch_size,
136
+ )
137
+ loaders = data_loader.create_client_dataloaders(client_id=0)
138
+ train_loader, val_loader = (
139
+ loaders if not isinstance(loaders, list) else loaders[0]
140
+ )
141
+ train_data = train_loader.dataset
142
+ val_data = val_loader.dataset if val_loader else None
143
+ else:
144
+ data_loader = FederatedDataLoader(
145
+ dataset_name=dataset,
146
+ num_clients=max(1, partition + 1) if partition is not None else 5,
147
+ batch_size=batch_size,
148
+ )
149
+ client_partition = partition or 0
150
+ loaders = data_loader.create_client_dataloaders(client_id=client_partition)
151
+ train_loader, val_loader = (
152
+ loaders if not isinstance(loaders, list) else loaders[0]
153
+ )
154
+ train_data = train_loader.dataset
155
+ val_data = val_loader.dataset if val_loader else None
156
+
157
+ if dataset in ["mnist", "synthetic"] and dataset != "custom":
158
+ model_fn: Callable[[], Union[MNISTModel, CIFAR10Model, SimpleModel]]
159
+ if dataset == "mnist":
160
+
161
+ def model_fn() -> MNISTModel:
162
+ return MNISTModel()
163
+ elif dataset == "cifar10":
164
+
165
+ def model_fn() -> CIFAR10Model:
166
+ return CIFAR10Model()
167
+ else:
168
+
169
+ def model_fn() -> SimpleModel:
170
+ return SimpleModel()
171
+ else:
172
+
173
+ def model_fn() -> SimpleModel:
174
+ return SimpleModel()
175
+
176
+ client = create_client(
177
+ client_id=client_id,
178
+ model_fn=model_fn,
179
+ train_data=train_data,
180
+ val_data=val_data,
181
+ enable_zkp=enable_zkp,
182
+ local_epochs=epochs,
183
+ batch_size=batch_size,
184
+ learning_rate=learning_rate,
185
+ proof_rigor=proof_rigor,
186
+ )
187
+
188
+ table = Table(title=f"Client {client_id} Configuration")
189
+ table.add_column("Parameter", style="cyan")
190
+ table.add_column("Value", style="green")
191
+ table.add_row("Server", server_address)
192
+ table.add_row("Dataset", dataset)
193
+ table.add_row("ZKP Enabled", "✓" if enable_zkp else "✗")
194
+ table.add_row("Epochs", str(epochs))
195
+ table.add_row("Batch Size", str(batch_size))
196
+ table.add_row("Learning Rate", str(learning_rate))
197
+ console.print(table)
198
+
199
+ with Progress(
200
+ SpinnerColumn(),
201
+ TextColumn("[progress.description]{task.description}"),
202
+ console=console,
203
+ ) as progress:
204
+ progress.add_task("Connecting to server...", total=None)
205
+ start_client(client, server_address)
206
+
207
+
208
+ def run_setup(*, action: str, skip_zkp: bool) -> None:
209
+ from secure_fl.cli.setup import print_setup_status
210
+ from secure_fl.cli.setup import setup_environment
211
+
212
+ if action == "check":
213
+ rprint("[bold]🔍 Checking System Requirements[/bold]")
214
+ print_setup_status()
215
+ elif action == "install":
216
+ rprint("[bold]📦 Installing Python Dependencies[/bold]")
217
+ success = setup_environment()
218
+ if success:
219
+ rprint("[green]✓ Python dependencies installed successfully[/green]")
220
+ else:
221
+ rprint("[red]✗ Failed to install some dependencies[/red]")
222
+ elif action == "zkp":
223
+ if skip_zkp:
224
+ rprint("[yellow]⚠ Skipping ZKP tools installation[/yellow]")
225
+ return
226
+ rprint("[bold]🔐 Setting up ZKP Tools[/bold]")
227
+ from secure_fl.cli.setup import install_zkp_tools
228
+
229
+ success = install_zkp_tools()
230
+ if success:
231
+ rprint("[green]✓ ZKP tools setup completed[/green]")
232
+ else:
233
+ rprint("[yellow]⚠ ZKP tools setup completed with warnings[/yellow]")
234
+ elif action == "clean":
235
+ rprint("[bold]🧹 Cleaning temporary files[/bold]")
236
+ for cache_dir in [".ruff_cache", ".mypy_cache", "__pycache__"]:
237
+ if os.path.exists(cache_dir):
238
+ shutil.rmtree(cache_dir)
239
+ rprint("[green]✓ Cleanup completed[/green]")
240
+ elif action == "full":
241
+ rprint("[bold]🚀 Full Setup[/bold]")
242
+ rprint("1. Installing Python dependencies...")
243
+ success = setup_environment()
244
+ if not skip_zkp:
245
+ rprint("2. Setting up ZKP tools...")
246
+ from secure_fl.cli.setup import install_zkp_tools
247
+
248
+ install_zkp_tools()
249
+ else:
250
+ rprint("2. Skipping ZKP tools...")
251
+
252
+ rprint("3. Creating configuration...")
253
+ create_example_config("config.yaml")
254
+ rprint("4. Running tests...")
255
+ try:
256
+ subprocess.run(["uv", "run", "pytest", "--tb=short"], check=True)
257
+ test_success = True
258
+ except subprocess.CalledProcessError:
259
+ test_success = False
260
+
261
+ if success and test_success:
262
+ rprint("[green]✓ Full setup completed successfully![/green]")
263
+ rprint("\n[bold]Next steps:[/bold]")
264
+ rprint(
265
+ "1. Run a demo: [cyan]secure-fl experiment --dataset synthetic --rounds 3[/cyan]"
266
+ )
267
+ rprint("2. Start a server: [cyan]secure-fl server --rounds 5[/cyan]")
268
+ rprint("3. Connect clients: [cyan]secure-fl client -i client_1[/cyan]")
269
+ else:
270
+ rprint("[yellow]⚠ Setup completed with test failures[/yellow]")
@@ -0,0 +1,258 @@
1
+ """
2
+ Command Line Interface for Secure FL
3
+
4
+ This module provides command-line interfaces for running the Secure FL framework,
5
+ including server, client, experiment, and setup commands.
6
+ """
7
+
8
+ import logging
9
+ import sys
10
+
11
+ import click
12
+ from rich import print as rprint
13
+ from rich.console import Console
14
+ from rich.logging import RichHandler
15
+
16
+ from secure_fl.cli.handlers import run_client
17
+ from secure_fl.cli.handlers import run_server
18
+ from secure_fl.cli.handlers import run_setup
19
+ from secure_fl.cli.zkp_check import check_zkp
20
+ from secure_fl.core.config import create_example_config
21
+
22
+ # Setup rich console and logging
23
+ console = Console()
24
+ logging.basicConfig(
25
+ level=logging.INFO,
26
+ format="%(message)s",
27
+ datefmt="[%X]",
28
+ handlers=[RichHandler(console=console, rich_tracebacks=True)],
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ @click.group()
34
+ @click.version_option(version="0.1.0")
35
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
36
+ @click.option("--quiet", "-q", is_flag=True, help="Suppress output")
37
+ def main(verbose: bool, quiet: bool) -> None:
38
+ """
39
+ 🔐 Secure FL: Dual-Verifiable Federated Learning with Zero-Knowledge Proofs
40
+
41
+ A complete framework for federated learning with ZKP verification using
42
+ client-side zk-STARKs and server-side zk-SNARKs.
43
+ """
44
+ if verbose:
45
+ logging.getLogger().setLevel(logging.DEBUG)
46
+ elif quiet:
47
+ logging.getLogger().setLevel(logging.ERROR)
48
+
49
+ # Display banner
50
+ if not quiet:
51
+ rprint(
52
+ """
53
+ [bold blue]🔐 Secure FL Framework[/bold blue]
54
+ [dim]Dual-Verifiable Federated Learning with Zero-Knowledge Proofs[/dim]
55
+ """
56
+ )
57
+
58
+
59
+ @main.command()
60
+ @click.option(
61
+ "--config", "-c", type=click.Path(exists=True), help="Configuration file path"
62
+ )
63
+ @click.option("--host", default="localhost", help="Server host address")
64
+ @click.option("--port", default=8080, type=int, help="Server port")
65
+ @click.option("--rounds", "-r", default=10, type=int, help="Number of training rounds")
66
+ @click.option(
67
+ "--min-clients", default=2, type=int, help="Minimum number of clients required"
68
+ )
69
+ @click.option(
70
+ "--enable-zkp/--disable-zkp",
71
+ default=True,
72
+ help="Enable/disable zero-knowledge proofs",
73
+ )
74
+ @click.option(
75
+ "--proof-rigor",
76
+ type=click.Choice(["low", "medium", "high"]),
77
+ default="high",
78
+ help="ZKP rigor level",
79
+ )
80
+ @click.option(
81
+ "--momentum", default=0.9, type=float, help="FedJSCM momentum coefficient"
82
+ )
83
+ @click.option(
84
+ "--blockchain/--no-blockchain", default=False, help="Enable blockchain verification"
85
+ )
86
+ @click.option(
87
+ "--model",
88
+ type=click.Choice(["mnist", "cifar10", "simple", "custom"]),
89
+ default="mnist",
90
+ help="Model type to use",
91
+ )
92
+ def server(
93
+ config: str | None,
94
+ host: str,
95
+ port: int,
96
+ rounds: int,
97
+ min_clients: int,
98
+ enable_zkp: bool,
99
+ proof_rigor: str,
100
+ momentum: float,
101
+ blockchain: bool,
102
+ model: str,
103
+ ) -> None:
104
+ """Start the Secure FL server"""
105
+ try:
106
+ run_server(
107
+ config=config,
108
+ host=host,
109
+ port=port,
110
+ rounds=rounds,
111
+ min_clients=min_clients,
112
+ enable_zkp=enable_zkp,
113
+ proof_rigor=proof_rigor,
114
+ momentum=momentum,
115
+ blockchain=blockchain,
116
+ model=model,
117
+ console=console,
118
+ )
119
+
120
+ except ImportError as e:
121
+ raise click.ClickException(f"Missing dependencies: {e}") from e
122
+ except Exception as e:
123
+ logger.error(f"Server failed to start: {e}")
124
+ raise click.ClickException(f"Server error: {e}") from e
125
+
126
+
127
+ @main.command()
128
+ @click.option(
129
+ "--server-address",
130
+ "-s",
131
+ default="localhost:8080",
132
+ help="Server address (host:port)",
133
+ )
134
+ @click.option("--client-id", "-i", required=True, help="Unique client identifier")
135
+ @click.option(
136
+ "--dataset",
137
+ type=click.Choice(["mnist", "cifar10", "synthetic"]),
138
+ default="mnist",
139
+ help="Dataset to use for training",
140
+ )
141
+ @click.option("--data-path", type=click.Path(), help="Path to custom dataset")
142
+ @click.option("--partition", type=int, help="Data partition index")
143
+ @click.option(
144
+ "--enable-zkp/--disable-zkp",
145
+ default=True,
146
+ help="Enable/disable zero-knowledge proof generation",
147
+ )
148
+ @click.option(
149
+ "--epochs", "-e", default=5, type=int, help="Local training epochs per round"
150
+ )
151
+ @click.option("--batch-size", "-b", default=32, type=int, help="Training batch size")
152
+ @click.option(
153
+ "--learning-rate", "-lr", default=0.01, type=float, help="Local learning rate"
154
+ )
155
+ @click.option(
156
+ "--proof-rigor",
157
+ type=click.Choice(["high", "medium", "low"]),
158
+ default="high",
159
+ help="ZKP proof rigor level",
160
+ )
161
+ def client(
162
+ server_address: str,
163
+ client_id: str,
164
+ dataset: str,
165
+ data_path: str | None,
166
+ partition: int | None,
167
+ enable_zkp: bool,
168
+ epochs: int,
169
+ batch_size: int,
170
+ learning_rate: float,
171
+ proof_rigor: str,
172
+ ) -> None:
173
+ """Start a Secure FL client"""
174
+ try:
175
+ run_client(
176
+ server_address=server_address,
177
+ client_id=client_id,
178
+ dataset=dataset,
179
+ data_path=data_path,
180
+ partition=partition,
181
+ enable_zkp=enable_zkp,
182
+ epochs=epochs,
183
+ batch_size=batch_size,
184
+ learning_rate=learning_rate,
185
+ proof_rigor=proof_rigor,
186
+ console=console,
187
+ )
188
+
189
+ except ImportError as e:
190
+ raise click.ClickException(f"Missing dependencies: {e}") from e
191
+ except Exception as e:
192
+ logger.error(f"Client failed to start: {e}")
193
+ raise click.ClickException(f"Client error: {e}") from e
194
+
195
+
196
+ @main.command()
197
+ @click.option(
198
+ "--action",
199
+ type=click.Choice(["install", "zkp", "check", "clean", "full"]),
200
+ default="check",
201
+ help="Setup action to perform",
202
+ )
203
+ @click.option("--force", is_flag=True, help="Force reinstallation")
204
+ @click.option("--skip-zkp", is_flag=True, help="Skip ZKP tools installation")
205
+ def setup(action: str, force: bool, skip_zkp: bool) -> None:
206
+ """Setup and configure Secure FL environment"""
207
+ try:
208
+ _ = force
209
+ run_setup(action=action, skip_zkp=skip_zkp)
210
+
211
+ except Exception as e:
212
+ logger.error(f"Setup failed: {e}")
213
+ raise click.ClickException(f"Setup error: {e}") from e
214
+
215
+
216
+ @main.command()
217
+ def info() -> None:
218
+ """Display system information and component status"""
219
+
220
+ from . import print_system_info
221
+
222
+ print_system_info()
223
+
224
+
225
+ def server_command() -> None:
226
+ """Entry point for secure-fl-server command"""
227
+ server.main(args=sys.argv[1:], prog_name="secure-fl-server")
228
+
229
+
230
+ def client_command() -> None:
231
+ """Entry point for secure-fl-client command"""
232
+ client.main(args=sys.argv[1:], prog_name="secure-fl-client")
233
+
234
+
235
+ def setup_command() -> None:
236
+ """Entry point for secure-fl-setup command"""
237
+ setup.main(args=sys.argv[1:], prog_name="secure-fl-setup")
238
+
239
+
240
+ @main.command()
241
+ @click.option("--output", "-o", default="config.yaml", help="Output file path")
242
+ def create_config(output: str) -> None:
243
+ """Create an example configuration file."""
244
+ try:
245
+ create_example_config(output)
246
+ console.print(f"✅ Example configuration created at [bold]{output}[/bold]")
247
+ console.print("Edit the file to customize your settings.")
248
+ except Exception as e:
249
+ console.print(f"❌ Error creating configuration: {e}", style="red")
250
+ raise click.Abort() from e
251
+
252
+
253
+ # Add check-zkp command to main CLI
254
+ main.add_command(check_zkp, name="check-zkp")
255
+
256
+
257
+ if __name__ == "__main__":
258
+ main()