Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

function to add new shares to a set of existing ones #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions SSSA/sssa.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x, y -- add a space please. :)


# 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return just the new_share? I think that makes more sense than adding the new share at the end of the list of shares... My 2c.

return shares
69 changes: 69 additions & 0 deletions SSSA/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we replace gcd with an iterative version and remove this? My 2c.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I didn't do this when I first wrote the code... it has been a while :-)

"""
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a mod_inverse function that could've been used here.

return num * inv
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be well, divmod? :-) num*inv isn't guaranteed to be in [0, p)... You just need to wrap it in another modulus statement: (num * inv) % p.


def lagrange_interpolate(self, x, x_s, y_s, p):
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this function but we're already doing most of the work in combine...

sssa-python/SSSA/sssa.py

Lines 57 to 69 in f987712

for share_index,share in enumerate(secrets):
origin = share[part_index][0]
originy = share[part_index][1]
numerator = 1
denominator = 1
for product_index,product in enumerate(secrets):
if product_index != share_index:
current = product[part_index][0]
numerator = (numerator * (-1*current)) % self.util.prime
denominator = (denominator * (origin - current)) % self.util.prime
working = ((originy * numerator * self.util.mod_inverse(denominator)) + self.util.prime)
secret[part_index] = (secret[part_index] + working) % self.util.prime

Seems like we should use what's in sssa.py already and extract it into utils.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep the newline at the end of the file. :-)

12 changes: 12 additions & 0 deletions sssa_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()