Python / Number Theory / Cryptography
An implementation of the quadratic sieve, the sub-exponential integer factoring algorithm behind practical attacks on small RSA keys. Written in Python as a notebook following Carl Pomerance's 2008 paper directly, then with vectorized performance optimizations.
The algorithm selects a smoothness bound B via the heuristic exp(0.5*sqrt(ln(n)*ln(ln(n)))) and builds a factor base from the primes p below B where the Legendre symbol (n/p) equals 1, the only primes that can divide \(Q(x) = x^2 - n\). For each such prime we solve the modular square root congruence \(x^2 \equiv n \pmod p\) with the Tonelli-Shanks algorithm; the two roots define the arithmetic progressions along which the sieve accumulates.
Sieving proceeds in blocks over intervals starting at \(\lceil\sqrt{n}\rceil\). Each block is a NumPy float32 array, and for every factor-base prime we add \(\log_2 p\) at the root offsets using vectorized slice arithmetic (sieve[offset::p] += log(p)) instead of per-index loops. Indices whose accumulated log-sum exceeds a threshold are flagged as candidate B-smooth values; the threshold sits at 75% of the expected magnitude \(\log_2 Q(x)\) to tolerate rounding error and small unsieved factors.
Candidates are confirmed via trial division over the factor base, and their exponent vectors are collected until the relation count exceeds the factor base size by a safety margin. We reduce the matrix of exponent parities over GF(2) with Gaussian elimination and extract a basis for its null space; each kernel vector selects a subset of relations whose product is a perfect square. This yields a congruence of squares \(X^2 \equiv Y^2 \pmod n\), and gcd(X - Y, n) reveals a non-trivial factor, retrying with the next kernel vector whenever the split is trivial.