SecretSharing.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #
  2. # SecretSharing.py : distribute a secret amongst a group of participants
  3. #
  4. # ===================================================================
  5. #
  6. # Copyright (c) 2014, Legrandin <helderijs@gmail.com>
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions
  11. # are met:
  12. #
  13. # 1. Redistributions of source code must retain the above copyright
  14. # notice, this list of conditions and the following disclaimer.
  15. # 2. Redistributions in binary form must reproduce the above copyright
  16. # notice, this list of conditions and the following disclaimer in
  17. # the documentation and/or other materials provided with the
  18. # distribution.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  23. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  24. # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  25. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  26. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  27. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  30. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. # POSSIBILITY OF SUCH DAMAGE.
  32. # ===================================================================
  33. from Cryptodome.Util.py3compat import is_native_int
  34. from Cryptodome.Util import number
  35. from Cryptodome.Util.number import long_to_bytes, bytes_to_long
  36. from Cryptodome.Random import get_random_bytes as rng
  37. def _mult_gf2(f1, f2):
  38. """Multiply two polynomials in GF(2)"""
  39. # Ensure f2 is the smallest
  40. if f2 > f1:
  41. f1, f2 = f2, f1
  42. z = 0
  43. while f2:
  44. if f2 & 1:
  45. z ^= f1
  46. f1 <<= 1
  47. f2 >>= 1
  48. return z
  49. def _div_gf2(a, b):
  50. """
  51. Compute division of polynomials over GF(2).
  52. Given a and b, it finds two polynomials q and r such that:
  53. a = b*q + r with deg(r)<deg(b)
  54. """
  55. if (a < b):
  56. return 0, a
  57. deg = number.size
  58. q = 0
  59. r = a
  60. d = deg(b)
  61. while deg(r) >= d:
  62. s = 1 << (deg(r) - d)
  63. q ^= s
  64. r ^= _mult_gf2(b, s)
  65. return (q, r)
  66. class _Element(object):
  67. """Element of GF(2^128) field"""
  68. # The irreducible polynomial defining this field is 1+x+x^2+x^7+x^128
  69. irr_poly = 1 + 2 + 4 + 128 + 2 ** 128
  70. def __init__(self, encoded_value):
  71. """Initialize the element to a certain value.
  72. The value passed as parameter is internally encoded as
  73. a 128-bit integer, where each bit represents a polynomial
  74. coefficient. The LSB is the constant coefficient.
  75. """
  76. if is_native_int(encoded_value):
  77. self._value = encoded_value
  78. elif len(encoded_value) == 16:
  79. self._value = bytes_to_long(encoded_value)
  80. else:
  81. raise ValueError("The encoded value must be an integer or a 16 byte string")
  82. def __eq__(self, other):
  83. return self._value == other._value
  84. def __int__(self):
  85. """Return the field element, encoded as a 128-bit integer."""
  86. return self._value
  87. def encode(self):
  88. """Return the field element, encoded as a 16 byte string."""
  89. return long_to_bytes(self._value, 16)
  90. def __mul__(self, factor):
  91. f1 = self._value
  92. f2 = factor._value
  93. # Make sure that f2 is the smallest, to speed up the loop
  94. if f2 > f1:
  95. f1, f2 = f2, f1
  96. if self.irr_poly in (f1, f2):
  97. return _Element(0)
  98. mask1 = 2 ** 128
  99. v, z = f1, 0
  100. while f2:
  101. # if f2 ^ 1: z ^= v
  102. mask2 = int(bin(f2 & 1)[2:] * 128, base=2)
  103. z = (mask2 & (z ^ v)) | ((mask1 - mask2 - 1) & z)
  104. v <<= 1
  105. # if v & mask1: v ^= self.irr_poly
  106. mask3 = int(bin((v >> 128) & 1)[2:] * 128, base=2)
  107. v = (mask3 & (v ^ self.irr_poly)) | ((mask1 - mask3 - 1) & v)
  108. f2 >>= 1
  109. return _Element(z)
  110. def __add__(self, term):
  111. return _Element(self._value ^ term._value)
  112. def inverse(self):
  113. """Return the inverse of this element in GF(2^128)."""
  114. # We use the Extended GCD algorithm
  115. # http://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor
  116. if self._value == 0:
  117. raise ValueError("Inversion of zero")
  118. r0, r1 = self._value, self.irr_poly
  119. s0, s1 = 1, 0
  120. while r1 > 0:
  121. q = _div_gf2(r0, r1)[0]
  122. r0, r1 = r1, r0 ^ _mult_gf2(q, r1)
  123. s0, s1 = s1, s0 ^ _mult_gf2(q, s1)
  124. return _Element(s0)
  125. def __pow__(self, exponent):
  126. result = _Element(self._value)
  127. for _ in range(exponent - 1):
  128. result = result * self
  129. return result
  130. class Shamir(object):
  131. """Shamir's secret sharing scheme.
  132. A secret is split into ``n`` shares, and it is sufficient to collect
  133. ``k`` of them to reconstruct the secret.
  134. """
  135. @staticmethod
  136. def split(k, n, secret, ssss=False):
  137. """Split a secret into ``n`` shares.
  138. The secret can be reconstructed later using just ``k`` shares
  139. out of the original ``n``.
  140. Each share must be kept confidential to the person it was
  141. assigned to.
  142. Each share is associated to an index (starting from 1).
  143. Args:
  144. k (integer):
  145. The sufficient number of shares to reconstruct the secret (``k < n``).
  146. n (integer):
  147. The number of shares that this method will create.
  148. secret (byte string):
  149. A byte string of 16 bytes (e.g. the AES 128 key).
  150. ssss (bool):
  151. If ``True``, the shares can be used with the ``ssss`` utility.
  152. Default: ``False``.
  153. Return (tuples):
  154. ``n`` tuples. A tuple is meant for each participant and it contains two items:
  155. 1. the unique index (an integer)
  156. 2. the share (a byte string, 16 bytes)
  157. """
  158. #
  159. # We create a polynomial with random coefficients in GF(2^128):
  160. #
  161. # p(x) = \sum_{i=0}^{k-1} c_i * x^i
  162. #
  163. # c_0 is the encoded secret
  164. #
  165. coeffs = [_Element(rng(16)) for i in range(k - 1)]
  166. coeffs.append(_Element(secret))
  167. # Each share is y_i = p(x_i) where x_i is the public index
  168. # associated to each of the n users.
  169. def make_share(user, coeffs, ssss):
  170. idx = _Element(user)
  171. share = _Element(0)
  172. for coeff in coeffs:
  173. share = idx * share + coeff
  174. if ssss:
  175. share += _Element(user) ** len(coeffs)
  176. return share.encode()
  177. return [(i, make_share(i, coeffs, ssss)) for i in range(1, n + 1)]
  178. @staticmethod
  179. def combine(shares, ssss=False):
  180. """Recombine a secret, if enough shares are presented.
  181. Args:
  182. shares (tuples):
  183. The *k* tuples, each containin the index (an integer) and
  184. the share (a byte string, 16 bytes long) that were assigned to
  185. a participant.
  186. ssss (bool):
  187. If ``True``, the shares were produced by the ``ssss`` utility.
  188. Default: ``False``.
  189. Return:
  190. The original secret, as a byte string (16 bytes long).
  191. """
  192. #
  193. # Given k points (x,y), the interpolation polynomial of degree k-1 is:
  194. #
  195. # L(x) = \sum_{j=0}^{k-1} y_i * l_j(x)
  196. #
  197. # where:
  198. #
  199. # l_j(x) = \prod_{ \overset{0 \le m \le k-1}{m \ne j} }
  200. # \frac{x - x_m}{x_j - x_m}
  201. #
  202. # However, in this case we are purely interested in the constant
  203. # coefficient of L(x).
  204. #
  205. k = len(shares)
  206. gf_shares = []
  207. for x in shares:
  208. idx = _Element(x[0])
  209. value = _Element(x[1])
  210. if any(y[0] == idx for y in gf_shares):
  211. raise ValueError("Duplicate share")
  212. if ssss:
  213. value += idx ** k
  214. gf_shares.append((idx, value))
  215. result = _Element(0)
  216. for j in range(k):
  217. x_j, y_j = gf_shares[j]
  218. numerator = _Element(1)
  219. denominator = _Element(1)
  220. for m in range(k):
  221. x_m = gf_shares[m][0]
  222. if m != j:
  223. numerator *= x_m
  224. denominator *= x_j + x_m
  225. result += y_j * numerator * denominator.inverse()
  226. return result.encode()