2 Commits

Author SHA1 Message Date
  John-Mark Gurney 1d3c778147 implement / (division), use self.__class__ more.. 2 years ago
  John-Mark Gurney 9391c8ce41 improve docs, handle some edge cases, use generator instead of list 2 years ago
1 changed files with 58 additions and 15 deletions
Split View
  1. +58
    -15
      shamirss.py

+ 58
- 15
shamirss.py View File

@@ -31,7 +31,7 @@ An implementation of Shamir's Secret Sharing.

This is over GF(2^256), so unlike some other implementations that are
over primes, it is valid for ALL values, and the output will be exactly
the same size as the secret. This also limits the number of shared to
the same length as the secret. This limits the number of shares to
255.

Sample usage:
@@ -115,12 +115,17 @@ def recover_data(shares, k):
if len(shares) < k:
raise ValueError('not enough shares to recover')

return bytes([ int(sum([ GF2p8(y[idx]) *
functools.reduce(operator.mul, [ pix * ((GF2p8(pix) - x) ** -1) for
pix, piy in shares[:k] if pix != x ], 1) for x, y in shares[:k] ],
return bytes([ int(sum(( GF2p8(y[idx]) *
functools.reduce(operator.mul, ( pix * ((GF2p8(pix) - x) ** -1) for
pix, piy in shares[:k] if pix != x ), 1) for x, y in shares[:k] ),
0)) for idx in range(len(shares[0][1]))])

class GF2p8:
# polynomial 0x187
'''An implementation of GF(2^8). It uses the polynomial 0x187
or x^8 + x^7 + x^2 + x + 1.
'''

_invcache = (None, 1, 195, 130, 162, 126, 65, 90, 81, 54, 63, 172, 227, 104, 45, 42, 235, 155, 27, 53, 220, 30, 86, 165, 178, 116, 52, 18, 213, 100, 21, 221, 182, 75, 142, 251, 206, 233, 217, 161, 110, 219, 15, 44, 43, 14, 145, 241, 89, 215, 58, 244, 26, 19, 9, 80, 169, 99, 50, 245, 201, 204, 173, 10, 91, 6, 230, 247, 71, 191, 190, 68, 103, 123, 183, 33, 175, 83, 147, 255, 55, 8, 174, 77, 196, 209, 22, 164, 214, 48, 7, 64, 139, 157, 187, 140, 239, 129, 168, 57, 29, 212, 122, 72, 13, 226, 202, 176, 199, 222, 40, 218, 151, 210, 242, 132, 25, 179, 185, 135, 167, 228, 102, 73, 149, 153, 5, 163, 238, 97, 3, 194, 115, 243, 184, 119, 224, 248, 156, 92, 95, 186, 34, 250, 240, 46, 254, 78, 152, 124, 211, 112, 148, 125, 234, 17, 138, 93, 188, 236, 216, 39, 4, 127, 87, 23, 229, 120, 98, 56, 171, 170, 11, 62, 82, 76, 107, 203, 24, 117, 192, 253, 32, 74, 134, 118, 141, 94, 158, 237, 70, 69, 180, 252, 131, 2, 84, 208, 223, 108, 205, 60, 106, 177, 61, 200, 36, 232, 197, 85, 113, 150, 101, 28, 88, 49, 160, 38, 111, 41, 20, 31, 109, 198, 136, 249, 105, 12, 121, 166, 66, 246, 207, 37, 154, 16, 159, 189, 128, 96, 144, 47, 114, 133, 51, 59, 231, 67, 137, 225, 143, 35, 193, 181, 146, 79)

@staticmethod
@@ -136,19 +141,31 @@ class GF2p8:

return r

# polynomial 0x187
_reduce = tuple(_makered(x, 0x87) for x in range(0, 16))
_reduce = (0, 135, 137, 14, 149, 18, 28, 155, 173, 42, 36, 163, 56, 191, 177, 54)

def __init__(self, v):
if v >= 256:
'''v must be in the range [ 0, 255 ].

Create an element of GF(2^8).

The operators have been overloaded, so most normal math works.

It will also automatically promote non-GF2p8 numbers if
possible, e.g. GF2p8(5) + 10 works.
'''

if v >= 256 or v < 0:
raise ValueError('%d is not a member of GF(2^8)' % v)

self._v = v
self._v = int(v)

if self._v != v:
raise ValueError('%d is not a member of GF(2^8)' % v)

# basic operations
def __add__(self, o):
if isinstance(o, int):
return self + self.__class__(o)
if not isinstance(o, self.__class__):
o = self.__class__(o)

return self.__class__(self._v ^ o._v)

@@ -163,7 +180,7 @@ class GF2p8:

def __mul__(self, o):

if isinstance(o, int):
if not isinstance(o, self.__class__):
o = self.__class__(o)

m = o._v
@@ -182,9 +199,21 @@ class GF2p8:
def __rmul__(self, o):
return self.__mul__(o)

def __truediv__(self, o):
if not isinstance(o, self.__class__):
o = self.__class__(o)

return self * (o ** -1)

def __rtruediv__(self, o):
if not isinstance(o, self.__class__):
o = self.__class__(o)

return o * (self ** -1)

def __pow__(self, x):
if x == -1 and self._invcache:
return GF2p8(self._invcache[self._v])
return self.__class__(self._invcache[self._v])

if x < 0:
x += 255
@@ -208,8 +237,8 @@ class GF2p8:
return r

def __eq__(self, o):
if isinstance(o, int):
return self._v == o
if not isinstance(o, self.__class__):
o = self.__class__(o)

return self._v == o._v

@@ -268,11 +297,19 @@ class TestShamirSS(unittest.TestCase):
for i in range(5):
self.assertEqual(val, recover_data([ a[j] for j in random.sample(range(30), 15) ], 15))

def test_gf2p8_reduce(self):
reduce = tuple(_makered(x, 0x87) for x in range(0, 16))

if GF2p8._reduce != reduce: # pragma: no cover
print('reduce:', repr(reduce))
self.assertEqual(GF2p8._reduce, reduce)


def test_gf2p8_inv(self):

a = GF2p8(random.randint(0, 255))

with unittest.mock.patch.object(GF2p8, '_invcache', []) as pinvc:
with unittest.mock.patch.object(GF2p8, '_invcache', ()) as pinvc:
ainv = a ** -1

self.assertEqual(a * ainv, 1)
@@ -302,6 +339,12 @@ class TestShamirSS(unittest.TestCase):

def test_gf2p8_errors(self):
self.assertRaises(ValueError, GF2p8, 1000)
self.assertRaises(ValueError, GF2p8, 40.5)
self.assertRaises(ValueError, GF2p8, -1)

def test_gf2p8_div(self):
self.assertEqual(GF2p8(10) / 11, GF2p8(11) ** -1 * GF2p8(10))
self.assertEqual(10 / GF2p8(11), GF2p8(11) ** -1 * GF2p8(10))

def test_gf2p8(self):
self.assertEqual(int(GF2p8(5)), 5)


Loading…
Cancel
Save