owenrusk.dev

tally

shamir's secret sharing over gf(256).

git clone https://owenrusk.dev/tally.git

tally / tests/test_tally.py -rw-r--r-- · 2728 bytes

 1 import itertools
 2 import os
 3 import unittest
 4 
 5 import tally
 6 from tally import ShareError, codec
 7 
 8 
 9 class SplitJoinTest(unittest.TestCase):
10     def test_round_trip(self) -> None:
11         for length in [1, 2, 7, 31, 100, 1024]:
12             secret = os.urandom(length)
13             lines = tally.split(secret, 3, 5)
14             with self.subTest(length=length):
15                 self.assertEqual(tally.join(lines[2:]), secret)
16 
17     def test_every_k_subset(self) -> None:
18         secret = os.urandom(40)
19         lines = tally.split(secret, 3, 6)
20         for subset in itertools.combinations(lines, 3):
21             self.assertEqual(tally.join(subset), secret)
22 
23     def test_one_id_per_split(self) -> None:
24         lines = tally.split(b"x", 2, 4)
25         self.assertEqual(len({codec.decode(line).id for line in lines}), 1)
26 
27     def test_fewer_than_k(self) -> None:
28         lines = tally.split(b"not a real secret", 3, 5)
29         with self.assertRaisesRegex(ShareError, "need 3 shares, got 2"):
30             tally.join(lines[:2])
31 
32     def test_same_share_twice(self) -> None:
33         lines = tally.split(b"not a real secret", 2, 3)
34         with self.assertRaisesRegex(ShareError, "shares 1 and 2 are both share 1"):
35             tally.join([lines[0], lines[0]])
36 
37     def test_different_splits(self) -> None:
38         a = tally.split(b"not a real secret", 2, 3)
39         b = tally.split(b"not a real secret", 2, 3)
40         with self.assertRaisesRegex(ShareError, "different splits"):
41             tally.join([a[0], b[1]])
42 
43     def test_typo_names_the_share(self) -> None:
44         lines = tally.split(b"not a real secret", 2, 3)
45         typo = lines[1][:-1] + ("0" if lines[1][-1] != "0" else "2")
46         with self.assertRaisesRegex(ShareError, "^share 2: "):
47             tally.join([lines[0], typo])
48 
49     def test_more_than_k(self) -> None:
50         secret = os.urandom(24)
51         lines = tally.split(secret, 3, 7)
52         self.assertEqual(tally.join(lines), secret)
53         self.assertEqual(tally.join(reversed(lines)), secret)
54 
55     def test_damaged_extra_share(self) -> None:
56         # a share that's wrong but still passes its own checksum
57         lines = tally.split(b"not a real secret", 3, 5)
58         share = codec.decode(lines[4])
59         flipped = bytes([share.data[0] ^ 1]) + share.data[1:]
60         bad = codec.encode(codec.Share(share.id, share.k, share.x, flipped))
61         with self.assertRaisesRegex(ShareError, "share 4 doesn't agree with the first 3"):
62             tally.join([*lines[:3], bad])
63 
64     def test_nothing(self) -> None:
65         with self.assertRaises(ShareError):
66             tally.split(b"", 2, 3)
67         with self.assertRaises(ShareError):
68             tally.join([])
69 
70 
71 if __name__ == "__main__":
72     unittest.main()