Skip to content

Commit 658733d

Browse files
authored
Add modular exponentiation and modular inverse, closes #45 (#65)
1 parent 4599350 commit 658733d

File tree

3 files changed

+136
-22
lines changed

3 files changed

+136
-22
lines changed

examples/rc_modexp.nim

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/bigints.nim

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
## Arbitrary precision integers.
22

3-
43
import std/[algorithm, bitops, math, options]
54

65
type
@@ -1019,3 +1018,63 @@ iterator `..<`*(a, b: BigInt): BigInt =
10191018
while res < b:
10201019
yield res
10211020
inc res
1021+
1022+
func invmod*(a, modulus: BigInt): BigInt =
1023+
## Compute the modular inverse of `a` modulo `modulus`.
1024+
## The return value is always in the range `[1, modulus-1]`
1025+
runnableExamples:
1026+
invmod(3.initBigInt, 7.initBigInt) = 5.initBigInt
1027+
1028+
# extended Euclidean algorithm
1029+
if modulus.isZero:
1030+
raise newException(DivByZeroDefect, "modulus must be nonzero")
1031+
elif modulus.isNegative:
1032+
raise newException(ValueError, "modulus must be strictly positive")
1033+
elif a.isZero:
1034+
raise newException(DivByZeroDefect, "0 has no modular inverse")
1035+
else:
1036+
var
1037+
r0 = ((a mod modulus) + modulus) mod modulus
1038+
r1 = modulus
1039+
s0 = one
1040+
s1 = zero
1041+
while r1 > 0:
1042+
let
1043+
q = r0 div r1
1044+
rk = r0 - q * r1
1045+
sk = s0 - q * s1
1046+
r0 = r1
1047+
r1 = rk
1048+
s0 = s1
1049+
s1 = sk
1050+
if r0 != one:
1051+
raise newException(ValueError, $a & " has no modular inverse modulo " & $modulus)
1052+
result = ((s0 mod modulus) + modulus) mod modulus
1053+
1054+
func powmod*(base, exponent, modulus: BigInt): BigInt =
1055+
## Compute modular exponentation of `base` with power `exponent` modulo `modulus`.
1056+
## The return value is always in the range `[0, modulus-1]`.
1057+
runnableExamples:
1058+
assert powmod(2.initBigInt, 3.initBigInt, 7.initBigInt) == 1.initBigInt
1059+
if modulus.isZero:
1060+
raise newException(DivByZeroDefect, "modulus must be nonzero")
1061+
elif modulus.isNegative:
1062+
raise newException(ValueError, "modulus must be strictly positive")
1063+
elif modulus == 1:
1064+
return zero
1065+
else:
1066+
var
1067+
base = base
1068+
exponent = exponent
1069+
if exponent < 0:
1070+
base = invmod(base, modulus)
1071+
exponent = -exponent
1072+
var
1073+
basePow = ((base mod modulus) + modulus) mod modulus # Base stays in [0, m-1]
1074+
result = one
1075+
while not exponent.isZero:
1076+
if (exponent.limbs[0] and 1) != 0:
1077+
result = (result * basePow) mod modulus
1078+
basePow = (basePow * basePow) mod modulus
1079+
exponent = exponent shr 1
1080+

tests/tbigints.nim

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,82 @@ proc main() =
380380
doAssert pow(zero, 0) == one
381381
doAssert pow(zero, 1) == zero
382382

383+
block: # invmod
384+
# with prime modulus
385+
let a = "30292868".initBigInt
386+
let b = "48810860".initBigInt
387+
let p = "60449131".initBigInt # p is prime
388+
doAssert invmod(a, p) == "51713091".initBigInt
389+
doAssert invmod(-b, p) == "31975542".initBigInt
390+
# with composite modulus
391+
let c = "2472018".initBigInt
392+
let n = "3917515".initBigInt # 5 * 7 * 19 * 43 * 137
393+
let d = "1831482".initBigInt
394+
let e = "2502552".initBigInt
395+
let f = "2086033".initBigInt
396+
let h = "1414963".initBigInt
397+
doAssert invmod(c, n) == "2622632".initBigInt
398+
doAssert invmod(one, n) == one
399+
doAssert invmod(n-one, n) == n-one
400+
401+
doAssert invmod( d, n) == h
402+
doAssert invmod(-d, n) == e
403+
doAssert invmod( f, n) == e
404+
doAssert invmod(-f, n) == h
405+
doAssert invmod( e, n) == f
406+
doAssert invmod(-e, n) == d
407+
doAssert invmod( h, n) == d
408+
doAssert invmod(-h, n) == f
409+
410+
doAssertRaises(DivByZeroDefect): discard invmod(zero, n)
411+
doAssertRaises(DivByZeroDefect): discard invmod(one, zero)
412+
doAssertRaises(ValueError): discard invmod(one, -7.initBigInt)
413+
doAssertRaises(ValueError): discard invmod(3.initBigInt, 18.initBigInt) # 3 is not invertible since gcd(3, 18) = 3 != 1
414+
415+
block: # powmod
416+
let a = "30292868".initBigInt
417+
let p = "60449131".initBigInt # p is prime
418+
let two = 2.initBigInt
419+
doAssert powmod(a, two, p) == "25760702".initBigInt
420+
# Fermat's little theorem: a^p ≡ a mod p
421+
doAssert powmod(a, p, p) == a
422+
# Euler's identity a^(p-1) \equiv 1 \bmod p
423+
doAssert powmod(a, p - one, p) == one
424+
# We can invert a using Euler's identity / Fermat's little theorem
425+
doAssert powmod(a, p - two, p) == "51713091".initBigInt
426+
# We can reduce the exponent modulo phi(p) = p - 1, since p is prime
427+
doAssert powmod(a, 2.initBigInt*p, p) == (a * a mod p)
428+
429+
let p2 = 761.initBigInt
430+
var a2 = 1.initBigInt
431+
# Fermat's little theorem: a^p ≡ a mod p
432+
while a2 < p2:
433+
doAssert powmod(a2, p2, p2) == a2
434+
a2.inc
435+
436+
block: # Composite modulus
437+
let a = "2472018".initBigInt
438+
let n = "3917515".initBigInt # 5 * 7 * 19 * 43 * 137
439+
let euler_phi = "2467584".initBigInt
440+
doAssert powmod(a, 52.initBigInt, n) == "2305846".initBigInt
441+
doAssert powmod(a, euler_phi, n) == one
442+
# Edge cases
443+
doAssert powmod(a, one, n) == a
444+
doAssert powmod(a, zero, n) == one
445+
doAssert powmod(zero, zero, n) == one
446+
doAssert powmod(zero, one, n) == zero
447+
448+
block: # powmod with negative base
449+
let a = "1986599".initBigInt
450+
let p = "10230581".initBigInt
451+
doAssert powmod(-a, 2.initBigInt, p) == "6199079".initBigInt
452+
453+
block: # powmod with negative exponent
454+
let a = "1912".initBigInt
455+
let p = "5297".initBigInt
456+
doAssert powmod(a, -1.initBigInt, p) == "1460".initBigInt
457+
doAssert powmod(a, one-p, p) == one
458+
383459
block: # div/mod
384460
doAssertRaises(DivByZeroDefect): discard one div zero
385461
doAssertRaises(DivByZeroDefect): discard one mod zero
@@ -455,6 +531,5 @@ proc main() =
455531
doAssert pred(a, 3) == initBigInt(4)
456532
doAssert succ(a, 3) == initBigInt(10)
457533

458-
459534
static: main()
460535
main()

0 commit comments

Comments
 (0)