Add mixnet verification to workflow

This commit is contained in:
Yingtong Li 2017-09-29 13:06:37 +10:00
parent a242f1f666
commit 239c7be952
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 80 additions and 7 deletions

View File

@ -46,6 +46,8 @@ else:
# ============== # ==============
class SHA256: class SHA256:
nbits = 256
def __init__(self): def __init__(self):
if is_python: if is_python:
self.impl = hashlib.sha256() self.impl = hashlib.sha256()

View File

@ -16,12 +16,13 @@
from eos.core.bigint import * from eos.core.bigint import *
from eos.core.objects import * from eos.core.objects import *
from eos.core.hashing import *
class BitStream(EosObject): class BitStream(EosObject):
def __init__(self, value=None): def __init__(self, value=None, nbits=None):
if value: if value:
self.impl = value self.impl = value
self.nbits = self.impl.nbits() self.nbits = nbits if nbits else self.impl.nbits()
else: else:
self.impl = ZERO self.impl = ZERO
self.nbits = 0 self.nbits = 0
@ -58,6 +59,17 @@ class BitStream(EosObject):
self.ptr += nbits self.ptr += nbits
self.nbits += nbits self.nbits += nbits
# Append to the end without affecting ptr
def append(self, bits, nbits=None):
if nbits is None:
nbits = bits.nbits()
if nbits < bits.nbits():
raise Exception('Too many bits to append to BitString')
self.impl = (self.impl << nbits) | bits
self.nbits += nbits
self.remaining += nbits
def read_string(self): def read_string(self):
length = self.read(32) length = self.read(32)
length = length.__int__() # JS attempts to call this twice if we do it in one line length = length.__int__() # JS attempts to call this twice if we do it in one line
@ -124,3 +136,23 @@ class BitStream(EosObject):
@classmethod @classmethod
def deserialise(cls, value): def deserialise(cls, value):
return cls(value) return cls(value)
class InfiniteHashBitStream(BitStream):
def __init__(self, seed):
self.sha = SHA256()
self.sha.update_bigint(seed)
self.ctr = 0
self.sha.update_text(str(self.ctr))
super().__init__(self.sha.hash_as_bigint(), self.sha.nbits)
def read(self, nbits=None):
# 11000110110
# ^----
if nbits is None:
nbits = self.remaining
while nbits > self.remaining:
self.ctr += 1
self.sha.update_text(str(self.ctr))
self.append(self.sha.hash_as_bigint(), self.sha.nbits)
return super().read(nbits)

View File

@ -16,6 +16,7 @@
from eos.core.bigint import * from eos.core.bigint import *
from eos.core.objects import * from eos.core.objects import *
from eos.core.hashing import *
from eos.base.election import * from eos.base.election import *
from eos.psr.bitstream import * from eos.psr.bitstream import *
from eos.psr.crypto import * from eos.psr.crypto import *
@ -48,15 +49,29 @@ class Trustee(EmbeddedObject):
email = StringField() email = StringField()
class MixChallengeResponse(EmbeddedObject): class MixChallengeResponse(EmbeddedObject):
index = IntField() challenge_index = IntField()
response_index = IntField()
reenc = EmbeddedObjectListField(BigInt) reenc = EmbeddedObjectListField(BigInt)
rand = EmbeddedObjectField(BigInt) rand = EmbeddedObjectField(BigInt)
class MixingTrustee(Trustee): class MixingTrustee(Trustee):
mixed_questions = ListField(EmbeddedObjectListField(BlockEncryptedAnswer)) mixed_questions = ListField(EmbeddedObjectListField(BlockEncryptedAnswer))
commitments = ListField(EmbeddedObjectListField(BigInt)) commitments = ListField(EmbeddedObjectListField(BigInt))
challenge = EmbeddedObjectField(BigInt) challenge = EmbeddedObjectListField(BigInt)
response = ListField(EmbeddedObjectListField(MixChallengeResponse)) response = ListField(EmbeddedObjectListField(MixChallengeResponse))
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.mixnets = [] # TODO: Remove this stuff
def compute_challenge(self, question_num):
sha = SHA256()
trustees = self.recurse_parents(Election).mixing_trustees
for i in range(len(trustees)):
sha.update_text(EosObject.to_json(MixingTrustee._fields['mixed_questions'].element_field.serialise(trustees[i].mixed_questions[question_num])))
for i in range(self._instance[1]):
sha.update_text(EosObject.to_json(MixingTrustee._fields['response'].element_field.serialise(trustees[i].response[question_num])))
return sha
class PSRElection(Election): class PSRElection(Election):
_db_name = Election._name _db_name = Election._name

View File

@ -242,7 +242,8 @@ class ElectionTestCase(EosTestCase):
# Do the mix # Do the mix
for i in range(len(election.questions)): for i in range(len(election.questions)):
for j in range(len(election.mixing_trustees)): for j in range(len(election.mixing_trustees)):
mixnet = RPCMixnet(j) # Wouldn't happen server-side IRL
election.mixing_trustees[j].mixnets.append(RPCMixnet(j))
if j > 0: if j > 0:
orig_answers = election.mixing_trustees[j - 1].mixed_questions[i] orig_answers = election.mixing_trustees[j - 1].mixed_questions[i]
else: else:
@ -250,7 +251,7 @@ class ElectionTestCase(EosTestCase):
for voter in election.voters: for voter in election.voters:
for ballot in voter.ballots: for ballot in voter.ballots:
orig_answers.append(ballot.encrypted_answers[i]) orig_answers.append(ballot.encrypted_answers[i])
shuffled_answers, commitments = mixnet.shuffle(orig_answers) shuffled_answers, commitments = election.mixing_trustees[j].mixnets[i].shuffle(orig_answers)
election.mixing_trustees[j].mixed_questions.append(EosList(shuffled_answers)) election.mixing_trustees[j].mixed_questions.append(EosList(shuffled_answers))
election.mixing_trustees[j].commitments.append(EosList(commitments)) election.mixing_trustees[j].commitments.append(EosList(commitments))
@ -261,7 +262,30 @@ class ElectionTestCase(EosTestCase):
election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').enter() election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').enter()
election.save() election.save()
# TODO # Record challenge responses
for i in range(len(election.questions)):
for j in range(len(election.mixing_trustees)):
trustee = election.mixing_trustees[j]
if j % 2 == 0:
trustee.challenge.append(trustee.compute_challenge(i).hash_as_bigint())
else:
trustee.challenge.append(election.mixing_trustees[j - 1].challenge[i])
challenge_bs = InfiniteHashBitStream(trustee.challenge[i])
trustee.response.append(EosList())
nbits = BigInt(len(trustee.mixed_questions[i])).nbits()
for k in range(len(trustee.mixed_questions[i])):
challenge_bit = challenge_bs.read(1)
should_reveal = ((j % 2) == (challenge_bit % 2))
if should_reveal:
response = trustee.mixnets[i].challenge(k)
trustee.response[i].append(MixChallengeResponse(
challenge_index=k,
response_index=response[0],
reenc=response[1],
rand=response[2]
))
election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').exit() election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').exit()
election.save() election.save()