From f9877128bd51f7f579c040d66644b46f86a0fbb4 Mon Sep 17 00:00:00 2001 From: Oli Schacher Date: Sun, 12 Apr 2020 06:39:10 +0200 Subject: [PATCH] function to add new shares to a set of existing ones --- SSSA/sssa.py | 27 ++++++++++++++++++++ SSSA/utils.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ sssa_tests.py | 12 +++++++++ 3 files changed, 108 insertions(+) diff --git a/SSSA/sssa.py b/SSSA/sssa.py index 2793360..22c6d83 100644 --- a/SSSA/sssa.py +++ b/SSSA/sssa.py @@ -69,3 +69,30 @@ def combine(self, shares): secret[part_index] = (secret[part_index] + working) % self.util.prime return self.util.merge_ints(secret) + + def add_share(self, shares): + # The "raw" secret is padded with \x00 and split into 32 char (64 hex) chunks: for each part there will be a polynomial. + # The result of 'create' still has "shares" entries, but each is a \sum_i^parts x_i+y_i. + # Reconstruct points on the polynomial(s) from the shares + x,y=self.util.shares_to_x_y(shares) + + # Initialize known numbers + numbers = [] + for parts in x: + numbers.extend(parts) + + # For each polynomial, interpolate another point + new_share = "" + for pol_nr in range(len(x)): + value = self.util.random() + while value in numbers: + value = self.util.random() + numbers.append(value) + + y_interpolated = self.util.lagrange_interpolate(value,x[pol_nr],y[pol_nr],self.util.prime) + + new_share += self.util.to_base64(value) + new_share += self.util.to_base64(y_interpolated) + + shares.append(new_share) + return shares diff --git a/SSSA/utils.py b/SSSA/utils.py index d185061..64108ce 100644 --- a/SSSA/utils.py +++ b/SSSA/utils.py @@ -107,3 +107,72 @@ def mod_inverse(self, number): if number < 0: remainder *= -1 return (self.prime + remainder) % self.prime + + + def shares_to_x_y(self,shares): + x = [] + y = [] + # must be: len(shares[0])//88 == len(shares[0])/88 + for i in range(len(shares[0])//88): + part_x = [] + part_y = [] + for share in shares: + part_x.append(self.from_base64(share[88*i+0:88*i+44])) + part_y.append(self.from_base64(share[88*i+44:88*i+88])) + x.append(tuple(part_x)) + y.append(tuple(part_y)) + return x,y + + # extended_gcd, divmod, lagrange_interpolate are copied from https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing + + def extended_gcd(self, a, b): + """ + Division in integers modulus p means finding the inverse of the + denominator modulo p and then multiplying the numerator by this + inverse (Note: inverse of A is B such that A*B % p == 1) this can + be computed via extended Euclidean algorithm + http://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Computation + """ + x = 0 + last_x = 1 + y = 1 + last_y = 0 + while b != 0: + quot = a // b + a, b = b, a % b + x, last_x = last_x - quot * x, x + y, last_y = last_y - quot * y, y + return last_x, last_y + + def divmod(self, num, den, p): + """Compute num / den modulo prime p + + To explain what this means, the return value will be such that + the following is true: den * divmod(num, den, p) % p == num + """ + inv, _ = self.extended_gcd(den, p) + return num * inv + + def lagrange_interpolate(self, x, x_s, y_s, p): + """ + Find the y-value for the given x, given n (x, y) points; + k points will define a polynomial of up to kth order. + """ + k = len(x_s) + assert k == len(set(x_s)), "points must be distinct" + def PI(vals): # upper-case PI -- product of inputs + accum = 1 + for v in vals: + accum *= v + return accum + nums = [] # avoid inexact division + dens = [] + for i in range(k): + others = list(x_s) + cur = others.pop(i) + nums.append(PI(x - o for o in others)) + dens.append(PI(cur - o for o in others)) + den = PI(dens) + num = sum([self.divmod(nums[i] * den * y_s[i] % p, dens[i], p) + for i in range(k)]) + return (self.divmod(num, den, p) + p) % p \ No newline at end of file diff --git a/sssa_tests.py b/sssa_tests.py index a6117f8..7358fd8 100644 --- a/sssa_tests.py +++ b/sssa_tests.py @@ -18,5 +18,17 @@ def test_library_combine(self): self.assertEqual(sss.combine(shares), "test-pass") + def test_add_share(self): + secret='the cake is a lie' + sss = sssa() + shares = sss.create(3,5,secret) + self.assertEqual(len(shares),5) + # restore secret using original shares + self.assertEqual(sss.combine(shares[:3]),secret) + # add new share and restore secret with a mix of original and new share + new_shares = sss.add_share(shares) + self.assertEqual(len(new_shares),6) + self.assertEqual(sss.combine(new_shares[-3:]),secret) + if __name__ == '__main__': unittest.main()