pollardsrho
Version:
integer factorization using Pollard's rho algorithm with Brent cycle detection, Miller–Rabin primality test
156 lines (142 loc) • 4.08 kB
text/typescript
// Pollard's rho implementation
// See https://en.algorithmica.org/hpc/algorithms/factorization/#pollard-brent-algorithm
// And https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm#Variants
//@external("env", "consoleLog")
//declare function consoleLog(a:u64): void;
// ((a * b) >> 64) from https://doc.lagout.org/security/Hackers%20Delight.pdf
function mulhu(a:u64, b:u64):u64 {
const ahi = a >> 32;
const alo = a & 0xFFFFFFFF;
const bhi = b >> 32;
const blo = b & 0xFFFFFFFF;
const t = ahi * blo + ((alo * blo) >> 32);
return ahi * bhi + (t >> 32) + ((alo * bhi + (t & 0xFFFFFFFF)) >> 32);
}
function modInverseMod2pow64(a:u64):u64 {
let aInv = a;
for (let e = 2; e < 64; e += e) {
aInv = u64(aInv * (2 - u64(a * aInv)));
}
return aInv;
}
function montgomeryReduce(phi:u64, plo:u64, n:u64, nInv:u64):u64 {
const x = mulhu(u64(plo * nInv), n);
return phi - x + (phi < x ? n : 0);
}
function montgomeryMultiply(x:u64, y:u64, n:u64, nInv:u64):u64 {
return montgomeryReduce(mulhu(x, y), u64(x * y), n, nInv);
}
function mulMod(a:u64, b:u64, m:u64):u64 {
let s = u64(0);
while (a !== 0) {
s = ((a & 1) !== 0 ? s - (m - b) + (s < m - b ? m : 0) : s);
b = b - (m - b) + (b < m - b ? m : 0);
a >>= 1;
}
return s;
}
function montgomeryTransform(x:u64, n:u64):u64 {
// (x * 2**64) % n :
return mulMod(x, u64(-1) % n + 1, n);
}
export function gcdBinary(a:u64, b:u64):u64 {
if (a === 0) {
return b;
}
if (b === 0) {
return a;
}
const z = i64.ctz(a | b);
a >>= i64.ctz(a);
b >>= i64.ctz(b);
do {
const absDiff = a < b ? b - a : a - b; // abs(a - b)
b = a < b ? a : b; // min(a, b)
a = absDiff;
a >>= i64.ctz(a);
} while (a !== 0);
b <<= z;
return b;
}
function f(x:u64, c:u64, n:u64, nInv:u64):u64 {
let y = montgomeryMultiply(x, x, n, nInv);
y = y - (n - c) + (y < (n - c) ? n : 0);
return y;
}
function absDiff(a:u64, b:u64):u64 {
return a < b ? b - a : a - b;
}
function internal(n:u64, x0:u64, c:u64, maxSteps:u32):u64 {
const nInv = modInverseMod2pow64(n);
x0 = montgomeryTransform(x0, n);
c = montgomeryTransform(c, n);
const one = u64(1); // (2**64)**-1 mod n in Montgomery Form
// Brent cycle detection:
let hare = x0;
let i = u32(0);
let power = u32(1);
let product = one;
let productStartHare = hare;
let productStartStep = i;
let cycleFound = false;
while (i <= maxSteps) {
const tortoise = hare;
do {
hare = f(hare, c, n, nInv);
i += 1;
if (i <= ((power * 3) >> 2)) { // see https://maths-people.anu.edu.au/~brent/pd/rpb051i.pdf
product = one;
productStartHare = hare;
productStartStep = i;
} else {
const d = absDiff(hare, tortoise);
if (product === one) {
product = d;
} else {
product = montgomeryMultiply(product, d, n, nInv);
}
if (i === power || i % 128 === 0 || cycleFound) {
const factor = gcdBinary(product, n);
if (factor !== 1) {
if (factor !== n) {
return factor;
}
if (cycleFound) {
return n;
}
cycleFound = true;
hare = productStartHare;
i = productStartStep;
}
product = one;
productStartHare = hare;
productStartStep = i;
}
}
} while (i !== power);
power <<= 1;
}
return 0;
}
export function PollardsRho64(n:u64, maxSteps:i32):u64 {
if (n <= 2) {
return 0;
}
if (n % 2 === 0) {
return 2;
}
const x0 = u64(2);
let c = u64(0);
let g = n;
do {
// it is much better to switch c, not x0
// f(x) = x^2 + c, some values are not good with x0=2 (from my tests: 0, -2, -6, -7)
c += 1;
if (c > 42) {
// n is prime?
return 0;
}
g = internal(n, x0, c, maxSteps);
} while (g === n);
return g;
}