Get started with threshold encryption
This commit is contained in:
parent
a19914af2c
commit
f1c4296887
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
93
eos/psr/secretsharing.py
Normal 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
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user