├── .gitignore ├── README.md ├── libnum ├── __init__.py ├── chains.py ├── common.py ├── ecc.py ├── factorize.py ├── modular.py ├── primes.py ├── ranges.py ├── sqrtmod.py ├── strings.py └── stuff.py ├── pyproject.toml └── tests ├── __init__.py ├── test_common.py ├── test_ecc.py ├── test_factorize.py ├── test_modular.py ├── test_primes.py ├── test_ranges.py ├── test_roots.py ├── test_strings.py └── test_stuff.py /.gitignore: -------------------------------------------------------------------------------- 1 | .*project 2 | .settings 3 | *.pyc 4 | dist 5 | build 6 | MANIFEST 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libnum 2 | 3 | This is a python library for some numbers functions: 4 | 5 | * working with primes (generating, primality tests) 6 | * common maths (gcd, lcm, n'th root) 7 | * modular arithmetics (inverse, Jacobi symbol, square root, solve CRT) 8 | * converting strings to numbers or binary strings 9 | 10 | Library may be used for learning/experimenting/research purposes. Should NOT be used for secure crypto implementations. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | $ pip install libnum 16 | ``` 17 | 18 | Note that only Python 3 version is maintained. 19 | 20 | ## Development 21 | 22 | For development or building this repository, [poetry](https://python-poetry.org/) is needed. 23 | 24 | Tests can be ran with 25 | 26 | ```bash 27 | $ pytest --doctest-modules . 28 | ``` 29 | 30 | ## List of functions 31 | 32 | Common maths 33 | 34 | * len\_in\_bits(n) - number of bits in binary representation of @n 35 | * randint\_bits(size) - random number with a given bit size 36 | * extract\_prime\_power(a, p) - s,t such that a = p**s * t 37 | * nroot(x, n) - truncated n'th root of x 38 | * gcd(a, b, ...) - greatest common divisor of all arguments 39 | * lcm(a, b, ...) - least common multiplier of all arguments 40 | * xgcd(a, b) - Extented Euclid GCD algorithm, returns (x, y, g) : a * x + b * y = gcd(a, b) = g 41 | 42 | Modular 43 | 44 | * has\_invmod(a, n) - checks if a has modulo inverse 45 | * invmod(a, n) - modulo inverse 46 | * solve\_crt(remainders, modules) - solve Chinese Remainder Theoreme 47 | * factorial\_mod(n, factors) - compute factorial modulo composite number, needs factorization 48 | * nCk\_mod(n, k, factors) - compute combinations number modulo composite number, needs factorization 49 | * nCk\_mod\_prime\_power(n, k, p, e) - compute combinations number modulo prime power 50 | 51 | Modular square roots 52 | 53 | * jacobi(a, b) - Jacobi symbol 54 | * has\_sqrtmod\_prime\_power(a, p, k) - checks if a number has modular square root, modulus is p**k 55 | * sqrtmod\_prime\_power(a, p, k) - modular square root by p**k 56 | * has\_sqrtmod(a, factors) - checks if a composite number has modular square root, needs factorization 57 | * sqrtmod(a, factors) - modular square root by a composite modulus, needs factorization 58 | 59 | Primes 60 | 61 | * primes(n) - list of primes not greater than @n, slow method 62 | * generate\_prime(size, k=25) - generates a pseudo-prime with @size bits length. @k is a number of tests. 63 | * generate\_prime\_from\_string(s, size=None, k=25) - generate a pseudo-prime starting with @s in string representation 64 | 65 | Factorization 66 | * is\_power(n) - check if @n is p**k, k >= 2: return (p, k) or False 67 | * factorize(n) - factorize @n (currently with rho-Pollard method) 68 | warning: format of factorization is now dict like {p1: e1, p2: e2, ...} 69 | 70 | ECC 71 | 72 | * Curve(a, b, p, g, order, cofactor, seed) - class for representing elliptic curve. Methods: 73 | * .is\_null(p) - checks if point is null 74 | * .is\_opposite(p1, p2) - checks if 2 points are opposite 75 | * .check(p) - checks if point is on the curve 76 | * .check\_x(x) - checks if there are points with given x on the curve (and returns them if any) 77 | * .find\_points\_in\_range(start, end) - list of points in range of x coordinate 78 | * .find\_points\_rand(count) - list of count random points 79 | * .add(p1, p2) - p1 + p2 on elliptic curve 80 | * .power(p, n) - n✕P or (P + P + ... + P) n times 81 | * .generate(n) - n✕G 82 | * .get\_order(p, limit) - slow method, trying to determine order of p; limit is max order to try 83 | 84 | Converting 85 | 86 | * s2n(s) - packed string to number 87 | * n2s(n) - number to packed string 88 | * s2b(s) - packed string to binary string 89 | * b2s(b) - binary string to packed string 90 | 91 | Stuff 92 | 93 | * grey\_code(n) - number in Grey code 94 | * rev\_grey\_code(g) - number from Grey code 95 | * nCk(n, k) - number of combinations 96 | * factorial(n) - factorial 97 | 98 | ## About 99 | 100 | Author: hellman 101 | 102 | License: [MIT License](http://opensource.org/licenses/MIT) 103 | -------------------------------------------------------------------------------- /libnum/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | libnum - Python library for some numbers functions: 3 | - working with primes (generating, primality tests) 4 | - common maths (gcd, lcm, modulo inverse, Jacobi symbol, sqrt) 5 | - elliptic curve cryptography functions 6 | """ 7 | 8 | # commonly used things 9 | from fractions import Fraction 10 | 11 | from .primes import * 12 | from .factorize import * 13 | from .common import * 14 | from .modular import * 15 | from .sqrtmod import * 16 | from .stuff import * 17 | from .strings import * 18 | from .chains import * 19 | from . import ecc 20 | 21 | 22 | # TODO: Add doctest after we have better docs 23 | -------------------------------------------------------------------------------- /libnum/chains.py: -------------------------------------------------------------------------------- 1 | from libnum import nroot, gcd, Fraction 2 | 3 | 4 | class Chain(object): 5 | def __init__(self, *args): 6 | self._chain = [] 7 | self._frac = None 8 | if len(args) == 1: 9 | self.chain = args[0][::] 10 | else: 11 | a, b = args 12 | self.frac = Fraction(a, b) 13 | 14 | def _calcFrac(self): 15 | r = Fraction(0, 1) 16 | for x in reversed(self.chain): 17 | r = Fraction(1, x + r) 18 | return 1/r 19 | 20 | def _calcChain(self): 21 | r = [] 22 | a, b = self.frac.numerator, self.frac.denominator 23 | while b != 1: 24 | r.append(a / b) 25 | a, b = b, a % b 26 | r.append(a) 27 | self._chain = r 28 | self._checkChain() 29 | return self._chain 30 | 31 | def _checkChain(self): 32 | if self._chain[-1] == 1: 33 | self._chain[-2] += 1 34 | self._chain = self._chain[:-1] 35 | return 36 | 37 | @property 38 | def frac(self): 39 | return self._frac 40 | 41 | @frac.setter 42 | def frac(self, f): 43 | self._frac = f 44 | self._chain = self._calcChain() 45 | 46 | @property 47 | def chain(self): 48 | return self._chain[::] 49 | 50 | @chain.setter 51 | def chain(self, c): 52 | self._chain = c 53 | self._checkChain() 54 | self._frac = self._calcFrac() 55 | 56 | @property 57 | def convergents(self): 58 | r1, r2 = 0, 1 59 | q1, q2 = 1, 0 60 | for c in self.chain: 61 | r1, r2, q1, q2 = q1, q2, c * q1 + r1, c * q2 + r2 62 | yield Fraction(q1, q2) 63 | 64 | 65 | def sqrt_chained_fractions(n, limit=None): 66 | ''' 67 | E.g. sqrt_chained_fractions(13) = [3,(1,1,1,1,6)] 68 | ''' 69 | s = nroot(n, 2) 70 | if s**2 == n: 71 | return [s] 72 | res = [] 73 | ps = 1, 0, 1 74 | seen = {ps: 0} 75 | while limit != 0: 76 | v, ps = _sqrt_iter(n, s, *ps) 77 | res.append(v) 78 | if ps in seen: 79 | pos = seen[ps] 80 | period = tuple(res[pos:]) 81 | res = res[:pos] 82 | res.append(period) 83 | return res 84 | else: 85 | seen[ps] = len(res) 86 | 87 | if limit is not None: 88 | limit -= 1 89 | return res 90 | 91 | 92 | def _sqrt_iter(n, s, t, a, b): 93 | ''' 94 | take t*(sqrt(n)+a)/b 95 | s = floor(sqrt(n)) 96 | return (v, next fraction params t, a, b) 97 | ''' 98 | v = t * (s + a) // b 99 | t2 = b 100 | b2 = t * (n - (b * v - a)**2) 101 | a2 = b * v - a 102 | g = gcd(t2, b2) 103 | t2 //= g 104 | b2 //= g 105 | return v, (t2, a2, b2) 106 | 107 | 108 | if __name__ == '__main__': 109 | for v in (2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 1337, 31337): 110 | print("sqrt(%d): %s" % (v, repr(sqrt_chained_fractions(v)))) 111 | -------------------------------------------------------------------------------- /libnum/common.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | 4 | from functools import reduce 5 | 6 | 7 | def len_in_bits(n): 8 | """ 9 | Return number of bits in binary representation of @n. 10 | Probably deprecated by .bit_length(). 11 | """ 12 | if not isinstance(n, int): 13 | raise TypeError("len_in_bits defined only for ints") 14 | return n.bit_length() 15 | 16 | 17 | def randint_bits(size): 18 | return random.getrandbits(size) | (1 << (size - 1)) 19 | 20 | 21 | def ceil(x, y): 22 | """ 23 | Divide x by y with ceiling. 24 | """ 25 | return (x + y - 1) // y 26 | 27 | 28 | def nroot(x, n): 29 | """ 30 | Return truncated n'th root of x. 31 | Using binary search. 32 | """ 33 | if n < 0: 34 | raise ValueError("can't extract negative root") 35 | 36 | if n == 0: 37 | raise ValueError("can't extract zero root") 38 | 39 | sign = 1 40 | if x < 0: 41 | sign = -1 42 | x = -x 43 | if n % 2 == 0: 44 | raise ValueError("can't extract even root of negative") 45 | 46 | high = 1 47 | while high ** n <= x: 48 | high <<= 1 49 | 50 | low = high >> 1 51 | while low < high: 52 | mid = (low + high) >> 1 53 | if low < mid and mid ** n < x: 54 | low = mid 55 | elif high > mid and mid ** n > x: 56 | high = mid 57 | else: 58 | return sign * mid 59 | return sign * (mid + 1) 60 | 61 | 62 | _gcd = math.gcd 63 | 64 | 65 | def _lcm(a, b): 66 | """ 67 | Return lowest common multiple. 68 | """ 69 | if not a or not b: 70 | return 0 71 | return abs(a * b) // _gcd(a, b) 72 | 73 | 74 | def gcd(*lst): 75 | """ 76 | Return gcd of a variable number of arguments. 77 | """ 78 | return abs(reduce(lambda a, b: _gcd(a, b), lst)) 79 | 80 | 81 | def lcm(*lst): 82 | """ 83 | Return lcm of a variable number of arguments. 84 | """ 85 | return abs(reduce(lambda a, b: _lcm(a, b), lst)) 86 | 87 | 88 | def xgcd(a, b): 89 | """ 90 | Extented Euclid GCD algorithm. 91 | Return (x, y, g) : a * x + b * y = gcd(a, b) = g. 92 | """ 93 | if a == 0: 94 | return 0, 1, b 95 | if b == 0: 96 | return 1, 0, a 97 | 98 | px, ppx = 0, 1 99 | py, ppy = 1, 0 100 | 101 | while b: 102 | q = a // b 103 | a, b = b, a % b 104 | x = ppx - q * px 105 | y = ppy - q * py 106 | ppx, px = px, x 107 | ppy, py = py, y 108 | 109 | return ppx, ppy, a 110 | 111 | 112 | def extract_prime_power(a, p): 113 | """ 114 | Return s, t such that a = p**s * t, t % p = 0 115 | """ 116 | s = 0 117 | if p > 2: 118 | while a and a % p == 0: 119 | s += 1 120 | a //= p 121 | elif p == 2: 122 | while a and a & 1 == 0: 123 | s += 1 124 | a >>= 1 125 | else: 126 | raise ValueError("Number %d is not a prime (is smaller than 2)" % p) 127 | return s, a 128 | -------------------------------------------------------------------------------- /libnum/ecc.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from .sqrtmod import sqrtmod_prime_power, has_sqrtmod_prime_power 4 | from .modular import invmod 5 | 6 | __all__ = ('NULL_POINT', 'Curve') 7 | 8 | NULL_POINT = (None, None) 9 | 10 | 11 | class Curve: 12 | def __init__(self, a, b, p, g=None, 13 | order=None, 14 | cofactor=None, 15 | seed=None): 16 | self.a = a 17 | self.b = b 18 | self.module = p 19 | 20 | self.g = g 21 | self.order = order 22 | self.cofactor = cofactor 23 | self.seed = seed 24 | self.points_count = None 25 | if self.cofactor == 1 and self.order is not None: 26 | self.points_count = self.order 27 | return None 28 | 29 | def is_null(self, p): 30 | """ 31 | Check if a point is curve's null point 32 | """ 33 | return p == NULL_POINT 34 | 35 | def is_opposite(self, p1, p2): 36 | """ 37 | Check if one point is opposite to another (p1 == -p2) 38 | """ 39 | x1, y1 = p1 40 | x2, y2 = p2 41 | return (x1 == x2 and y1 == -y2 % self.module) 42 | 43 | def check(self, p): 44 | """ 45 | Check if point is on the curve 46 | """ 47 | x, y = p 48 | if self.is_null(p): 49 | return True 50 | left = (y ** 2) % self.module 51 | right = self.right(x) 52 | return left == right 53 | 54 | def check_x(self, x): 55 | """ 56 | Check if there is a point on the curve with given @x coordinate 57 | """ 58 | if x > self.module or x < 0: 59 | raise ValueError("Value " + str(x) + 60 | " is not in range [0; ]") 61 | a = self.right(x) 62 | n = self.module 63 | 64 | if not has_sqrtmod_prime_power(a, n): 65 | return False 66 | 67 | ys = sqrtmod_prime_power(a, n) 68 | return map(lambda y: (x, y), ys) 69 | 70 | def right(self, x): 71 | """ 72 | Right part of the curve equation: x^3 + a*x + b (mod p) 73 | """ 74 | return (x ** 3 + self.a * x + self.b) % self.module 75 | 76 | def find_points_in_range(self, start=0, end=None): 77 | """ 78 | List of points in given range for x coordinate 79 | """ 80 | points = [] 81 | 82 | if end is None: 83 | end = self.module - 1 84 | 85 | for x in range(start, end + 1): 86 | p = self.check_x(x) 87 | if not p: 88 | continue 89 | points.extend(p) 90 | 91 | return points 92 | 93 | def find_points_rand(self, number=1): 94 | """ 95 | List of @number random points on the curve 96 | """ 97 | points = [] 98 | 99 | while len(points) < number: 100 | x = random.randint(0, self.module) 101 | p = self.check_x(x) 102 | if not p: 103 | continue 104 | points.append(p) 105 | 106 | return points 107 | 108 | def add(self, p1, p2): 109 | """ 110 | Sum of two points 111 | """ 112 | if self.is_null(p1): 113 | return p2 114 | 115 | if self.is_null(p2): 116 | return p1 117 | 118 | if self.is_opposite(p1, p2): 119 | return NULL_POINT 120 | 121 | x1, y1 = p1 122 | x2, y2 = p2 123 | 124 | l = 0 125 | if x1 != x2: 126 | l = (y2 - y1) * invmod(x2 - x1, self.module) 127 | else: 128 | l = (3 * x1 ** 2 + self.a) * invmod(2 * y1, self.module) 129 | 130 | x = (l * l - x1 - x2) % self.module 131 | y = (l * (x1 - x) - y1) % self.module # yes, it's that new x 132 | return (x, y) 133 | 134 | def power(self, p, n): 135 | """ 136 | n✕P or (P + P + ... + P) n times 137 | """ 138 | if n == 0 or self.is_null(p): 139 | return NULL_POINT 140 | 141 | res = NULL_POINT 142 | while n: 143 | if n & 1: 144 | res = self.add(res, p) 145 | p = self.add(p, p) 146 | n >>= 1 147 | return res 148 | 149 | def generate(self, n): 150 | """ 151 | Too lazy to give self.g to self.power 152 | """ 153 | return self.power(self.g, n) 154 | 155 | def get_order(self, p, limit=None): 156 | """ 157 | Tries to calculate order of @p, returns None if @limit is reached 158 | (SLOW method) 159 | """ 160 | order = 1 161 | res = p 162 | while not self.is_null(res): 163 | res = self.add(res, p) 164 | order += 1 165 | if limit is not None and order >= limit: 166 | return None 167 | return order 168 | -------------------------------------------------------------------------------- /libnum/factorize.py: -------------------------------------------------------------------------------- 1 | """ 2 | Some factorization methods are listed here 3 | """ 4 | 5 | import math 6 | import random 7 | 8 | from functools import reduce 9 | from .primes import primes, prime_test 10 | from .common import gcd, nroot 11 | 12 | 13 | __all__ = "factorize unfactorize".split() 14 | 15 | 16 | _PRIMES_CHECK = primes(100) 17 | _PRIMES_P1 = primes(100) 18 | 19 | 20 | def rho_pollard_reduce(n, f): 21 | # use Pollard's (p-1) method to narrow down search 22 | a = random.randint(2, n - 2) 23 | for p in _PRIMES_P1: 24 | a = pow(a, p, n) 25 | 26 | b = a 27 | Q = 1 28 | while True: 29 | a = f(a) 30 | b = f(f(b)) 31 | Q = (Q * (b - a)) % n 32 | 33 | g = gcd(Q, n) 34 | if g == n: 35 | a = b = random.randint(2, n - 2) 36 | Q = 1 37 | elif g != 1: 38 | return g 39 | 40 | 41 | _FUNC_REDUCE = lambda n: rho_pollard_reduce(n, lambda x: (pow(x, 2, n) + 1) % n) 42 | 43 | 44 | def factorize(n): 45 | """ 46 | Use _FUNC_REDUCE (defaults to rho-pollard method) to factorize @n 47 | Return a dict like {p: e} 48 | """ 49 | if n in (0, 1): 50 | return {n: 1} 51 | 52 | prime_factors = {} 53 | 54 | if n < 0: 55 | n = -n 56 | prime_factors[-1] = 1 57 | 58 | for p in _PRIMES_CHECK: 59 | while n % p == 0: 60 | prime_factors[p] = prime_factors.get(p, 0) + 1 61 | n //= p 62 | 63 | factors = [n] 64 | if n == 1: 65 | if not prime_factors: 66 | prime_factors[1] = 1 67 | return prime_factors 68 | 69 | while factors: 70 | n = factors.pop() 71 | 72 | if prime_test(n): 73 | p = n 74 | prime_factors[p] = prime_factors.get(p, 0) + 1 75 | continue 76 | 77 | is_pp = is_power(n) 78 | if is_pp: 79 | if prime_test(p): 80 | p, e = is_pp 81 | prime_factors[p] = prime_factors.get(p, 0) + e 82 | continue 83 | # else we need to factor @p and remember power 84 | # it's not implemented now 85 | # / it doesn't fasten factorize much 86 | 87 | divizor = _FUNC_REDUCE(n) 88 | other = n // divizor 89 | factors.append(divizor) 90 | if other > 1: 91 | factors.append(other) 92 | return prime_factors 93 | 94 | 95 | def unfactorize(factors): 96 | return reduce(lambda acc, p_e: acc * (p_e[0] ** p_e[1]), factors.items(), 1) 97 | 98 | 99 | def is_power(n): 100 | limit = int(math.log(n, 2)) 101 | for power in range(limit, 1, -1): 102 | p = nroot(n, power) 103 | if pow(p, power) == n: 104 | return p, power 105 | return False 106 | -------------------------------------------------------------------------------- /libnum/modular.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from functools import reduce 4 | 5 | from .common import gcd, xgcd 6 | from .stuff import factorial_get_prime_pow, factorial 7 | 8 | 9 | def has_invmod(a, modulus): 10 | """ 11 | Check if @a can be inversed under @modulus. 12 | Call this before calling invmod. 13 | """ 14 | if modulus < 2: 15 | raise ValueError("modulus must be greater than 1") 16 | 17 | if a == 0 or gcd(a, modulus) != 1: 18 | return False 19 | else: 20 | return True 21 | 22 | 23 | def invmod(a, n): 24 | """ 25 | Return 1 / a (mod n). 26 | @a and @n must be co-primes. 27 | """ 28 | if n < 2: 29 | raise ValueError("modulus must be greater than 1") 30 | 31 | x, y, g = xgcd(a, n) 32 | 33 | if g != 1: 34 | raise ValueError("no invmod for given @a and @n") 35 | else: 36 | return x % n 37 | 38 | 39 | def solve_crt(remainders, modules): 40 | """ 41 | Solve Chinese Remainder Theorem. 42 | @modules and @remainders are lists. 43 | @modules must be pairwise coprimes. 44 | """ 45 | if len(modules) != len(remainders): 46 | raise TypeError("modules and remainders lists must have same len") 47 | 48 | if len(modules) == 0: 49 | raise ValueError("Empty lists are given") 50 | 51 | if len(modules) == 1: 52 | return remainders[0] 53 | 54 | x = 0 55 | N = reduce(operator.mul, modules) 56 | for i, module in enumerate(modules): 57 | if module == 1: 58 | continue 59 | 60 | Ni = N // module 61 | b = invmod(Ni, module) 62 | 63 | x += remainders[i] * Ni * b 64 | return x % N 65 | 66 | 67 | def nCk_mod(n, k, factors): 68 | """ 69 | Compute nCk modulo, factorization of modulus is needed 70 | """ 71 | rems = [] 72 | mods = [] 73 | for p, e in factors.items(): 74 | rems.append(nCk_mod_prime_power(n, k, p, e)) 75 | mods.append(p ** e) 76 | return solve_crt(rems, mods) 77 | 78 | 79 | def factorial_mod(n, factors): 80 | """ 81 | Compute factorial modulo, factorization of modulus is needed 82 | """ 83 | rems = [] 84 | mods = [] 85 | for p, e in factors.items(): 86 | pe = p ** e 87 | if n >= pe or factorial_get_prime_pow(n, p) >= e: 88 | factmod = 0 89 | else: 90 | factmod = factorial(n) % pe 91 | rems.append(factmod) 92 | mods.append(pe) 93 | return solve_crt(rems, mods) 94 | 95 | 96 | def nCk_mod_prime_power(n, k, p, e): 97 | """ 98 | Compute nCk mod small prime power: p**e 99 | Algorithm by Andrew Granville: 100 | http://www.dms.umontreal.ca/~andrew/PDF/BinCoeff.pdf 101 | What can be optimized: 102 | - compute (n-k)*(n-k+1)*...*n / 1*2*...*k instead of n!, k!, r! 103 | - ... 104 | """ 105 | 106 | def nCk_get_prime_pow(n, k, p): 107 | res = factorial_get_prime_pow(n, p) 108 | res -= factorial_get_prime_pow(k, p) 109 | res -= factorial_get_prime_pow(n - k, p) 110 | return res 111 | 112 | def nCk_get_non_prime_part(n, k, p, e): 113 | pe = p ** e 114 | r = n - k 115 | 116 | fact_pe = [1] 117 | acc = 1 118 | for x in range(1, pe): 119 | if x % p == 0: 120 | x = 1 121 | acc = (acc * x) % pe 122 | fact_pe.append(acc) 123 | 124 | top = bottom = 1 125 | is_negative = 0 126 | digits = 0 127 | 128 | while n != 0: 129 | if acc != 1: 130 | if digits >= e: 131 | is_negative ^= n & 1 132 | is_negative ^= r & 1 133 | is_negative ^= k & 1 134 | 135 | top = (top * fact_pe[n % pe]) % pe 136 | bottom = (bottom * fact_pe[r % pe]) % pe 137 | bottom = (bottom * fact_pe[k % pe]) % pe 138 | 139 | n //= p 140 | r //= p 141 | k //= p 142 | 143 | digits += 1 144 | 145 | res = (top * invmod(bottom, pe)) % pe 146 | if p != 2 or e < 3: 147 | if is_negative: 148 | res = pe - res 149 | return res 150 | 151 | prime_part_pow = nCk_get_prime_pow(n, k, p) 152 | if prime_part_pow >= e: 153 | return 0 154 | 155 | modpow = e - prime_part_pow 156 | 157 | r = nCk_get_non_prime_part(n, k, p, modpow) % (p ** modpow) 158 | return ((p ** prime_part_pow) * r) % (p ** e) 159 | -------------------------------------------------------------------------------- /libnum/primes.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | import operator 4 | 5 | from functools import reduce 6 | 7 | from .sqrtmod import jacobi 8 | from .common import len_in_bits, gcd, extract_prime_power, randint_bits 9 | from .strings import s2n 10 | 11 | _primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] 12 | _small_primes_product = 1 13 | _primes_bits = [[] for i in range(11)] 14 | _primes_mask = [] 15 | 16 | 17 | def _init(): 18 | global _small_primes_product, _primes, _primes_bits, _primes_mask 19 | _primes = primes(1024) 20 | for p in _primes: 21 | _primes_bits[len_in_bits(p)].append(p) 22 | _small_primes_product = reduce(operator.mul, _primes) 23 | _primes_mask = [(x in _primes) for x in range(_primes[-1] + 1)] 24 | return 25 | 26 | 27 | def primes(until): 28 | """ 29 | Return list of primes not greater than @until. Rather slow. 30 | """ 31 | global _primes, _primes_mask 32 | 33 | if until < 2: 34 | return [] 35 | 36 | if until <= _primes[-1]: 37 | for index, prime in enumerate(_primes): 38 | if prime > until: 39 | return _primes[:index] 40 | 41 | i = _primes[-1] 42 | while i < until + 1: 43 | i += 2 44 | sqrt = math.sqrt(i) + 1 45 | for j in _primes: 46 | if i % j == 0: 47 | break 48 | if j > sqrt: 49 | _primes.append(i) 50 | break 51 | return _primes 52 | 53 | 54 | def generate_prime(size, k=25): 55 | """ 56 | Generate a pseudo-prime with @size bits length. 57 | Optional arg @k=25 defines number of tests. 58 | """ 59 | if size < 2: 60 | raise ValueError("No primes smaller than 2 bits!") 61 | 62 | if size <= 10: 63 | return random.choice(_primes_bits[size]) 64 | 65 | while True: 66 | n = randint_bits(size) | 1 # only odd 67 | 68 | if gcd(_small_primes_product, n) != 1: 69 | continue 70 | 71 | if prime_test(n, k): 72 | return n 73 | return 74 | 75 | 76 | def generate_prime_from_string(s, size=None, k=25): 77 | """ 78 | Generate a pseudo-prime starting with @s in string representation. 79 | Optional arg @size defines length in bits, if is not set than +some bytes. 80 | Optional arg @k=25 defines number of tests. 81 | """ 82 | if not size: 83 | if len(s) > 512: 84 | size = len(s) * 8 + 32 85 | else: 86 | size = len(s) * 8 + 16 87 | 88 | if len(s) * 8 >= size: 89 | raise ValueError("given size is smaller than string length") 90 | 91 | if size % 8: 92 | raise ValueError("size must be 8*n") 93 | 94 | extend_len = size - len(s) * 8 95 | 96 | visible_part = s2n(s) << extend_len 97 | hi = 2 ** extend_len 98 | 99 | while True: 100 | n = visible_part | random.randint(1, hi) | 1 # only even 101 | 102 | if gcd(_small_primes_product, n) != 1: 103 | continue 104 | 105 | if prime_test(n, k): 106 | return n 107 | return 108 | 109 | 110 | def prime_test_ferma(p, k=25): 111 | """ 112 | Test for primality based on Ferma's Little Theorem 113 | Totally fails in Carmichael'e numbers 114 | """ 115 | if p < 2: 116 | return False 117 | if p <= 3: 118 | return True 119 | if p & 1 == 0: 120 | return False 121 | 122 | for j in range(k): 123 | a = random.randint(2, p - 1) 124 | if gcd(a, p) != 1: 125 | return False 126 | 127 | result = pow(a, p - 1, p) 128 | if result != 1: 129 | return False 130 | return True 131 | 132 | 133 | def prime_test_solovay_strassen(p, k=25): 134 | """ 135 | Test for primality by Solovai-Strassen 136 | Stronger than Ferma's test 137 | """ 138 | if p < 2: 139 | return False 140 | if p <= 3: 141 | return True 142 | if p & 1 == 0: 143 | return False 144 | 145 | for j in range(k): 146 | a = random.randint(2, p - 1) 147 | if gcd(a, p) != 1: 148 | return False 149 | 150 | result = pow(a, (p - 1) // 2, p) 151 | if result not in (1, p - 1): 152 | return False 153 | 154 | if result != jacobi(a, p) % p: 155 | return False 156 | return True 157 | 158 | 159 | def prime_test_miller_rabin(p, k=25): 160 | """ 161 | Test for primality by Miller-Rabin 162 | Stronger than Solovay-Strassen's test 163 | """ 164 | if p < 2: 165 | return False 166 | if p <= 3: 167 | return True 168 | if p & 1 == 0: 169 | return False 170 | 171 | # p - 1 = 2**s * m 172 | s, m = extract_prime_power(p - 1, 2) 173 | 174 | for j in range(k): 175 | a = random.randint(2, p - 2) 176 | if gcd(a, p) != 1: 177 | return False 178 | 179 | b = pow(a, m, p) 180 | if b in (1, p - 1): 181 | continue 182 | 183 | for i in range(s): 184 | b = pow(b, 2, p) 185 | 186 | if b == 1: 187 | return False 188 | 189 | if b == p - 1: 190 | # is there one more squaring left to result in 1 ? 191 | if i < s - 1: 192 | break # good 193 | else: 194 | return False # bad 195 | else: 196 | # result is not 1 197 | return False 198 | return True 199 | 200 | 201 | prime_test = prime_test_miller_rabin 202 | 203 | 204 | _init() 205 | -------------------------------------------------------------------------------- /libnum/ranges.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from functools import reduce 4 | 5 | """ 6 | TODO: fix properties for empty 7 | or not? IndexError is ok 8 | max([]) = ValueError 9 | """ 10 | 11 | 12 | class Ranges(object): 13 | """ 14 | Represent Int-ranges unions 15 | Example: 3-10 or 15-30 or 31-31 16 | - operators: 17 | intersection ( & ) 18 | union ( | ) 19 | - iterator yields all integers from ranges 20 | - .segments property - tuple of segments 21 | - len(R.segments) - count of segments 22 | - iter(R.segments) - iterator for segments 23 | - etc. 24 | - add_range method - unite with (x, y) range 25 | """ 26 | 27 | def __init__(self, *ranges): 28 | self._segments = [] 29 | for (a, b) in ranges: 30 | self.add_range(a, b) 31 | 32 | def add_range(self, x, y): 33 | if y < x: 34 | raise ValueError("end is smaller than start: %d < %d" % (y, x)) 35 | 36 | for index, (a, b) in enumerate(self._segments): 37 | if y < a - 1: 38 | self._segments.insert(index, (x, y)) 39 | return 40 | 41 | if x > b + 1: 42 | continue 43 | 44 | new_a = min(a, x) 45 | new_b = max(b, y) 46 | 47 | index2 = index + 1 48 | while index2 < len(self._segments): 49 | a, b = self._segments[index2] 50 | if new_b < a - 1: 51 | break 52 | 53 | new_b = max(new_b, b) 54 | del self._segments[index2] 55 | 56 | self._segments[index] = (new_a, new_b) 57 | return 58 | self._segments.append((x, y)) 59 | return 60 | 61 | def __or__(self, other): 62 | res = Ranges() 63 | for x, y in self._segments: 64 | res.add_range(x, y) 65 | for x, y in other._segments: 66 | res.add_range(x, y) 67 | return res 68 | 69 | def __and__(self, other): 70 | res = [] 71 | index1 = 0 72 | index2 = 0 73 | list1 = self._segments 74 | list2 = other._segments 75 | while index1 < len(list1) and index2 < len(list2): 76 | a, b = list1[index1] 77 | A, B = list2[index2] 78 | 79 | if A < a: 80 | list2, list1 = list1, list2 81 | index2, index1 = index1, index2 82 | a, b, A, B = A, B, a, b 83 | 84 | # a..A..B..b 85 | if a <= A <= B <= b: 86 | res.append((A, B)) 87 | index2 += 1 88 | continue 89 | 90 | # a..b...A..B 91 | if b < A: 92 | index1 += 1 93 | continue 94 | 95 | # a..A..b..B 96 | res.append((A, b)) 97 | index1 += 1 98 | return Ranges(*res) 99 | 100 | def __iter__(self): 101 | for a, b in self._segments: 102 | while a <= b: 103 | yield a 104 | a += 1 105 | return 106 | 107 | def __eq__(self, other): 108 | return self.segments == other.segments 109 | 110 | @property 111 | def len(self): 112 | return reduce( 113 | lambda acc, ab: acc + 1 + ab[1] - ab[0], 114 | self._segments, 0 115 | ) 116 | 117 | @property 118 | def min(self): 119 | return self._segments[0][0] 120 | 121 | @property 122 | def max(self): 123 | return self._segments[-1][1] 124 | 125 | @property 126 | def segments(self): 127 | return tuple(self._segments) 128 | 129 | def __str__(self): 130 | return str(self.segments) 131 | 132 | def __contains__(self, other): 133 | assert isinstance(other, int) 134 | for a, b in self._segments: 135 | if a <= other <= b: 136 | return True 137 | return False 138 | 139 | def to_json(self): 140 | return json.dumps(self._segments) 141 | 142 | @classmethod 143 | def from_json(cls, j): 144 | return Ranges(*json.loads(j)) 145 | -------------------------------------------------------------------------------- /libnum/sqrtmod.py: -------------------------------------------------------------------------------- 1 | import random 2 | from itertools import product 3 | 4 | from .common import extract_prime_power 5 | from .modular import solve_crt, invmod 6 | 7 | 8 | def has_sqrtmod(a, factors=None): 9 | """ 10 | Check if @a is quadratic residue, factorization needed 11 | @factors - list of (prime, power) tuples 12 | """ 13 | if not factors: 14 | raise ValueError("Factors can't be empty: %s" % factors) 15 | 16 | for p, k in factors.items(): 17 | if p <= 1 or k <= 0: 18 | raise ValueError("Not valid prime power: %s**%s" % (p, k)) 19 | 20 | if not has_sqrtmod_prime_power(a, p, k): 21 | return False 22 | return True 23 | 24 | 25 | def sqrtmod(a, factors): 26 | """ 27 | x ^ 2 = a (mod *factors). 28 | Yield square roots by product of @factors as modulus. 29 | @factors - list of (prime, power) tuples 30 | """ 31 | coprime_factors = [p ** k for p, k in factors.items()] 32 | 33 | sqrts = [] 34 | for i, (p, k) in enumerate(factors.items()): 35 | # it's bad that all roots by each modulus are calculated here 36 | # - we can start yielding roots faster 37 | sqrts.append( 38 | list(sqrtmod_prime_power(a % coprime_factors[i], p, k)) 39 | ) 40 | 41 | for rems in product(*sqrts): 42 | yield solve_crt(rems, coprime_factors) 43 | return 44 | 45 | 46 | def has_sqrtmod_prime_power(a, p, n=1): 47 | """ 48 | Check if @a (mod @p**@n) is quadratic residue, @p is prime. 49 | """ 50 | if p < 2: 51 | raise ValueError("Prime must be greater than 1: " + str(p)) 52 | 53 | if n < 1: 54 | raise ValueError("Prime power must be positive: " + str(n)) 55 | 56 | a = a % (p ** n) 57 | 58 | if a in (0, 1): 59 | return True 60 | 61 | e, a = extract_prime_power(a, p) 62 | 63 | if e: 64 | if e & 1: 65 | return False 66 | else: 67 | return has_sqrtmod_prime_power(a, p, n) 68 | 69 | if p == 2: # power of 2 70 | return a % 8 == 1 71 | return jacobi(a, p) == 1 72 | 73 | 74 | def sqrtmod_prime_power(a, p, k=1): 75 | """ 76 | Yield square roots of @a mod @p**@k, 77 | @p - prime 78 | @k >= 1 79 | """ 80 | if k < 1: 81 | raise ValueError("prime power k < 1: %d" % k) 82 | 83 | powers = [1] 84 | pow_p = 1 85 | for i in range(k): 86 | pow_p *= p 87 | powers.append(pow_p) 88 | 89 | # x**2 == a (mod p), p is prime 90 | def sqrtmod_prime(a, p): 91 | if a == 0: 92 | return (0,) 93 | if a == 1: 94 | return (1, p-1) if p != 2 else (1,) 95 | 96 | if jacobi(a, p) == -1: 97 | raise ValueError("No square root for %d (mod %d)" % (a, p)) 98 | 99 | while True: 100 | b = random.randint(1, p - 1) 101 | if jacobi(b, p) == -1: 102 | break 103 | 104 | pow2, t = extract_prime_power(p - 1, 2) 105 | ai = invmod(a, p) 106 | 107 | c = pow(b, t, p) 108 | r = pow(a, (t + 1) // 2, p) 109 | for i in range(1, pow2): 110 | e = pow(2, pow2 - i - 1, p - 1) 111 | d = pow(pow(r, 2, p) * ai, e, p) 112 | if d == p - 1: 113 | r = (r * c) % p 114 | c = pow(c, 2, p) 115 | return (r, (-r) % p) # both roots 116 | 117 | # x**2 == a (mod p**k), p is prime, gcd(a, p) == 1 118 | def sqrtmod_prime_power_for_coprime(a, p, k): 119 | if a == 1: 120 | if p == 2: 121 | if k == 1: 122 | return (1, ) 123 | if k == 2: 124 | return (1, 3) 125 | if k == 3: 126 | return (1, 3, 5, 7) 127 | else: 128 | return 1, pow_p - 1 129 | 130 | if p == 2: # roots mod 2**k 131 | roots = 1, 3 132 | powind = 3 133 | while powind < k: 134 | next_powind = powind + 1 135 | next_roots = set() 136 | 137 | arem = a % powers[next_powind] 138 | for r in roots: # can be done better 139 | if pow(r, 2, powers[next_powind]) == arem: 140 | next_roots.add(r) 141 | 142 | r = powers[powind] - r 143 | if pow(r, 2, powers[next_powind]) == arem: 144 | next_roots.add(r) 145 | 146 | powind = next_powind 147 | roots = next_roots 148 | 149 | roots = [pow_p - r for r in roots] + list(roots) 150 | return roots 151 | 152 | else: # p >= 3 153 | r = sqrtmod_prime(a, p)[0] # any root 154 | powind = 1 155 | while powind < k: 156 | next_powind = min(powind * 2, k) 157 | # Represent root: x = +- (r + p**powind * t1) 158 | b = (a - r**2) % powers[next_powind] 159 | b = (b * invmod(2*r, powers[next_powind])) % powers[next_powind] 160 | if b: 161 | if b % powers[powind]: 162 | raise ValueError("No square root for given value") 163 | b //= powers[powind] 164 | b %= powers[powind] 165 | # Represent t1 = t2 * p**powind + b 166 | # Re-represent root: 167 | # x = +- [ (r + p**powind * b) + t2 * p**(powind*2) ] 168 | r += powers[powind] * b 169 | powind = next_powind 170 | # For next round: x = +- (r + t2 * p**next_powind) 171 | return r % pow_p, (-r) % pow_p 172 | return 173 | 174 | # x**2 == 0 (mod p**k), p is prime 175 | def sqrt_for_zero(p, k): 176 | roots = [0] 177 | start_k = (k // 2 + 1) if k & 1 else (k // 2) 178 | 179 | r = powers[start_k] % pow_p 180 | r0 = r 181 | while True: 182 | if r: # don't duplicate zero 183 | roots.append(r) 184 | r = (r + powers[start_k]) % pow_p 185 | if r == r0: 186 | break 187 | return roots 188 | 189 | # main code 190 | 191 | if a == 0: 192 | for r in sqrt_for_zero(p, k): 193 | yield r 194 | return 195 | 196 | e, a = extract_prime_power(a, p) 197 | 198 | if e & 1: 199 | raise ValueError("No square root for %d (mod %d**%d)" % (a, p, k)) 200 | 201 | p_acc = powers[e >> 1] 202 | sqrt_k = k - e 203 | 204 | roots = sqrtmod_prime_power_for_coprime(a, p, sqrt_k) 205 | 206 | if sqrt_k == 0: 207 | for r in roots: 208 | yield (r * p_acc) % pow_p 209 | return 210 | 211 | all_roots = set() 212 | for r in roots: 213 | r0 = r % pow_p 214 | while True: 215 | root = (r * p_acc) % pow_p 216 | if root not in all_roots: 217 | yield root 218 | all_roots.add(root) 219 | r = (r + powers[sqrt_k]) % pow_p 220 | if r == r0: 221 | break 222 | return 223 | 224 | 225 | def jacobi(a, n): 226 | """ 227 | Return Jacobi symbol (or Legendre symbol if n is prime) 228 | """ 229 | s = 1 230 | while True: 231 | if n < 1: 232 | raise ValueError("Too small module for Jacobi symbol: " + str(n)) 233 | if n & 1 == 0: 234 | raise ValueError("Jacobi is defined only for odd modules") 235 | if n == 1: 236 | return s 237 | a = a % n 238 | if a == 0: 239 | return 0 240 | if a == 1: 241 | return s 242 | 243 | if a & 1 == 0: 244 | if n % 8 in (3, 5): 245 | s = -s 246 | a >>= 1 247 | continue 248 | 249 | if a % 4 == 3 and n % 4 == 3: 250 | s = -s 251 | 252 | a, n = n, a 253 | return 254 | -------------------------------------------------------------------------------- /libnum/strings.py: -------------------------------------------------------------------------------- 1 | from .common import len_in_bits 2 | 3 | 4 | def s2n(s): 5 | r""" 6 | String to number (big endian). 7 | 8 | >>> s2n("BA") # 0x4241 9 | 16961 10 | >>> s2n(b'\x01\x00') 11 | 256 12 | """ 13 | if isinstance(s, str): 14 | s = s.encode("utf-8") 15 | return int.from_bytes(s, "big") 16 | 17 | 18 | def n2s(n): 19 | r""" 20 | Number to string (big endian). 21 | 22 | >>> n2s(0x4241) 23 | b'BA' 24 | >>> n2s(0x100) 25 | b'\x01\x00' 26 | """ 27 | nbits = len_in_bits(n) 28 | nbytes = (nbits + 7) >> 3 29 | return n.to_bytes(nbytes, "big") 30 | 31 | 32 | def s2b(s): 33 | """ 34 | String to binary. 35 | 36 | >>> s2b("qwe") 37 | '011100010111011101100101' 38 | """ 39 | res = bin(s2n(s))[2:] 40 | return "0" * ((8 - len(res)) % 8) + res 41 | 42 | 43 | def b2s(b): 44 | """ 45 | Binary to string. 46 | 47 | >>> b2s("011100010111011101100101") 48 | b'qwe' 49 | """ 50 | return n2s(int(b, 2)) 51 | -------------------------------------------------------------------------------- /libnum/stuff.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from functools import reduce 4 | 5 | 6 | def grey_code(n): 7 | return n ^ (n >> 1) 8 | 9 | 10 | def rev_grey_code(g): 11 | n = 0 12 | while g: 13 | n ^= g 14 | g >>= 1 15 | return n 16 | 17 | 18 | def factorial(n): 19 | res = 1 20 | while n > 1: 21 | res *= n 22 | n -= 1 23 | return res 24 | 25 | 26 | def factorial_get_prime_pow(n, p): 27 | """ 28 | Return power of prime @p in @n! 29 | """ 30 | count = 0 31 | ppow = p 32 | while ppow <= n: 33 | count += n // ppow 34 | ppow *= p 35 | return count 36 | 37 | 38 | def nCk(n, k): 39 | """ 40 | Combinations number 41 | """ 42 | if n < 0: 43 | raise ValueError("Invalid value for n: %s" % n) 44 | if k < 0 or k > n: 45 | return 0 46 | if k in (0, n): 47 | return 1 48 | if k in (1, n-1): 49 | return n 50 | 51 | low_min = 1 52 | low_max = min(n, k) 53 | high_min = max(1, n - k + 1) 54 | high_max = n 55 | return ( 56 | reduce(operator.mul, range(high_min, high_max + 1), 1) 57 | // reduce(operator.mul, range(low_min, low_max + 1), 1) 58 | ) 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "libnum" 3 | version = "1.7.1" 4 | description = "Working with numbers (primes, modular, etc.)" 5 | authors = ["hellman"] 6 | license = "MIT" 7 | readme = "README.md" 8 | keywords = ["numbers", "modular", "cryptography", "number theory"] 9 | classifiers = [ 10 | 'Intended Audience :: Science/Research', 11 | 'Topic :: Scientific/Engineering :: Mathematics', 12 | 'Topic :: Security :: Cryptography', 13 | ] 14 | 15 | [tool.poetry.urls] 16 | homepage = "http://github.com/hellman/libnum" 17 | 18 | [tool.poetry.dependencies] 19 | python = "^3.4" 20 | 21 | [tool.poetry.dev-dependencies] 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0a5"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellman/libnum/d90dc9ec5769bcadd483f98f0c71b587ceeb80f0/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_common.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from libnum import len_in_bits, gcd, lcm, nroot 3 | 4 | 5 | def test_len_in_bits(): 6 | assert len_in_bits(140737488355328) == 48 7 | assert len_in_bits(1023) == 10 8 | assert len_in_bits(1024) == 11 9 | assert len_in_bits(1) == 1 10 | assert len_in_bits(0) == 0 11 | 12 | # test number close to powers of two 13 | for p in (1, 10, 100, 1000, 10000, 100000): 14 | pow2 = 1 << p 15 | assert len_in_bits(pow2 + 1), p == 1 16 | assert len_in_bits(pow2), p == 1 17 | assert len_in_bits(pow2 - 1) == p 18 | 19 | with pytest.raises(TypeError): 20 | len_in_bits("qwe") 21 | 22 | 23 | def test_nroot(): 24 | for x in range(0, 100): 25 | for p in range(1, 3): 26 | n = x ** p 27 | assert nroot(n, p) == x 28 | 29 | assert nroot(-64, 3) == -4 30 | assert nroot(100, 2) == 10 31 | assert nroot(999, 3) == 9 32 | assert nroot(1000, 3) == 10 33 | assert nroot(1001, 3) == 10 34 | 35 | with pytest.raises(ValueError): 36 | nroot(100, -1) 37 | with pytest.raises(ValueError): 38 | nroot(-100, 4) 39 | with pytest.raises(ValueError): 40 | nroot(1, 0) 41 | with pytest.raises(TypeError): 42 | nroot("qwe") 43 | 44 | 45 | def test_gcd_pair(): 46 | assert gcd(100, 75) == 25 47 | assert gcd(-10, 155) == 5 48 | assert gcd(30, -77) == 1 49 | assert gcd(0, -77) == 77 50 | assert gcd(0, 0) == 0 51 | assert gcd(13, 0) == 13 52 | assert gcd(0, 13) == 13 53 | with pytest.raises(TypeError): 54 | gcd("qwe", 10) 55 | with pytest.raises(TypeError): 56 | gcd(10, "qwe") 57 | 58 | 59 | def test_gcd_list(): 60 | assert gcd(100, 75, 150, -325) == 25 61 | assert gcd(-10, -155, -50) == 5 62 | assert gcd(-13) == 13 63 | assert gcd(3, 0, 30) == 3 64 | with pytest.raises(TypeError): 65 | gcd("qwe") 66 | 67 | 68 | def test_lcm_pair(): 69 | assert lcm(100, 75) == 300 70 | assert lcm(1, 31) == 31 71 | assert lcm(2, 37) == 74 72 | 73 | assert lcm(1, 0) == 0 74 | assert lcm(0, 1) == 0 75 | 76 | with pytest.raises(TypeError): 77 | lcm("qwe", 10) 78 | with pytest.raises(TypeError): 79 | lcm(10, "qwe") 80 | 81 | 82 | def test_lcm_list(): 83 | assert lcm(100, 75) == 300 84 | assert lcm(100500) == 100500 85 | assert lcm(10, 20, 30, 40, 5, 80) == 240 86 | 87 | assert lcm(123, 0, 0) == 0 88 | assert lcm(0, 100, 123) == 0 89 | 90 | with pytest.raises(TypeError): 91 | lcm("qwe", 10) 92 | with pytest.raises(TypeError): 93 | lcm(10, "qwe") 94 | -------------------------------------------------------------------------------- /tests/test_ecc.py: -------------------------------------------------------------------------------- 1 | from libnum import ecc 2 | 3 | 4 | def test_curve(): 5 | NP = ecc.NULL_POINT 6 | c = ecc.Curve(1, 3, 7) 7 | 8 | points = [NP, (4, 1), (6, 6), (5, 0), (6, 1), (4, 6)] 9 | good = [(None, None), (4, 1), (6, 6), (5, 0), (6, 1), (4, 6), 10 | (4, 1), (6, 6), (5, 0), (6, 1), (4, 6), (None, None), 11 | (6, 6), (5, 0), (6, 1), (4, 6), (None, None), (4, 1), 12 | (5, 0), (6, 1), (4, 6), (None, None), (4, 1), (6, 6), 13 | (6, 1), (4, 6), (None, None), (4, 1), (6, 6), (5, 0), 14 | (4, 6), (None, None), (4, 1), (6, 6), (5, 0), (6, 1)] 15 | 16 | res = [] 17 | for i in points: 18 | for j in points: 19 | res += [c.add(i, j)] 20 | 21 | assert res == good 22 | -------------------------------------------------------------------------------- /tests/test_factorize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from functools import reduce 4 | from libnum.factorize import factorize 5 | from libnum.factorize import is_power 6 | 7 | 8 | def test_powers(): 9 | numbers = [2, 3, 5, 6, 7, 10, 1993, 1995] 10 | pows = [2, 3, 4, 5, 6, 7, 8, 10, 30] 11 | for n in numbers: 12 | for k in pows: 13 | val = n ** k 14 | assert is_power(val) == (n, k) 15 | 16 | 17 | def test_not_powers(): 18 | numbers = [2, 3, 5, 6, 7, 10, 1993, 1995] 19 | for n in numbers: 20 | assert not is_power(n) 21 | 22 | 23 | def test_samples(): 24 | test_lists = [ 25 | set([(3, 1), (5, 5), (19, 2), (1993, 1), (37, 1), (2, 4)]), 26 | set([(2, 100), (3, 50), (5, 20), (7, 15), (11, 10), (13, 5)]), 27 | set([(1993, 5)]), 28 | set([(2, 4000)]), 29 | ] 30 | 31 | for primes_list in test_lists: 32 | while primes_list: 33 | n = reduce(lambda a, b: a * (b[0] ** b[1]), primes_list, 1) 34 | primes_list_test = set(sorted(factorize(n).items())) 35 | assert primes_list == primes_list_test 36 | primes_list.pop() 37 | 38 | 39 | def test_zero(): 40 | assert factorize(0) == {0: 1} 41 | 42 | 43 | def test_small(): 44 | assert factorize(1) == {1: 1} 45 | assert factorize(2) == {2: 1} 46 | assert factorize(3) == {3: 1} 47 | assert factorize(4) == {2: 2} 48 | assert factorize(5) == {5: 1} 49 | assert factorize(6) == {2: 1, 3: 1} 50 | assert factorize(7) == {7: 1} 51 | assert factorize(8) == {2: 3} 52 | assert factorize(9) == {3: 2} 53 | assert factorize(10) == {2: 1, 5: 1} 54 | 55 | 56 | def test_small_negative(): 57 | assert factorize(-1) == {-1: 1} 58 | assert factorize(-2) == {-1: 1, 2: 1} 59 | assert factorize(-3) == {-1: 1, 3: 1} 60 | assert factorize(-4) == {-1: 1, 2: 2} 61 | assert factorize(-5) == {-1: 1, 5: 1} 62 | assert factorize(-6) == {-1: 1, 2: 1, 3: 1} 63 | assert factorize(-7) == {-1: 1, 7: 1} 64 | assert factorize(-8) == {-1: 1, 2: 3} 65 | assert factorize(-9) == {-1: 1, 3: 2} 66 | assert factorize(-10) == {-1: 1, 2: 1, 5: 1} 67 | 68 | 69 | def test_errors(): 70 | with pytest.raises(TypeError): 71 | factorize("1") 72 | with pytest.raises(TypeError): 73 | factorize(10.3) 74 | with pytest.raises(TypeError): 75 | factorize(complex(10, 3)) 76 | with pytest.raises(TypeError): 77 | factorize((2, 3)) 78 | -------------------------------------------------------------------------------- /tests/test_modular.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import random 3 | import operator 4 | 5 | from functools import reduce 6 | from itertools import combinations_with_replacement 7 | from libnum.common import gcd, xgcd 8 | from libnum.primes import primes 9 | from libnum.factorize import factorize 10 | from libnum.stuff import factorial, nCk 11 | from libnum.sqrtmod import jacobi 12 | from libnum.modular import ( 13 | has_invmod, invmod, 14 | nCk_mod_prime_power, 15 | factorial_mod, 16 | solve_crt, 17 | ) 18 | 19 | 20 | def test_has_invmod(): 21 | for modulus in range(2, 1000, 31): 22 | for a in range(2, modulus, 5): 23 | if has_invmod(a, modulus): 24 | x = invmod(a, modulus) 25 | assert (a * x) % modulus == 1 26 | else: 27 | assert gcd(a, modulus) != 1 28 | with pytest.raises(ValueError): 29 | has_invmod(1, 1) 30 | with pytest.raises(ValueError): 31 | has_invmod(1, 0) 32 | with pytest.raises(ValueError): 33 | has_invmod(1, -100) 34 | with pytest.raises(TypeError): 35 | has_invmod("qwe", 10) 36 | with pytest.raises(TypeError): 37 | has_invmod(10, "qwe") 38 | 39 | 40 | def test_invmod(): 41 | for modulus in range(3, 1001, 37): 42 | for a in range(2, modulus, 5): 43 | if has_invmod(a, modulus): 44 | x = invmod(a, modulus) 45 | assert (a * x) % modulus == 1 46 | else: 47 | with pytest.raises(ValueError): 48 | invmod(a, modulus) 49 | with pytest.raises(ValueError): 50 | invmod(1, 1) 51 | with pytest.raises(ValueError): 52 | invmod(1, 0) 53 | with pytest.raises(ValueError): 54 | invmod(1, -100) 55 | with pytest.raises(TypeError): 56 | invmod("qwe", 10) 57 | with pytest.raises(TypeError): 58 | invmod(10, "qwe") 59 | 60 | 61 | def test_euclid(): 62 | for b in range(1, 1000, 13): 63 | for a in range(1, 1000, 7): 64 | g = gcd(a, b) 65 | x, y, g2 = xgcd(a, b) 66 | assert g == g2 67 | assert a * x + b * y == g 68 | assert xgcd(0, 10)[1:] == (1, 10) 69 | assert xgcd(10, 0)[0::2] == (1, 10) 70 | assert xgcd(0, 0)[2] == 0 71 | with pytest.raises(TypeError): 72 | xgcd("qwe", 10) 73 | with pytest.raises(TypeError): 74 | xgcd(10, "qwe") 75 | 76 | 77 | def test_crt(): 78 | for module in [2, 3, 5, 7, 1993]: 79 | for a in range(module): 80 | assert solve_crt([a], [module]) == a 81 | assert solve_crt([a, 0], [module, 1]) == a 82 | modules = [2, 3, 5, 19, 137] 83 | for i in range(1000): 84 | rems = [] 85 | a = 7 86 | for m in modules: 87 | rems.append(a % m) 88 | a += 31337 89 | a = solve_crt(rems, modules) 90 | for i in range(len(modules)): 91 | assert rems[i] == a % modules[i] 92 | with pytest.raises(TypeError): 93 | solve_crt([1, 2, 3], [1, 2]) 94 | with pytest.raises(ValueError): 95 | solve_crt([], []) 96 | 97 | 98 | def test_jacobi(): 99 | 100 | def test_jacobi_prime(module): 101 | sqrs = set() 102 | for a in range(module): 103 | sqrs.add((a * a) % module) 104 | for a in range(module): 105 | if gcd(a, module) == 1: 106 | real = 1 if a in sqrs else -1 107 | else: 108 | real = 0 109 | test = jacobi(a, module) 110 | assert real == test 111 | 112 | plist = primes(100) + [293, 1993, 2969, 2971, 9973, 11311] 113 | for module in plist[2:]: 114 | test_jacobi_prime(module) 115 | 116 | plist = primes(10)[2:] 117 | lezhs = {} 118 | 119 | for p in plist: 120 | lezhs[p] = [jacobi(a, p) for a in range(p)] 121 | 122 | for pnum in range(2, 4): 123 | for f in combinations_with_replacement(plist, pnum): 124 | n = reduce(operator.mul, f) 125 | for a in range(n): 126 | real = reduce(operator.mul, [lezhs[p][a % p] for p in f]) 127 | test = jacobi(a, n) 128 | if real != test: 129 | print("") 130 | print("%d | %d %s" % (a, n, repr(f))) 131 | print("Lezhandre symbols: %s" % 132 | repr([lezhs[p][a % p] for p in f])) 133 | for p in f: 134 | print(lezhs[p]) 135 | print("real %s" % repr(real)) 136 | print("test %s" % repr(test)) 137 | print() 138 | assert real == test 139 | 140 | with pytest.raises(ValueError): 141 | jacobi(1, 2) 142 | with pytest.raises(ValueError): 143 | jacobi(0, 6) 144 | with pytest.raises(ValueError): 145 | jacobi(0, 0) 146 | with pytest.raises(Exception): 147 | jacobi("qwe", 1024) 148 | with pytest.raises(Exception): 149 | jacobi(123, "qwe") 150 | 151 | 152 | def test_nCk_mod_pp(): 153 | print("\nTesting nCk mod prime powers") 154 | for p, max_e in [(2, 8), (3, 4), (5, 3), (7, 3), (11, 2), (13, 2)]: 155 | print(" prime %s pow up to %s" % (repr(p), repr(max_e))) 156 | for i in range(100): 157 | k = random.randint(1, 10000) 158 | n = k + random.randint(0, 10000) 159 | e = random.randint(1, max_e) 160 | my = nCk_mod_prime_power(n, k, p, e) 161 | real = nCk(n, k) % (p ** e) 162 | assert my == real 163 | 164 | 165 | def test_nCk_mod(): 166 | # TODO: do 167 | pass 168 | 169 | 170 | def test_factorial_mod(): 171 | print("\nTesting factorial mod prime powers") 172 | for p, max_e in [(2, 8), (3, 4), (5, 3), (7, 3), (11, 2)]: 173 | print(" prime %s pow up to %s" % (repr(p), repr(max_e))) 174 | for i in range(250): 175 | n = random.randint(1, 3000) 176 | e = random.randint(1, max_e) 177 | my = factorial_mod(n, {p: e}) 178 | real = factorial(n) % (p ** e) 179 | assert my == real 180 | 181 | print("\nTesting factorial mod small composites") 182 | for i in range(150): 183 | n = random.randint(1, 8000) 184 | x = random.randint(0, n * 2) 185 | my = factorial_mod(x, factorize(n)) 186 | real = factorial(x) % n 187 | assert my == real 188 | -------------------------------------------------------------------------------- /tests/test_primes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from libnum.primes import ( 4 | primes, 5 | prime_test, 6 | prime_test_ferma, 7 | prime_test_solovay_strassen, 8 | prime_test_miller_rabin, 9 | generate_prime, 10 | generate_prime_from_string, 11 | ) 12 | from libnum import len_in_bits, n2s 13 | 14 | 15 | def test_primes(): 16 | p = primes(100000) 17 | assert p[0] == 2 18 | assert len(p) == 9592 19 | assert p[9591] == 99991 # 9592th prime number 20 | with pytest.raises(TypeError): 21 | primes("") 22 | 23 | assert primes(-1) == [] 24 | assert primes(1) == [] 25 | with pytest.raises(TypeError): 26 | primes(1000000, "fake") 27 | 28 | 29 | def test_genprime(): 30 | for size in (2, 10, 64, 128, 129, 256): 31 | for ntry in range(10): 32 | p = generate_prime(size, k=25) 33 | assert len_in_bits(p) == size 34 | 35 | assert prime_test_ferma(p, k=25) 36 | assert prime_test_solovay_strassen(p, k=25) 37 | assert prime_test_miller_rabin(p, k=25) 38 | assert prime_test(p, k=25) 39 | 40 | with pytest.raises(ValueError): 41 | generate_prime(1) 42 | with pytest.raises(TypeError): 43 | generate_prime("") 44 | 45 | 46 | def test_genprime_str(): 47 | begin = b"preved medved \xde\xad\xbe\xef\x00\x00\x00\x00" 48 | n = generate_prime_from_string(begin) 49 | s = n2s(n) 50 | assert s.startswith(begin) 51 | assert prime_test(n, 25) 52 | 53 | with pytest.raises(TypeError): 54 | generate_prime_from_string(31337) 55 | with pytest.raises(ValueError): 56 | generate_prime_from_string("test", 8) 57 | with pytest.raises(ValueError): 58 | generate_prime_from_string("test", -8) 59 | 60 | 61 | def do_test_prime_test(func): 62 | for p in (3, 1993, 17333, 1582541, 459167430810992879232575962113190418519): 63 | assert func(p, 50) 64 | 65 | for p in primes(1000): 66 | assert func(p, 50) 67 | 68 | for not_p in (4, 1994, 1995, 16231845893292108971): 69 | assert not func(not_p, 50) 70 | 71 | with pytest.raises(TypeError): 72 | func("test") 73 | 74 | 75 | def test_fermatest(): 76 | return do_test_prime_test(prime_test_ferma) 77 | 78 | 79 | def test_solovay(): 80 | return do_test_prime_test(prime_test_solovay_strassen) 81 | 82 | 83 | def test_miller(): 84 | return do_test_prime_test(prime_test_miller_rabin) 85 | -------------------------------------------------------------------------------- /tests/test_ranges.py: -------------------------------------------------------------------------------- 1 | from libnum.ranges import Ranges 2 | 3 | 4 | def test_Ranges(): 5 | r = Ranges((0, 10)) 6 | assert r.len == 11 7 | assert r.min == 0 8 | assert r.max == 10 9 | r.add_range(100, 200) 10 | assert r == Ranges((0, 10), (100, 200)) 11 | assert r.len == 11 + 101 12 | assert r.min == 0 13 | assert r.max == 200 14 | -------------------------------------------------------------------------------- /tests/test_roots.py: -------------------------------------------------------------------------------- 1 | import random 2 | import pytest 3 | 4 | from libnum.common import gcd, randint_bits 5 | from libnum.primes import generate_prime 6 | from libnum.sqrtmod import ( 7 | sqrtmod, sqrtmod_prime_power, 8 | has_sqrtmod, has_sqrtmod_prime_power, 9 | jacobi, 10 | ) 11 | from libnum.factorize import factorize, unfactorize 12 | 13 | 14 | def check_valid_sqrt_pp(x, a, p, k): 15 | check_jacobi(a, p ** k, 1) 16 | 17 | all_roots = list(sqrtmod_prime_power(a, p, k)) 18 | assert all_roots 19 | assert sorted(all_roots) == sorted(set(all_roots)) 20 | 21 | reduced_roots = map(lambda a: a % p ** k, all_roots) 22 | assert sorted(all_roots) == sorted(reduced_roots) 23 | 24 | for r in all_roots: 25 | assert pow(r, 2, p ** k) == a 26 | 27 | if x is not None: 28 | assert x in all_roots 29 | 30 | 31 | def check_valid_sqrt_composite(x, a, factors): 32 | n = unfactorize(factors) 33 | 34 | all_roots = list(sqrtmod(a, factors)) 35 | assert all_roots 36 | assert sorted(all_roots) == sorted(set(all_roots)) 37 | 38 | for r in all_roots: 39 | assert pow(r, 2, n) == a 40 | 41 | if x is not None: 42 | assert x in all_roots 43 | 44 | 45 | def check_jacobi(a, n, has_sqrt): 46 | if n & 1 == 0: 47 | return 48 | j = jacobi(a, n) 49 | if gcd(a, n) != 1: 50 | assert j == 0 51 | elif has_sqrt: 52 | assert j == 1 53 | else: 54 | assert j 55 | 56 | 57 | def test_has_sqrtmod(): 58 | with pytest.raises(TypeError): 59 | has_sqrtmod_prime_power(3, 9, "") 60 | with pytest.raises(TypeError): 61 | has_sqrtmod_prime_power(3, "", 30) 62 | with pytest.raises(TypeError): 63 | has_sqrtmod_prime_power("", 9, 30) 64 | with pytest.raises(ValueError): 65 | has_sqrtmod_prime_power(1, 2, 0) # 1 mod 2**0 66 | with pytest.raises(ValueError): 67 | has_sqrtmod_prime_power(1, 1, 2) # 1 mod 1**2 68 | 69 | with pytest.raises(TypeError): 70 | has_sqrtmod(3, {9: ""}) 71 | with pytest.raises(TypeError): 72 | has_sqrtmod(3, {"": 2}) 73 | with pytest.raises(TypeError): 74 | has_sqrtmod("", {9: 30}) 75 | with pytest.raises(TypeError): 76 | has_sqrtmod("", {(9,): 30}) 77 | with pytest.raises(ValueError): 78 | has_sqrtmod(1, {2: 0}) # 1 mod 2**0 79 | with pytest.raises(ValueError): 80 | has_sqrtmod(1, {1: 2}) # 1 mod 1**2 81 | with pytest.raises(ValueError): 82 | has_sqrtmod(3, {}) 83 | 84 | 85 | def test_sqrt_pp_all(): 86 | print("\nTesting all residues by small modules") 87 | pairs = [(2, 11), (3, 7), (5, 5), (7, 4), (11, 3), (13, 3), (97, 2)] 88 | for prime, maxpow in pairs: 89 | for k in range(1, maxpow + 1): 90 | n = prime ** k 91 | print(" Testing %s**%s" % (prime, k)) 92 | for x in range(n): 93 | a = pow(x, 2, n) 94 | 95 | is_sqrt = has_sqrtmod(a, {prime: k}) 96 | is_sqrt2 = has_sqrtmod_prime_power(a, prime, k) 97 | assert is_sqrt == is_sqrt2 98 | if is_sqrt: 99 | check_valid_sqrt_pp(None, a, prime, k) 100 | 101 | check_jacobi(a, n, is_sqrt) 102 | 103 | assert has_sqrtmod_prime_power(a, prime, k) 104 | assert has_sqrtmod(a, {prime: k}) 105 | check_valid_sqrt_pp(x, a, prime, k) 106 | 107 | 108 | def test_sqrt_pp_rand(): 109 | print("\nTesting random residues by random modules") 110 | pairs = [(2, 500), (10, 100), (64, 15), (128, 5), (129, 5), (256, 2)] 111 | for size, maxpow in pairs: 112 | for i in range(10): 113 | p = generate_prime(size, k=25) 114 | print(" Testing %s-bit prime with max power %s: %s..." % 115 | (size, maxpow, str(p)[:32])) 116 | for j in range(10): 117 | k = random.randint(1, maxpow) 118 | x = random.randint(0, p ** k - 1) 119 | a = pow(x, 2, p ** k) 120 | check_valid_sqrt_pp(x, a, p, k) 121 | 122 | 123 | def test_sqrt_composite_all(): 124 | print("\nTesting all residues by small composite modules") 125 | ns = [ 126 | 10, 30, 50, 99, 100, 655, 1025, 1337, 127 | 7 ** 3 * 3, 2 ** 6 * 13, 2 ** 4 * 3 ** 3 * 5, 3 * 3 * 5 * 7, 128 | 1024, 129 | ] 130 | for n in ns: 131 | f = factorize(n) 132 | print(" Testing %s = %s" % (n, f)) 133 | for x in range(n): 134 | a = pow(x, 2, n) 135 | is_sqrt = has_sqrtmod(a, f) 136 | if is_sqrt: 137 | check_valid_sqrt_composite(None, a, f) 138 | 139 | check_jacobi(a, n, is_sqrt) 140 | 141 | assert has_sqrtmod(a, f) 142 | check_valid_sqrt_composite(x, a, f) 143 | 144 | 145 | def test_sqrt_composite_rand(): 146 | print("\nTesting all residues by random composite modules") 147 | for size, ntries in [(2, 2), (3, 3), (5, 10), (7, 20), (10, 20)]: 148 | for i in range(ntries): 149 | n = randint_bits(size) 150 | f = factorize(n) 151 | print(" Testing %s-bit number: %s..." % 152 | (size, str(n)[:32])) 153 | for x in range(n): 154 | a = pow(x, 2, n) 155 | is_sqrt = has_sqrtmod(a, f) 156 | if is_sqrt: 157 | check_valid_sqrt_composite(None, a, f) 158 | 159 | check_jacobi(a, n, is_sqrt) 160 | 161 | assert has_sqrtmod(a, f) 162 | check_valid_sqrt_composite(x, a, f) 163 | 164 | 165 | def test_sqrt_composite_rand_rand(): 166 | print("\nTesting random residues by random composite modules") 167 | for size, ntries in [(10, 20), (20, 20), (24, 20), (30, 20)]: 168 | for i in range(ntries): 169 | n = randint_bits(size) 170 | f = factorize(n) 171 | print(" Testing %s-bit number: %s..." % 172 | (size, str(n)[:32])) 173 | for j in range(30): 174 | x = random.randint(0, n - 1) 175 | a = pow(x, 2, n) 176 | is_sqrt = has_sqrtmod(a, f) 177 | if is_sqrt: 178 | check_valid_sqrt_composite(None, a, f) 179 | 180 | check_jacobi(a, n, is_sqrt) 181 | 182 | assert has_sqrtmod(a, f) 183 | check_valid_sqrt_composite(x, a, f) 184 | -------------------------------------------------------------------------------- /tests/test_strings.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from libnum import s2n, n2s, s2b, b2s 3 | 4 | 5 | def test_s2n_n2s(): 6 | s = b"long string to test" 7 | val = 2418187513319072758194084480823884981773628276 8 | assert s2n(s) == val 9 | assert n2s(val) == s 10 | with pytest.raises(TypeError): 11 | s2n(100) 12 | with pytest.raises(TypeError): 13 | n2s("qwe") 14 | 15 | 16 | def test_s2b_b2s(): 17 | s = b"just string" 18 | bs = "01101010011101010111001101110100001000000111" 19 | bs += "00110111010001110010011010010110111001100111" 20 | assert s2b(s) == bs 21 | assert b2s(bs) == s 22 | with pytest.raises(TypeError): 23 | s2b(123) 24 | with pytest.raises(TypeError): 25 | b2s(123) 26 | with pytest.raises(ValueError): 27 | b2s(b"deadbeef") 28 | -------------------------------------------------------------------------------- /tests/test_stuff.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from libnum import grey_code, rev_grey_code, nCk 3 | 4 | 5 | def test_grey_code(): 6 | pg = 0 7 | for a in range(10000): 8 | g = grey_code(a) 9 | if a: # two consequent grey numbers differ only in one bit 10 | x = g ^ pg 11 | assert x ^ (x - 1) == (x << 1) - 1 12 | pg = g 13 | assert a == rev_grey_code(g) 14 | with pytest.raises(TypeError): 15 | grey_code("qwe") 16 | 17 | 18 | def test_nck(): 19 | for n in (2, 5, 7, 100): 20 | csum = 0 21 | for x in range(n + 1): 22 | csum += nCk(n, x) 23 | assert csum == 2 ** n 24 | 25 | row = [1] 26 | for n in range(1, 200): 27 | row = [0] + row + [0] 28 | row = [row[i - 1] + row[i] for i in range(1, len(row))] 29 | for i in range(len(row)): 30 | assert row[i] == nCk(n, i) 31 | 32 | assert nCk(10, -1) == 0 33 | assert nCk(10, 11) == 0 34 | assert nCk(0, 0) == 1 35 | assert nCk(0, 1) == 0 36 | with pytest.raises(ValueError): 37 | nCk(-1, 0) 38 | --------------------------------------------------------------------------------