viff

view viff/bedoza/bedoza_triple.py @ 1574:0d3b99e1e3eb

BeDOZa: Connected zero-knowledge proof to the remaining protocol.
author Thomas P Jakobsen <tpj@cs.au.dk>
date Mon Oct 04 21:51:33 2010 +0200 (19 months ago)
parents 6a727af6cb6c
children
line source
1 # Copyright 2010 VIFF Development Team.
2 #
3 # This file is part of VIFF, the Virtual Ideal Functionality Framework.
4 #
5 # VIFF is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License (LGPL) as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # VIFF is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with VIFF. If not, see <http://www.gnu.org/licenses/>.
18 """Triple generation for the BeDOZa protocol.
19 TODO: Explain more.
20 """
22 import itertools
23 import hashlib
25 from twisted.internet.defer import Deferred, gatherResults, succeed
27 from viff.runtime import Runtime, ShareList, gather_shares
28 from viff.field import FieldElement, GF
29 from viff.constants import TEXT
30 from viff.util import rand
31 from viff.bedoza.shares import BeDOZaShare, BeDOZaShareContents, PartialShare
32 from viff.bedoza.shares import PartialShareContents
33 from viff.bedoza.share_generators import PartialShareGenerator
34 from viff.bedoza.keylist import BeDOZaKeyList
35 from viff.bedoza.maclist import BeDOZaMACList
36 from viff.bedoza.add_macs import add_macs
37 from viff.bedoza.modified_paillier import ModifiedPaillier
38 from viff.bedoza.util import fast_pow
39 from viff.bedoza.util import _convolute
40 from viff.bedoza.share import generate_partial_share_contents
42 from viff.triple import Triple
44 # TODO: Use secure random instead!
45 from random import Random
47 try:
48 import pypaillier
49 except ImportError:
50 # The pypaillier module is not released yet, so we cannot expect
51 # the import to work.
52 print "Error: The pypaillier module or one of the used functions " \
53 "are not available."
56 class TripleGenerator(object):
58 def __init__(self, runtime, security_parameter, p, random):
59 assert p > 1
60 self.random = random
61 # TODO: Generate Paillier cipher with N_i sufficiently larger than p
62 self.runtime = runtime
63 self.p = p
64 self.Zp = GF(p)
65 self.k = self._bit_length_of(p)
66 self.security_parameter = security_parameter
67 self.u_bound = 2**(self.security_parameter + 4 * self.k)
68 self.zk_random = Random(self.random.getrandbits(128))
70 paillier_random = Random(self.random.getrandbits(128))
71 alpha_random = Random(self.random.getrandbits(128))
72 self.paillier = ModifiedPaillier(runtime, paillier_random)
74 # Debug output.
75 #print "n_%d**2:%d" % (runtime.id, self.paillier.pubkey['n_square'])
76 #print "n_%d:%d" % (runtime.id, self.paillier.pubkey['n'])
77 #print "n_%d bitlength: %d" % \
78 # (runtime.id, self._bit_length_of(self.paillier.pubkey['n']))
80 #self.Zp = GF(p)
81 #self.Zn2 = GF(self.paillier.pubkey['n_square'])
82 #self.alpha = self.Zp(self.random.randint(0, p - 1))
83 self.alpha = alpha_random.randint(0, p - 1)
84 self.n2 = runtime.players[runtime.id].pubkey['n_square']
87 def generate_triples(self):
88 """Generates triples for use in the BeDOZa protocol.
90 Returns a deferred that will eventually yield a list
91 containing *self.security_parameter* multiplicative
92 triples. Each of these triples is an object of type
93 viff.triple.Triple. The components of each triple t, e.g.
95 t.a, t.b, and t.c,
97 are each of type BeDOZaShare and guaranteed to satisfy the
98 equation
100 t.c = t.a * t.b.
102 This method carries out the off-line work that is needed for
103 the BeDOZa protocol to work.
105 Data sent over the network is packaged in large hunks in order
106 to optimize. TODO: Explain better.
108 TODO: This method needs to have enough RAM to represent all n
109 triples in memory at the same time. Is there a nice way to
110 stream this, e.g. by using Python generators?
112 """
113 return self._generate_triples(self.security_parameter)
116 def _generate_triples(self, number_of_triples):
117 self.runtime.increment_pc()
118 triples = self._generate_triple_candidates(2 * number_of_triples)
119 return self._verify_triple_candidates(number_of_triples, triples)
122 def _generate_triple_candidates(self, n):
123 """Generates triple candidates for use in the BeDOZa protocol.
125 Returns a deferred that will eventually yield a list of 3n
126 shares of type viff.bedoza.shares.BeDOZaShare corresponding to
127 n multiplicative tuples. The first n are the a's, then comes n
128 b's followed by n c's.
130 The triples are only candidates because consistency of the
131 triples is only half-way guaranteed in the precense of active
132 adversaries. More concretely, the triples returned by this
133 method are guaranteed - even in the precense of an active
134 adversary - to be of the right size. But they may not satisfy
135 the equation
137 c = a * b.
139 """
140 self.runtime.increment_pc()
142 gen = PartialShareGenerator(self.Zp, self.runtime, self.random,
143 self.paillier)
144 partial_shares = []
145 for _ in xrange(2 * n):
146 partial_shares.append(
147 gen.generate_share(
148 self.random.randint(0, self.Zp.modulus - 1)))
149 partial_shares_c = self._full_mul(partial_shares[0: n],
150 partial_shares[n: 2 * n])
151 full_shares = add_macs(self.runtime, self.Zp, self.u_bound, self.alpha,
152 self.random, self.paillier,
153 partial_shares + partial_shares_c)
154 return full_shares
157 def _verify_triple_candidates(self, n, triple_candidates):
158 """Verify remaining consistency of triple candidates.
160 Takes as input a deferred list of 2n triple candidates as
161 generated by _generate_triple_candidates(2 * n) and returns
162 another deferred that will eventually yield a list of
163 viff.triple.Triple objects that are guaranteed to be fully
164 consistent. That is, the shares of the triples are guaranteed
165 to be of the right size and for each triple, the shares are
166 quaranteed to satisfy c = a * b.
168 The technique implemented in this method is essentially that
169 listed in Figure 5.5 in the progress report "LEGO and Other
170 Cryptographic Constructions" by Claudio Orlandi.
172 However, since we only implement a protocol that is secure in
173 the Random Oracle Model and not the Common Reference String
174 Model, we can safely skip step 2 of Figure 5.5 and instead
175 generate r by having all players P_i broadcast a random
176 element r_i and then have each player compute r as the hash of
177 the sum of all r_i's.
179 """
180 assert n == len(triple_candidates) / 6
182 def verify(v, a, b, c):
183 if v.value != 0:
184 raise Exception(
185 "TripleTest failed - The two triple candidates were "
186 "inconsistent.")
187 return Triple(a, b, c)
189 def prepare_verification(rs_serialized, results):
190 # Repr/eval deserialization.
191 rs = [eval(rss) for rss in rs_serialized]
193 for i in xrange(n):
194 a = triple_candidates[i]
195 b = triple_candidates[i + 2 * n]
196 c = triple_candidates[i + 4 * n]
197 x = triple_candidates[i + n]
198 y = triple_candidates[i + 3 * n]
199 z = triple_candidates[i + 5 * n]
201 # Hash all received random values to agree on a single
202 # random value for each triple.
203 hash = hashlib.sha1()
204 for rp in rs:
205 hash.update(str(rp[i]))
206 # TODO: We should use a secure random generator here.
207 rand = Random(hash.digest())
208 r = self.Zp(rand.randint(0, self.p - 1))
210 l = self.runtime._cmul(r, x, self.Zp)
211 m = self.runtime._cmul(r, y, self.Zp)
212 k = self.runtime._cmul(r * r, z, self.Zp)
213 v = c - self.runtime._basic_multiplication(a, b, l, m, k)
214 v = self.runtime.open(v)
215 self.runtime.schedule_callback(v, verify, a, b, c)
216 v.addCallbacks(results[i].callback, results[i].errback)
218 # TODO: Handle errors better.
219 def err_handler(err):
220 print err
222 results = [Deferred() for _ in xrange(n)]
224 ri = [self.random.randint(0, self.p - 1) for _ in xrange(n)]
225 # TODO: We use repr/eval as simple marshalling. Should be
226 # factored out and maybe optimized by using better compression
227 # here.
228 ris = self.runtime.broadcast(
229 self.runtime.players.keys(), self.runtime.players.keys(), repr(ri))
230 ris = gatherResults(ris)
231 self.runtime.schedule_callback(ris, prepare_verification, results)
232 ris.addErrback(err_handler)
234 # TODO: Which makes most sense?
235 #
236 # 1) Compute each triple separately and return list of
237 # deferred triples, or
239 # 2) Compute triples as a batch and return single deferred
240 # that evaluates to list of triples.
243 return results
246 def _bit_length_of(self, i):
247 bit_length = 0
248 while i:
249 i >>= 1
250 bit_length += 1
251 return bit_length
254 def _mul(self, inx, jnx, n, ais=None, cjs=None):
255 """Multiply each of the field elements in *ais* with the
256 corresponding encrypted elements in *cjs*.
258 Returns a deferred which will yield a list of field elements.
259 """
260 CKIND = 1
262 """The transmission_restraint_constant is the number of
263 encrypted shares we can safely transmit in one call to
264 sendData. The sendData method can only transmit up to
265 65536 bytes.
266 The constant has been imperically determined by running
267 TripleGenerator.generate_triples.
268 TODO: How can we allow a user of the runtime to adjust this
269 constraint at a higher level of abstraction?
271 """
272 transmission_restraint_constant = 425
274 number_of_packets = n / transmission_restraint_constant
275 if n % transmission_restraint_constant != 0:
276 number_of_packets += 1
278 self.runtime.increment_pc()
280 pc = tuple(self.runtime.program_counter)
282 deferred = []
283 zis = []
284 if self.runtime.id == inx:
285 Nj_square = self.paillier.get_modulus_square(jnx)
286 all_cs = []
287 for iny, (ai, cj) in enumerate(zip(ais, cjs)):
288 if iny % transmission_restraint_constant == 0:
289 cs = []
290 all_cs.append(cs)
291 u = rand.randint(0, self.u_bound)
292 Ej_u = self.paillier.encrypt(u, jnx)
293 cs.append( (fast_pow(cj, ai.value, Nj_square) * Ej_u) % Nj_square )
294 zi = self.Zp(-u)
295 zis.append(zi)
297 for cs in all_cs:
298 self.runtime.protocols[jnx].sendData(pc, CKIND, str(cs))
300 if self.runtime.id == jnx:
301 all_cs = []
302 for _ in xrange(number_of_packets):
303 cs = Deferred()
304 self.runtime._expect_data(inx, CKIND, cs)
305 all_cs.append(cs)
307 def decrypt(all_cs, pc, zis):
308 zjs = []
309 cs = reduce(lambda x, y: x + eval(y), all_cs, [])
310 for iny, c in enumerate(cs):
311 t = self.paillier.decrypt(c)
312 zj = self.Zp(t)
313 zjs.append(zj)
314 if not zis == []:
315 return [x + y for x, y in zip(zis, zjs)]
316 else:
317 return zjs
318 all_cs_d = gatherResults(all_cs)
319 all_cs_d.addCallback(decrypt, pc, zis)
320 deferred = all_cs_d
321 else:
322 zis_deferred = Deferred()
323 zis_deferred.callback(zis)
324 deferred = zis_deferred
326 return deferred
329 def _full_mul(self, a, b):
330 """Multiply each of the PartialShares in the list *a* with the
331 corresponding PartialShare in the list *b*.
333 Returns a deferred which will yield a list of PartialShares.
334 """
335 self.runtime.increment_pc()
337 def do_full_mul(shareContents, result_shares):
338 """Share content belonging to ai, bi are at:
339 shareContents[i], shareContents[len(shareContents) + i].
340 """
341 deferreds = []
342 len_shares = len(shareContents)
344 ais = [shareContent.value for shareContent in shareContents[0:len_shares/2]]
345 bis = [shareContent.value for shareContent in shareContents[len_shares/2:]]
347 b_enc_shares = []
348 for inx in self.runtime.players:
349 b_enc_shares.append([s.enc_shares[inx - 1]
350 for s in shareContents[len_shares/2:]])
352 values = len(ais) * [0]
354 for inx in xrange(0, len(self.runtime.players)):
355 for jnx in xrange(0, len(self.runtime.players)):
356 deferreds.append(self._mul(inx + 1,
357 jnx + 1,
358 len(ais),
359 ais,
360 b_enc_shares[jnx]))
362 def compute_shares(zils, values, result_shares):
363 for zil in zils:
364 for inx, zi in enumerate(zil):
365 values[inx] += zi
367 return values
369 d = gatherResults(deferreds)
370 d.addCallback(compute_shares, values, result_shares)
372 def callBackPartialShareContents(partialShareContents, result_shares):
373 for v, s in zip(partialShareContents, result_shares):
374 s.callback(v)
375 return None
377 d.addCallback(lambda values: generate_partial_share_contents(
378 values, self.runtime, self.paillier, self.k, self.zk_random))
379 d.addCallback(callBackPartialShareContents, result_shares)
380 return d
381 result_shares = [PartialShare(self.runtime, self.Zp) for _ in a]
382 self.runtime.schedule_callback(gatherResults(a + b),
383 do_full_mul,
384 result_shares)
385 return result_shares
388 # TODO: Represent all numbers by GF objects, Zp, Zn, etc.
389 # E.g. paillier encrypt should return Zn^2 elms and decrypt should
390 # return Zp elements.