flwr-nightly 1.18.0.dev20250416__py3-none-any.whl → 1.18.0.dev20250417__py3-none-any.whl

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.
@@ -15,61 +15,83 @@
15
15
  """Shamir's secret sharing."""
16
16
 
17
17
 
18
- import pickle
18
+ import os
19
19
  from concurrent.futures import ThreadPoolExecutor
20
- from typing import cast
21
20
 
22
21
  from Crypto.Protocol.SecretSharing import Shamir
23
22
  from Crypto.Util.Padding import pad, unpad
24
23
 
25
24
 
26
25
  def create_shares(secret: bytes, threshold: int, num: int) -> list[bytes]:
27
- """Return list of shares (bytes)."""
26
+ """Return a list of shares (bytes).
27
+
28
+ Shares are created from the provided secret using Shamir's secret sharing.
29
+ """
30
+ # Shamir's secret sharing requires the secret to be a multiple of 16 bytes
31
+ # (AES block size). Pad the secret to the next multiple of 16 bytes.
28
32
  secret_padded = pad(secret, 16)
29
- secret_padded_chunk = [
30
- (threshold, num, secret_padded[i : i + 16])
31
- for i in range(0, len(secret_padded), 16)
32
- ]
33
- share_list: list[list[tuple[int, bytes]]] = [[] for _ in range(num)]
33
+ chunks = [secret_padded[i : i + 16] for i in range(0, len(secret_padded), 16)]
34
+
35
+ # The share list should contain shares of the secret, and each share consists of:
36
+ # <4 bytes of index><share of chunk1><share of chunk2>...<share of chunkN>
37
+ share_list: list[bytearray] = [bytearray() for _ in range(num)]
34
38
 
35
- with ThreadPoolExecutor(max_workers=10) as executor:
39
+ # Create shares for each chunk in parallel
40
+ max_workers = min(len(chunks), os.cpu_count() or 1)
41
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
36
42
  for chunk_shares in executor.map(
37
- lambda arg: _shamir_split(*arg), secret_padded_chunk
43
+ lambda chunk: _shamir_split(threshold, num, chunk), chunks
38
44
  ):
39
45
  for idx, share in chunk_shares:
40
- # Index in `chunk_shares` starts from 1
41
- share_list[idx - 1].append((idx, share))
46
+ # Initialize the share with the index if it is empty
47
+ if not share_list[idx - 1]:
48
+ share_list[idx - 1] += idx.to_bytes(4, "little", signed=False)
42
49
 
43
- return [pickle.dumps(shares) for shares in share_list]
50
+ # Append the share to the bytes
51
+ share_list[idx - 1] += share
52
+
53
+ return [bytes(share) for share in share_list]
44
54
 
45
55
 
46
56
  def _shamir_split(threshold: int, num: int, chunk: bytes) -> list[tuple[int, bytes]]:
57
+ """Create shares for a chunk using Shamir's secret sharing.
58
+
59
+ Each share is a tuple (index, share_bytes), where share_bytes is 16 bytes long.
60
+ """
47
61
  return Shamir.split(threshold, num, chunk, ssss=False)
48
62
 
49
63
 
50
- # Reconstructing secret with PyCryptodome
51
64
  def combine_shares(share_list: list[bytes]) -> bytes:
52
- """Reconstruct secret from shares."""
53
- unpickled_share_list: list[list[tuple[int, bytes]]] = [
54
- cast(list[tuple[int, bytes]], pickle.loads(share)) for share in share_list
55
- ]
65
+ """Reconstruct the secret from a list of shares."""
66
+ # Compute the number of chunks
67
+ # Each share contains 4 bytes of index and 16 bytes of share for each chunk
68
+ chunk_num = (len(share_list[0]) - 4) >> 4
56
69
 
57
- chunk_num = len(unpickled_share_list[0])
58
70
  secret_padded = bytearray(0)
59
- chunk_shares_list: list[list[tuple[int, bytes]]] = []
60
- for i in range(chunk_num):
61
- chunk_shares: list[tuple[int, bytes]] = []
62
- for share in unpickled_share_list:
63
- chunk_shares.append(share[i])
64
- chunk_shares_list.append(chunk_shares)
65
-
66
- with ThreadPoolExecutor(max_workers=10) as executor:
71
+ chunk_shares_list: list[list[tuple[int, bytes]]] = [[] for _ in range(chunk_num)]
72
+
73
+ # Split shares into chunks
74
+ for share in share_list:
75
+ # The first 4 bytes are the index
76
+ index = int.from_bytes(share[:4], "little", signed=False)
77
+ for i in range(chunk_num):
78
+ start = (i << 4) + 4
79
+ chunk_shares_list[i].append((index, share[start : start + 16]))
80
+
81
+ # Combine shares for each chunk in parallel
82
+ max_workers = min(chunk_num, os.cpu_count() or 1)
83
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
67
84
  for chunk in executor.map(_shamir_combine, chunk_shares_list):
68
85
  secret_padded += chunk
69
86
 
70
- secret = unpad(secret_padded, 16)
71
- return bytes(secret)
87
+ try:
88
+ secret = unpad(bytes(secret_padded), 16)
89
+ except ValueError:
90
+ # If unpadding fails, it means the shares are not valid
91
+ raise ValueError("Failed to combine shares") from None
92
+ return secret
72
93
 
73
94
 
74
95
  def _shamir_combine(shares: list[tuple[int, bytes]]) -> bytes:
96
+ """Reconstruct a chunk from shares using Shamir's secret sharing."""
75
97
  return Shamir.combine(shares, ssss=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.18.0.dev20250416
3
+ Version: 1.18.0.dev20250417
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -145,7 +145,7 @@ flwr/common/recorddict_compat.py,sha256=Znn1xRGiqLpPPgviVqyb-GPTM-pCK6tpnEmhWSXa
145
145
  flwr/common/retry_invoker.py,sha256=T6puUH3nCxdRzQHeanyr-0nTxhRiS1TH07rmef9vuLQ,14482
146
146
  flwr/common/secure_aggregation/__init__.py,sha256=MgW6uHGhyFLBAYQqa1Vzs5n2Gc0d4yEw1_NmerFir70,731
147
147
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=5E4q4-Fw0CNz4tLah_QHj7m_rDeM4ucHcFlPWB_Na3Q,738
148
- flwr/common/secure_aggregation/crypto/shamir.py,sha256=U_WzU4ghYdNr6nPPURZPHWi0AWjeZW1-di--1BVy1L4,2779
148
+ flwr/common/secure_aggregation/crypto/shamir.py,sha256=N8pPa5cEksowNoAqfFm5SP3IuxuVi9GGMa3JOtPniQY,3954
149
149
  flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=H6Rlpr4i-VAwOMNdyfH33uDpXNsquiK1gKrOixtKqek,5333
150
150
  flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=TrggOlizlny3V2KS7-3mpDr33En8UmW9oJcxcS_6oh0,3013
151
151
  flwr/common/secure_aggregation/quantization.py,sha256=ssFZpiRyj9ltIh0Ai3vGkDqWFO4SoqgoD1mDU9XqMEM,2400
@@ -326,7 +326,7 @@ flwr/superexec/exec_servicer.py,sha256=Z0YYfs6eNPhqn8rY0x_R04XgR2mKFpggt07IH0EhU
326
326
  flwr/superexec/exec_user_auth_interceptor.py,sha256=iqygALkOMBUu_s_R9G0mFThZA7HTUzuXCLgxLCefiwI,4440
327
327
  flwr/superexec/executor.py,sha256=M5ucqSE53jfRtuCNf59WFLqQvA1Mln4741TySeZE7qQ,3112
328
328
  flwr/superexec/simulation.py,sha256=j6YwUvBN7EQ09ID7MYOCVZ70PGbuyBy8f9bXU0EszEM,4088
329
- flwr_nightly-1.18.0.dev20250416.dist-info/METADATA,sha256=gaWOX_Z91ciYX-c5Q_Ih-BtNhA2rLyIb8ENPCQH90fE,15868
330
- flwr_nightly-1.18.0.dev20250416.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
331
- flwr_nightly-1.18.0.dev20250416.dist-info/entry_points.txt,sha256=2-1L-GNKhwGw2_7_RoH55vHw2SIHjdAQy3HAVAWl9PY,374
332
- flwr_nightly-1.18.0.dev20250416.dist-info/RECORD,,
329
+ flwr_nightly-1.18.0.dev20250417.dist-info/METADATA,sha256=OS2TSt_sdwWENvHBSddnb5tfZ6fgU-9rtq7piRLFv44,15868
330
+ flwr_nightly-1.18.0.dev20250417.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
331
+ flwr_nightly-1.18.0.dev20250417.dist-info/entry_points.txt,sha256=2-1L-GNKhwGw2_7_RoH55vHw2SIHjdAQy3HAVAWl9PY,374
332
+ flwr_nightly-1.18.0.dev20250417.dist-info/RECORD,,