diff --git a/eos/core/bigint/python.py b/eos/core/bigint/python.py index 88dcae4..3882bf0 100644 --- a/eos/core/bigint/python.py +++ b/eos/core/bigint/python.py @@ -27,6 +27,9 @@ class BigInt(EosObject): self.impl = int(a, b) if isinstance(a, str) else int(a) + def __repr__(self): + return ''.format(str(self)) + def __pow__(self, other, modulo=None): if not isinstance(other, BigInt): other = BigInt(other) diff --git a/eos/psr/bitstream.py b/eos/psr/bitstream.py index 9641f9f..07a7362 100644 --- a/eos/psr/bitstream.py +++ b/eos/psr/bitstream.py @@ -70,6 +70,16 @@ class BitStream(EosObject): self.nbits += nbits self.remaining += nbits + def read_bigint(self): + length = self.read(32) + length = length.__int__() + return self.read(length) + + def write_bigint(self, value): + self.write(BigInt(value.nbits()), 32) # TODO: Arbitrary lengths + self.write(value) + return self + def read_string(self): length = self.read(32) length = length.__int__() # JS attempts to call this twice if we do it in one line @@ -98,6 +108,8 @@ class BitStream(EosObject): else: for i in range(len(strg)): self.write(BigInt(strg.charCodeAt(i)), 7) + + return self # Make the size of this BitStream a multiple of the block_size def multiple_of(self, block_size, pad_at_end=False): diff --git a/eos/psr/crypto.py b/eos/psr/crypto.py index fe16cec..cc958ae 100644 --- a/eos/psr/crypto.py +++ b/eos/psr/crypto.py @@ -140,3 +140,36 @@ class SEGCiphertext(EGCiphertext): c = SHA256().update_bigint(gs, self.gamma, self.delta).hash_as_bigint() return self.c == c + +class Polynomial(EmbeddedObject): + coefficients = EmbeddedObjectListField(BigInt) # x^0, x^1, ... x^n + modulus = EmbeddedObjectField(BigInt) + + def value(self, x): + if not isinstance(x, BigInt): + x = BigInt(x) + + result = ZERO + for i in range(len(self.coefficients)): + #result = (result + ((self.coefficients[i] * pow(x, i, self.modulus)) % self.modulus)) % self.modulus + result = result + (self.coefficients[i] * pow(x, i)) + return result + +class PedersenVSSPrivateKey(EmbeddedObject): + public_key = EmbeddedObjectField(SEGPublicKey) + + x = EmbeddedObjectField(BigInt) # secret + + def get_modified_secret(self): + mod_s = self.x + for j in range(1, threshold + 1): # 1 to threshold + + def decrypt(self, ciphertext): + if ( + ciphertext.gamma <= ZERO or ciphertext.gamma >= self.public_key.group.p or + ciphertext.delta <= ZERO or ciphertext.delta >= self.public_key.group.p + ): + raise Exception('Ciphertext is malformed') + + gamma_inv = pow(ciphertext.gamma, self.public_key.group.p - ONE - self.x, self.public_key.group.p) + return gamma_inv diff --git a/eos/psr/secretsharing.py b/eos/psr/secretsharing.py new file mode 100644 index 0000000..ad70fdf --- /dev/null +++ b/eos/psr/secretsharing.py @@ -0,0 +1,93 @@ +# Eos - Verifiable elections +# Copyright © 2017 RunasSudo (Yingtong Li) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from eos.core.bigint import * +from eos.core.objects import * +from eos.core.hashing import * +from eos.psr.crypto import * +from eos.psr.bitstream import * + +class PedersenVSSSetup: + def __init__(self): + self.group = None + self.participants = [] + self.threshold = 0 + + self.public_key = None + + def compute_public_key(self): + h = ONE + for participant in self.participants: + h = (h * participant.h.val) % self.group.p + + self.public_key = SEGPublicKey(group=self.group, X=h) + return self.public_key + + def combine_decryption(self, decryption_shares): + gamma_inv = ONE + for i, share in decryption_shares: + lagrange_num = ONE + lagrange_den = ONE + for j, _ in decryption_shares: + lagrange_num = lagrange_num * BigInt(j) + lagrange_den = lagrange_den * BigInt(j - i) + if lagrange_num % lagrange_den != 0: + raise Exception('Cannot raise to fractional exponent') + gamma_inv = gamma_inv * pow(share, lagrange_num / lagrange_den, self.group.p) + +class PedersenVSSCommitment(EmbeddedObject): + val = EmbeddedObjectField(BigInt) + rand = EmbeddedObjectField(BigInt) + +class PedersenVSSParticipant(): + def __init__(self, setup): + self.setup = setup + + self.pk = None + self.shares_received = [] + self.F = [] + + self.h = None + self.h_commitment = None + + self.sk = None # non-threshold + self.threshold_sk = None + self.f = Polynomial(modulus=self.setup.group.p) + + def commit_pk_share(self): + # Generate random polynomial + for _ in range(0, self.setup.threshold): # 0 to k-1 + coeff = self.setup.group.random_element() + self.f.coefficients.append(coeff) + #self.F.append(PedersenVSSCommitment(val=coeff, rand=self.sk.public_key.group.random_element())) + self.F.append(pow(self.setup.group.g, coeff, self.setup.group.p)) + + self.h = PedersenVSSCommitment(val=self.F[0], rand=self.setup.group.random_element()) + self.h_commitment = SHA256().update_obj(self.h).hash_as_bigint() + + return self.h_commitment + + def get_share_for(self, other_idx): + other = self.setup.participants[other_idx] + #return other.pk.encrypt(self.f.value(other_idx + 1) % self.setup.group.p) + return BitStream().write_bigint(self.f.value(other_idx + 1)).multiple_of(other.pk.group.p.nbits(), True).map(other.pk.encrypt, other.pk.group.p.nbits()) + + def compute_secret_key(self): + x = ZERO + for share in self.shares_received: + x = (x + share) % self.setup.group.p + self.threshold_sk = PedersenVSSPrivateKey(public_key=self.setup.public_key, x=x) + return self.threshold_sk diff --git a/eos/psr/tests.py b/eos/psr/tests.py index 0901fce..69b0589 100644 --- a/eos/psr/tests.py +++ b/eos/psr/tests.py @@ -22,6 +22,7 @@ from eos.psr.bitstream import * from eos.psr.crypto import * from eos.psr.election import * from eos.psr.mixnet import * +from eos.psr.secretsharing import * from eos.psr.workflow import * class EGTestCase(EosTestCase): @@ -297,3 +298,83 @@ class ElectionTestCase(EosTestCase): # Release result self.do_task_assert(election, 'eos.base.workflow.TaskReleaseResults', None) election.save() + +class AAAPVSSTestCase(EosTestCase): + @py_only + def test_basic(self): + setup = PedersenVSSSetup() + setup.group = DEFAULT_GROUP + setup.threshold = 3 # 3 of 5 + + for _ in range(5): + participant = PedersenVSSParticipant(setup) + participant.sk = EGPrivateKey.generate() + participant.pk = participant.sk.public_key + setup.participants.append(participant) + + # Step 1 + + for participant in setup.participants: + participant.commit_pk_share() + + # IRL: Send hi=F[0] commitments around + + # Send shares around + for i in range(len(setup.participants)): + participant = setup.participants[i] + for j in range(len(setup.participants)): + other = setup.participants[j] + share = participant.get_share_for(j) + #share_dec = other.sk.decrypt(share) + share_dec = BitStream.unmap(share, other.sk.decrypt, other.sk.public_key.group.p.nbits()).read_bigint() + other.shares_received.append(share_dec) + + # Step 2 + + # IRL: Decommit hi=F[0], send F around + + # Verify shares + for i in range(len(setup.participants)): + participant = setup.participants[i] + for j in range(len(setup.participants)): + other = setup.participants[j] + + # Verify share received by other from participant + share_dec = other.shares_received[i] + g_share_dec_expected = ONE + for k in range(0, setup.threshold): + g_share_dec_expected = (g_share_dec_expected * pow(participant.F[k], pow(j + 1, k), setup.group.p)) % setup.group.p + if pow(setup.group.g, share_dec, setup.group.p) != g_share_dec_expected: + import pdb; pdb.set_trace() + raise Exception('Share not consistent with commitments') + + # Compute threshold public key + pk = setup.compute_public_key() + + # Compute secret key shares + for participant in setup.participants: + participant.compute_secret_key() + + # Encrypt data + + pt = pk.group.random_element() + ct = pk.encrypt(pt) + + # Decrypt data + + decryption_shares = [] + + # Pick any threshold + __pragma__('skip') + import random + __pragma__('noskip') + threshold_participants = list(range(len(setup.participants))) + random.shuffle(threshold_participants) + threshold_participants = threshold_participants[:setup.threshold] + + for i in setup.threshold: + share = setup.participants[i].threshold_sk.decrypt(ct) + decryption_shares.append((i, share)) + + m = setup.combine_decryptions(decryption_shares) + self.assertEqualJSON(pt, m)