Skip to content

Commit

Permalink
Trac sagemath#34719: move carmichael_lambda() from sage.crypto.util t…
Browse files Browse the repository at this point in the history
…o sage.arith.misc

The Carmichael λ function currently resides in the `sage.crypto.util`
module, but it's really much more general than "just" crypto: λ(n) is
the exponent of the unit group of ℤ/n. (The morally comparable Euler φ
function does live in `sage.arith.misc` already.)

This patch lifts `carmichael_lambda()` out of obscurity, plus some tiny
tweaks.

URL: https://trac.sagemath.org/34719
Reported by: lorenz
Ticket author(s): Lorenz Panny
Reviewer(s): Matthias Koeppe
  • Loading branch information
Release Manager committed Nov 15, 2022
2 parents 4edaafe + 7e9aefc commit 8443dc3
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 155 deletions.
4 changes: 2 additions & 2 deletions src/sage/arith/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
inverse_mod, get_gcd, get_inverse_mod, power_mod,
rational_reconstruction, mqrr_rational_reconstruction,
trial_division, factor, prime_divisors, odd_part, prime_to_m_part,
is_square, is_squarefree, euler_phi, crt, CRT, CRT_list, CRT_basis,
CRT_vectors, multinomial, multinomial_coefficients,
is_square, is_squarefree, euler_phi, carmichael_lambda, crt, CRT,
CRT_list, CRT_basis, CRT_vectors, multinomial, multinomial_coefficients,
binomial, factorial, kronecker_symbol, kronecker, legendre_symbol,
primitive_root, nth_prime, quadratic_residues, moebius,
continuant, number_of_divisors, hilbert_symbol, hilbert_conductor,
Expand Down
151 changes: 151 additions & 0 deletions src/sage/arith/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from sage.rings.abc import RealField, ComplexField

from sage.rings.fast_arith import arith_int, arith_llong, prime_range
from sage.arith.functions import LCM_list


##################################################################
Expand Down Expand Up @@ -3134,6 +3135,156 @@ def plot(self, xmin=1, xmax=50, pointsize=30, rgbcolor=(0, 0, 1),
euler_phi = Euler_Phi()


def carmichael_lambda(n):
r"""
Return the Carmichael function of a positive integer ``n``.
The Carmichael function of `n`, denoted `\lambda(n)`, is the smallest
positive integer `k` such that `a^k \equiv 1 \pmod{n}` for all
`a \in \ZZ/n\ZZ` satisfying `\gcd(a, n) = 1`. Thus, `\lambda(n) = k`
is the exponent of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`.
INPUT:
- ``n`` -- a positive integer.
OUTPUT:
- The Carmichael function of ``n``.
ALGORITHM:
If `n = 2, 4` then `\lambda(n) = \varphi(n)`. Let `p \geq 3` be an odd
prime and let `k` be a positive integer. Then
`\lambda(p^k) = p^{k - 1}(p - 1) = \varphi(p^k)`. If `k \geq 3`, then
`\lambda(2^k) = 2^{k - 2}`. Now consider the case where `n > 3` is
composite and let `n = p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t}` be the
prime factorization of `n`. Then
.. MATH::
\lambda(n)
= \lambda(p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t})
= \text{lcm}(\lambda(p_1^{k_1}), \lambda(p_2^{k_2}), \dots, \lambda(p_t^{k_t}))
EXAMPLES:
The Carmichael function of all positive integers up to and including 10::
sage: from sage.arith.misc import carmichael_lambda
sage: list(map(carmichael_lambda, [1..10]))
[1, 1, 2, 2, 4, 2, 6, 2, 6, 4]
The Carmichael function of the first ten primes::
sage: list(map(carmichael_lambda, primes_first_n(10)))
[1, 2, 4, 6, 10, 12, 16, 18, 22, 28]
Cases where the Carmichael function is equivalent to the Euler phi
function::
sage: carmichael_lambda(2) == euler_phi(2)
True
sage: carmichael_lambda(4) == euler_phi(4)
True
sage: p = random_prime(1000, lbound=3, proof=True)
sage: k = randint(1, 1000)
sage: carmichael_lambda(p^k) == euler_phi(p^k)
True
A case where `\lambda(n) \neq \varphi(n)`::
sage: k = randint(3, 1000)
sage: carmichael_lambda(2^k) == 2^(k - 2)
True
sage: carmichael_lambda(2^k) == 2^(k - 2) == euler_phi(2^k)
False
Verifying the current implementation of the Carmichael function using
another implementation. The other implementation that we use for
verification is an exhaustive search for the exponent of the
multiplicative group `(\ZZ/n\ZZ)^{\ast}`. ::
sage: from sage.arith.misc import carmichael_lambda
sage: n = randint(1, 500)
sage: c = carmichael_lambda(n)
sage: def coprime(n):
....: return [i for i in range(n) if gcd(i, n) == 1]
sage: def znpower(n, k):
....: L = coprime(n)
....: return list(map(power_mod, L, [k]*len(L), [n]*len(L)))
sage: def my_carmichael(n):
....: if n == 1:
....: return 1
....: for k in range(1, n):
....: L = znpower(n, k)
....: ones = [1] * len(L)
....: T = [L[i] == ones[i] for i in range(len(L))]
....: if all(T):
....: return k
sage: c == my_carmichael(n)
True
Carmichael's theorem states that `a^{\lambda(n)} \equiv 1 \pmod{n}`
for all elements `a` of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`.
Here, we verify Carmichael's theorem. ::
sage: from sage.arith.misc import carmichael_lambda
sage: n = randint(2, 1000)
sage: c = carmichael_lambda(n)
sage: ZnZ = IntegerModRing(n)
sage: M = ZnZ.list_of_elements_of_multiplicative_group()
sage: ones = [1] * len(M)
sage: P = [power_mod(a, c, n) for a in M]
sage: P == ones
True
TESTS:
The input ``n`` must be a positive integer::
sage: from sage.arith.misc import carmichael_lambda
sage: carmichael_lambda(0)
Traceback (most recent call last):
...
ValueError: Input n must be a positive integer.
sage: carmichael_lambda(randint(-10, 0))
Traceback (most recent call last):
...
ValueError: Input n must be a positive integer.
Bug reported in :trac:`8283`::
sage: from sage.arith.misc import carmichael_lambda
sage: type(carmichael_lambda(16))
<class 'sage.rings.integer.Integer'>
REFERENCES:
- :wikipedia:`Carmichael_function`
"""
n = Integer(n)
# sanity check
if n < 1:
raise ValueError("Input n must be a positive integer.")

L = list(n.factor())
t = []

# first get rid of the prime factor 2
if n & 1 == 0:
two,e = L.pop(0)
assert two == 2
k = e - 2 if e >= 3 else e - 1
t.append(1 << k)

# then other prime factors
t += [p**(k - 1) * (p - 1) for p, k in L]

# finish the job
return LCM_list(t)


def crt(a, b, m=None, n=None):
r"""
Return a solution to a Chinese Remainder Theorem problem.
Expand Down
2 changes: 1 addition & 1 deletion src/sage/crypto/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def blum_blum_shub(length, seed=None, p=None, q=None,
is the period. ::
sage: from sage.crypto.stream import blum_blum_shub
sage: from sage.crypto.util import carmichael_lambda
sage: from sage.arith.misc import carmichael_lambda
sage: carmichael_lambda(carmichael_lambda(7*11))
4
sage: s = [GF(2)(int(str(x))) for x in blum_blum_shub(60, p=7, q=11, seed=13)]
Expand Down
155 changes: 3 additions & 152 deletions src/sage/crypto/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
from sage.rings.integer import Integer
from sage.rings.finite_rings.integer_mod import Mod as mod

from sage.misc.lazy_import import lazy_import
lazy_import('sage.arith.misc', ('carmichael_lambda'), deprecation=34719)

def ascii_integer(B):
r"""
Return the ASCII integer corresponding to the binary string ``B``.
Expand Down Expand Up @@ -255,158 +258,6 @@ def bin_to_ascii(B):
A.append(chr(ascii_integer(b[8*i: 8*(i+1)])))
return "".join(A)

def carmichael_lambda(n):
r"""
Return the Carmichael function of a positive integer ``n``.
The Carmichael function of `n`, denoted `\lambda(n)`, is the smallest
positive integer `k` such that `a^k \equiv 1 \pmod{n}` for all
`a \in \ZZ/n\ZZ` satisfying `\gcd(a, n) = 1`. Thus, `\lambda(n) = k`
is the exponent of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`.
INPUT:
- ``n`` -- a positive integer.
OUTPUT:
- The Carmichael function of ``n``.
ALGORITHM:
If `n = 2, 4` then `\lambda(n) = \varphi(n)`. Let `p \geq 3` be an odd
prime and let `k` be a positive integer. Then
`\lambda(p^k) = p^{k - 1}(p - 1) = \varphi(p^k)`. If `k \geq 3`, then
`\lambda(2^k) = 2^{k - 2}`. Now consider the case where `n > 3` is
composite and let `n = p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t}` be the
prime factorization of `n`. Then
.. MATH::
\lambda(n)
= \lambda(p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t})
= \text{lcm}(\lambda(p_1^{k_1}), \lambda(p_2^{k_2}), \dots, \lambda(p_t^{k_t}))
EXAMPLES:
The Carmichael function of all positive integers up to and including 10::
sage: from sage.crypto.util import carmichael_lambda
sage: list(map(carmichael_lambda, [1..10]))
[1, 1, 2, 2, 4, 2, 6, 2, 6, 4]
The Carmichael function of the first ten primes::
sage: list(map(carmichael_lambda, primes_first_n(10)))
[1, 2, 4, 6, 10, 12, 16, 18, 22, 28]
Cases where the Carmichael function is equivalent to the Euler phi
function::
sage: carmichael_lambda(2) == euler_phi(2)
True
sage: carmichael_lambda(4) == euler_phi(4)
True
sage: p = random_prime(1000, lbound=3, proof=True)
sage: k = randint(1, 1000)
sage: carmichael_lambda(p^k) == euler_phi(p^k)
True
A case where `\lambda(n) \neq \varphi(n)`::
sage: k = randint(3, 1000)
sage: carmichael_lambda(2^k) == 2^(k - 2)
True
sage: carmichael_lambda(2^k) == 2^(k - 2) == euler_phi(2^k)
False
Verifying the current implementation of the Carmichael function using
another implementation. The other implementation that we use for
verification is an exhaustive search for the exponent of the
multiplicative group `(\ZZ/n\ZZ)^{\ast}`. ::
sage: from sage.crypto.util import carmichael_lambda
sage: n = randint(1, 500)
sage: c = carmichael_lambda(n)
sage: def coprime(n):
....: return [i for i in range(n) if gcd(i, n) == 1]
sage: def znpower(n, k):
....: L = coprime(n)
....: return list(map(power_mod, L, [k]*len(L), [n]*len(L)))
sage: def my_carmichael(n):
....: if n == 1:
....: return 1
....: for k in range(1, n):
....: L = znpower(n, k)
....: ones = [1] * len(L)
....: T = [L[i] == ones[i] for i in range(len(L))]
....: if all(T):
....: return k
sage: c == my_carmichael(n)
True
Carmichael's theorem states that `a^{\lambda(n)} \equiv 1 \pmod{n}`
for all elements `a` of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`.
Here, we verify Carmichael's theorem. ::
sage: from sage.crypto.util import carmichael_lambda
sage: n = randint(2, 1000)
sage: c = carmichael_lambda(n)
sage: ZnZ = IntegerModRing(n)
sage: M = ZnZ.list_of_elements_of_multiplicative_group()
sage: ones = [1] * len(M)
sage: P = [power_mod(a, c, n) for a in M]
sage: P == ones
True
TESTS:
The input ``n`` must be a positive integer::
sage: from sage.crypto.util import carmichael_lambda
sage: carmichael_lambda(0)
Traceback (most recent call last):
...
ValueError: Input n must be a positive integer.
sage: carmichael_lambda(randint(-10, 0))
Traceback (most recent call last):
...
ValueError: Input n must be a positive integer.
Bug reported in :trac:`8283`::
sage: from sage.crypto.util import carmichael_lambda
sage: type(carmichael_lambda(16))
<class 'sage.rings.integer.Integer'>
REFERENCES:
- :wikipedia:`Carmichael_function`
"""
n = Integer(n)
# sanity check
if n < 1:
raise ValueError("Input n must be a positive integer.")

L = n.factor()
t = []

# first get rid of the prime factor 2
if n & 1 == 0:
e = L[0][1]
L = L[1:] # now, n = 2**e * L.value()
if e < 3: # for 1 <= k < 3, lambda(2**k) = 2**(k - 1)
e = e - 1
else: # for k >= 3, lambda(2**k) = 2**(k - 2)
e = e - 2
t.append(1 << e) # 2**e

# then other prime factors
t += [p**(k - 1) * (p - 1) for p, k in L]

# finish the job
return lcm(t)


def has_blum_prime(lbound, ubound):
r"""
Expand Down

0 comments on commit 8443dc3

Please sign in to comment.