discrete_logarithm/
index_calculus.rs

1use primal::Primes;
2use rug::{rand::RandState, Integer};
3
4use crate::Error;
5
6/// Check if a number can be factored using the given factor base.
7/// Returns the exponents vector if smooth, None otherwise.
8fn is_smooth(mut n: Integer, factorbase: &[usize]) -> Option<Vec<u32>> {
9    let mut factors = vec![0u32; factorbase.len()];
10
11    for (i, &p) in factorbase.iter().enumerate() {
12        let prime = Integer::from(p);
13        while n.is_divisible(&prime) {
14            factors[i] += 1;
15            n /= &prime;
16        }
17    }
18
19    if n != 1 {
20        None // the number doesn't factor completely over the factor base
21    } else {
22        Some(factors)
23    }
24}
25
26/// Index Calculus algorithm for computing the discrete logarithm of `a` in base `b` modulo `n`.
27///
28/// The group order must be given and prime. It is not suitable for small orders
29/// and the algorithm might fail to find a solution in such situations.
30///
31/// This algorithm is particularly efficient for large prime orders when
32/// exp(2*sqrt(log(n)*log(log(n)))) < sqrt(order).
33///
34/// # Examples
35///
36/// ```
37/// use discrete_logarithm::discrete_log_index_calculus;
38/// use rug::Integer;
39///
40/// let n = Integer::from(24570203447_u64);
41/// let a = Integer::from(23859756228_u64);
42/// let b = Integer::from(2);
43/// let order = Integer::from(12285101723_u64);
44///
45/// let x = discrete_log_index_calculus(&n, &a, &b, Some(&order)).unwrap();
46/// assert_eq!(x, Integer::from(4519867240_u64));
47/// ```
48///
49/// If the order of the group is known, it must be passed as `order`.
50pub fn discrete_log_index_calculus(
51    n: &Integer,
52    a: &Integer,
53    b: &Integer,
54    order: Option<&Integer>,
55) -> Result<Integer, Error> {
56    let a = a.clone() % n;
57    let b = b.clone() % n;
58
59    let order = match order {
60        Some(order) => order.clone(),
61        None => return Err(Error::LogDoesNotExist),
62    };
63
64    // Compute the bound B for the factorbase using the heuristic from the sympy implementation
65    // B = exp(0.5 * sqrt(log(n) * log(log(n))) * (1 + 1/log(log(n))))
66    let n_f64 = n.to_f64();
67    let log_n = n_f64.ln();
68    let log_log_n = log_n.ln();
69    let b_bound = (0.5 * (log_n * log_log_n).sqrt() * (1.0 + 1.0 / log_log_n)).exp();
70    let b_bound = b_bound as usize;
71
72    // Compute the factorbase - all primes up to B (exclusive, matching sympy's primerange(B))
73    let factorbase: Vec<usize> = Primes::all().take_while(|&p| p < b_bound).collect();
74    let lf = factorbase.len();
75
76    if lf == 0 {
77        return Err(Error::LogDoesNotExist);
78    }
79
80    // Maximum number of tries to find a relation
81    let max_tries = (5 * b_bound * b_bound) as u64;
82
83    // First, find a relation for a
84    let mut relationa: Option<(Vec<Integer>, Integer)> = None;
85    let mut abx = a.clone();
86
87    for x in 0..order.to_u64().unwrap_or(u64::MAX) {
88        if abx == 1 {
89            return Ok((order.clone() - x) % &order);
90        }
91
92        if let Some(factors) = is_smooth(abx.clone(), &factorbase) {
93            // Convert to Integer and compute modulo order
94            let factors_int: Vec<Integer> =
95                factors.iter().map(|&f| Integer::from(f) % &order).collect();
96            relationa = Some((factors_int, Integer::from(x)));
97            break;
98        }
99
100        abx = abx * &b % n;
101    }
102
103    let (mut relationa, relationa_x) = match relationa {
104        Some(r) => r,
105        None => return Err(Error::LogDoesNotExist),
106    };
107    relationa.push(relationa_x);
108
109    // Now find relations for the factorbase elements
110    let mut relations: Vec<Option<Vec<Integer>>> = vec![None; lf];
111    let mut k = 1; // number of relations found
112    let mut kk = 0; // number of consecutive failures
113
114    let mut rand_state = RandState::new();
115    let order_minus_1: Integer = order.clone() - 1;
116
117    while k < 3 * lf && kk < max_tries {
118        // Generate random exponent x in [1, order-1]
119        let x = order_minus_1.clone().random_below(&mut rand_state) + 1;
120
121        // Compute b^x mod n
122        let bx = b.clone().pow_mod(&x, n).unwrap();
123
124        // Try to factor it over the factorbase
125        let relation = match is_smooth(bx, &factorbase) {
126            Some(factors) => {
127                let mut rel: Vec<Integer> =
128                    factors.iter().map(|&f| Integer::from(f) % &order).collect();
129                rel.push(x);
130                rel
131            }
132            None => {
133                kk += 1;
134                continue;
135            }
136        };
137
138        k += 1;
139        kk = 0;
140
141        // Gaussian elimination step
142        let mut relation = relation;
143        let mut index = lf; // index of first nonzero entry
144
145        for i in 0..lf {
146            let ri = relation[i].clone() % &order;
147
148            if ri > 0 && relations[i].is_some() {
149                // Make this entry zero using existing relation
150                let existing = relations[i].as_ref().unwrap();
151                for j in 0..=lf {
152                    let diff = relation[j].clone() - &ri * &existing[j];
153                    relation[j] = (diff % &order + &order) % &order;
154                }
155            } else {
156                relation[i] = ri.clone();
157            }
158
159            if relation[i] > 0 && index == lf {
160                index = i;
161            }
162        }
163
164        if index == lf || relations[index].is_some() {
165            // No new information
166            continue;
167        }
168
169        // Normalize the relation
170        let rinv = relation[index].clone().invert(&order).unwrap();
171        for item in relation.iter_mut().skip(index) {
172            *item = (rinv.clone() * &*item) % &order;
173        }
174
175        relations[index] = Some(relation.clone());
176
177        // Reduce relationa with the new relation
178        for i in 0..lf {
179            if relationa[i] > 0 && relations[i].is_some() {
180                let rbi = relationa[i].clone();
181                let existing = relations[i].as_ref().unwrap();
182                for j in 0..=lf {
183                    let diff = relationa[j].clone() - &rbi * &existing[j];
184                    relationa[j] = (diff % &order + &order) % &order;
185                }
186            }
187            if relationa[i] > 0 {
188                break; // We have a nonzero entry, don't need to continue reducing
189            }
190        }
191
192        // Check if all unknowns are eliminated
193        let all_zero = (0..lf).all(|i| relationa[i] == 0);
194        if all_zero {
195            let x = (order.clone() - &relationa[lf]) % &order;
196
197            // Verify the result
198            if b.clone().pow_mod(&x, n).unwrap() == a {
199                return Ok(x);
200            }
201
202            return Err(Error::LogDoesNotExist);
203        }
204    }
205
206    Err(Error::LogDoesNotExist)
207}
208
209#[cfg(test)]
210mod tests {
211    use std::str::FromStr;
212
213    use rug::ops::Pow;
214
215    use super::*;
216
217    #[test]
218    fn index_calculus() {
219        // Test case from sympy documentation
220        assert_eq!(
221            discrete_log_index_calculus(
222                &Integer::from_str("24570203447").unwrap(),
223                &Integer::from_str("23859756228").unwrap(),
224                &2.into(),
225                Some(&Integer::from_str("12285101723").unwrap())
226            )
227            .unwrap(),
228            Integer::from_str("4519867240").unwrap()
229        );
230    }
231
232    #[test]
233    fn index_calculus_small() {
234        // Small test cases
235        assert_eq!(
236            discrete_log_index_calculus(
237                &587.into(),
238                &(Integer::from(2).pow(9)),
239                &2.into(),
240                Some(&293.into())
241            )
242            .unwrap(),
243            9
244        );
245    }
246}