secure-fl 2026.2.20.dev5__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 (57) hide show
  1. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/PKG-INFO +4 -4
  2. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/README.md +3 -3
  3. {secure_fl-2026.2.20.dev5 → 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.dev5 → 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.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/__init__.py +2 -2
  10. {secure_fl-2026.2.20.dev5 → 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.dev5 → 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.dev5 → 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.dev5/proofs/.gitkeep +0 -0
  25. secure_fl-2026.2.20.dev5/proofs/server/pot12_final.ptau +0 -0
  26. secure_fl-2026.2.20.dev5/src/secure_fl/.build_number +0 -1
  27. secure_fl-2026.2.20.dev5/src/secure_fl/cli/main.py +0 -611
  28. secure_fl-2026.2.20.dev5/src/secure_fl/zkp/proof_manager.py +0 -951
  29. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/.gitignore +0 -0
  30. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/LICENSE +0 -0
  31. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/experiments/README.md +0 -0
  32. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/__init__.py +0 -0
  33. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/__main__.py +0 -0
  34. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/cli/setup.py +0 -0
  35. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/__init__.py +0 -0
  36. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/_version.py +0 -0
  37. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/config.py +0 -0
  38. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/exceptions.py +0 -0
  39. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/core/types.py +0 -0
  40. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/aggregation.py +0 -0
  41. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/federation/stability_monitor.py +0 -0
  42. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/__init__.py +0 -0
  43. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/cifar10.py +0 -0
  44. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/flexible_mlp.py +0 -0
  45. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/mnist.py +0 -0
  46. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/resnet.py +0 -0
  47. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/models/simple.py +0 -0
  48. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/__init__.py +0 -0
  49. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/client_circuits/__init__.py +0 -0
  50. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/client_circuits/delta_bound.py +0 -0
  51. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/client_circuits/sgd_full_trace.cairo +0 -0
  52. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1/src/secure_fl}/proofs/server/test.txt +0 -0
  53. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/py.typed +0 -0
  54. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/utils/__init__.py +0 -0
  55. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/utils/helpers.py +0 -0
  56. {secure_fl-2026.2.20.dev5 → secure_fl-2026.2.22.dev1}/src/secure_fl/utils/logging_config.py +0 -0
  57. {secure_fl-2026.2.20.dev5 → 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.dev5
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
@@ -311,9 +311,9 @@ secure-fl/
311
311
  │ ├── core/ # Types, config, exceptions, versioning
312
312
  │ ├── cli/ # CLI commands and setup helpers
313
313
  │ └── utils/ # Logging and helper utilities
314
- ├── proofs/ # ZKP circuits
315
- │ ├── client_circuits/ # zk-STARK (Cairo)
316
- │ └── 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
317
317
  ├── experiments/ # Research experiments
318
318
  ├── tests/ # Test suite
319
319
  └── docs/ # Documentation
@@ -255,9 +255,9 @@ secure-fl/
255
255
  │ ├── core/ # Types, config, exceptions, versioning
256
256
  │ ├── cli/ # CLI commands and setup helpers
257
257
  │ └── utils/ # Logging and helper utilities
258
- ├── proofs/ # ZKP circuits
259
- │ ├── client_circuits/ # zk-STARK (Cairo)
260
- │ └── 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
261
261
  ├── experiments/ # Research experiments
262
262
  ├── tests/ # Test suite
263
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()
@@ -0,0 +1,78 @@
1
+ """ZKP tool availability checks and CLI command."""
2
+
3
+ import subprocess
4
+ import sys
5
+
6
+ import click
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+
11
+ def check_zkp_tools() -> dict[str, tuple[bool, str]]:
12
+ """Check availability and version information of required ZKP tools."""
13
+ tools: dict[str, tuple[bool, str]] = {}
14
+
15
+ tool_checks = {
16
+ "rust": ["rustc", "--version"],
17
+ "circom": ["circom", "--version"],
18
+ "snarkjs": ["snarkjs", "--version"],
19
+ "nodejs": ["node", "--version"],
20
+ }
21
+
22
+ for tool_name, cmd in tool_checks.items():
23
+ try:
24
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
25
+ if result.returncode == 0:
26
+ tools[tool_name] = (True, result.stdout.strip())
27
+ else:
28
+ tools[tool_name] = (False, "Installation failed")
29
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
30
+ tools[tool_name] = (False, "Not found")
31
+
32
+ return tools
33
+
34
+
35
+ @click.command()
36
+ def check_zkp() -> None:
37
+ """Check ZKP tools installation and versions."""
38
+ console = Console()
39
+
40
+ with console.status("Checking ZKP tools..."):
41
+ tools = check_zkp_tools()
42
+
43
+ table = Table(title="ZKP Tools Status", show_header=True)
44
+ table.add_column("Tool", style="bold")
45
+ table.add_column("Status", justify="center")
46
+ table.add_column("Version/Info", style="dim")
47
+
48
+ for tool_name, (available, version_info) in tools.items():
49
+ if available:
50
+ status = "[green]✓ Available[/green]"
51
+ table.add_row(tool_name.title(), status, version_info)
52
+ else:
53
+ status = "[red]✗ Missing[/red]"
54
+ table.add_row(tool_name.title(), status, version_info)
55
+
56
+ console.print(table)
57
+
58
+ required_tools = ["rust", "circom", "snarkjs", "nodejs"]
59
+ missing_tools = [
60
+ tool for tool in required_tools if not tools.get(tool, (False, ""))[0]
61
+ ]
62
+
63
+ if missing_tools:
64
+ console.print("\n[yellow]⚠️ Missing required ZKP tools:[/yellow]")
65
+ for tool in missing_tools:
66
+ console.print(f" • {tool.title()}")
67
+
68
+ console.print("\n[blue]Installation instructions:[/blue]")
69
+ console.print(
70
+ "1. Install Rust: curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh"
71
+ )
72
+ console.print("2. Install Node.js: https://nodejs.org/")
73
+ console.print("3. Install Circom: cargo install circom")
74
+ console.print("4. Install SnarkJS: npm install -g snarkjs")
75
+
76
+ sys.exit(1)
77
+
78
+ console.print("\n[green]✅ All ZKP tools are properly installed![/green]")
@@ -9,8 +9,8 @@ algorithm with zero-knowledge proof integration.
9
9
 
10
10
  from .aggregation import FedJSCMAggregator
11
11
  from .client import SecureFlowerClient
12
- from .client import create_client
13
- from .client import start_client
12
+ from .client_runtime import create_client
13
+ from .client_runtime import start_client
14
14
  from .server import SecureFlowerServer
15
15
  from .server import SecureFlowerStrategy
16
16
  from .server import create_server_strategy