/* Copyright (C) 2015 Vladimir Glazachev This file is part of FLINT. FLINT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See . */ #include "aprcl.h" /* Below is the implementation of primality test using Jacobi sums. The steps are described in: [1] "A Course in Computational Algebraic Number Theory" by H. Cohen For a different version of the algorithm, also see: [2] "Implementation of a New Primality Test" by H. Cohen and A.K. Lenstra The algorithm consist of 4 steps: (1.) Precomutation; (2.) Pseudoprime tests with Jacobi sums; (3.) Additional tests; (4.) Final trial division and primality proving. This file contains the implementation of steps (2.) and (3.). It also contains the Jacobi sum primality test. A small part of implementation of step (1.) is here and most are in config_jacobi.c file. (4.) implemented in function aprcl_is_prime_final_division(). Standard variables: n - number to check for primality; R - configuration parameter; s - configuration parameter depends on the R; s^2 > n; a^R = 1 mod s for all integer a coprime to s; q - prime number such that (q | s); p - prime number such that (p | q - 1); r - prime power p^k such that (r | q - 1) and not (r * p | q - 1); k - power of p in r; v - n % r; u - n / r; Standard notation: \zeta_p - p-th root of unity; \sigma_x - automorphism of Z[\zeta_p] for which \sigma_x(\zeta_p) = \zeta_p^x; \sigma_x^{-1} - inverse of \sigma_x; \chi_{p, q} - character defined by \chi_{p, q}(g^x) = \zeta_{p^k}^x, there g is a primitive root modulo q; J(p, q) - jacobi sum j(\chi_{p, q}, \chi_{p, q}); J_2(q) - jacobi sum ( j(\chi_{2, q}^{2^{k - 3}}, \chi_{2, q}^{3 * 2^{k - 3}}) )^2; J_3(q) - jacobi sum j(\chi_{2, q}, \chi_{2, q}, \chi_{2, q}) = J(2, q) * j(\chi_{2, q}^2, \chi_{2, q}); */ /*----------------------------------------------------------------------------*/ /* Checks the case p != 2. Computes j0 = j_{0, p, q}, jv = j_{v, p, q} and checks that j0^u * jv is root of unity. Parameters: j = J(p, q); u, v from standard variables; Returns: If there exist h such that j0^u * jv = \zeta_{p^k}^h returns h; otherwise returns -1. For details about j0 and jv see (i1a) in [2] or algorithm (9.1.28) step 4.a in [1]. */ slong _aprcl_is_prime_jacobi_check_pk(const unity_zp j, const fmpz_t u, ulong v) { slong h; ulong i, r; unity_zp j0, jv, temp, aut; /* initialization */ r = n_pow(j->p, j->exp); /* r = p^k */ unity_zp_init(j0, j->p, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(jv, j->p, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(temp, j->p, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(aut, j->p, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_coeff_set_ui(j0, 0, 1); /* j0 = 1 */ unity_zp_coeff_set_ui(jv, 0, 1); /* jv = 1 */ /* for i in 1..p^k */ for (i = 1; i <= r; i++) { /* only for i that coprime to p */ if (i % j->p == 0) continue; /* update j0 = \prod{\sigma_i^{-1}(j^i)} */ unity_zp_pow_ui(temp, j, i); _unity_zp_reduce_cyclotomic(temp); /* aut = \sigma_i^{-1}(temp) */ unity_zp_aut_inv(aut, temp, i); /* j0 *= aut */ unity_zp_mul(temp, j0, aut); unity_zp_swap(temp, j0); /* update jv = \prod{\sigma_i^{-1}(j^{(v * i) / r})} */ /* note: overflow in v * i is not a concern here */ unity_zp_pow_ui(temp, j, (v * i) / r); _unity_zp_reduce_cyclotomic(temp); /* aut = \sigma_i^{-1}(temp) */ unity_zp_aut_inv(aut, temp, i); /* jv *= aut */ unity_zp_mul(temp, jv, aut); unity_zp_swap(temp, jv); } /* temp = j0^u */ unity_zp_pow_sliding_fmpz(temp, j0, u); /* j0 = j0^u * jv */ unity_zp_mul(j0, jv, temp); /* try to find h */ h = unity_zp_is_unity(j0); /* clear */ unity_zp_clear(aut); unity_zp_clear(j0); unity_zp_clear(jv); unity_zp_clear(temp); return h; } /* Check the case p = 2 and k = 1. Computes j0 = j_{0, 2, q}, jv = j_{v, 2, q} and checks that j0^u * jv is root of unity. j^0^u * jv = (-q)^{(n - 1) / 2}. Parameters: q, n from standard variables; Returns: if j0^u * jv = 1 returns 0; if j0^u * jv = -1 returns 1; otherwise returns -1. For details see algorithm (9.1.28) step 4.d in [1]. */ slong _aprcl_is_prime_jacobi_check_21(ulong q, const fmpz_t n) { slong h; fmpz_t qpow, ndec, temp; /* initialization */ fmpz_init(temp); fmpz_init_set_ui(qpow, q); fmpz_init_set(ndec, n); /* qpow = -q mod n */ fmpz_sub(qpow, n, qpow); fmpz_sub_ui(ndec, ndec, 1); /* temp = (n - 1) / 2 */ fmpz_fdiv_q_2exp(temp, ndec, 1); /* qpow = (-q)^{(n - 1) / 2} */ fmpz_powm(qpow, qpow, temp, n); h = -1; /* check if qpow == +-1 */ if (fmpz_equal_ui(qpow, 1)) h = 0; if (fmpz_equal(qpow, ndec)) h = 1; /* clear */ fmpz_clear(temp); fmpz_clear(qpow); fmpz_clear(ndec); return h; } /* Check the case p = 2 and k = 2. Computes j0 = j_{0, 2, q}, jv = j_{v, 2, q} and checks that j0^u * jv is root of unity. Parameters: j = J(2, q); u, v, q from standard variables; Returns: If there exist h such that j0^u * jv = \zeta_{4}^h returns h \zeta_4^h \in (1, i, -1, -i); otherwise returns -1. For details see algorithm (9.1.28) step 4.c in [1]. */ slong _aprcl_is_prime_jacobi_check_22(const unity_zp j, const fmpz_t u, ulong v, ulong q) { slong h; unity_zp j0, jv, j_pow; /* initialization */ unity_zp_init(j_pow, 2, 2, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(j0, 2, 2, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(jv, 2, 2, fmpz_mod_ctx_modulus(j->ctx)); /* set j0 = q * j^2 */ unity_zp_mul(j_pow, j, j); unity_zp_mul_scalar_ui(j0, j_pow, q); /* if v == 1 jv = 1 if v == 3 jv = j^2 */ if (v == 1) unity_zp_coeff_set_ui(jv, 0, 1); else if (v == 3) unity_zp_swap(jv, j_pow); /* j0 = j0^u * jv */ unity_zp_pow_sliding_fmpz(j_pow, j0, u); unity_zp_mul(j0, jv, j_pow); /* try to find h */ h = unity_zp_is_unity(j0); /* clear */ unity_zp_clear(j_pow); unity_zp_clear(j0); unity_zp_clear(jv); return h; } /* Check the case p = 2 and k >= 3. Computes j0 = j_{0, 2, q}, jv = j_{v, 2, q} and checks that j0^u * jv is root of unity. Parameters: j = J(2, q); j2_1 = J_3(q); j2_2 = j_2(q); u, v from standard variables; Returns: If there exist h such that j0^u * jv = \zeta_{2^k}^h returns h otherwise returns -1. For details see algorithm (9.1.28) step 4.b in [1]. */ slong _aprcl_is_prime_jacobi_check_2k(const unity_zp j, const unity_zp j2_1, const unity_zp j2_2, const fmpz_t u, ulong v) { slong h; ulong i, r; unity_zp j_j0, j0, jv, temp, aut; /* initialization */ r = n_pow(j->p, j->exp); unity_zp_init(temp, 2, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(j_j0, 2, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(aut, 2, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(j0, 2, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_init(jv, 2, j->exp, fmpz_mod_ctx_modulus(j->ctx)); unity_zp_coeff_set_ui(j0, 0, 1); /* j0 = 1 */ unity_zp_coeff_set_ui(jv, 0, 1); /* jv = 1 */ /* j_j0 = J(2, q) * J_3(q) */ unity_zp_mul(j_j0, j, j2_1); /* for i in 1..p^k and (i == 1 or i == 3 mod 8) */ for (i = 1; i < r;) { /* i == 1 mod 8 */ /* update j0 = \prod{\sigma_i^{-1}(j^i)} */ unity_zp_pow_ui(temp, j_j0, i); _unity_zp_reduce_cyclotomic(temp); /* aut = \sigma_i^{-1}(temp) */ unity_zp_aut_inv(aut, temp, i); /* j0 *= aut */ unity_zp_mul(temp, j0, aut); unity_zp_swap(temp, j0); /* update jv = \prod{\sigma_i^{-1}(j^{(v * i) / r})} */ unity_zp_pow_ui(temp, j_j0, (v * i) / r); _unity_zp_reduce_cyclotomic(temp); /* aut = \sigma_i^{-1}(temp) */ unity_zp_aut_inv(aut, temp, i); /* jv *= aut */ unity_zp_mul(temp, jv, aut); unity_zp_swap(temp, jv); /* i == 3 mod 8 */ i += 2; /* update j0 = \prod{\sigma_i^{-1}(j^i)} */ unity_zp_pow_ui(temp, j_j0, i); _unity_zp_reduce_cyclotomic(temp); /* aut = \sigma_i^{-1}(temp) */ unity_zp_aut_inv(aut, temp, i); /* j0 *= aut */ unity_zp_mul(temp, j0, aut); unity_zp_swap(temp, j0); /* update jv = \prod{\sigma_i^{-1}(j^{(v * i) / r})} */ unity_zp_pow_ui(temp, j_j0, (v * i) / r); _unity_zp_reduce_cyclotomic(temp); /* aut = \sigma_i^{-1}(temp) */ unity_zp_aut_inv(aut, temp, i); /* jv *= aut */ unity_zp_mul(temp, jv, aut); unity_zp_swap(temp, jv); /* now again i == 1 mod 8 */ i += 6; } /* if v % 8 not congruent 1 or 3 modulo 8 then jv *= J_2(q)^2 */ if (v % 8 != 1 && v % 8 != 3) { unity_zp_mul(temp, j2_2, j2_2); unity_zp_mul(j_j0, jv, temp); unity_zp_swap(j_j0, jv); } /* set j0 = j0^u * jv */ unity_zp_pow_sliding_fmpz(temp, j0, u); unity_zp_mul(j0, jv, temp); /* try to find h */ h = unity_zp_is_unity(j0); /* clear */ unity_zp_clear(aut); unity_zp_clear(j0); unity_zp_clear(jv); unity_zp_clear(j_j0); unity_zp_clear(temp); return h; } /* Try to find prime number q such that: q == mod 2p; n^{(q - 1) / p} != 1 mod q; if p == 2 then 4 | q - 1 and 8 not | q - 1. If the q is found we have two cases: - if p == 2 verify 1) h from above is a primitive root; 2) q^{(n - 1) / 2} = -1 mod n; - if p != 2 verify 1) h from above is a primitive root; if this is not the case for (p, q) then n is composite. If we can't find q check p | n or if n is a perfect power; if so then n is composite. Otherwise n can be prime but we can't prove its primality. Parameters: n, p from standard variables; Returns: if n composite : returns 2; if n can be prime : returns 1; if we do not find q and n can be prime : returns 0. For details see algorithm (9.1.28) step 5 in [1]. */ int _aprcl_is_prime_jacobi_additional_test(const fmpz_t n, ulong p) { int result, p_counter, m; ulong q; fmpz_t npow, qmod; /* initialization */ result = 0; p_counter = 50; /* check only first 50 primes q = 2mp + 1 */ m = 9; /* begin from q = 2*9*p + 1 */ fmpz_init(npow); fmpz_init(qmod); /* check first 50 primes */ while (p_counter > 0) { /* q = 2mp + 1 and m is odd, so if p == 2 then q - 1 | 4 and not q - 1 | 8. */ q = 2 * m * p + 1; /* if q prime */ if (n_is_prime(q) && fmpz_fdiv_ui(n, q) != 0) { fmpz_set_ui(qmod, q); /* npow = n^{(q - 1) / p} */ fmpz_powm_ui(npow, n, (q - 1) / p, qmod); /* if n^{(q - 1) / p} != 1 mod n then we find q */ if (!fmpz_equal_ui(npow, 1)) break; /* else decrease the prime counter */ p_counter--; } /* m is still odd */ m += 2; } /* if we find q */ if (p_counter != 0) { if (fmpz_fdiv_ui(n, q) == 0 && !fmpz_equal_ui(n, q)) result = 2; else { ulong v, k; slong h; fmpz_t u; unity_zp jacobi_sum; fmpz_init(u); /* find max k such that p^k | q - 1; if p = 2 => k = 2 */ k = aprcl_p_power_in_q(q - 1, p); /* compute J(p, q) */ unity_zp_init(jacobi_sum, p, k, n); unity_zp_jacobi_sum_pq(jacobi_sum, q, p); /* compute u and v */ fmpz_tdiv_q_ui(u, n, n_pow(p, k)); v = fmpz_tdiv_ui(n, n_pow(p, k)); /* if p == 2 */ if (p == 2) { /* find h using p = 2, k = 2 */ h = _aprcl_is_prime_jacobi_check_22(jacobi_sum, u, v, q); /* if h not found or h not primitive root then n is composite */ if (h < 0 || h % 2 == 0) result = 2; else /* else verify condition for p = 2 */ { fmpz_t ndec, ndecdiv, qpow; fmpz_init_set(ndec, n); fmpz_init(ndecdiv); fmpz_init_set_ui(qpow, q); /* ndec = n - 1 */ fmpz_sub_ui(ndec, ndec, 1); /* ndecdiv = (n - 1) / 2 */ fmpz_fdiv_q_2exp(ndecdiv, ndec, 1); /* qpow = q^{(n - 1) / 2} */ fmpz_powm(qpow, qpow, ndecdiv, n); /* if q^{(n - 1) / 2} = -1 mod n then n can be prime */ if (fmpz_equal(qpow, ndec)) result = 1; else /* else n is composite */ result = 2; fmpz_clear(ndec); fmpz_clear(ndecdiv); fmpz_clear(qpow); } } else /* if p != 2 */ { /* find h using (2.a) */ h = _aprcl_is_prime_jacobi_check_pk(jacobi_sum, u, v); /* if h not found or h not primitive root then n is composite */ if (h < 0 || h % p == 0) result = 2; else /* else n can be prime */ result = 1; } fmpz_clear(u); unity_zp_clear(jacobi_sum); } } /* if we do not find a q then check (4.c) */ if (p_counter == 0) { fmpz_t root; if (fmpz_tdiv_ui(n, p) == 0) /* if p | n then n is composite */ result = 2; fmpz_init(root); if (fmpz_is_perfect_power(root, n)) /* if n is perfect power, composite */ result = 2; fmpz_clear(root); } /* otherwise we can't prove composite or prime */ /* clear */ fmpz_clear(npow); fmpz_clear(qmod); return result; } primality_test_status _aprcl_is_prime_jacobi(const fmpz_t n, const aprcl_config config) { int *lambdas; ulong i, j, nmod4; primality_test_status result; fmpz_t temp, p2, ndec, ndecdiv, u, q_pow; /* deal with primes that can divide R */ if (fmpz_cmp_ui(n, 2) == 0) return PRIME; if (fmpz_cmp_ui(n, 3) == 0) return PRIME; /* initialization */ fmpz_init(q_pow); fmpz_init(u); fmpz_init(temp); fmpz_init(p2); fmpz_init(ndecdiv); fmpz_init_set(ndec, n); fmpz_sub_ui(ndec, ndec, 1); fmpz_fdiv_q_2exp(ndecdiv, ndec, 1); result = PROBABPRIME; /* Condition (Lp) is satisfied iff: For each p | R we must show that for all prime r | n and all positive integers a there exists l such that: r^(p-1) congruent n^(l(p-1)) mod p^a If (Lp), then lambdas_p = 1. */ lambdas = (int*) flint_malloc(sizeof(int) * config->rs.num); /* nmod4 = n % 4 */ nmod4 = fmpz_tdiv_ui(n, 4); /* For every prime p | R, set lambdas_p: to 1 if p >= 3 and n^{p - 1} != 1 mod p^2; to 0 otherwise. */ for (i = 0; i < config->rs.num; i++) { ulong p = config->rs.p[i]; if (p > 2) { fmpz_set_ui(p2, p * p); fmpz_powm_ui(temp, n, p - 1, p2); if (fmpz_equal_ui(temp, 1) == 0) lambdas[i] = 1; else lambdas[i] = 0; } else lambdas[i] = 0; } /* check that s*R and n are coprime; if not then n is composite */ if (aprcl_is_mul_coprime_ui_fmpz(config->R, config->s, n) == 0) result = COMPOSITE; /* Begin pseudoprime tests with Jacobi sums step. */ /* for every prime q | s */ for (i = 0; i < config->qs->num; i++) { n_factor_t q_factors; ulong q; if (config->qs_used[i] == 0) continue; if (result == COMPOSITE) break; q = fmpz_get_ui(config->qs->p + i); /* set q; q must get into ulong */ /* if n == q; q - prime => n - prime */ if (fmpz_equal_ui(n, q)) { result = PRIME; break; } /* find prime factors of q - 1 */ n_factor_init(&q_factors); n_factor(&q_factors, q - 1, 1); /* for every prime p | q - 1 */ for (j = 0; j < q_factors.num; j++) { int pind; slong h; ulong v, p, r, k; unity_zp jacobi_sum, jacobi_sum2_1, jacobi_sum2_2; if (result == COMPOSITE) break; p = q_factors.p[j]; /* set p; p | q - 1 */ k = q_factors.exp[j]; /* set max k for which p^k | q - 1 */ r = n_pow(p, k); /* set r = p^k */ pind = _aprcl_p_ind(config, p); /* find index of p in lambdas */ /* if lambdas_p == 0 set q_pow = q^{(n - 1) / 2} and p == 2 */ fmpz_set_ui(q_pow, q); if (lambdas[pind] == 0 && p == 2) fmpz_powm(q_pow, q_pow, ndecdiv, n); /* compute u = n / r and v = n % r */ fmpz_tdiv_q_ui(u, n, r); v = fmpz_tdiv_ui(n, r); /* init unity_zp for jacobi sums */ unity_zp_init(jacobi_sum, p, k, n); unity_zp_init(jacobi_sum2_1, p, k, n); unity_zp_init(jacobi_sum2_2, p, k, n); /* compute set jacobi_sum = J(p, q) */ unity_zp_jacobi_sum_pq(jacobi_sum, q, p); /* if p == 2 and k >= 3 we also need to compute J_2(q) and J_3(q) */ if (p == 2 && k >= 3) { /* compute J_3(q) */ unity_zp_jacobi_sum_2q_one(jacobi_sum2_1, q); /* compute J_2(q) */ unity_zp_jacobi_sum_2q_two(jacobi_sum2_2, q); } if (p == 2 && k == 1) { h = _aprcl_is_prime_jacobi_check_21(q, n); /* if h not found then n is composite */ if (h < 0) result = COMPOSITE; /* check (Lp); if h == 1 (unity root = -1) and n % 4 == 1 then lambdas_2 = 1 */ if (lambdas[pind] == 0 && h == 1 && nmod4 == 1) lambdas[pind] = 1; } if (p == 2 && k == 2) { h = _aprcl_is_prime_jacobi_check_22(jacobi_sum, u, v, q); /* if h not found then n is composite */ if (h < 0) result = COMPOSITE; /* check (Lp); if h == 1 or 3 (unity root = -i or i) and q^{(n - 1) / 2} = -1 mod n then lambdas_2 = 1 */ if (h % 2 != 0 && lambdas[pind] == 0 && fmpz_equal(q_pow, ndec)) lambdas[pind] = 1; } if (p == 2 && k >= 3) { h = _aprcl_is_prime_jacobi_check_2k(jacobi_sum, jacobi_sum2_1, jacobi_sum2_2, u, v); /* if h not found then n is composite */ if (h < 0) result = COMPOSITE; /* check (Lp); if h % 2 != 0 (primitive unity root) and q^{(n - 1) / 2} = -1 mod n then lambdas_2 = 1 */ if (h % 2 != 0 && lambdas[pind] == 0 && fmpz_equal(q_pow, ndec)) lambdas[pind] = 1; } if (p != 2) { h = _aprcl_is_prime_jacobi_check_pk(jacobi_sum, u, v); /* if h not found then n is composite */ if (h < 0) result = COMPOSITE; /* check (Lp); if h % p != 0 (primitive unity root) then lambdas_p = 1 */ if (h % p != 0 && lambdas[pind] == 0) lambdas[pind] = 1; } /* clear unity_zp for jacobi sums */ unity_zp_clear(jacobi_sum); unity_zp_clear(jacobi_sum2_1); unity_zp_clear(jacobi_sum2_2); } } /* Begin L_p tests */ /* if n can be prime */ if (result == PROBABPRIME) { /* for every lambdas_p */ for (i = 0; i < config->rs.num; i++) { /* if lambdas_p == 0 need run additional test for p */ if (lambdas[i] == 0) { int r = _aprcl_is_prime_jacobi_additional_test(n, config->rs.p[i]); /* if r == 2 then we prove that n is composite */ if (r == 2) { result = COMPOSITE; } else if (r == 1) { /* if r == 1 then we check that n still can be prime */ lambdas[i] = 1; } else { /* if r == 0 then we can't find q and we can't prove n prime or composite */ result = UNKNOWN; } } } } /* Trial division and primality proving step */ /* If result = PROBAPRIME then all lambdas_p = 1. Using this information we know that: for every divisor p of f of n there exists i from 0..R-1 such that f == n^i mod s. This is done in aprcl_is_prime_final_division() function. */ if (result == PROBABPRIME) { /* if we don't find divisors of n then n is composite */ if (aprcl_is_prime_final_division(n, config->s, config->R) == 1) result = PRIME; else result = COMPOSITE; } /* clear */ flint_free(lambdas); fmpz_clear(u); fmpz_clear(q_pow); fmpz_clear(p2); fmpz_clear(ndec); fmpz_clear(ndecdiv); fmpz_clear(temp); return result; } int aprcl_is_prime_jacobi(const fmpz_t n) { primality_test_status result; aprcl_config config; /* Choose R and s values for n and store its factorisation. See definition in config_jacobi functions documentation. s^2 > n. */ aprcl_config_jacobi_init(config, n); result = _aprcl_is_prime_jacobi(n, config); aprcl_config_jacobi_clear(config); if (result == PROBABPRIME || result == UNKNOWN) { flint_printf("aprcl_is_prime_jacobi: failed to prove n prime\n"); fmpz_print(n); flint_printf("\n"); flint_abort(); } /* if we prove primality, returns 1 */ if (result == PRIME) return 1; return 0; }