Modular exponentiation is a exponentiation performed over modulus. It is used in many cryptographic algorithms.
Modular exponentiation accepts three input integers, say x, y and n and computes according to the formula below,
(x ^ y) mod n
Native Approaches
A naive approach to compute modular exponentiation is to compute the exponentiation first and then the modulus.
This approach is OK for small x, y and n. But in cryptographic algorithms, the values of x, y and n are normally several hundreds of bits long and this approach takes too much memory and multiplication.
A slightly improved version is to compute x mod n repeatedly and multiply to the current result. That is,
rv = x mod n => rv = rv * (x mod n) => rv = rv *(x mod n) ….
This approach reduces the usage of memory. But it still perform poorly when y is very big because the number of the multiplications it needs to perform is very large. Suppose y = 2^100, then the number of multiplications it needs to perform is also 2^100, which is almost infeasible in practice.
A Better Approach
A better approach is to repeatedly square x mod n. That is,
x mod n => (x mod n)^2 => (x mod n)^4 => (x mod n)^8 =>…=>
(x mod n) ^ logy
In this way, the number of multiplications needed can be reduced to logy. For y = 2^100, the number of multiplications needed is just log(2^100) = 100.
In general, this approach can be expressed by the equation below,
(x ^ y) mod n = [ (x ^ ( y / 2 ) ) mod n] ^ 2 if y is even
(x ^ y) mod n = [ x * [( x ^ ( y / 2 ) ) mod n ] ^ 2 ] mod n if y is odd
This approach can be easily implemented in a recursive way. Below is a C implementation for this algorithm,
/**
polymial-time computation for (x^y)%N
the idea is for mod N
(x^y) = [x^(y/2)]^2 if y is even
= x*[x^(y/2)]^2 if y is odd
**/
#include <stdio.h>
#include <stdlib.h>
unsigned long modexp(int x, int y, int n) {
unsigned long i;
if (y == 0) {
return 1%n;
}
if (y == 1) {
return x%n;
}
i = modexp(x, y/2, n);
printf("%d:%ld\n", y, i);
if (y&0x01) { //odd
//return (x*i*i)%n;
return (x%n*i*i)%n;
} else { //even
return (i*i)%n;
}
}
int main(int argc, char **argv) {
int x, y, N;
x = atoi(argv[1]);
y = atoi(argv[2]);
N = atoi(argv[3]);
printf("(x^y)modN = %lu\n", modexp(x, y, N));
return 0;
}
One thing to note in the code above is that when y is odd, we compute using (x%n*i*i)%n instead of (x*i*i)%n. This is to prevent overflow caused by multiplication of x*i*i when x is a very big number.
Also note that when both x and n are big, the modulo x%n is big, the x%n*i*i can still cause overflow. Solving this issue requires different representations of integers and it is not discussed in this post. For most commonly used integers (except cryptographic), the program above is good enough.
For a given input number y, the number of recursions is ~logy. Therefore the time complexity is O(logy). When y is measured in bits, say, N bits, then the number of recursions is ~N. Therefore the time complexity is O(N).
Save the code as modexp.c and compile it using the command,
gcc -o modexp modexp.c
Below are some sample tests,
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp 4 13 497
3:4
6:64
13:120
(x^y)modN = 445
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp 1234 5678 90
2:64
5:46
11:64
22:64
44:46
88:46
177:46
354:64
709:46
1419:64
2839:64
5678:64
(x^y)modN = 46
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp 3 14 30
3:3
7:27
14:27
(x^y)modN = 9
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp 2 16 123
2:2
4:4
8:16
16:10
(x^y)modN = 100
An Alternative Approach
An alternative approach can help us to get rid of the recursion in the program above. The idea is based on the binary representations of an integer in computer. For example, in the first test case above, y = 13 = 2^3 + 2^2 + 2^0. Therefore to compute (4^13) mod 497, we can compute a = (4^(2^3)) mod 497, b = (4^(2^2)) mod 497 and c = (4^(2^0)) mod 497, and then multiply the results together.
In general, given x and n, we can pre-compute [ x ^ (2 ^ N) ] mod n, where N is the number of bits needed to represent the biggest value of y. If y is constrained to be less than 2^100, then N = 100.
In addition, [x ^ (2 ^ N) ] mod n = { [x ^ ( 2 ^ (N-1) ) ] mod n} ^ 2. Therefore, we can compute from N = 0, 1 to N = 100 easily.
r0 = [x ^ (2 ^ 0)] mod n = x mod n
r1 = [x ^ (2 ^ 1)] mod n = r0 ^ 2
r2 = [x ^ (2 ^ 2)] mod n = r1 ^ 2
…
For a given y, we then scan the bits that is set to 1 and read the results from the pre-computation and multiply the results together.
Below is a sample C implementation for this approach,
/**
polymial-time computation for (x^y)%N
non-recursive version
**/
#include <stdio.h>
#include <stdlib.h>
#define MAXP 1000
unsigned long m[MAXP];
void precompute(int x, int n) {
int i;
m[0] = x%n;
for (i = 1; i < MAXP; ++i) {
m[i] = (m[i-1]*m[i-1])%n;
// printf("%d:%ld\n", i, m[i]);
}
}
unsigned long modexp(int x, int y, int n) {
int i;
unsigned long rv;
int tmp;
rv = -1;
for (i = 0; tmp != 0; ++i) {
tmp = y >> i;
if (tmp & 1) {
if (rv == -1) {
rv = m[i]%n;
} else {
rv = (m[i]*rv)%n;
}
printf("%d:%ld\n", i, rv);
}
}
return (rv == -1 ? 1%n:rv);
}
int main(int argc, char **argv) {
int x, y, N;
x = atoi(argv[1]);
y = atoi(argv[2]);
N = atoi(argv[3]);
precompute(x, N);
printf("(x^y)modN = %lu\n", modexp(x, y, N));
return 0;
}
Save the code to modexp2.c and compile it using the command below,
gcc -o modexp2 modexp2.c
Below are some simple tests,
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp2 4 13 497
0:4
2:30
3:445
(x^y)modN = 445
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp2 1234 5678 90
1:46
2:46
3:46
5:46
9:46
10:46
12:46
(x^y)modN = 46
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp2 3 14 30
1:9
2:9
3:9
(x^y)modN = 9
roman10@ra-ubuntu-1:~/Desktop/integer$ ./modexp2 2 16 123
4:100
(x^y)modN = 100
References:
1. Algorithms. Dasgupta, C.H.Papadimitriou, and U.V. Vazirani
2. Wikipedia Modular Exponentiation: http://en.wikipedia.org/wiki/Modular_exponentiation
3. Numerical Recipes: http://numericalrecipes.blogspot.com/2009/03/modular-exponentiation.html