Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ members = [
"examples/pinocchio",
"examples/prove-verify-circom",
"examples/baby-snark",
"examples/pohlig-hellman-attack",
"examples/schnorr-signature",
"examples/rsa",

]
exclude = ["ensure-no_std"]
resolver = "2"
Expand Down
10 changes: 10 additions & 0 deletions examples/pohlig-hellman-attack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "pohlig-hellman-attack"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true


[dependencies]
lambdaworks-math = { workspace = true }
92 changes: 92 additions & 0 deletions examples/pohlig-hellman-attack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Pohlig-Hellman Attack Implementation

This implementation demonstrates the Pohlig-Hellman algorithm for solving the discrete logarithm problem on elliptic curves. The algorithm is practical when the group order has small prime factors.

This attack is significantly more efficient than attempting to solve the discrete logarithm problem using a brute-force search. In the group used for our implementation, the logarithm can be recovered in under one second with the Pohlig-Hellman attack, whereas a brute-force approach would take longer. This highlights the importance of working with groups of large prime order in cryptographic applications. For example, the group of the BLS12-381 elliptic curve over $\mathbb{F}_p$ has composite order $n$, and we need to ensure we are always working over a subgroup of large prime order $r$.

## Discrete Logarithm Problem

Given two points $g$ and $h$ on an elliptic curve, where $$h = g^x$$ for some unknown integer $x$, the discrete logarithm problem consists of finding the value of $x$.

## Mathematical Foundations

### Group Structure

Let $G$ be a finite cyclic group of order $n$ with generator $g$. Let's say we want to find $x$ such that $h = g^x$. The Pohlig-Hellman algorithm exploits the structure of $G$ when $n$ has small prime factors. In other words, if the factorization of $n$ is:

$$n = \prod_{i=1}^{k} p_i^{e_i} = p_1^{e_1} \ldots p_k^{e_k},$$

where $p_i$ are small distinct primes and $e_i$ are positive integers, then the algorithm can find $x$ efficiently.

### Algorithm Steps

1. **Factorization**: Decompose the group order $n$ into its prime power factors.
2. **Subgroup Resolution**: For each prime power $p_i^{e_i}$:
- Compute $g_i = g^{n/p_i^{e_i}}$.
- Compute $h_i = h^{n/p_i^{e_i}}$.
- Solve $h_i = g_i^{x_i}$ in the subgroup of order $p_i^{e_i}$, using the Baby Step Giant Step algorithm. This will give us $x_i$ such that $x \equiv x_i \text{ mod } p_i^{e_i}$.
3. **Chinese Remainder Theorem**: Combine the solutions $x_i$ to find $x$ modulus $n$.

### Baby Step Giant Step Algorithm

Given a generator point $g$ of a subgroup of order $n$ and another point $h$ such that
$$ h = g^x \text { mod } n$$
the algorithm finds $x$, reducing the complexity of $O(n)$ (using brute force) to $O(\sqrt{n})$.

#### Algorithm Steps

1. Compute the step size $m = \lceil \sqrt{n} \rceil$.
2. Compute and store the list of points $\{g^0, g^1, \ldots, g^{m-1} \}$
3. We compute each element of another list $\{hg^{-0m}, hg^{-1m}, hg^{-2m}, \ldots, hg^{-m^2}\}$, and look for a coincedence between both lists.
4. If a match $g^j = hg^{-im}$ is found then the descrete log is $x = im + j$ because:
$$
\begin{aligned}
g^j &= hg^{-im} \\
g^{im + j} &= h
\end{aligned}
$$
5. If no match is found after $m$ iterations, then no solution exists within the given subgroup.

### Chinese Remainder Theorem

Once the subproblems are solved, the algorithm uses the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem) to find a solution that satisfies all congruences:

$$x \equiv x_1 \pmod{n_1}$$
$$x \equiv x_2 \pmod{n_2}$$
$$\vdots$$
$$x \equiv x_k \pmod{n_k}$$

The solution is given by:
$$x = \sum_{i=1}^{k} a_i M_i N_i \pmod{M}$$
where $N = \prod_{i=1}^{k} n_i$, $N_i = N/n_i$, and $M_i$ is the modular inverse of $N_i$ modulus $n_i$.

## Implementation

You'll find two files in this folder. The file `chinese_remainder_theorem` contains all the algorithms needed to compute the final step.

In the other file, called `pohlig_hellman`, we define a group vulnerable to this attack and the algorithms that compute it.

We chose to use a subgroup $H$ of the BLS12-381 Elliptic Curve of order
$$s = 96192362849 = 11 \cdot 10177 \cdot 859267.$$
This group is vulnerable to the attack because its factorization consists of small primes.

To find the generator $h$ of this group we took $g$ the generator of a bigger group of order $n$ and computed $$h = g^{\frac{n}{s}}.$$

### Usage Example

```rust
// Create the group
let group = PohligHellmanGroup::new();
let generator = &group.generator;
let order = &group.order;

// Define q = g^x.
let x = 7u64;
let q = generator.operate_with_self(x);

// Find x.
let x_found = group.pohlig_hellman_attack(&q).unwrap();

// Check the result.
assert_eq(generator.operate_with_self(x_found), q);
```
104 changes: 104 additions & 0 deletions examples/pohlig-hellman-attack/src/chinese_remainder_theorem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#[derive(Debug)]
pub enum ChineseRemainderTheoremError {
ModuliNotCoprime,
}

/// Given an array [(x_1, n_1), ... , (x_k, n_k)] it returns x such that
/// x ≡ x_1 mod n_1,
/// x ≡ x_2 mod n_2,
/// ...
/// x ≡ x_k mod n_k.
///
/// The moduli n_i should be pairwise coprimes.
///
/// The Chinese Remainder Theorem states that if the moduli n_i are pairwise coprimes,
/// then there is always a unique solution x to these equations in the range [0, N-1]
/// where N is the product of all moduli.
pub fn chinese_remainder_theorem(
equations: &[(i128, i128)],
) -> Result<i128, ChineseRemainderTheoremError> {
// Calculate the product of all moduli
let n: i128 = equations.iter().map(|&(_, m)| m).product();

// For each equation, compute:
// 1. n_i = n / m_i (product of all moduli except current).
// 2. x_i = inverse of n_i modulo m_i.
// 3. Add a_i * n_i * x_i to the result.
let mut result = 0;
for &(a, m) in equations {
let n_i = n / m;

// Find x_i such that n_i * x_i ≡ 1 (mod m)
let x_i = mod_inverse(n_i, m).ok_or(ChineseRemainderTheoremError::ModuliNotCoprime)?;

// Compute (result + a * n_i * x_i) % n
result = (result + a * n_i * x_i) % n;
}

Ok((result % n + n) % n)
}

/// Computes the multiplicative inverse of x modulo n.
pub fn mod_inverse(x: i128, n: i128) -> Option<i128> {
let (g, x, _) = extended_euclidean_algorithm(x, n);
if g == 1 {
Some((x % n + n) % n)
} else {
None
}
}

/// Computes the extended Euclidean algorithm for two integers.
pub fn extended_euclidean_algorithm(a: i128, b: i128) -> (i128, i128, i128) {
let (mut s, mut old_s) = (0, 1);
let (mut t, mut old_t) = (1, 0);
let (mut r, mut old_r) = (b, a);

while r != 0 {
let quotient = old_r / r;

// Update remainders
let temp_r = r;
r = old_r - quotient * r;
old_r = temp_r;

// Update coefficients
let temp_s = s;
s = old_s - quotient * s;
old_s = temp_s;

let temp_t = t;
t = old_t - quotient * t;
old_t = temp_t;
}

(old_r, old_s, old_t)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_mod_inverse() {
assert_eq!(mod_inverse(3, 7), Some(5));
assert_eq!(mod_inverse(17, 3120), Some(2753));
assert_eq!(mod_inverse(2, 4), None); // gcd(2, 4) = 2, so '2' has no inverse modulus 4.
assert_eq!(mod_inverse(1, 5), Some(1));
}

#[test]
fn test_chinese_remainder_theorem() {
// x ≡ 2 (mod 3), x ≡ 3 (mod 5), x ≡ 2 (mod 7)
let equations = [(2, 3), (3, 5), (2, 7)];
assert_eq!(chinese_remainder_theorem(&equations).unwrap(), 23);

// x ≡ 1 (mod 4), x ≡ 2 (mod 27)
let equations = [(1, 4), (2, 27)];
assert_eq!(chinese_remainder_theorem(&equations).unwrap(), 29);

// x ≡ 1 (mod 3), x ≡ 2 (mod 4), x ≡ 3 (mod 5)
let equations = [(1, 3), (2, 4), (3, 5)];
assert_eq!(chinese_remainder_theorem(&equations).unwrap(), 58);
}
}
2 changes: 2 additions & 0 deletions examples/pohlig-hellman-attack/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod chinese_remainder_theorem;
pub mod pohlig_hellman;
Loading
Loading