Sfoglia il codice sorgente

3.2 section added and garbage removed

aggstam 4 anni fa
parent
commit
4f3dd6bd64

+ 63 - 0
script/research/streamlet/3.2-blocks-and-blockchain.py

@@ -0,0 +1,63 @@
+# Section 3.2 from "Streamlet: Textbook Streamlined Blockchains"
+
+class Block:
+	''' This class represents a tuple of the form (h, e, txs).
+		Each blocks parent hash h may be computed simply as a hash of the parent block. '''
+	def __init__(self, h, e, txs):
+		self.h = h # parent hash
+		self.e = e # epoch number
+		self.txs = txs # transactions payload
+	
+	def __repr__(self):
+		return "Block=[h={0}, e={1}, txs={2}]".format(self.h, self.e, self.txs)
+	
+	def __hash__(self):
+		return hash((self.h, self.e, self.txs)) # python hash is used for demostranation porpuses only.
+
+class Blockchain:
+	''' This class represents a sequence of blocks starting with the genesis block. '''
+	def __init__(self, genesis_block):
+		self.chain = [genesis_block]
+	
+	def __repr__(self):
+		return "Blockchain=[chain={0}]".format(self.chain)
+	
+	''' A block is considered valid when its parent hash is equal to the hash of the 
+		previous block and their epochs are incremental, exluding genesis. '''
+	def check_block_validity(self, block, previous_block):
+		assert(block.h != '⊥') # genesis block check
+		assert(block.h == hash(previous_block))
+		assert(block.e > previous_block.e)
+
+	''' A blockchain is considered valid, when every block is valid, based on check_block_validity method. '''
+	def check_chain_validity(self):
+		for index, block in enumerate(self.chain[1:]):
+			self.check_block_validity(block, self.chain[index])
+	
+	''' Insertion of a valid block. '''	
+	def add_block(self, block):		
+		self.check_block_validity(block, self.chain[-1])
+		self.chain.append(block)
+
+
+# We generate a genesis block and a blockchain.
+genesis_block = Block("⊥", 0, '⊥')
+chain = Blockchain(genesis_block)
+
+# A new block is generated and appended to the blockchain, since its valid.
+block1 = Block(hash(genesis_block), 1, "'tx1','tx2','tx3'")
+chain.add_block(block1)
+
+# A new block is generated and appended to the blockchain, since its valid.
+block2 = Block(hash(block1), 2, "tx4,tx5,tx6")
+chain.add_block(block2)
+
+# We check entire blockchain validity.
+chain.check_chain_validity()
+
+# Following code examples will fail, due to block validity checks:
+# wrong_block = Block(hash(block1), 3, "tx4,tx5,tx6")
+# chain.add_block(wrong_block)
+
+# wrong_block = Block(hash(block1), 1, "tx4,tx5,tx6") # 
+# chain.add_block(wrong_block)

+ 0 - 66
script/research/streamlet/streamlet.py

@@ -1,66 +0,0 @@
-from tinysmpc import VirtualMachine, PrivateScalar, SharedScalar
-
-# Generating generals
-general0 = VirtualMachine('general0')
-general1 = VirtualMachine('general1')
-general2 = VirtualMachine('general2')
-general3 = VirtualMachine('general3')
-
-# Using a simple number to represent the block for testing purposes
-print('General 0 is the leader and shares block 42...')
-block = PrivateScalar(42, general0)
-shared_block = block.share([general0, general1, general2, general3])
-print(general0)
-print(general1)
-print(general2)
-print(general3)
-print()
-
-# 1 Stands for vote for, 0 for vote against
-print('Generals vote on the block...')
-general0_vote = PrivateScalar(1, general0)
-general1_vote = PrivateScalar(1, general1)
-general2_vote = PrivateScalar(1, general2)
-general3_vote = PrivateScalar(0, general3)
-shared_general0_vote = general0_vote.share([general0, general1, general2, general3])
-shared_general1_vote = general1_vote.share([general0, general1, general2, general3])
-shared_general2_vote = general2_vote.share([general0, general1, general2, general3])
-shared_general3_vote = general3_vote.share([general0, general1, general2, general3])
-print(general0)
-print(general1)
-print(general2)
-print(general3)
-print()
-
-# Each general sums votes to notarize block if votes exceed 2n/3
-print('Generals check votes...')
-votes_thresshold = (2*4)/3
-generals_votes_sum = shared_general0_vote + shared_general1_vote + shared_general2_vote + shared_general3_vote
-
-general0_votes_sum = generals_votes_sum.reconstruct(general0)
-print('General 0 votes sum: {0}'.format(general0_votes_sum.value))
-if (general0_votes_sum.value > votes_thresshold):
-    print('General 0 will notarize block')
-else:
-    print('General 0 will not notarize block')
-       
-general1_votes_sum = generals_votes_sum.reconstruct(general1)
-print('General 1 votes sum: {0}'.format(general1_votes_sum.value))
-if (general1_votes_sum.value > votes_thresshold):
-    print('General 1 will notarize block')
-else:
-    print('General 1 will not notarize block')
-
-general2_votes_sum = generals_votes_sum.reconstruct(general2)
-print('General 2 votes sum: {0}'.format(general2_votes_sum.value))
-if (general2_votes_sum.value > votes_thresshold):
-    print('General 2 will notarize block')
-else:
-    print('General 2 will not notarize block')
-
-general3_votes_sum = generals_votes_sum.reconstruct(general3)
-print('General 3 votes sum: {0}'.format(general3_votes_sum.value))
-if (general3_votes_sum.value > votes_thresshold):
-    print('General 3 will notarize block')
-else:
-    print('General 3 will not notarize block')

+ 0 - 4
script/research/streamlet/tinysmpc/__init__.py

@@ -1,4 +0,0 @@
-from .tinysmpc import VirtualMachine, PrivateScalar, SharedScalar
-
-__all__ = ['VirtualMachine', 'PrivateScalar', 'SharedScalar']
-__title__ = 'tinysmpc'

+ 0 - 53
script/research/streamlet/tinysmpc/finite_ring.py

@@ -1,53 +0,0 @@
-# This module provides useful functions for operating on integers in a finite ring.
-#
-# (Any integer that is *shared* in TinySMPC must be an element of a finite ring.
-#  By default, this is the int64 ring, but we also support modulus prime rings.)
-
-# Mathematical note:
-#
-# For additive secret sharing to work, we need all of the numbers we're working with
-# to be in a finite abelian group under addition. [1] 
-# 
-# Technically, for SMPC over additive secret sharing, we'd probably like to be able to 
-# multiply integers as well, so we're actually operating in a ring.
-# 
-# This is not a problem, because int64 is a finite ring! [2]
-# 
-# Another popular choice of a finite abelian ring is the integers modulo a prime [3], 
-# with the caveat that this doesn't support negative numbers. Thus, this implementation
-# defaults to using the int64 ring. We support prime rings as well, which are explicitly
-# used in the PrivateCompare algorithm.
-#
-# [1] 6.1 in https://cs.nyu.edu/courses/spring07/G22.3033-013/scribe/lecture01.pdf
-# [2] https://math.stackexchange.com/q/3692052/28855
-# [3] https://mortendahl.github.io/2017/09/03/the-spdz-protocol-part1/
-
-from random import randint, randrange
-
-# Anywhere in the codebase, if Q is None, that means we're computing with int64s!
-# This is the default behavior. (See the mathematical note above for why.)
-MAX_INT64 =  9223372036854775807
-MIN_INT64 = -9223372036854775808
-
-def mod(n, Q=None):
-    '''Keeps n inside the finite ring. That is:
-         - If we're in a prime ring (Q is the prime size), modulo it by Q
-         - If we're in the int64 ring, do the normal int64 overflow behavior
-           (we need to explicitly overflow since Python3 ints are unbounded)
-    '''
-    if Q is not None: return n % Q
-    return (n + MAX_INT64 + 1) % 2**64 - (MAX_INT64 + 1)  # https://stackoverflow.com/a/7771499/908744
-    
-def rand_element(Q=None):
-    '''Generates a random int64, or a random integer [0, Q) if Q is specified.
-       i.e. an element of the int64 ring, or the size-Q prime ring.'''
-    if Q is not None: return randrange(Q)
-    return randint(MIN_INT64, MAX_INT64)
-
-def assert_is_element(n, Q=None):
-    '''Assert that n is a valid int64, or a valid integer mod Q, if Q is provided.'''
-    val = n if isinstance(n, int) else n.value
-    if Q is None: 
-        assert MIN_INT64 <= val <= MAX_INT64, f'{n} is not an int64 and cannot be reconstructed. Use a smaller value.'
-    else:
-        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.'

+ 0 - 20
script/research/streamlet/tinysmpc/fixed_point.py

@@ -1,20 +0,0 @@
-# This module defines the conversion functions from float <> int, 
-# so that we can use floats in TinySMPC.
-
-from .finite_ring import MAX_INT64, MIN_INT64
-
-PRECISION = 8
-MAX_FLOAT = MAX_INT64 / 10**PRECISION  # 92233720368.54776 (floats must be <, not <= this value, due to precision issues)
-MIN_FLOAT = MIN_INT64 / 10**PRECISION  # -92233720368.54776 (floats must be >, not >= this value, due to precision issues)
-
-def fixed_point(fl):
-    '''Converts a float to an fixed point int, with PRECISION decimal points of precision.'''
-    assert MIN_FLOAT < fl < MAX_FLOAT
-    return int(fl * 10**PRECISION)
-
-def float_point(n, n_mults=0):
-    '''Converts a fixed point integer to a float.
-       n_mults is the number of multiplications that generated the int, since multiplications
-       of fixed point integers will accumulate extra scaling factors.'''
-    scale_factor = (10**PRECISION)**n_mults
-    return n / 10**PRECISION / scale_factor

+ 0 - 95
script/research/streamlet/tinysmpc/secret_sharing.py

@@ -1,95 +0,0 @@
-# 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

+ 0 - 31
script/research/streamlet/tinysmpc/shared_addition.py

@@ -1,31 +0,0 @@
-# This module defines addition on SharedScalars, using the SPDZ algorithm 
-# for addition [1].
-#
-# Technically, this method is extremely simple as it follows directly 
-# from additive secret sharing, and likely predates SPDZ.
-#
-# [1] "Computations" on pg 6 of https://eprint.iacr.org/2011/535.pdf
-
-# Small hack:
-# 
-# We can't import the SharedScalar class in this module as that would
-# create a circular dependency. 
-# 
-# However, we'd obviously still like to be able to construct new 
-# SharedScalars here when doing arithmetic. To be able to do so, 
-# we can use `type(sh)` to get access to the SharedScalar class &
-# constructor.
-
-def add_2sh(sh1, sh2):
-    '''Implements addition on two SharedScalars.'''
-    # To do the addition, we add each machine's shares together
-    sh1._assert_can_operate(sh2)
-    sum_shares = [sh1.share_of[owner] + sh2.share_of[owner]
-                  for owner in sh1.owners]
-    return type(sh1)(sum_shares, Q=sh1.Q)
-
-def add_sh_pub(sh, pub):
-    '''Implements addition on a SharedScalar and a public integer.'''
-    # To do the addition, we add the integer to one (random) share only
-    new_shares = [sh.shares[0] + pub] + sh.shares[1:]
-    return type(sh)(new_shares, sh.Q)

+ 0 - 148
script/research/streamlet/tinysmpc/shared_comparison.py

@@ -1,148 +0,0 @@
-# This module defines comparison between a SharedScalar and public integer,
-# using the PrivateCompare algorithm in SecureNN [1]. 
-#
-# The notation used here is as close to the paper's as possible.
-#
-# [1] Algorithm 3 in https://eprint.iacr.org/2018/442.pdf
-
-# Security note:
-#
-# The PrivateCompare algorithm [1] requires a bitwise share representation.
-# However, this is not the share representation of SharedScalars, so we use
-# the workaround of reconstructing the private value on a temporarily created
-# VirtualMachine, and then resharing with the bitwise representation.
-# 
-# Technically speaking, this isn't really secure. However, it's still useful
-# for educational purposes, and enables a nice high-level API like `x > 10`,
-# where x is any normal SharedScalar (even the output of an arithmetic op). 
-# 
-# I'd like to implement a better solution, eventually. Here are the options:
-#   1) Be able to convert from SharedScalar's Shares -> bitwise Shares directly.
-#      ^I don't know if this is possible.
-#   2) Make SharedScalar have two share representations. The normal/current one,
-#      and a bitwise one. And update all arithmetic operations to support the 
-#      bitwise sharing scheme.
-#      ^This would add too much complexity.
-#
-# Alternatively, you can also directly use _share_bitwise() and _private_compare()
-# from this module on unshared integers to generate fresh bitwise shares.
-
-# Small hack:
-#
-# In the other shared_* modules, we use the `type(sh)` hack. However,
-# PrivateCompare requires fairly heavy operations on Shares, SharedScalars, 
-# etc, so we instead import these classes at function runtime.
-# 
-# Personally, I don't like this style, but it's the price to pay for modularity.
-# (Dependency-wise, these functions should really be part of tinysmpc.py, 
-#  but it's so much cleaner to split them out.)
-
-from .finite_ring import MIN_INT64
-from .secret_sharing import Share
-from random import random, randint, shuffle
-
-P = 67  # Smaller prime field size to encode bit values
-L = 64  # Number of bits of the integers we're using
-
-def greater_than(x_sh, pub):
-    '''Provides the high-level API for comparing x_sh (SharedScalar) > pub (int).
-       This basically does some TinySMPC-specific setup before calling PrivateCompare.'''
-    assert len(x_sh.owners) == 2, 'PrivateCompare only works for 2-party shares'
-    
-    # Reconstruct the private value on a temporary VM (see the Security Note above)
-    from .tinysmpc import VirtualMachine
-    tmp_vm = VirtualMachine('tmp_vm')
-    x = x_sh.reconstruct(tmp_vm).value
-    
-    # The paper's implementation only works on positive numbers, but we want negatives too!
-    # So, just shift TinySMPC's int64s into the positive range (int64 + -MIN_INT64).
-    if pub < 0 or x < 0: pub += -MIN_INT64; x += -MIN_INT64
-    
-    # Decompose x into its bit representation, and share each bit independently
-    x_sh = _share_bitwise(x, list(x_sh.owners))
-    
-    return _private_compare(x_sh, pub)
-
-def _private_compare(x_sh, r, β=None):
-    '''Compares x_sh > r, where x_sh is bitwise shared and r is a public integer.
-       Returns 0 or 1 as a PrivateScalar on a temporary VirtualMachine.
-       This is the PrivateCompare algorithm in [1].'''
-    # A necessary evil; see the "small hack" note above
-    from .tinysmpc import PrivateScalar, SharedScalar, VirtualMachine
-
-    # Decompose r into its bit representation (public)
-    rb = _get_bits(r)
-    
-    # Common randomness (public)
-    β = randint(0, 1) if β is None else β
-    s = _randlist()
-    u = _randlist()
-    π = _fixed_shuffle()
-
-    # Line 1
-    t = (r + 1) % 2**L
-    tb = _get_bits(t)
-    
-    # Line 2
-    p0, p1 = tuple(x_sh[0].owners)
-    w_c = {p0: {'w': [None] * L, 'c': [None] * L}, 
-           p1: {'w': [None] * L, 'c': [None] * L}}
-    for j, machine in enumerate([p0, p1]):  
-        w, c = w_c[machine]['w'], w_c[machine]['c']
-        
-        # Line 3
-        for i in range(L-1, -1, -1):
-            sh = x_sh[i].share_of[machine]
-            
-            # Line 4
-            if β == 0:
-                w[i] = sh + j*rb[i] - 2*rb[i]*sh
-                c[i] = j*rb[i] - sh + j + sum(w[i+1:])
-
-            # Line 7
-            elif (β == 1) and (r != 2**L - 1):
-                w[i] = sh + j*tb[i] - 2*tb[i]*sh
-                c[i] = -1*j*tb[i] + sh + j + sum(w[i+1:])
-
-            # Line 10
-            else:  
-                if i != 1:  c_val = ((1 - j)*(u[i] + 1) - j*u[i]) % P
-                else: c_val = ((-1)**j * u[i]) % P
-                c[i] = Share(c_val, machine, Q=P)
-
-    # Line 14
-    d_p0 = [s[i] * w_c[p0]['c'][i] for i in range(L)]
-    d_p1 = [s[i] * w_c[p1]['c'][i] for i in range(L)]
-    π(d_p0); π(d_p1)
-    d_shared = [SharedScalar([d0, d1], Q=P) for d0, d1 in zip(d_p0, d_p1)]
-    
-    # Line 15
-    p2 = VirtualMachine('p2')
-    d = [d_sh.reconstruct(p2) for d_sh in d_shared]
-    β_prime = any(ps.value == 0 for ps in d)  # (we break the abstraction of only operating on PrivateScalars a bit)
-        
-    # Return x > r
-    return PrivateScalar(β ^ β_prime, p2)    
-    
-def _share_bitwise(n, machines):
-    '''Split integer n into bitwise secret shares, returns a list of SharedScalars (one per bit).'''
-    from .tinysmpc import PrivateScalar
-    bits = _get_bits(n)
-    ps_bits = [PrivateScalar(bit, machines[0]) for bit in bits]
-    sh_bits = [ps_bit.share(machines, P) for ps_bit in ps_bits]
-    return sh_bits
-
-def _get_bits(n):
-    '''Returns the (reverse) binary representation of n as an L-sized list.'''
-    bits = bin(n).replace('0b', '')
-    bits = '0' * (L - len(bits)) + bits
-    return list(map(int, reversed(bits)))  # FYI: the paper requires reversed binary, but doesn't say this!
-
-def _randlist():
-    '''Returns a list of L random integers in [1, P-1].'''
-    return [randint(1, P-1) for _ in range(L)]
-
-def _fixed_shuffle():
-    '''Returns a deterministic shuffle function that always permutes a list in the same way.'''
-    seed = random()
-    return lambda x: shuffle(x, lambda: seed)

+ 0 - 54
script/research/streamlet/tinysmpc/shared_multiplication.py

@@ -1,54 +0,0 @@
-# This module defines multiplication on SharedScalars, using the SPDZ 
-# algorithm for multiplication [1].
-#
-# [1] https://bristolcrypto.blogspot.com/2016/10/what-is-spdz-part-2-circuit-evaluation.html
-
-# Small hack:
-# 
-# We can't import the SharedScalar class in this module as that would
-# create a circular dependency. 
-# 
-# However, we'd obviously still like to be able to construct new 
-# SharedScalars here when doing arithmetic. To be able to do so, 
-# we can use `type(sh)` to get access to the SharedScalar class &
-# constructor.
-
-from .finite_ring import mod, rand_element
-from .secret_sharing import n_to_shares
-from random import choice
-
-def mult_2sh(sh1, sh2):
-    '''Implements multiplication on two SharedScalars.'''
-    # Make sure that these two SharedScalars are compatible 
-    sh1._assert_can_operate(sh2)
-    
-    # Generate a random multiplication triple (public)
-    a, b = rand_element(sh1.Q), rand_element(sh1.Q)
-    c = mod(a * b, sh1.Q)
-
-    # Share the triple across all machines
-    # (It'd be nicer to use the higher-level PrivateScalar.share() here, 
-    # but we don't have access to PrivateScalar in this module.)
-    machines = list(sh1.owners)
-    shared_a = type(sh1)(n_to_shares(a, machines, sh1.Q), sh1.Q)
-    shared_b = type(sh1)(n_to_shares(b, machines, sh1.Q), sh1.Q)
-    shared_c = type(sh1)(n_to_shares(c, machines, sh1.Q), sh1.Q)
-
-    # Compute sh1 - a, sh2 - b (shared)
-    shared_sh1_m_a = sh1 - shared_a
-    shared_sh2_m_b = sh2 - shared_b
-
-    # Reconstruct sh1 - a, sh2 - b (public)
-    rand_machine = choice(machines)
-    sh1_m_a = shared_sh1_m_a.reconstruct(rand_machine).value
-    sh2_m_b = shared_sh2_m_b.reconstruct(rand_machine).value
-
-    # Magic! Compute each machine's share of the product
-    shared_prod = shared_c + (sh1_m_a * shared_b) + (sh2_m_b * shared_a) + (sh1_m_a * sh2_m_b)
-    return shared_prod
-
-def mult_sh_pub(sh, pub):
-    '''Implements multiplication on a SharedScalar and a public integer.'''
-    # To do the multiplication, we multiply the integer with all shares
-    prod_shares = [share * pub for share in sh.shares]
-    return type(sh)(prod_shares, Q=sh.Q)

+ 0 - 97
script/research/streamlet/tinysmpc/tinysmpc.py

@@ -1,97 +0,0 @@
-# This is TinySMPC's top-level module that defines its user-facing API:
-# the three classes VirtualMachine, PrivateScalar, and SharedScalar.
-#
-# For modularity, almost all of the behavior of these classes is implemented 
-# in functions imported from the other files here. Check them out!
-
-from .finite_ring import assert_is_element, mod, rand_element
-from .secret_sharing import n_from_shares, n_to_shares
-from .shared_addition import add_2sh, add_sh_pub
-from .shared_comparison import greater_than
-from .shared_multiplication import mult_2sh, mult_sh_pub
-
-class VirtualMachine():
-    '''A very simple class that represents a machine's data. 
-       It just has a name and owns objects (PrivateScalars and Shares).'''
-    def __init__(self, name):
-        self.name = name
-        self.objects = []
-    
-    def __repr__(self):
-        return f'VirtualMachine(\'{self.name}\')\n - ' + '\n - '.join(map(str, self.objects))
-
-class PrivateScalar():
-    '''A class that represents a secret number that belongs to a machine.'''
-    def __init__(self, value, owner):
-        self.value = value
-        self.owner = owner
-        owner.objects.append(self)
-
-    def share(self, machines, Q=None):
-        '''Split self.value into secret shares and distribute them across machines (tracked in a SharedScalar).'''
-        shares = n_to_shares(self.value, machines, Q)
-        return SharedScalar(shares, Q)
-    
-    def __repr__(self):
-        return f'PrivateScalar({self.value}, \'{self.owner.name}\')'
-    
-class SharedScalar():
-    '''A class that tracks all secret shares that corresponds to one PrivateScalar.
-       It supports *secure* arithmetic with other SharedScalars or integers (+, -, *).'''
-    def __init__(self, shares, Q=None):
-        assert all(share.Q == Q for share in shares)
-        self.shares = shares
-        self.share_of = {share.owner: share for share in shares}
-        self.owners = {share.owner for share in shares}
-        self.Q = Q
-        
-    def reconstruct(self, owner):
-        '''Send all shares to one machine, and reconstruct the hidden value as a PrivateScalar.'''
-        value = n_from_shares(self.shares, owner, self.Q)
-        return PrivateScalar(value, owner)
-        
-    def __add__(self, other):
-        '''Called by: self + other.'''
-        if isinstance(other, int):            return add_sh_pub(self, other)
-        elif isinstance(other, SharedScalar): return add_2sh(self, other)
-        
-    def __radd__(self, other):
-        '''Called by: other + self (when other is not a SharedScalar).'''
-        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 SharedScalar).'''
-        return (-1*self).__add__(other)
-    
-    def __mul__(self, other):
-        '''Called by: self * other.'''
-        if isinstance(other, int):            return mult_sh_pub(self, other)
-        elif isinstance(other, SharedScalar): return mult_2sh(self, other)
-            
-    def __rmul__(self, other):
-        '''Called by: other * self (when other is not a SharedScalar).'''
-        return self.__mul__(other)
-    
-    def __pow__(self, other):
-        '''Called by: self ** other. Only implemented when other is a public integer > 0.'''
-        assert isinstance(other, int) and other > 0
-        res = self
-        for _ in range(other-1): res *= self
-        return res
-    
-    def __gt__(self, other):
-        '''Called by: self > other. Only implemented when other is a public integer.'''
-        assert isinstance(other, int)
-        return greater_than(self, other)
-    
-    def __repr__(self):
-        return 'SharedScalar\n - ' + '\n - '.join(map(str, self.shares))
-    
-    def _assert_can_operate(self, other):
-        '''Assert that two SharedScalars have the same owners and rings.'''
-        assert self.owners == other.owners, f'{self}\nand\n{other}\ndo not have the same owners.'
-        assert self.Q == other.Q, f'{self}\nand\n{other}\nare not over the same rings.'