{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Problem:Given a polynomial $f\\in \\mathbb F_q[x]$, determine the $\\mathbb F_q$-rational roots of $f$.
Solution:Rabin's algorithm!
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "p=61; F=GF(p); R.=PolynomialRing(F); f = x^8-2*x+5" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[(44, 1), (39, 1), (15, 1)]" ] }, "execution_count": 2, "metadata": { }, "output_type": "execute_result" } ], "source": [ "f.roots()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "(x + 17) * (x + 22) * (x + 46) * (x^2 + 46*x + 1) * (x^3 + 52*x^2 + 41*x + 33)" ] }, "execution_count": 3, "metadata": { }, "output_type": "execute_result" } ], "source": [ "f.factor()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[(44, 1),\n", " (39, 1),\n", " (15, 1),\n", " (46*z6^5 + 32*z6^4 + 56*z6^3 + 22*z6^2 + 48*z6 + 44, 1),\n", " (7*z6^5 + 35*z6^4 + 2*z6^3 + 4*z6 + 37, 1),\n", " (15*z6^5 + 29*z6^4 + 5*z6^3 + 39*z6^2 + 13*z6 + 32, 1),\n", " (28*z6^5 + 30*z6^4 + 20*z6^3 + 26*z6^2 + 21*z6 + 30, 1),\n", " (26*z6^5 + 57*z6^4 + 39*z6^3 + 35*z6^2 + 36*z6 + 3, 1)]" ] }, "execution_count": 4, "metadata": { }, "output_type": "execute_result" } ], "source": [ "f.change_ring(GF(p^6)).roots()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Theorem:$\\mathbb F_{p^n} = \\{\\alpha\\in \\overline{\\mathbb F}_p: \\alpha^{p^n}=\\alpha\\}$, in other words, we have $$x^{p^n}-x=\\prod_{\\alpha\\in \\mathbb F_{p^n}}(x-\\alpha)$$
Proof:For $n=1$ this follows from Fermat's little theorem. If we identify $\\mathbb F_p\\simeq \\mathbb Z/p\\mathbb Z$, we have $a^{p-1}=1\\bmod p$ for $a\\in [0,p-1]$, so the $p$ elements of $\\mathbb Z/p\\mathbb Z$ are precisely the roots of $x^p-x$.
\n", " For $n>1$ this is true by definition: $\\mathbb F_{p^n}:=\\overline{\\mathbb{F}}_{p}^{\\langle \\alpha \\mapsto \\alpha^{p^n}\\rangle}$ is the fixed field of the $p^n$-power Frobenius automorphism of $\\overline{\\mathbb F}_p$, equivalently, the splitting field of $x^{p^n}-x$ over $\\mathbb F_p$.
Corollary:For any prime power $q$ we have $\\mathbb F_{q^n} = \\{\\alpha\\in \\overline{\\mathbb F}_q: \\alpha^{q^n}=\\alpha\\}$ and $x^{q^n}-x=\\prod_{\\alpha\\in \\mathbb F_{q^n}}(x-\\alpha)$.
" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 5, "metadata": { }, "output_type": "execute_result" } ], "source": [ "product([x-i for i in F]) == x^p-x" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 6, "metadata": { }, "output_type": "execute_result" } ], "source": [ "product([x.change_ring(GF(p^2))-i for i in GF(p^2)]) == x^(p^2)-x" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", "
Corollary:Let $f\\in \\mathbb F_q[x]$ be nonzero and let $S:=\\{\\alpha\\in \\mathbb F_{q^n}:f(\\alpha)=0\\}$. Then $$\\gcd(f(x),x^{q^n}-x)=\\prod_{\\alpha\\in S}(x-\\alpha).$$ In particular, $\\#S=\\deg(\\gcd(f(x),x^{q^n}-x)$.
\n", "As usual, $\\gcd(a,b)$ denotes the unique monic representative of the nonzero principal ideal $(a,b)$ of $\\mathbb F_q[x]$.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 57 µs, sys: 0 ns, total: 57 µs\n", "Wall time: 60.6 µs\n" ] }, { "data": { "text/plain": [ "3" ] }, "execution_count": 7, "metadata": { }, "output_type": "execute_result" } ], "source": [ "%time gcd(f,x^p-x).degree()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 465 µs, sys: 90 µs, total: 555 µs\n", "Wall time: 562 µs\n" ] }, { "data": { "text/plain": [ "5" ] }, "execution_count": 8, "metadata": { }, "output_type": "execute_result" } ], "source": [ "%time gcd(f,x^(p^2)-x).degree()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 42.7 ms, sys: 330 µs, total: 43 ms\n", "Wall time: 43 ms\n" ] }, { "data": { "text/plain": [ "6" ] }, "execution_count": 9, "metadata": { }, "output_type": "execute_result" } ], "source": [ "%time gcd(f,x^(p^3)-x).degree()" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "%time gcd(f,x^(p^6)-x).degree()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", "
Key point:Make sure you exponentiate in the right ring!

\n", "The first step in computing $\\gcd(f(x),x^q-x)$ is to compute $x^q-x\\bmod f$ (note that $q$ may be exponentially large, but $\\deg f$ certainly won't be).
\n", " If $\\pi_f\\colon \\mathbb{F}_q[x]\\to\\mathbb{F}_q[x]/(f)$ is the ring homomorphism defined by $x\\mapsto x\\bmod f$, then $$\\pi_f(x^q-x)=\\pi_f(x^q)-\\pi_f(x)=\\pi_f(x)^q-\\pi_f(x),$$ so the order of operations makes no mathematical difference, but the RHS can be computed exponentially faster than either the middle or the LHS!

The complexity of computing $x^q\\bmod f$ with Euclidean division is quasi-linear in $q$,
\n", " The complexity of computing $\\pi_f(x)^q$ using binary exponentiation is quasi-linear in $\\log q$.
" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "pif=R.quotient(f)\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "%time print(pif(x)^(p^6))" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "p=2^255-19; F=GF(p); R.=PolynomialRing(F); f=x^8-2*x+5; pif=R.quotient(f);\n", "%time print(f.factor())" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "%time print(pif(x)^p)\n", "%time print(pif(x)^(p^2))\n", "%time print(pif(x)^(p^3))\n", "%time print(pif(x)^(p^4))\n", "%time print(pif(x)^(p^10))" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def distinct_root_poly(f):\n", " q = f.base_ring().cardinality()\n", " pif = f.parent().quotient(f)\n", " return gcd(f,(pif(x)^q).lift()-x)\n", "\n", "def count_distinct_roots(f):\n", " return distinct_root_poly(f).degree()" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "p=61; F=GF(p); R.=PolynomialRing(F); f=x^8-2*x+5; pif=R.quotient(f)\n", "for n in range(1,7):\n", " print(\"%d distinct roots over F_{%d^%d}\" % (count_distinct_roots(f.change_ring(GF(p^n))),p,n))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

Okay, we can count (distinct) roots, but what if we actually want to know what they are?

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "gcd(f,(pif(x)^p-x).lift())" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "s = (p-1) // 2 # we assume p (or more generally q) is odd\n", "gcd(f,(pif(x)^s-1).lift())" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "print(gcd(f,(pif(x)^p-x).lift())/gcd(f,(pif(x)^s-1).lift()))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

Excellent, we managed to split the roots of $f$ by picking out two that are roots of $x^s-1$, where $s=(q-1)/2$ (we are assuming $q$ is odd).
\n", " What are the roots of $x^s-1$ anyway?

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "print(sorted((x^s-1).roots()))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

My keen sense of pattern recognition (or a recollection of Euler's criterion) tells me these are the roots of $x^s-1$ are precisely the squares (quadratic residues) in $\\mathbb F_p^\\times.$

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "product([x-a^2 for a in F if a < p-a]) == (x^s-1)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

But if $f$ has multiple roots that are quadratic residues (or maybe none), then what?

" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

Rabin: Instead of $x^s-1$, use $(x+\\delta)^s-1$ for a random $\\delta\\in \\mathbb F_q$.

" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Definition:Let us say that $\\alpha,\\beta\\in \\mathbb F_q$ are of different type if both are nonzero and $\\alpha^s\\ne \\beta^s$, in other words, one is a quadratic residue and one is not.
Theorem:For every pair of distinct $\\alpha,\\beta\\in \\mathbb F_q$ we have $$\\#\\{\\delta\\in \\mathbb F_q:\\alpha+\\delta \\text{ and } \\beta+\\delta \\text{ are of different type }\\} = \\frac{q-1}{2}.$$
Proof:See Section 3.11 in the lecture notes for a short easy proof.
Corollary:Let $f\\in \\mathbb F_q[x]$, let $g(x)=\\gcd(f,x^q-x)$, let $s=(q-1)/2\\in \\Z$, and let $\\delta$ be a uniformly random element of $\\mathbb F_q$.
If $g$ has more than one nonzero root, then $$\\Pr[0<\\deg\\gcd(g,(x+\\delta)^s-1)<\\deg g] \\ge \\frac{1}{2}.$$
" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def rational_root(f):\n", " \"\"\"Returns a rational root of f or None if f has no rational roots.\"\"\"\n", " q = f.base_ring().cardinality()\n", " assert q % 2 == 1\n", " s = (q-1) // 2 # Note // yields an integer (whereas / would yield a rational)\n", " x = f.parent().gen()\n", " if f[0]==0:\n", " return f.base_ring()(0)\n", " g = distinct_root_poly(f)\n", " print(\"Initial g = gcd(f,x^q-x) = %s\" % g)\n", " pig = g.parent().quotient(g)\n", " while g.degree() > 1:\n", " delta = g.base_ring().random_element()\n", " print(\"delta = %s\" % delta)\n", " h = gcd(g, ((pig(x)-delta)^s-1).lift())\n", " if 0 < h.degree() < g.degree():\n", " g = h if 2*h.degree() <= g.degree() else g // h # as above, we use // to get a polynomial\n", " pig = g.parent().quotient(g)\n", " print(\"Better g = %s\" % g)\n", " return -g[0] if g.degree() == 1 else None\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", "
Theorem:Let $n=\\log q$ and $d=\\deg f$. The expected running time of $\\texttt{rational\\_root}$ is $\\tilde O(n^2d)$ bit operations.
\n", "

Here the soft-O notation means up to a poly-logarithmic factors. See the lecture notes for a more precise bound and a proof.

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "for i in range(5):\n", " r = rational_root(f)\n", " print(\"Found root %s\\n\" % r)\n", " assert f(r) == 0\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "h = product([x-i for i in range(1,20)])\n", "for i in range(5):\n", " r = rational_root(h)\n", " print(\"Found root %s\\n\" % r)\n", " assert h(r) == 0\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "p=2^61-1; F=GF(p); R.=PolynomialRing(F); f=x^8-2*x+5; h = product([x-i for i in range(1,20)])\n", "for i in range(3):\n", " %time print(\"Found root %s\\n\" % rational_root(h))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

What if we want all the rational roots?

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def distinct_rational_roots(f,recurse=False):\n", " \"\"\"Returns a list of all the distinct rational roots of f.\"\"\"\n", " q = f.base_ring().cardinality()\n", " assert q % 2 == 1\n", " s = (q-1) // 2\n", " x = f.parent().gen()\n", " roots = []\n", " if recurse:\n", " g = f\n", " else:\n", " g = distinct_root_poly(f)\n", " if g(0) == 0:\n", " roots = [g.base_ring()(0)]\n", " g = g // x\n", " if g.degree() <= 1:\n", " return [-g[0]] if g.degree() == 1 else []\n", " while True:\n", " delta = g.base_ring().random_element()\n", " pig = g.parent().quotient(g)\n", " h = gcd(g, ((pig(x)-delta)^s-1).lift())\n", " if 0 < h.degree() < g.degree():\n", " roots += distinct_rational_roots(h, True)\n", " roots += distinct_rational_roots(g//h, True)\n", " return roots\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", "
Theorem:Let $n=\\log q$ and $d=\\deg f$. The expected running time of $\\texttt{distinct\\_rational\\_root}$ is $\\tilde O(n^2d)$ bit operations.
\n", "

This looks the same as the bound for $\\texttt{rational\\_root}$, but in fact there is an extra factor of $\\log d$ hidden in the soft-O notation.

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "%time distinct_rational_roots(f)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "%time distinct_rational_roots(product([x-F.random_element() for i in range(10)]))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

What if we want all the rational roots and their multiplicities?

" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

Yun: Compute the squarefree factorization!

\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Definition:The squarefree factorization of a monic $f\\in \\mathbb F_q[x]$ is the unique list of monic squarefree polynomials $g_1,\\ldots,g_m$ with $g_m\\ne 1$ for which $f=g_1g_2^2\\cdots g_m^m$.
Key fact:If $f=f_1\\cdots f_n$ is the factorization of a monic $f\\in \\mathbb F_q[x]$ into irreducibles then $f_i=f_j$ for some $i\\ne j$ if and only if $f_i|\\gcd(f,f')$.
This holds in any perfect field.
" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def squarefree_factorization(f):\n", " \"\"\"Yun's algorithm to compute a list of polynomials g_1,..,g_m such that f=lc(f)g_1g_2^2...g_m^m with g_m != 1, assuming deg(f) < char(Fq). We include g_0=1 for convenience.\"\"\"\n", " assert f.degree() < f.base_ring().characteristic()\n", " if not f.is_monic:\n", " f = f.monic()\n", " df = f.derivative()\n", " u = gcd(f,df)\n", " v, w = f//u, df//u\n", " gs =[f.parent()(1)]\n", " while True:\n", " g = gcd(v, w-v.derivative()); gs.append(g)\n", " if v.degree() == g.degree():\n", " return gs\n", " v, w = v//g, (w-v.derivative())//g" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", "
Theorem:Let $n=\\log q$ and $d=\\deg f$. The running time of $\\texttt{squarefree\\_factorization}$ is $\\tilde O(nd)$ bit operations.
\n", "

This is negligible compared to the complexity of $\\texttt{distinct\\_rational\\_roots}$, so we might as well call $\\texttt{squarefree\\_factorization}$ first.
Even if we only want distinct roots, this will actually save time if $f$ is not squarefree.

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "h = product([(x^2+F.random_element()*x+F.random_element())^i for i in range(1,10)])\n", "%time gs = squarefree_factorization(h)\n", "assert h == product([gs[i]^i for i in range(len(gs))])\n", "gs" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def rational_roots(f):\n", " \"\"\"Returns a list of tuples identifying the rational roots of f and their multiplicities.\"\"\"\n", " gs = squarefree_factorization(f)\n", " return reduce(lambda x, y: x+y,[[(a,i) for a in distinct_rational_roots(gs[i])] for i in range(1,len(gs))])" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "h = product([(x-F.random_element())^i for i in range(1,6)])\n", "%time rational_roots(h)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

What if we want to factor $f$ into irreducibles?

\n", "

Rabin: You can do that with my root-finding algorithm.

\n", "

Cantor-Zassenhaus: Yes, but that requires working in extension fields, and with our algorithm there is no need!

\n", "

The first step is to compute the distinct degree factorization

\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Definition:The distinct degree factorization of a monic $f\\in \\mathbb F_q[x]$ is the unique list of polynomials $g_1,\\ldots,g_d$ with $f=g_1\\cdots g_d$ such that each $g_i$ is a (possibly empty) product of distinct monic irreducible polynomials of degree $i$, for $1\\le i\\le d=\\deg f$.
Key point:This can be done deterministically by taking succesive gcds with $x^{q^i}-x$.
" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def distinct_degree_poly(f,i):\n", " q = f.base_ring().cardinality()\n", " pif = f.parent().quotient(f)\n", " return gcd(f,(pif(x)^(q^i)).lift()-x)\n", "\n", "def distinct_degree_factorization(f, squarefree=False):\n", " \"\"\"\n", " Computes the distinct degree factorization of f as list gs with gs[0]=1 and gs[i]=g_i such that f=prod_i gs[i]^i.\n", " The optional parameter squarefree inidicates whether f is known to be squarefree (usually one computes the squarefree factorization first).\n", " \"\"\"\n", " if not squarefree:\n", " return naive_distinct_degree_factorization(f)\n", " d = f.degree()\n", " gs = [f.parent()(1) for i in range(d+1)]\n", " for i in range(1,d+1):\n", " if 2*i > f.degree():\n", " gs[f.degree()] = f\n", " break\n", " gs[i] = distinct_degree_poly(f,i)\n", " f = f // gs[i]\n", " return gs\n", "\n", "def naive_distinct_degree_factorization(f):\n", " \"\"\"\n", " Computed distinct degree factorization of arbitrary f by combining distinct degree factorization of polys in square free factorization.\n", " There is no reason to ever use this, it is provided just for the sake of completeness\n", " \"\"\"\n", " d = f.degree()\n", " gs = [f.parent()(1) for i in range(d+1)]\n", " hs = [distinct_degree_factorization(g, squarefree=True) for g in squarefree_factorization(f)]\n", " # combine results (this is silly, we are undoing work here)\n", " for i in range(1,len(hs)):\n", " for j in range(1,len(hs[i])):\n", " gs[j] *= hs[i][j]^i\n", " return gs\n", "\n", "def facpat(f):\n", " \"\"\"Returns a dictionary {d:i} whose keys are the degrees of the irreducible factors of f and whose values are the number of irreducible factors of that degree.\"\"\"\n", " X = {}\n", "\n", " gs = squarefree_factorization(f)\n", " for i in range(1,len(gs)):\n", " gis = distinct_degree_factorization(gs[i],squarefree=True)\n", " for j in range(1,len(gis)):\n", " if gis[j].degree() > 0:\n", " X[j] = X.get(j,0) + i * (gis[j].degree() // j)\n", " return X" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "%time facpat(f)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "h = (x^9-1)^2*(x^32-1)\n", "assert h == product(distinct_degree_factorization(h))\n", "%time print(facpat(h))\n", "print([(r[0].degree(),r[1]) for r in h.factor()])" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "

The key innovation introduced by Cantor-Zassenhaus is that when searching for irreducible factors $g$ of $f$ of degree $j$, rather than computing $\\gcd(f,(x+\\delta)^s-1))$ using a random $\\delta\\in \\mathbb F_{q^j}$ and computing the gcd in $\\mathbb F_{q^j}[x]$ (which is how one would go about finding a root of $g$ using Rabin's algorithm), it is better to compute $\\gcd(f,u^s-1)$ in $\\mathbb F_q[x]$ using a random $u\\in \\mathbb F_q[x]$ coprime to $f$ with $\\deg u < \\deg f$. This faster because we are working the ring $\\mathbb F_q[x]/(f)$ rather than $\\mathbb F_{q^j}[x]/(f)$ (so the bit-size of each element is smaller by a factor of $j$), and the probability of success is the same.

\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Theorem:Let $f\\in \\mathbb F_q[x]$ be the product of $r\\ge 2$ distinct monic irreducible polynomials of degree $j$ and let $u\\in \\mathbb F_q[j]$ be a random polynomial coprime to $f$ with $\\deg u < \\deg f$. Then $$\\Pr[0<\\deg\\gcd(f,u^s-1)<\\deg f] = 2^{1-r} \\ge \\frac{1}{2}.$$
Proof:This follows from applying the Chinese Remainder Theorem to $\\mathbb F_q[x]/(f)\\simeq \\mathbb F_{q^j}^r$. See Section 3.12 of the lecture notes for details.
\n", "\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def equal_degree_factorization(f,j):\n", " \"\"\"Completely factors a monic polynomial f in Fq[x] that is known to be the product of distinct irreducible polynomials of degree j\"\"\"\n", " d = f.degree()\n", " assert d % j == 0\n", " if d == 0:\n", " return []\n", " elif d == j:\n", " return [f]\n", " Fq = f.base_ring(); q = Fq.cardinality()\n", " assert q%2 == 1\n", " s = (q^j-1) // 2\n", " x = f.parent().gen()\n", " pif = f.parent().quotient(f)\n", " while True:\n", " # generate a random polynomial of degree < deg(f), note f.parent()(v) converts a vector of Fq coefficients to a polynomial\n", " u = f.parent()([Fq.random_element() for i in range(d)])\n", " h = gcd(f,u)\n", " if 0 < h.degree() < d:\n", " return equal_degree_factorization(h,j) + equal_degree_factorization(f//h,j)\n", " # at this point we know u is coprime to f\n", " h = gcd(f,(pif(u)^s-1).lift())\n", " if 0 < h.degree() < d:\n", " return equal_degree_factorization(h,j) + equal_degree_factorization(f//h,j)\n", "\n", "def factorization(f):\n", " \"\"\"Computes the complete factorization of a non-constant f in Fq[x] using the Cantor-Zassenhaaus algorithm (assumes q is odd).\"\"\"\n", " assert f.degree() > 0\n", " if not f.is_monic:\n", " f = f.monic()\n", " x = f.parent().gen()\n", " res = []\n", " gs = squarefree_factorization(f)\n", " for i in range(1,len(gs)):\n", " gis = distinct_degree_factorization(gs[i],squarefree=True)\n", " for j in range(1,len(gis)):\n", " res += [(g,i) for g in equal_degree_factorization(gis[j],j)]\n", " # sort output to make it canonical (independent of random choices)\n", " return sorted(res)\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "assert factorization(f) == sorted(list(f.factor()))\n", "print(factorization(f))\n", "%timeit -n 10 factorization(f)\n", "%timeit -n 10 f.factor()" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "assert factorization(h) == sorted(list((h).factor()))\n", "print(factorization(h))\n", "%timeit -n 1 -r 5 factorization(h)\n", "%timeit -n 1 -r 5 h.factor()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", " \n", " \n", " \n", " \n", "
Theorem:Let $n=\\log q$ and $d=\\deg f$. The expected running time of $\\texttt{factorization}$ is $\\tilde O(n^2d^2)$ bit operations.
\n", "

There are other factorization methods based on linear algebra and modular composition that improve the dependence on $d$.
\n", "Currently the best asymptotic bound is achieved by the Kedlaya-Umans algorithm with an expected running time of $\\tilde O(d^{3/2}n + dn^2)$.

" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "p=2^255-19; F=GF(p); R.=PolynomialRing(F); f=x^8-2*x+5; h = product([x-i for i in range(1,20)])\n", "assert factorization(f^3+f^2+1) == sorted(list((f^3+f^2+1).factor()))\n", "%time print(factorization(f))\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ ] } ], "metadata": { "kernelspec": { "display_name": "SageMath 9.2", "language": "sagemath", "metadata": { "cocalc": { "description": "Open-source mathematical software system", "priority": 1, "url": "https://www.sagemath.org/" } }, "name": "sage-9.2", "resource_dir": "/ext/jupyter/kernels/sage-9.2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }