viff

changeset 1491:5daa155c1d0b

BeDOZa: Partial implementation of triple generation for the BeDOZa protocol.
author Thomas P Jakobsen <tpj@cs.au.dk>
date Wed, 14 Jul 2010 13:32:10 +0200
parents e837d8eec15d
children 3627b0e969c9
files viff/bedoza_triple.py viff/test/test_bedoza_triple.py
diffstat 2 files changed, 578 insertions(+), 0 deletions(-) [+]
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/viff/bedoza_triple.py	Wed Jul 14 13:32:10 2010 +0200
     1.3 @@ -0,0 +1,287 @@
     1.4 +# Copyright 2010 VIFF Development Team.
     1.5 +#
     1.6 +# This file is part of VIFF, the Virtual Ideal Functionality Framework.
     1.7 +#
     1.8 +# VIFF is free software: you can redistribute it and/or modify it
     1.9 +# under the terms of the GNU Lesser General Public License (LGPL) as
    1.10 +# published by the Free Software Foundation, either version 3 of the
    1.11 +# License, or (at your option) any later version.
    1.12 +#
    1.13 +# VIFF is distributed in the hope that it will be useful, but WITHOUT
    1.14 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    1.15 +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
    1.16 +# Public License for more details.
    1.17 +#
    1.18 +# You should have received a copy of the GNU Lesser General Public
    1.19 +# License along with VIFF. If not, see <http://www.gnu.org/licenses/>.
    1.20 +
    1.21 +"""Triple generation for the BeDOZa protocol.
    1.22 +    TODO: Explain more.
    1.23 +"""
    1.24 +
    1.25 +from twisted.internet.defer import Deferred, gatherResults, succeed
    1.26 +
    1.27 +from viff.runtime import Runtime, Share, ShareList, gather_shares
    1.28 +from viff.field import FieldElement, GF
    1.29 +from viff.constants import TEXT
    1.30 +from viff.simplearithmetic import SimpleArithmetic
    1.31 +from viff.util import rand
    1.32 +
    1.33 +from bedoza import BeDOZaKeyList, BeDOZaMessageList, BeDOZaShare
    1.34 +
    1.35 +# TODO: Use secure random instead!
    1.36 +from random import Random
    1.37 +
    1.38 +from hash_broadcast import HashBroadcastMixin
    1.39 +
    1.40 +try:
    1.41 +    import pypaillier
    1.42 +except ImportError:
    1.43 +    # The pypaillier module is not released yet, so we cannot expect
    1.44 +    # the import to work.
    1.45 +    print "Error: The pypaillier module or one of the used functions " \
    1.46 +        "are not available."
    1.47 +
    1.48 +class Triple(object):
    1.49 +    def __init__(self, a, b, c):
    1.50 +        self.a, self.b, self.c = a, b, c
    1.51 +    def __str__(self):
    1.52 +        return "(%s,%s,%s)" % (self.a, self.b, self.c)
    1.53 +
    1.54 +
    1.55 +def _send(runtime, vals, serialize=str, deserialize=int):
    1.56 +    """Send vals[i] to player i + 1. Returns deferred list.
    1.57 +
    1.58 +    Works as default for integers. If other stuff has to be
    1.59 +    sent, supply another serialization, deserialition.
    1.60 +    """
    1.61 +    pc = tuple(runtime.program_counter)
    1.62 +    for p in runtime.players:
    1.63 +        msg = serialize(vals[p - 1])
    1.64 +        runtime.protocols[p].sendData(pc, TEXT, msg)
    1.65 +    def err_handler(err):
    1.66 +        print err
    1.67 +    values = []
    1.68 +    for p in runtime.players:
    1.69 +        d = Deferred()
    1.70 +        d.addCallbacks(deserialize, err_handler)
    1.71 +        runtime._expect_data(p, TEXT, d)
    1.72 +        values.append(d)
    1.73 +    result = gatherResults(values)
    1.74 +    return result
    1.75 +
    1.76 +def _convolute(runtime, val, serialize=str, deserialize=int):
    1.77 +    """As send, but sends the same val to all players."""
    1.78 +    return _send(runtime, [val] * runtime.num_players,
    1.79 +                 serialize=serialize, deserialize=deserialize)
    1.80 +
    1.81 +def _convolute_gf_elm(runtime, gf_elm):
    1.82 +    return _convolute(runtime, gf_elm,
    1.83 +                      serialize=lambda x: str(x.value),
    1.84 +                      deserialize=lambda x: gf_elm.field(int(x)))
    1.85 +
    1.86 +def _send_gf_elm(runtime, vals):
    1.87 +    return _send(runtime, vals, 
    1.88 +                 serialize=lambda x: str(x.value),
    1.89 +                 deserialize=lambda x: gf_elm.field(int(x)))
    1.90 +
    1.91 +
    1.92 +
    1.93 +
    1.94 +class PartialShareContents(object):
    1.95 +    """A BeDOZa share without macs, e.g. < a >.
    1.96 +    TODO: BeDOZaShare should extend this class?
    1.97 +    
    1.98 +    TODO: Should the partial share contain the public encrypted shares?
    1.99 +    TODO: It may be wrong to pass encrypted_shares to super constructor; 
   1.100 +      does it mean that the already public values get passed along on the
   1.101 +      network even though all players already posess them?
   1.102 +    """
   1.103 +    def __init__(self, value, enc_shares):
   1.104 +        self.value = value
   1.105 +        self.enc_shares = enc_shares
   1.106 +
   1.107 +    def __str__(self):
   1.108 +        return "PartialShareContents(%d; %s)" % (self.value, self.enc_shares)
   1.109 +
   1.110 +# In VIFF, callbacks get the *contents* of a share as input. Hence, in order
   1.111 +# to get a PartialShare as input to callbacks, we need this extra level of
   1.112 +# wrapper indirection.
   1.113 +class PartialShare(Share):
   1.114 +    def __init__(self, runtime, value, enc_shares):
   1.115 +        partial_share_contents = PartialShareContents(value, enc_shares)
   1.116 +        Share.__init__(self, runtime, value.field, partial_share_contents)
   1.117 +
   1.118 +
   1.119 +
   1.120 +class ModifiedPaillier(object):
   1.121 +    """See Ivan's paper, beginning of section 6."""
   1.122 +
   1.123 +    def __init__(self, runtime, random):
   1.124 +        self.runtime = runtime;
   1.125 +        self.random = random
   1.126 +
   1.127 +    def encrypt(self, value, player_id=None):
   1.128 +        """Encrypt using public key of player player_id. Defaults to own public key."""
   1.129 +        assert isinstance(value, int), \
   1.130 +            "paillier encrypts only integers, got %s" % value.__class__        
   1.131 +        # TODO: Assert value in the right range.
   1.132 +        
   1.133 +        if not player_id:
   1.134 +            player_id = self.runtime.id
   1.135 +
   1.136 +        pubkey = self.runtime.players[player_id].pubkey
   1.137 +
   1.138 +        randomness = self.random.randint(1, long(pubkey['n']))
   1.139 +        # TODO: Transform value.
   1.140 +        enc = pypaillier.encrypt_r(value, randomness, pubkey)
   1.141 +        return enc
   1.142 +
   1.143 +    def decrypt(self, enc_value):
   1.144 +        """Decrypt using own private key."""
   1.145 +        assert isinstance(enc_value, long), \
   1.146 +            "paillier decrypts only longs, got %s" % enc_value.__class__
   1.147 +        # TODO: Assert enc_value in the right range.
   1.148 +        seckey = self.runtime.players[self.runtime.id].seckey
   1.149 +        return pypaillier.decrypt(enc_value, seckey)
   1.150 +
   1.151 +
   1.152 +class TripleGenerator(object):
   1.153 +
   1.154 +    def __init__(self, runtime, p, random):
   1.155 +        assert p > 1
   1.156 +        self.random = random
   1.157 +        # TODO: Generate Paillier cipher with N_i sufficiently larger than p
   1.158 +        self.runtime = runtime
   1.159 +        self.p = p
   1.160 +        self.Zp = GF(p)
   1.161 +        self.k = self._bit_length_of(p)
   1.162 +
   1.163 +        paillier_random = Random(self.random.getrandbits(128))
   1.164 +        alpha_random = Random(self.random.getrandbits(128))
   1.165 +        self.paillier = ModifiedPaillier(runtime, paillier_random)
   1.166 +        
   1.167 +        # Debug output.
   1.168 +        #print "n_%d**2:%d" % (runtime.id, self.paillier.pubkey['n_square'])
   1.169 +        #print "n_%d:%d" % (runtime.id, self.paillier.pubkey['n'])
   1.170 +        #print "n_%d bitlength: %d" % (runtime.id, self._bit_length_of(self.paillier.pubkey['n']))
   1.171 +
   1.172 +        #self.Zp = GF(p)
   1.173 +        #self.Zn2 = GF(self.paillier.pubkey['n_square'])
   1.174 +        #self.alpha = self.Zp(self.random.randint(0, p - 1))
   1.175 +        self.alpha = alpha_random.randint(0, p - 1)
   1.176 +        self.n2 = runtime.players[runtime.id].pubkey['n_square']
   1.177 +
   1.178 +    def _bit_length_of(self, i):
   1.179 +        bit_length = 0
   1.180 +        while i:
   1.181 +            i >>= 1
   1.182 +            bit_length += 1
   1.183 +        return bit_length
   1.184 +
   1.185 +    def generate_triples(self, n):
   1.186 +        """Generates and returns a set of n triples.
   1.187 +        
   1.188 +        Data sent over the network is packaged in large hunks in order
   1.189 +        to optimize. TODO: Explain better.
   1.190 +
   1.191 +        TODO: This method needs to have enough RAM to represent all n
   1.192 +        triples in memory at the same time. Is there a nice way to
   1.193 +        stream this, e.g. by using Python generators?
   1.194 +        """
   1.195 +        triples = self._generate_passive_triples(n)
   1.196 +        # TODO: Do some ZK stuff.
   1.197 +
   1.198 +    def _generate_passive_triples(self, n):
   1.199 +        """Generates and returns a set of n passive tuples.
   1.200 +        
   1.201 +        E.g. where consistency is only guaranteed if all players follow the
   1.202 +        protool.
   1.203 +        """
   1.204 +        pass
   1.205 +    
   1.206 +    def _add_macs(self, partial_shares):
   1.207 +        """Adds macs to the set of PartialBeDOZaShares.
   1.208 +        
   1.209 +        Returns a list of full shares, e.g. including macs.
   1.210 +        (the full shares are deferreds of type BeDOZaShare.)
   1.211 +        """
   1.212 +        
   1.213 +        # TODO: Currently only does this for one partial share.
   1.214 +
   1.215 +        # TODO: Would be nice with a class ShareContents like the class
   1.216 +        # PartialShareContents used here.
   1.217 +        
   1.218 +        self.runtime.increment_pc() # Huh!?
   1.219 +
   1.220 +        mac_keys = []
   1.221 +
   1.222 +        i = 0
   1.223 +
   1.224 +        c_list = []
   1.225 +        for j in range(self.runtime.num_players):
   1.226 +            # TODO: This is probably not the fastes way to generate
   1.227 +            # the betas.
   1.228 +            beta = self.random.randint(0, 2**(4 * self.k))
   1.229 +        
   1.230 +            # TODO: Outcommented until mod paillier works for negative numbers.
   1.231 +            #if rand.choice([True, False]):
   1.232 +            #    beta = -beta
   1.233 +            
   1.234 +            enc_beta = self.paillier.encrypt(beta, player_id=j+1)
   1.235 +            c_j = partial_shares[i].enc_shares[j]
   1.236 +            c = (pow(c_j, self.alpha, self.n2) * enc_beta) % self.n2
   1.237 +            if self.runtime.id == 1 and j + 1 == 2:
   1.238 +                print
   1.239 +                print
   1.240 +                print "p=", self.p
   1.241 +                print "player 1: public key of player %s is %s" % (j+1, 
   1.242 +                                                                   self.runtime.players[j+1].pubkey)
   1.243 +                print "player %s encrypted share: %s" % (j+1, partial_shares[i].enc_shares[j])
   1.244 +                print "alpha = %s = %s" % (self.alpha, self.Zp(self.alpha))
   1.245 +                print "beta = %s = %s" %  (beta, self.Zp(beta))
   1.246 +                print "enc_beta =", enc_beta
   1.247 +                print "Player 1 sends c = %s to player %d" % (c, j+1)
   1.248 +                print
   1.249 +                print
   1.250 +            c_list.append(c)
   1.251 +            mac_keys.append(self.Zp(beta))
   1.252 +        received_cs = _send(self.runtime, c_list)
   1.253 +
   1.254 +        def finish_sharing(recevied_cs):
   1.255 +            mac_key_list = BeDOZaKeyList(self.alpha, mac_keys)
   1.256 +            # print "received cs:", received_cs.result
   1.257 +            decrypted_cs = [self.Zp(self.paillier.decrypt(c)) for c in received_cs.result]
   1.258 +            if self.runtime.id == 2:
   1.259 +                print
   1.260 +                print
   1.261 +                print "PLAYER2: Got %s from player 1" % received_cs.result[0]
   1.262 +                print "PLAYER2: Decrypted c from player 1: %s" % decrypted_cs[0]
   1.263 +                print "PLAYER2: My share a_2 =", partial_shares[0].value
   1.264 +                print
   1.265 +
   1.266 +            mac_msg_list = BeDOZaMessageList(decrypted_cs)
   1.267 +            # Twisted HACK: Need to pack share into tuple.
   1.268 +            return BeDOZaShare(self.runtime,
   1.269 +                               partial_shares[i].value.field,
   1.270 +                               partial_shares[i].value,
   1.271 +                               mac_key_list,
   1.272 +                               mac_msg_list),
   1.273 +
   1.274 +        self.runtime.schedule_callback(received_cs, finish_sharing)
   1.275 +        return [received_cs]
   1.276 +
   1.277 +        # for player i:
   1.278 +        #     receive c from player i and set 
   1.279 +        #         m^i=Decrypt(c)
   1.280 +    
   1.281 +    def _mul(self):
   1.282 +        pass
   1.283 +    
   1.284 +    def _full_mul(self):
   1.285 +        pass
   1.286 +
   1.287 +
   1.288 +# TODO: Represent all numbers by GF objects, Zp, Zn, etc.
   1.289 +# E.g. paillier encrypt should return Zn^2 elms and decrypt should
   1.290 +# return Zp elements.
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/viff/test/test_bedoza_triple.py	Wed Jul 14 13:32:10 2010 +0200
     2.3 @@ -0,0 +1,291 @@
     2.4 +# Copyright 2010 VIFF Development Team.
     2.5 +#
     2.6 +# This file is part of VIFF, the Virtual Ideal Functionality Framework.
     2.7 +#
     2.8 +# VIFF is free software: you can redistribute it and/or modify it
     2.9 +# under the terms of the GNU Lesser General Public License (LGPL) as
    2.10 +# published by the Free Software Foundation, either version 3 of the
    2.11 +# License, or (at your option) any later version.
    2.12 +#
    2.13 +# VIFF is distributed in the hope that it will be useful, but WITHOUT
    2.14 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    2.15 +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
    2.16 +# Public License for more details.
    2.17 +#
    2.18 +# You should have received a copy of the GNU Lesser General Public
    2.19 +# License along with VIFF. If not, see <http://www.gnu.org/licenses/>.
    2.20 +
    2.21 +import sys
    2.22 +
    2.23 +# We don't need secure random numbers for test purposes.
    2.24 +from random import Random
    2.25 +
    2.26 +from twisted.internet.defer import gatherResults, Deferred, DeferredList
    2.27 +
    2.28 +from viff.test.util import RuntimeTestCase, protocol
    2.29 +from viff.constants import TEXT
    2.30 +from viff.runtime import gather_shares, Share
    2.31 +from viff.config import generate_configs
    2.32 +from viff.bedoza import BeDOZaRuntime, BeDOZaShare, BeDOZaKeyList, BeDOZaMessageList
    2.33 +
    2.34 +from viff.bedoza_triple import TripleGenerator, PartialShare, PartialShareContents, ModifiedPaillier
    2.35 +from viff.bedoza_triple import _send, _convolute, _convolute_gf_elm
    2.36 +
    2.37 +from viff.field import FieldElement, GF
    2.38 +from viff.config import generate_configs
    2.39 +
    2.40 +
    2.41 +# Ok to use non-secure random generator in tests.
    2.42 +#from viff.util import rand
    2.43 +import random
    2.44 +
    2.45 +# The PyPaillier and commitment packages are not standard parts of VIFF so we
    2.46 +# skip them instead of letting them fail if the packages are not available. 
    2.47 +try:
    2.48 +    import pypaillier
    2.49 +except ImportError:
    2.50 +    pypaillier = None
    2.51 +
    2.52 +# HACK: The paillier keys that are available as standard in VIFF tests
    2.53 +# are not suited for use with pypaillier. Hence, we use NaClPaillier
    2.54 +# to generate test keys. This confusion will disappear when pypaillier
    2.55 +# replaces the current Python-based paillier implementation.
    2.56 +from viff.paillierutil import NaClPaillier
    2.57 +
    2.58 +# HACK^2: Currently, the NaClPaillier hack only works when triple is
    2.59 +# imported. It should ideally work without the triple package.
    2.60 +try:
    2.61 +    import tripple
    2.62 +except ImportError:
    2.63 +    tripple = None
    2.64 +
    2.65 +
    2.66 +
    2.67 +def _log(rt, msg):
    2.68 +    print "player%d ------> %s" % (rt.id, msg)
    2.69 +
    2.70 +
    2.71 +# TODO: Code duplication. There should be only one share generator, it should
    2.72 +# be placed along with the tests, and it should be able to generate partial
    2.73 +# as well as full bedoza shares.
    2.74 +class PartialShareGenerator:
    2.75 +
    2.76 +    def __init__(self, Zp, runtime, random, paillier):
    2.77 +        self.paillier = paillier
    2.78 +        self.Zp = Zp
    2.79 +        self.runtime = runtime
    2.80 +        self.random = random
    2.81 +
    2.82 +    def generate_share(self, value):
    2.83 +        r = [self.Zp(self.random.randint(0, self.Zp.modulus - 1)) # TODO: Exclusve?
    2.84 +             for _ in range(self.runtime.num_players - 1)]
    2.85 +        if self.runtime.id == 1:
    2.86 +            share = value - sum(r)
    2.87 +        else:
    2.88 +            share = r[self.runtime.id - 2]
    2.89 +        enc_share = self.paillier.encrypt(share.value)
    2.90 +        enc_shares = _convolute(self.runtime, enc_share)
    2.91 +        def create_partial_share(enc_shares, share):
    2.92 +            return PartialShare(self.runtime, share, enc_shares)
    2.93 +        self.runtime.schedule_callback(enc_shares, create_partial_share, share)
    2.94 +        return enc_shares
    2.95 +
    2.96 +class BeDOZaTestCase(RuntimeTestCase):
    2.97 +
    2.98 +    runtime_class = BeDOZaRuntime
    2.99 +
   2.100 +    # TODO: During test, we would like generation of Paillier keys to
   2.101 +    # be deterministic. How do we obtain that?
   2.102 +    def generate_configs(self, *args):
   2.103 +        # In production, paillier keys should be something like 2000
   2.104 +        # bit. For test purposes, it is ok to use small keys.
   2.105 +        # TODO: paillier freezes if key size is too small, e.g. 13.
   2.106 +        return generate_configs(paillier=NaClPaillier(70), *args)
   2.107 +
   2.108 +
   2.109 +class DataTransferTest(BeDOZaTestCase):
   2.110 +    num_players = 3
   2.111 +
   2.112 +    @protocol
   2.113 +    def test_convolute_int(self, runtime):
   2.114 +        res = _convolute(runtime, runtime.id)
   2.115 +        def verify(result):
   2.116 +            self.assertEquals(runtime.players.keys(), result)
   2.117 +        runtime.schedule_callback(res, verify)
   2.118 +        return res
   2.119 +
   2.120 +    @protocol
   2.121 +    def test_send(self, runtime):
   2.122 +        msg_send = [100 * p + runtime.id for p in runtime.players]
   2.123 +        msg_receive = [100 * runtime.id + p for p in runtime.players]
   2.124 +        res = _send(runtime, msg_send)
   2.125 +        def verify(result):
   2.126 +            self.assertEquals(msg_receive, result)
   2.127 +        runtime.schedule_callback(res, verify)
   2.128 +        return res
   2.129 + 
   2.130 +    @protocol
   2.131 +    def test_convolute_field_element(self, runtime):
   2.132 +        Zp = GF(17)
   2.133 +        res = _convolute_gf_elm(runtime, Zp(runtime.id))
   2.134 +        def verify(result):
   2.135 +            self.assertEquals(runtime.players.keys(), result)
   2.136 +        runtime.schedule_callback(res, verify)
   2.137 +        return res
   2.138 +
   2.139 +
   2.140 +class ModifiedPaillierTest(BeDOZaTestCase):
   2.141 +    num_players = 3
   2.142 +
   2.143 +    @protocol
   2.144 +    def test_modified_paillier_can_decrypt_encrypted_one(self, runtime):
   2.145 +        paillier = ModifiedPaillier(runtime, Random(234838))
   2.146 +        val = 1
   2.147 +        encrypted_val = paillier.encrypt(val)
   2.148 +        decrypted_val = paillier.decrypt(encrypted_val)
   2.149 +        self.assertEquals(val, decrypted_val)
   2.150 + 
   2.151 +    @protocol
   2.152 +    def test_modified_paillier_can_decrypt_encrypted_positive(self, runtime):
   2.153 +        paillier = ModifiedPaillier(runtime, Random(777737))
   2.154 +        val = 7
   2.155 +        encrypted_val = paillier.encrypt(val)
   2.156 +        decrypted_val = paillier.decrypt(encrypted_val)
   2.157 +        self.assertEquals(val, decrypted_val)
   2.158 +
   2.159 +    @protocol
   2.160 +    def test_modified_paillier_can_encrypt_to_other(self, runtime):
   2.161 +        paillier = ModifiedPaillier(runtime, Random(57503))
   2.162 +        msg = []
   2.163 +        for p in runtime.players:
   2.164 +            msg.append(paillier.encrypt(runtime.id, player_id=p))
   2.165 +        received = _send(runtime, msg)
   2.166 +        def verify(enc):
   2.167 +            plain = [paillier.decrypt(e) for e in enc]
   2.168 +            self.assertEquals(range(1, self.num_players + 1), plain)
   2.169 +        runtime.schedule_callback(received, verify)
   2.170 +        return received
   2.171 +        
   2.172 +
   2.173 +#    @protocol
   2.174 +#    def test_modified_paillier_can_decrypt_encrypted_negative(self, runtime):
   2.175 +#        pass
   2.176 +
   2.177 +    # TODO: Boundary tests.
   2.178 +
   2.179 +def partial_share(random, runtime, Zp, val, paillier=None):
   2.180 +    if not paillier:
   2.181 +        paillier_random = Random(random.getrandbits(128))
   2.182 +        paillier = ModifiedPaillier(runtime, paillier_random)
   2.183 +    share_random = Random(random.getrandbits(128))
   2.184 +    gen = PartialShareGenerator(Zp, runtime, share_random, paillier)
   2.185 +    return gen.generate_share(Zp(val))
   2.186 +
   2.187 +
   2.188 +class ParialShareGeneratorTest(BeDOZaTestCase):
   2.189 +    num_players = 3
   2.190 + 
   2.191 +    @protocol
   2.192 +    def test_shares_have_correct_type(self, runtime):
   2.193 +        Zp = GF(23)
   2.194 +        share = partial_share(Random(23499), runtime, Zp, 7)
   2.195 +        def test(share):
   2.196 +            self.assertEquals(Zp, share.value.field)
   2.197 +        runtime.schedule_callback(share, test)
   2.198 +        return share
   2.199 + 
   2.200 +    @protocol
   2.201 +    def test_shares_are_additive(self, runtime):
   2.202 +        secret = 7
   2.203 +        share = partial_share(Random(34993), runtime, GF(23), secret)
   2.204 +        def convolute(share):
   2.205 +            values = _convolute_gf_elm(runtime, share.value)
   2.206 +            def test_sum(vals):
   2.207 +                self.assertEquals(secret, sum(vals))
   2.208 +            runtime.schedule_callback(values, test_sum)
   2.209 +        runtime.schedule_callback(share, convolute)
   2.210 +        return share
   2.211 +
   2.212 +
   2.213 +    @protocol
   2.214 +    def test_encrypted_shares_decrypt_correctly(self, runtime):
   2.215 +        random = Random(3423993)
   2.216 +        modulus = 17
   2.217 +        secret = 7
   2.218 +        paillier = ModifiedPaillier(runtime, Random(random.getrandbits(128)))
   2.219 +        share = partial_share(Random(random.getrandbits(128)), runtime, GF(modulus), secret, paillier=paillier)
   2.220 +        def decrypt(share):
   2.221 +            decrypted_share = paillier.decrypt(share.enc_shares[runtime.id - 1])
   2.222 +            decrypted_shares = _convolute(runtime, decrypted_share)
   2.223 +            def test_sum(vals):
   2.224 +                self.assertEquals(secret, sum(vals) % modulus)
   2.225 +            runtime.schedule_callback(decrypted_shares, test_sum)
   2.226 +        runtime.schedule_callback(share, decrypt)
   2.227 +        return share
   2.228 +
   2.229 +
   2.230 +class TripleTest(BeDOZaTestCase): 
   2.231 +    num_players = 3
   2.232 +    
   2.233 +    @protocol
   2.234 +    def test_add_macs_produces_correct_sharing(self, runtime):
   2.235 +        # TODO: Here we use the open method of the BeDOZa runtime in
   2.236 +        # order to verify the macs of the generated full share. In
   2.237 +        # order to be more unit testish, this test should use its own
   2.238 +        # way of verifying these.
   2.239 +        p = 17
   2.240 +        secret = 9
   2.241 +        random = Random(283883)        
   2.242 +        triple_generator = TripleGenerator(runtime, p, random)
   2.243 +        paillier = triple_generator.paillier
   2.244 +        share = partial_share(random, runtime, GF(p), secret, paillier=paillier)
   2.245 +        def add_macs(share):
   2.246 +            full_share_list = triple_generator._add_macs([share])
   2.247 +            d = gatherResults(full_share_list)
   2.248 +            def foo(ls):
   2.249 +                # Twisted HACK: Need to unpack value ls[0] from tuple.
   2.250 +                opened_share = runtime.open(ls[0][0])
   2.251 +                def verify(open_share):
   2.252 +                    print "Share opened:", open_share
   2.253 +                    self.assertEquals(secret, open_share.value)
   2.254 +                runtime.schedule_callback(opened_share, verify)
   2.255 +                return opened_share
   2.256 +            d.addCallback(foo)
   2.257 +            return d
   2.258 +        runtime.schedule_callback(share, add_macs)
   2.259 +        return share
   2.260 +
   2.261 +    test_add_macs_produces_correct_sharing.skip = "add_macs not implemented fully yet"
   2.262 +
   2.263 +        
   2.264 +#    @protocol
   2.265 +#    def test_add_macs_preserves_value_of_sharing(self, runtime):
   2.266 +#        partial_share = self._generate_partial_share_of(42)
   2.267 +#        full_share = TripleGenerator()._add_macs(partial_share)
   2.268 +#        secret = self._open_sharing(full_share)
   2.269 +#        self.assertEquals(42, secret)
   2.270 +#        return partial_share
   2.271 +#    #test_add_macs_preserves_value_of_sharing.skip = "nyi"
   2.272 +#        
   2.273 +#    @protocol
   2.274 +#    def test_add_macs_preserves_value_of_zero_sharing(self, runtime):
   2.275 +#        partial_share = self._generate_partial_share_of(0)
   2.276 +#        full_share = TripleGenerator()._add_macs(partial_share)
   2.277 +#        secret = self._open_sharing(full_share)
   2.278 +#        self.assertEquals(0, secret)
   2.279 +#        return partial_share
   2.280 +#    #test_add_macs_preserves_value_of_zero_sharing.skip = "nyi"
   2.281 +# 
   2.282 +
   2.283 +missing_package = None
   2.284 +if not pypaillier:
   2.285 +    missing_package = "pypaillier"
   2.286 +if not tripple:
   2.287 +    missing_package = "tripple"
   2.288 +if missing_package:
   2.289 +    test_cases = [ModifiedPaillierTest,
   2.290 +                  PartialShareGeneratorTest,
   2.291 +                  TripleTest
   2.292 +                  ]
   2.293 +    for test_case in test_cases:
   2.294 +        test_case.skip =  "Skipped due to missing %s package." % missing_package