# Copyright (c) 2019, Felix Fontein # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later # Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time. # Do not use this from other collections or standalone plugins/modules! from __future__ import annotations def binary_exp_mod(f: int, e: int, *, m: int) -> int: """Computes f^e mod m in O(log e) multiplications modulo m.""" # Compute len_e = floor(log_2(e)) len_e = -1 x = e while x > 0: x >>= 1 len_e += 1 # Compute f**e mod m result = 1 for k in range(len_e, -1, -1): result = (result * result) % m if ((e >> k) & 1) != 0: result = (result * f) % m return result def simple_gcd(a: int, b: int) -> int: """Compute GCD of its two inputs.""" while b != 0: a, b = b, a % b return a def quick_is_not_prime(n: int) -> bool: """Does some quick checks to see if we can poke a hole into the primality of n. A result of `False` does **not** mean that the number is prime; it just means that we could not detect quickly whether it is not prime. """ if n <= 2: return n < 2 # The constant in the next line is the product of all primes < 200 prime_product = 7799922041683461553249199106329813876687996789903550945093032474868511536164700810 gcd = simple_gcd(n, prime_product) if gcd > 1: if n < 200 and gcd == n: # Explicitly check for all primes < 200 return n not in ( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, ) return True # TODO: maybe do some iterations of Miller-Rabin to increase confidence # (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) return False def count_bytes(no: int) -> int: """ Given an integer, compute the number of bytes necessary to store its absolute value. """ no = abs(no) if no == 0: return 0 return (no.bit_length() + 7) // 8 def count_bits(no: int) -> int: """ Given an integer, compute the number of bits necessary to store its absolute value. """ no = abs(no) if no == 0: return 0 return no.bit_length() def convert_int_to_bytes(no: int, *, count: int | None = None) -> bytes: """ Convert the absolute value of an integer to a byte string in network byte order. If ``count`` is provided, it must be sufficiently large so that the integer's absolute value can be represented with these number of bytes. The resulting byte string will have length exactly ``count``. The value zero will be converted to an empty byte string if ``count`` is provided. """ no = abs(no) if count is None: count = count_bytes(no) return no.to_bytes(count, byteorder="big") def convert_int_to_hex(no: int, *, digits: int | None = None) -> str: """ Convert the absolute value of an integer to a string of hexadecimal digits. If ``digits`` is provided, the string will be padded on the left with ``0``s so that the returned value has length ``digits``. If ``digits`` is not sufficient, the string will be longer. """ no = abs(no) value = f"{no:x}" if digits is not None and len(value) < digits: value = "0" * (digits - len(value)) + value return value def convert_bytes_to_int(data: bytes) -> int: """ Convert a byte string to an unsigned integer in network byte order. """ return int.from_bytes(data, byteorder="big", signed=False) __all__ = ( "binary_exp_mod", "convert_bytes_to_int", "convert_int_to_bytes", "convert_int_to_hex", "count_bits", "count_bytes", "quick_is_not_prime", "simple_gcd", )