├── .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 |
--------------------------------------------------------------------------------