owenrusk.dev

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)