Skip to content

Commit 48ed7f3

Browse files
feat(Maths): add baby-step giant-step discrete log
Classic BSGS discrete logarithm for prime moduli with Vitest coverage. Signed-off-by: Felipe Fernandes <felipe.of.dev@gmail.com>
1 parent 5c39e87 commit 48ed7f3

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

Maths/BabyStepGiantStep.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Baby-step giant-step discrete logarithm modulo a prime.
3+
* https://en.wikipedia.org/wiki/Baby-step_giant-step
4+
*
5+
* Solves base^x ≡ target (mod modulus) for the smallest non-negative x.
6+
*/
7+
8+
/**
9+
* @param {number} base
10+
* @param {number} target
11+
* @param {number} modulus prime (or modulus where base is invertible)
12+
* @returns {number} smallest non-negative discrete log
13+
*/
14+
export function babyStepGiantStep(base, target, modulus) {
15+
if (
16+
typeof base !== 'number' ||
17+
typeof target !== 'number' ||
18+
typeof modulus !== 'number' ||
19+
!Number.isInteger(base) ||
20+
!Number.isInteger(target) ||
21+
!Number.isInteger(modulus)
22+
) {
23+
throw new TypeError('Arguments must be integers')
24+
}
25+
if (modulus <= 1) throw new RangeError('modulus must be > 1')
26+
27+
base = ((base % modulus) + modulus) % modulus
28+
target = ((target % modulus) + modulus) % modulus
29+
30+
if (target === 1) return 0
31+
if (base === 0) {
32+
if (target === 0) return 1
33+
throw new RangeError('no discrete log')
34+
}
35+
36+
const modPow = (b, e, mod) => {
37+
let r = 1
38+
b = ((b % mod) + mod) % mod
39+
while (e > 0) {
40+
if (e % 2 === 1) r = (r * b) % mod
41+
b = (b * b) % mod
42+
e = Math.floor(e / 2)
43+
}
44+
return r
45+
}
46+
47+
const m = Math.ceil(Math.sqrt(modulus - 1))
48+
const baby = new Map()
49+
let value = 1
50+
for (let j = 0; j < m; j++) {
51+
if (!baby.has(value)) baby.set(value, j)
52+
value = (value * base) % modulus
53+
}
54+
55+
// factor = base^{-m} mod modulus (Fermat inverse assumes prime modulus)
56+
const invBase = modPow(base, modulus - 2, modulus)
57+
const factor = modPow(invBase, m, modulus)
58+
59+
let gamma = target
60+
for (let i = 0; i < m; i++) {
61+
if (baby.has(gamma)) {
62+
return i * m + baby.get(gamma)
63+
}
64+
gamma = (gamma * factor) % modulus
65+
}
66+
67+
throw new RangeError('no discrete log')
68+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { babyStepGiantStep } from '../BabyStepGiantStep'
2+
3+
describe('babyStepGiantStep', () => {
4+
it.each([
5+
[2, 1, 5, 0],
6+
[2, 3, 5, 3], // 2^3 = 8 ≡ 3 (mod 5)
7+
[5, 8, 13, 3], // 5^3 = 125 ≡ 8 (mod 13)
8+
[3, 13, 17, 4] // 3^4 = 81 ≡ 13 (mod 17)
9+
])('solves %i^x ≡ %i (mod %i)', (base, target, modulus, expected) => {
10+
expect(babyStepGiantStep(base, target, modulus)).toBe(expected)
11+
})
12+
13+
it('throws when no solution exists', () => {
14+
expect(() => babyStepGiantStep(2, 0, 5)).toThrow(RangeError)
15+
})
16+
17+
it('throws for non-integer inputs', () => {
18+
expect(() => babyStepGiantStep('2', 3, 5)).toThrow(TypeError)
19+
})
20+
})

0 commit comments

Comments
 (0)