6"""Native Python (slow) reimplementation of libminisketch' algorithms."""
26 2**8 + 2**4 + 2**3 + 2**1 + 1,
31 2**13 + 2**4 + 2**3 + 2**1 + 1,
34 2**16 + 2**5 + 2**3 + 2**1 + 1,
37 2**19 + 2**5 + 2**2 + 2**1 + 1,
42 2**24 + 2**4 + 2**3 + 2**1 + 1,
44 2**26 + 2**4 + 2**3 + 2**1 + 1,
45 2**27 + 2**5 + 2**2 + 2**1 + 1,
50 2**32 + 2**7 + 2**3 + 2**2 + 1,
55 2**37 + 2**6 + 2**4 + 2**1 + 1,
56 2**38 + 2**6 + 2**5 + 2**1 + 1,
58 2**40 + 2**5 + 2**4 + 2**3 + 1,
61 2**43 + 2**6 + 2**4 + 2**3 + 1,
63 2**45 + 2**4 + 2**3 + 2**1 + 1,
66 2**48 + 2**5 + 2**3 + 2**2 + 1,
68 2**50 + 2**4 + 2**3 + 2**2 + 1,
69 2**51 + 2**6 + 2**3 + 2**1 + 1,
71 2**53 + 2**6 + 2**2 + 2**1 + 1,
74 2**56 + 2**7 + 2**4 + 2**2 + 1,
77 2**59 + 2**7 + 2**4 + 2**2 + 1,
79 2**61 + 2**5 + 2**2 + 2**1 + 1,
82 2**64 + 2**4 + 2**3 + 2**1 + 1
86 """Class to perform GF(2^field_size) operations on elements represented as integers.
88 Given that elements are represented as integers, addition
is simply xor,
and not
93 """Construct a GF2Ops object for the specified field size."""
99 """Multiply x by 2 in GF(2^field_size)."""
106 """Multiply x by y in GF(2^field_size)."""
116 """Square x in GF(2^field_size)."""
117 return self.
mul(x, x)
120 """Compute the inverse of x in GF(2^field_size)."""
126 r1l, r2l = self.
field_size + 1, r2.bit_length()
131 r1l = r1.bit_length()
140 """Test class for basic arithmetic properties of GF2Ops."""
143 """Test operations for given field_size."""
147 x = random.randrange(1 << field_size)
148 y = random.randrange(1 << field_size)
151 self.assertEqual(x2, gf.mul(x, 2))
152 self.assertEqual(x2, gf.mul(2, x))
153 self.assertEqual(xy == 0, x == 0
or y == 0)
154 self.assertEqual(xy == x, y == 1
or x == 0)
155 self.assertEqual(xy == y, x == 1
or y == 0)
156 self.assertEqual(xy, gf.mul(y, x))
159 for _
in range(field_size):
161 self.assertEqual(xp, x)
164 self.assertEqual(y == yi, y == 1)
165 self.assertEqual(gf.mul(y, yi), 1)
167 self.assertEqual(y, yii)
171 self.assertEqual(xyi, gf.mul(xi, yi))
175 for field_size
in range(2, 65):
192 """Return a monic version of the polynomial poly."""
194 inv = gf.inv(poly[-1])
195 return [gf.mul(inv, v)
for v
in poly]
198 """Return the polynomial (quotient, remainder) of poly divided by mod."""
199 assert len(mod) > 0
and mod[-1] == 1
200 if len(poly) < len(mod):
203 div = [0
for _
in range(len(val) - len(mod) + 1)]
204 while len(val) >= len(mod):
206 div[len(val) - len(mod)] = term
210 for x
in range(len(mod) - 1):
211 val[1 + x - len(mod)] ^= gf.mul(term, mod[x])
213 while len(val) > 0
and val[-1] == 0:
218 """Return the polynomial GCD of a and b."""
229 """Return the square of polynomial poly."""
236 return [0
if i & 1
else gf.sqr(poly[i // 2])
for i
in range(2 * len(poly) - 1)]
239 """Compute y + y^2 + y^4 + ... + y^(2^(field_size-1)) mod poly, where y = param*x."""
241 for _
in range(gf.field_size - 1):
254 """Compute x^(2^field_size) mod poly."""
256 for _
in range(gf.field_size):
261 """Find the roots of poly if fully factorizable with unique roots, [] otherwise."""
278 def rec_split(poly, randv):
279 """Recursively split poly using the Berlekamp trace algorithm."""
281 assert len(poly) > 1
and poly[-1] == 1
294 randv = gf.mul2(randv)
300 if len(gcd) != len(poly)
and len(gcd) > 1:
307 return rec_split(factor1, randv) + rec_split(factor2, randv)
310 return sorted(rec_split(poly, random.randrange(1, 1 << gf.field_size)))
313 """Test class for poly_find_roots."""
316 """Run tests for given field_size."""
318 for test_size
in [0, 1, 2, 3, 10]:
319 roots = [random.randrange(1 << field_size)
for _
in range(test_size)]
320 roots_set = set(roots)
324 new_poly = [0] + poly
325 for n, c
in enumerate(poly):
326 new_poly[n] ^= gf.mul(c, root)
331 if len(roots) == len(roots_set):
332 self.assertEqual(found_roots, sorted(roots))
334 self.assertEqual(found_roots, [])
338 for field_size
in range(2, 65):
342 """Implement the Berlekamp-Massey algorithm.
344 Takes as input a sequence of GF(2^field_size) elements,
and returns the shortest LSFR
345 that generates it, represented
as a polynomial.
351 for n, discrepancy
in enumerate(syndromes):
353 for i
in range(1, len(current)):
354 discrepancy ^= gf.mul(syndromes[n - i], current[i])
358 x = n + 1 - (len(current) - 1) - (len(prev) - 1)
359 if 2 * (len(current) - 1) <= n:
361 current.extend(0
for _
in range(len(prev) + x - len(current)))
362 mul = gf.mul(discrepancy, b_inv)
363 for i, v
in enumerate(prev):
364 current[i + x] ^= gf.mul(mul, v)
366 b_inv = gf.inv(discrepancy)
368 mul = gf.mul(discrepancy, b_inv)
369 for i, v
in enumerate(prev):
370 current[i + x] ^= gf.mul(mul, v)
374 """A Minisketch sketch.
376 This represents a sketch of a certain capacity, with elements of a certain bit size.
380 """Initialize an empty sketch with the specified field_size size and capacity."""
387 """Add an element to this sketch. 1 <= element < 2**field_size."""
388 sqr = self.
gf.sqr(element)
391 element = self.
gf.mul(sqr, element)
394 """Compute how many bytes a serialization of this sketch will be in size."""
398 """Serialize this sketch to bytes."""
405 """Deserialize a byte array into this sketch, overwriting its contents."""
407 val = int.from_bytes(byte_data,
'little')
412 """Return a clone of this sketch."""
419 """Merge a sketch with another sketch. Corresponds to XOR'ing their serializations."""
420 assert self.
capacity == other.capacity
426 """Decode the contents of this sketch.
428 Returns either a list of elements or None if undecodable.
435 all_syndromes = [0
for _
in range(2 * len(self.
odd_syndromes))]
438 all_syndromes[i * 2 + 1] = self.
gf.sqr(all_syndromes[i])
446 if max_count
is not None and len(poly) > 1 + max_count:
458 """Test class for Minisketch."""
462 """Construct two random lists of elements in [1..2**field_size-1].
464 Each list will have unique elements that don't appear in the other (num_a_only in the first
465 and num_b_only
in the second),
and num_both elements will appear
in both.
"""
468 for _
in range(num_a_only + num_b_only + num_both):
470 r = random.randrange(1, 1 << field_size)
474 full_a = sample[:num_a_only + num_both]
475 full_b = sample[num_a_only:]
476 random.shuffle(full_a)
477 random.shuffle(full_b)
478 return full_a, full_b
481 """Test Minisketch methods for a specific field and capacity."""
482 used_capacity = random.randrange(capacity + 1)
483 num_a = random.randrange(used_capacity + 1)
484 num_both = random.randrange(min(2 * capacity, (1 << field_size) - 1 - used_capacity) + 1)
485 full_a, full_b = self.
construct_data(field_size, num_a, used_capacity - num_a, num_both)
492 sketch_combined = sketch_a.clone()
493 sketch_b_ser = sketch_b.serialize()
494 sketch_b_received =
Minisketch(field_size, capacity)
495 sketch_b_received.deserialize(sketch_b_ser)
496 sketch_combined.merge(sketch_b_received)
497 decode = sketch_combined.decode()
498 self.assertEqual(decode, sorted(set(full_a) ^ set(full_b)))
502 for field_size
in range(2, 65):
503 for capacity
in [0, 1, 2, 5, 10, field_size]:
506if __name__ ==
'__main__':
def __init__(self, field_size)
def __init__(self, field_size, capacity)
def deserialize(self, byte_data)
def decode(self, max_count=None)
def serialized_size(self)
def field_size_test(self, field_size)
def construct_data(cls, field_size, num_a_only, num_b_only, num_both)
def field_size_capacity_test(self, field_size, capacity)
def field_size_test(self, field_size)
def poly_divmod(poly, mod, gf)
def poly_tracemod(poly, param, gf)
def poly_frobeniusmod(poly, gf)
def poly_find_roots(poly, gf)
def berlekamp_massey(syndromes, gf)