Find the roots of poly if fully factorizable with unique roots, [] otherwise.
(poly, gf)
| 258 | return out |
| 259 | |
| 260 | def poly_find_roots(poly, gf): |
| 261 | """Find the roots of poly if fully factorizable with unique roots, [] otherwise.""" |
| 262 | assert len(poly) > 0 |
| 263 | # If the polynomial is constant (and nonzero), it has no roots. |
| 264 | if len(poly) == 1: |
| 265 | return [] |
| 266 | # Make the polynomial monic (which doesn't change its roots). |
| 267 | poly = poly_monic(poly, gf) |
| 268 | # If the polynomial is of the form x+a, return a. |
| 269 | if len(poly) == 2: |
| 270 | return [poly[0]] |
| 271 | # Otherwise, first test that poly can be completely factored into unique roots. The polynomial |
| 272 | # x^(2^fieldsize)-x has every field element once as root. Thus we want to know that that is a |
| 273 | # multiple of poly. Compute x^(field_size) mod poly, which needs to equal x if that is the case |
| 274 | # (unless poly has degree <= 1, but that case is handled above). |
| 275 | if poly_frobeniusmod(poly, gf) != [0, 1]: |
| 276 | return [] |
| 277 | |
| 278 | def rec_split(poly, randv): |
| 279 | """Recursively split poly using the Berlekamp trace algorithm.""" |
| 280 | # See https://hal.archives-ouvertes.fr/hal-00626997/document. |
| 281 | assert len(poly) > 1 and poly[-1] == 1 # Require a monic poly. |
| 282 | # If poly is of the form x+a, its root is a. |
| 283 | if len(poly) == 2: |
| 284 | return [poly[0]] |
| 285 | # Try consecutive randomization factors randv, until one is found that factors poly. |
| 286 | while True: |
| 287 | # Compute the trace of (randv*x) mod poly. This is a polynomial that maps half of the |
| 288 | # domain to 0, and the other half to 1. Which half that is is controlled by randv. |
| 289 | # By taking it modulo poly, we only add a multiple of poly. Thus the result has at least |
| 290 | # the shared roots of the trace polynomial and poly still, but may have others. |
| 291 | trace = poly_tracemod(poly, randv, gf) |
| 292 | # Using the set {2^i*a for i=0..fieldsize-1} gives optimally independent randv values |
| 293 | # (no more than fieldsize are ever needed). |
| 294 | randv = gf.mul2(randv) |
| 295 | # Now take the GCD of this trace polynomial with poly. The result is a polynomial |
| 296 | # that only has the shared roots of the trace polynomial and poly as roots. |
| 297 | gcd = poly_gcd(trace, poly, gf) |
| 298 | # If the result has a degree higher than 1, and lower than that of poly, we found a |
| 299 | # useful factorization. |
| 300 | if len(gcd) != len(poly) and len(gcd) > 1: |
| 301 | break |
| 302 | # Otherwise, continue with another randv. |
| 303 | # Find the actual factors: the monic version of the GCD above, and poly divided by it. |
| 304 | factor1 = poly_monic(gcd, gf) |
| 305 | factor2, _ = poly_divmod(poly, gcd, gf) |
| 306 | # Recurse. |
| 307 | return rec_split(factor1, randv) + rec_split(factor2, randv) |
| 308 | |
| 309 | # Invoke the recursive splitting with a random initial factor, and sort the results. |
| 310 | return sorted(rec_split(poly, random.randrange(1, 1 << gf.field_size))) |
| 311 | |
| 312 | class TestPolyFindRoots(unittest.TestCase): |
| 313 | """Test class for poly_find_roots.""" |
no test coverage detected