Enforce a consistent message length for encrypted ballots

Closes #6
This commit is contained in:
RunasSudo 2017-12-07 18:02:01 +10:30
parent 9fc9d08fb4
commit c0752f71ed
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 47 additions and 21 deletions

View File

@ -35,7 +35,7 @@ class Ballot(EmbeddedObject):
election_id = UUIDField()
election_hash = StringField()
answers = EmbeddedObjectListField(is_hashed=False)
answers = EmbeddedObjectListField(is_hashed=False) # Used for ballots to be audited
def deaudit(self):
encrypted_answers_deaudit = EosList()
@ -118,19 +118,23 @@ class ListChoiceQuestion(Question):
if len(answer.choices) == 0:
return '(blank votes)'
return ', '.join([self.choices[choice] for choice in answer.choices])
class ApprovalQuestion(ListChoiceQuestion):
pass
def max_bits(self):
answer = self.answer_type(choices=list(range(len(self.choices))))
return len(EosObject.to_json(EosObject.serialise_and_wrap(answer))) * 8
class ApprovalAnswer(Answer):
choices = ListField(IntField())
class PreferentialQuestion(ListChoiceQuestion):
pass
class ApprovalQuestion(ListChoiceQuestion):
answer_type = ApprovalAnswer
class PreferentialAnswer(Answer):
choices = ListField(IntField())
class PreferentialQuestion(ListChoiceQuestion):
answer_type = PreferentialAnswer
class RawResult(Result):
plaintexts = ListField(EmbeddedObjectListField())
answers = EmbeddedObjectListField()

View File

@ -60,8 +60,9 @@ class DBInfo:
dbinfo = DBInfo()
def db_connect(db_name, db_uri='mongodb://localhost:27017/', db_type='mongodb'):
dbinfo.provider = eos.core.db.db_providers[db_type](db_name, db_uri)
dbinfo.provider.connect()
if is_python:
dbinfo.provider = eos.core.db.db_providers[db_type](db_name, db_uri)
dbinfo.provider.connect()
# Fields
# ======

View File

@ -111,18 +111,29 @@ class BitStream(EosObject):
return self
def pad_by(self, padding_size, pad_at_end=False):
if padding_size > 0:
if pad_at_end:
# Suitable for structured data
self.seek(self.nbits)
self.write(ZERO, padding_size)
else:
# Suitable for raw numbers
self.nbits += padding_size
return self # For convenient chaining
def pad_to(self, target_size, pad_at_end=False):
if self.nbits < target_size:
diff = target_size - self.nbits
self.pad_by(diff, pad_at_end)
return self
# Make the size of this BitStream a multiple of the block_size
def multiple_of(self, block_size, pad_at_end=False):
if self.nbits % block_size != 0:
diff = block_size - (self.nbits % block_size)
if pad_at_end:
# Suitable for structured data
self.seek(self.nbits)
self.write(ZERO, diff)
else:
# Suitable for raw numbers
self.nbits += diff
return self # For convenient chaining
self.pad_by(diff, pad_at_end)
return self
def map(self, func, block_size):
if self.nbits % block_size != 0:

View File

@ -41,7 +41,7 @@ class CyclicGroup(EmbeddedObject):
crypto_method = BigInt.crypto_random if crypto_random else BigInt.noncrypto_random
return crypto_method(ZERO, self.q - ONE)
# RFC 3526
# RFC 3526, 2048-bit MODP Group
DEFAULT_GROUP = CyclicGroup(
p=BigInt('FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF', 16),
g=TWO

View File

@ -27,10 +27,14 @@ class BlockEncryptedAnswer(EncryptedAnswer):
blocks = EmbeddedObjectListField()
@classmethod
def encrypt(cls, pk, obj):
def encrypt(cls, pk, obj, nbits=None):
pt = EosObject.to_json(EosObject.serialise_and_wrap(obj))
bs = BitStream()
bs.write_string(pt)
if nbits is not None:
if bs.nbits > nbits:
raise Exception('Message is too big')
bs.pad_to(nbits, True)
bs.multiple_of(pk.nbits(), True)
ct = bs.map(pk.encrypt, pk.nbits())

View File

@ -153,7 +153,12 @@ class BlockEGTestCase(EosTestCase):
ct = BlockEncryptedAnswer.encrypt(self.sk.public_key, obj)
_, m = ct.decrypt(self.sk)
self.assertEqualJSON(obj, m)
# Force another block
ct2 = BlockEncryptedAnswer.encrypt(self.sk.public_key, obj, (len(ct.blocks) * self.sk.public_key.nbits()) + 1)
self.assertEqual(len(ct2.blocks), len(ct.blocks) + 1)
_, m = ct2.decrypt(self.sk)
self.assertEqualJSON(obj, m)
class MixnetTestCase(EosTestCase):
@ -177,7 +182,7 @@ class MixnetTestCase(EosTestCase):
def do_mixnet(mix_order):
# Set up mixnet
mixnet = RPCMixnet(mix_order)
mixnet = RPCMixnet(mix_order=mix_order)
# Mix answers
shuffled_answers, commitments = mixnet.shuffle(answers)

View File

@ -21,9 +21,10 @@ isLibrariesLoaded = false;
function generateEncryptedVote(election, answers) {
encrypted_answers = [];
for (var answer_json of answers) {
for (var q_num = 0; q_num < answers.length; q_num++) {
answer_json = answers[q_num];
answer = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null);
encrypted_answer = eosjs.eos.psr.election.__all__.BlockEncryptedAnswer.encrypt(election.public_key, answer);
encrypted_answer = eosjs.eos.psr.election.__all__.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits());
encrypted_answers.push(eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(encrypted_answer, null));
}