Add mixnet verification to workflow
This commit is contained in:
parent
a242f1f666
commit
239c7be952
@ -46,6 +46,8 @@ else:
|
||||
# ==============
|
||||
|
||||
class SHA256:
|
||||
nbits = 256
|
||||
|
||||
def __init__(self):
|
||||
if is_python:
|
||||
self.impl = hashlib.sha256()
|
||||
|
@ -16,12 +16,13 @@
|
||||
|
||||
from eos.core.bigint import *
|
||||
from eos.core.objects import *
|
||||
from eos.core.hashing import *
|
||||
|
||||
class BitStream(EosObject):
|
||||
def __init__(self, value=None):
|
||||
def __init__(self, value=None, nbits=None):
|
||||
if value:
|
||||
self.impl = value
|
||||
self.nbits = self.impl.nbits()
|
||||
self.nbits = nbits if nbits else self.impl.nbits()
|
||||
else:
|
||||
self.impl = ZERO
|
||||
self.nbits = 0
|
||||
@ -58,6 +59,17 @@ class BitStream(EosObject):
|
||||
self.ptr += 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):
|
||||
length = self.read(32)
|
||||
length = length.__int__() # JS attempts to call this twice if we do it in one line
|
||||
@ -124,3 +136,23 @@ class BitStream(EosObject):
|
||||
@classmethod
|
||||
def deserialise(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)
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
from eos.core.bigint import *
|
||||
from eos.core.objects import *
|
||||
from eos.core.hashing import *
|
||||
from eos.base.election import *
|
||||
from eos.psr.bitstream import *
|
||||
from eos.psr.crypto import *
|
||||
@ -48,16 +49,30 @@ class Trustee(EmbeddedObject):
|
||||
email = StringField()
|
||||
|
||||
class MixChallengeResponse(EmbeddedObject):
|
||||
index = IntField()
|
||||
challenge_index = IntField()
|
||||
response_index = IntField()
|
||||
reenc = EmbeddedObjectListField(BigInt)
|
||||
rand = EmbeddedObjectField(BigInt)
|
||||
|
||||
class MixingTrustee(Trustee):
|
||||
mixed_questions = ListField(EmbeddedObjectListField(BlockEncryptedAnswer))
|
||||
commitments = ListField(EmbeddedObjectListField(BigInt))
|
||||
challenge = EmbeddedObjectField(BigInt)
|
||||
challenge = EmbeddedObjectListField(BigInt)
|
||||
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):
|
||||
_db_name = Election._name
|
||||
|
||||
|
@ -242,7 +242,8 @@ class ElectionTestCase(EosTestCase):
|
||||
# Do the mix
|
||||
for i in range(len(election.questions)):
|
||||
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:
|
||||
orig_answers = election.mixing_trustees[j - 1].mixed_questions[i]
|
||||
else:
|
||||
@ -250,7 +251,7 @@ class ElectionTestCase(EosTestCase):
|
||||
for voter in election.voters:
|
||||
for ballot in voter.ballots:
|
||||
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].commitments.append(EosList(commitments))
|
||||
|
||||
@ -261,7 +262,30 @@ class ElectionTestCase(EosTestCase):
|
||||
election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').enter()
|
||||
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.save()
|
||||
|
Loading…
Reference in New Issue
Block a user