tally
shamir's secret sharing over gf(256).
git clone https://owenrusk.dev/tally.git
commit bfca0968cceb76b8d1602c13f8627017de3efd9f parent 24930610342c02da53780dd5aef52c76fb7e58da author Owen Rusk <owen@papermothgames.com> date 2024-06-12 21:05:17 -0500
shamir: split and combine, one byte at a time
| tally/shamir.py | +42 | -0 |
diff --git a/tally/shamir.py b/tally/shamir.py
new file mode 100644
index 0000000..4fb7bcb
--- /dev/null
+++ b/tally/shamir.py
@@ -0,0 +1,42 @@
+import secrets
+
+from . import gf256
+
+
+def _eval(coeffs: list[int], x: int) -> int:
+ # horner, coeffs[0] is the constant term
+ y = 0
+ for c in reversed(coeffs):
+ y = gf256.mul(y, x) ^ c
+ return y
+
+
+def split(secret: bytes, k: int, n: int) -> list[tuple[int, bytes]]:
+ if not 2 <= k <= n <= 255:
+ raise ValueError(f"need 2 <= k <= n <= 255, got k={k} n={n}")
+ shares = [bytearray() for _ in range(n)]
+ for byte in secret:
+ # a fresh polynomial for every byte, with the byte as its constant term
+ coeffs = [byte, *secrets.token_bytes(k - 1)]
+ for x in range(1, n + 1):
+ shares[x - 1].append(_eval(coeffs, x))
+ return [(x, bytes(ys)) for x, ys in enumerate(shares, 1)]
+
+
+def combine(shares: list[tuple[int, bytes]]) -> bytes:
+ xs = [x for x, _ in shares]
+ if len(set(xs)) != len(xs):
+ raise ValueError("two shares with the same x")
+ out = bytearray()
+ for i in range(len(shares[0][1])):
+ # lagrange at x = 0
+ acc = 0
+ for j, (xj, ys) in enumerate(shares):
+ num = den = 1
+ for m, xm in enumerate(xs):
+ if m != j:
+ num = gf256.mul(num, xm)
+ den = gf256.mul(den, xm ^ xj)
+ acc ^= gf256.mul(ys[i], gf256.div(num, den))
+ out.append(acc)
+ return bytes(out)