1

tl;dr How do you get an extremely large 80 digit BigInt exact prime, not a "probable" prime? It appears the code I have found and attached below only gives you a "probable" prime. Now the question is how to then figure out if it is an "exact" prime (i.e. not probable, but actual)?


I was led to this BigInt "random value between" code for generating a random BigInt between a min and max, and this BigInt prime number test code, which I've pasted together below. I then added a simple while loop to generate a bigint of a certain magnitude, and check for prime (and in my case also that the prime is p ≡ 3 mod 4 (prime % 4 === 3):

let i = 0n

while (i < 1000000000n) {
  let n = randomBigIntBetween(
    1000000000100000000010000000001000000000100000000010000000001000000000n,
    10000000001000000000100000000010000000001000000000100000000010000000001000000000n
  )
  if (isPrime(n) && n % 4n === 3n) {
    console.log(String(n))
  }

  i++
}

function randomBigIntBetween(minInclusive, maxExclusive) {
  var maxInclusive = (maxExclusive - minInclusive) - BigInt(1)
  var x = BigInt(1)
  var y = BigInt(0)
  while(true) {
     x = x * BigInt(2)
     var randomBit = BigInt(Math.random()<0.5 ? 1 : 0)
     y = y * BigInt(2) + randomBit
     if(x > maxInclusive) {
       if (y <= maxInclusive) { return y + minInclusive }
       // Rejection
       x = x - maxInclusive - BigInt(1)
       y = y - maxInclusive - BigInt(1)
     }
  }
}

// Javascript program Miller-Rabin primality test
// based on JavaScript code found at https://www.geeksforgeeks.org/primality-test-set-3-miller-rabin/

// Utility function to do
// modular exponentiation.
// It returns (x^y) % p
function power(x, y, p)
{

    // Initialize result
    // (JML- all literal integers converted to use n suffix denoting BigInt)
    let res = 1n;

    // Update x if it is more than or
    // equal to p
    x = x % p;
    while (y > 0n)
    {

        // If y is odd, multiply
        // x with result
        if (y & 1n)
            res = (res*x) % p;

        // y must be even now
        y = y/2n; // (JML- original code used a shift operator, but division is clearer)
        x = (x*x) % p;
    }
    return res;
}


// This function is called
// for all k trials. It returns
// false if n is composite and
// returns false if n is
// probably prime. d is an odd
// number such that d*2<sup>r</sup> = n-1
// for some r >= 1
function millerTest(d, n)
{
    // (JML- all literal integers converted to use n suffix denoting BigInt)

    // Pick a random number in [2..n-2]
    // Corner cases make sure that n > 4
    /*
        JML- I can't mix the Number returned by Math.random with
        operations involving BigInt. The workaround is to create a random integer
        with precision 6 and convert it to a BigInt.
    */
    const r = BigInt(Math.floor(Math.random() * 100_000))
    // JML- now I have to divide by the multiplier used above (BigInt version)
    const y = r*(n-2n)/100_000n
    let a = 2n + y % (n - 4n);

    // Compute a^d % n
    let x = power(a, d, n);

    if (x == 1n || x == n-1n)
        return true;

    // Keep squaring x while one
    // of the following doesn't
    // happen
    // (i) d does not reach n-1
    // (ii) (x^2) % n is not 1
    // (iii) (x^2) % n is not n-1
    while (d != n-1n)
    {
        x = (x * x) % n;
        d *= 2n;

        if (x == 1n)
            return false;
        if (x == n-1n)
            return true;
    }

    // Return composite
    return false;
}

// It returns false if n is
// composite and returns true if n
// is probably prime. k is an
// input parameter that determines
// accuracy level. Higher value of
// k indicates more accuracy.
function isPrime( n, k=40)
{
    // (JML- all literal integers converted to use n suffix denoting BigInt)
    // Corner cases
    if (n <= 1n || n == 4n) return false;
    if (n <= 3n) return true;

    // Find r such that n =
    // 2^d * r + 1 for some r >= 1
    let d = n - 1n;
    while (d % 2n == 0n)
        d /= 2n;

    // Iterate given nber of 'k' times
    for (let i = 0; i < k; i++)
        if (!millerTest(d, n))
            return false;

    return true;
}

So far it printed out a few primes for me within the range, or what I think should be primes, right? I don't know enough of the math involved or the "miller test" for primes to know if this algorithm is actually finding an exact prime, or is finding something that might be a prime.

The author from the corresponding blog post opens by saying:

The Miller-Rabin Primality Test is a reliable test for prime numbers, even though it can only determine the probability that a number is prime.

(accentuation added)

So as far as I can tell, this algorithm seems to only get us part of the way there? What must we do so that we can build a list of what are guaranteed to be prime numbers? Assuming we want extremely large BigInt primes...

Actually, for my current use case, I need to find primes between 70 and 80 digits, but I would like to know how to find primes for arbitrary sized digits up to 65536 digits if possible.

Knowing that "a prime number has exactly two factors — 1 and the number itself", means we need to find the factors of the BigInt somehow, I think. That lead me here and also to this function:

function primeFactors(n){
  const factors = []

  let divisor = 2n
  let i = 0

  while (n > 2n) {
    if (n % divisor == 0n) {
      factors.push(divisor)
      n = n / divisor
    } else{
      divisor++
    }

    i++

    if (i % 100 === 0) {
      console.log(i)
    }
  }

  console.log(i)

  return factors
}

I then added it to my original while loop:

while (true) {
  let n = rbigint(
    1000000000100000000010000000001000000000100000000010000000001000000000n,
    10000000001000000000100000000010000000001000000000100000000010000000001000000000n
  )
  if (isPrime(n) && n % 4n === 3n) {
    const factors = primeFactors(n)
    console.log(factors)
    console.log(String(n))
  }
}

As you can see, I also added those console.log statements in order to debug how many iterations were going on, because calling primeFactor was hanging. Well after a few seconds I cancelled the process having logged 22481400 iterations without seemingly being close to finishing, I'm not sure how long it would take. Trying to only log every 10 million iterations, it just chugs away, never completing. I cancelled after 300000000 iterations to calculate the factors of the first number for which isPrime(n) && n % 4n === 3n was true. It seems we would need at least 300000000300000000300000000300000000300000000300000000 or some crazy number of iterations to complete the factorization.... I don't know how to calculate this part, but wondering how to get the prime anyways.

So the question is, how do I get an "exact" prime, not a "probable" prime, in JavaScript, when targeting these extremely large BigInt values?

Lance
  • 75,200
  • 93
  • 289
  • 503
  • 1
    It doesn't seem like this is a coding problem. This seems like a math problem, and perhaps this should be asked at [math.stackexchange.com](https://math.stackexchange.com/)? – Wyck Jan 12 '22 at 14:41
  • It is absolutely a coding problem, I am literally trying to write this code right this second. – Lance Jan 12 '22 at 14:46
  • 4
    I count 6 question marks in your post. The questions you asked are: _How do you get an extremely large 80 digit BigInt exact prime, not a "probable" prime? how to then figure out if it is an "exact" prime (i.e. not probable, but actual)? ...should be primes, right? I don't know enough of the math involved... ...this algorithm seems to only get us part of the way there? What must we do so that we can build a list of what are guaranteed to be prime numbers? how do I get an "exact" prime, not a "probable" prime...?_ None are about code. Every single one is about the math of the algorithm. – Wyck Jan 12 '22 at 14:53
  • _how do I get an "exact" prime, not a "probable" prime, in JavaScript_ – Lance Jan 12 '22 at 14:55
  • 2
    The fastest method I'm aware of is a variant of Pocklington's Theorem that generates guaranteed primes. Actually, it is probably faster to weed out non-primes with a single MR test and then use ECPP to prove primality of the result, but ECPP is a hell of a lot of tricky code. And even then, as in @MrSmith42 says, computers themselves are not reliable enough to give you 100% certainty that the result is prime. – President James K. Polk Jan 12 '22 at 14:59
  • What does that even mean, I still don't get it. _"computers themselves are not reliable enough to give you 100% certainty that the result is prime."_ How do they produce [these lists](https://en.wikipedia.org/wiki/Largest_known_prime_number) then, are these primes still uncertain? – Lance Jan 12 '22 at 15:02
  • 2
    I appreciate your problem and I want to get you the help you need, but I see your problem in two halves. **1)** design an algorithm that will compute an "exact" prime (on the order of 80 to 65536 digits), using few enough calculations that it can be computed within a human lifetime. (math.stackexchange) and **2)** code that algorithm up in JavaScript (stackoverflow). And it seems like your problem is still #1. How to execute 10^53 iterations of a loop is not a JavaScript problem--it's infeasible to do at the speed of modern computers. You still need an algorithm that works. – Wyck Jan 12 '22 at 15:22

2 Answers2

5

You do not need to check if the number is exactly a prime. There is a probability that a bit in your computer flips, because of stellar radiation for example. This is very unlikely but as long as it is more likely than that the Rabin-Miller test marks a non prime as a prime is lower than you should be alright.

So a hardware failure is more probable than that the number is not a prime, you couldn't ask for more from a prime test.

MrSmith42
  • 9,961
  • 6
  • 38
  • 49
  • It must be a prime though, I am trying to plug it into [this equation](https://math.stackexchange.com/questions/4354737/how-did-we-discover-that-this-quadratic-residue-oriented-prng-generates-unique-n) which is a PRNG function that **requires** a p ≡ 3 mod 4 prime. I would like one that this PRNG can use to generate a pseudo-random integer that is up to 256 bits in size. – Lance Jan 12 '22 at 14:44
  • 1
    @Lance: You have missed his point. You can't guarantee it is prime, even if you use an algorithm that is guaranteed to produce primes. – President James K. Polk Jan 12 '22 at 14:57
  • I don't understand, the algorithm is guaranteed to produce primes, yet you can't guarantee it's a prime? The [math people](https://math.stackexchange.com/questions/4355040/is-it-practical-to-find-actual-very-large-prime-numbers-such-as-those-up-to-100) say it is "easy" and "routine" to find prime numbers up to 100 digits, but over 1000 digits is not possible today. That is my question, what is the algorithm to find a 100 digit prime number, not a probable? – Lance Jan 12 '22 at 15:00
  • 2
    @Lance, I think they mean the algorithm is guaranteed to produce primes, but the hardware cannot guarantee it. That's why they use a term of probable. – ikhvjs Jan 12 '22 at 15:17
  • 1
    @Lance: Which "math people" are saying that "over 1000 digits is not possible today"? The current record for largest number proved prime by ECPP (which is an algorithm that doesn't expect numbers to be of a special form) is 40000 digits. See https://primes.utm.edu/top20/page.php?id=27, for example. – Mark Dickinson Jan 12 '22 at 20:38
  • 1
    Sorry I didn't mean not possible, I meant not practical for quickly determining them with a computer. – Lance Jan 12 '22 at 21:16
1

If all you need is a random prime number, then I would recommend the deterministic PWS algorithm by Selfridge. It only detects 25% of prime numbers but, those it does detect, it is suspected to detect with 100% complete accuracy. (That is, it never returns a detect positive for a composite number.)

Emphasize the word "suspect" as there is no proof yet of the PWS algorithm. However, do not let this deter you becuase, even if a counterexample the PWS algorithm fails for is found, then the PWS algorithm will still be really really good (at least as good as a few dozen iterations of Miller Rabin), just not perfect. If you really need to find huge random primes in NodeJS with guaranteed accuracy, then you would be much better off spawning an openssl process for every number.

Read more. Visit https://en.wikipedia.org/wiki/John_Selfridge#Selfridge's_conjecture_about_primality_testing.

// Usage: console.log(selfridgePrimalityTest(123456789n) ? "is prime" : "not prime");
var selfridgePrimalityTest = (function() {
  "use strict";

  const globalThis = typeof self === "undefined" ? global : self;
  const BigInt = globalThis.BigInt;
  const asUintN = BigInt.asUintN;
    const Number = globalThis.Number;

    // fastModConst turns slow modulo into fast multiplication via magic math stuffs
  function fastModConst(_constN) {
    // Inspired by math I learned in middle school
    var constN = BigInt(_constN);
    var quadConstN = constN << 2n;
    var bitSizeNum = (((constN.toString(2).length|0) + 4) & -32) + 32 | 0;
    var bitSize = BigInt(bitSizeNum);
    var quotient = (1n << (bitSize << 1n)) - 1n;
    var magicM = quotient / constN + 1n;
    var lowerM = asUintN(bitSizeNum, magicM);
    var upperM = magicM >> bitSize;

    return function(x) {
      // shortcut for the common case
      if (x < quadConstN) {
        var remainder = x;
      } else {
        // Use Karatsuba for faster modular multiplication
        //  See en.wikipedia.org/wiki/Karatsuba_algorithm
        var x = BigInt(x);
        var lowerX = asUintN(bitSizeNum, x);
        var upperX = x >> bitSize;

        // The browser does (or will in a future version) optimize shifted bigint multiplication
        var dividendApproximation = upperX*upperM + ((lowerX*upperM) >> bitSize) + ((upperX*lowerM) >> bitSize);

        // dividendApproximation should generally be plus or minus 1 of the true value
        var multiple = dividendApproximation * constN;
        var remainder = x - multiple;
      }

      // This small adjustment of 1 or 2 iterations (typically actually 0 iterations) is wayyyy faster than doubling
      //  the size of the operands as doubling the size of the operands would be far slower due to larger multiplication
      if (constN <= remainder) {
        while (quadConstN <= remainder) remainder -= quadConstN;
        while (constN <= remainder) remainder -= constN;
      }
      while (remainder < 0n) remainder += constN;

      return remainder;
    };
  }
  // from https://stackoverflow.com/a/74250267/5601591
  function fibonacciModulus(_nth, _mod) {
    "use strict";
    var nth = BigInt(_nth);
    var mod = BigInt(_mod);
    var nthString = nth.toString(2);
    var nthSize = nthString.length|0;
    var modulator = fastModConst(mod);

    // adapted from https://www.geeksforgeeks.org/fast-doubling-method-to-find-the-nth-fibonacci-number
    var resA = 0n; // F(n)
    var resB = 1n; // F(n+1)
    for (var n=0; n < nthSize; n=n+1|0) {
      var c = 2n * resB - resA;

      if (c < 0n)
        c += mod;

      // As F(2n) = F(n)[2F(n+1) – F(n)]
      // Here c  = F(2n)
      c = modulator(resA * c);

      // As F(2n + 1) = F(n)^2 + F(n+1)^2
      // Here d = F(2n + 1)
      var d = modulator(resA*resA + resB*resB);

      // Check if N is odd
      // or even
      if (nthString[n] === "0") {
        resA = c;
        resB = d;
      } else {
        resA = d;
        resB = c + d;
      }
    }

    return resA;
  }
  // from https://stackoverflow.com/a/74250267/5601591
  function modularExponuntiation(_bas, _pow, _mod) {
    var base = BigInt(_bas);
    var mod = BigInt(_mod);
    var power = BigInt(_pow);

    var modulator = fastModConst(mod);
    var powerString = power.toString(2);
    var result = 1n;
    var baseExp = base;

    for (var i=(powerString.length|0)-1|0; i >= 0; i=i-1|0) {
      if ("1" === powerString[i]) {
        result = modulator(result * baseExp);
      }

      baseExp = modulator(baseExp * baseExp);
    }

    return result;
  }

    var primeHitTable = new Int32Array([
        131072,32800,8,-2147481598,536871424,128,32,8388616,2097154,537395200,
        134348800,33587200,8192,2097152,524800,128,32768,8200,-2147481598,512,
        134217856,33554464,8388608,-2145386496,537395200,134348800,33554432,
        8396800,2048,524800,131200,32800,8200,-2147483648,536871424,134217728,
        32,8388616,-2145386494,536870912,134348800,32768,8396800,2048,524800,
        131200,32768,8192,-2147483646,536871424,134217856,33554464,8,2097154,
        524288,134348800,33587200,8388608,2099200,524288,131200,32,8200,
        -2147481598,536870912,128,33554464,8,-2145386496,537395200,134348800,
        33587200,0
    ]); 
    
    // IMPORTANT: this WILL return false for 75% of prime numbers
    //      BUT, when it returns true, the number is definately prime
    // https://en.wikipedia.org/wiki/John_Selfridge#Selfridge%27s_conjecture_about_primality_testing
    function selfridgePrimalityTest(_x) {
        var x = BigInt(_x);
        var index = Number(x % 2310n)|0;
        
        if (0 === ((primeHitTable[index >>> 5] >>> index) & 1)) return false;
        if (0n !== fibonacciModulus(x+1n, x)) return false;
        if (1n !== modularExponuntiation(2n, x-1n, x)) return false;
        
        return true;
    }

  return selfridgePrimalityTest;
})();

Run the interactive example below to generate a random approximately 231-digit prime number. Then, you can verify the primality of the generated number by running openssl prime <your prime here> in your terminal.

// Usage: console.log(selfridgePrimalityTest(123456789n) ? "is prime" : "not prime");
var selfridgePrimalityTest = (function() {
  "use strict";

  const globalThis = typeof self === "undefined" ? global : self;
  const BigInt = globalThis.BigInt;
  const asUintN = BigInt.asUintN;
    const Number = globalThis.Number;

    // fastModConst turns slow modulo into fast multiplication via magic math stuffs
  function fastModConst(_constN) {
    // Inspired by math I learned in middle school
    var constN = BigInt(_constN);
    var quadConstN = constN << 2n;
    var bitSizeNum = (((constN.toString(2).length|0) + 4) & -32) + 32 | 0;
    var bitSize = BigInt(bitSizeNum);
    var quotient = (1n << (bitSize << 1n)) - 1n;
    var magicM = quotient / constN + 1n;
    var lowerM = asUintN(bitSizeNum, magicM);
    var upperM = magicM >> bitSize;

    return function(x) {
      // shortcut for the common case
      if (x < quadConstN) {
        var remainder = x;
      } else {
        // Use Karatsuba for faster modular multiplication
        //  See en.wikipedia.org/wiki/Karatsuba_algorithm
        var x = BigInt(x);
        var lowerX = asUintN(bitSizeNum, x);
        var upperX = x >> bitSize;

        // The browser does (or will in a future version) optimize shifted bigint multiplication
        var dividendApproximation = upperX*upperM + ((lowerX*upperM) >> bitSize) + ((upperX*lowerM) >> bitSize);

        // dividendApproximation should generally be plus or minus 1 of the true value
        var multiple = dividendApproximation * constN;
        var remainder = x - multiple;
      }

      // This small adjustment of 1 or 2 iterations (typically actually 0 iterations) is wayyyy faster than doubling
      //  the size of the operands as doubling the size of the operands would be far slower due to larger multiplication
      if (constN <= remainder) {
        while (quadConstN <= remainder) remainder -= quadConstN;
        while (constN <= remainder) remainder -= constN;
      }
      while (remainder < 0n) remainder += constN;

      return remainder;
    };
  }
  // from https://stackoverflow.com/a/74250267/5601591
  function fibonacciModulus(_nth, _mod) {
    "use strict";
    var nth = BigInt(_nth);
    var mod = BigInt(_mod);
    var nthString = nth.toString(2);
    var nthSize = nthString.length|0;
    var modulator = fastModConst(mod);

    // adapted from https://www.geeksforgeeks.org/fast-doubling-method-to-find-the-nth-fibonacci-number
    var resA = 0n; // F(n)
    var resB = 1n; // F(n+1)
    for (var n=0; n < nthSize; n=n+1|0) {
      var c = 2n * resB - resA;

      if (c < 0n)
        c += mod;

      // As F(2n) = F(n)[2F(n+1) – F(n)]
      // Here c  = F(2n)
      c = modulator(resA * c);

      // As F(2n + 1) = F(n)^2 + F(n+1)^2
      // Here d = F(2n + 1)
      var d = modulator(resA*resA + resB*resB);

      // Check if N is odd
      // or even
      if (nthString[n] === "0") {
        resA = c;
        resB = d;
      } else {
        resA = d;
        resB = c + d;
      }
    }

    return resA;
  }
  // from https://stackoverflow.com/a/74250267/5601591
  function modularExponuntiation(_bas, _pow, _mod) {
    var base = BigInt(_bas);
    var mod = BigInt(_mod);
    var power = BigInt(_pow);

    var modulator = fastModConst(mod);
    var powerString = power.toString(2);
    var result = 1n;
    var baseExp = base;

    for (var i=(powerString.length|0)-1|0; i >= 0; i=i-1|0) {
      if ("1" === powerString[i]) {
        result = modulator(result * baseExp);
      }

      baseExp = modulator(baseExp * baseExp);
    }

    return result;
  }

    var primeHitTable = new Int32Array([
        131072,32800,8,-2147481598,536871424,128,32,8388616,2097154,537395200,
        134348800,33587200,8192,2097152,524800,128,32768,8200,-2147481598,512,
        134217856,33554464,8388608,-2145386496,537395200,134348800,33554432,
        8396800,2048,524800,131200,32800,8200,-2147483648,536871424,134217728,
        32,8388616,-2145386494,536870912,134348800,32768,8396800,2048,524800,
        131200,32768,8192,-2147483646,536871424,134217856,33554464,8,2097154,
        524288,134348800,33587200,8388608,2099200,524288,131200,32,8200,
        -2147481598,536870912,128,33554464,8,-2145386496,537395200,134348800,
        33587200,0
    ]); 
    
    // IMPORTANT: this WILL return false for 75% of prime numbers
    //      BUT, when it returns true, the number is definately prime
    // https://en.wikipedia.org/wiki/John_Selfridge#Selfridge%27s_conjecture_about_primality_testing
    function selfridgePrimalityTest(_x) {
        var x = BigInt(_x);
        var index = Number(x % 2310n)|0;
        
        if (0 === ((primeHitTable[index >>> 5] >>> index) & 1)) return false;
        if (0n !== fibonacciModulus(x+1n, x)) return false;
        if (1n !== modularExponuntiation(2n, x-1n, x)) return false;
        
        return true;
    }

  return selfridgePrimalityTest;
})();

(function(selfridgePrimalityTest) {
    "use strict";

    for (var tryN=0; tryN < 128; tryN++) {
        var primeSearch = BigInt("0x"+crypto.getRandomValues(new Uint32Array(24)).reduce((a,b) => a+b.toString(16).padStart(8,"0"), ""));
        primeSearch -= (primeSearch - 2n) % 1229779565176982820n;
        primeSearch += 41n;
        var lowerOrderSearchNum = 43n;
        for (var i=0; i < 256; i++) {
            // There's a guarentee that, if the primeSearch is prime, then lowerOrderSearchNum must also be prime due to mathematical magic
            if (selfridgePrimalityTest(lowerOrderSearchNum) && selfridgePrimalityTest(primeSearch)) {
                document.body.appendChild( document.createTextNode("Found a prime number: " + primeSearch) );
                return;
            }
            lowerOrderSearchNum += 2n;
            primeSearch += 2n;
        }
    }

    document.body.appendChild( document.createTextNode("Failed to find a random prime after 32768 iterations. (This rarely happens to try running the demo again.)") );
})(selfridgePrimalityTest);
body {word-break: break-all}
Jack G
  • 4,553
  • 2
  • 41
  • 50