secret_sharing.py 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. # This module defines how additive secret sharing works in TinySMPC:
  2. # - how to create secret shares from a number
  3. # - how to reconstruct the number from the shares
  4. # - the internal Share class that represents a single secret share
  5. #
  6. # We use the simple additive secret sharing scheme that's compatible
  7. # with SPDZ. This is sort of a well-known "obvious" scheme, so has
  8. # no canonical citation [1].
  9. #
  10. # However, you can read more about it in [2] and [3].
  11. #
  12. # [1] https://crypto.stackexchange.com/questions/68666/reference-for-additive-secret-sharing
  13. # [2] https://mortendahl.github.io/2017/06/04/secret-sharing-part1/
  14. # [3] https://cs.nyu.edu/courses/spring07/G22.3033-013/scribe/lecture01.pdf
  15. from .fixed_point import fixed_point, float_point
  16. from .finite_ring import assert_is_element, mod, rand_element
  17. class Share():
  18. '''A class that represents a secret share that belongs to a machine.
  19. It supports ring arithmetic with other Shares or integers (+, -, *).'''
  20. def __init__(self, value, owner, Q=None):
  21. assert_is_element(value, Q)
  22. self.value = value
  23. self.owner = owner
  24. self.Q = Q
  25. owner.objects.append(self)
  26. def send_to(self, owner):
  27. '''Send a copy of a Share to a different owner/machine.'''
  28. return Share(self.value, owner, self.Q)
  29. def __add__(self, other):
  30. '''Called by: self + other.'''
  31. self._assert_can_operate(other)
  32. other_value = other if isinstance(other, int) else other.value
  33. sum_value = mod(self.value + other_value, self.Q)
  34. return Share(sum_value, self.owner, self.Q)
  35. def __radd__(self, other):
  36. '''Called by: other + self (when other is not a Share).'''
  37. return self.__add__(other)
  38. def __sub__(self, other):
  39. '''Called by: self - other.'''
  40. return self.__add__(-1*other)
  41. def __rsub__(self, other):
  42. '''Called by: other - self (when other is not a Share).'''
  43. return (-1*self).__add__(other)
  44. def __mul__(self, other):
  45. '''Called by: self * other.'''
  46. self._assert_can_operate(other)
  47. other_value = other if isinstance(other, int) else other.value
  48. prod_value = mod(self.value * other_value, self.Q)
  49. return Share(prod_value, self.owner, self.Q)
  50. def __rmul__(self, other):
  51. '''Called by: other * self (when other is not a Share).'''
  52. return self.__mul__(other)
  53. def __repr__(self):
  54. return f'Share({self.value}, \'{self.owner.name}\', Q={self.Q})'
  55. def _assert_can_operate(self, other):
  56. '''Assert that two Shares have the same owners and rings.'''
  57. if isinstance(other, int): return # It's okay to do operations with any public integers
  58. assert self.owner == other.owner, f'{self} and {other} do not have the same owners.'
  59. assert self.Q == other.Q, f'{self} and {other} are not over the same rings.'
  60. def n_to_shares(n, owners, Q=None):
  61. '''Create additive secret Shares for an integer n, split across a group of machines.'''
  62. # Make sure there are no duplicate owners (technically this is okay, but let's keep it simple)
  63. assert len(owners) == len(set(owners))
  64. # Make sure the number actually fits into the finite ring, so we can reconstruct it!
  65. assert_is_element(n, Q)
  66. # Generate the value of each secret share using additive secret sharing
  67. values = [rand_element(Q) for _ in owners[:-1]]
  68. values.append(mod(n - sum(values), Q))
  69. # Give one secret Share to each machine
  70. shares = [Share(value, owner, Q) for value, owner in zip(values, owners)]
  71. return shares
  72. def n_from_shares(shares, owner, Q=None):
  73. '''Given a list of additive secret Shares, reconstruct the integer value they're hiding.'''
  74. # First, move all shares onto one machine
  75. local_shares = [share.send_to(owner) for share in shares]
  76. # Now, reconstruct the original value (we just add the shares!)
  77. return sum(local_shares).value