|
| 1 | +""" |
| 2 | +The following Python implementation of Shamir's secret sharing is |
| 3 | +released into the Public Domain under the terms of CC0 and OWFa: |
| 4 | +https://creativecommons.org/publicdomain/zero/1.0/ |
| 5 | +http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0. |
| 6 | +
|
| 7 | +See the bottom few lines for usage. Tested on Python 2 and 3. |
| 8 | +""" |
| 9 | +from __future__ import annotations |
| 10 | + |
| 11 | +import functools |
| 12 | +import random |
| 13 | + |
| 14 | +_RINT = functools.partial(random.SystemRandom().randint, 0) |
| 15 | + |
| 16 | + |
| 17 | +def _eval_at(poly: list[int], x: int, prime: int) -> int: |
| 18 | + """ |
| 19 | + Evaluate polynomial (coefficient tuple) at x, used to generate a |
| 20 | + shamir pool in make_random_shares below. |
| 21 | + """ |
| 22 | + accum = 0 |
| 23 | + for coeff in reversed(poly): |
| 24 | + accum *= x |
| 25 | + accum += coeff |
| 26 | + accum %= prime |
| 27 | + |
| 28 | + return accum |
| 29 | + |
| 30 | + |
| 31 | +def _extended_gcd(a: int, b: int) -> int: |
| 32 | + """ |
| 33 | + Division in integers modulus p means finding the inverse of the |
| 34 | + denominator modulo p and then multiplying the numerator by this |
| 35 | + inverse (Note: inverse of A is B such that A*B % p == 1). This can |
| 36 | + be computed via the extended Euclidean algorithm |
| 37 | + http://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Computation. |
| 38 | + """ |
| 39 | + x = 0 |
| 40 | + last_x = 1 |
| 41 | + y = 1 |
| 42 | + last_y = 0 |
| 43 | + while b != 0: |
| 44 | + quot = a // b |
| 45 | + a, b = b, a % b |
| 46 | + x, last_x = last_x - quot * x, x |
| 47 | + y, last_y = last_y - quot * y, y |
| 48 | + |
| 49 | + return last_x, last_y |
| 50 | + |
| 51 | + |
| 52 | +def _divmod(num: int, den: int, p: int) -> int: |
| 53 | + """ |
| 54 | + Compute num / den modulo prime p. |
| 55 | +
|
| 56 | + To explain this, the result will be such that: |
| 57 | + den * _divmod(num, den, p) % p == num |
| 58 | + """ |
| 59 | + inv, _ = _extended_gcd(den, p) |
| 60 | + |
| 61 | + return num * inv |
| 62 | + |
| 63 | + |
| 64 | +def _lagrange_interpolate(x: int, x_s: list[int], y_s: list[int], p: int) -> int: |
| 65 | + """ |
| 66 | + Find the y-value for the given x, given n (x, y) points; |
| 67 | + k points will define a polynomial of up to kth order. |
| 68 | + """ |
| 69 | + k = len(x_s) |
| 70 | + if k != len(set(x_s)): |
| 71 | + msg = "points must be distinct" |
| 72 | + raise ValueError(msg) |
| 73 | + |
| 74 | + def _pi(vals: list[int]) -> int: # upper-case PI -- product of inputs |
| 75 | + accum = 1 |
| 76 | + for v in vals: |
| 77 | + accum *= v |
| 78 | + return accum |
| 79 | + |
| 80 | + nums = [] # avoid inexact division |
| 81 | + dens = [] |
| 82 | + for i in range(k): |
| 83 | + others = list(x_s) |
| 84 | + cur = others.pop(i) |
| 85 | + nums.append(_pi(x - o for o in others)) |
| 86 | + dens.append(_pi(cur - o for o in others)) |
| 87 | + |
| 88 | + den = _pi(dens) |
| 89 | + num = sum([_divmod(nums[i] * den * y_s[i] % p, dens[i], p) for i in range(k)]) |
| 90 | + |
| 91 | + return (_divmod(num, den, p) + p) % p |
| 92 | + |
| 93 | + |
| 94 | +def make_random_shares(secret: int, minimum: int, shares: int, prime: int) -> list[tuple[int, int]]: |
| 95 | + """Generate a random shamir pool for a given secret, returns share points.""" |
| 96 | + if minimum > shares: |
| 97 | + msg = "Pool secret would be irrecoverable." |
| 98 | + raise ValueError(msg) |
| 99 | + poly = [secret] + [_RINT(prime - 1) for i in range(minimum - 1)] |
| 100 | + return [(i, _eval_at(poly, i, prime)) for i in range(1, shares + 1)] |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | +def recover_secret(shares: list[tuple[int, int]], prime: int) -> int: |
| 105 | + """ |
| 106 | + Recover the secret from share points |
| 107 | + (points (x,y) on the polynomial). |
| 108 | + """ |
| 109 | + if len(shares) < 2: |
| 110 | + msg = "need at least two shares" |
| 111 | + raise ValueError(msg) |
| 112 | + |
| 113 | + x_s, y_s = zip(*shares) |
| 114 | + |
| 115 | + return _lagrange_interpolate(0, x_s, y_s, prime) |
0 commit comments