finite_ring.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. # This module provides useful functions for operating on integers in a finite ring.
  2. #
  3. # (Any integer that is *shared* in TinySMPC must be an element of a finite ring.
  4. # By default, this is the int64 ring, but we also support modulus prime rings.)
  5. # Mathematical note:
  6. #
  7. # For additive secret sharing to work, we need all of the numbers we're working with
  8. # to be in a finite abelian group under addition. [1]
  9. #
  10. # Technically, for SMPC over additive secret sharing, we'd probably like to be able to
  11. # multiply integers as well, so we're actually operating in a ring.
  12. #
  13. # This is not a problem, because int64 is a finite ring! [2]
  14. #
  15. # Another popular choice of a finite abelian ring is the integers modulo a prime [3],
  16. # with the caveat that this doesn't support negative numbers. Thus, this implementation
  17. # defaults to using the int64 ring. We support prime rings as well, which are explicitly
  18. # used in the PrivateCompare algorithm.
  19. #
  20. # [1] 6.1 in https://cs.nyu.edu/courses/spring07/G22.3033-013/scribe/lecture01.pdf
  21. # [2] https://math.stackexchange.com/q/3692052/28855
  22. # [3] https://mortendahl.github.io/2017/09/03/the-spdz-protocol-part1/
  23. from random import randint, randrange
  24. # Anywhere in the codebase, if Q is None, that means we're computing with int64s!
  25. # This is the default behavior. (See the mathematical note above for why.)
  26. MAX_INT64 = 9223372036854775807
  27. MIN_INT64 = -9223372036854775808
  28. def mod(n, Q=None):
  29. '''Keeps n inside the finite ring. That is:
  30. - If we're in a prime ring (Q is the prime size), modulo it by Q
  31. - If we're in the int64 ring, do the normal int64 overflow behavior
  32. (we need to explicitly overflow since Python3 ints are unbounded)
  33. '''
  34. if Q is not None: return n % Q
  35. return (n + MAX_INT64 + 1) % 2**64 - (MAX_INT64 + 1) # https://stackoverflow.com/a/7771499/908744
  36. def rand_element(Q=None):
  37. '''Generates a random int64, or a random integer [0, Q) if Q is specified.
  38. i.e. an element of the int64 ring, or the size-Q prime ring.'''
  39. if Q is not None: return randrange(Q)
  40. return randint(MIN_INT64, MAX_INT64)
  41. def assert_is_element(n, Q=None):
  42. '''Assert that n is a valid int64, or a valid integer mod Q, if Q is provided.'''
  43. val = n if isinstance(n, int) else n.value
  44. if Q is None:
  45. assert MIN_INT64 <= val <= MAX_INT64, f'{n} is not an int64 and cannot be reconstructed. Use a smaller value.'
  46. else:
  47. assert 0 <= val < Q, f'{n} does not fit inside a size-{Q} prime ring, so it cannot be split into shares that can be reconstructed. Use a larger Q or a smaller value.'