owenrusk.dev

tally

shamir's secret sharing over gf(256).

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

tally / tests/test_codec.py -rw-r--r-- · 2917 bytes

 1 import os
 2 import unittest
 3 
 4 from tally import codec
 5 from tally.codec import Share, ShareError
 6 
 7 SHARE = Share(id=bytes.fromhex("a1b2c3d4"), k=3, x=2, data=b"not a real secret")
 8 
 9 
10 class CodecTest(unittest.TestCase):
11     def test_round_trip(self) -> None:
12         self.assertEqual(codec.decode(codec.encode(SHARE)), SHARE)
13 
14     def test_round_trip_lengths(self) -> None:
15         for length in range(1, 40):
16             share = Share(codec.new_id(), 2, 255, os.urandom(length))
17             self.assertEqual(codec.decode(codec.encode(share)), share)
18 
19     def test_looks_right(self) -> None:
20         text = codec.encode(SHARE)
21         self.assertTrue(text.startswith("t1-"))
22         groups = text.split("-")[1:]
23         self.assertTrue(all(len(g) == codec.GROUP for g in groups[:-1]))
24         self.assertTrue(set("".join(groups)) <= set(codec.ALPHABET))
25 
26     def test_case_and_spacing_dont_matter(self) -> None:
27         text = codec.encode(SHARE)
28         self.assertEqual(codec.decode(text.upper()), SHARE)
29         self.assertEqual(codec.decode("  " + text.replace("-", " - ") + "\n"), SHARE)
30 
31     def test_lookalikes(self) -> None:
32         text = codec.encode(SHARE)
33         body = text[3:]
34         self.assertTrue("0" in body and "1" in body)
35         mistyped = text[:3] + body.replace("0", "o").replace("1", "l")
36         self.assertEqual(codec.decode(mistyped), SHARE)
37         mistyped = text[:3] + body.replace("1", "i")
38         self.assertEqual(codec.decode(mistyped), SHARE)
39 
40     def test_not_a_share(self) -> None:
41         for text in ["", "hello", "t1", "x1-abcd", "t1-ab", "t1-abcd-u000"]:
42             with self.subTest(text=text), self.assertRaises(ShareError):
43                 codec.decode(text)
44 
45     def test_other_version(self) -> None:
46         text = "t2" + codec.encode(SHARE)[2:]
47         with self.assertRaisesRegex(ShareError, "format 2"):
48             codec.decode(text)
49 
50     def test_typo(self) -> None:
51         text = codec.encode(SHARE)
52         typo = text[:10] + ("x" if text[10] != "x" else "y") + text[11:]
53         with self.assertRaisesRegex(ShareError, "checksum"):
54             codec.decode(typo)
55 
56     def test_every_single_character_typo_is_caught(self) -> None:
57         text = codec.encode(SHARE)
58         for i, c in enumerate(text):
59             if c == "-":
60                 continue
61             for other in codec.ALPHABET:
62                 if other == c:
63                     continue
64                 typo = text[:i] + other + text[i + 1 :]
65                 with self.subTest(i=i, other=other), self.assertRaises(ShareError):
66                     codec.decode(typo)
67 
68     def test_dropped_character_is_caught(self) -> None:
69         text = codec.encode(SHARE)
70         for i, c in enumerate(text):
71             if c != "-":
72                 with self.subTest(i=i), self.assertRaises(ShareError):
73                     codec.decode(text[:i] + text[i + 1 :])
74 
75 
76 if __name__ == "__main__":
77     unittest.main()