12

My problem is to compute (g^x) mod p quickly in JavaScript, where ^ is exponentiation, mod is the modulo operation. All inputs are nonnegative integers, x has about 256 bits, and p is a prime number of 2048 bits, and g may have up to 2048 bits.

Most of the software I've found that can do this in JavaScript seems to use the JavaScript BigInt library (http://www.leemon.com/crypto/BigInt.html). Doing a single exponentiation of such size with this library takes about 9 seconds on my slow browser (Firefox 3.0 with SpiderMonkey). I'm looking for a solution which is at least 10 times faster. The obvious idea of using square-and-multiply (exponentiation by squaring, http://en.wikipedia.org/wiki/Exponentiation_by_squaring) is too slow for 2048-bit numbers: it needs up to 4096 multiplications.

Upgrading the browser is not an option. Using another programming language is not an option. Sending the numbers to a web service is not an option.

Is there a faster alternative implemented?

Update: By doing some extra preparations (i.e. precomputing a few hundred powers) as recommended by the article http://www.ccrwest.org/gordon/fast.pdf mentioned in outis' answer below, it is possible do to a 2048-bit modular exponentiation using only at most 354 modular multiplications. (The traditional square-and-multiply method is much slower: it uses maximum 4096 modular multiplications.) Doing so speeds up the modular exponentiation by a factor of 6 in Firefox 3.0, and by a factor of 4 in Google Chrome. The reason why we are not getting the full speedup of 4096/354 is that BigInt's modular exponentation algorithm is already faster than square-and-multiply, because it uses Montgomery reduction (http://en.wikipedia.org/wiki/Montgomery_reduction).

Update: Starting from BigInt's code, it seems worthwhile doing two levels of hand-optimized (and inlined) Karatsuba multiplication (http://en.wikipedia.org/wiki/Karatsuba_algorithm), and only then revert to the base-32768 O(n^2) multiplication implemented in BigInt. This speeds up multiplications by a factor of 2.25 for 2048-bit integers. Unfortunately, the modulo operation does not become faster.

Update: Using the modified Barrett reduction defined in http://www.lirmm.fr/arith18/papers/hasenplaugh-FastModularReduction.pdf and Karatsuba multiplication and precomputing powers (as defined in http://www.ccrwest.org/gordon/fast.pdf), I can get down the time needed for a single multiplication from 73 seconds to 12.3 seconds in Firefox 3.0. This seems to be the best I can do, but it is still too slow.

Update: The ActionScript 2 (AS2) interpreter in the Flash Player isn't worth using, because it seems to be slower than the JavaScript interpreter in Firefox 3.0: for Flash Player 9, it seems to be 4.2 times slower, and for Flash Player 10, it seems to be 2.35 times slower. Does anybody know the speed difference between ActionScript2 and ActionScript3 (AS3) for number chrunching?

Update: The ActionScript 3 (AS3) interpreter in Flash Player 9 isn't worth using because it has just about the same speed as the JavaScript int Firefox 3.0.

Update: The ActionScript 3 (AS3) interpreter in Flash Player 10 can be up to 6.5 times faster than the JavaScript interpreter in Firefox 3.0 if int is used instead of Number, and Vector.<int> is used instead of Array. At least it was 2.41 times faster for 2048-bit big integer multiplication. So it might be worth doing the modular exponentiation in AS3, executing it in Flash Player 10 if available. Please note that this is still slower than V8, the JavaScript interpreter of Google Chrome. See http://ptspts.blogspot.com/2009/10/javascript-and-actionscript-performance.html for a speed comparison of various programming language and JavaScript implementations.

Update: There is a very fast Java solution, which can be called from the browser's JavaScript if the Java plugin is installed. The following solution is about 310 times faster than the pure JavaScript implementation using BigInt.

<body>hi0
<script type="text/javascript">
document.body.innerHTML += '<br>hi1';
if ('object'==typeof java) {
  var x = new java.math.BigInteger("123456789123456789", 10);
  var p = new java.math.BigInteger("234567891234567891", 10);
  var g = new java.math.BigInteger("3", 10);
  var v = x.modPow(x, p);
  document.body.innerHTML += '<br>' + v.toString();
  document.body.innerHTML += '<br>' + v.toString(16);
} else {
  document.body.innerHTML += '<br>java plugin not installed';
}
</script></body>

Can anyone translate this code to Silverlight (C#)?

pts
  • 80,836
  • 20
  • 110
  • 183
  • 1
    What CPU line was this question originally targeting? "Firefox 3.0" is singlethreaded and lacks any _natively accelerated_ [BigInt support](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-bigint-objects); I would assume the machines in question were Intel `Cedar Mill` / AMD `Windsor`, or thereabouts? I would be interested to see a resolution to the question as originally asked: whether SpiderMonkey 1.8 has the *potential* to evaluate that math in a timely manner on that hardware – JamesTheAwesomeDude Sep 22 '22 at 19:33
  • @JamesTheAwesomeDude: I can't remember any of those details. It may be worth checking in the Firefox 3.0 source code how its JavaScript engine does 32-bit integer arithmetic. Probably thete isn't much acceleration. – pts Sep 23 '22 at 10:31

5 Answers5

4

I use "%" for modular (mod) and "/" for integer division. Let function f(p,g,x,r) calculate (r*g^x)%p on the condition that r<p and g<p. f() can be implemented as:

bigint_t f(p,g,x,r) {
  bigint_t i, z = g, y;
  for (i = 1; i < x; ++i) {
    y = z; z *= g;
    if (z > p) break;
  }
  if (i >= x - 1) return r*z%p; // g^x*r%p = g^x*r
  else return f(p,y,x/i,g^(x%i)*r%p); // reduce to (r*g^(x%i)%p)*(g^i)^(x/i)%p
}

This routine involves a little more calculation, but each integer is less than 4096 bits which is usually much smaller than g^x. I believe this could be more efficient than the direct calculation. Also note that g^(x%i) can be calculated in a faster manner because we have calculated g^(i+1).

EDIT: see this post. Mehrdad gives the right (and better) solution.

Community
  • 1
  • 1
user172818
  • 4,518
  • 1
  • 18
  • 20
  • Your implementation gives me an infinite recursion for f(100, 3, 8, 1), instead of returning 61. Does your algorithm have a proper name? – pts Sep 20 '09 at 11:27
  • Sorry, there is minor error there. I have changed that. This method is just the result of simple math, too simple to get a name. – user172818 Sep 20 '09 at 14:56
  • The method is based on the observation that (k*p+g)^x%p = g^x%p. It repeatedly applies this rule to avoid calculating g^x directly. – user172818 Sep 20 '09 at 15:02
  • f recurses forever when x/i > 2 in the recursive call to f because y*y > p, so the loop in the recursive call will exit with i=1. Try f(100,5,8,1) (only not really, because you'll have to kill your browser). Note also that `i==floor(log(p)/log(g))`. Depending on the implementation of exponentiation and log, using `i=floor(log(p)/log(g)); y=g^i` might be faster than the loop. Once the recursion problem is fixed, f would be logarithmic in x for the best case, but sqrt in x for the worst case. Exponentiation by squaring (which BigInt uses) is logarithmic in x in all cases. – outis Sep 21 '09 at 15:12
4

Would some other client side technology that's callable from JS, such as a Java applet or Flash movie, be acceptable? Leemon's implementation (based on Montgomery Reduction) is already fairly fast. You can tweak BigInt, or you can try a different algorithm, but you probably won't get an order of magnitude improvement.

outis
  • 75,655
  • 22
  • 151
  • 221
  • I have to confirm that BigInt is quite well optimized. I've tried to implement multiplication using the Karatsuba algorithm, but it become 4 times as slow as BigInt's simple O(n^2) multiplication. – pts Oct 03 '09 at 07:35
  • Thank you for mentioning the paper, it looks promising. – pts Oct 03 '09 at 07:36
  • Using BigInt.js with the techniques in the article you have linked I could speed up modular multiplication of 2048-bit integers by a factor of 6 on Firefox 3.0 and a factor of 4 on Google Chrome. Unfortunately, this is still too slow for me, so I have to find a different crypto protocol, which needs less calculations. – pts Oct 03 '09 at 10:01
  • I'd like to give more upvotes for this answer because I've found the article linked very useful. But I cannot accept it as the answer, because it is still not fast enough. – pts Oct 03 '09 at 13:40
2

Why not do it server side in some kind of web service using a more appropriate language like C? Times will then be time for one round trip (less than 9 seconds), plus the time for the server to calculate the result using some BigInt library in native code. This is likely to be much faster.

1800 INFORMATION
  • 131,367
  • 29
  • 160
  • 239
  • 2
    You may not want to send your private key to the server. – Steve Gilham Sep 20 '09 at 08:57
  • 3
    Who said anything about private keys? – 1800 INFORMATION Sep 20 '09 at 08:59
  • With C using the GMP library, it is about 1042 times faster. But using a different programming language or sending the numbers to a server is not an option in my problem. – pts Sep 20 '09 at 09:05
  • So I suppose poking them into silverlight for the heavy crunching is out of the question too then? – Dan F Sep 20 '09 at 12:12
  • I don't want to have Silverlight as a dependency. But using its bigint or Java's bigint if available in the browser can be feasible. But in this question I need a solution which implements fast modular exponentiation in JavaScript. – pts Oct 03 '09 at 13:39
2

Try this Montgomery modular reduction from http://code.google.com/p/bi2php/ on JavaScript.

An6rey
  • 21
  • 1
1

The question as originally stated is no longer applicable, but this thread still floats to the top of Google even today for JavaScript programmers searching for the modular exponentiation algorithm.

There are multiple ways to do it ranging from a naive multiply-reduce loop to bit-twiddling magick; here's one based on the so-called "binary right-to-left method", which splits the difference between being performant and being a readable reference implementation:

function bn_powMod(a, e, m) {
  // h/t https://umaranis.com/2018/07/12/calculate-modular-exponentiation-powermod-in-javascript-ap-n/
  if (m === 1n)
    return 0n;
  if (e < 0n)
    return bn_powMod(bn_modInv(a, m), -e, m);
  for (var b = 1n; e; e >>= 1n) {
    if (e % 2n === 1n)
      b = (b * a) % m;
    a = (a * a) % m;
  }
  return b;
}

function bn_modInv(a, m) {
  // h/t https://github.com/python/cpython/blob/v3.8.0/Objects/longobject.c#L4184
  const m0 = m;
  var b = 1n, c = 0n, q, r;
  while (m) {
    [q, r] = [a/m, a%m];
    [a, b, c, m] = [m, c, b - q*c, r];
  }
  if (a !== 1n)
    throw new RangeError("Not invertible");
  if (b < 0n)
    b += m0;
  return b;
}

If you're looking for actually performant solutions, you might try Stanford's sjcl.bn.montpowermod, npm's modular-power, or Pomcor's pjclMontExp; the last of these options does not base off ES6 BigInts and was apparently designed for high performance under constraint, so it might be a candidate even for older browsers on underpowered hardware.


If you've found this thread because you're rolling your own in-browser RSA-based cryptography, please remember that timing attacks and an infinite variety of implementation attacks exist, and consider using something more prepackaged.