| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- # This module defines how additive secret sharing works in TinySMPC:
- # - how to create secret shares from a number
- # - how to reconstruct the number from the shares
- # - the internal Share class that represents a single secret share
- #
- # We use the simple additive secret sharing scheme that's compatible
- # with SPDZ. This is sort of a well-known "obvious" scheme, so has
- # no canonical citation [1].
- #
- # However, you can read more about it in [2] and [3].
- #
- # [1] https://crypto.stackexchange.com/questions/68666/reference-for-additive-secret-sharing
- # [2] https://mortendahl.github.io/2017/06/04/secret-sharing-part1/
- # [3] https://cs.nyu.edu/courses/spring07/G22.3033-013/scribe/lecture01.pdf
- from .fixed_point import fixed_point, float_point
- from .finite_ring import assert_is_element, mod, rand_element
- class Share():
- '''A class that represents a secret share that belongs to a machine.
- It supports ring arithmetic with other Shares or integers (+, -, *).'''
- def __init__(self, value, owner, Q=None):
- assert_is_element(value, Q)
- self.value = value
- self.owner = owner
- self.Q = Q
- owner.objects.append(self)
-
- def send_to(self, owner):
- '''Send a copy of a Share to a different owner/machine.'''
- return Share(self.value, owner, self.Q)
-
- def __add__(self, other):
- '''Called by: self + other.'''
- self._assert_can_operate(other)
- other_value = other if isinstance(other, int) else other.value
- sum_value = mod(self.value + other_value, self.Q)
- return Share(sum_value, self.owner, self.Q)
-
- def __radd__(self, other):
- '''Called by: other + self (when other is not a Share).'''
- return self.__add__(other)
-
- def __sub__(self, other):
- '''Called by: self - other.'''
- return self.__add__(-1*other)
-
- def __rsub__(self, other):
- '''Called by: other - self (when other is not a Share).'''
- return (-1*self).__add__(other)
-
- def __mul__(self, other):
- '''Called by: self * other.'''
- self._assert_can_operate(other)
- other_value = other if isinstance(other, int) else other.value
- prod_value = mod(self.value * other_value, self.Q)
- return Share(prod_value, self.owner, self.Q)
-
- def __rmul__(self, other):
- '''Called by: other * self (when other is not a Share).'''
- return self.__mul__(other)
- def __repr__(self):
- return f'Share({self.value}, \'{self.owner.name}\', Q={self.Q})'
-
- def _assert_can_operate(self, other):
- '''Assert that two Shares have the same owners and rings.'''
- if isinstance(other, int): return # It's okay to do operations with any public integers
- assert self.owner == other.owner, f'{self} and {other} do not have the same owners.'
- assert self.Q == other.Q, f'{self} and {other} are not over the same rings.'
- def n_to_shares(n, owners, Q=None):
- '''Create additive secret Shares for an integer n, split across a group of machines.'''
- # Make sure there are no duplicate owners (technically this is okay, but let's keep it simple)
- assert len(owners) == len(set(owners))
- # Make sure the number actually fits into the finite ring, so we can reconstruct it!
- assert_is_element(n, Q)
- # Generate the value of each secret share using additive secret sharing
- values = [rand_element(Q) for _ in owners[:-1]]
- values.append(mod(n - sum(values), Q))
-
- # Give one secret Share to each machine
- shares = [Share(value, owner, Q) for value, owner in zip(values, owners)]
-
- return shares
- def n_from_shares(shares, owner, Q=None):
- '''Given a list of additive secret Shares, reconstruct the integer value they're hiding.'''
- # First, move all shares onto one machine
- local_shares = [share.send_to(owner) for share in shares]
-
- # Now, reconstruct the original value (we just add the shares!)
- return sum(local_shares).value
|