commit 50f7b1159e2b1e49c0bc8b37c470d0f9eb349d68 Author: geoffrey Date: Sun Jan 11 09:19:22 2026 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d271b56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +build/** +Cryptotools.egg-info/** +dist/** +**.swp diff --git a/Cryptotools/Cryptotools.egg-info/PKG-INFO b/Cryptotools/Cryptotools.egg-info/PKG-INFO new file mode 100644 index 0000000..1f32233 --- /dev/null +++ b/Cryptotools/Cryptotools.egg-info/PKG-INFO @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: Cryptotools +Version: 1.0 +Summary: Cryptography library +Author: Geoffrey Bucchino +Author-email: contact@bucchino.org diff --git a/Cryptotools/Cryptotools.egg-info/SOURCES.txt b/Cryptotools/Cryptotools.egg-info/SOURCES.txt new file mode 100644 index 0000000..9c3a71f --- /dev/null +++ b/Cryptotools/Cryptotools.egg-info/SOURCES.txt @@ -0,0 +1,13 @@ +README.md +setup.py +Cryptotools/Cryptotools.egg-info/PKG-INFO +Cryptotools/Cryptotools.egg-info/SOURCES.txt +Cryptotools/Cryptotools.egg-info/dependency_links.txt +Cryptotools/Cryptotools.egg-info/top_level.txt +Cryptotools/Numbers/__init__.py +Cryptotools/Numbers/carmi.py +Cryptotools/Numbers/numbers.py +Cryptotools/Numbers/primeNumber.py +tests/test_carmi.py +tests/test_numbers.py +tests/test_phi.py \ No newline at end of file diff --git a/Cryptotools/Cryptotools.egg-info/dependency_links.txt b/Cryptotools/Cryptotools.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Cryptotools/Cryptotools.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/Cryptotools/Cryptotools.egg-info/top_level.txt b/Cryptotools/Cryptotools.egg-info/top_level.txt new file mode 100644 index 0000000..c4a38ac --- /dev/null +++ b/Cryptotools/Cryptotools.egg-info/top_level.txt @@ -0,0 +1 @@ +Numbers diff --git a/Cryptotools/Encryptions/RSA.py b/Cryptotools/Encryptions/RSA.py new file mode 100644 index 0000000..5e1fdd3 --- /dev/null +++ b/Cryptotools/Encryptions/RSA.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.coprime import phi +from Cryptotools.Numbers.carmi import carmichael_lambda +from Cryptotools.Utils.utils import gcd +from Cryptotools.Numbers.primeNumber import getPrimeNumber +from sys import getsizeof + +class RSAKey: + """ + This class store the RSA key with the modulus associated + The key is a tuple of the key and the modulus n + + Attributes: + key: It's the exponent key, can be public or private + modulus: It's the public modulus + length: It's the key length + """ + def __init__(self, key, modulus, length): + """ + Contain the RSA Key. An object of RSAKey can be a public key or a private key + + Args: + key (Integer): it's the exponent of the key + modulus (Integer): it's the public modulus of the key + length (Integer): length of the key + """ + + self._key = key + self._modulus = modulus + self._length = length + + @property + def key(self): + return self._key + + @property + def modulus(self): + return self._modulus + + @property + def length(self): + return self.length + +class RSA: + """ + This class generate public key based on RSA algorithm + + Attributes: + p: it's the prime number for generating the modulus + q: it's the second prime number for the modulus + public: Object of the RSAKey which is the public key + private: Object of the RSAKey for the private key + """ + + def __init__(self): + """ + Build a RSA Key + """ + self._p = None + self._q = None + self._public = None + self._private = None + + def generateKeys(self, size=512): + """ + This function generate both public and private keys + Args: + size: It's the size of the key and must be multiple of 64 + """ + # p and q must be coprime + self._p = getPrimeNumber(size) + self._q = getPrimeNumber(size) + + # compute n = pq + n = self._p * self._q + + phin = (self._p - 1) * (self._q - 1) + + # e must be coprime with phi(n) + # According to the FIPS 186-5, the public key exponent must be odd + # and the minimum size is 65536 (Cf. Section 5.4 PKCS #1) + # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf + e = 65535 # Works + for i in range(2, phin - 1): + if gcd(phin, e) == 1: + break + e += 1 + # print(gcd(phin, e)) + self._public = RSAKey(e, n, getsizeof(e)) + + # d is the reverse modulo of phi(n) + # d = self._inverseModular(e, phin) + d = pow(e, -1, phin) # Works in python 3.8 + self._private = RSAKey(d, n, getsizeof(d)) + + @property + def e(self): + return self._public.key + + @property + def d(self): + return self._private.key + + @property + def n(self): + return self._public.modulus + + @property + def p(self): + return self._p + + @property + def q(self): + return self._q + + def encrypt(self, data) -> list: + """ + This function return a list of data encrypted with the public key + + Args: + data (str): it's the plaintext which need to be encrypted + + Returns: + return a list of the data encrypted, each entries contains the value encoded + """ + dataEncoded = self._str2bin(data) + return list( + pow(int(x), self._public.key, self._public.modulus) + for x in dataEncoded + ) + + def decrypt(self, data) -> list: + """ + This function return a list decrypted with the private key + + Args: + data (str): It's the encrypted data which need to be decrypted + + Returns: + Return the list of data uncrypted into plaintext + """ + decrypted = list() + for x in data: + d = pow(x, self._private.key, self._private.modulus) + decrypted.append(chr(d)) + return ''.join(decrypted) + + def _str2bin(self, data) -> list: + """ + This function convert a string into the unicode value + + Args: + data: the string which need to be converted + + Returns: + Return a list of unicode values of data + """ + return list(ord(x) for x in data) + + def _inverseModular(self, a, n): + """ + This function compute the modular inverse for finding d, the decryption key + + Args: + a (Integer): the base of the exponent + n (Integer): the modulus + """ + for b in range(1, n): + #if pow(a, b, n) == 1: + if (a * b) % n == 1: + inv = b + break + return inv diff --git a/Cryptotools/Encryptions/__init__.py b/Cryptotools/Encryptions/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/Cryptotools/Encryptions/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/Cryptotools/Groups/__init__.py b/Cryptotools/Groups/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/Cryptotools/Groups/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/Cryptotools/Groups/cyclic.py b/Cryptotools/Groups/cyclic.py new file mode 100644 index 0000000..fe63ce4 --- /dev/null +++ b/Cryptotools/Groups/cyclic.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.group import Group + + +class Cyclic(Group): + """ + This object contain a list of the Group for a cyclic group. This class find all generator of the group. + This class is inherited from Group object + + Attributes: + G (list): list of all elements in the group + n (Integer): it's the value where the group has been generated + operation (Function): it's the operation generating the group + generators (list): contain all generators of the group + generatorChecked (boolean): Check if generators has been found + """ + def __init__(self, G:list, n, ope): + super().__init__(n, G, ope) # Call the Group's constructor + self._G = G + self._n = n + self._operation = ope + self._generators = list() + self._generatorChecked = False + + def generator(self): + """ + This function find all generators in the group G + """ + + index = 1 + G = sorted(self._g) + for g in range(2, self._n): + z = list() + for entry in range(1, self._n): + res = self._operation(g, index, self._n) + if res not in z: + z.append(res) + index = index + 1 + + # We check if that match with G + # If yes, we find a generator + if sorted(z) == G: + self._generators.append(g) + self._generatorChecked = True + + def getPrimitiveRoot(self): + """ + This function return the primitive root modulo of n + + Returns: + Return the primitive root of the group. None if no primitive has been found + """ + + index = 1 + G = sorted(self._g) + for g in range(2, self._n): + z = list() + for entry in range(1, self._n): + res = self._operation(g, index, self._n) + if res not in z: + z.append(res) + index += 1 + + # If the group is the same has G, we found a generator + if sorted(z) == G: + return g + return None + + def getGenerators(self) -> list: + """ + This function return all generators of that group + + Returns: + Return the list of generators found. The function generators() must be called before to call this function + """ + if not self._generatorChecked: + self.generator() + self._generatorChecked = True + return self._generators + + def isCyclic(self) -> bool: + """ + Check if the group is a cyclic group, means we have at least one generator + + Returns: + REturn a boolean, False if the group is not Cyclic otherwise return True + """ + if len(self.getGenerators()) == 0: + return False + return True diff --git a/Cryptotools/Groups/galois.py b/Cryptotools/Groups/galois.py new file mode 100644 index 0000000..741ec2a --- /dev/null +++ b/Cryptotools/Groups/galois.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.group import Group + + +class Galois: + """ + This class contain the Galois Field (Finite Field) + + + + Attributes: + q (Integer): it's the number of the GF + operation (Function): Function for generating the group + identityAdd (Integer): it's the identity element in the GF(n) for the addition operation + identityMul (Integer): it's the identity element in the GF(n) for the multiplicative operation + """ + def __init__(self, q, operation): + self._q = q + self._operation = operation + self._identityAdd = 0 + self._identityMul = 1 + self._F = [x for x in range(q)] + self._add = [[0 for x in range(q)] for y in range(q)] + + # TODO: May do we do a deep copy between all groups ? + self._div = [[0 for x in range(q)] for y in range(q)] + self._mul = [[0 for x in range(q)] for y in range(q)] + self._sub = [[0 for x in range(q)] for y in range(q)] + self._primitiveRoot = list() + + def primitiveRoot(self): + """ + In this function, we going to find the primitive root modulo n of the galois field + + Returns: + Return the list of primitive root of the GF(q) + """ + for x in range(2, self._q): + z = list() + for entry in range(1, self._q): + res = self._operation(x, entry, self._q) + if res not in z: + z.append(res) + + if self.isPrimitiveRoot(z, self._q - 1): + if x not in self._primitiveRoot: # It's dirty, need to find why we have duplicate entry + self._primitiveRoot.append(x) + return z + + def getPrimitiveRoot(self): + """ + Return the list of primitives root + + Returns: + Return the primitive root + """ + return self._primitiveRoot + + def isPrimitiveRoot(self, z, length): + """ + Check if z is a primitive root + + Args: + z (list): check if z is a primitive root + length (Integer): Length of the GF(q) + """ + if len(z) == length: + return True + return False + + def add(self): + """ + This function do the operation + on the Galois Field + + Returns: + Return a list of the group with the addition operation + """ + for x in range(0, self._q): + for y in range(0, self._q): + self._add[x][y] = (x + y) % self._q + return self._add + + def _inverseModular(self, a, n): + """ + This function find the reverse modular of a by n + + Returns: + Return the reverse modular + """ + for b in range(1, n): + if (a * b) % n == 1: + inv = b + break + return inv + + def div(self): + """ + This function do the operation / on the Galois Field + + Returns: + Return a list of the group with the division operation + """ + for x in range(1, self._q): + for y in range(1, self._q): + inv = self._inverseModular(y, self._q) + + self._div[x][y] = (x * inv) % self._q + return self._div + + def mul(self): + """ + This function do the operation * on the Galois Field + + Returns: + Return a list of the group with the multiplication operation + """ + for x in range(0, self._q): + for y in range(0, self._q): + self._mul[x][y] = (x * y) % self._q + return self._mul + + def sub(self): + """ + This function do the operation - on the Galois Field + + Returns: + Return a list of the group with the subtraction operation + """ + for x in range(0, self._q): + for y in range(0, self._q): + self._sub[x][y] = (x - y) % self._q + return self._sub + + def check_closure_law(self, arithmetic): + """ + This function check the closure law. + By definition, every element in the GF is an abelian group, which respect the closure law: for a and b belongs to G, a + b belongs to G, the operation is a binary operation + + Args: + Arithmetics (str): contain the operation to be made, must be '+', '*', '/' or /-' + """ + if arithmetic not in ['+', '*', '/', '-']: + raise Exception("The arithmetic need to be '+', '*', '/' or '-'") + + if arithmetic == '+': + G = self._add + elif arithmetic == '*': + G = self._mul + elif arithmetic == '/': + G = self._div + else: + G = self._sub + + start = 0 + """ + In case of multiplicative, we bypass the first line, because all elements are zero, otherwise the test fail + """ + if arithmetic == '*' or arithmetic == '/': + start = 1 + + isClosure = True + for x in range(start, self._q): + gr = Group(self._q, G[x], self._operation) + if not gr.closure(): + isClosure = False + del gr + + if isClosure: + print(f"The group {arithmetic} respect closure law") + else: + print(f"The group {arithmetic} does not respect closure law") + + def check_identity_add(self): + """ + This function check the identity element and must satisfy this condition: $a + 0 = a$ for each element in the GF(n) + In Group Theory, an identity element is an element in the group which do not change the value every element in the group + + Returns: + + """ + for x in self._F: + if not self._identityAdd + x == x: + raise Exception( + f"The identity element {self._identityAdd} "\ + "do not satisfy $a + element = a$" + ) + + def check_identity_mul(self): + """ + This function check the identity element and must satisfy this condition: $a * 1 = a$ for each element in the GF(n) + In Group Theory, an identity element is an element in the group which do not change the value every element in the group + + Returns: + + """ + for x in self._F: + if not self._identityMul * x == x: + raise Exception( + f"The identity element {self._identityAdd} "\ + "do not satisfy $a * element = a$" + ) + + def printMatrice(self, m): + """ + This function print the GF(m) + + Args: + m (list): Matrix of the GF + """ + header = str() + header = " " + for x in range(0, self._q): + header += f"{x} " + + header += "\n--|" + "-" * (len(header)- 3) +"\n" + + s = str() + + for x in range(0, self._q): + s += f"{x} | " + for y in range(0, self._q): + s += f"{m[x][y]} " + s += "\n" + + s = header + s + print(s) diff --git a/Cryptotools/Groups/group.py b/Cryptotools/Groups/group.py new file mode 100644 index 0000000..b96484d --- /dev/null +++ b/Cryptotools/Groups/group.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +class Group: + """ + This class generate a group self._g based on the operation (here denoted +) + with the function ope: (a ** b) % n + + In group theory, any group has an identity element (e), which with the binary operation, do not change the value and must satisfy the condition: $a + e = 0$ + + Attributes: + n (Integer): It's the G_n elements in the Group + g (List): The set of the Group + operation (Function): Function for generating the Group + identity (Integer): The identity of the group. + reverse (Dict): For each elements of the Group of n, we have the reverse value + """ + def __init__(self, n, g, ope): + self._n = n + self._g = g + self._operation = ope + self._identity = 0 + self._reverse = dict() + + def getG(self) -> list(): + """ + This function return the Group + + Returns: + List of all elements in the Group + """ + return self._g + + def closure(self) -> bool: + """ + Check the closure law + In a group, each element a, b belongs to G, such as a + b belongs to G + + Returns: + Return a Boolean if the closure law is respected. True if yes otherwise it's False + """ + for e1 in self._g: + for e2 in self._g: + res = self._operation(e1, e2, self._n) + if not res in self._g: + # raise Exception(f"{res} not in g. g is not a group") + return False + return True + + def associative(self) -> bool: + """ + Check the associative law. + In a group, for any a, b and c belongs to G, + they must respect this condition: (a + b) + c = a + (a + b) + + Returns: + Return a boolean if the Associative law is respected. True if yes otherwise it's False + """ + a = self._g[0] + b = self._g[1] + c = self._g[2] + + res_ope = self._operation(a, b, self._n) + res1 = self._operation(res_ope, c, self._n) + + res_ope = self._operation(b, c, self._n) + res2 = self._operation(a, res_ope, self._n) + if res1 != res2: + # raise Exception(f"{res1} is different from {res2}. g is not a group") + return False + return True + + def identity(self) -> bool: + """ + Check the identity law. + In a group, an identity element exist and must be uniq + + Returns: + Return a Boolean if the identity elements has been found. True if found otherwise it's False + """ + for a in self._g: + for b in self._g: + if not self._operation(a, b, self._n) == b: + break + + self._identity = a + return True + return False + + def getIdentity(self) -> int: + """ + Return the identity element. The function identitu() must be called before. + + Returns: + Return the identity element if it has been found + """ + return self._identity + + def reverse(self) -> bool: + """ + Check the inverse law + In a group, for each element belongs to G + they must have an inverse a ^ (-1) = e (identity) + + Returns: + Return a Boolean if the all elements ha a reverse element. True if yes otherwise it's False + """ + reverse = False + for a in self._g: + for b in self._g: + if self._operation(a, b, self._n) == self._identity: + self._reverse[a] = b + reverse = True + break + return reverse + + def getReverses(self) -> dict: + """ + This function return the dictionary of all reverses elements. The key is the element in G and the value is the reverse element + + Returns: + Return the reverse dictionary + + """ + return self._reverse diff --git a/Cryptotools/Numbers/__init__.py b/Cryptotools/Numbers/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/Cryptotools/Numbers/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/Cryptotools/Numbers/__pycache__/__init__.cpython-310.pyc b/Cryptotools/Numbers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..4dd26cb Binary files /dev/null and b/Cryptotools/Numbers/__pycache__/__init__.cpython-310.pyc differ diff --git a/Cryptotools/Numbers/__pycache__/__init__.cpython-39.pyc b/Cryptotools/Numbers/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..3a1d189 Binary files /dev/null and b/Cryptotools/Numbers/__pycache__/__init__.cpython-39.pyc differ diff --git a/Cryptotools/Numbers/__pycache__/carmi.cpython-310.pyc b/Cryptotools/Numbers/__pycache__/carmi.cpython-310.pyc new file mode 100644 index 0000000..b046345 Binary files /dev/null and b/Cryptotools/Numbers/__pycache__/carmi.cpython-310.pyc differ diff --git a/Cryptotools/Numbers/__pycache__/carmi.cpython-39.pyc b/Cryptotools/Numbers/__pycache__/carmi.cpython-39.pyc new file mode 100644 index 0000000..c5b283b Binary files /dev/null and b/Cryptotools/Numbers/__pycache__/carmi.cpython-39.pyc differ diff --git a/Cryptotools/Numbers/carmi.py b/Cryptotools/Numbers/carmi.py new file mode 100644 index 0000000..71cfde0 --- /dev/null +++ b/Cryptotools/Numbers/carmi.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import gcd, isPrimeNumber +from Cryptotools.lcm import lcm +from Cryptotools.Numbers.coprime import phi +from Cryptotools.Utils.utils import gcd +from sympy import factorint +from functools import reduce + + +def carmichael_lambda(n: int) -> int: + """ + This function is a carmichael lambda for identifying the smallest integer m + and saisfy this condition: a ** m congrue 1 modulo n + This function is relatively closed with the euler's totient (phi of n) + + Args: + n (Integer): found the smallest integer of n + + Returns: + REturn the smallest integer of n + """ + + # First, factorizing n number + # factorint return a list of prime factors of n + # Base on that theorem: + # https://en.wikipedia.org/wiki/Fundamental_theorem_of_arithmetic + factorsN = factorint(n) + + """ + The result of the factorint, we have the following: + n = p ** e1, p ** e2, ..., p ** ek + """ + + factors = list() + + for p, e in factorsN.items(): + # Base on the theorem of arithmetic, p is always prime + if isPrimeNumber(p): + if p == 2 and e >= 3: + factors.append(phi(pow(p, e)) // 2) + elif p >= 2 and e > 0: + factors.append(phi(pow(p, e))) + elif p > 2: + factors.append(phi(pow(p, e))) + + # Now, we can compute lcm + result = reduce(lcm, factors) + + return result + +def carmichael_lambda2(n): + """ + Deprecated function + """ + # Get all coprimes with n + coprimes = list() + for a in range(1, n): + if gcd(a, n) == 1: + coprimes.append(a) + + # Need to find the smallest value + """ + Need to find the smallest value m. Must to satisfy this condition: + a ** m congrus 1 mod n + """ + for m in range(1, n): + print(f"{m} {a ** m % n}") + + print(coprimes) + +def carmi_numbers(n): + """ + This function get all GCD of the number if these number are prime or not + + Args: + n (Integer): Iterate to n elements + + Returns: + Return a list of prime numbers + """ + # https://kconrad.math.uconn.edu/blurbs/ugradnumthy/carmichaelkorselt.pdf + primes = list() + for i in range(2, n - 1): + if gcd(n, i) == i: + if isPrimeNumber(i): + primes.append(i) + + return primes + +def is_carmichael(n, primes): + """ + This function check if the n number is a carmichael number. The arguments primes at least 3 prime numbers + As it's said in the Carmichael theorem, a carmichael number has three factors and they are all primes: https://en.wikipedia.org/wiki/Carmichael_number + With the Korselt's criterion, to check if the number is a carmichael number p - 1 / n - 1. + + Args: + n (Integer): the carmichael number + primes (list): list of prime numbers + + Returns: + Boolean of the carmichael's number result + """ + r = True + n = n - 1 + + if len(primes) < 3: + return False + + # Korselt's criterion + for p in primes: + p = p - 1 + if n % p != 0: + r = False + break + return r + +def is_carmi_number(n) -> bool: + """ + This function check if the number n is a carmichael number (pseudoprime) + + Args: + n (Integer): Iterate to n elements + + Returns: + Return a Boolean if n is a carmichael number or not. True if yes + """ + j = 0 + for i in range(n): + if i ** n % n == i: + j += 1 + if j == n: + return True + return False + +def generate_carmi_numbers(nbr) -> list: + """ + This function generate carmichael numbers, they are called pseudoprimes + For instance: a = 545, n = 561 + gcd(a, n) = 1 + pow(a, n - 1) % n = 1 + + Args: + nbr (Integer): Find all pseudoprimes until nbr is achieves + + Returns: + Return the list of pseudoprimes + """ + carmi = list() + + count = 0 + n = 2 + while count < nbr: + # First, we need to find a, coprime with n + for a in range(1, n): + # We check if n is coprime with a + # All integers must be coprime with n, a belong to n + if gcd(a, n) == 1: + # Check if is a carmichael number + # Fermat's little theorem, a ** (n - 1) % n, must be congruent to 1 + if pow(a, (n - 1)) % n == 1: + #if (a ** (n - 1)) % n == 1: + #print(f"{gcd(a, n)} {a} {n}") + if n not in carmi: + count += 1 + carmi.append(n) + else: + print(f"{gcd(a, n)} {a} {n}") + + break + n += 1 + print(carmi) + #for cop in range(2, n): + # if gcd(cop, n) == 1: + # coprimes.append(cop) + + return carmi diff --git a/Cryptotools/Numbers/coprime.py b/Cryptotools/Numbers/coprime.py new file mode 100644 index 0000000..6bf355e --- /dev/null +++ b/Cryptotools/Numbers/coprime.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +from Cryptotools.Utils.utils import gcd + + +# https://oeis.org/A000010 +def phi(n): + """ + This function count all phi(n) + + Args: + n (integer): it's the phi(n) value + + Returns: + Return the phi(n) + """ + y = 1 + for i in range(2, n): + if gcd(n, i) == 1: + y += 1 + return y + diff --git a/Cryptotools/Numbers/factorization.py b/Cryptotools/Numbers/factorization.py new file mode 100644 index 0000000..9211df0 --- /dev/null +++ b/Cryptotools/Numbers/factorization.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +from random import randint +from Cryptotools.Numbers.primeNumber import _millerRabinTest, isPrimeNumber +from Cryptotools.Utils.utils import gcd +from Cryptotools.lcm import lcm +from math import floor, log + + +def pollard_p_minus_1(n, B=None) -> int: + """ + This function factorize the number n into factor based on the Pollard's p - 1 algorithm + + The first is to choose B, which is a positive integer and B < n + In the second step, we need to define $M=\prod _{primes~q\leq B}q^{\lfloor \log_{q}{B}\rfloor }$ + + Args: + n (Integer): The number n to factorize + + Returns: + Return the factorize of the number n or None + """ + + if B is None: + B = randint(1, n - 1) + # B = randint(1, n - 1) + + #### We define M + # Select all prime numbers 1 < B + q = list() + for x in range(2, B): + # with the _millerRabinTest, the program crash, because the randint() is empty + # because, both min value and max value are 2 + # if _millerRabinTest(x): + if isPrimeNumber(x): + q.append(x) + + M = 1 + for prime in q: + e = floor(log(B, prime)) + # print(prime, e) + M *= pow(prime, e) + + # We choose a and coprime with n + a = 2 + while True: + if gcd(a, n) == 1: + break + a += 1 + + # We can compute g = gcd((a ** M) - 1, n) + g = gcd(pow(a, M, n) - 1, n) + if g > 1: + return g + + return None diff --git a/Cryptotools/Numbers/numbers.py b/Cryptotools/Numbers/numbers.py new file mode 100644 index 0000000..3295de3 --- /dev/null +++ b/Cryptotools/Numbers/numbers.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + + +def fibonacci(n) -> list: + """ + This function generate the list of Fibonacci + + Args: + n (integer): it's maximum value of Fibonacci sequence + + Returns: + Return a list of n element in the Fibonacci sequence + """ + fibo = [0, 1] + fibo.append(fibo[0] + fibo[1]) + for i in range(2, n): + fibo.append(fibo[i - 1] + fibo[i]) + return fibo + diff --git a/Cryptotools/Numbers/primeNumber.py b/Cryptotools/Numbers/primeNumber.py new file mode 100644 index 0000000..4622872 --- /dev/null +++ b/Cryptotools/Numbers/primeNumber.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 + +from random import randint, seed, getrandbits +from math import log2 +from Cryptotools.Utils.utils import gcd + + +def isPrimeNumber(p): + """ + Check if the number p is a prime number or not. The function iterate until p is achieve. + + This function is not the efficient way to determine if p is prime or a composite. + + To identify if p is prime or not, the function check the result of the Euclidean Division has a remainder. If yes, it's means it's not a prime number + + Args: + p (Integer): number if possible prime or not + + Returns: + Return a boolean if the number p is a prime number or not. True if yes + """ + for i in range(2, p): + if p % i == 0: + return False + return True + +# https://people.csail.mit.edu/rivest/Rsapaper.pdf +# https://arxiv.org/pdf/1912.11546 +# https://link.springer.com/content/pdf/10.1007/3-540-44499-8_27.pdf +# https://link.springer.com/article/10.1007/BF00202269 +# https://www.geeksforgeeks.org/dsa/how-to-generate-large-prime-numbers-for-rsa-algorithm/ +# https://crypto.stackexchange.com/questions/20548/generation-of-strong-primes +# https://crypto.stackexchange.com/questions/71/how-can-i-generate-large-prime-numbers-for-rsa + +def getPrimeNumber(n, safe=True): + """ + This function generate a large prime number + based on "A method for Obtaining Digital Signatures + and Public-Key Cryptosystems" + Section B. How to Find Large Prime Numbers + https://dl.acm.org/doi/pdf/10.1145/359340.359342 + + Args: + n (Integer): The size of the prime number. Must be a multiple of 64 + safe (Boolean): When generating the prime number, the function must find a safe prime number, based on the Germain primes (2p + 1 is also a prime number) + + Returns: + Return the prime number + """ + if n % 64 != 0: + print("Must be multiple of 64") + return + # from sys import getsizeof + upper = getrandbits(n) + lower = upper >> 7 + r = randint(lower, upper) + + while r % 2 == 0: + r += 1 + + # Now, we are going to compute r as prime number + i = 100 + while 1: + # Check if it's a prime number + if _millerRabinTest(r): + break + + # TODO: it's dirty, need to change that + # i = int(log2(r)) + # i *= randint(2, 50) + + r += i + # print(f"{i} {r}") + i += 1 + + + # print(_fermatLittleTheorem(r)) + # print(getsizeof(r)) + return r + +def getSmallPrimeNumber(n) -> int: + """ + This function is deprecated + + Args: + n (Integer): Get the small prime number until n + + Returns: + Return the first prime number found + """ + is_prime = True + + while True: + for i in range(2, n): + # If n is divisible by i, it's not a prime, we break the loop + if n % i == 0: + is_prime = False + break + + if is_prime: + break + is_prime = True + n = n + 1 + + p = n + return p + +def get_prime_numbers(n) -> list: + """ + This function get all prime number of the n + + Args: + n (Integer): find all prime number until n is achieves + + Returns: + Return a list of prime numbers + """ + l = list() + for i in range(2, n): + if n % i == 0: + l.append(i) + return l + +def get_n_prime_numbers(n) -> list: + """ + This function return a list of n prime numbers + + Args: + n (Integer): the range, must be an integer + + Returns: + Return a list of n prime numbers + """ + l = list() + + count = 2 + index = 0 + while index < n: + is_prime = True + for x in range(2, count): + if count % x == 0: + is_prime = False + break + + if is_prime: + l.append(count) + index += 1 + + count += 1 + + return l + +def are_coprime(p1, p2): + """ + This function check if p1 and p2 are coprime + + Args: + p1 (list): list of prime numbers of the first number + p2 (list): list of prime number of the second number + + Returns: + Return a boolean result + """ + r = True + for entry in p1: + if entry in p2: + r = False + break + return r + +def sieveOfEratosthenes(n) -> list: + """ + This function build a list of prime number based on the Sieve of Erastosthenes + + Args: + n (Integer): Interate until n is achives + + Returns: + Return a list of all prime numbers to n + """ + if n < 1: + return list() + + eratost = dict() + for i in range(2, n): + eratost[i] = True + + for i in range(2, n): + if eratost[i]: + for j in range(i*i, n, i): + eratost[j] = False + + sieve = list() + for i in range(2, n): + if eratost[i]: + sieve.append(i) + + return sieve + +def _fermatLittleTheorem(n) -> bool: + """ + The Fermat's little theorem. if n is a prime number, any number from 0 to n- 1 is a multiple of n + + Args: + n (Integer): Check if n is prime or not + + Returns: + Return True if the number a is a multiple of n, otherwise it's False + """ + a = randint(1, n - 1) + # We compute a ** (n - 1) % n + if pow(a, n - 1, n) == 1: + return True + return False + +def _millerRabinTest(n) -> bool: + """ + This function execute a Miller Rabin test + For the algorithm, it's based on the pseudo-algo provided by this document: + * https://www.cs.cornell.edu/courses/cs4820/2010sp/handouts/MillerRabin.pdf + The Mille-Rabin test is an efficient way to determine if n is prime or not + + Args: + n (Integer): Check if n is a prime number + Returns: + Return a boolean. True for a prime otherwise, it's a composite number + + """ + if n < 2 or (n > 2 and n % 2 == 0): # If n is even, it's a composite + return False + + k = 0 + q = n - 1 + #while (q & 1) == 0: + # k += 1 + # q >>= 1 + while q % 2 == 0: + k += 1 + q //= 2 + + # We choose a: a < n - 1 + for _ in range(40): + a = randint(2, n - 1) + # We compute a ** q % n + x = pow(a, q, n) + + # If it's a composite number + if x == 1 or x == n - 1: + continue + + for _ in range(k): + z = pow(x, 2, n) + if z == 1 or z == n - 1: + return False + else: + return False + return True + +def sophieGermainPrime(p) -> bool: + """ + Check if the number p is a safe prime number: 2p + 1 is also a prime + + Args: + p (Integer): Possible prime number + + Returns: + Return True if p is a safe prime number, otherwise it's False + """ + pp = 2 * p + 1 + return _millerRabinTest(pp) + +def isSafePrime(n) -> bool: + """ + This function has not been implemented yet, but check if the number n is a safe prime number. This function will test different properties of the possible prime number n + + Args: + n (Integer): the prime number to check + + Returns: + Return a Boolean if the prime number n is safe or not. True if yes, otherwise it's False + """ + if n.bit_length() >= 256: + return True + + # Do Sophie Germain's test + + return False + diff --git a/Cryptotools/Utils/__init__.py b/Cryptotools/Utils/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/Cryptotools/Utils/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/Cryptotools/Utils/number.py b/Cryptotools/Utils/number.py new file mode 100644 index 0000000..538e4bd --- /dev/null +++ b/Cryptotools/Utils/number.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +COMPOSITE = 0 +PRIME = 1 + +class Number: + def __init__(self): + self._int = int() + self._kind = COMPOSITE diff --git a/Cryptotools/Utils/utils.py b/Cryptotools/Utils/utils.py new file mode 100644 index 0000000..9090efe --- /dev/null +++ b/Cryptotools/Utils/utils.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +def gcd(a, b): + """ + This function calculate the GCD (Greatest Common Divisor of the number a of b + Args: + a (Integer): the number a + b (integer): the number b + + Return: + the GCD + """ + + if b == 0: + return a + return gcd(b, a%b) + +def egcd(a, b): + """ + This function compute the Extended Euclidean algorithm + https://user.eng.umd.edu/~danadach/Cryptography_20/ExtEuclAlg.pdf + https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm + + Args: + a (Integer): the number a + b (Integer): the number b + """ + if a == 0: + return b, 0, 1 + gcd, x1, y1 = egcd(b % a, a) + x = y1 - (b // a) * x1 + y = x1 + return gcd, x, y + diff --git a/Cryptotools/__init__.py b/Cryptotools/__init__.py new file mode 100644 index 0000000..9c3c3bd --- /dev/null +++ b/Cryptotools/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +__all__ = ["Numbers", "Groups"] diff --git a/Cryptotools/lcm.py b/Cryptotools/lcm.py new file mode 100644 index 0000000..ebdcd7c --- /dev/null +++ b/Cryptotools/lcm.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import gcd + +def compute_multiple(a): + """ + This function compute the 20 first multiples of the number a + + Args: + a (Integer): the number + + Return: + list of multiple of a + """ + + r = list() + for i in range(1, 20): + r.append(a * i) + return r + +def lcm(a, b): + """ + This function get the LCM (Least Common Multiple) a of b + + Args: + a (Integer): the number + b (Integer): the number + + Return: + the LCM + """ + return (b // gcd(a, b)) * a + diff --git a/README.md b/README.md new file mode 100644 index 0000000..36c40cd --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Introduction +This project contains the module *cryptotools* which can used for cryptography + +# Install the program +``` +$ virtualenv ~/venv/myenv +$ source ~/venv/myenv/bin/activate +$ python3 setup.py install +``` + +## Examples +In the directory *examples/*, you have different python files for using the module + +## Unitests +### Test phi +``` +$ python3 tests/test_phi.py -v +``` + +### Test Fibonacci +For testing Fibonacci sequence, I generate a list from OEIS wich contains 40 first numbers of Fibonacci. The `test_numbers.py` test the Fibonacci sequence generated from the functions `fibonacci` in `utils/numbers.py`: + +``` +$ python3 tests/test_numbers.py -v +``` + +# Build the doc + +``` +$ mkdocs build +``` diff --git a/docs/examples-rsa-keys.md b/docs/examples-rsa-keys.md new file mode 100644 index 0000000..0b66fb7 --- /dev/null +++ b/docs/examples-rsa-keys.md @@ -0,0 +1,27 @@ +# Generating RSA Keys +In the following section, we are going to see how to generate RSA Keys and how to encrypt data with these keys. + +``` +#!/usr/bin/env python3 + +from Cryptotools.Encryptions.RSA import RSA + + +rsa = RSA() +rsa.generateKeys(size=512) + +e = rsa.e +d = rsa.d +n = rsa.n + +s = "I am encrypted with RSA" +print(f"plaintext: {s}") +encrypted = rsa.encrypt(s) + +# Encrypt data +print(f"ciphertext: {encrypted}") + +# We decrypt +plaintext = rsa.decrypt(encrypted) +print(f"Plaintext: {plaintext}") +``` diff --git a/docs/group-theory.md b/docs/group-theory.md new file mode 100644 index 0000000..dc18237 --- /dev/null +++ b/docs/group-theory.md @@ -0,0 +1,9 @@ +# Group Theory +## Group +::: Cryptotools.Groups.group + +## Cyclic group +::: Cryptotools.Groups.cyclic + +## Galois Field (Finite Field) +::: Cryptotools.Groups.galois diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b575c8e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,13 @@ +# Welcome to CryptoTools + +* [Introduction](/introduction) +* [Installation](/installation) +* Low-Level Cryptographic + * [Number Theory](/number-theory) + * [Group Theory](/group-theory) +* Public Keys: + * [RSA](/rsa) +* Examples: + * [Generating RSA keys](/examples-rsa-keys) + + diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..adc2c0a --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,18 @@ +# Installation +To install the project, you should install from my own Python repository but, you first need to deploy your virtual enviroment. + +## Installation from source +You can install from the source. Download the [project](https://gitea.bucchino.org/gbucchino/cryptotools.git) and install it in your virtual environment: + +``` +$ virtualenv ~/venv/cryptotools +$ source ~/venv/cryptotools/bin/activate +``` + +``` +$ git clone https://gitea.bucchino.org/gbucchino/cryptotools.git +$ cd cryptotools +$ python3 setup.py install +``` + +The installation is completed. You can know use Cryptotools package into your project. In the directory `examples` you may find some examples scripts diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 0000000..29f95f8 --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,5 @@ +# CryptoTools +CryptoTools is a Python package that provides low-level cryptographic primitives for generating strong numbers. With this project, it's possible to generate public key cryptosystems such as RSA. +This project has a academic purpose and can not be used in production enviroment yet. + +So far, my cryptographic modules are not compliant with [FIPS 140-3](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-3.pdf) but in the future, that will be. diff --git a/docs/number-theory.md b/docs/number-theory.md new file mode 100644 index 0000000..9efa817 --- /dev/null +++ b/docs/number-theory.md @@ -0,0 +1,9 @@ +# Number Theory +For generating keys, we need strong prime number, they are the basis. CryptoTools provides functions for generating these numbers. + +## prime numbers +::: Cryptotools.Numbers.primeNumber + +## Fibonacci sequence +::: Cryptotools.Numbers.numbers + diff --git a/docs/rsa.md b/docs/rsa.md new file mode 100644 index 0000000..19d8ad6 --- /dev/null +++ b/docs/rsa.md @@ -0,0 +1,3 @@ +CryptoTools provides several functions for generating a RSA Keys + +::: Cryptotools.Encryptions.RSA diff --git a/examples/carmi.py b/examples/carmi.py new file mode 100644 index 0000000..bf131af --- /dev/null +++ b/examples/carmi.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +from Cryptotools.Utils.utils import gcd + + +def carmi_first_method(n) -> int: + coprimes = list() + for i in range(1, n): + if gcd(i, n) == 1: + coprimes.append(i) + print(coprimes) + smallest = list() + for m in range(1, n): + l = 0 + for a in coprimes: + if a ** m % n == 1: + l += 1 + if l == len(coprimes): + smallest.append(m) + + #print(smallest) + return min(smallest) + +print(f"lambda(12) = {carmi_first_method(12)}") +print(f"lambda(19) = {carmi_first_method(19)}") +#print(f"lambda(28) = {carmi_first_method(28)}") +#print(f"lambda(32) = {carmi_first_method(32)}") +#print(f"lambda(33) = {carmi_first_method(33)}") +#print(f"lambda(35) = {carmi_first_method(35)}") +#print(f"lambda(36) = {carmi_first_method(36)}") +#print(f"lambda(135) = {carmi_first_method(135)}") +#print(f"lambda(1200) = {carmi_first_method(1200)}") diff --git a/examples/coprime.py b/examples/coprime.py new file mode 100644 index 0000000..09368ab --- /dev/null +++ b/examples/coprime.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.coprime import phi +from Cryptotools.lcm import lcm +from Cryptotools.Numbers.carmi import carmi_numbers, is_carmichael, carmichael_lambda + +print(f"phi(19) = {phi(19)}") + +print("Carmichael's number") +l = carmi_numbers(561) +print(is_carmichael(561, l)) + +# Test Carmichael lambda +print("Carmichael") +print(f"12 {carmichael_lambda(12)}") +print(f"19 {carmichael_lambda(19)}") +print(f"28 {carmichael_lambda(28)}") +print(f"32 {carmichael_lambda(32)}") +print(f"33 {carmichael_lambda(33)}") +print(f"35 {carmichael_lambda(35)}") +print(f"36 {carmichael_lambda(36)}") +print(f"135 {carmichael_lambda(135)}") +print(f"1200 {carmichael_lambda(1200)}") diff --git a/examples/cyclic.py b/examples/cyclic.py new file mode 100644 index 0000000..cb21afb --- /dev/null +++ b/examples/cyclic.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.cyclic import Cyclic +from Cryptotools.Numbers.primeNumber import isPrimeNumber +from random import choice + + +def operation(a, b, n): + return (a ** b) % n + +n = 19 +g = list() +for i in range(1, n): + g.append(i) + +print(f"n = {n}") +print(f"G = {g}") + +cyclic = Cyclic(g, n, operation) +order = len(g) # Length of the group +print(f"len: {order}") +print(f"prime: {isPrimeNumber(order)}") + +cyclic.generator() + +print(f"All generators: {cyclic.getGenerators()}") +print(f"Is cyclic: {cyclic.isCyclic()}") + +# Check if the abelian group is respected +print(f"It is an abelian group: {cyclic.closure()}\n") + + +e = choice(cyclic.getGenerators()) +z = list() +for a in range(1, n): + res = operation(e, a, n) + z.append(res) + +print(z) +if z == g: + print(f"The group generated with the generator {e} works") diff --git a/examples/cyclic_dlp.py b/examples/cyclic_dlp.py new file mode 100644 index 0000000..eecfdcf --- /dev/null +++ b/examples/cyclic_dlp.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +from Cryptotools.Groups.cyclic import Cyclic +from Cryptotools.Numbers.primeNumber import getPrimeNumber +from random import randint, choice +from math import log, log10, log2 + +""" + Here, we will try to understand why we need to have a generator when we encrypt data for Diffie-Hellman + https://crypto.stackexchange.com/questions/25489/why-does-diffie-hellman-need-be-a-cyclic-group +""" +def operation(a, b, n): + return (a ** b) % n + +def getGenerator(gr, p): + index = 1 + for g in range(2, p): + z = list() + for entry in range(1, p): + res = operation(g, index, p) + #if res not in z: + z.append(res) + index = index + 1 + print(f"{g}: {sorted(z)}") + +def generateGroupByG(gr, p, g): + index = 1 + z = list() + for entry in range(1, p): + res = operation(g, index, p) + #if res not in z: + z.append(res) + index = index + 1 + return z + +def computePublicKey(key, p, g): + return (g ** key) % p + + +gr = list() +# Public value +p = 5 +g = 0 +for i in range(1, p): + gr.append(i) + +print(f"p = {p}") +print(f"G = {gr}") +# We try with a generator which is not in list +cyclic = Cyclic(gr, p, operation) +generators = cyclic.getGenerators() +print(f"All generators: {generators}") + +# Generate group with g = 2 +grWithG = generateGroupByG(gr, p, 2) +print(f"Group generated with g = 2: {grWithG}") + +for a in grWithG: + # print(f"log2({a}) = {log(a, 2)}") # Same as below + print(f"log2({a}) = {log2(a)}") + +print() + +for a in range(1, len(grWithG) + 1): + print(f"log2({a}) = {log2(a)}") diff --git a/examples/cyclic_generator.py b/examples/cyclic_generator.py new file mode 100644 index 0000000..76775fd --- /dev/null +++ b/examples/cyclic_generator.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.cyclic import Cyclic +from random import choice + + +def operation(a, b, n): + return (a ** b) % n + +n = 19 +g = list() +for i in range(1, n): + g.append(i) + +print(f"n = {n}") +print(f"G = {g}") + +cyclic = Cyclic(g, n, operation) + +cyclic.generator() +generators = cyclic.getGenerators() +print(f"All generators: {generators}") + +e = choice(generators) +z = list() +for a in range(1, n): + res = operation(e, a, n) + z.append(res) + +z = sorted(z) +if z == g: + print(f"Working with the generator {e}") diff --git a/examples/cyclic_subgroup.py b/examples/cyclic_subgroup.py new file mode 100644 index 0000000..a101581 --- /dev/null +++ b/examples/cyclic_subgroup.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +from Cryptotools.Groups.cyclic import Cyclic +from Cryptotools.Numbers.primeNumber import getPrimeNumber +from random import randint, choice +from math import log, log10 + +""" + Here, we will try to understand why we need to have a generator when we encrypt data for Diffie-Hellman + https://crypto.stackexchange.com/questions/25489/why-does-diffie-hellman-need-be-a-cyclic-group +""" +def operation(a, b, n): + return (a ** b) % n + +def getGenerator(gr, p): + cyclic = Cyclic(gr, p, operation) + generators = cyclic.getGenerators() + print(f"All generators: {generators}") + cyclic.identity() + print(f"Identity: {cyclic.getIdentity()}") + + return generators + + +gr = list() +# Public value +p = 13 +g = 0 +for i in range(1, p): + gr.append(i) + +print(f"p = {p}") +print(f"G = {gr}") +# We try with a generator which is not in list +generators = getGenerator(gr, p) + +g = 3 # In the group +#g = # Not in the group +#print(f"g = {g}") + + + +# Try to compute the cyclic subgroup +for G in range(p + 1): + res = 0 + for a in range(G): + r = operation(G, a, p) + res = res + r + if res == p: + print(f"G = {G}; res = {res}") diff --git a/examples/cyclic_test.py b/examples/cyclic_test.py new file mode 100644 index 0000000..8351322 --- /dev/null +++ b/examples/cyclic_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.cyclic import Cyclic +from Cryptotools.Utils.utils import gcd +from Cryptotools.Numbers.primeNumber import isPrimeNumber +from random import choice + + +def operation(a, b, n): + return (a ** b) % n + +def test1(n): + """ + We test with n is not prime but the order of the group is a prime number + """ + g = list() + g2 = list() + for i in range(1, n): + #if gcd(i, n) == 1: + g.append(i) + + print(f"n = {n}") + print(f"G = {g}") + Gsorted = sorted(g) + + cyclic = Cyclic(g, n, operation) + order = len(g) # https://en.wikipedia.org/wiki/Order_(group_theory) + print(f"len: {order}") + print(f"prime: {isPrimeNumber(order)}") + g2 = list() + for i in range(1, order): + if gcd(i, order) == 1: + g2.append(i) + pass + + print(g2) + # Pick a number in the previous list and check if we can generate all number with it + item = choice(g2) + print() + + # if the order is prime, the group is cyclic ? + + # Check if we have all items + g3 = list() + for i in range(1, n): + res = operation(i, item, n) + if gcd(res, item) == 1: + g3.append(res) + #print(res) + pass + + G2sorted = sorted(g2) + G3sorted = sorted(g3) + print() + # print(f"{g} = {cyclic.generator(g)}") + gen = cyclic.generator() + + print(f"All generators: {cyclic.getGenerators()}") + print(f"Is cyclic: {cyclic.isCyclic()}") + + print() + print(G2sorted) + print(G3sorted) + if G3sorted == Gsorted: + print(f"Matching with item {item}") # Always match with item 1 + + # Check if the abelian group is respected + print(f"It is an abelian group: {cyclic.closure()}\n") + + + +test1(19) +test1(12) diff --git a/examples/dh_cyclic.py b/examples/dh_cyclic.py new file mode 100644 index 0000000..c587129 --- /dev/null +++ b/examples/dh_cyclic.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +from Cryptotools.Groups.cyclic import Cyclic +from Cryptotools.Numbers.primeNumber import getPrimeNumber +from random import randint, choice +from math import log, log10 + +""" + Here, we will try to understand why we need to have a generator when we encrypt data for Diffie-Hellman + https://crypto.stackexchange.com/questions/25489/why-does-diffie-hellman-need-be-a-cyclic-group +""" +def operation(a, b, n): + return (a ** b) % n + +def getGenerator(gr, p): + cyclic = Cyclic(gr, p, operation) + generators = cyclic.getGenerators() + print(f"All generators: {generators}") + + # Test with a no generator + item = 2 + while item in generators: # we loop until we found an item which is not a generators of the group + item = randint(1, p) + return item + +def computePublicKey(key, p, g): + return (g ** key) % p + +def computeEphemeralKey(public, secret, p): + return (public ** secret) % p + +gr = list() +# Public value +#p = getPrimeNumber(n = 8) +#p = 257 +p = 19 +g = 0 +for i in range(1, p): + gr.append(i) + +print(f"p = {p}") +print(f"G = {gr}") +# We try with a generator which is not in list +g = getGenerator(gr, p) +#g = [3, 5, 6, 7, 10, 12, 14, 19, 20, 24, 27, 28, 33, 37, 38, 39, 40, 41, 43, 45, 47, 48, 51, 53, 54, 55, 56, 63, 65, 66, 69, 71, 74, 75, 76, 77, 78, 80, 82, 83, 85, 86, 87, 90, 91, 93, 94, 96, 97, 101, 102, 103, 105, 106, 107, 108, 109, 110, 112, 115, 119, 125, 126, 127, 130, 131, 132, 138, 142, 145, 147, 148, 149, 150, 151, 152, 154, 155, 156, 160, 161, 163, 164, 166, 167, 170, 171, 172, 174, 175, 177, 179, 180, 181, 182, 183, 186, 188, 191, 192, 194, 201, 202, 203, 204, 206, 209, 210, 212, 214, 216, 217, 218, 219, 220, 224, 229, 230, 233, 237, 238, 243, 245, 247, 250, 251, 252, 254] +g = 29 # Not in the group +print(f"g = {g}") + +# We can compute with the secret key +secretKeyA = 5 +secretKeyB = 10 +publicKeyA = computePublicKey(secretKeyA, p, g) +publicKeyB = computePublicKey(secretKeyB, p, g) +print(f"Public key A: {publicKeyA}") +print(f"Public key B: {publicKeyB}") + +# Eve sniff the traffic and knows p, g and publicKeyA and B + + +# Generator need to be use, because that avoid to Eve to try to find a secret key of Alice or Bob ??? +# Utiliser un generateur qui genere la tout le groupe, va permettre d'éviter à Eve de trouver la secret key de Alice ou Bob ???? +# https://eitca.org/cybersecurity/eitc-is-acc-advanced-classical-cryptography/diffie-hellman-cryptosystem/diffie-hellman-key-exchange-and-the-discrete-log-problem/examination-review-diffie-hellman-key-exchange-and-the-discrete-log-problem/what-are-the-roles-of-the-prime-number-p-and-the-generator-alpha-in-the-diffie-hellman-key-exchange-process/ +# https://www.perplexity.ai/search/why-generator-in-cyclic-group-QRYR6.rxSI218hs_x5CvnQ#0 + +# They exchange their public keys +ephemeralKeyA = computeEphemeralKey(publicKeyB, secretKeyA, p) +ephemeralKeyB = computeEphemeralKey(publicKeyA, secretKeyB, p) +print(f"Ephemeral key A: {ephemeralKeyA}") +print(f"Ephemeral key B: {ephemeralKeyB}") + +# Test log10 +#for i in range(1, 1000): +# r = log10(i) +# if isinstance(r, int): +# print(f"{i} = {r}") diff --git a/examples/dh_no_generator.py b/examples/dh_no_generator.py new file mode 100644 index 0000000..8649f89 --- /dev/null +++ b/examples/dh_no_generator.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +from Cryptotools.Groups.cyclic import Cyclic +from Cryptotools.Numbers.primeNumber import getPrimeNumber +from random import randint, choice +from math import log, log10 + +""" + Here, we will try to understand why we need to have a generator when we encrypt data for Diffie-Hellman + https://crypto.stackexchange.com/questions/25489/why-does-diffie-hellman-need-be-a-cyclic-group +""" +def operation(a, b, n): + return (a ** b) % n + +def getGenerator(gr, p): + index = 1 + for g in range(2, p): + z = list() + for entry in range(1, p): + res = operation(g, index, p) + #if res not in z: + z.append(res) + index = index + 1 + print(f"{g}: {sorted(z)}") + +def getGenerator2(gr, p, g): + index = 1 + z = list() + for entry in range(1, p): + res = operation(g, index, p) + if res not in z: + z.append(res) + index = index + 1 + return z + + +def computePublicKey(key, p, g): + return (g ** key) % p + +def computeEphemeralKey(public, secret, p): + return (public ** secret) % p + +gr = list() +# Public value +p = 43 +g = 0 +for i in range(1, p): + gr.append(i) + +print(f"p = {p}") +print(f"G = {gr}") +# We try with a generator which is not in list +cyclic = Cyclic(gr, p, operation) +generators = cyclic.getGenerators() +print(f"All generators: {generators}") + +g = 3 # In the group +print(f"g = {g}") + +# We can compute with the secret key +secretKeyA = 5 +secretKeyB = 10 +publicKeyA = computePublicKey(secretKeyA, p, g) +publicKeyB = computePublicKey(secretKeyB, p, g) +print(f"Public key A: {publicKeyA}") +print(f"Public key B: {publicKeyB}\n") + +# Here, g is in the generator, we need to compute all values until that match with publicKeyA +for a in range(1, p): + res = operation(g, a, p) + if res == publicKeyA: + print(f"Brute forced secret key of A: {a}") + +for a in range(1, p): + res = operation(g, a, p) + if res == publicKeyB: + print(f"Brute forced secret key of B: {a}") + +print() + +#for a in generators: +# print(a) +# +#print() + +# Here, we generate all key withthe same generator +keys = list() +for a in range(1, p): + keys.append(operation(g, a, p)) + +print((keys)) +print(sorted(keys)) + +print() + +print(f"Keys with g = 4: {sorted(getGenerator2(gr, p, 4))}") + +# Do the same, but g is not a generator +g = 4 # Not in the generator group +publicKeyA = computePublicKey(secretKeyA, p, g) +publicKeyB = computePublicKey(secretKeyB, p, g) +print(f"Public key A: {publicKeyA}") +print(f"Public key B: {publicKeyB}") + +keys = list() +for a in range(1, p): + keys.append(operation(g, a, p)) +print(keys) + +# Eve sniff the traffic and knows p, g and publicKeyA and B +# Eve, knows p and g, because it's public +# Eve need to guess the secretKey of A. +# For doing that, we iterate all posibility until that match with the publicKeyA + +#for a in range(1, 4): +# res = operation(g, a, p) +# print(res) diff --git a/examples/fermat_factorization.py b/examples/fermat_factorization.py new file mode 100644 index 0000000..02dd2d1 --- /dev/null +++ b/examples/fermat_factorization.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +import unittest +from Cryptotools.Numbers.primeNumber import isPrimeNumber, _millerRabinTest, getPrimeNumber +from math import sqrt, isqrt, ceil +from sympy import sqrt as sq + +def get_closest_prime(p): + q = p + 1 + l = list() + while True: + if _millerRabinTest(q): + l.append(q) + if len(l) == 20: + break + q += 1 + return l + +def test(): + while True: + p = getPrimeNumber(64) + b = sqrt(p) + if b % 2 == 0.0: + break + print(p) + +#test() +#exit(1) +p = 7901 +q = 7817 +#p = getPrimeNumber(64) +#q = get_closest_prime(p)[1] +p = 7943202761666983 # Works +q = 7943202761667119 +#p = 314159200000000028138418196395985880850000485810513 +#q = 314159200000000028138415196395985880850000485810479 +print(f"p = {p}") +print(f"q = {q}") +n = p * q +print(f"n = {n}") + +a = ceil(sqrt(n)) +#print(a) +#print(sqrt(n)) +#print(sqrt(n) < n) # True +#print(a * a) +#print(a * a < n) +#print() +#a = isqrt(n) + 1 +iteration = 0 +while True: + # b2 = (a ** 2) - n + iteration += 1 + b2 = pow(a, 2) - n + #b2 = ceil(a*a) - n + # print(b2) + sqb2 = ceil(sqrt(b2)) + # print(b2, isqrt(pow(b2, 2))) + if b2 % 2 == 0.0: + #if isqrt(pow(b2, 2)) == b2: + b = isqrt(b2) + break + a = a + 1 + +print(f"Iteration: {iteration}") +print(f"a = {a}") +print(f"b2 = {b2}") +print(f"b = {b}") +p = int((a + b)) +q = int((a - b)) +print(p) +print(q) +#N = (a + b) * (a - b) +N = p * q +print(_millerRabinTest(p)) +print(_millerRabinTest(q)) +print(f"N = {N}") +print(n == N) +print() + diff --git a/examples/fibonacci.py b/examples/fibonacci.py new file mode 100644 index 0000000..1730e5e --- /dev/null +++ b/examples/fibonacci.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.numbers import fibonacci + +print(fibonacci(40)) diff --git a/examples/galois.py b/examples/galois.py new file mode 100644 index 0000000..2b1a018 --- /dev/null +++ b/examples/galois.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.galois import Galois +import matplotlib.pyplot as plt # pip install numpy==1.26.4 + + +def operation(a, b, n): + return (a ** b) % n + +q = 13 +g = Galois(q, operation) +add = g.add() +div = g.div() +mul = g.mul() +sub = g.sub() +g.check_closure_law('+') +g.check_closure_law('*') +g.check_closure_law('-') +g.check_closure_law('/') + +print("Addition") +g.printMatrice(add) +print("Division") +g.printMatrice(div) +print("Multiplication") +g.printMatrice(mul) +print("Substraction") +g.printMatrice(sub) + + +print("Primitives root") +print(g.primitiveRoot(), end="\n\n") + +#print("Identity elements") +g.check_identity_add() +g.check_identity_mul() diff --git a/examples/group.py b/examples/group.py new file mode 100644 index 0000000..1931d3c --- /dev/null +++ b/examples/group.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +from Cryptotools.Groups.group import Group +from Cryptotools.Utils.utils import gcd +import matplotlib.pyplot as plt + + +n = 18 +def operation(e1, e2, n) -> int: + return e1 * e2 % n + +# Generate the group G +g = list() +for i in range(1, n): + #if gcd(i, n) == 1: + g.append(i) + +G = Group(n = n, g = g, ope=operation) +print(f"n = {n}") +print(f"G = {G.getG()}") + +if G.closure() is False: + print("The closure law is not respected. It's not an abelian (commutative) group") +else: + print("It is a closure group") + +if G.associative() is False: + print("The associative law is not respected. It's not a group") +else: + print("It is an associative group") + +if G.identity() is False: + print("The group hasn't an identity element") +else: + print(f"Identity: {G.getIdentity()}") + +if G.reverse() is False: + print(f"The group hasn't a reverse") +else: + print(f"Reverses: {G.getReverses()}") + +#reverses = G.getReverses() +#plt.rcParams["figure.figsize"] = [7.00, 3.50] +#plt.rcParams["figure.autolayout"] = True +#for key, value in reverses.items(): +# x = key +# y = value +# plt.plot(x, y, marker="o", markersize=2, markeredgecolor="blue") +# #plt.Circle((0, n), 0.2, color='r') +# +#plt.xlim(0, n) +#plt.ylim(0, n) +#plt.grid() +#plt.show() + diff --git a/examples/main.py b/examples/main.py new file mode 100644 index 0000000..2bdc08d --- /dev/null +++ b/examples/main.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from utils.primeNumber import primeNumber + +print(primeNumber(19)) +print(primeNumber(20)) diff --git a/examples/plotlib.py b/examples/plotlib.py new file mode 100644 index 0000000..da545c5 --- /dev/null +++ b/examples/plotlib.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import get_n_prime_numbers +import matplotlib.pyplot as plt +#import numpy as np + +# Get list of n prime numbers +n = 100 +primes = get_n_prime_numbers(n) +print(primes) + +# With matplotlib, show into a graphics and try to explain that +plt.rcParams["figure.figsize"] = [7.00, 3.50] +plt.rcParams["figure.autolayout"] = True +for i in range(0, n): + x = primes[i] + y = primes[i] + plt.plot(x, y, marker="o", markersize=2, markeredgecolor="blue") + + +plt.xlim(0, n) +plt.ylim(0, n) +plt.grid() +plt.show() + diff --git a/examples/pollard.py b/examples/pollard.py new file mode 100644 index 0000000..39c0e5f --- /dev/null +++ b/examples/pollard.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.factorization import pollard_p_minus_1 +from Cryptotools.Numbers.primeNumber import _millerRabinTest, getPrimeNumber +from sympy.ntheory.factor_ import pollard_pm1 +from sympy import factorint + + + +def pollard(n): + p = pollard_p_minus_1(n, B=10) + if p is not None: + print(p, n / p) + print(pollard_pm1(n, B=10)) + print() + return True + return False + +def safePrime(p): + nP = 2 * p + 1 + return _millerRabinTest(nP) + +#pollard(299) +#pollard(257 * 1009) +#pollard(1684) # Doesn't works + +# Test for RSA +#p = 281 +while True: + while True: + p = getPrimeNumber(64) + if ((p - 1) / 2520) % 2 == 0.0: + #print(p) + break + #p = 322506219347091343 + #q = 13953581789873249851 + # n = p * q + while True: + q = getPrimeNumber(64) + if int(((q - 1) / 2520) % 2) == 1: + #print(q) + break + + #q = 223 + n = p * q + ##print(n) + #print(pollard(n)) + #break + if pollard(n): # We can factorize n + break + +print(safePrime(p)) +#print(safePrime(q)) diff --git a/examples/prime.py b/examples/prime.py new file mode 100644 index 0000000..a36e613 --- /dev/null +++ b/examples/prime.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import getPrimeNumber, sophieGermainPrime + +p = getPrimeNumber(512) +print(p) +if sophieGermainPrime(p): + print("It's a safe prime") diff --git a/examples/pseudoprimes.py b/examples/pseudoprimes.py new file mode 100644 index 0000000..e4b44a8 --- /dev/null +++ b/examples/pseudoprimes.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import _millerRabinTest, _fermatLittleTheorem +from Cryptotools.Numbers.carmi import carmi_numbers, is_carmi_number, generate_carmi_numbers + +print(_fermatLittleTheorem(1729)) +print(_fermatLittleTheorem(1105)) +print(_fermatLittleTheorem(6601)) +print(_millerRabinTest(1729)) + +print(f"1729: {is_carmi_number(1729)}") +print(f"1105: {is_carmi_number(1105)}") +print(f"110: {is_carmi_number(110)}") +print(f"2465: {is_carmi_number(2465)}") +print(f"6601: {is_carmi_number(6601)}") +#print(carmi_numbers(1729)) +print(generate_carmi_numbers(10)) diff --git a/examples/rsa.py b/examples/rsa.py new file mode 100644 index 0000000..2f9e5e2 --- /dev/null +++ b/examples/rsa.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +from Cryptotools.Encryptions.RSA import RSA + + +rsa = RSA() +rsa.generateKeys(size=512) + +e = rsa.e +d = rsa.d +n = rsa.n + +s = "I am encrypted with RSA" +print(f"plaintext: {s}") +encrypted = rsa.encrypt(s) + +# Encrypt data +# print(f"ciphertext: {encrypted}") + +# We decrypt +plaintext = rsa.decrypt(encrypted) +print(f"Plaintext: {plaintext}") diff --git a/examples/rsa_problem.py b/examples/rsa_problem.py new file mode 100644 index 0000000..f4dbb08 --- /dev/null +++ b/examples/rsa_problem.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.coprime import phi +from Cryptotools.Utils.utils import gcd + +def generate_keys(): + p = 7853 + q = 7919 + n = p * q + e = 65536 # It's public value, must be coprime with phi n + + print(n) + #phin = phi(n) + phin = (p - 1) * (q - 1) + print(phin) + + for _ in range(2, phin): + if gcd(phin, e) == 1: + break + e += 1 + + print(e) + plaintext = 'A' + ciphertext = pow(ord(plaintext), e, n) + print(f"Ciphertext: {ciphertext}") + +# Now, we can test + +# To resolve that formula: C = x ** e mod n +# Where C is the ciphertext, and C, e and n are known (public values) +# First, we need to find the reverse modular of phi(n) or carmi(n) +# z = e -1 mod phi(n) +# After that, we have our decryption key, we can resolve x +# x = C ** z mod n +# The RSA Problem is to decrypt with the public-key +# We just need to find the decryption key with the public-key and the modulus + +# First, we need to find phi(n) +# phin = phi(n) # I computed here, result = 62172136 + +n = 62187907 +phin = 62172136 +e = 65537 +ciphertext = 38605768 +# print(phin) + +# Find the reverse modular +d = pow(e, -1, phin) +# print(d) +plaintext = pow(ciphertext, d, n) +print(chr(plaintext)) + diff --git a/examples/sieveOfEratosthenes.py b/examples/sieveOfEratosthenes.py new file mode 100644 index 0000000..e278ab7 --- /dev/null +++ b/examples/sieveOfEratosthenes.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import sieveOfEratosthenes + +print(sieveOfEratosthenes(100)) +print() diff --git a/examples/test_gcd.py b/examples/test_gcd.py new file mode 100644 index 0000000..7ccd4d5 --- /dev/null +++ b/examples/test_gcd.py @@ -0,0 +1,15 @@ + +from Cryptotools.Utils.utils import gcd + + +for i in range(1, 30): + if 30 % i == 0: + print(i) + +print() + +for i in range(1, 40): + if 40 % i == 0: + print(i) + +print(gcd(30, 40)) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..04cebae --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,17 @@ +site_name: CryptoTools documentation +theme: + name: "readthedocs" + +plugins: + - mkdocstrings + +nav: + - Introduction: introduction.md + - Installation: installation.md + - Low-level cryptographic: + - Number theory: number-theory.md + - Group theory: group-theory.md + - Public Keys: + - RSA: rsa.md + - Examples: + - Generating RSA Keys: examples-rsa-keys.md diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..747f68a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "Cryptotools" +version = "0.1.0" +description = "" +authors = [ + {name = "gbucchino",email = "gbucchino@fortinet-us.com"} +] +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ +] + + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..79cdabf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +sympy +mkdocs +mkdocstrings[python] +mkdocs-readthedocs +mkdocs-material diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..37daa98 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +from setuptools import setup, find_packages + + +_version = "1.0" +_project_name = "Cryptotools" + +_packages = [ + "Numbers", + "Groups", + "Utils", +] + +setup( + name=_project_name, + version=_version, + description="Cryptography library", + author="Geoffrey Bucchino", + author_email="contact@bucchino.org", + packages=find_packages(), + #packages=_packages, + #package_dir={"": "Cryptotools"} +) diff --git a/sieves_base.py b/sieves_base.py new file mode 100644 index 0000000..db5cdf0 --- /dev/null +++ b/sieves_base.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +from Cryptotools.Numbers.primeNumber import isPrimeNumber + +def get_bases(n): + # Generate the n first prime numbers + bases = list() + i = 2 + while (len(bases) < n): + if isPrimeNumber(i): + bases.append(i) + i = i + 1 + + return bases + +bases = get_bases(1000) + + + +print("(") +index = 0 +for i in range(0, len(bases)): + if index == 10: + index = 0 + print(f"\n", end="") + else: + print(f"{bases[i]}", end=",") + index = index + 1 +print(")") diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..7a70374 --- /dev/null +++ b/site/404.html @@ -0,0 +1,122 @@ + + + + + + + + CryptoTools documentation + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • +
  • +
  • +
+
+
+
+
+ + +

404

+ +

Page not found

+ + +
+
+ +
+
+ +
+ +
+ +
+ + + + + +
+ + + + + + + + diff --git a/site/assets/_mkdocstrings.css b/site/assets/_mkdocstrings.css new file mode 100644 index 0000000..8a5d3cf --- /dev/null +++ b/site/assets/_mkdocstrings.css @@ -0,0 +1,57 @@ + +/* Avoid breaking parameters name, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* No line break before first paragraph of descriptions. */ +.doc-md-description, +.doc-md-description>p:first-child { + display: inline; +} + +/* Avoid breaking code headings. */ +.doc-heading code { + white-space: normal; +} + +/* Improve rendering of parameters, returns and exceptions. */ +.doc-contents .field-name { + min-width: 100px; +} + +/* Other curious-spacing fixes. */ +.doc-contents .field-name, +.doc-contents .field-body { + border: none !important; + padding: 0 !important; +} + +.doc-contents p { + margin: 1em 0 1em; +} + +.doc-contents .field-list { + margin: 0 !important; +} + +.doc-contents pre { + padding: 0 !important; +} + +.doc-contents .wy-table-responsive { + margin-bottom: 0 !important; +} + +.doc-contents td.code { + padding: 0 !important; +} + +.doc-contents td.linenos { + padding: 0 8px !important; +} + +.doc-children, +footer { + margin-top: 20px; +} \ No newline at end of file diff --git a/site/css/fonts/Roboto-Slab-Bold.woff b/site/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/site/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/site/css/fonts/Roboto-Slab-Bold.woff2 b/site/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/site/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/site/css/fonts/Roboto-Slab-Regular.woff b/site/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/site/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/site/css/fonts/Roboto-Slab-Regular.woff2 b/site/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/site/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/site/css/fonts/fontawesome-webfont.eot b/site/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/site/css/fonts/fontawesome-webfont.eot differ diff --git a/site/css/fonts/fontawesome-webfont.svg b/site/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/site/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/css/fonts/fontawesome-webfont.ttf b/site/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/site/css/fonts/fontawesome-webfont.ttf differ diff --git a/site/css/fonts/fontawesome-webfont.woff b/site/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/site/css/fonts/fontawesome-webfont.woff differ diff --git a/site/css/fonts/fontawesome-webfont.woff2 b/site/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/site/css/fonts/fontawesome-webfont.woff2 differ diff --git a/site/css/fonts/lato-bold-italic.woff b/site/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/site/css/fonts/lato-bold-italic.woff differ diff --git a/site/css/fonts/lato-bold-italic.woff2 b/site/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/site/css/fonts/lato-bold-italic.woff2 differ diff --git a/site/css/fonts/lato-bold.woff b/site/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/site/css/fonts/lato-bold.woff differ diff --git a/site/css/fonts/lato-bold.woff2 b/site/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/site/css/fonts/lato-bold.woff2 differ diff --git a/site/css/fonts/lato-normal-italic.woff b/site/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/site/css/fonts/lato-normal-italic.woff differ diff --git a/site/css/fonts/lato-normal-italic.woff2 b/site/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/site/css/fonts/lato-normal-italic.woff2 differ diff --git a/site/css/fonts/lato-normal.woff b/site/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/site/css/fonts/lato-normal.woff differ diff --git a/site/css/fonts/lato-normal.woff2 b/site/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/site/css/fonts/lato-normal.woff2 differ diff --git a/site/css/theme.css b/site/css/theme.css new file mode 100644 index 0000000..ad77300 --- /dev/null +++ b/site/css/theme.css @@ -0,0 +1,13 @@ +/* + * This file is copied from the upstream ReadTheDocs Sphinx + * theme. To aid upgradability this file should *not* be edited. + * modifications we need should be included in theme_extra.css. + * + * https://github.com/readthedocs/sphinx_rtd_theme + */ + + /* sphinx_rtd_theme version 1.2.0 | MIT license */ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} diff --git a/site/css/theme_extra.css b/site/css/theme_extra.css new file mode 100644 index 0000000..ab0631a --- /dev/null +++ b/site/css/theme_extra.css @@ -0,0 +1,197 @@ +/* + * Wrap inline code samples otherwise they shoot of the side and + * can't be read at all. + * + * https://github.com/mkdocs/mkdocs/issues/313 + * https://github.com/mkdocs/mkdocs/issues/233 + * https://github.com/mkdocs/mkdocs/issues/834 + */ +.rst-content code { + white-space: pre-wrap; + word-wrap: break-word; + padding: 2px 5px; +} + +/** + * Make code blocks display as blocks and give them the appropriate + * font size and padding. + * + * https://github.com/mkdocs/mkdocs/issues/855 + * https://github.com/mkdocs/mkdocs/issues/834 + * https://github.com/mkdocs/mkdocs/issues/233 + */ +.rst-content pre code { + white-space: pre; + word-wrap: normal; + display: block; + padding: 12px; + font-size: 12px; +} + +/** + * Fix code colors + * + * https://github.com/mkdocs/mkdocs/issues/2027 + */ +.rst-content code { + color: #E74C3C; +} + +.rst-content pre code { + color: #000; + background: #f8f8f8; +} + +/* + * Fix link colors when the link text is inline code. + * + * https://github.com/mkdocs/mkdocs/issues/718 + */ +a code { + color: #2980B9; +} +a:hover code { + color: #3091d1; +} +a:visited code { + color: #9B59B6; +} + +/* + * The CSS classes from highlight.js seem to clash with the + * ReadTheDocs theme causing some code to be incorrectly made + * bold and italic. + * + * https://github.com/mkdocs/mkdocs/issues/411 + */ +pre .cs, pre .c { + font-weight: inherit; + font-style: inherit; +} + +/* + * Fix some issues with the theme and non-highlighted code + * samples. Without and highlighting styles attached the + * formatting is broken. + * + * https://github.com/mkdocs/mkdocs/issues/319 + */ +.rst-content .no-highlight { + display: block; + padding: 0.5em; + color: #333; +} + + +/* + * Additions specific to the search functionality provided by MkDocs + */ + +.search-results { + margin-top: 23px; +} + +.search-results article { + border-top: 1px solid #E1E4E5; + padding-top: 24px; +} + +.search-results article:first-child { + border-top: none; +} + +form .search-query { + width: 100%; + border-radius: 50px; + padding: 6px 12px; + border-color: #D1D4D5; +} + +/* + * Improve inline code blocks within admonitions. + * + * https://github.com/mkdocs/mkdocs/issues/656 + */ + .rst-content .admonition code { + color: #404040; + border: 1px solid #c7c9cb; + border: 1px solid rgba(0, 0, 0, 0.2); + background: #f8fbfd; + background: rgba(255, 255, 255, 0.7); +} + +/* + * Account for wide tables which go off the side. + * Override borders to avoid weirdness on narrow tables. + * + * https://github.com/mkdocs/mkdocs/issues/834 + * https://github.com/mkdocs/mkdocs/pull/1034 + */ +.rst-content .section .docutils { + width: 100%; + overflow: auto; + display: block; + border: none; +} + +td, th { + border: 1px solid #e1e4e5 !important; + border-collapse: collapse; +} + +/* + * Without the following amendments, the navigation in the theme will be + * slightly cut off. This is due to the fact that the .wy-nav-side has a + * padding-bottom of 2em, which must not necessarily align with the font-size of + * 90 % on the .rst-current-version container, combined with the padding of 12px + * above and below. These amendments fix this in two steps: First, make sure the + * .rst-current-version container has a fixed height of 40px, achieved using + * line-height, and then applying a padding-bottom of 40px to this container. In + * a second step, the items within that container are re-aligned using flexbox. + * + * https://github.com/mkdocs/mkdocs/issues/2012 + */ + .wy-nav-side { + padding-bottom: 40px; +} + +/* For section-index only */ +.wy-menu-vertical .current-section p { + background-color: #e3e3e3; + color: #404040; +} + +/* + * The second step of above amendment: Here we make sure the items are aligned + * correctly within the .rst-current-version container. Using flexbox, we + * achieve it in such a way that it will look like the following: + * + * [No repo_name] + * Next >> // On the first page + * << Previous Next >> // On all subsequent pages + * + * [With repo_name] + * Next >> // On the first page + * << Previous Next >> // On all subsequent pages + * + * https://github.com/mkdocs/mkdocs/issues/2012 + */ +.rst-versions .rst-current-version { + padding: 0 12px; + display: flex; + font-size: initial; + justify-content: space-between; + align-items: center; + line-height: 40px; +} + +/* + * Please note that this amendment also involves removing certain inline-styles + * from the file ./mkdocs/themes/readthedocs/versions.html. + * + * https://github.com/mkdocs/mkdocs/issues/2012 + */ +.rst-current-version span { + flex: 1; + text-align: center; +} diff --git a/site/examples-rsa-keys/index.html b/site/examples-rsa-keys/index.html new file mode 100644 index 0000000..af1636d --- /dev/null +++ b/site/examples-rsa-keys/index.html @@ -0,0 +1,158 @@ + + + + + + + + Generating RSA Keys - CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Generating RSA Keys

+

In the following section, we are going to see how to generate RSA Keys and how to encrypt data with these keys.

+
#!/usr/bin/env python3
+
+from Cryptotools.Encryptions.RSA import RSA
+
+
+rsa = RSA()
+rsa.generateKeys(size=512)
+
+e = rsa.e
+d = rsa.d
+n = rsa.n
+
+s = "I am encrypted with RSA"
+print(f"plaintext: {s}")
+encrypted = rsa.encrypt(s)
+
+# Encrypt data
+print(f"ciphertext: {encrypted}")
+
+# We decrypt
+plaintext = rsa.decrypt(encrypted)
+print(f"Plaintext: {plaintext}")
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + « Previous + + + +
+ + + + + + + + diff --git a/site/group-theory/index.html b/site/group-theory/index.html new file mode 100644 index 0000000..98bd03c --- /dev/null +++ b/site/group-theory/index.html @@ -0,0 +1,3059 @@ + + + + + + + + Group theory - CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Group Theory

+

Group

+ + +
+ + + + +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + +

+ Group + + +

+ + +
+ + +

This class generate a group self._g based on the operation (here denoted +) +with the function ope: (a ** b) % n

+

In group theory, any group has an identity element (e), which with the binary operation, do not change the value and must satisfy the condition: $a + e = 0$

+ + + + + + + + + + + + + +
Attributes: +
    +
  • + n + (Integer) + – +
    +

    It's the G_n elements in the Group

    +
    +
  • +
  • + g + (List) + – +
    +

    The set of the Group

    +
    +
  • +
  • + operation + (Function) + – +
    +

    Function for generating the Group

    +
    +
  • +
  • + identity + (Integer) + – +
    +

    The identity of the group.

    +
    +
  • +
  • + reverse + (Dict) + – +
    +

    For each elements of the Group of n, we have the reverse value

    +
    +
  • +
+
+ + + + + + + +
+ Source code in Cryptotools/Groups/group.py +
  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
class Group:
+    """
+        This class generate a group self._g based on the operation (here denoted +)
+        with the function ope: (a ** b) % n
+
+        In group theory, any group has an identity element (e), which with the binary operation, do not change the value and must satisfy the condition: $a + e = 0$
+
+        Attributes:
+            n (Integer): It's the G_n elements in the Group
+            g (List): The set of the Group
+            operation (Function): Function for generating the Group
+            identity (Integer): The identity of the group.
+            reverse (Dict): For each elements of the Group of n, we have the reverse value
+    """
+    def __init__(self, n, g, ope):
+        self._n = n
+        self._g = g
+        self._operation = ope
+        self._identity = 0
+        self._reverse = dict()
+
+    def getG(self) -> list():
+        """
+            This function return the Group
+
+            Returns:
+                List of all elements in the Group
+        """
+        return self._g
+
+    def closure(self) -> bool:
+        """
+            Check the closure law
+            In a group, each element a, b belongs to G, such as a + b belongs to G
+
+            Returns:
+                Return a Boolean if the closure law is respected. True if yes otherwise it's False
+        """
+        for e1 in self._g:
+            for e2 in self._g:
+                res = self._operation(e1, e2, self._n)
+                if  not res in self._g:
+                    # raise Exception(f"{res} not in g. g is not a group")
+                    return False
+        return True 
+
+    def associative(self) -> bool:
+        """
+            Check the associative law. 
+            In a group, for any a, b and c belongs to G,
+            they must respect this condition: (a + b) + c = a + (a + b)
+
+            Returns:
+                Return a boolean if the Associative law is respected. True if yes otherwise it's False
+        """
+        a = self._g[0]
+        b = self._g[1]
+        c = self._g[2]
+
+        res_ope = self._operation(a, b, self._n)
+        res1 = self._operation(res_ope, c, self._n)
+
+        res_ope = self._operation(b, c, self._n)
+        res2 = self._operation(a, res_ope, self._n)
+        if res1 != res2:
+            # raise Exception(f"{res1} is different from {res2}. g is not a group")
+            return False
+        return True
+
+    def identity(self) -> bool:
+        """
+            Check the identity law.
+            In a group, an identity element exist and must be uniq
+
+            Returns:
+                Return a Boolean if the identity elements has been found. True if found otherwise it's False
+        """
+        for a in self._g:
+            for b in self._g:
+                if not self._operation(a, b, self._n) == b:
+                    break
+
+                self._identity = a
+                return True
+        return False
+
+    def getIdentity(self) -> int:
+        """
+            Return the identity element. The function identitu() must be called before.
+
+            Returns:
+                Return the identity element if it has been found
+        """
+        return self._identity
+
+    def reverse(self) -> bool:
+        """
+            Check the inverse law
+            In a group, for each element belongs to G
+            they must have an inverse a ^ (-1) = e (identity)
+
+            Returns:
+                Return a Boolean if the all elements ha a reverse element. True if yes otherwise it's False
+        """
+        reverse = False
+        for a in self._g:
+            for b in self._g:
+                if self._operation(a, b, self._n) == self._identity:
+                    self._reverse[a] = b
+                    reverse = True
+                    break
+        return reverse        
+
+    def getReverses(self) -> dict:
+        """
+            This function return the dictionary of all reverses elements. The key is the element in G and the value is the reverse element
+
+            Returns:
+                Return the reverse dictionary
+
+        """
+        return self._reverse
+
+
+ + + +
+ + + + + + + + + + +
+ + +

+ associative() + +

+ + +
+ +

Check the associative law. +In a group, for any a, b and c belongs to G, +they must respect this condition: (a + b) + c = a + (a + b)

+ + + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    Return a boolean if the Associative law is respected. True if yes otherwise it's False

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
def associative(self) -> bool:
+    """
+        Check the associative law. 
+        In a group, for any a, b and c belongs to G,
+        they must respect this condition: (a + b) + c = a + (a + b)
+
+        Returns:
+            Return a boolean if the Associative law is respected. True if yes otherwise it's False
+    """
+    a = self._g[0]
+    b = self._g[1]
+    c = self._g[2]
+
+    res_ope = self._operation(a, b, self._n)
+    res1 = self._operation(res_ope, c, self._n)
+
+    res_ope = self._operation(b, c, self._n)
+    res2 = self._operation(a, res_ope, self._n)
+    if res1 != res2:
+        # raise Exception(f"{res1} is different from {res2}. g is not a group")
+        return False
+    return True
+
+
+
+ +
+ +
+ + +

+ closure() + +

+ + +
+ +

Check the closure law +In a group, each element a, b belongs to G, such as a + b belongs to G

+ + + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    Return a Boolean if the closure law is respected. True if yes otherwise it's False

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
def closure(self) -> bool:
+    """
+        Check the closure law
+        In a group, each element a, b belongs to G, such as a + b belongs to G
+
+        Returns:
+            Return a Boolean if the closure law is respected. True if yes otherwise it's False
+    """
+    for e1 in self._g:
+        for e2 in self._g:
+            res = self._operation(e1, e2, self._n)
+            if  not res in self._g:
+                # raise Exception(f"{res} not in g. g is not a group")
+                return False
+    return True 
+
+
+
+ +
+ +
+ + +

+ getG() + +

+ + +
+ +

This function return the Group

+ + + + + + + + + + + + + +
Returns: +
    +
  • + list() + – +
    +

    List of all elements in the Group

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
24
+25
+26
+27
+28
+29
+30
+31
def getG(self) -> list():
+    """
+        This function return the Group
+
+        Returns:
+            List of all elements in the Group
+    """
+    return self._g
+
+
+
+ +
+ +
+ + +

+ getIdentity() + +

+ + +
+ +

Return the identity element. The function identitu() must be called before.

+ + + + + + + + + + + + + +
Returns: +
    +
  • + int + – +
    +

    Return the identity element if it has been found

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
89
+90
+91
+92
+93
+94
+95
+96
def getIdentity(self) -> int:
+    """
+        Return the identity element. The function identitu() must be called before.
+
+        Returns:
+            Return the identity element if it has been found
+    """
+    return self._identity
+
+
+
+ +
+ +
+ + +

+ getReverses() + +

+ + +
+ +

This function return the dictionary of all reverses elements. The key is the element in G and the value is the reverse element

+ + + + + + + + + + + + + +
Returns: +
    +
  • + dict + – +
    +

    Return the reverse dictionary

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
116
+117
+118
+119
+120
+121
+122
+123
+124
def getReverses(self) -> dict:
+    """
+        This function return the dictionary of all reverses elements. The key is the element in G and the value is the reverse element
+
+        Returns:
+            Return the reverse dictionary
+
+    """
+    return self._reverse
+
+
+
+ +
+ +
+ + +

+ identity() + +

+ + +
+ +

Check the identity law. +In a group, an identity element exist and must be uniq

+ + + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    Return a Boolean if the identity elements has been found. True if found otherwise it's False

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
def identity(self) -> bool:
+    """
+        Check the identity law.
+        In a group, an identity element exist and must be uniq
+
+        Returns:
+            Return a Boolean if the identity elements has been found. True if found otherwise it's False
+    """
+    for a in self._g:
+        for b in self._g:
+            if not self._operation(a, b, self._n) == b:
+                break
+
+            self._identity = a
+            return True
+    return False
+
+
+
+ +
+ +
+ + +

+ reverse() + +

+ + +
+ +

Check the inverse law +In a group, for each element belongs to G +they must have an inverse a ^ (-1) = e (identity)

+ + + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    Return a Boolean if the all elements ha a reverse element. True if yes otherwise it's False

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/group.py +
 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
def reverse(self) -> bool:
+    """
+        Check the inverse law
+        In a group, for each element belongs to G
+        they must have an inverse a ^ (-1) = e (identity)
+
+        Returns:
+            Return a Boolean if the all elements ha a reverse element. True if yes otherwise it's False
+    """
+    reverse = False
+    for a in self._g:
+        for b in self._g:
+            if self._operation(a, b, self._n) == self._identity:
+                self._reverse[a] = b
+                reverse = True
+                break
+    return reverse        
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +

Cyclic group

+ + +
+ + + + +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + +

+ Cyclic + + +

+ + +
+

+ Bases: Group

+ + +

This object contain a list of the Group for a cyclic group. This class find all generator of the group. +This class is inherited from Group object

+ + + + + + + + + + + + + +
Attributes: +
    +
  • + G + (list) + – +
    +

    list of all elements in the group

    +
    +
  • +
  • + n + (Integer) + – +
    +

    it's the value where the group has been generated

    +
    +
  • +
  • + operation + (Function) + – +
    +

    it's the operation generating the group

    +
    +
  • +
  • + generators + (list) + – +
    +

    contain all generators of the group

    +
    +
  • +
  • + generatorChecked + (boolean) + – +
    +

    Check if generators has been found

    +
    +
  • +
+
+ + + + + + + +
+ Source code in Cryptotools/Groups/cyclic.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
class Cyclic(Group):
+    """
+        This object contain a list of the Group for a cyclic group. This class find all generator of the group.
+        This class is inherited from Group object
+
+        Attributes:
+            G (list): list of all elements in the group
+            n (Integer): it's the value where the group has been generated
+            operation (Function): it's the operation generating the group
+            generators (list): contain all generators of the group 
+            generatorChecked (boolean): Check if generators has been found
+    """
+    def __init__(self, G:list, n, ope):
+        super().__init__(n, G, ope) # Call the Group's constructor
+        self._G = G
+        self._n = n
+        self._operation = ope
+        self._generators = list()
+        self._generatorChecked = False
+
+    def generator(self):
+        """
+            This function find all generators in the group G
+        """
+
+        index = 1
+        G = sorted(self._g)
+        for g in range(2, self._n):
+            z = list()
+            for entry in range(1, self._n):
+                res = self._operation(g, index, self._n)
+                if res not in z:
+                    z.append(res)
+                    index = index + 1
+
+            # We check if that match with G
+            # If yes, we find a generator
+            if sorted(z) == G:
+                self._generators.append(g)
+        self._generatorChecked = True
+
+    def getPrimitiveRoot(self):
+        """
+            This function return the primitive root modulo of n
+
+            Returns:
+                Return the primitive root of the group. None if no primitive has been found
+        """
+
+        index = 1
+        G = sorted(self._g)
+        for g in range(2, self._n):
+            z = list()
+            for entry in range(1, self._n):
+                res = self._operation(g, index, self._n)
+                if res not in z:
+                    z.append(res)
+                    index += 1
+
+            # If the group is the same has G, we found a generator
+            if sorted(z) == G:
+                return g
+        return None
+
+    def getGenerators(self) -> list:
+        """
+            This function return all generators of that group
+
+            Returns:
+                Return the list of generators found. The function generators() must be called before to call this function
+        """
+        if not self._generatorChecked:
+            self.generator()
+            self._generatorChecked = True
+        return self._generators
+
+    def isCyclic(self) -> bool:
+        """
+            Check if the group is a cyclic group, means we have at least one generator
+
+            Returns:
+                REturn a boolean, False if the group is not Cyclic otherwise return True
+        """
+        if len(self.getGenerators()) == 0:
+            return False
+        return True
+
+
+ + + +
+ + + + + + + + + + +
+ + +

+ generator() + +

+ + +
+ +

This function find all generators in the group G

+ + +
+ Source code in Cryptotools/Groups/cyclic.py +
26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
def generator(self):
+    """
+        This function find all generators in the group G
+    """
+
+    index = 1
+    G = sorted(self._g)
+    for g in range(2, self._n):
+        z = list()
+        for entry in range(1, self._n):
+            res = self._operation(g, index, self._n)
+            if res not in z:
+                z.append(res)
+                index = index + 1
+
+        # We check if that match with G
+        # If yes, we find a generator
+        if sorted(z) == G:
+            self._generators.append(g)
+    self._generatorChecked = True
+
+
+
+ +
+ +
+ + +

+ getGenerators() + +

+ + +
+ +

This function return all generators of that group

+ + + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    Return the list of generators found. The function generators() must be called before to call this function

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/cyclic.py +
70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
def getGenerators(self) -> list:
+    """
+        This function return all generators of that group
+
+        Returns:
+            Return the list of generators found. The function generators() must be called before to call this function
+    """
+    if not self._generatorChecked:
+        self.generator()
+        self._generatorChecked = True
+    return self._generators
+
+
+
+ +
+ +
+ + +

+ getPrimitiveRoot() + +

+ + +
+ +

This function return the primitive root modulo of n

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return the primitive root of the group. None if no primitive has been found

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/cyclic.py +
47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
def getPrimitiveRoot(self):
+    """
+        This function return the primitive root modulo of n
+
+        Returns:
+            Return the primitive root of the group. None if no primitive has been found
+    """
+
+    index = 1
+    G = sorted(self._g)
+    for g in range(2, self._n):
+        z = list()
+        for entry in range(1, self._n):
+            res = self._operation(g, index, self._n)
+            if res not in z:
+                z.append(res)
+                index += 1
+
+        # If the group is the same has G, we found a generator
+        if sorted(z) == G:
+            return g
+    return None
+
+
+
+ +
+ +
+ + +

+ isCyclic() + +

+ + +
+ +

Check if the group is a cyclic group, means we have at least one generator

+ + + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    REturn a boolean, False if the group is not Cyclic otherwise return True

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/cyclic.py +
82
+83
+84
+85
+86
+87
+88
+89
+90
+91
def isCyclic(self) -> bool:
+    """
+        Check if the group is a cyclic group, means we have at least one generator
+
+        Returns:
+            REturn a boolean, False if the group is not Cyclic otherwise return True
+    """
+    if len(self.getGenerators()) == 0:
+        return False
+    return True
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +

Galois Field (Finite Field)

+ + +
+ + + + +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + +

+ Galois + + +

+ + +
+ + +

This class contain the Galois Field (Finite Field)

+ + + + + + + + + + + + + +
Attributes: +
    +
  • + q + (Integer) + – +
    +

    it's the number of the GF

    +
    +
  • +
  • + operation + (Function) + – +
    +

    Function for generating the group

    +
    +
  • +
  • + identityAdd + (Integer) + – +
    +

    it's the identity element in the GF(n) for the addition operation

    +
    +
  • +
  • + identityMul + (Integer) + – +
    +

    it's the identity element in the GF(n) for the multiplicative operation

    +
    +
  • +
+
+ + + + + + + +
+ Source code in Cryptotools/Groups/galois.py +
  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
class Galois:
+    """
+        This class contain the Galois Field (Finite Field)
+
+
+
+        Attributes:
+            q (Integer): it's the number of the GF
+            operation (Function): Function for generating the group
+            identityAdd (Integer): it's the identity element in the GF(n) for the addition operation
+            identityMul (Integer): it's the identity element in the GF(n) for the multiplicative operation
+    """
+    def __init__(self, q, operation):
+        self._q = q
+        self._operation = operation
+        self._identityAdd = 0
+        self._identityMul = 1
+        self._F = [x for x in range(q)] 
+        self._add = [[0 for x in range(q)] for y in range(q)] 
+
+        # TODO: May do we do a deep copy between all groups ?
+        self._div = [[0 for x in range(q)] for y in range(q)] 
+        self._mul = [[0 for x in range(q)] for y in range(q)] 
+        self._sub = [[0 for x in range(q)] for y in range(q)] 
+        self._primitiveRoot = list()
+
+    def primitiveRoot(self):
+        """
+            In this function, we going to find the primitive root modulo n of the galois field
+
+            Returns:
+               Return the list of primitive root of the GF(q) 
+        """
+        for x in range(2, self._q):
+            z = list()
+            for entry in range(1, self._q):
+                res = self._operation(x, entry, self._q)
+                if res not in z:
+                    z.append(res)
+
+            if self.isPrimitiveRoot(z, self._q - 1):
+                if x not in self._primitiveRoot: # It's dirty, need to find why we have duplicate entry
+                    self._primitiveRoot.append(x)
+        return z
+
+    def getPrimitiveRoot(self):
+        """
+            Return the list of primitives root
+
+            Returns:
+                Return the primitive root
+        """
+        return self._primitiveRoot
+
+    def isPrimitiveRoot(self, z, length):
+        """
+            Check if z is a primitive root 
+
+            Args:
+                z (list): check if z is a primitive root
+                length (Integer): Length of the GF(q)
+        """
+        if len(z) == length:
+            return True
+        return False
+
+    def add(self):
+        """
+            This function do the operation + on the Galois Field
+
+            Returns:
+                Return a list of the group with the addition operation
+        """
+        for x in range(0, self._q):
+            for y in range(0, self._q):
+                self._add[x][y] = (x + y) % self._q
+        return self._add
+
+    def _inverseModular(self, a, n):
+        """
+            This function find the reverse modular of a by n
+
+            Returns:
+                Return the reverse modular
+        """
+        for b in range(1, n):
+            if (a * b) % n == 1:
+                inv = b
+                break
+        return inv
+
+    def div(self):
+        """
+            This function do the operation / on the Galois Field
+
+            Returns:
+                Return a list of the group with the division operation
+        """
+        for x in range(1, self._q):
+            for y in range(1, self._q):
+                inv = self._inverseModular(y, self._q)
+
+                self._div[x][y] = (x * inv) % self._q
+        return self._div
+
+    def mul(self):
+        """
+            This function do the operation * on the Galois Field
+
+            Returns:
+                Return a list of the group with the multiplication operation
+        """
+        for x in range(0, self._q):
+            for y in range(0, self._q):
+                self._mul[x][y] = (x * y) % self._q
+        return self._mul
+
+    def sub(self):
+        """
+            This function do the operation - on the Galois Field
+
+            Returns:
+                Return a list of the group with the subtraction operation
+        """
+        for x in range(0, self._q):
+            for y in range(0, self._q):
+                self._sub[x][y] = (x - y) % self._q
+        return self._sub
+
+    def check_closure_law(self, arithmetic):
+        """
+            This function check the closure law.
+            By definition, every element in the GF is an abelian group, which respect the closure law: for a and b belongs to G, a + b belongs to G, the operation is a binary operation
+
+            Args:
+                Arithmetics (str): contain the operation to be made, must be '+', '*', '/' or /-'
+        """
+        if arithmetic not in ['+', '*', '/', '-']:
+            raise Exception("The arithmetic need to be '+', '*', '/' or '-'")
+
+        if arithmetic == '+':
+            G = self._add
+        elif arithmetic == '*':
+            G = self._mul
+        elif arithmetic == '/':
+            G = self._div
+        else:
+            G = self._sub
+
+        start = 0
+        """
+            In case of multiplicative, we bypass the first line, because all elements are zero, otherwise the test fail
+        """
+        if arithmetic == '*' or arithmetic == '/':
+            start = 1
+
+        isClosure = True
+        for x in range(start, self._q):
+            gr = Group(self._q, G[x], self._operation)
+            if not gr.closure():
+                isClosure = False
+            del gr
+
+        if isClosure:
+            print(f"The group {arithmetic} respect closure law")
+        else:
+            print(f"The group {arithmetic} does not respect closure law")
+
+    def check_identity_add(self):
+        """
+            This function check the identity element and must satisfy this condition: $a + 0 = a$ for each element in the GF(n)
+            In Group Theory, an identity element is an element in the group which do not change the value every element in the group
+
+            Returns:
+
+        """
+        for x in self._F:
+            if not self._identityAdd + x == x:
+                raise Exception(
+                        f"The identity element {self._identityAdd} "\
+                         "do not satisfy $a + element = a$"
+                )
+
+    def check_identity_mul(self):
+        """
+            This function check the identity element and must satisfy this condition: $a * 1 = a$ for each element in the GF(n)
+            In Group Theory, an identity element is an element in the group which do not change the value every element in the group
+
+            Returns:
+
+        """
+        for x in self._F:
+            if not self._identityMul * x == x:
+                raise Exception(
+                        f"The identity element {self._identityAdd} "\
+                         "do not satisfy $a * element = a$"
+                )
+
+    def printMatrice(self, m):
+        """
+            This function print the GF(m)
+
+            Args:
+                m (list): Matrix of the GF
+        """
+        header = str()
+        header = "    "
+        for x in range(0, self._q):
+            header += f"{x} "
+
+        header += "\n--|" + "-" * (len(header)- 3) +"\n"
+
+        s = str()
+
+        for x in range(0, self._q):
+            s += f"{x} | "
+            for y in range(0, self._q):
+                s += f"{m[x][y]} "
+            s += "\n"
+
+        s = header + s
+        print(s)
+
+
+ + + +
+ + + + + + + + + + +
+ + +

+ add() + +

+ + +
+ +

This function do the operation + on the Galois Field

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return a list of the group with the addition operation

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
def add(self):
+    """
+        This function do the operation + on the Galois Field
+
+        Returns:
+            Return a list of the group with the addition operation
+    """
+    for x in range(0, self._q):
+        for y in range(0, self._q):
+            self._add[x][y] = (x + y) % self._q
+    return self._add
+
+
+
+ +
+ +
+ + +

+ check_closure_law(arithmetic) + +

+ + +
+ +

This function check the closure law. +By definition, every element in the GF is an abelian group, which respect the closure law: for a and b belongs to G, a + b belongs to G, the operation is a binary operation

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + Arithmetics + (str) + – +
    +

    contain the operation to be made, must be '+', '*', '/' or /-'

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
def check_closure_law(self, arithmetic):
+    """
+        This function check the closure law.
+        By definition, every element in the GF is an abelian group, which respect the closure law: for a and b belongs to G, a + b belongs to G, the operation is a binary operation
+
+        Args:
+            Arithmetics (str): contain the operation to be made, must be '+', '*', '/' or /-'
+    """
+    if arithmetic not in ['+', '*', '/', '-']:
+        raise Exception("The arithmetic need to be '+', '*', '/' or '-'")
+
+    if arithmetic == '+':
+        G = self._add
+    elif arithmetic == '*':
+        G = self._mul
+    elif arithmetic == '/':
+        G = self._div
+    else:
+        G = self._sub
+
+    start = 0
+    """
+        In case of multiplicative, we bypass the first line, because all elements are zero, otherwise the test fail
+    """
+    if arithmetic == '*' or arithmetic == '/':
+        start = 1
+
+    isClosure = True
+    for x in range(start, self._q):
+        gr = Group(self._q, G[x], self._operation)
+        if not gr.closure():
+            isClosure = False
+        del gr
+
+    if isClosure:
+        print(f"The group {arithmetic} respect closure law")
+    else:
+        print(f"The group {arithmetic} does not respect closure law")
+
+
+
+ +
+ +
+ + +

+ check_identity_add() + +

+ + +
+ +

This function check the identity element and must satisfy this condition: $a + 0 = a$ for each element in the GF(n) +In Group Theory, an identity element is an element in the group which do not change the value every element in the group

+

Returns:

+ + +
+ Source code in Cryptotools/Groups/galois.py +
174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
def check_identity_add(self):
+    """
+        This function check the identity element and must satisfy this condition: $a + 0 = a$ for each element in the GF(n)
+        In Group Theory, an identity element is an element in the group which do not change the value every element in the group
+
+        Returns:
+
+    """
+    for x in self._F:
+        if not self._identityAdd + x == x:
+            raise Exception(
+                    f"The identity element {self._identityAdd} "\
+                     "do not satisfy $a + element = a$"
+            )
+
+
+
+ +
+ +
+ + +

+ check_identity_mul() + +

+ + +
+ +

This function check the identity element and must satisfy this condition: $a * 1 = a$ for each element in the GF(n) +In Group Theory, an identity element is an element in the group which do not change the value every element in the group

+

Returns:

+ + +
+ Source code in Cryptotools/Groups/galois.py +
189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
def check_identity_mul(self):
+    """
+        This function check the identity element and must satisfy this condition: $a * 1 = a$ for each element in the GF(n)
+        In Group Theory, an identity element is an element in the group which do not change the value every element in the group
+
+        Returns:
+
+    """
+    for x in self._F:
+        if not self._identityMul * x == x:
+            raise Exception(
+                    f"The identity element {self._identityAdd} "\
+                     "do not satisfy $a * element = a$"
+            )
+
+
+
+ +
+ +
+ + +

+ div() + +

+ + +
+ +

This function do the operation / on the Galois Field

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return a list of the group with the division operation

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
def div(self):
+    """
+        This function do the operation / on the Galois Field
+
+        Returns:
+            Return a list of the group with the division operation
+    """
+    for x in range(1, self._q):
+        for y in range(1, self._q):
+            inv = self._inverseModular(y, self._q)
+
+            self._div[x][y] = (x * inv) % self._q
+    return self._div
+
+
+
+ +
+ +
+ + +

+ getPrimitiveRoot() + +

+ + +
+ +

Return the list of primitives root

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return the primitive root

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
51
+52
+53
+54
+55
+56
+57
+58
def getPrimitiveRoot(self):
+    """
+        Return the list of primitives root
+
+        Returns:
+            Return the primitive root
+    """
+    return self._primitiveRoot
+
+
+
+ +
+ +
+ + +

+ isPrimitiveRoot(z, length) + +

+ + +
+ +

Check if z is a primitive root

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + z + (list) + – +
    +

    check if z is a primitive root

    +
    +
  • +
  • + length + (Integer) + – +
    +

    Length of the GF(q)

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
def isPrimitiveRoot(self, z, length):
+    """
+        Check if z is a primitive root 
+
+        Args:
+            z (list): check if z is a primitive root
+            length (Integer): Length of the GF(q)
+    """
+    if len(z) == length:
+        return True
+    return False
+
+
+
+ +
+ +
+ + +

+ mul() + +

+ + +
+ +

This function do the operation * on the Galois Field

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return a list of the group with the multiplication operation

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
def mul(self):
+    """
+        This function do the operation * on the Galois Field
+
+        Returns:
+            Return a list of the group with the multiplication operation
+    """
+    for x in range(0, self._q):
+        for y in range(0, self._q):
+            self._mul[x][y] = (x * y) % self._q
+    return self._mul
+
+
+
+ +
+ +
+ + +

+ primitiveRoot() + +

+ + +
+ +

In this function, we going to find the primitive root modulo n of the galois field

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return the list of primitive root of the GF(q)

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
def primitiveRoot(self):
+    """
+        In this function, we going to find the primitive root modulo n of the galois field
+
+        Returns:
+           Return the list of primitive root of the GF(q) 
+    """
+    for x in range(2, self._q):
+        z = list()
+        for entry in range(1, self._q):
+            res = self._operation(x, entry, self._q)
+            if res not in z:
+                z.append(res)
+
+        if self.isPrimitiveRoot(z, self._q - 1):
+            if x not in self._primitiveRoot: # It's dirty, need to find why we have duplicate entry
+                self._primitiveRoot.append(x)
+    return z
+
+
+
+ +
+ +
+ + +

+ printMatrice(m) + +

+ + +
+ +

This function print the GF(m)

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + m + (list) + – +
    +

    Matrix of the GF

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
def printMatrice(self, m):
+    """
+        This function print the GF(m)
+
+        Args:
+            m (list): Matrix of the GF
+    """
+    header = str()
+    header = "    "
+    for x in range(0, self._q):
+        header += f"{x} "
+
+    header += "\n--|" + "-" * (len(header)- 3) +"\n"
+
+    s = str()
+
+    for x in range(0, self._q):
+        s += f"{x} | "
+        for y in range(0, self._q):
+            s += f"{m[x][y]} "
+        s += "\n"
+
+    s = header + s
+    print(s)
+
+
+
+ +
+ +
+ + +

+ sub() + +

+ + +
+ +

This function do the operation - on the Galois Field

+ + + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return a list of the group with the subtraction operation

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Groups/galois.py +
123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
def sub(self):
+    """
+        This function do the operation - on the Galois Field
+
+        Returns:
+            Return a list of the group with the subtraction operation
+    """
+    for x in range(0, self._q):
+        for y in range(0, self._q):
+            self._sub[x][y] = (x - y) % self._q
+    return self._sub
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + « Previous + + + Next » + + +
+ + + + + + + + diff --git a/site/img/favicon.ico b/site/img/favicon.ico new file mode 100644 index 0000000..e85006a Binary files /dev/null and b/site/img/favicon.ico differ diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..b2600a2 --- /dev/null +++ b/site/index.html @@ -0,0 +1,148 @@ + + + + + + + + CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ +

Welcome to CryptoTools

+ + +
+
+ +
+
+ +
+ +
+ +
+ + + + + +
+ + + + + + + + + + diff --git a/site/installation/index.html b/site/installation/index.html new file mode 100644 index 0000000..0af617d --- /dev/null +++ b/site/installation/index.html @@ -0,0 +1,149 @@ + + + + + + + + Installation - CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ +

Installation

+

To install the project, you should install from my own Python repository but, you first need to deploy your virtual enviroment.

+

Installation from source

+

You can install from the source. Download the project and install it in your virtual environment:

+
$ virtualenv ~/venv/cryptotools
+$ source ~/venv/cryptotools/bin/activate
+
+
$ git clone https://gitea.bucchino.org/gbucchino/cryptotools.git
+$ cd cryptotools
+$ python3 setup.py install
+
+

The installation is completed. You can know use Cryptotools package into your project. In the directory examples you may find some examples scripts

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + « Previous + + + Next » + + +
+ + + + + + + + diff --git a/site/introduction/index.html b/site/introduction/index.html new file mode 100644 index 0000000..245b451 --- /dev/null +++ b/site/introduction/index.html @@ -0,0 +1,136 @@ + + + + + + + + Introduction - CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ +

CryptoTools

+

CryptoTools is a Python package that provides low-level cryptographic primitives for generating strong numbers. With this project, it's possible to generate public key cryptosystems such as RSA. +This project has a academic purpose and can not be used in production enviroment yet.

+

So far, my cryptographic modules are not compliant with FIPS 140-3 but in the future, that will be.

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + + Next » + + +
+ + + + + + + + diff --git a/site/js/html5shiv.min.js b/site/js/html5shiv.min.js new file mode 100644 index 0000000..1a01c94 --- /dev/null +++ b/site/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); diff --git a/site/js/jquery-3.6.0.min.js b/site/js/jquery-3.6.0.min.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/site/js/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t + + + + + + + Number theory - CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Number Theory

+

For generating keys, we need strong prime number, they are the basis. CryptoTools provides functions for generating these numbers.

+

prime numbers

+ + +
+ + + + +
+ + + + + + + + + + +
+ + + + + + + + + + +
+ + +

+ are_coprime(p1, p2) + +

+ + +
+ +

This function check if p1 and p2 are coprime

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + p1 + (list) + – +
    +

    list of prime numbers of the first number

    +
    +
  • +
  • + p2 + (list) + – +
    +

    list of prime number of the second number

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return a boolean result

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
def are_coprime(p1, p2):
+    """
+        This function check if p1 and p2 are coprime
+
+        Args:
+            p1 (list): list of prime numbers of the first number
+            p2 (list): list of prime number of the second number
+
+        Returns:
+            Return a  boolean result
+    """
+    r = True
+    for entry in p1:
+        if entry in p2:
+            r = False
+            break
+    return r
+
+
+
+ +
+ +
+ + +

+ getPrimeNumber(n, safe=True) + +

+ + +
+ +

This function generate a large prime number +based on "A method for Obtaining Digital Signatures +and Public-Key Cryptosystems" +Section B. How to Find Large Prime Numbers +https://dl.acm.org/doi/pdf/10.1145/359340.359342

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (Integer) + – +
    +

    The size of the prime number. Must be a multiple of 64

    +
    +
  • +
  • + safe + (Boolean, default: + True +) + – +
    +

    When generating the prime number, the function must find a safe prime number, based on the Germain primes (2p + 1 is also a prime number)

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return the prime number

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
def getPrimeNumber(n, safe=True):
+    """
+        This function generate a large prime number
+        based on "A method for Obtaining Digital Signatures
+        and Public-Key Cryptosystems"
+        Section B. How to Find Large Prime Numbers
+        https://dl.acm.org/doi/pdf/10.1145/359340.359342
+
+        Args:
+            n (Integer): The size of the prime number. Must be a multiple of 64
+            safe (Boolean): When generating the prime number, the function must find a safe prime number, based on the Germain primes (2p + 1 is also a prime number)
+
+        Returns:
+            Return the prime number
+    """
+    if n % 64 != 0:
+        print("Must be multiple of 64")
+        return
+    # from sys import getsizeof
+    upper = getrandbits(n) 
+    lower = upper >> 7
+    r = randint(lower, upper)
+
+    while r % 2 == 0:
+        r += 1
+
+    # Now, we are going to compute r as prime number
+    i = 100
+    while 1:
+        # Check if it's a prime number
+        if _millerRabinTest(r):
+            break
+
+        # TODO: it's dirty, need to change that
+        # i = int(log2(r))
+        # i *= randint(2, 50)
+
+        r += i
+        # print(f"{i} {r}")
+        i += 1
+
+
+    # print(_fermatLittleTheorem(r))
+    # print(getsizeof(r))
+    return r
+
+
+
+ +
+ +
+ + +

+ getSmallPrimeNumber(n) + +

+ + +
+ +

This function is deprecated

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (Integer) + – +
    +

    Get the small prime number until n

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + int + – +
    +

    Return the first prime number found

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
def getSmallPrimeNumber(n) -> int:
+    """
+        This function is deprecated
+
+        Args:
+            n (Integer): Get the small prime number until n
+
+        Returns:
+            Return the first prime number found
+    """
+    is_prime = True
+
+    while True:  
+        for i in range(2, n):
+            # If n is divisible by i, it's not a prime, we break the loop
+            if n % i == 0:
+                is_prime = False
+                break
+
+        if is_prime:
+            break
+        is_prime = True
+        n = n + 1
+
+    p = n
+    return p
+
+
+
+ +
+ +
+ + +

+ get_n_prime_numbers(n) + +

+ + +
+ +

This function return a list of n prime numbers

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (Integer) + – +
    +

    the range, must be an integer

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    Return a list of n prime numbers

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
def get_n_prime_numbers(n) -> list:
+    """
+        This function return a list of n prime numbers
+
+        Args:
+            n (Integer): the range, must be an integer
+
+        Returns:
+            Return a list of n prime numbers
+    """
+    l = list()
+
+    count = 2
+    index = 0
+    while index < n:
+        is_prime = True
+        for x in range(2, count):
+            if count % x == 0:
+                is_prime = False
+                break
+
+        if is_prime:
+            l.append(count)
+            index += 1
+
+        count += 1
+
+    return l
+
+
+
+ +
+ +
+ + +

+ get_prime_numbers(n) + +

+ + +
+ +

This function get all prime number of the n

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (Integer) + – +
    +

    find all prime number until n is achieves

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    Return a list of prime numbers

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
def get_prime_numbers(n) -> list:
+    """
+        This function get all prime number of the n
+
+        Args:
+            n (Integer): find all prime number until n is achieves
+
+        Returns:
+         Return a list of prime numbers
+    """
+    l = list()
+    for i in range(2, n):
+        if n % i == 0:
+            l.append(i)
+    return l
+
+
+
+ +
+ +
+ + +

+ isPrimeNumber(p) + +

+ + +
+ +

Check if the number p is a prime number or not. The function iterate until p is achieve.

+

This function is not the efficient way to determine if p is prime or a composite.

+

To identify if p is prime or not, the function check the result of the Euclidean Division has a remainder. If yes, it's means it's not a prime number

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + p + (Integer) + – +
    +

    number if possible prime or not

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + – +
    +

    Return a boolean if the number p is a prime number or not. True if yes

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
def isPrimeNumber(p):
+    """
+        Check if the number p is a prime number or not. The function iterate until p is achieve.
+
+        This function is not the efficient way to determine if p is prime or a composite.
+
+        To identify if p is prime or not, the function check the result of the Euclidean Division has a remainder. If yes, it's means it's not a prime number
+
+        Args:
+            p (Integer): number if possible prime or not
+
+        Returns:
+            Return a boolean if the number p is a prime number or not. True if yes
+    """
+    for i in range(2, p):
+        if p % i == 0:
+            return False
+    return True
+
+
+
+ +
+ +
+ + +

+ isSafePrime(n) + +

+ + +
+ +

This function has not been implemented yet, but check if the number n is a safe prime number. This function will test different properties of the possible prime number n

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (Integer) + – +
    +

    the prime number to check

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    Return a Boolean if the prime number n is safe or not. True if yes, otherwise it's False

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
def isSafePrime(n) -> bool:
+    """
+        This function has not been implemented yet, but check if the number n is a safe prime number. This function will test different properties of the possible prime number n
+
+        Args:
+            n (Integer): the prime number to check
+
+        Returns:
+            Return a Boolean if the prime number n is safe or not. True if yes, otherwise it's False
+    """
+    if n.bit_length() >= 256:
+        return True
+
+    # Do Sophie Germain's test
+
+    return False
+
+
+
+ +
+ +
+ + +

+ sieveOfEratosthenes(n) + +

+ + +
+ +

This function build a list of prime number based on the Sieve of Erastosthenes

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (Integer) + – +
    +

    Interate until n is achives

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    Return a list of all prime numbers to n

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
def sieveOfEratosthenes(n) -> list:
+    """
+        This function build a list of prime number based on the Sieve of Erastosthenes
+
+        Args:
+            n (Integer): Interate until n is achives
+
+        Returns:
+            Return a list of all prime numbers to n
+    """
+    if n < 1:
+        return list()
+
+    eratost = dict()
+    for i in range(2, n):
+        eratost[i] = True
+
+    for i in range(2, n):
+        if eratost[i]:
+            for j in range(i*i, n, i):
+                eratost[j] = False
+
+    sieve = list()
+    for i in range(2, n):
+        if eratost[i]:
+            sieve.append(i)
+
+    return sieve
+
+
+
+ +
+ +
+ + +

+ sophieGermainPrime(p) + +

+ + +
+ +

Check if the number p is a safe prime number: 2p + 1 is also a prime

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + p + (Integer) + – +
    +

    Possible prime number

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + bool + – +
    +

    Return True if p is a safe prime number, otherwise it's False

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/primeNumber.py +
259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
def sophieGermainPrime(p) -> bool:
+    """
+        Check if the number p is a safe prime number: 2p + 1 is also a prime
+
+        Args:
+            p (Integer):  Possible prime number
+
+        Returns:
+            Return True if p is a safe prime number, otherwise it's False
+    """
+    pp = 2 * p + 1
+    return _millerRabinTest(pp)
+
+
+
+ +
+ + + +
+ +
+ +

Fibonacci sequence

+ + +
+ + + + +
+ + + + + + + + + + +
+ + + + + + + + + + +
+ + +

+ fibonacci(n) + +

+ + +
+ +

This function generate the list of Fibonacci

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + n + (integer) + – +
    +

    it's maximum value of Fibonacci sequence

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    Return a list of n element in the Fibonacci sequence

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Numbers/numbers.py +
 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
def fibonacci(n) -> list:
+    """
+        This function generate the list of Fibonacci
+
+        Args:
+            n (integer): it's maximum value of Fibonacci sequence
+
+        Returns:
+            Return a list of n element in the Fibonacci sequence
+    """
+    fibo = [0, 1]
+    fibo.append(fibo[0] + fibo[1])
+    for i in range(2, n):
+        fibo.append(fibo[i - 1] + fibo[i])
+    return fibo
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + « Previous + + + Next » + + +
+ + + + + + + + diff --git a/site/objects.inv b/site/objects.inv new file mode 100644 index 0000000..429a915 Binary files /dev/null and b/site/objects.inv differ diff --git a/site/rsa/index.html b/site/rsa/index.html new file mode 100644 index 0000000..4ac228f --- /dev/null +++ b/site/rsa/index.html @@ -0,0 +1,1086 @@ + + + + + + + + RSA - CryptoTools documentation + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

CryptoTools provides several functions for generating a RSA Keys

+ + +
+ + + + +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + +

+ RSA + + +

+ + +
+ + +

This class generate public key based on RSA algorithm

+ + + + + + + + + + + + + +
Attributes: +
    +
  • + p + – +
    +

    it's the prime number for generating the modulus

    +
    +
  • +
  • + q + – +
    +

    it's the second prime number for the modulus

    +
    +
  • +
  • + public + – +
    +

    Object of the RSAKey which is the public key

    +
    +
  • +
  • + private + – +
    +

    Object of the RSAKey for the private key

    +
    +
  • +
+
+ + + + + + + +
+ Source code in Cryptotools/Encryptions/RSA.py +
 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
class RSA:
+    """
+        This class generate public key based on RSA algorithm
+
+        Attributes:
+            p: it's the prime number for generating the modulus
+            q: it's the second prime number for the modulus
+            public: Object of the RSAKey which is the public key
+            private: Object of the RSAKey for the private key
+    """
+
+    def __init__(self):
+        """
+            Build a RSA Key
+        """
+        self._p = None
+        self._q = None
+        self._public = None
+        self._private = None
+
+    def generateKeys(self, size=512):
+        """
+            This function generate both public and private keys
+            Args:
+                size: It's the size of the key and must be multiple of 64
+        """
+        # p and q must be coprime
+        self._p = getPrimeNumber(size)
+        self._q = getPrimeNumber(size)
+
+        # compute n = pq
+        n = self._p * self._q
+
+        phin = (self._p - 1) * (self._q - 1)
+
+        # e must be coprime with phi(n)
+        # According to the FIPS 186-5, the public key exponent must be odd
+        # and the minimum size is 65536 (Cf. Section 5.4 PKCS #1)
+        # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
+        e = 65535 # Works
+        for i in range(2, phin - 1):
+            if gcd(phin, e) == 1:
+                break
+            e += 1
+        # print(gcd(phin, e))
+        self._public = RSAKey(e, n, getsizeof(e))
+
+        # d is the reverse modulo of phi(n)
+        # d = self._inverseModular(e, phin)
+        d = pow(e, -1, phin) # Works in python 3.8
+        self._private = RSAKey(d, n, getsizeof(d))
+
+    @property
+    def e(self):
+        return self._public.key
+
+    @property
+    def d(self):
+        return self._private.key
+
+    @property
+    def n(self):
+        return self._public.modulus
+
+    @property
+    def p(self):
+        return self._p
+
+    @property
+    def q(self):
+        return self._q
+
+    def encrypt(self, data) -> list:
+        """
+            This function return a list of data encrypted with the public key
+
+            Args:
+                data (str): it's the plaintext which need to be encrypted
+
+            Returns:
+                return a list of the data encrypted, each entries contains the value encoded
+        """
+        dataEncoded = self._str2bin(data)
+        return list(
+            pow(int(x), self._public.key, self._public.modulus)
+            for x in dataEncoded
+        )
+
+    def decrypt(self, data) -> list:
+        """
+            This function return a list decrypted with the private key
+
+            Args:
+                data (str): It's the encrypted data which need to be decrypted
+
+            Returns:
+                Return the list of data uncrypted into plaintext
+        """
+        decrypted = list()
+        for x in data:
+            d = pow(x, self._private.key, self._private.modulus)
+            decrypted.append(chr(d))
+        return ''.join(decrypted)
+
+    def _str2bin(self, data) -> list:
+        """
+            This function convert a string into the unicode value
+
+            Args:
+                data: the string which need to be converted
+
+            Returns:
+                Return a list of unicode values of data
+        """
+        return list(ord(x) for x in data)
+
+    def _inverseModular(self, a, n):
+        """
+            This function compute the modular inverse for finding d, the decryption key
+
+            Args:
+                a (Integer): the base of the exponent
+                n (Integer): the modulus
+        """
+        for b in range(1, n):
+            #if pow(a, b, n) == 1:
+            if (a * b) % n == 1:
+                inv = b
+                break
+        return inv
+
+
+ + + +
+ + + + + + + + + + +
+ + +

+ __init__() + +

+ + +
+ +

Build a RSA Key

+ + +
+ Source code in Cryptotools/Encryptions/RSA.py +
56
+57
+58
+59
+60
+61
+62
+63
def __init__(self):
+    """
+        Build a RSA Key
+    """
+    self._p = None
+    self._q = None
+    self._public = None
+    self._private = None
+
+
+
+ +
+ +
+ + +

+ decrypt(data) + +

+ + +
+ +

This function return a list decrypted with the private key

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + data + (str) + – +
    +

    It's the encrypted data which need to be decrypted

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    Return the list of data uncrypted into plaintext

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Encryptions/RSA.py +
133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
def decrypt(self, data) -> list:
+    """
+        This function return a list decrypted with the private key
+
+        Args:
+            data (str): It's the encrypted data which need to be decrypted
+
+        Returns:
+            Return the list of data uncrypted into plaintext
+    """
+    decrypted = list()
+    for x in data:
+        d = pow(x, self._private.key, self._private.modulus)
+        decrypted.append(chr(d))
+    return ''.join(decrypted)
+
+
+
+ +
+ +
+ + +

+ encrypt(data) + +

+ + +
+ +

This function return a list of data encrypted with the public key

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + data + (str) + – +
    +

    it's the plaintext which need to be encrypted

    +
    +
  • +
+
+ + + + + + + + + + + + +
Returns: +
    +
  • + list + – +
    +

    return a list of the data encrypted, each entries contains the value encoded

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Encryptions/RSA.py +
117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
def encrypt(self, data) -> list:
+    """
+        This function return a list of data encrypted with the public key
+
+        Args:
+            data (str): it's the plaintext which need to be encrypted
+
+        Returns:
+            return a list of the data encrypted, each entries contains the value encoded
+    """
+    dataEncoded = self._str2bin(data)
+    return list(
+        pow(int(x), self._public.key, self._public.modulus)
+        for x in dataEncoded
+    )
+
+
+
+ +
+ +
+ + +

+ generateKeys(size=512) + +

+ + +
+ +

This function generate both public and private keys +Args: + size: It's the size of the key and must be multiple of 64

+ + +
+ Source code in Cryptotools/Encryptions/RSA.py +
65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
def generateKeys(self, size=512):
+    """
+        This function generate both public and private keys
+        Args:
+            size: It's the size of the key and must be multiple of 64
+    """
+    # p and q must be coprime
+    self._p = getPrimeNumber(size)
+    self._q = getPrimeNumber(size)
+
+    # compute n = pq
+    n = self._p * self._q
+
+    phin = (self._p - 1) * (self._q - 1)
+
+    # e must be coprime with phi(n)
+    # According to the FIPS 186-5, the public key exponent must be odd
+    # and the minimum size is 65536 (Cf. Section 5.4 PKCS #1)
+    # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
+    e = 65535 # Works
+    for i in range(2, phin - 1):
+        if gcd(phin, e) == 1:
+            break
+        e += 1
+    # print(gcd(phin, e))
+    self._public = RSAKey(e, n, getsizeof(e))
+
+    # d is the reverse modulo of phi(n)
+    # d = self._inverseModular(e, phin)
+    d = pow(e, -1, phin) # Works in python 3.8
+    self._private = RSAKey(d, n, getsizeof(d))
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ RSAKey + + +

+ + +
+ + +

This class store the RSA key with the modulus associated +The key is a tuple of the key and the modulus n

+ + + + + + + + + + + + + +
Attributes: +
    +
  • + key + – +
    +

    It's the exponent key, can be public or private

    +
    +
  • +
  • + modulus + – +
    +

    It's the public modulus

    +
    +
  • +
  • + length + – +
    +

    It's the key length

    +
    +
  • +
+
+ + + + + + + +
+ Source code in Cryptotools/Encryptions/RSA.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
class RSAKey:
+    """
+        This class store the RSA key with the modulus associated
+        The key is a tuple of the key and the modulus n
+
+        Attributes:
+            key: It's the exponent key, can be public or private
+            modulus: It's the public modulus
+            length: It's the key length
+    """
+    def __init__(self, key, modulus, length):
+        """
+            Contain the RSA Key. An object of RSAKey can be a public key or a private key
+
+            Args:
+                key (Integer): it's the exponent of the key
+                modulus (Integer): it's the public modulus of the key
+                length (Integer): length of the key
+        """
+
+        self._key     = key
+        self._modulus = modulus
+        self._length  = length
+
+    @property
+    def key(self):
+        return self._key
+
+    @property
+    def modulus(self):
+        return self._modulus
+
+    @property
+    def length(self):
+        return self.length
+
+
+ + + +
+ + + + + + + + + + +
+ + +

+ __init__(key, modulus, length) + +

+ + +
+ +

Contain the RSA Key. An object of RSAKey can be a public key or a private key

+ + + + + + + + + + + + + +
Parameters: +
    +
  • + key + (Integer) + – +
    +

    it's the exponent of the key

    +
    +
  • +
  • + modulus + (Integer) + – +
    +

    it's the public modulus of the key

    +
    +
  • +
  • + length + (Integer) + – +
    +

    length of the key

    +
    +
  • +
+
+ +
+ Source code in Cryptotools/Encryptions/RSA.py +
19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
def __init__(self, key, modulus, length):
+    """
+        Contain the RSA Key. An object of RSAKey can be a public key or a private key
+
+        Args:
+            key (Integer): it's the exponent of the key
+            modulus (Integer): it's the public modulus of the key
+            length (Integer): length of the key
+    """
+
+    self._key     = key
+    self._modulus = modulus
+    self._length  = length
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + « Previous + + + Next » + + +
+ + + + + + + + diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..0f8724e --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/site/sitemap.xml.gz b/site/sitemap.xml.gz new file mode 100644 index 0000000..d0bfde9 Binary files /dev/null and b/site/sitemap.xml.gz differ diff --git a/tests/carmi_function_oeis b/tests/carmi_function_oeis new file mode 100644 index 0000000..d42e74e --- /dev/null +++ b/tests/carmi_function_oeis @@ -0,0 +1 @@ +1, 1, 2, 2, 4, 2, 6, 2, 6, 4, 10, 2, 12, 6, 4, 4, 16, 6, 18, 4, 6, 10, 22, 2, 20, 12, 18, 6, 28, 4, 30, 8, 10, 16, 12, 6, 36, 18, 12, 4, 40, 6, 42, 10, 12, 22, 46, 4, 42, 20, 16, 12, 52, 18, 20, 6, 18, 28, 58, 4, 60, 30, 6, 16, 12, 10, 66, 16, 22, 12, 70, 6, 72, 36, 20, 18, 30, 12, 78, 4, 54 diff --git a/tests/carmi_numbers_oeis b/tests/carmi_numbers_oeis new file mode 100644 index 0000000..578e3db --- /dev/null +++ b/tests/carmi_numbers_oeis @@ -0,0 +1 @@ +561, 1105, 1729, 2465, 2821, 6601, 8911, 10585, 15841, 29341, 41041, 46657, 52633, 62745, 63973, 75361, 101101, 115921, 126217, 162401, 172081, 188461, 252601, 278545, 294409, 314821, 334153, 340561, 399001, 410041, 449065, 488881, 512461, 530881, 552721 diff --git a/tests/execute_tests.py b/tests/execute_tests.py new file mode 100755 index 0000000..3b6afcc --- /dev/null +++ b/tests/execute_tests.py @@ -0,0 +1,8 @@ +#!/usr/bin/bash + +set -e +source /home/geoffrey/venv/forensic/bin/activate; +for test in `ls tests/test_*.py`; do + echo $test; + python3 $test -v; +done diff --git a/tests/fibonacci_oeis b/tests/fibonacci_oeis new file mode 100644 index 0000000..a5b2ef4 --- /dev/null +++ b/tests/fibonacci_oeis @@ -0,0 +1 @@ +0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155 diff --git a/tests/phi_oeis b/tests/phi_oeis new file mode 100644 index 0000000..52a3679 --- /dev/null +++ b/tests/phi_oeis @@ -0,0 +1 @@ +1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18, 24, 16, 40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36, 28, 58, 16, 60, 30, 36, 32, 48, 20, 66, 32, 44 diff --git a/tests/prime_numbers_oeis b/tests/prime_numbers_oeis new file mode 100644 index 0000000..ecb82d0 --- /dev/null +++ b/tests/prime_numbers_oeis @@ -0,0 +1 @@ +2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271 diff --git a/tests/test_breaking_rsa.py b/tests/test_breaking_rsa.py new file mode 100644 index 0000000..e85677a --- /dev/null +++ b/tests/test_breaking_rsa.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import unittest +from Cryptotools.Numbers.primeNumber import isPrimeNumber +from math import sqrt, isqrt, ceil +from random import choice + + +class TestBreakingRSA(unittest.TestCase): + def test_breaking_rsa(self): + # primes = self._list_primes() + #p = choice(primes) + #q = choice(primes) + self._breaking_rsa(7901, 7817) + self._breaking_rsa(7907, 7919) + self._breaking_rsa(7103, 7127) # Works + # self._breaking_rsa(7103, 7121) # Doesn't works + + def _breaking_rsa(self, p, q): + # print(f"p = {p}") + # print(f"q = {q}") + n = p * q + # print(f"n = {n}") + + # a = isqrt(n) + 1 + a = ceil(sqrt(n)) + iteration = 1 + while True: + b2 = (a ** 2) - n + sqb2 = sqrt(b2) + if b2 % 2 == 0.0: + b = sqrt(b2) + break + a = a + 1 + iteration += 1 + # print(f"Iteration: {iteration}") + # print(f"a = {a}") + # print(f"b2 = {b2}") + # print(f"b = {b}") + p = int((a + b)) + q = int((a - b)) + #N = (a + b) * (a - b) + N = p * q + # print(isPrimeNumber(p)) + # print(isPrimeNumber(q)) + # print(N) + # print() + self.assertTrue(N == n) + + def _list_primes(self): + l = list() + i = 100 # We start at 100 + while (len(l) < 1000): + if isPrimeNumber(i): + l.append(i) + i = i + 1 + return l + +unittest.main() diff --git a/tests/test_carmi.py b/tests/test_carmi.py new file mode 100644 index 0000000..22f8b94 --- /dev/null +++ b/tests/test_carmi.py @@ -0,0 +1,44 @@ +import unittest + +from Cryptotools.Numbers.carmi import carmi_numbers, is_carmichael, carmichael_lambda + + +class TestCarmichael(unittest.TestCase): + def _generate_carmi(self): + # Source: https://oeis.org/A002322 + with open("tests/carmi_function_oeis", "r") as f: + data = f.readlines() + carmi_s = data[0].split(",") + carmi = list() + for entry in carmi_s: + carmi.append(entry.strip()) + return carmi + + def _generate_carmi_numbers(self): + # Source: https://oeis.org/A002997 + with open("tests/carmi_numbers_oeis", "r") as f: + data = f.readlines() + carmi_s = data[0].split(",") + carmi = list() + for entry in carmi_s: + carmi.append(entry.strip()) + return carmi # len 69 + + def test_carmi_lambda(self): + carmi_list = self._generate_carmi() + index = 2 + for i in range(2, len(carmi_list)): + res = carmichael_lambda(index) + print(f"{res} {carmi_list[i]}") + index += 1 + # self.assertEqual(res, carmi_list[i], "Wrong value") + + def test_carmi_number(self): + pass + #carmi_list = self._generate_carmi_numbers() + #for i in range(0, len(carmi_list) - 10): + # value = int(carmi_list[i]) + # l = carmi_numbers(value) + # r = is_carmichael(value, l) + # self.assertTrue(r) +unittest.main() diff --git a/tests/test_numbers.py b/tests/test_numbers.py new file mode 100644 index 0000000..14e49dd --- /dev/null +++ b/tests/test_numbers.py @@ -0,0 +1,22 @@ +import unittest + +from Cryptotools.Numbers.numbers import fibonacci + +class TestNumbers(unittest.TestCase): + def _generate_fibonacci(self): + # Source https://oeis.org/A000045 + with open("tests/fibonacci_oeis", "r") as f: + data = f.readlines() + fibo_s = data[0].split(",") + fibo = list() + for entry in fibo_s: + fibo.append(int(entry.strip())) + return fibo + + def test_fibonacci(self): + fibo_oeis = self._generate_fibonacci() + fibo = fibonacci(40) + for i in range(0, len(fibo_oeis)): + self.assertEqual(fibo[i], fibo_oeis[i], "Wrong value") + +unittest.main() diff --git a/tests/test_phi.py b/tests/test_phi.py new file mode 100644 index 0000000..38cedb9 --- /dev/null +++ b/tests/test_phi.py @@ -0,0 +1,26 @@ +import unittest + +from Cryptotools.Numbers.coprime import phi + +class TestPhi(unittest.TestCase): + def _generate_phi(self): + # Source: https://oeis.org/A000010 + with open("tests/phi_oeis", "r") as f: + data = f.readlines() + phi_s = data[0].split(",") + phi = list() + for entry in phi_s: + phi.append(int(entry.strip())) + return phi # len 69 + + def test_phi(self): + phi_list = self._generate_phi() + index = 0 + for i in range(1, len(phi_list) + 1): + res = phi(i) + # print(f"{i} {res}") + value = phi_list[index] + # print(f"{res}: {value}") + self.assertEqual(res, value, "Wrong value") + index += 1 +unittest.main() diff --git a/tests/test_primes.py b/tests/test_primes.py new file mode 100644 index 0000000..f373cbd --- /dev/null +++ b/tests/test_primes.py @@ -0,0 +1,57 @@ +import unittest + +from Cryptotools.Numbers.primeNumber import getPrimeNumber, isPrimeNumber, sieveOfEratosthenes, _fermatLittleTheorem + +""" + To confirm if our algorithms for generating a list of prime numbers, + Our test check with the list generated by OEIS: + https://oeis.org/A000040 + +""" + +class TestPrime(unittest.TestCase): + """ + Lorsqu'on test, on genere une liste de prime number + Et une liste de non prime number + Nos tests vont verifier si les test de millerRabbin fonctionne et retourne bien que les non primes sont bien non primes + """ + def _generate_sieve(self): + # Source: https://oeis.org/A002322 + with open("tests/prime_numbers_oeis", "r") as f: + data = f.readlines() + p_s = data[0].split(",") + primes = list() + for entry in p_s: + primes.append(int(entry.strip())) + return primes + + def test_prime(self): + pass + #for i in range(25): + # n = getPrimeNumber(128) + + def test_is_prime(self): + pass + #for i in range(25): + # n = getPrimeNumber() + # # self.assertTrue(isPrimeNumber(n)) + + def test_sieve_eratost(self): + sieves = self._generate_sieve() + eratost = sieveOfEratosthenes(100) + + for i in range(len(eratost)): + self.assertEqual(eratost[i], sieves[i], "Wrong value") + + def test_fermat(self): + numbers = { + 5: True, + 19: True, + 20: False + } + + #for number in numbers: + # if numbers[number] != _fermatLittleTheorem(number): + # self.assertFalse(numbers[number]) + +unittest.main()