Someone told me that the Frobenius pseudoprime algorithm take three times longer to run than the Miller–Rabin primality test but has seven times the resolution. So then if one where to run the former ten times and the later thirty times, both would take the same time to run, but the former would provide about 233% more analyse power. In trying to find out how to perform the test, the following paper was discovered with the algorithm at the end:
A Simple Derivation for the Frobenius Pseudoprime Test
There is an attempt at implementing the algorithm below, but the program never prints out a number. Could someone who is more familiar with the math notation or algorithm verify what is going on please?
Edit 1: The code below has corrections added, but the implementation for compute_wm_wm1 is missing. Could someone explain the recursive definition from an algorithmic standpoint? It is not “clicking” for me.
Edit 2: The erroneous code has been removed, and an implementation of the compute_wm_wm1 function has been added below. It appears to work but may require further optimization to be practical.
from random import SystemRandom
from fractions import gcd
random = SystemRandom().randrange
def find_prime_number(bits, test):
number = random((1 << bits - 1) + 1, 1 << bits, 2)
while True:
for _ in range(test):
if not frobenius_pseudoprime(number):
break
else:
return number
number += 2
def frobenius_pseudoprime(integer):
assert integer & 1 and integer >= 3
a, b, d = choose_ab(integer)
w1 = (a ** 2 * extended_gcd(b, integer)[0] - 2) % integer
m = (integer - jacobi_symbol(d, integer)) >> 1
wm, wm1 = compute_wm_wm1(w1, m, integer)
if w1 * wm != 2 * wm1 % integer:
return False
b = pow(b, (integer - 1) >> 1, integer)
return b * wm % integer == 2
def choose_ab(integer):
a, b = random(1, integer), random(1, integer)
d = a ** 2 - 4 * b
while is_square(d) or gcd(2 * d * a * b, integer) != 1:
a, b = random(1, integer), random(1, integer)
d = a ** 2 - 4 * b
return a, b, d
def is_square(integer):
if integer < 0:
return False
if integer < 2:
return True
x = integer >> 1
seen = set([x])
while x * x != integer:
x = (x + integer // x) >> 1
if x in seen:
return False
seen.add(x)
return True
def extended_gcd(n, d):
x1, x2, y1, y2 = 0, 1, 1, 0
while d:
n, (q, d) = d, divmod(n, d)
x1, x2, y1, y2 = x2 - q * x1, x1, y2 - q * y1, y1
return x2, y2
def jacobi_symbol(n, d):
j = 1
while n:
while not n & 1:
n >>= 1
if d & 7 in {3, 5}:
j = -j
n, d = d, n
if n & 3 == 3 == d & 3:
j = -j
n %= d
return j if d == 1 else 0
def compute_wm_wm1(w1, m, n):
a, b = 2, w1
for shift in range(m.bit_length() - 1, -1, -1):
if m >> shift & 1:
a, b = (a * b - w1) % n, (b * b - 2) % n
else:
a, b = (a * a - 2) % n, (a * b - w1) % n
return a, b
print('Probably prime:\n', find_prime_number(300, 10))
You seem to have misunderstood the algorithm completely due to not being familiar with the notation.
That comes from the line
But the b-1 doesn’t mean
1/bhere, but the modular inverse ofbmodulon, i.e. an integercwithb·c ≡ 1 (mod n). You can most easily find such acby continued fraction expansion ofb/nor, equivalently, but with slightly more computation, by the extended Euclidean algorithm. Since you’re probably not familiar with continued fractions, I recommend the latter.comes from
and misunderstands the Jacobi symbol as a fraction/division (admittedly, I have displayed it here even more like a fraction, but since the site doesn’t support LaTeX rendering, we’ll have to make do).
The Jacobi symbol is a generalisation of the Legendre symbol – denoted identically – which indicates whether a number is a quadratic residue modulo an odd prime (if
nis a quadratic residue modulop, i.e. there is akwithk^2 ≡ n (mod p)andnis not a multiple ofp, then(n/p) = 1, ifnis a multiple ofp, then(n/p) = 0, otherwise(n/p) = -1). The Jacobi symbol lifts the restriction that the ‘denominator’ be an odd prime and allows arbitrary odd numbers as ‘denominators’. Its value is the product of the Legendre symbols with the same ‘numerator’ for all primes dividingn(according to multiplicity). More on that, and how to compute Jacobi symbols efficiently in the linked article.The line should correctly read
The following lines I completely fail to understand, logically, here should follow the calculation of
Wm and Wm+1 using the recursion
An efficient method of using that recursion to compute the required values is given around formula (11) of the PDF.
The remainder of the function is almost correct, except of course that it now gets the wrong data due to earlier misunderstandings.
The (in)equality here should be modulo
integer, namelyif (w1*w_m0 - 2*w_m2) % integer != 0.Note, however, that if
nis a prime, thenwhere
(b/n)is the Legendre (or Jacobi) symbol (for prime ‘denominators’, the Jacobi symbol is the Legendre symbol), henceb^((n-1)/2) ≡ ±1 (mod n). So you could use that as an extra check, if Wm is not 2 orn-2,ncan’t be prime, nor can it be ifb^((n-1)/2) (mod n)is not 1 orn-1.Probably computing
b^((n-1)/2) (mod n)first and checking whether that’s 1 orn-1is a good idea, since if that check fails (that is the Euler pseudoprime test, by the way) you don’t need the other, no less expensive, computations anymore, and if it succeeds, it’s very likely that you need to compute it anyway.Regarding the corrections, they seem correct, except for one that made a glitch I previously overlooked possibly worse:
That applies the modulus only to
2 * wm1.Concerning the recursion for the Wj, I think it is best to explain with a working implementation, first in toto for easy copy and paste:
Then with explanations in between:
We need the value of W1, the index of the desired number, and the number by which to take the modulus as input. The value W0 is always 2, so we don’t need that as a parameter.
Call it as
in
frobenius_pseudoprime(aside: not a good name, most of the numbers returningTrueare real primes).We initialise
aandbto W0 and W1 respectively. At each point,aholds the value of Wj andbthe value of Wj+1, wherejis the value of the bits ofmso far consumed. For example, withm = 13, the values ofj,aandbdevelop as follows:The bits are consumed left-to-right, so we have to find the first set bit of
mand place our ‘pointer’ right before itI subtracted a bit from the computed logarithm just to be entirely sure that we don’t get fooled by a floating point error (by the way, using
loglimits you to numbers of at most 1024 bits, about 308 decimal digits; if you want to treat larger numbers, you have to find the base-2 logarithm ofmin a different way, usinglogwas the simplest way, and it’s just a proof of concept, so I used that here).Shift the mask until it’s greater than
m,so the set bit points just beforem‘s first set bit. Then shift one position back, so we point at the bit.If the next bit is set, the value of the initial portion of consumed bits of
mgoes fromjto2*j+1, so the next values of the W sequence we need are W2j+1 foraand W2j+2 forb. By the above recursion formula,Since
awas Wj andbwas Wj+1,abecomes(a*b - W_1) % nandbbecomes(b * b - 2) % n.If the next bit is not set, the value of the initial portion of consumed bits of
mgoes fromjto2*j, soabecomes W2j = (Wj2 – 2) (mod n), andbbecomesW2j+1 = (Wj * Wj+1 – W1) (mod n).
Move the pointer to the next bit. When we have moved past the final bit,
maskbecomes 0 and the loop ends. The initial portion of consumed bits ofmis now all ofm‘s bits, so the value is of coursem.Then we can
Some additional remarks:
Primes are not too frequent among the larger numbers, so just picking random numbers is likely to take a lot of attempts to hit one. You will probably find a prime (or probable prime) faster if you pick one random number and check candidates in order.
Another point is that such a test as the Frobenius test is disproportionally expensive to find that e.g. a multiple of 3 is composite. Before using such a test (or a Miller-Rabin test, or a Lucas test, or an Euler test, …), you should definitely do a bit of trial division to weed out most of the composites and do the work only where it has a fighting chance of being worth it.
Oh, and the
is_squarefunction isn’t prepared to deal with arguments less than 2, divide-by-zero errors lurk there,should help.