diff --git a/secretsharing/primes.py b/secretsharing/primes.py index 417ea8b..4c5a6c8 100644 --- a/secretsharing/primes.py +++ b/secretsharing/primes.py @@ -30,6 +30,7 @@ def calculate_mersenne_primes(): primes.append(prime) return primes + SMALLEST_257BIT_PRIME = (2**256 + 297) SMALLEST_321BIT_PRIME = (2**320 + 27) SMALLEST_385BIT_PRIME = (2**384 + 231) diff --git a/secretsharing/sharing.py b/secretsharing/sharing.py index cd4e278..5cfe858 100644 --- a/secretsharing/sharing.py +++ b/secretsharing/sharing.py @@ -91,7 +91,7 @@ def share_string_to_point(share_string, charset): raise ValueError("Share has characters that aren't in the charset.") x = charset_to_int(x_string, charset) y = charset_to_int(y_string, charset) - return (x, y) + return x, y class SecretSharer(): @@ -110,10 +110,7 @@ def __init__(self): def split_secret(cls, secret_string, share_threshold, num_shares): secret_int = charset_to_int(secret_string, cls.secret_charset) points = secret_int_to_points(secret_int, share_threshold, num_shares) - shares = [] - for point in points: - shares.append(point_to_share_string(point, cls.share_charset)) - return shares + return cls.points_to_shares(points) @classmethod def recover_secret(cls, shares): @@ -124,6 +121,13 @@ def recover_secret(cls, shares): secret_string = int_to_charset(secret_int, cls.secret_charset) return secret_string + @classmethod + def points_to_shares(cls, points): + shares = [] + for point in points: + shares.append(point_to_share_string(point, cls.share_charset)) + return shares + class HexToHexSecretSharer(SecretSharer): """ Standard sharer for converting hex secrets to hex shares. @@ -161,3 +165,50 @@ class BitcoinToZB32SecretSharer(SecretSharer): """ secret_charset = base58_chars share_charset = zbase32_chars + + +class SecretSharerNew(SecretSharer): + """ + This class can be used to generate new shares for a secret once some + shares have already been generated. eg. Alice decides to shard her secret + in 5-of-10, later she decides that 10 more shares should be generated for + the same secret making it 5-of-15. Any of the old shares and the newly + generated shares can be used together + """ + def __init__(self): + # Holds information for each secret + self.secrets = {} + + def generate_shares(self, secret_string, share_threshold, num_shares, + max_shares=1000): + if max_shares > 1000: + raise ValueError('Why do you want greater than 1000 shares') + if secret_string not in self.secrets: + self._generate_and_record_params_for_secret(secret_string, + share_threshold, + max_shares) + secret_int, prime, coefficients = self._get_params_for_secret( + secret_string) + points = get_polynomial_points(coefficients, num_shares, prime) + return self.points_to_shares(points) + + def _generate_and_record_params_for_secret(self, secret_string, + share_threshold, max_shares): + secret_int = charset_to_int(secret_string, self.secret_charset) + prime = get_large_enough_prime([secret_int, max_shares]) + coefficients = random_polynomial(share_threshold - 1, secret_int, prime) + self._record_params_for_secret(secret_string, secret_int, prime, + coefficients) + + def _record_params_for_secret(self, secret_string, secret_int, prime, + coefficients): + self.secrets[secret_string] = { + 'int': secret_int, + 'prime': prime, + 'coefficients': coefficients + } + + def _get_params_for_secret(self, secret_string): + return self.secrets[secret_string]['int'], \ + self.secrets[secret_string]['prime'], \ + self.secrets[secret_string]['coefficients'] diff --git a/unit_tests.py b/unit_tests.py index 79d6566..e371b6c 100644 --- a/unit_tests.py +++ b/unit_tests.py @@ -9,13 +9,14 @@ import random import unittest -from test import test_support +from test import support from utilitybelt import base64_chars from secretsharing import secret_int_to_points, points_to_secret_int, \ point_to_share_string, share_string_to_point, SecretSharer, \ HexToHexSecretSharer, PlaintextToHexSecretSharer, \ BitcoinToB58SecretSharer, BitcoinToB32SecretSharer, \ BitcoinToZB32SecretSharer +from secretsharing.sharing import SecretSharerNew class ShamirSharingTest(unittest.TestCase): @@ -25,11 +26,12 @@ def setUp(self): def tearDown(self): pass - def split_and_recover_secret(self, sharer_class, m, n, secret): + @staticmethod + def split_and_recover_secret(sharer_class, m, n, secret): shares = sharer_class.split_secret(secret, m, n) random.shuffle(shares) recovered_secret = sharer_class.recover_secret(shares[0:m]) - assert(recovered_secret == secret) + assert (recovered_secret == secret) def test_hex_to_hex_sharing(self): recovered_secret = self.split_and_recover_secret( @@ -84,9 +86,51 @@ def test_2_of_2_sharing(self): "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a") +class ShamirSharingNewTest(unittest.TestCase): + def test_4_of_7_sharing(self): + # A simple test works + ShamirSharingTest.split_and_recover_secret( + SecretSharerNew, 4, 7, + "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a") + + def test_5_of_30_sharing(self): + # Generate new shares for the same secret + secret = "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a" + sharer = SecretSharerNew() + + # Shares can recover secret + shares_10 = sharer.generate_shares(secret, 5, 10) + assert SecretSharerNew.recover_secret(shares_10) == secret + shares_15 = sharer.generate_shares(secret, 5, 15) + assert SecretSharerNew.recover_secret(shares_15) == secret + shares_25 = sharer.generate_shares(secret, 5, 25) + assert SecretSharerNew.recover_secret(shares_25) == secret + shares_30 = sharer.generate_shares(secret, 5, 30) + assert SecretSharerNew.recover_secret(shares_30) == secret + shares_50 = sharer.generate_shares(secret, 5, 50) + assert SecretSharerNew.recover_secret(shares_50) == secret + + # Mix shares from different `generate_shares` calls + # Ranges for all except `shares_10` are used so that shares don't repeat + assert SecretSharerNew.recover_secret(random.sample(shares_10, 2) + + random.sample(shares_15[10:15], 3)) == secret + assert SecretSharerNew.recover_secret(random.sample(shares_15, 1) + + random.sample(shares_25[15:25], + 4)) == secret + + assert SecretSharerNew.recover_secret([random.sample(i, 1)[0] + for i in [shares_10, + shares_15[10:15], + shares_25[15:25], + shares_30[25:30], + shares_50[30:50] + ]]) == secret + + def test_main(): - test_support.run_unittest( - ShamirSharingTest + support.run_unittest( + ShamirSharingTest, + ShamirSharingNewTest )