Get started with threshold encryption

This commit is contained in:
Yingtong Li 2017-10-05 19:12:02 +11:00
parent a19914af2c
commit f1c4296887
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 222 additions and 0 deletions

View File

@ -27,6 +27,9 @@ class BigInt(EosObject):
self.impl = int(a, b) if isinstance(a, str) else int(a)
def __repr__(self):
return '<BigInt {}>'.format(str(self))
def __pow__(self, other, modulo=None):
if not isinstance(other, BigInt):
other = BigInt(other)

View File

@ -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
@ -99,6 +109,8 @@ class BitStream(EosObject):
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):
if self.nbits % block_size != 0:

View File

@ -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

93
eos/psr/secretsharing.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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)