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.
- flwr/common/secure_aggregation/crypto/shamir.py +51 -29
- {flwr_nightly-1.18.0.dev20250416.dist-info → flwr_nightly-1.18.0.dev20250417.dist-info}/METADATA +1 -1
- {flwr_nightly-1.18.0.dev20250416.dist-info → flwr_nightly-1.18.0.dev20250417.dist-info}/RECORD +5 -5
- {flwr_nightly-1.18.0.dev20250416.dist-info → flwr_nightly-1.18.0.dev20250417.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.18.0.dev20250416.dist-info → flwr_nightly-1.18.0.dev20250417.dist-info}/entry_points.txt +0 -0
@@ -15,61 +15,83 @@
|
|
15
15
|
"""Shamir's secret sharing."""
|
16
16
|
|
17
17
|
|
18
|
-
import
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
share_list: list[
|
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
|
-
|
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
|
43
|
+
lambda chunk: _shamir_split(threshold, num, chunk), chunks
|
38
44
|
):
|
39
45
|
for idx, share in chunk_shares:
|
40
|
-
#
|
41
|
-
share_list[idx - 1]
|
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
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
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)
|
{flwr_nightly-1.18.0.dev20250416.dist-info → flwr_nightly-1.18.0.dev20250417.dist-info}/RECORD
RENAMED
@@ -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=
|
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.
|
330
|
-
flwr_nightly-1.18.0.
|
331
|
-
flwr_nightly-1.18.0.
|
332
|
-
flwr_nightly-1.18.0.
|
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,,
|
{flwr_nightly-1.18.0.dev20250416.dist-info → flwr_nightly-1.18.0.dev20250417.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|