├── .gitignore ├── .idea ├── Randomness_Testing.iml ├── libraries │ └── R_User_Library.xml ├── misc.xml └── modules.xml ├── ApproximateEntropy.py ├── BinaryMatrix.py ├── Complexity.py ├── CumulativeSum.py ├── FrequencyTest.py ├── GUI.py ├── LICENSE ├── Main.py ├── Matrix.py ├── OLD_Main.py ├── README.md ├── RandomExcursions.py ├── RunTest.py ├── Serial.py ├── Spectral.py ├── TemplateMatching.py ├── Tools.py ├── Universal.py ├── data ├── data.e ├── data.pi ├── data.sqrt2 ├── data.sqrt3 ├── test_data.bin ├── test_data_01.txt └── test_data_02.txt ├── result ├── 20180107_Binary_Data.txt ├── 20180107_Binary_File.txt ├── 20180107_String_File.txt ├── 20180117.txt └── 20180117_URL_Result.txt ├── test_bin_file.py ├── test_e.py ├── test_pi.py ├── test_sqrt2.py ├── test_sqrt3.py └── test_url_01.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | -------------------------------------------------------------------------------- /.idea/Randomness_Testing.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/R_User_Library.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ApproximateEntropy.py: -------------------------------------------------------------------------------- 1 | from math import log as log 2 | from numpy import zeros as zeros 3 | from scipy.special import gammaincc as gammaincc 4 | 5 | class ApproximateEntropy: 6 | 7 | @staticmethod 8 | def approximate_entropy_test(binary_data:str, verbose=False, pattern_length=10): 9 | """ 10 | from the NIST documentation http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 11 | 12 | As with the Serial test of Section 2.11, the focus of this test is the frequency of all possible 13 | overlapping m-bit patterns across the entire sequence. The purpose of the test is to compare 14 | the frequency of overlapping blocks of two consecutive/adjacent lengths (m and m+1) against the 15 | expected result for a random sequence. 16 | 17 | :param binary_data: a binary string 18 | :param verbose True to display the debug message, False to turn off debug message 19 | :param pattern_length: the length of the pattern (m) 20 | :return: ((p_value1, bool), (p_value2, bool)) A tuple which contain the p_value and result of serial_test(True or False) 21 | """ 22 | length_of_binary_data = len(binary_data) 23 | 24 | # Augment the n-bit sequence to create n overlapping m-bit sequences by appending m-1 bits 25 | # from the beginning of the sequence to the end of the sequence. 26 | # NOTE: documentation says m-1 bits but that doesnt make sense, or work. 27 | binary_data += binary_data[:pattern_length + 1:] 28 | 29 | # Get max length one patterns for m, m-1, m-2 30 | max_pattern = '' 31 | for i in range(pattern_length + 2): 32 | max_pattern += '1' 33 | 34 | # Keep track of each pattern's frequency (how often it appears) 35 | vobs_01 = zeros(int(max_pattern[0:pattern_length:], 2) + 1) 36 | vobs_02 = zeros(int(max_pattern[0:pattern_length + 1:], 2) + 1) 37 | 38 | for i in range(length_of_binary_data): 39 | # Work out what pattern is observed 40 | vobs_01[int(binary_data[i:i + pattern_length:], 2)] += 1 41 | vobs_02[int(binary_data[i:i + pattern_length + 1:], 2)] += 1 42 | 43 | # Calculate the test statistics and p values 44 | vobs = [vobs_01, vobs_02] 45 | 46 | sums = zeros(2) 47 | for i in range(2): 48 | for j in range(len(vobs[i])): 49 | if vobs[i][j] > 0: 50 | sums[i] += vobs[i][j] * log(vobs[i][j] / length_of_binary_data) 51 | sums /= length_of_binary_data 52 | ape = sums[0] - sums[1] 53 | 54 | xObs = 2.0 * length_of_binary_data * (log(2) - ape) 55 | 56 | p_value = gammaincc(pow(2, pattern_length - 1), xObs / 2.0) 57 | 58 | if verbose: 59 | print('Approximate Entropy Test DEBUG BEGIN:') 60 | print("\tLength of input:\t\t\t", length_of_binary_data) 61 | print('\tLength of each block:\t\t', pattern_length) 62 | print('\tApEn(m):\t\t\t\t\t', ape) 63 | print('\txObs:\t\t\t\t\t\t', xObs) 64 | print('\tP-Value:\t\t\t\t\t', p_value) 65 | print('DEBUG END.') 66 | 67 | return (p_value, (p_value >= 0.01)) -------------------------------------------------------------------------------- /BinaryMatrix.py: -------------------------------------------------------------------------------- 1 | from copy import copy as copy 2 | 3 | class BinaryMatrix: 4 | 5 | def __init__(self, matrix, rows, cols): 6 | """ 7 | This class contains the algorithm specified in the NIST suite for computing the **binary rank** of a matrix. 8 | :param matrix: the matrix we want to compute the rank for 9 | :param rows: the number of rows 10 | :param cols: the number of columns 11 | :return: a BinaryMatrix object 12 | """ 13 | self.M = rows 14 | self.Q = cols 15 | self.A = matrix 16 | self.m = min(rows, cols) 17 | 18 | def compute_rank(self, verbose=False): 19 | """ 20 | This method computes the binary rank of self.matrix 21 | :param verbose: if this is true it prints out the matrix after the forward elimination and backward elimination 22 | operations on the rows. This was used to testing the method to check it is working as expected. 23 | :return: the rank of the matrix. 24 | """ 25 | if verbose: 26 | print("Original Matrix\n", self.A) 27 | 28 | i = 0 29 | while i < self.m - 1: 30 | if self.A[i][i] == 1: 31 | self.perform_row_operations(i, True) 32 | else: 33 | found = self.find_unit_element_swap(i, True) 34 | if found == 1: 35 | self.perform_row_operations(i, True) 36 | i += 1 37 | 38 | if verbose: 39 | print("Intermediate Matrix\n", self.A) 40 | 41 | i = self.m - 1 42 | while i > 0: 43 | if self.A[i][i] == 1: 44 | self.perform_row_operations(i, False) 45 | else: 46 | if self.find_unit_element_swap(i, False) == 1: 47 | self.perform_row_operations(i, False) 48 | i -= 1 49 | 50 | if verbose: 51 | print("Final Matrix\n", self.A) 52 | 53 | return self.determine_rank() 54 | 55 | def perform_row_operations(self, i, forward_elimination): 56 | """ 57 | This method performs the elementary row operations. This involves xor'ing up to two rows together depending on 58 | whether or not certain elements in the matrix contain 1's if the "current" element does not. 59 | :param i: the current index we are are looking at 60 | :param forward_elimination: True or False. 61 | """ 62 | if forward_elimination: 63 | j = i + 1 64 | while j < self.M: 65 | if self.A[j][i] == 1: 66 | self.A[j, :] = (self.A[j, :] + self.A[i, :]) % 2 67 | j += 1 68 | else: 69 | j = i - 1 70 | while j >= 0: 71 | if self.A[j][i] == 1: 72 | self.A[j, :] = (self.A[j, :] + self.A[i, :]) % 2 73 | j -= 1 74 | 75 | def find_unit_element_swap(self, i, forward_elimination): 76 | """ 77 | This given an index which does not contain a 1 this searches through the rows below the index to see which rows 78 | contain 1's, if they do then they swapped. This is done on the forward and backward elimination 79 | :param i: the current index we are looking at 80 | :param forward_elimination: True or False. 81 | """ 82 | row_op = 0 83 | if forward_elimination: 84 | index = i + 1 85 | while index < self.M and self.A[index][i] == 0: 86 | index += 1 87 | if index < self.M: 88 | row_op = self.swap_rows(i, index) 89 | else: 90 | index = i - 1 91 | while index >= 0 and self.A[index][i] == 0: 92 | index -= 1 93 | if index >= 0: 94 | row_op = self.swap_rows(i, index) 95 | return row_op 96 | 97 | def swap_rows(self, i, ix): 98 | """ 99 | This method just swaps two rows in a matrix. Had to use the copy package to ensure no memory leakage 100 | :param i: the first row we want to swap and 101 | :param ix: the row we want to swap it with 102 | :return: 1 103 | """ 104 | temp = copy(self.A[i, :]) 105 | self.A[i, :] = self.A[ix, :] 106 | self.A[ix, :] = temp 107 | return 1 108 | 109 | def determine_rank(self): 110 | """ 111 | This method determines the rank of the transformed matrix 112 | :return: the rank of the transformed matrix 113 | """ 114 | rank = self.m 115 | i = 0 116 | while i < self.M: 117 | all_zeros = 1 118 | for j in range(self.Q): 119 | if self.A[i][j] == 1: 120 | all_zeros = 0 121 | if all_zeros == 1: 122 | rank -= 1 123 | i += 1 124 | return rank -------------------------------------------------------------------------------- /Complexity.py: -------------------------------------------------------------------------------- 1 | from copy import copy as copy 2 | from numpy import dot as dot 3 | from numpy import histogram as histogram 4 | from numpy import zeros as zeros 5 | from scipy.special import gammaincc as gammaincc 6 | 7 | class ComplexityTest: 8 | 9 | @staticmethod 10 | def linear_complexity_test(binary_data:str, verbose=False, block_size=500): 11 | """ 12 | Note that this description is taken from the NIST documentation [1] 13 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 14 | The focus of this test is the length of a linear feedback shift register (LFSR). The purpose of this test is to 15 | determine whether or not the sequence is complex enough to be considered random. Random sequences are 16 | characterized by longer LFSRs. An LFSR that is too short implies non-randomness. 17 | 18 | :param binary_data: a binary string 19 | :param verbose True to display the debug messgae, False to turn off debug message 20 | :param block_size: Size of the block 21 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 22 | 23 | """ 24 | 25 | length_of_binary_data = len(binary_data) 26 | 27 | # The number of degrees of freedom; 28 | # K = 6 has been hard coded into the test. 29 | degree_of_freedom = 6 30 | 31 | # π0 = 0.010417, π1 = 0.03125, π2 = 0.125, π3 = 0.5, π4 = 0.25, π5 = 0.0625, π6 = 0.020833 32 | # are the probabilities computed by the equations in Section 3.10 33 | pi = [0.01047, 0.03125, 0.125, 0.5, 0.25, 0.0625, 0.020833] 34 | 35 | t2 = (block_size / 3.0 + 2.0 / 9) / 2 ** block_size 36 | mean = 0.5 * block_size + (1.0 / 36) * (9 + (-1) ** (block_size + 1)) - t2 37 | 38 | number_of_block = int(length_of_binary_data / block_size) 39 | 40 | if number_of_block > 1: 41 | block_end = block_size 42 | block_start = 0 43 | blocks = [] 44 | for i in range(number_of_block): 45 | blocks.append(binary_data[block_start:block_end]) 46 | block_start += block_size 47 | block_end += block_size 48 | 49 | complexities = [] 50 | for block in blocks: 51 | complexities.append(ComplexityTest.berlekamp_massey_algorithm(block)) 52 | 53 | t = ([-1.0 * (((-1) ** block_size) * (chunk - mean) + 2.0 / 9) for chunk in complexities]) 54 | vg = histogram(t, bins=[-9999999999, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 9999999999])[0][::-1] 55 | im = ([((vg[ii] - number_of_block * pi[ii]) ** 2) / (number_of_block * pi[ii]) for ii in range(7)]) 56 | 57 | xObs = 0.0 58 | for i in range(len(pi)): 59 | xObs += im[i] 60 | 61 | # P-Value = igamc(K/2, xObs/2) 62 | p_value = gammaincc(degree_of_freedom / 2.0, xObs / 2.0) 63 | 64 | if verbose: 65 | print('Linear Complexity Test DEBUG BEGIN:') 66 | print("\tLength of input:\t", length_of_binary_data) 67 | print('\tLength in bits of a block:\t', ) 68 | print("\tDegree of Freedom:\t\t", degree_of_freedom) 69 | print('\tNumber of Blocks:\t', number_of_block) 70 | print('\tValue of Vs:\t\t', vg) 71 | print('\txObs:\t\t\t\t', xObs) 72 | print('\tP-Value:\t\t\t', p_value) 73 | print('DEBUG END.') 74 | 75 | 76 | return (p_value, (p_value >= 0.01)) 77 | else: 78 | return (-1.0, False) 79 | 80 | @staticmethod 81 | def berlekamp_massey_algorithm(block_data): 82 | """ 83 | An implementation of the Berlekamp Massey Algorithm. Taken from Wikipedia [1] 84 | [1] - https://en.wikipedia.org/wiki/Berlekamp-Massey_algorithm 85 | The Berlekamp–Massey algorithm is an algorithm that will find the shortest linear feedback shift register (LFSR) 86 | for a given binary output sequence. The algorithm will also find the minimal polynomial of a linearly recurrent 87 | sequence in an arbitrary field. The field requirement means that the Berlekamp–Massey algorithm requires all 88 | non-zero elements to have a multiplicative inverse. 89 | :param block_data: 90 | :return: 91 | """ 92 | n = len(block_data) 93 | c = zeros(n) 94 | b = zeros(n) 95 | c[0], b[0] = 1, 1 96 | l, m, i = 0, -1, 0 97 | int_data = [int(el) for el in block_data] 98 | while i < n: 99 | v = int_data[(i - l):i] 100 | v = v[::-1] 101 | cc = c[1:l + 1] 102 | d = (int_data[i] + dot(v, cc)) % 2 103 | if d == 1: 104 | temp = copy(c) 105 | p = zeros(n) 106 | for j in range(0, l): 107 | if b[j] == 1: 108 | p[j + i - m] = 1 109 | c = (c + p) % 2 110 | if l <= 0.5 * i: 111 | l = i + 1 - l 112 | m = i 113 | b = temp 114 | i += 1 115 | return l -------------------------------------------------------------------------------- /CumulativeSum.py: -------------------------------------------------------------------------------- 1 | from numpy import abs as abs 2 | from numpy import array as array 3 | from numpy import floor as floor 4 | from numpy import max as max 5 | from numpy import sqrt as sqrt 6 | from numpy import sum as sum 7 | from numpy import zeros as zeros 8 | from scipy.stats import norm as norm 9 | 10 | class CumulativeSums: 11 | 12 | @staticmethod 13 | def cumulative_sums_test(binary_data:str, mode=0, verbose=False): 14 | """ 15 | from the NIST documentation http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 16 | 17 | The focus of this test is the maximal excursion (from zero) of the random walk defined by the cumulative sum of 18 | adjusted (-1, +1) digits in the sequence. The purpose of the test is to determine whether the cumulative sum of 19 | the partial sequences occurring in the tested sequence is too large or too small relative to the expected 20 | behavior of that cumulative sum for random sequences. This cumulative sum may be considered as a random walk. 21 | For a random sequence, the excursions of the random walk should be near zero. For certain types of non-random 22 | sequences, the excursions of this random walk from zero will be large. 23 | 24 | :param binary_data: a binary string 25 | :param mode A switch for applying the test either forward through the input sequence (mode = 0) 26 | or backward through the sequence (mode = 1). 27 | :param verbose True to display the debug messgae, False to turn off debug message 28 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 29 | 30 | """ 31 | 32 | length_of_binary_data = len(binary_data) 33 | counts = zeros(length_of_binary_data) 34 | 35 | # Determine whether forward or backward data 36 | if not mode == 0: 37 | binary_data = binary_data[::-1] 38 | 39 | counter = 0 40 | for char in binary_data: 41 | sub = 1 42 | if char == '0': 43 | sub = -1 44 | if counter > 0: 45 | counts[counter] = counts[counter -1] + sub 46 | else: 47 | counts[counter] = sub 48 | 49 | counter += 1 50 | # Compute the test statistic z =max1≤k≤n|Sk|, where max1≤k≤n|Sk| is the largest of the 51 | # absolute values of the partial sums Sk. 52 | abs_max = max(abs(counts)) 53 | 54 | start = int(floor(0.25 * floor(-length_of_binary_data / abs_max + 1))) 55 | end = int(floor(0.25 * floor(length_of_binary_data / abs_max - 1))) 56 | 57 | terms_one = [] 58 | for k in range(start, end + 1): 59 | sub = norm.cdf((4 * k - 1) * abs_max / sqrt(length_of_binary_data)) 60 | terms_one.append(norm.cdf((4 * k + 1) * abs_max / sqrt(length_of_binary_data)) - sub) 61 | 62 | start = int(floor(0.25 * floor(-length_of_binary_data / abs_max - 3))) 63 | end = int(floor(0.25 * floor(length_of_binary_data / abs_max) - 1)) 64 | 65 | terms_two = [] 66 | for k in range(start, end + 1): 67 | sub = norm.cdf((4 * k + 1) * abs_max / sqrt(length_of_binary_data)) 68 | terms_two.append(norm.cdf((4 * k + 3) * abs_max / sqrt(length_of_binary_data)) - sub) 69 | 70 | p_value = 1.0 - sum(array(terms_one)) 71 | p_value += sum(array(terms_two)) 72 | 73 | if verbose: 74 | print('Cumulative Sums Test DEBUG BEGIN:') 75 | print("\tLength of input:\t", length_of_binary_data) 76 | print('\tMode:\t\t\t\t', mode) 77 | print('\tValue of z:\t\t\t', abs_max) 78 | print('\tP-Value:\t\t\t', p_value) 79 | print('DEBUG END.') 80 | 81 | return (p_value, (p_value >= 0.01)) -------------------------------------------------------------------------------- /FrequencyTest.py: -------------------------------------------------------------------------------- 1 | from math import fabs as fabs 2 | from math import floor as floor 3 | from math import sqrt as sqrt 4 | from scipy.special import erfc as erfc 5 | from scipy.special import gammaincc as gammaincc 6 | 7 | class FrequencyTest: 8 | 9 | @staticmethod 10 | def monobit_test(binary_data:str, verbose=False): 11 | """ 12 | The focus of the test is the proportion of zeroes and ones for the entire sequence. 13 | The purpose of this test is to determine whether the number of ones and zeros in a sequence are approximately 14 | the same as would be expected for a truly random sequence. The test assesses the closeness of the fraction of 15 | ones to 陆, that is, the number of ones and zeroes in a sequence should be about the same. 16 | All subsequent tests depend on the passing of this test. 17 | 18 | if p_value < 0.01, then conclude that the sequence is non-random (return False). 19 | Otherwise, conclude that the the sequence is random (return True). 20 | 21 | :param binary_data The seuqnce of bit being tested 22 | :param verbose True to display the debug messgae, False to turn off debug message 23 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 24 | 25 | """ 26 | 27 | length_of_bit_string = len(binary_data) 28 | 29 | # Variable for S(n) 30 | count = 0 31 | # Iterate each bit in the string and compute for S(n) 32 | for bit in binary_data: 33 | if bit == '0': 34 | # If bit is 0, then -1 from the S(n) 35 | count -= 1 36 | elif bit == '1': 37 | # If bit is 1, then +1 to the S(n) 38 | count += 1 39 | 40 | # Compute the test statistic 41 | sObs = count / sqrt(length_of_bit_string) 42 | 43 | # Compute p-Value 44 | p_value = erfc(fabs(sObs) / sqrt(2)) 45 | 46 | if verbose: 47 | print('Frequency Test (Monobit Test) DEBUG BEGIN:') 48 | print("\tLength of input:\t", length_of_bit_string) 49 | print('\t# of \'0\':\t\t\t', binary_data.count('0')) 50 | print('\t# of \'1\':\t\t\t', binary_data.count('1')) 51 | print('\tS(n):\t\t\t\t', count) 52 | print('\tsObs:\t\t\t\t', sObs) 53 | print('\tf:\t\t\t\t\t',fabs(sObs) / sqrt(2)) 54 | print('\tP-Value:\t\t\t', p_value) 55 | print('DEBUG END.') 56 | 57 | # return a p_value and randomness result 58 | return (p_value, (p_value >= 0.01)) 59 | 60 | @staticmethod 61 | def block_frequency(binary_data:str, block_size=128, verbose=False): 62 | """ 63 | The focus of the test is the proportion of ones within M-bit blocks. 64 | The purpose of this test is to determine whether the frequency of ones in an M-bit block is approximately M/2, 65 | as would be expected under an assumption of randomness. 66 | For block size M=1, this test degenerates to test 1, the Frequency (Monobit) test. 67 | 68 | :param binary_data: The length of each block 69 | :param block_size: The seuqnce of bit being tested 70 | :param verbose True to display the debug messgae, False to turn off debug message 71 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 72 | """ 73 | 74 | length_of_bit_string = len(binary_data) 75 | 76 | 77 | if length_of_bit_string < block_size: 78 | block_size = length_of_bit_string 79 | 80 | # Compute the number of blocks based on the input given. Discard the remainder 81 | number_of_blocks = floor(length_of_bit_string / block_size) 82 | 83 | if number_of_blocks == 1: 84 | # For block size M=1, this test degenerates to test 1, the Frequency (Monobit) test. 85 | return FrequencyTest.monobit_test(binary_data[0:block_size]) 86 | 87 | # Initialized variables 88 | block_start = 0 89 | block_end = block_size 90 | proportion_sum = 0.0 91 | 92 | # Create a for loop to process each block 93 | for counter in range(number_of_blocks): 94 | # Partition the input sequence and get the data for block 95 | block_data = binary_data[block_start:block_end] 96 | 97 | # Determine the proportion 蟺i of ones in each M-bit 98 | one_count = 0 99 | for bit in block_data: 100 | if bit == '1': 101 | one_count += 1 102 | # compute π 103 | pi = one_count / block_size 104 | 105 | # Compute Σ(πi -½)^2. 106 | proportion_sum += pow(pi - 0.5, 2.0) 107 | 108 | # Next Block 109 | block_start += block_size 110 | block_end += block_size 111 | 112 | # Compute 4M Σ(πi -½)^2. 113 | result = 4.0 * block_size * proportion_sum 114 | 115 | # Compute P-Value 116 | p_value = gammaincc(number_of_blocks / 2, result / 2) 117 | 118 | if verbose: 119 | print('Frequency Test (Block Frequency Test) DEBUG BEGIN:') 120 | print("\tLength of input:\t", length_of_bit_string) 121 | print("\tSize of Block:\t\t", block_size) 122 | print('\tNumber of Blocks:\t', number_of_blocks) 123 | print('\tCHI Squared:\t\t', result) 124 | print('\t1st:\t\t\t\t', number_of_blocks / 2) 125 | print('\t2nd:\t\t\t\t', result / 2) 126 | print('\tP-Value:\t\t\t', p_value) 127 | print('DEBUG END.') 128 | 129 | return (p_value, (p_value >= 0.01)) -------------------------------------------------------------------------------- /GUI.py: -------------------------------------------------------------------------------- 1 | from tkinter import ttk 2 | from tkinter import Canvas 3 | from tkinter import DISABLED # Keep for Entry state if needed, though ttk uses 'readonly' 4 | from tkinter import Frame 5 | from tkinter import IntVar 6 | from tkinter import LabelFrame # ttk.LabelFrame is available if we want to switch this too 7 | from tkinter import Scrollbar 8 | from tkinter import StringVar 9 | 10 | class CustomButton: 11 | 12 | def __init__(self, master, title, x_coor, y_coor, width, action=None): 13 | # ttk.Button does not have a config method for font in the same way. 14 | # Style objects are preferred for ttk widgets. For simplicity here, let's assume default font or 15 | # handle styling at a higher level if necessary (e.g. via ttk.Style). 16 | self.button = ttk.Button(master, text=title, command=action) 17 | self.button.place(x=x_coor, y=y_coor, width=width, height=25) 18 | 19 | def config(self, **kwargs): 20 | if 'state' in kwargs: 21 | button_state = kwargs.pop('state') 22 | if button_state == 'disabled' or button_state == DISABLED: 23 | self.button.state(['disabled']) 24 | elif button_state == 'normal': 25 | self.button.state(['!disabled']) 26 | 27 | # Pass any other configuration options to the underlying ttk.Button 28 | if kwargs: 29 | self.button.configure(**kwargs) 30 | 31 | class Input: 32 | 33 | def __init__(self, master, title, x_coor, y_coor, has_button=False, action=None, button_xcoor=1050, button_width=180): 34 | # Setup Labels 35 | label = ttk.Label(master, text=title, font=("Calibri", 12)) 36 | label.place(x=x_coor, y=y_coor, height=25) 37 | 38 | self.__data = StringVar() 39 | self.__data_entry = ttk.Entry(master, textvariable=self.__data, font=("Calibri", 10)) 40 | self.__data_entry.place(x=150, y=y_coor, width=900, height=25) 41 | 42 | if has_button: 43 | self.__data_entry.config(state='readonly') # ttk.Entry uses 'readonly' for disabled look but selectable text 44 | button_title = 'Select ' + title 45 | # Using ttk.Button for consistency 46 | button = ttk.Button(master, text=button_title, command=action) 47 | button.place(x=button_xcoor, y=y_coor, width=180, height=25) 48 | 49 | def set_data(self, value): 50 | self.__data.set(value) 51 | 52 | def get_data(self): 53 | return self.__data.get() 54 | 55 | def change_state(self, state): # state can be 'normal', 'disabled', 'readonly' 56 | self.__data_entry.config(state=state) 57 | 58 | class LabelTag: 59 | 60 | def __init__(self, master, title, x_coor, y_coor, width, font_size=18, border=0, relief='flat'): 61 | # ttk.Label uses 'borderwidth' and 'relief' options directly in constructor. 62 | # Font can be set directly too. 63 | label = ttk.Label(master, text=title, borderwidth=border, relief=relief, font=("Calibri", font_size)) 64 | label.place(x=x_coor, y=y_coor, width=width, height=25) 65 | 66 | class Options: # This class seems unused in Main.py based on current context, but updating it. 67 | 68 | def __init__(self, master, title, data, x_coor, y_coor, width): 69 | self.__selected = StringVar() 70 | # Ensure data is not empty and contains valid strings for OptionMenu 71 | if not data or not all(isinstance(item, str) for item in data): 72 | data = ["Default"] # Provide a default if data is invalid/empty 73 | 74 | label = ttk.Label(master, text=title, font=("Calibri", 12)) 75 | label.place(x=x_coor, y=y_coor, height=25, width=100) 76 | 77 | self.__selected.set(data[0]) 78 | # ttk.OptionMenu constructor is slightly different: master, variable, default_value, *values 79 | self.__option = ttk.OptionMenu(master, self.__selected, data[0], *data) 80 | self.__option.place(x=150, y=y_coor, height=25, width=width) 81 | 82 | def set_selected(self, data): 83 | self.__selected.set(data) 84 | 85 | def get_selected(self): 86 | return self.__selected.get() 87 | 88 | def update_data(self, data_list): # Assuming data_list is a list of strings 89 | # ttk.OptionMenu doesn't have direct option_clear/add. Recreate or set new menu. 90 | # For simplicity, if this method is crucial, it might need a more complex handling 91 | # such as destroying and recreating the OptionMenu or directly manipulating its internal menu. 92 | # A common approach is to update the StringVar and the list of options it points to, 93 | # then potentially re-initialize the OptionMenu if direct update isn't supported. 94 | # Given this class is likely unused, this simplification is acceptable for now. 95 | menu = self.__option["menu"] 96 | menu.delete(0, "end") 97 | if not data_list or not all(isinstance(item, str) for item in data_list): 98 | data_list = ["Default"] 99 | 100 | for string in data_list: 101 | menu.add_command(label=string, command=lambda value=string: self.__selected.set(value)) 102 | self.__selected.set(data_list[0]) 103 | 104 | 105 | class TestItem: 106 | 107 | def __init__(self, master, title, x_coor, y_coor, serial=False, p_value_x_coor=365, p_value_width=500, result_x_coor=870, result_width=350, font_size=12, two_columns=False): 108 | self.__chb_var = IntVar() 109 | self.__p_value = StringVar() 110 | self.__result = StringVar() 111 | self.__p_value_02 = StringVar() 112 | self.__result_02 = StringVar() 113 | 114 | # ttk.Checkbutton font is typically managed by style. 115 | # For direct font setting, it's less straightforward than tkinter.Checkbutton. 116 | # Using style is preferred. For now, let's omit direct font config on Checkbutton. 117 | checkbox = ttk.Checkbutton(master, text=title, variable=self.__chb_var) 118 | checkbox.place(x=x_coor, y=y_coor) # Consider adjusting height/width if needed 119 | 120 | p_value_entry = ttk.Entry(master, textvariable=self.__p_value, font=("Calibri", font_size)) 121 | p_value_entry.config(state='readonly') 122 | p_value_entry.place(x=p_value_x_coor, y=y_coor, width=p_value_width, height=25) 123 | 124 | result_entry = ttk.Entry(master, textvariable=self.__result, font=("Calibri", font_size)) 125 | result_entry.config(state='readonly') 126 | result_entry.place(x=result_x_coor, y=y_coor, width=result_width, height=25) 127 | 128 | if serial and two_columns: 129 | p_value_entry_02 = ttk.Entry(master, textvariable=self.__p_value_02, font=("Calibri", font_size)) 130 | p_value_entry_02.config(state='readonly') 131 | p_value_entry_02.place(x=875, y=y_coor, width=235, height=25) 132 | 133 | result_entry_02 = ttk.Entry(master, textvariable=self.__result_02, font=("Calibri", font_size)) 134 | result_entry_02.config(state='readonly') 135 | result_entry_02.place(x=1115, y=y_coor, width=110, height=25) 136 | elif serial and not two_columns: # This case seems specific for Serial test layout 137 | p_value_entry_02 = ttk.Entry(master, textvariable=self.__p_value_02, font=("Calibri", font_size)) 138 | p_value_entry_02.config(state='readonly') 139 | p_value_entry_02.place(x=p_value_x_coor, y=y_coor+25, width=p_value_width, height=25) # Adjusted y 140 | 141 | result_entry_02 = ttk.Entry(master, textvariable=self.__result_02, font=("Calibri", font_size)) 142 | result_entry_02.config(state='readonly') 143 | result_entry_02.place(x=result_x_coor, y=y_coor+25, width=result_width, height=25) # Adjusted y 144 | 145 | def get_check_box_value(self): 146 | return self.__chb_var.get() 147 | 148 | def set_check_box_value(self, value): 149 | self.__chb_var.set(value) 150 | 151 | def set_p_value(self, value): 152 | self.__p_value.set(value) 153 | 154 | def set_result_value(self, value): 155 | self.__result.set(value) 156 | 157 | def set_p_value_02(self, value): 158 | self.__p_value_02.set(value) 159 | 160 | def set_result_value_02(self, value): 161 | self.__result_02.set(value) 162 | 163 | def set_values(self, values): 164 | self.__p_value.set(values[0]) 165 | self.__result.set(self.__get_result_string(values[1])) 166 | 167 | def set_p_2_values(self, values): 168 | self.__p_value_02(values[0]) 169 | self.__result_02(self.__get_result_string(values[1])) 170 | 171 | def reset(self): 172 | self.set_check_box_value(0) 173 | self.set_p_value('') 174 | self.set_result_value('') 175 | self.set_p_value_02('') 176 | self.set_result_value_02('') 177 | 178 | def __get_result_string(self, result): 179 | if result == True: 180 | return 'Random' 181 | else: 182 | return 'Non-Random' 183 | 184 | class RandomExcursionTestItem: 185 | 186 | def __init__(self, master, title, x_coor, y_coor, data, variant=False, font_size=11): 187 | self.__chb_var = IntVar() 188 | self.__state = StringVar() 189 | self.__count = StringVar() 190 | self.__xObs = StringVar() 191 | self.__p_value = StringVar() 192 | self.__result = StringVar() 193 | self.__results = [] # This will store the list of tuples for excursion results 194 | self.__variant = variant 195 | 196 | # ttk.Checkbutton, font styling is less direct. 197 | checkbox = ttk.Checkbutton(master, text=title, variable=self.__chb_var) 198 | checkbox.place(x=x_coor, y=y_coor) 199 | 200 | # LabelTag is already updated to use ttk.Label 201 | state_label = LabelTag(master, 'State', (x_coor + 60), (y_coor + 30), width=100, font_size=font_size, border=2, relief='groove') 202 | 203 | # Ensure data for OptionMenu is valid 204 | if not data or not all(isinstance(item, str) for item in data): 205 | data = ["DefaultState"] # Fallback data 206 | 207 | if variant: 208 | self.__state.set(data[0] if data[0] in ['-9.0', '-8.0', '-7.0', '-6.0', '-5.0', '-4.0', '-3.0', '-2.0', '-1.0', '+1.0', '+2.0', '+3.0', '+4.0', '+5.0', '+6.0', '+7.0', '+8.0', '+9.0'] else data[0]) # Ensure initial value is in list 209 | else: 210 | self.__state.set(data[0] if data[0] in ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4'] else data[0]) # Ensure initial value is in list 211 | 212 | state_option = ttk.OptionMenu(master, self.__state, self.__state.get(), *data) 213 | state_option.place(x=(x_coor + 60), y=(y_coor + 60), height=25, width=100) 214 | self.__state.trace_add("write", self.update) # Use trace_add for newer Tkinter versions 215 | 216 | entry_font = ("Calibri", font_size) 217 | if not variant: 218 | xObs_label = LabelTag(master, 'Chi^2', (x_coor + 165), (y_coor + 30), width=350, font_size=font_size, border=2, relief='groove') 219 | xObs_Entry = ttk.Entry(master, textvariable=self.__xObs, font=entry_font) 220 | xObs_Entry.config(state='readonly') 221 | xObs_Entry.place(x=(x_coor + 165), y=(y_coor + 60), width=350, height=25) 222 | else: 223 | count_label = LabelTag(master, 'Count', (x_coor + 165), (y_coor + 30), width=350, font_size=font_size, border=2, relief='groove') 224 | count_Entry = ttk.Entry(master, textvariable=self.__count, font=entry_font) 225 | count_Entry.config(state='readonly') 226 | count_Entry.place(x=(x_coor + 165), y=(y_coor + 60), width=350, height=25) 227 | 228 | p_value_label = LabelTag(master, 'P-Value', (x_coor + 520), (y_coor + 30), width=350, font_size=font_size, border=2, relief='groove') 229 | p_value_Entry = ttk.Entry(master, textvariable=self.__p_value, font=entry_font) 230 | p_value_Entry.config(state='readonly') 231 | p_value_Entry.place(x=(x_coor + 520), y=(y_coor + 60), width=350, height=25) 232 | 233 | conclusion_label = LabelTag(master, 'Result', (x_coor + 875), (y_coor + 30), width=150, font_size=font_size, border=2, relief='groove') 234 | conclusion_Entry = ttk.Entry(master, textvariable=self.__result, font=entry_font) 235 | conclusion_Entry.config(state='readonly') 236 | conclusion_Entry.place(x=(x_coor + 875), y=(y_coor + 60), width=150, height=25) 237 | 238 | def get_check_box_value(self): 239 | return self.__chb_var.get() 240 | 241 | def set_check_box_value(self, value): 242 | self.__chb_var.set(value) 243 | 244 | def set_results(self, results): 245 | self.__results = results 246 | self.update() 247 | 248 | def update(self, *_): 249 | match = False 250 | for result in self.__results: 251 | if result[0] == self.__state.get(): 252 | if self.__variant: 253 | self.__count.set(result[2]) 254 | else: 255 | self.__xObs.set(result[2]) 256 | 257 | self.__p_value.set(result[3]) 258 | self.__result.set(self.get_result_string(result[4])) 259 | match = True 260 | 261 | if not match: 262 | if self.__variant: 263 | self.__count.set('') 264 | else: 265 | self.__xObs.set('') 266 | 267 | self.__p_value.set('') 268 | self.__result.set('') 269 | 270 | def get_result_string(self, result): 271 | if result == True: 272 | return 'Random' 273 | else: 274 | return 'Non-Random' 275 | 276 | def reset(self): 277 | self.__chb_var.set('0') 278 | if self.__variant: 279 | self.__state.set('-1.0') 280 | self.__count.set('') 281 | else: 282 | self.__state.set('+1') 283 | self.__xObs.set('') 284 | 285 | self.__p_value.set('') 286 | self.__result.set('') 287 | 288 | class ScrollLabelFrame(LabelFrame): 289 | def __init__(self, parent, label): 290 | super().__init__(master=parent, text=label, padx=5, pady=5) 291 | self._canvas = Canvas(self, background="#ffffff") 292 | self.inner_frame = Frame(self._canvas, background="#ffffff") 293 | self._scroll_bar = Scrollbar(self, orient="vertical", command=self._canvas.yview) 294 | self._canvas.configure(yscrollcommand=self._scroll_bar.set) 295 | self._scroll_bar.pack(side="right", fill="y") 296 | self._canvas.pack(side="left", fill="both", expand=True) 297 | self._canvas_window = self._canvas.create_window((4,4), window=self.inner_frame, anchor="nw", #add view port frame to canvas 298 | tags="self.inner_frame") 299 | 300 | self.inner_frame.bind("", self.onFrameConfigure) # bind an event whenever the size of the viewPort frame changes. 301 | self._canvas.bind("", self.onCanvasConfigure) # bind an event whenever the size of the viewPort frame changes. 302 | 303 | self.onFrameConfigure(None) 304 | 305 | def onFrameConfigure(self, event): 306 | '''Reset the scroll region to encompass the inner frame''' 307 | self._canvas.configure(scrollregion=self._canvas.bbox( 308 | "all")) # whenever the size of the frame changes, alter the scroll region respectively. 309 | 310 | def onCanvasConfigure(self, event): 311 | '''Reset the canvas window to encompass inner frame when required''' 312 | canvas_width = event.width 313 | self._canvas.itemconfig(self._canvas_window, width=canvas_width) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2019 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import threading 3 | import numpy as np 4 | from tkinter import * 5 | from tkinter.filedialog import askopenfilename # Removed askopenfilenames 6 | from tkinter.filedialog import asksaveasfile 7 | from tkinter import messagebox 8 | import queue 9 | from tkinter import ttk 10 | 11 | from GUI import CustomButton 12 | from GUI import Input 13 | from GUI import LabelTag 14 | from GUI import RandomExcursionTestItem 15 | from GUI import TestItem 16 | from Tools import Tools 17 | 18 | from ApproximateEntropy import ApproximateEntropy as aet 19 | from Complexity import ComplexityTest as ct 20 | from CumulativeSum import CumulativeSums as cst 21 | from FrequencyTest import FrequencyTest as ft 22 | from Matrix import Matrix as mt 23 | from RandomExcursions import RandomExcursions as ret 24 | from RunTest import RunTest as rt 25 | from Serial import Serial as serial 26 | from Spectral import SpectralTest as st 27 | from TemplateMatching import TemplateMatching as tm 28 | from Universal import Universal as ut 29 | 30 | class Main(Frame): 31 | 32 | def __init__(self, master=None): 33 | 34 | Frame.__init__(self, master=master) 35 | self._master = master 36 | self.init_variables() 37 | self.init_window() 38 | 39 | def init_variables(self): 40 | 41 | self._test_type = ['01. Frequency (Monobit) Test', 42 | '02. Frequency Test within a Block', 43 | '03. Runs Test', 44 | '04. Test for the Longest Run of Ones in a Block', 45 | '05. Binary Matrix Rank Test', 46 | '06. Discrete Fourier Transform (Spectral) Test', 47 | '07. Non-overlapping Template Matching Test', 48 | '08. Overlapping Template Matching Test', 49 | '09. Maurer\'s "Universal Statistical" Test', 50 | '10. Linear Complexity Test', 51 | '11. Serial Test', 52 | '12. Approximate Entropy Test', 53 | '13. Cumulative Sums Test (Forward)', 54 | '14. Cumulative Sums Test (Backward)', 55 | '15. Random Excursions Test', 56 | '16. Random Excursions Variant Test'] 57 | 58 | 59 | self.__test_function = { 60 | 0:ft.monobit_test, 61 | 1:ft.block_frequency, 62 | 2:rt.run_test, 63 | 3:rt.longest_one_block_test, 64 | 4:mt.binary_matrix_rank_text, 65 | 5:st.spectral_test, 66 | 6:tm.non_overlapping_test, 67 | 7:tm.overlapping_patterns, 68 | 8:ut.statistical_test, 69 | 9:ct.linear_complexity_test, 70 | 10:serial.serial_test, 71 | 11:aet.approximate_entropy_test, 72 | 12:cst.cumulative_sums_test, 73 | 13:cst.cumulative_sums_test, 74 | 14:ret.random_excursions_test, 75 | 15:ret.variant_test 76 | } 77 | 78 | self._test_result = [] 79 | self._test_string = [] 80 | self._latest_results = [] 81 | self._ui_queue = queue.Queue() 82 | self.__is_binary_file = False # Restored 83 | self.__is_data_file = False # Restored 84 | self.__file_name = "" # Restored for select_binary/data_file 85 | 86 | def init_window(self): 87 | frame_title = 'A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications' 88 | title_label = LabelTag(self.master, frame_title, 0, 5, 1260) 89 | 90 | # Setup LabelFrame for Input - Reverted to original fixed height 91 | input_label_frame = LabelFrame(self.master, text="Input Data") 92 | input_label_frame.config(font=("Calibri", 14)) 93 | input_label_frame.propagate(0) # Prevent resizing by children 94 | input_label_frame.place(x=20, y=30, width=1260, height=125) # Original height 95 | 96 | # Restore original Input widgets 97 | self.__binary_input = Input(input_label_frame, 'Binary Data', 10, 5) 98 | self.__binary_data_file_input = Input(input_label_frame, 'Binary Data File', 10, 35, True, 99 | self.select_binary_file, button_xcoor=1060, button_width=160) 100 | self.__string_data_file_input = Input(input_label_frame, 'String Data File', 10, 65, True, 101 | self.select_data_file, button_xcoor=1060, button_width=160) 102 | 103 | # Setup LabelFrame for Randomness Test - y position reverted 104 | self._stest_selection_label_frame = LabelFrame(self.master, text="Randomness Testing", padx=5, pady=5) 105 | self._stest_selection_label_frame.config(font=("Calibri", 14)) 106 | self._stest_selection_label_frame.place(x=20, y=155, width=1260, height=450) # Original y and height 107 | 108 | test_type_label_01 = LabelTag(self._stest_selection_label_frame, 'Test Type', 10, 5, 250, 11, border=2, 109 | relief="groove") 110 | p_value_label_01 = LabelTag(self._stest_selection_label_frame, 'P-Value', 265, 5, 235, 11, border=2, 111 | relief="groove") 112 | result_label_01 = LabelTag(self._stest_selection_label_frame, 'Result', 505, 5, 110, 11, border=2, 113 | relief="groove") 114 | 115 | test_type_label_02 = LabelTag(self._stest_selection_label_frame, 'Test Type', 620, 5, 250, 11, border=2, 116 | relief="groove") 117 | p_value_label_02 = LabelTag(self._stest_selection_label_frame, 'P-Value', 875, 5, 235, 11, border=2, 118 | relief="groove") 119 | result_label_02 = LabelTag(self._stest_selection_label_frame, 'Result', 1115, 5, 110, 11, border=2, 120 | relief="groove") 121 | 122 | self._test = [] 123 | 124 | self._monobit = TestItem(self._stest_selection_label_frame, self._test_type[0], 10, 35, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 125 | self._test.append(self._monobit) 126 | 127 | self._block = TestItem(self._stest_selection_label_frame, self._test_type[1], 620, 35, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11) 128 | self._test.append(self._block) 129 | 130 | self._run = TestItem(self._stest_selection_label_frame, self._test_type[2], 10, 60, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 131 | self._test.append(self._run) 132 | 133 | self._long_run = TestItem(self._stest_selection_label_frame, self._test_type[3], 620, 60, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11) 134 | self._test.append(self._long_run) 135 | 136 | self._rank = TestItem(self._stest_selection_label_frame, self._test_type[4], 10, 85, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 137 | self._test.append(self._rank) 138 | 139 | self._spectral = TestItem(self._stest_selection_label_frame, self._test_type[5], 620, 85, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11) 140 | self._test.append(self._spectral) 141 | 142 | self._non_overlappong = TestItem(self._stest_selection_label_frame, self._test_type[6], 10, 110, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 143 | self._test.append(self._non_overlappong) 144 | 145 | self._overlapping = TestItem(self._stest_selection_label_frame, self._test_type[7], 620, 110, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11) 146 | self._test.append(self._overlapping) 147 | 148 | self._universal = TestItem(self._stest_selection_label_frame, self._test_type[8], 10, 135, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 149 | self._test.append(self._universal) 150 | 151 | self._linear = TestItem(self._stest_selection_label_frame, self._test_type[9], 620, 135, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11) 152 | self._test.append(self._linear) 153 | 154 | self._serial = TestItem(self._stest_selection_label_frame, self._test_type[10], 10, 160, serial=True, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11, two_columns=True) 155 | self._test.append(self._serial) 156 | 157 | self._entropy = TestItem(self._stest_selection_label_frame, self._test_type[11], 10, 185, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 158 | self._test.append(self._entropy) 159 | 160 | self._cusum_f = TestItem(self._stest_selection_label_frame, self._test_type[12], 10, 210, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11) 161 | self._test.append(self._cusum_f) 162 | 163 | self._cusum_r = TestItem(self._stest_selection_label_frame, self._test_type[13], 620, 210, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11) 164 | self._test.append(self._cusum_r) 165 | 166 | self._excursion = RandomExcursionTestItem(self._stest_selection_label_frame, self._test_type[14], 10, 235, 167 | ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4'], font_size=11) 168 | self._test.append(self._excursion) 169 | 170 | self._variant = RandomExcursionTestItem(self._stest_selection_label_frame, self._test_type[15], 10, 325, 171 | ['-9.0', '-8.0', '-7.0', '-6.0', '-5.0', '-4.0', '-3.0', '-2.0', 172 | '-1.0', 173 | '+1.0', '+2.0', '+3.0', '+4.0', '+5.0', '+6.0', '+7.0', '+8.0', 174 | '+9.0'], variant=True, font_size=11) 175 | self._test.append(self._variant) 176 | 177 | self._result_field = [ 178 | self._monobit, 179 | self._block, 180 | self._run, 181 | self._long_run, 182 | self._rank, 183 | self._spectral, 184 | self._non_overlappong, 185 | self._overlapping, 186 | self._universal, 187 | self._linear, 188 | self._serial, 189 | self._entropy, 190 | self._cusum_f, 191 | self._cusum_r 192 | ] 193 | 194 | select_all_button = CustomButton(self.master, 'Select All Test', 20, 615, 100, self.select_all) 195 | deselect_all_button = CustomButton(self.master, 'De-Select All Test', 125, 615, 150, self.deselect_all) 196 | self.execute_button = CustomButton(self.master, 'Execute Test', 280, 615, 100, self.execute) 197 | save_button = CustomButton(self.master, 'Save as Text File', 385, 615, 100, self.save_result_to_file) 198 | reset_button = CustomButton(self.master, 'Reset', 490, 615, 100, self.reset) 199 | exit_button = CustomButton(self.master, 'Exit Program', 595, 615, 100, self.exit) # This was 'exit' variable, changed to 'exit_button' for clarity 200 | 201 | # Frame for status elements - Adjusted y position 202 | status_frame = ttk.Frame(self.master) 203 | status_frame.place(x=20, y=635, width=1260, height=40) 204 | 205 | self.status_label = ttk.Label(status_frame, text="", font=("Calibri", 10), anchor="w") 206 | self.status_label.pack(side=TOP, fill=X, padx=5, pady=(0,2)) # pady to give a little space before progressbar 207 | 208 | self.progress_bar = ttk.Progressbar(status_frame, orient=HORIZONTAL, length=1260, mode='determinate') 209 | self.progress_bar.pack(side=BOTTOM, fill=X, padx=5, pady=(2,0)) 210 | self.progress_bar['value'] = 0 # Ensure initial value is 0 211 | 212 | def select_binary_file(self): 213 | """ 214 | Called tkinter.askopenfilename to give user an interface to select the binary input file and perform the following: 215 | 1. Clear Binary Data Input Field. (The textfield) 216 | 2. Set selected file name to Binary Data File Input Field. 217 | 3. Clear String Data file input field. 218 | 219 | :return: None 220 | """ 221 | print('Select Binary File') 222 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Binary Input File.") 223 | if self.__file_name: 224 | self.__binary_input.set_data('') 225 | self.__binary_data_file_input.set_data(self.__file_name) 226 | self.__string_data_file_input.set_data('') 227 | self.__is_binary_file = True 228 | self.__is_data_file = False 229 | 230 | def select_data_file(self): 231 | """ 232 | Called tkinter.askopenfilename to give user an interface to select the string input file and perform the following: 233 | 1. Clear Binary Data Input Field. (The textfield) 234 | 2. Clear Binary Data File Input Field. 235 | 3. Set selected file name to String Data File Input Field. 236 | 237 | :return: None 238 | """ 239 | print('Select Data File') 240 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Data File.") 241 | if self.__file_name: 242 | self.__binary_input.set_data('') 243 | self.__binary_data_file_input.set_data('') 244 | self.__string_data_file_input.set_data(self.__file_name) 245 | self.__is_binary_file = False 246 | self.__is_data_file = True 247 | 248 | def select_all(self): 249 | """ 250 | Select all test type displayed in the GUI. (Check all checkbox) 251 | 252 | :return: None 253 | """ 254 | print('Select All Test') 255 | for item in self._test: 256 | item.set_check_box_value(1) 257 | 258 | def deselect_all(self): 259 | """ 260 | Unchecked all checkbox 261 | 262 | :return: None 263 | """ 264 | print('Deselect All Test') 265 | for item in self._test: 266 | item.set_check_box_value(0) 267 | 268 | def execute(self): 269 | """ 270 | Execute the tests and display the result in the GUI 271 | 272 | :return: None 273 | """ 274 | print('Execute') 275 | 276 | # Input validation and data preparation (reverted logic) 277 | if len(self.__binary_input.get_data().strip().rstrip()) == 0 and \ 278 | len(self.__binary_data_file_input.get_data().strip().rstrip()) == 0 and \ 279 | len(self.__string_data_file_input.get_data().strip().rstrip()) == 0: 280 | messagebox.showwarning("Warning", 'You must input the binary data or read the data from from the file.') 281 | return None 282 | elif (len(self.__binary_input.get_data().strip().rstrip()) > 0 and \ 283 | (len(self.__binary_data_file_input.get_data().strip().rstrip()) > 0 or \ 284 | len(self.__string_data_file_input.get_data().strip().rstrip()) > 0)) or \ 285 | (len(self.__binary_data_file_input.get_data().strip().rstrip()) > 0 and \ 286 | len(self.__string_data_file_input.get_data().strip().rstrip()) > 0): 287 | messagebox.showwarning("Warning", 'You can only use one input method at a time: direct binary, binary data file, or string data file.') 288 | return None 289 | 290 | input_data_sequences = [] # Will hold the single binary string to test 291 | 292 | if not len(self.__binary_input.get_data()) == 0: 293 | input_data_sequences.append(self.__binary_input.get_data().strip()) 294 | status_message = "Processing direct binary input..." 295 | elif self.__is_binary_file and self.__file_name: # Binary Data File 296 | try: 297 | with open(self.__file_name, 'r') as handle: 298 | temp = [data.strip().rstrip() for data in handle] 299 | test_data = ''.join(temp)[:1000000] 300 | if not all(c in '01' for c in test_data): 301 | messagebox.showerror("Error", f"File {self.__file_name} contains non-binary characters.") 302 | return None 303 | if not test_data: 304 | messagebox.showwarning("Warning", f"Binary data file '{os.path.basename(self.__file_name)}' is empty or resulted in empty data.") 305 | return None 306 | input_data_sequences.append(test_data) 307 | status_message = f"Processing binary data file: {os.path.basename(self.__file_name)}..." 308 | except Exception as e: 309 | messagebox.showerror("Error reading binary data file", f"Could not read file {self.__file_name}: {e}") 310 | return None 311 | elif self.__is_data_file and self.__file_name: # String Data File 312 | processed_binary_data_list = [] 313 | try: 314 | with open(self.__file_name, 'r') as handle: 315 | for item in handle: 316 | item_stripped = item.strip() 317 | if not item_stripped: continue 318 | if item_stripped.startswith('http://') or item_stripped.startswith('https://'): 319 | url_content = Tools.url_to_binary(item_stripped) 320 | processed_binary_data_list.append(Tools.string_to_binary(url_content)) 321 | else: 322 | processed_binary_data_list.append(Tools.string_to_binary(item_stripped)) 323 | test_data = "".join(processed_binary_data_list) 324 | if not test_data: 325 | messagebox.showwarning("Warning", f"String data file '{os.path.basename(self.__file_name)}' resulted in empty binary data.") 326 | return None 327 | # Basic validation for binary string (already done by Tools.string_to_binary generally) 328 | input_data_sequences.append(test_data) 329 | status_message = f"Processing string data file: {os.path.basename(self.__file_name)}..." 330 | except Exception as e: 331 | messagebox.showerror("Error processing string data file", f"Could not process file {self.__file_name}: {e}") 332 | return None 333 | 334 | if not input_data_sequences or not input_data_sequences[0]: 335 | messagebox.showwarning("Warning", "Input data is empty or could not be processed.") 336 | return None 337 | 338 | test_data_to_process = input_data_sequences[0] # Worker expects a single string 339 | 340 | # Length validation (re-add if needed, using self._test_min_lengths) 341 | # selected_test_indices = self._get_selected_test_indices() # This method was removed, need to inline or re-add 342 | # if not selected_test_indices: 343 | # messagebox.showwarning("Warning", "No tests selected.") 344 | # return None 345 | # validation_result = self._validate_input_length(len(test_data_to_process), selected_test_indices) 346 | # if isinstance(validation_result, str): 347 | # messagebox.showwarning("Input Data Warning", validation_result) 348 | # return None 349 | 350 | try: 351 | self.execute_button.config(state=DISABLED) # This should use the CustomButton's config 352 | self.status_label.config(text=status_message) # Use the status_message set above 353 | self.progress_bar['value'] = 0 354 | self.progress_bar['maximum'] = 100 # Default max, will be updated by 'start' msg from worker 355 | 356 | self._latest_results = [] # Clear previous results 357 | 358 | # Pass the determined test_data_to_process (list of paths or single string) to the worker 359 | worker_thread = threading.Thread(target=self._execute_tests_worker, args=(test_data_to_process,)) 360 | worker_thread.start() 361 | 362 | self.master.after(100, self._process_ui_queue) 363 | except Exception as e: 364 | messagebox.showerror("Error", str(e)) 365 | print(e) 366 | 367 | def _execute_tests_worker(self, test_data_input): 368 | """ 369 | Worker method to execute randomness tests in a separate thread. 370 | Stores results in self._latest_results and appends to self._test_result (for single runs). 371 | Handles batch processing by iterating through files. (Reverted: only single string input) 372 | """ 373 | # Reverted: This worker now only processes a single string. 374 | # selected_test_indices = self._get_selected_test_indices() # This method is removed 375 | # num_selected_tests = len(selected_test_indices) 376 | 377 | num_selected_tests = sum(1 for item in self._test if item.get_check_box_value() == 1) 378 | 379 | if num_selected_tests == 0: 380 | self._ui_queue.put({'type': 'error', 'message': 'No tests selected.'}) 381 | return 382 | 383 | self._ui_queue.put({'type': 'start', 'total_tests': num_selected_tests}) # Removed mode 384 | try: 385 | current_run_results = [() for _ in range(len(self._test_type))] 386 | completed_count = 0 387 | test_idx = 0 # Reverted from iterating selected_test_indices 388 | for item in self._test: # Reverted 389 | if item.get_check_box_value() == 1: 390 | if test_idx == 13: 391 | current_run_results[test_idx] = self.__test_function[test_idx](test_data_input, mode=1) 392 | else: 393 | current_run_results[test_idx] = self.__test_function[test_idx](test_data_input) 394 | completed_count += 1 395 | self._ui_queue.put({ 396 | 'type': 'progress', 397 | 'test_name': self._test_type[test_idx], 398 | 'completed_tests': completed_count, 399 | 'total_tests_in_current_run': num_selected_tests 400 | }) 401 | test_idx += 1 402 | 403 | self._latest_results = current_run_results 404 | self._test_result.insert(0, self._latest_results) 405 | self._ui_queue.put({'type': 'complete', 'results': self._latest_results}) # Removed mode 406 | print("Test run completed in worker. Results sent to UI queue.") 407 | except Exception as e: 408 | print(f"Error in worker thread: {e}") 409 | self._ui_queue.put({'type': 'error', 'message': str(e)}) 410 | 411 | def _process_ui_queue(self): 412 | """ 413 | Process messages from the UI queue to update the GUI. 414 | """ 415 | try: 416 | while True: # Process all messages currently in the queue 417 | msg = self._ui_queue.get_nowait() 418 | 419 | if msg['type'] == 'start': 420 | # Removed mode handling, progress bar max is always total_tests 421 | self.progress_bar['maximum'] = msg['total_tests'] if msg.get('total_tests', 0) > 0 else 100 422 | self.progress_bar['value'] = 0 423 | self.status_label.config(text=f"Test run started. Total selected tests: {msg['total_tests']}.") 424 | self.write_results([]) 425 | 426 | elif msg['type'] == 'progress': 427 | self.progress_bar['value'] = msg['completed_tests'] 428 | self.status_label.config(text=f"Running test {msg['completed_tests']}/{msg['total_tests_in_current_run']}: {msg['test_name']}...") 429 | 430 | elif msg['type'] == 'complete': # Reverted: only one type of complete 431 | self.status_label.config(text="Test run completed successfully.") 432 | self.write_results(msg['results']) 433 | messagebox.showinfo("Execute", "Test Run Complete.") 434 | self.progress_bar['value'] = 0 435 | self.execute_button.config(state=NORMAL) 436 | return 437 | 438 | elif msg['type'] == 'error': 439 | self.status_label.config(text=f"Error: {msg['message']}") 440 | messagebox.showerror("Error", msg['message']) 441 | self.progress_bar['value'] = 0 442 | self.execute_button.config(state=NORMAL) 443 | self._current_processing_mode = None 444 | return 445 | 446 | except queue.Empty: 447 | # If queue is empty, do nothing and continue polling 448 | pass 449 | except Exception as e: 450 | # Handle any other unexpected errors during UI update 451 | print(f"Error processing UI queue: {e}") 452 | self.status_label.config(text="Error updating UI.") 453 | self.execute_button.config(state=NORMAL) # Ensure button is re-enabled 454 | return # Stop polling on unexpected error 455 | 456 | self.master.after(100, self._process_ui_queue) # Continue polling 457 | 458 | def write_results(self, results): 459 | """ 460 | Write the result in the GUI 461 | 462 | :param results: result of the randomness test 463 | :return: None 464 | """ 465 | count = 0 466 | for result in results: 467 | if len(result) == 0: 468 | if count == 10: 469 | self._result_field[count].set_p_value('') 470 | self._result_field[count].set_result_value('') 471 | self._result_field[count].set_p_value_02('') 472 | self._result_field[count].set_result_value_02('') 473 | elif count == 14: 474 | self._excursion.set_results('') 475 | elif count == 15: 476 | self._variant.set_results('') 477 | else: 478 | self._result_field[count].set_p_value('') 479 | self._result_field[count].set_result_value('') 480 | else: 481 | if count == 10: 482 | self._result_field[count].set_p_value(result[0][0]) 483 | self._result_field[count].set_result_value(self.get_result_string(result[0][1])) 484 | self._result_field[count].set_p_value_02(result[1][0]) 485 | self._result_field[count].set_result_value_02(self.get_result_string(result[1][1])) 486 | elif count == 14: 487 | print(result) 488 | self._excursion.set_results(result) 489 | elif count == 15: 490 | print(result) 491 | self._variant.set_results(result) 492 | else: 493 | self._result_field[count].set_p_value(result[0]) 494 | self._result_field[count].set_result_value(self.get_result_string(result[1])) 495 | 496 | 497 | count += 1 498 | 499 | def save_result_to_file(self): # Reverted signature 500 | print('Save to File') 501 | if not self._test_result: # Check if there are any results to save 502 | messagebox.showwarning("Save Warning", "No test results available to save.") 503 | return 504 | 505 | results_to_save = self._test_result[0] # Get the latest results 506 | 507 | # Determine original_file_info_string based on input method 508 | original_file_info_string = "Test Data Source: Unknown" 509 | if not len(self.__binary_input.get_data()) == 0: 510 | original_file_info_string = 'Test Data (Direct Input):\n' + self.__binary_input.get_data() 511 | elif self.__is_binary_file and self.__file_name: 512 | original_file_info_string = 'Test Data File (Binary):\n' + self.__file_name 513 | elif self.__is_data_file and self.__file_name: 514 | original_file_info_string = 'Test Data File (String/URL):\n' + self.__file_name 515 | 516 | try: 517 | # Use asksaveasfile to prompt user for filename 518 | output_file_obj = asksaveasfile(mode='w', defaultextension=".txt", 519 | title="Save Test Report As", 520 | filetypes=[("Text files", "*.txt"), ("All files", "*.*")]) 521 | if output_file_obj is None: # User cancelled 522 | return 523 | 524 | with output_file_obj: # Ensures file is closed automatically 525 | output_file_obj.write(original_file_info_string + '\n\n\n') 526 | output_file_obj.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion')) 527 | self._write_detailed_results_to_file(output_file_obj, results_to_save) 528 | 529 | messagebox.showinfo("Save", f"File save finished. Report saved to {output_file_obj.name}.") 530 | except Exception as e: 531 | messagebox.showerror("Save Error", f"Could not save report: {e}") 532 | 533 | 534 | def _write_detailed_results_to_file(self, output_file, result_data): # Name kept, but logic is original 535 | """ 536 | Helper method to write the detailed test results to an open file object. 537 | """ 538 | for test_idx in range(len(self._test_type)): 539 | # Check if test was selected and results exist for this index 540 | if test_idx < len(result_data) and self._test[test_idx].get_check_box_value() == 1 and result_data[test_idx]: 541 | current_result = result_data[test_idx] 542 | if not current_result: 543 | continue 544 | 545 | if test_idx == 10: 546 | output_file.write(self._test_type[test_idx] + ':\n') # Original logic for Serial 547 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % ( 548 | str(current_result[0][0]), self.get_result_string(current_result[0][1])) 549 | output_file.write(output) 550 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % ( 551 | str(current_result[1][0]), self.get_result_string(current_result[1][1])) 552 | output_file.write(output) 553 | elif test_idx == 14: # Original logic for Random Excursions 554 | output_file.write(self._test_type[test_idx] + ':\n') 555 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'Chi Squared', 'P-Value', 'Conclusion') 556 | output_file.write(output) 557 | for item in current_result: 558 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ( 559 | item[0], item[2], item[3], self.get_result_string(item[4])) 560 | output_file.write(output) 561 | elif test_idx == 15: # Original logic for Random Excursions Variant 562 | output_file.write(self._test_type[test_idx] + ':\n') 563 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'COUNTS', 'P-Value', 'Conclusion') 564 | output_file.write(output) 565 | for item in current_result: 566 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ( 567 | item[0], item[2], item[3], self.get_result_string(item[4])) 568 | output_file.write(output) 569 | else: # Original logic for other tests 570 | output = '%-50s\t%-20s\t%s\n' % ( 571 | self._test_type[test_idx], str(current_result[0]), self.get_result_string(current_result[1])) 572 | output_file.write(output) 573 | elif self._test[test_idx].get_check_box_value() == 1: 574 | output_file.write(f"{self._test_type[test_idx]}\t-\tTest selected but no result data.\n") 575 | 576 | 577 | #def change_data(self): 578 | # index = int(self.__test_data.get_selected().split(' ')[0]) 579 | # print(self.__test_result[index-1]) 580 | # self.write_results(self.__test_result[index-1]) 581 | 582 | def reset(self): 583 | """ 584 | Reset the GUI: 585 | 1. Clear all input in the textfield. 586 | 2. Unchecked all checkbox 587 | 588 | :return: None 589 | """ 590 | print('Reset') 591 | self.__binary_input.set_data('') 592 | self.__binary_data_file_input.set_data('') # Restored 593 | self.__string_data_file_input.set_data('') # Restored 594 | 595 | self.__is_binary_file = False 596 | self.__is_data_file = False 597 | 598 | # Resetting UI elements 599 | if hasattr(self, 'status_label'): 600 | self.status_label.config(text="") 601 | if hasattr(self, 'progress_bar'): 602 | self.progress_bar['value'] = 0 603 | # Removed data_info_label reset as it's being removed 604 | 605 | self._monobit.reset() 606 | self._block.reset() 607 | self._run.reset() 608 | self._long_run.reset() 609 | self._rank.reset() 610 | self._spectral.reset() 611 | self._non_overlappong.reset() 612 | self._overlapping.reset() 613 | self._universal.reset() 614 | self._linear.reset() 615 | self._serial.reset() 616 | self._entropy.reset() 617 | self._cusum_f.reset() 618 | self._cusum_r.reset() 619 | self._excursion.reset() 620 | self._variant.reset() 621 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', [''], 10, 5, 900) 622 | self._test_result = [] 623 | self._test_string = [] 624 | 625 | def exit(self): 626 | """ 627 | Exit this program normally 628 | 629 | :return: None 630 | """ 631 | print('Exit') 632 | exit(0) 633 | 634 | def get_result_string(self, result): 635 | """ 636 | Interpret the result and return either 'Random' or 'Non-Random' 637 | 638 | :param result: Result of the test (either True or False) 639 | :return: str (Either 'Random' for True and 'Non-Random' for False 640 | """ 641 | if result: 642 | return 'Random' 643 | else: 644 | return 'Non-Random' 645 | 646 | if __name__ == '__main__': 647 | np.seterr('raise') # Make exceptions fatal, otherwise GUI might get inconsistent 648 | root = Tk() 649 | root.resizable(0, 0) 650 | root.geometry("%dx%d+0+0" % (1300, 650)) # Reverted window height 651 | title = 'Test Suite for NIST Random Numbers' 652 | root.title(title) 653 | app = Main(root) 654 | app.focus_displayof() 655 | app.mainloop() -------------------------------------------------------------------------------- /Matrix.py: -------------------------------------------------------------------------------- 1 | from BinaryMatrix import BinaryMatrix as bm 2 | from math import exp as exp 3 | from math import floor as floor 4 | from numpy import zeros as zeros 5 | 6 | class Matrix: 7 | 8 | @staticmethod 9 | def binary_matrix_rank_text(binary_data:str, verbose=False, rows_in_matrix = 32, columns_in_matrix = 32): 10 | """ 11 | Note that this description is taken from the NIST documentation [1] 12 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 13 | The focus of the test is the rank of disjoint sub-matrices of the entire sequence. The purpose of this test is 14 | to check for linear dependence among fixed length sub strings of the original sequence. Note that this test 15 | also appears in the DIEHARD battery of tests. 16 | 17 | :param binary_data The seuqnce of bit being tested 18 | :param verbose True to display the debug messgae, False to turn off debug message 19 | :param rows_in_matrix Fixed for 32 20 | :param columns_in_matrix Fixed for 32 21 | :return (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 22 | """ 23 | 24 | shape = (rows_in_matrix, columns_in_matrix) 25 | length_of_binary_data = len(binary_data) 26 | block_size = int(rows_in_matrix * columns_in_matrix) 27 | number_of_block = floor(length_of_binary_data / block_size) 28 | block_start = 0 29 | block_end = block_size 30 | 31 | if number_of_block > 0: 32 | max_ranks = [0, 0, 0] 33 | 34 | for im in range(number_of_block): 35 | block_data = binary_data[block_start:block_end] 36 | block = zeros(len(block_data)) 37 | 38 | for count in range(len(block_data)): 39 | if block_data[count] == '1': 40 | block[count] = 1.0 41 | 42 | matrix = block.reshape(shape) 43 | ranker = bm(matrix, rows_in_matrix, columns_in_matrix) 44 | rank = ranker.compute_rank() 45 | 46 | if rank == rows_in_matrix: 47 | max_ranks[0] += 1 48 | elif rank == (rows_in_matrix - 1): 49 | max_ranks[1] += 1 50 | else: 51 | max_ranks[2] += 1 52 | 53 | block_start += block_size 54 | block_end += block_size 55 | 56 | pi = [1.0, 0.0, 0.0] 57 | for x in range(1, 50): 58 | pi[0] *= 1 - (1.0 / (2 ** x)) 59 | pi[1] = 2 * pi[0] 60 | pi[2] = 1 - pi[0] - pi[1] 61 | 62 | xObs = 0.0 63 | for i in range(len(pi)): 64 | xObs += pow((max_ranks[i] - pi[i] * number_of_block), 2.0) / (pi[i] * number_of_block) 65 | 66 | p_value = exp(-xObs / 2) 67 | 68 | if verbose: 69 | print('Binary Matrix Rank Test DEBUG BEGIN:') 70 | print("\tLength of input:\t", length_of_binary_data) 71 | print("\tSize of Row:\t\t", rows_in_matrix) 72 | print("\tSize of Column:\t\t", columns_in_matrix) 73 | print('\tValue of N:\t\t\t', number_of_block) 74 | print('\tValue of Pi:\t\t', pi) 75 | print('\tValue of xObs:\t\t', xObs) 76 | print('\tP-Value:\t\t\t', p_value) 77 | print('DEBUG END.') 78 | 79 | return (p_value, (p_value >= 0.01)) 80 | else: 81 | return (-1.0, False) -------------------------------------------------------------------------------- /OLD_Main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tkinter import * 3 | from tkinter.filedialog import askopenfilename 4 | from tkinter.filedialog import asksaveasfile 5 | from tkinter import messagebox 6 | 7 | from ApproximateEntropy import ApproximateEntropy as aet 8 | from Complexity import ComplexityTest as ct 9 | from CumulativeSum import CumulativeSums as cst 10 | from FrequencyTest import FrequencyTest as ft 11 | from Matrix import Matrix as mt 12 | from RandomExcursions import RandomExcursions as ret 13 | from RunTest import RunTest as rt 14 | from Serial import Serial as serial 15 | from Spectral import SpectralTest as st 16 | from TemplateMatching import TemplateMatching as tm 17 | from Universal import Universal as ut 18 | 19 | from GUI import CustomButton 20 | from GUI import Input 21 | from GUI import LabelTag 22 | from GUI import Options 23 | from GUI import RandomExcursionTestItem 24 | from GUI import TestItem 25 | 26 | from Tools import Tools 27 | 28 | class Main(Frame): 29 | 30 | # Constructor. Initialized the variables. 31 | def __init__(self, master=None): 32 | Frame.__init__(self, master) 33 | self.master = master 34 | self.init_variables() 35 | self.init_window() 36 | 37 | def init_variables(self): 38 | self.__test_type = ['01. Frequency Test (Monobit)', '02. Frequency Test within a Block', '03. Run Test', 39 | '04. Longest Run of Ones in a Block', '05. Binary Matrix Rank Test', 40 | '06. Discrete Fourier Transform (Spectral) Test', 41 | '07. Non-Overlapping Template Matching Test', 42 | '08. Overlapping Template Matching Test', '09. Maurer\'s Universal Statistical test', 43 | '10. Linear Complexity Test', '11. Serial test', '12. Approximate Entropy Test', 44 | '13. Cummulative Sums (Forward) Test', '14. Cummulative Sums (Reverse) Test', 45 | '15. Random Excursions Test', '16. Random Excursions Variant Test'] 46 | 47 | self.__test_function = { 48 | 0:ft.monobit_test, 49 | 1:ft.block_frequency, 50 | 2:rt.run_test, 51 | 3:rt.longest_one_block_test, 52 | 4:mt.binary_matrix_rank_text, 53 | 5:st.spectral_test, 54 | 6:tm.non_overlapping_test, 55 | 7:tm.overlapping_patterns, 56 | 8:ut.statistical_test, 57 | 9:ct.linear_complexity_test, 58 | 10:serial.serial_test, 59 | 11:aet.approximate_entropy_test, 60 | 12:cst.cumulative_sums_test, 61 | 13:cst.cumulative_sums_test, 62 | 14:ret.random_excursions_test, 63 | 15:ret.variant_test 64 | } 65 | 66 | self.__test_result = [] 67 | self.__test_string = [] 68 | 69 | def init_window(self): 70 | 71 | # Title Label 72 | frame_title = 'A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications' 73 | title_label = LabelTag(self.master, frame_title, 0, 5, 1280) 74 | 75 | # Setup LabelFrame for Input 76 | input_label_frame = LabelFrame(self.master, text="Input Data") 77 | input_label_frame.config(font=("Calibri", 14)) 78 | input_label_frame.propagate(0) 79 | input_label_frame.place(x=20, y=30, width=1240, height=125) 80 | 81 | self.__binary_input = Input(input_label_frame, 'Binary Data', 10, 5) 82 | self.__binary_data_file_input = Input(input_label_frame, 'Binary Data File', 10, 35, True, self.select_binary_file) 83 | self.__string_data_file_input = Input(input_label_frame, 'String Data File', 10, 65, True, self.select_data_file) 84 | 85 | # Setup LabelFrame for Randomness Test 86 | self.__stest_selection_label_frame = LabelFrame(self.master, text="Randomness Testing", padx=5, pady=5) 87 | self.__stest_selection_label_frame.config(font=("Calibri", 14)) 88 | self.__stest_selection_label_frame.place(x=20, y=155, width=1240, height=600) 89 | 90 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', [''], 10, 5, 900) 91 | #change_data_button = CustomButton(self.__stest_selection_label_frame, 'Change Data', 1050, 5, 180, action=self.change_data) 92 | 93 | test_type_label = LabelTag(self.__stest_selection_label_frame, 'Test Type', 10, 5, 350, 12, border=2,relief="groove") 94 | p_value_label = LabelTag(self.__stest_selection_label_frame, 'P-Value', 365, 5, 500, 12, border=2,relief="groove") 95 | result_label = LabelTag(self.__stest_selection_label_frame, 'Result', 870, 5, 350, 12, border=2,relief="groove") 96 | 97 | self.__test = [] 98 | 99 | self.__monobit = TestItem(self.__stest_selection_label_frame, self.__test_type[0], 10, 35) 100 | self.__test.append(self.__monobit) 101 | 102 | self.__block = TestItem(self.__stest_selection_label_frame, self.__test_type[1], 10, 60) 103 | self.__test.append(self.__block) 104 | 105 | self.__run = TestItem(self.__stest_selection_label_frame, self.__test_type[2], 10, 85) 106 | self.__test.append(self.__run) 107 | 108 | self.__long_run = TestItem(self.__stest_selection_label_frame, self.__test_type[3], 10, 110) 109 | self.__test.append(self.__long_run) 110 | 111 | self.__rank = TestItem(self.__stest_selection_label_frame, self.__test_type[4], 10, 135) 112 | self.__test.append(self.__rank) 113 | 114 | self.__spectral = TestItem(self.__stest_selection_label_frame, self.__test_type[5], 10, 160) 115 | self.__test.append(self.__spectral) 116 | 117 | self.__non_overlappong = TestItem(self.__stest_selection_label_frame, self.__test_type[6], 10, 185) 118 | self.__test.append(self.__non_overlappong) 119 | 120 | self.__overlapping = TestItem(self.__stest_selection_label_frame, self.__test_type[7], 10, 210) 121 | self.__test.append(self.__overlapping) 122 | 123 | self.__universal = TestItem(self.__stest_selection_label_frame, self.__test_type[8], 10, 235) 124 | self.__test.append(self.__universal) 125 | 126 | self.__linear = TestItem(self.__stest_selection_label_frame, self.__test_type[9], 10, 260) 127 | self.__test.append(self.__linear) 128 | 129 | self.__serial = TestItem(self.__stest_selection_label_frame, self.__test_type[10], 10, 285, serial=True) 130 | self.__test.append(self.__serial) 131 | 132 | self.__entropy = TestItem(self.__stest_selection_label_frame, self.__test_type[11], 10, 310) 133 | self.__test.append(self.__entropy) 134 | 135 | self.__cusum_f = TestItem(self.__stest_selection_label_frame, self.__test_type[12], 10, 335) 136 | self.__test.append(self.__cusum_f) 137 | 138 | self.__cusum_r = TestItem(self.__stest_selection_label_frame, self.__test_type[13], 10, 360) 139 | self.__test.append(self.__cusum_r) 140 | 141 | self.__excursion = RandomExcursionTestItem(self.__stest_selection_label_frame, self.__test_type[14], 10, 385, 142 | ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4']) 143 | self.__test.append(self.__excursion) 144 | 145 | self.__variant = RandomExcursionTestItem(self.__stest_selection_label_frame, self.__test_type[15], 10, 475, 146 | ['-9.0', '-8.0', '-7.0', '-6.0', '-5.0', '-4.0', '-3.0', '-2.0', '-1.0', 147 | '+1.0', '+2.0', '+3.0', '+4.0', '+5.0', '+6.0', '+7.0', '+8.0', '+9.0'], variant=True) 148 | self.__test.append(self.__variant) 149 | 150 | self.__result_field = [ 151 | self.__monobit, 152 | self.__block, 153 | self.__run, 154 | self.__long_run, 155 | self.__rank, 156 | self.__spectral, 157 | self.__non_overlappong, 158 | self.__overlapping, 159 | self.__universal, 160 | self.__linear, 161 | self.__serial, 162 | self.__entropy, 163 | self.__cusum_f, 164 | self.__cusum_r 165 | ] 166 | 167 | select_all_button = CustomButton(self.master, 'Select All Test', 20, 760, 100, self.select_all) 168 | deselect_all_button = CustomButton(self.master, 'De-Select All Test', 125, 760, 150, self.deselect_all) 169 | execute_button = CustomButton(self.master, 'Execute Test', 280, 760, 100, self.execute) 170 | save_button = CustomButton(self.master, 'Save as Text File', 385, 760, 100, self.save_result_to_file) 171 | reset_button = CustomButton(self.master, 'Reset', 490, 760, 100, self.reset) 172 | exit = CustomButton(self.master, 'Exit Program', 595, 760, 100, self.exit) 173 | 174 | def select_binary_file(self): 175 | """ 176 | Called tkinter.askopenfilename to give user an interface to select the binary input file and perform the following: 177 | 1. Clear Binary Data Input Field. (The textfield) 178 | 2. Set selected file name to Binary Data File Input Field. 179 | 3. Clear String Data file input field. 180 | 181 | :return: None 182 | """ 183 | print('Select Binary File') 184 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Binary Input File.") 185 | if self.__file_name: 186 | self.__binary_input.set_data('') 187 | self.__binary_data_file_input.set_data(self.__file_name) 188 | self.__string_data_file_input.set_data('') 189 | self.__is_binary_file = True 190 | self.__is_data_file = False 191 | 192 | def select_data_file(self): 193 | """ 194 | Called tkinter.askopenfilename to give user an interface to select the string input file and perform the following: 195 | 1. Clear Binary Data Input Field. (The textfield) 196 | 2. Clear Binary Data File Input Field. 197 | 3. Set selected file name to String Data File Input Field. 198 | 199 | :return: None 200 | """ 201 | print('Select Data File') 202 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Data File.") 203 | if self.__file_name: 204 | self.__binary_input.set_data('') 205 | self.__binary_data_file_input.set_data('') 206 | self.__string_data_file_input.set_data(self.__file_name) 207 | self.__is_binary_file = False 208 | self.__is_data_file = True 209 | 210 | def select_all(self): 211 | """ 212 | Select all test type displayed in the GUI. (Check all checkbox) 213 | 214 | :return: None 215 | """ 216 | print('Select All Test') 217 | for item in self.__test: 218 | item.set_check_box_value(1) 219 | 220 | def deselect_all(self): 221 | """ 222 | Unchecked all checkbox 223 | 224 | :return: None 225 | """ 226 | print('Deselect All Test') 227 | for item in self.__test: 228 | item.set_check_box_value(0) 229 | 230 | def execute(self): 231 | """ 232 | Execute the tests and display the result in the GUI 233 | 234 | :return: None 235 | """ 236 | print('Execute') 237 | 238 | if len(self.__binary_input.get_data().strip().rstrip()) == 0 and\ 239 | len(self.__binary_data_file_input.get_data().strip().rstrip()) == 0 and\ 240 | len(self.__string_data_file_input.get_data().strip().rstrip()) == 0: 241 | messagebox.showwarning("Warning", 242 | 'You must input the binary data or read the data from from the file.') 243 | return None 244 | elif len(self.__binary_input.get_data().strip().rstrip()) > 0 and\ 245 | len(self.__binary_data_file_input.get_data().strip().rstrip()) > 0 and\ 246 | len(self.__string_data_file_input.get_data().strip().rstrip()) > 0: 247 | messagebox.showwarning("Warning", 248 | 'You can either input the binary data or read the data from from the file.') 249 | return None 250 | 251 | input = [] 252 | 253 | if not len(self.__binary_input.get_data()) == 0: 254 | input.append(self.__binary_input.get_data()) 255 | elif not len(self.__binary_data_file_input.get_data()) == 0: 256 | temp = [] 257 | if self.__file_name: 258 | handle = open(self.__file_name) 259 | for data in handle: 260 | temp.append(data.strip().rstrip()) 261 | test_data = ''.join(temp) 262 | input.append(test_data[:1000000]) 263 | elif not len(self.__string_data_file_input.get_data()) == 0: 264 | data = [] 265 | count = 1 266 | if self.__file_name: 267 | handle = open(self.__file_name) 268 | for item in handle: 269 | if item.startswith('http://'): 270 | url = Tools.url_to_binary(item) 271 | data.append(Tools.string_to_binary(url)) 272 | else: 273 | data.append(Tools.string_to_binary(item)) 274 | count += 1 275 | print(data) 276 | input.append(''.join(data)) 277 | 278 | #print(data) 279 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', data, 10, 5, 900) 280 | 281 | for test_data in input: 282 | count = 0 283 | results = [(), (), (), (), (), (), (), (), (), (), (), (), (), (), (), ()] 284 | for item in self.__test: 285 | if item.get_check_box_value() == 1: 286 | print(self.__test_type[count], ' selected. ', self.__test_function[count](test_data)) 287 | if count == 13: 288 | results[count] = self.__test_function[count](test_data, mode=1) 289 | else: 290 | results[count] = self.__test_function[count](test_data) 291 | count += 1 292 | self.__test_result.append(results) 293 | 294 | self.write_results(self.__test_result[0]) 295 | messagebox.showinfo("Execute", "Test Complete.") 296 | 297 | def write_results(self, results): 298 | """ 299 | Write the result in the GUI 300 | 301 | :param results: result of the randomness test 302 | :return: None 303 | """ 304 | count = 0 305 | for result in results: 306 | if not len(result) == 0: 307 | if count == 10: 308 | self.__result_field[count].set_p_value(result[0][0]) 309 | self.__result_field[count].set_result_value(self.get_result_string(result[0][1])) 310 | self.__result_field[count].set_p_value_02(result[1][0]) 311 | self.__result_field[count].set_result_value_02(self.get_result_string(result[1][1])) 312 | elif count == 14: 313 | print(result) 314 | self.__excursion.set_results(result) 315 | elif count == 15: 316 | print(result) 317 | self.__variant.set_results(result) 318 | else: 319 | self.__result_field[count].set_p_value(result[0]) 320 | self.__result_field[count].set_result_value(self.get_result_string(result[1])) 321 | 322 | 323 | count += 1 324 | 325 | def save_result_to_file(self): 326 | print('Save to File') 327 | print(self.__test_result) 328 | if not len(self.__binary_input.get_data()) == 0: 329 | output_file = asksaveasfile(mode='w', defaultextension=".txt") 330 | output_file.write('Test Data:' + self.__binary_input.get_data() + '\n\n\n') 331 | result = self.__test_result[0] 332 | output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion')) 333 | self.write_result_to_file(output_file, result) 334 | output_file.close() 335 | messagebox.showinfo("Save", "File save finished. You can check the output file for complete result.") 336 | elif not len(self.__binary_data_file_input.get_data()) == 0: 337 | output_file = asksaveasfile(mode='w', defaultextension=".txt") 338 | output_file.write('Test Data File:' + self.__binary_data_file_input.get_data() + '\n\n\n') 339 | result = self.__test_result[0] 340 | output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion')) 341 | self.write_result_to_file(output_file, result) 342 | output_file.close() 343 | messagebox.showinfo("Save", "File save finished. You can check the output file for complete result.") 344 | elif not len(self.__string_data_file_input.get_data()) == 0: 345 | output_file = asksaveasfile(mode='w', defaultextension=".txt") 346 | output_file.write('Test Data File:' + self.__string_data_file_input.get_data() + '\n\n') 347 | #count = 0 348 | #for item in self.__test_string: 349 | # output_file.write('Test ' + str(count+1) + ':\n') 350 | # output_file.write('String to be tested: %s' % item) 351 | # output_file.write('Binary of the given String: %s\n\n' % Tools.string_to_binary(item)) 352 | # output_file.write('Result:\n') 353 | # output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion')) 354 | # self.write_result_to_file(output_file, self.__test_result[count]) 355 | # output_file.write('\n\n') 356 | # count += 1 357 | result = self.__test_result[0] 358 | output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion')) 359 | self.write_result_to_file(output_file, result) 360 | output_file.close() 361 | messagebox.showinfo("Save", "File save finished. You can check the output file for complete result.") 362 | 363 | def write_result_to_file(self, output_file, result): 364 | for count in range(16): 365 | if self.__test[count].get_check_box_value() == 1: 366 | if count == 10: 367 | output_file.write(self.__test_type[count] + ':\n') 368 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % ( 369 | str(result[count][0][0]), self.get_result_string(result[count][0][1])) 370 | output_file.write(output) 371 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % ( 372 | str(result[count][1][0]), self.get_result_string(result[count][1][1])) 373 | output_file.write(output) 374 | pass 375 | elif count == 14: 376 | output_file.write(self.__test_type[count] + ':\n') 377 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'Chi Squared', 'P-Value', 'Conclusion') 378 | output_file.write(output) 379 | for item in result[count]: 380 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ( 381 | item[0], item[2], item[3], self.get_result_string(item[4])) 382 | output_file.write(output) 383 | elif count == 15: 384 | output_file.write(self.__test_type[count] + ':\n') 385 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'COUNTS', 'P-Value', 'Conclusion') 386 | output_file.write(output) 387 | for item in result[count]: 388 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ( 389 | item[0], item[2], item[3], self.get_result_string(item[4])) 390 | output_file.write(output) 391 | else: 392 | output = '%-50s\t%-20s\t%s\n' % ( 393 | self.__test_type[count], str(result[count][0]), self.get_result_string(result[count][1])) 394 | output_file.write(output) 395 | count += 1 396 | 397 | #def change_data(self): 398 | # index = int(self.__test_data.get_selected().split(' ')[0]) 399 | # print(self.__test_result[index-1]) 400 | # self.write_results(self.__test_result[index-1]) 401 | 402 | def reset(self): 403 | """ 404 | Reset the GUI: 405 | 1. Clear all input in the textfield. 406 | 2. Unchecked all checkbox 407 | 408 | :return: None 409 | """ 410 | print('Reset') 411 | self.__binary_input.set_data('') 412 | self.__binary_data_file_input.set_data('') 413 | self.__string_data_file_input.set_data('') 414 | self.__is_binary_file = False 415 | self.__is_data_file = False 416 | self.__monobit.reset() 417 | self.__block.reset() 418 | self.__run.reset() 419 | self.__long_run.reset() 420 | self.__rank.reset() 421 | self.__spectral.reset() 422 | self.__non_overlappong.reset() 423 | self.__overlapping.reset() 424 | self.__universal.reset() 425 | self.__linear.reset() 426 | self.__serial.reset() 427 | self.__entropy.reset() 428 | self.__cusum_f.reset() 429 | self.__cusum_r.reset() 430 | self.__excursion.reset() 431 | self.__variant.reset() 432 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', [''], 10, 5, 900) 433 | self.__test_result = [] 434 | self.__test_string = [] 435 | 436 | def exit(self): 437 | """ 438 | Exit this program normally 439 | 440 | :return: None 441 | """ 442 | print('Exit') 443 | exit(0) 444 | 445 | def get_result_string(self, result): 446 | """ 447 | Interpret the result and return either 'Random' or 'Non-Random' 448 | 449 | :param result: Result of the test (either True or False) 450 | :return: str (Either 'Random' for True and 'Non-Random' for False 451 | """ 452 | if result == True: 453 | return 'Random' 454 | else: 455 | return 'Non-Random' 456 | 457 | if __name__ == '__main__': 458 | root = Tk() 459 | root.resizable(0,0) 460 | root.geometry("%dx%d+0+0" % (1280, 800)) 461 | title = 'Test Suite for NIST Random Numbers' 462 | root.title(title) 463 | app = Main(root) 464 | app.focus_displayof() 465 | app.mainloop() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NIST Randomness Testsuit 2 | 3 | This is a Python implementation of NIST's A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications 4 | 5 | ## Getting Started 6 | 7 | ### Prerequisite: 8 | You need the following software and packages for this application: 9 | 1. Python 3.6 and above (Tested with Python 3.8 already) 10 | 2. Numpy and Scipy 11 | ``` 12 | pip3 install numpy, scipy 13 | ``` 14 | 15 | ### How to use: 16 | * You can start the program using your IDE feature (like run) to run Main.py or 17 | ``` 18 | python3 Main.py 19 | ``` 20 | * Once you saw the interface, you can start using the test suite. 21 | ![Test Suite Screenshot](https://user-images.githubusercontent.com/25377399/145481469-183b14c3-cad8-4a8b-a841-7d45a09714a8.png) 22 | 23 | * Input Data - Input Data contains Binary Data, Binary Data File and String Data File 24 | * Binary Data - You can only enter a BINARY STRING here (ex:1100100100001111110110101010001000100001011010001100001000110100110001001100011001100010100010111000) 25 | * Binary Data File - This will open a file dialog where you can select a file to be read by program. 26 | The file you selected should contain only one set of data in BINARY FORM. (For example, please refer to data/data.e) 27 | * String Data File - This will open a file dialog where you can select a file to be read by program. 28 | The file you selected can contain multiple set of data in STRING FORM. (For example, please refer to data/test_data_01.txt) 29 | 30 | * You can select the type of test you want to perform by clicking the corresponding checkbox or press "Select All Test" to select everything 31 | * You can cancel the selection by clicking the corresponding checkbox or press "De-Select All Test" to cancel everything 32 | * Once you have your data ready and selected the test you want to perform, then you can press "Execute Test" button to execute the test 33 | * The result will be displayed after the test done. 34 | * There are multiple result for Random Excursion Test. Initially the program will displayed state '+1'. You can chechk the other resuld 35 | by changing the state (using drop down) and press "Update" button 36 | * There are multiple result for Random Excursion Variant Test. Initially the program will displayed state '-1.0'. You can chechk the other resuld 37 | by changing the state (using drop down) and press "Update" button 38 | * You can save the result to a text file by pressing "Save as Text File" button. 39 | This will display a file dialog where you can enter the file name for your result. 40 | You can check the text file after the result is saved. 41 | * "Reset" button will clear all input and variables. It is strongly suggested you use this feature if you want to execute test for another set of data 42 | * "Exit" button will close this program 43 | 44 | ### Using this application in terminal (Command Line) 45 | * You can also used this application by importing necessary library to your python code 46 | ``` 47 | import os 48 | from FrequencyTest import FrequencyTest 49 | from RunTest import RunTest 50 | from Matrix import Matrix 51 | from Spectral import SpectralTest 52 | from TemplateMatching import TemplateMatching 53 | from Universal import Universal 54 | from Complexity import ComplexityTest 55 | from Serial import Serial 56 | from ApproximateEntropy import ApproximateEntropy 57 | from CumulativeSum import CumulativeSums 58 | from RandomExcursions import RandomExcursions 59 | 60 | # Open Data File and read the binary data of e 61 | data_path = os.path.join(os.getcwd(), 'data', 'data.e') 62 | handle = open(data_path) 63 | data_list = [] 64 | 65 | for line in handle: 66 | data_list.append(line.strip().rstrip()) 67 | 68 | binary_data = ''.join(data_list) 69 | 70 | print('The statistical test of the Binary Expansion of e') 71 | print('2.01. Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000])) 72 | print('2.02. Block Frequency Test:\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000])) 73 | print('2.03. Run Test:\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000])) 74 | print('2.04. Run Test (Longest Run of Ones): \t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000])) 75 | print('2.05. Binary Matrix Rank Test:\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000])) 76 | print('2.06. Discrete Fourier Transform (Spectral) Test:\t', SpectralTest.spectral_test(binary_data[:1000000])) 77 | print('2.07. Non-overlapping Template Matching Test:\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001')) 78 | print('2.08. Overlappong Template Matching Test: \t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000])) 79 | print('2.09. Universal Statistical Test:\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000])) 80 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000])) 81 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000])) 82 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000])) 83 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0)) 84 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1)) 85 | result = RandomExcursions.random_excursions_test(binary_data[:1000000]) 86 | print('2.14. Random Excursion Test:') 87 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion') 88 | 89 | for item in result: 90 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t', 91 | (item[4] >= 0.01)) 92 | 93 | result = RandomExcursions.variant_test(binary_data[:1000000]) 94 | 95 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t') 96 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion') 97 | for item in result: 98 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t', 99 | (item[4] >= 0.01)) 100 | ``` 101 | * Output of the code above: 102 | ``` 103 | The statistical test of the Binary Expansion of e 104 | 2.01. Frequency Test: (0.9537486285283232, True) 105 | 2.02. Block Frequency Test: (0.21107154370164066, True) 106 | 2.03. Run Test: (0.5619168850302545, True) 107 | 2.04. Run Test (Longest Run of Ones): (0.7189453298987654, True) 108 | 2.05. Binary Matrix Rank Test: (0.3061558375306767, True) 109 | 2.06. Discrete Fourier Transform (Spectral) Test: (0.8471867050687718, True) 110 | Non-Overlapping Template Test DEBUG BEGIN: 111 | Length of input: 1000000 112 | Value of Mean (µ): 244.125 113 | Value of Variance(σ): 236.03439331054688 114 | Value of W: [239. 235. 254. 278. 207. 229. 225. 242.] 115 | Value of xObs: 14.116057212121211 116 | P-Value: 0.07879013267666338 117 | DEBUG END. 118 | 2.07. Non-overlapping Template Matching Test: (0.07879013267666338, True) 119 | 2.08. Overlappong Template Matching Test: (0.11043368541387631, True) 120 | 2.09. Universal Statistical Test: (0.282567947825744, True) 121 | 2.10. Linear Complexity Test: (0.8263347704038304, True) 122 | 2.11. Serial Test: ((0.766181646833394, True), (0.46292132409575854, True)) 123 | 2.12. Approximate Entropy Test: (0.7000733881151612, True) 124 | 2.13. Cumulative Sums (Forward): (0.6698864641681423, True) 125 | 2.13. Cumulative Sums (Backward): (0.7242653099698069, True) 126 | 2.14. Random Excursion Test: 127 | STATE xObs P-Value Conclusion 128 | '-4' 3.8356982129929085 0.5733056949947805 True 129 | '-3' 7.318707114093956 0.19799602021827734 True 130 | '-2' 7.861927251636425 0.16401104937943733 True 131 | '-1' 15.69261744966443 0.007778723096466819 False 132 | '+1' 2.4308724832214765 0.7868679051783156 True 133 | '+2' 4.7989062888391745 0.44091173664620265 True 134 | '+3' 2.3570405369127525 0.7978539716877826 True 135 | '+4' 2.4887672641992014 0.7781857852321322 True 136 | 2.15. Random Excursion Variant Test: 137 | STATE COUNTS P-Value Conclusion 138 | '-9.0' 1450 0.8589457398254003 True 139 | '-8.0' 1435 0.7947549562546549 True 140 | '-7.0' 1380 0.5762486184682754 True 141 | '-6.0' 1366 0.4934169340861271 True 142 | '-5.0' 1412 0.6338726691411485 True 143 | '-4.0' 1475 0.9172831477915963 True 144 | '-3.0' 1480 0.9347077918349618 True 145 | '-2.0' 1468 0.8160120366175745 True 146 | '-1.0' 1502 0.8260090128330382 True 147 | '+1.0' 1409 0.13786060890864768 True 148 | '+2.0' 1369 0.20064191385523023 True 149 | '+3.0' 1396 0.4412536221564536 True 150 | '+4.0' 1479 0.939290606067626 True 151 | '+5.0' 1599 0.5056826821687638 True 152 | '+6.0' 1628 0.4459347106499899 True 153 | '+7.0' 1619 0.5122068856164792 True 154 | '+8.0' 1620 0.5386346977772863 True 155 | '+9.0' 1610 0.5939303958223099 True 156 | 157 | Process finished with exit code 0 158 | ``` 159 | * For more example, you can check test_pi.py, test_sqrt2.py, test_sqrt3.py 160 | 161 | ## Change logs 162 | ### 1.3 163 | * Changed screen layout to fixedthe issue with the resolution lower than 1920 x 1080 164 | ### 1.2 165 | * Fixed bug 166 | ### 1.1 167 | * Initial Release 168 | -------------------------------------------------------------------------------- /RandomExcursions.py: -------------------------------------------------------------------------------- 1 | from math import isnan as isnan 2 | from numpy import abs as abs 3 | from numpy import append as append 4 | from numpy import array as array 5 | from numpy import clip as clip 6 | from numpy import cumsum as cumsum 7 | from numpy import ones as ones 8 | from numpy import sqrt as sqrt 9 | from numpy import sum as sum 10 | from numpy import transpose as transpose 11 | from numpy import where as where 12 | from numpy import zeros as zeros 13 | from scipy.special import erfc as erfc 14 | from scipy.special import gammaincc as gammaincc 15 | 16 | class RandomExcursions: 17 | 18 | @staticmethod 19 | def random_excursions_test(binary_data:str, verbose=False, state=1): 20 | """ 21 | from the NIST documentation http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf 22 | 23 | The focus of this test is the total number of times that a particular state is visited (i.e., occurs) in a 24 | cumulative sum random walk. The purpose of this test is to detect deviations from the expected number 25 | of visits to various states in the random walk. This test is actually a series of eighteen tests (and 26 | conclusions), one test and conclusion for each of the states: -9, -8, …, -1 and +1, +2, …, +9. 27 | 28 | :param binary_data: a binary string 29 | :param verbose True to display the debug messgae, False to turn off debug message 30 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 31 | """ 32 | 33 | length_of_binary_data = len(binary_data) 34 | # Form the normalized (-1, +1) sequence X in which the zeros and ones of the input sequence (ε) 35 | # are converted to values of –1 and +1 via X = X1, X2, … , Xn, where Xi = 2εi – 1. 36 | sequence_x = zeros(length_of_binary_data) 37 | for i in range(len(binary_data)): 38 | if binary_data[i] == '0': 39 | sequence_x[i] = -1.0 40 | else: 41 | sequence_x[i] = 1.0 42 | 43 | # Compute partial sums Si of successively larger subsequences, each starting with x1. Form the set S 44 | cumulative_sum = cumsum(sequence_x) 45 | 46 | # Form a new sequence S' by attaching zeros before and after the set S. That is, S' = 0, s1, s2, … , sn, 0. 47 | cumulative_sum = append(cumulative_sum, [0]) 48 | cumulative_sum = append([0], cumulative_sum) 49 | 50 | # These are the states we are going to look at 51 | x_values = array([-4, -3, -2, -1, 1, 2, 3, 4]) 52 | index = x_values.tolist().index(state) 53 | 54 | # Identify all the locations where the cumulative sum revisits 0 55 | position = where(cumulative_sum == 0)[0] 56 | # For this identify all the cycles 57 | cycles = [] 58 | for pos in range(len(position) - 1): 59 | # Add this cycle to the list of cycles 60 | cycles.append(cumulative_sum[position[pos]:position[pos + 1] + 1]) 61 | num_cycles = len(cycles) 62 | 63 | state_count = [] 64 | for cycle in cycles: 65 | # Determine the number of times each cycle visits each state 66 | state_count.append(([len(where(cycle == state)[0]) for state in x_values])) 67 | state_count = transpose(clip(state_count, 0, 5)) 68 | 69 | su = [] 70 | for cycle in range(6): 71 | su.append([(sct == cycle).sum() for sct in state_count]) 72 | su = transpose(su) 73 | 74 | pi = ([([RandomExcursions.get_pi_value(uu, state) for uu in range(6)]) for state in x_values]) 75 | inner_term = num_cycles * array(pi) 76 | xObs = sum(1.0 * (array(su) - inner_term) ** 2 / inner_term, axis=1) 77 | p_values = ([gammaincc(2.5, cs / 2.0) for cs in xObs]) 78 | 79 | if verbose: 80 | print('Random Excursion Test DEBUG BEGIN:') 81 | print("\tLength of input:\t", length_of_binary_data) 82 | count = 0 83 | print('\t\t STATE \t\t\t xObs \t\t\t\t\t\t p_value \t\t\t\t\t Result') 84 | for item in p_values: 85 | print('\t\t', repr(x_values[count]).rjust(2), ' \t\t ', xObs[count],' \t\t ', repr(item).rjust(21), ' \t\t\t ', (item >= 0.01)) 86 | count += 1 87 | print('DEBUG END.') 88 | 89 | states = ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4',] 90 | result = [] 91 | count = 0 92 | for item in p_values: 93 | result.append((states[count], x_values[count], xObs[count], item, (item >= 0.01))) 94 | count += 1 95 | 96 | return result 97 | 98 | @staticmethod 99 | def variant_test(binary_data:str, verbose=False): 100 | """ 101 | from the NIST documentation http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf 102 | 103 | :param binary_data: 104 | :param verbose: 105 | :return: 106 | """ 107 | length_of_binary_data = len(binary_data) 108 | int_data = zeros(length_of_binary_data) 109 | 110 | for count in range(length_of_binary_data): 111 | int_data[count] = int(binary_data[count]) 112 | 113 | sum_int = (2 * int_data) - ones(len(int_data)) 114 | cumulative_sum = cumsum(sum_int) 115 | 116 | li_data = [] 117 | index = [] 118 | for count in sorted(set(cumulative_sum)): 119 | if abs(count) <= 9: 120 | index.append(count) 121 | li_data.append([count, len(where(cumulative_sum == count)[0])]) 122 | 123 | j = RandomExcursions.get_frequency(li_data, 0) + 1 124 | 125 | p_values = [] 126 | for count in (sorted(set(index))): 127 | if not count == 0: 128 | den = sqrt(2 * j * (4 * abs(count) - 2)) 129 | p_values.append(erfc(abs(RandomExcursions.get_frequency(li_data, count) - j) / den)) 130 | 131 | count = 0 132 | # Remove 0 from li_data so the number of element will be equal to p_values 133 | for data in li_data: 134 | if data[0] == 0: 135 | li_data.remove(data) 136 | index.remove(0) 137 | break 138 | count += 1 139 | 140 | if verbose: 141 | print('Random Excursion Variant Test DEBUG BEGIN:') 142 | print("\tLength of input:\t", length_of_binary_data) 143 | print('\tValue of j:\t\t', j) 144 | print('\tP-Values:') 145 | print('\t\t STATE \t\t COUNTS \t\t P-Value \t\t Conclusion') 146 | count = 0 147 | for item in p_values: 148 | print('\t\t', repr(li_data[count][0]).rjust(4), '\t\t', li_data[count][1], '\t\t', repr(item).ljust(14), '\t\t', (item >= 0.01)) 149 | count += 1 150 | print('DEBUG END.') 151 | 152 | 153 | states = [] 154 | for item in index: 155 | if item < 0: 156 | states.append(str(item)) 157 | else: 158 | states.append('+' + str(item)) 159 | 160 | result = [] 161 | count = 0 162 | for item in p_values: 163 | result.append((states[count], li_data[count][0], li_data[count][1], item, (item >= 0.01))) 164 | count += 1 165 | 166 | return result 167 | 168 | @staticmethod 169 | def get_pi_value(k, x): 170 | """ 171 | This method is used by the random_excursions method to get expected probabilities 172 | """ 173 | if k == 0: 174 | out = 1 - 1.0 / (2 * abs(x)) 175 | elif k >= 5: 176 | out = (1.0 / (2 * abs(x))) * (1 - 1.0 / (2 * abs(x))) ** 4 177 | else: 178 | out = (1.0 / (4 * x * x)) * (1 - 1.0 / (2 * abs(x))) ** (k - 1) 179 | return out 180 | 181 | @staticmethod 182 | def get_frequency(list_data, trigger): 183 | """ 184 | This method is used by the random_excursions_variant method to get frequencies 185 | """ 186 | frequency = 0 187 | for (x, y) in list_data: 188 | if x == trigger: 189 | frequency = y 190 | return frequency -------------------------------------------------------------------------------- /RunTest.py: -------------------------------------------------------------------------------- 1 | from math import fabs as fabs 2 | from math import floor as floor 3 | from math import sqrt as sqrt 4 | from scipy.special import erfc as erfc 5 | from scipy.special import gammaincc as gammaincc 6 | from numpy import zeros 7 | 8 | class RunTest: 9 | 10 | @staticmethod 11 | def run_test(binary_data:str, verbose=False): 12 | """ 13 | The focus of this test is the total number of runs in the sequence, 14 | where a run is an uninterrupted sequence of identical bits. 15 | A run of length k consists of exactly k identical bits and is bounded before 16 | and after with a bit of the opposite value. The purpose of the runs test is to 17 | determine whether the number of runs of ones and zeros of various lengths is as 18 | expected for a random sequence. In particular, this test determines whether the 19 | oscillation between such zeros and ones is too fast or too slow. 20 | 21 | :param binary_data: The seuqnce of bit being tested 22 | :param verbose True to display the debug messgae, False to turn off debug message 23 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 24 | """ 25 | one_count = 0 26 | vObs = 0 27 | length_of_binary_data = len(binary_data) 28 | 29 | # Predefined tau = 2 / sqrt(n) 30 | # TODO Confirm with Frank about the discrepancy between the formula and the sample of 2.3.8 31 | tau = 2 / sqrt(length_of_binary_data) 32 | 33 | # Step 1 - Compute the pre-test proportion πof ones in the input sequence: π = Σjεj / n 34 | one_count = binary_data.count('1') 35 | 36 | pi = one_count / length_of_binary_data 37 | 38 | # Step 2 - If it can be shown that absolute value of (π - 0.5) is greater than or equal to tau 39 | # then the run test need not be performed. 40 | if abs(pi - 0.5) >= tau: 41 | ##print("The test should not have been run because of a failure to pass test 1, the Frequency (Monobit) test.") 42 | return (0.0000, False) 43 | else: 44 | # Step 3 - Compute vObs 45 | for item in range(1, length_of_binary_data): 46 | if binary_data[item] != binary_data[item - 1]: 47 | vObs += 1 48 | vObs += 1 49 | 50 | # Step 4 - Compute p_value = erfc((|vObs − 2nπ * (1−π)|)/(2 * sqrt(2n) * π * (1−π))) 51 | p_value = erfc(abs(vObs - (2 * (length_of_binary_data) * pi * (1 - pi))) / (2 * sqrt(2 * length_of_binary_data) * pi * (1 - pi))) 52 | 53 | if verbose: 54 | print('Run Test DEBUG BEGIN:') 55 | print("\tLength of input:\t\t\t\t", length_of_binary_data) 56 | print("\tTau (2/sqrt(length of input)):\t", tau) 57 | print('\t# of \'1\':\t\t\t\t\t\t', one_count) 58 | print('\t# of \'0\':\t\t\t\t\t\t', binary_data.count('0')) 59 | print('\tPI (1 count / length of input):\t', pi) 60 | print('\tvObs:\t\t\t\t\t\t\t', vObs) 61 | print('\tP-Value:\t\t\t\t\t\t', p_value) 62 | print('DEBUG END.') 63 | 64 | return (p_value, (p_value > 0.01)) 65 | 66 | @staticmethod 67 | def longest_one_block_test(binary_data:str, verbose=False): 68 | """ 69 | The focus of the test is the longest run of ones within M-bit blocks. The purpose of this test is to determine 70 | whether the length of the longest run of ones within the tested sequence is consistent with the length of the 71 | longest run of ones that would be expected in a random sequence. Note that an irregularity in the expected 72 | length of the longest run of ones implies that there is also an irregularity in the expected length of the 73 | longest run of zeroes. Therefore, only a test for ones is necessary. 74 | 75 | :param binary_data: The sequence of bits being tested 76 | :param verbose True to display the debug messgae, False to turn off debug message 77 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 78 | """ 79 | length_of_binary_data = len(binary_data) 80 | # print('Length of binary string: ', length_of_binary_data) 81 | 82 | # Initialized k, m. n, pi and v_values 83 | if length_of_binary_data < 128: 84 | # Not enough data to run this test 85 | return (0.00000, False, 'Error: Not enough data to run this test') 86 | elif length_of_binary_data < 6272: 87 | k = 3 88 | m = 8 89 | v_values = [1, 2, 3, 4] 90 | pi_values = [0.21484375, 0.3671875, 0.23046875, 0.1875] 91 | elif length_of_binary_data < 750000: 92 | k = 5 93 | m = 128 94 | v_values = [4, 5, 6, 7, 8, 9] 95 | pi_values = [0.1174035788, 0.242955959, 0.249363483, 0.17517706, 0.102701071, 0.112398847] 96 | else: 97 | # If length_of_bit_string > 750000 98 | k = 6 99 | m = 10000 100 | v_values = [10, 11, 12, 13, 14, 15, 16] 101 | pi_values = [0.0882, 0.2092, 0.2483, 0.1933, 0.1208, 0.0675, 0.0727] 102 | 103 | number_of_blocks = floor(length_of_binary_data / m) 104 | block_start = 0 105 | block_end = m 106 | xObs = 0 107 | # This will intialized an array with a number of 0 you specified. 108 | frequencies = zeros(k + 1) 109 | 110 | # print('Number of Blocks: ', number_of_blocks) 111 | 112 | for count in range(number_of_blocks): 113 | block_data = binary_data[block_start:block_end] 114 | max_run_count = 0 115 | run_count = 0 116 | 117 | # This will count the number of ones in the block 118 | for bit in block_data: 119 | if bit == '1': 120 | run_count += 1 121 | max_run_count = max(max_run_count, run_count) 122 | else: 123 | max_run_count = max(max_run_count, run_count) 124 | run_count = 0 125 | 126 | max(max_run_count, run_count) 127 | 128 | #print('Block Data: ', block_data, '. Run Count: ', max_run_count) 129 | 130 | if max_run_count < v_values[0]: 131 | frequencies[0] += 1 132 | for j in range(k): 133 | if max_run_count == v_values[j]: 134 | frequencies[j] += 1 135 | if max_run_count > v_values[k - 1]: 136 | frequencies[k] += 1 137 | 138 | block_start += m 139 | block_end += m 140 | 141 | # print("Frequencies: ", frequencies) 142 | # Compute xObs 143 | for count in range(len(frequencies)): 144 | xObs += pow((frequencies[count] - (number_of_blocks * pi_values[count])), 2.0) / ( 145 | number_of_blocks * pi_values[count]) 146 | 147 | p_value = gammaincc(float(k / 2), float(xObs / 2)) 148 | 149 | if verbose: 150 | print('Run Test (Longest Run of Ones in a Block) DEBUG BEGIN:') 151 | print("\tLength of input:\t\t\t\t", length_of_binary_data) 152 | print("\tSize of each Block:\t\t\t\t", m) 153 | print('\tNumber of Block:\t\t\t\t', number_of_blocks) 154 | print("\tValue of K:\t\t\t\t\t\t", k) 155 | print('\tValue of PIs:\t\t\t\t\t', pi_values) 156 | print('\tFrequencies:\t\t\t\t\t', frequencies) 157 | print('\txObs:\t\t\t\t\t\t\t', xObs) 158 | print('\tP-Value:\t\t\t\t\t\t', p_value) 159 | print('DEBUG END.') 160 | 161 | return (p_value, (p_value > 0.01)) -------------------------------------------------------------------------------- /Serial.py: -------------------------------------------------------------------------------- 1 | from numpy import zeros as zeros 2 | from scipy.special import gammaincc as gammaincc 3 | class Serial: 4 | 5 | @staticmethod 6 | def serial_test(binary_data:str, verbose=False, pattern_length=16): 7 | """ 8 | From the NIST documentation http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 9 | 10 | The focus of this test is the frequency of all possible overlapping m-bit patterns across the entire 11 | sequence. The purpose of this test is to determine whether the number of occurrences of the 2m m-bit 12 | overlapping patterns is approximately the same as would be expected for a random sequence. Random 13 | sequences have uniformity; that is, every m-bit pattern has the same chance of appearing as every other 14 | m-bit pattern. Note that for m = 1, the Serial test is equivalent to the Frequency test of Section 2.1. 15 | 16 | :param binary_data: a binary string 17 | :param verbose True to display the debug message, False to turn off debug message 18 | :param pattern_length: the length of the pattern (m) 19 | :return: ((p_value1, bool), (p_value2, bool)) A tuple which contain the p_value and result of serial_test(True or False) 20 | """ 21 | length_of_binary_data = len(binary_data) 22 | binary_data += binary_data[:(pattern_length -1):] 23 | 24 | # Get max length one patterns for m, m-1, m-2 25 | max_pattern = '' 26 | for i in range(pattern_length + 1): 27 | max_pattern += '1' 28 | 29 | # Step 02: Determine the frequency of all possible overlapping m-bit blocks, 30 | # all possible overlapping (m-1)-bit blocks and 31 | # all possible overlapping (m-2)-bit blocks. 32 | vobs_01 = zeros(int(max_pattern[0:pattern_length:], 2) + 1) 33 | vobs_02 = zeros(int(max_pattern[0:pattern_length - 1:], 2) + 1) 34 | vobs_03 = zeros(int(max_pattern[0:pattern_length - 2:], 2) + 1) 35 | 36 | for i in range(length_of_binary_data): 37 | # Work out what pattern is observed 38 | vobs_01[int(binary_data[i:i + pattern_length:], 2)] += 1 39 | vobs_02[int(binary_data[i:i + pattern_length - 1:], 2)] += 1 40 | vobs_03[int(binary_data[i:i + pattern_length - 2:], 2)] += 1 41 | 42 | vobs = [vobs_01, vobs_02, vobs_03] 43 | 44 | # Step 03 Compute for ψs 45 | sums = zeros(3) 46 | for i in range(3): 47 | for j in range(len(vobs[i])): 48 | sums[i] += pow(vobs[i][j], 2) 49 | sums[i] = (sums[i] * pow(2, pattern_length - i) / length_of_binary_data) - length_of_binary_data 50 | 51 | # Cimpute the test statistics and p values 52 | #Step 04 Compute for ∇ 53 | nabla_01 = sums[0] - sums[1] 54 | nabla_02 = sums[0] - 2.0 * sums[1] + sums[2] 55 | 56 | # Step 05 Compute for P-Value 57 | p_value_01 = gammaincc(pow(2, pattern_length - 1) / 2, nabla_01 / 2.0) 58 | p_value_02 = gammaincc(pow(2, pattern_length - 2) / 2, nabla_02 / 2.0) 59 | 60 | if verbose: 61 | print('Serial Test DEBUG BEGIN:') 62 | print("\tLength of input:\t", length_of_binary_data) 63 | print('\tValue of Sai:\t\t', sums) 64 | print('\tValue of Nabla:\t\t', nabla_01, nabla_02) 65 | print('\tP-Value 01:\t\t\t', p_value_01) 66 | print('\tP-Value 02:\t\t\t', p_value_02) 67 | print('DEBUG END.') 68 | 69 | return ((p_value_01, p_value_01 >= 0.01), (p_value_02, p_value_02 >= 0.01)) -------------------------------------------------------------------------------- /Spectral.py: -------------------------------------------------------------------------------- 1 | from math import fabs as fabs 2 | from math import floor as floor 3 | from math import log as log 4 | from math import sqrt as sqrt 5 | from numpy import where as where 6 | from scipy import fftpack as sff 7 | from scipy.special import erfc as erfc 8 | 9 | class SpectralTest: 10 | 11 | @staticmethod 12 | def spectral_test(binary_data:str, verbose=False): 13 | """ 14 | Note that this description is taken from the NIST documentation [1] 15 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 16 | The focus of this test is the peak heights in the Discrete Fourier Transform of the sequence. The purpose of 17 | this test is to detect periodic features (i.e., repetitive patterns that are near each other) in the tested 18 | sequence that would indicate a deviation from the assumption of randomness. The intention is to detect whether 19 | the number of peaks exceeding the 95 % threshold is significantly different than 5 %. 20 | 21 | :param binary_data: The seuqnce of bit being tested 22 | :param verbose True to display the debug messgae, False to turn off debug message 23 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 24 | """ 25 | length_of_binary_data = len(binary_data) 26 | plus_one_minus_one = [] 27 | 28 | # Step 1 - The zeros and ones of the input sequence (ε) are converted to values of –1 and +1 29 | # to create the sequence X = x1, x2, …, xn, where xi = 2εi – 1. 30 | for char in binary_data: 31 | if char == '0': 32 | plus_one_minus_one.append(-1) 33 | elif char == '1': 34 | plus_one_minus_one.append(1) 35 | 36 | # Step 2 - Apply a Discrete Fourier transform (DFT) on X to produce: S = DFT(X). 37 | # A sequence of complex variables is produced which represents periodic 38 | # components of the sequence of bits at different frequencies 39 | spectral = sff.fft(plus_one_minus_one) 40 | 41 | # Step 3 - Calculate M = modulus(S´) ≡ |S'|, where S´ is the substring consisting of the first n/2 42 | # elements in S, and the modulus function produces a sequence of peak heights. 43 | slice = floor(length_of_binary_data / 2) 44 | modulus = abs(spectral[0:slice]) 45 | 46 | # Step 4 - Compute T = sqrt(log(1 / 0.05) * length_of_string) the 95 % peak height threshold value. 47 | # Under an assumption of randomness, 95 % of the values obtained from the test should not exceed T. 48 | tau = sqrt(log(1 / 0.05) * length_of_binary_data) 49 | 50 | # Step 5 - Compute N0 = .95n/2. N0 is the expected theoretical (95 %) number of peaks 51 | # (under the assumption of randomness) that are less than T. 52 | n0 = 0.95 * (length_of_binary_data / 2) 53 | 54 | # Step 6 - Compute N1 = the actual observed number of peaks in M that are less than T. 55 | n1 = len(where(modulus < tau)[0]) 56 | 57 | # Step 7 - Compute d = (n_1 - n_0) / sqrt (length_of_string * (0.95) * (0.05) / 4) 58 | d = (n1 - n0) / sqrt(length_of_binary_data * (0.95) * (0.05) / 4) 59 | 60 | # Step 8 - Compute p_value = erfc(abs(d)/sqrt(2)) 61 | p_value = erfc(fabs(d) / sqrt(2)) 62 | 63 | if verbose: 64 | print('Discrete Fourier Transform (Spectral) Test DEBUG BEGIN:') 65 | print('\tLength of Binary Data:\t', length_of_binary_data) 66 | print('\tValue of T:\t\t\t\t', tau) 67 | print('\tValue of n1:\t\t\t', n1) 68 | print('\tValue of n0:\t\t\t', n0) 69 | print('\tValue of d:\t\t\t\t', d) 70 | print('\tP-Value:\t\t\t\t', p_value) 71 | print('DEBUG END.') 72 | 73 | return (p_value, (p_value >= 0.01)) 74 | -------------------------------------------------------------------------------- /TemplateMatching.py: -------------------------------------------------------------------------------- 1 | from math import floor as floor 2 | from numpy import array as array 3 | from numpy import exp as exp 4 | from numpy import zeros as zeros 5 | from scipy.special import gammaincc as gammaincc 6 | from scipy.special import hyp1f1 as hyp1f1 7 | 8 | 9 | class TemplateMatching: 10 | 11 | @staticmethod 12 | def non_overlapping_test(binary_data:str, verbose=False, template_pattern='000000001', block=8): 13 | """ 14 | Note that this description is taken from the NIST documentation [1] 15 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 16 | The focus of this test is the number of occurrences of pre-specified target strings. The purpose of this 17 | test is to detect generators that produce too many occurrences of a given non-periodic (aperiodic) pattern. 18 | For this test and for the Overlapping Template Matching test of Section 2.8, an m-bit window is used to 19 | search for a specific m-bit pattern. If the pattern is not found, the window slides one bit position. If the 20 | pattern is found, the window is reset to the bit after the found pattern, and the search resumes. 21 | :param binary_data: The seuqnce of bit being tested 22 | :param template_pattern: The pattern to match to 23 | :param verbose True to display the debug messgae, False to turn off debug message 24 | :param block The number of independent blocks. Has been fixed at 8 in the test code. 25 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 26 | """ 27 | 28 | length_of_binary = len(binary_data) 29 | pattern_size = len(template_pattern) 30 | block_size = floor(length_of_binary / block) 31 | pattern_counts = zeros(block) 32 | 33 | # For each block in the data 34 | for count in range(block): 35 | block_start = count * block_size 36 | block_end = block_start + block_size 37 | block_data = binary_data[block_start:block_end] 38 | # Count the number of pattern hits 39 | inner_count = 0 40 | while inner_count < block_size: 41 | sub_block = block_data[inner_count:inner_count+pattern_size] 42 | if sub_block == template_pattern: 43 | pattern_counts[count] += 1 44 | inner_count += pattern_size 45 | else: 46 | inner_count += 1 47 | 48 | # Calculate the theoretical mean and variance 49 | # Mean - µ = (M-m+1)/2m 50 | mean = (block_size - pattern_size + 1) / pow(2, pattern_size) 51 | # Variance - σ2 = M((1/pow(2,m)) - ((2m -1)/pow(2, 2m))) 52 | variance = block_size * ((1 / pow(2, pattern_size)) - (((2 * pattern_size) - 1) / (pow(2, pattern_size * 2)))) 53 | 54 | # Calculate the xObs Squared statistic for these pattern matches 55 | xObs = 0 56 | for count in range(block): 57 | xObs += pow((pattern_counts[count] - mean), 2.0) / variance 58 | 59 | # Calculate and return the p value statistic 60 | p_value = gammaincc((block / 2), (xObs / 2)) 61 | 62 | if verbose: 63 | print('Non-Overlapping Template Test DEBUG BEGIN:') 64 | print("\tLength of input:\t\t", length_of_binary) 65 | print('\tValue of Mean (µ):\t\t', mean) 66 | print('\tValue of Variance(σ):\t', variance) 67 | print('\tValue of W:\t\t\t\t', pattern_counts) 68 | print('\tValue of xObs:\t\t\t', xObs) 69 | print('\tP-Value:\t\t\t\t', p_value) 70 | print('DEBUG END.') 71 | 72 | return (p_value, (p_value >= 0.01)) 73 | 74 | @staticmethod 75 | def overlapping_patterns(binary_data:str, verbose=False, pattern_size=9, block_size=1032): 76 | """ 77 | Note that this description is taken from the NIST documentation [1] 78 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 79 | The focus of the Overlapping Template Matching test is the number of occurrences of pre-specified target 80 | strings. Both this test and the Non-overlapping Template Matching test of Section 2.7 use an m-bit 81 | window to search for a specific m-bit pattern. As with the test in Section 2.7, if the pattern is not found, 82 | the window slides one bit position. The difference between this test and the test in Section 2.7 is that 83 | when the pattern is found, the window slides only one bit before resuming the search. 84 | 85 | :param binary_data: a binary string 86 | :param verbose True to display the debug messgae, False to turn off debug message 87 | :param pattern_size: the length of the pattern 88 | :param block_size: the length of the block 89 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 90 | """ 91 | length_of_binary_data = len(binary_data) 92 | pattern = '' 93 | for count in range(pattern_size): 94 | pattern += '1' 95 | 96 | number_of_block = floor(length_of_binary_data / block_size) 97 | 98 | # λ = (M-m+1)/pow(2, m) 99 | lambda_val = float(block_size - pattern_size + 1) / pow(2, pattern_size) 100 | # η = λ/2 101 | eta = lambda_val / 2.0 102 | 103 | pi = [TemplateMatching.get_prob(i, eta) for i in range(5)] 104 | diff = float(array(pi).sum()) 105 | pi.append(1.0 - diff) 106 | 107 | pattern_counts = zeros(6) 108 | for i in range(number_of_block): 109 | block_start = i * block_size 110 | block_end = block_start + block_size 111 | block_data = binary_data[block_start:block_end] 112 | # Count the number of pattern hits 113 | pattern_count = 0 114 | j = 0 115 | while j < block_size: 116 | sub_block = block_data[j:j + pattern_size] 117 | if sub_block == pattern: 118 | pattern_count += 1 119 | j += 1 120 | if pattern_count <= 4: 121 | pattern_counts[pattern_count] += 1 122 | else: 123 | pattern_counts[5] += 1 124 | 125 | xObs = 0.0 126 | for i in range(len(pattern_counts)): 127 | xObs += pow(pattern_counts[i] - number_of_block * pi[i], 2.0) / (number_of_block * pi[i]) 128 | 129 | p_value = gammaincc(5.0 / 2.0, xObs / 2.0) 130 | 131 | if verbose: 132 | print('Overlapping Template Test DEBUG BEGIN:') 133 | print("\tLength of input:\t\t", length_of_binary_data) 134 | print('\tValue of Vs:\t\t\t', pattern_counts) 135 | print('\tValue of xObs:\t\t\t', xObs) 136 | print('\tP-Value:\t\t\t\t', p_value) 137 | print('DEBUG END.') 138 | 139 | 140 | return (p_value, (p_value >= 0.01)) 141 | 142 | @staticmethod 143 | def get_prob(u, x): 144 | out = 1.0 * exp(-x) 145 | if u != 0: 146 | out = 1.0 * x * exp(2 * -x) * (2 ** -u) * hyp1f1(u + 1, 2, x) 147 | return out -------------------------------------------------------------------------------- /Tools.py: -------------------------------------------------------------------------------- 1 | class Tools: 2 | 3 | @staticmethod 4 | def string_to_binary(input:str): 5 | binary = [] 6 | for char in input: 7 | temp = bin(ord(char))[2:] 8 | while(len(temp) < 8): 9 | temp = '0' + temp 10 | binary.append(temp) 11 | 12 | return ''.join(binary) 13 | 14 | @staticmethod 15 | def string_to_binary_no_concat(input: str): 16 | binary = [] 17 | for char in input: 18 | binary.append(bin(ord(char))[2:]) 19 | 20 | return ''.join(binary) 21 | 22 | @staticmethod 23 | def url_to_binary(input:str): 24 | binary = [] 25 | url = input.split('/')[-1].split('.')[0] 26 | 27 | return url 28 | 29 | @staticmethod 30 | def bytes_to_binary(input:bytes): 31 | binary = [] 32 | for b in input: 33 | binary.append(f'{b:08b}') 34 | return ''.join(binary) 35 | -------------------------------------------------------------------------------- /Universal.py: -------------------------------------------------------------------------------- 1 | from math import floor as floor 2 | from math import log as log 3 | from math import sqrt as sqrt 4 | from numpy import zeros as zeros 5 | from scipy.special import erfc as erfc 6 | 7 | class Universal: 8 | 9 | @staticmethod 10 | def statistical_test(binary_data:str, verbose=False): 11 | """ 12 | Note that this description is taken from the NIST documentation [1] 13 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf 14 | The focus of this test is the number of bits between matching patterns (a measure that is related to the 15 | length of a compressed sequence). The purpose of the test is to detect whether or not the sequence can be 16 | significantly compressed without loss of information. A significantly compressible sequence is considered 17 | to be non-random. **This test is always skipped because the requirements on the lengths of the binary 18 | strings are too high i.e. there have not been enough trading days to meet the requirements. 19 | 20 | :param binary_data: a binary string 21 | :param verbose True to display the debug messgae, False to turn off debug message 22 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False) 23 | """ 24 | length_of_binary_data = len(binary_data) 25 | pattern_size = 5 26 | if length_of_binary_data >= 387840: 27 | pattern_size = 6 28 | if length_of_binary_data >= 904960: 29 | pattern_size = 7 30 | if length_of_binary_data >= 2068480: 31 | pattern_size = 8 32 | if length_of_binary_data >= 4654080: 33 | pattern_size = 9 34 | if length_of_binary_data >= 10342400: 35 | pattern_size = 10 36 | if length_of_binary_data >= 22753280: 37 | pattern_size = 11 38 | if length_of_binary_data >= 49643520: 39 | pattern_size = 12 40 | if length_of_binary_data >= 107560960: 41 | pattern_size = 13 42 | if length_of_binary_data >= 231669760: 43 | pattern_size = 14 44 | if length_of_binary_data >= 496435200: 45 | pattern_size = 15 46 | if length_of_binary_data >= 1059061760: 47 | pattern_size = 16 48 | 49 | if 5 < pattern_size < 16: 50 | # Create the biggest binary string of length pattern_size 51 | ones = "" 52 | for i in range(pattern_size): 53 | ones += "1" 54 | 55 | # How long the state list should be 56 | num_ints = int(ones, 2) 57 | vobs = zeros(num_ints + 1) 58 | 59 | # Keeps track of the blocks, and whether were are initializing or summing 60 | num_blocks = floor(length_of_binary_data / pattern_size) 61 | # Q = 10 * pow(2, pattern_size) 62 | init_bits = 10 * pow(2, pattern_size) 63 | 64 | test_bits = num_blocks - init_bits 65 | 66 | # These are the expected values assuming randomness (uniform) 67 | c = 0.7 - 0.8 / pattern_size + (4 + 32 / pattern_size) * pow(test_bits, -3 / pattern_size) / 15 68 | variance = [0, 0, 0, 0, 0, 0, 2.954, 3.125, 3.238, 3.311, 3.356, 3.384, 3.401, 3.410, 3.416, 3.419, 3.421] 69 | expected = [0, 0, 0, 0, 0, 0, 5.2177052, 6.1962507, 7.1836656, 8.1764248, 9.1723243, 70 | 10.170032, 11.168765, 12.168070, 13.167693, 14.167488, 15.167379] 71 | sigma = c * sqrt(variance[pattern_size] / test_bits) 72 | 73 | cumsum = 0.0 74 | # Examine each of the K blocks in the test segment and determine the number of blocks since the 75 | # last occurrence of the same L-bit block (i.e., i – Tj). Replace the value in the table with the 76 | # location of the current block (i.e., Tj= i). Add the calculated distance between re-occurrences of 77 | # the same L-bit block to an accumulating log2 sum of all the differences detected in the K blocks 78 | for i in range(num_blocks): 79 | block_start = i * pattern_size 80 | block_end = block_start + pattern_size 81 | block_data = binary_data[block_start: block_end] 82 | # Work out what state we are in 83 | int_rep = int(block_data, 2) 84 | 85 | # Initialize the state list 86 | if i < init_bits: 87 | vobs[int_rep] = i + 1 88 | else: 89 | initial = vobs[int_rep] 90 | vobs[int_rep] = i + 1 91 | cumsum += log(i - initial + 1, 2) 92 | 93 | # Compute the statistic 94 | phi = float(cumsum / test_bits) 95 | stat = abs(phi - expected[pattern_size]) / (float(sqrt(2)) * sigma) 96 | 97 | # Compute for P-Value 98 | p_value = erfc(stat) 99 | 100 | if verbose: 101 | print('Maurer\'s Universal Statistical Test DEBUG BEGIN:') 102 | print("\tLength of input:\t\t", length_of_binary_data) 103 | print('\tLength of each block:\t', pattern_size) 104 | print('\tNumber of Blocks:\t\t', init_bits) 105 | print('\tValue of phi:\t\t\t', phi) 106 | print('\tP-Value:\t\t\t\t', p_value) 107 | print('DEBUG END.') 108 | 109 | return (p_value, (p_value>=0.01)) 110 | else: 111 | return (-1.0, False) -------------------------------------------------------------------------------- /data/test_data.bin: -------------------------------------------------------------------------------- 1 | y�ЏU�lm�� �%��?�벗�R�'0Ƙ���gC4�f;�e�g�pYq�©�+��%P�M�K�W�R�~v=�����Dd���ɐg�%���H�I�讐��A:80,� t�ɶ���) x�S��$�߮D�O��ݱ�2w���n�G2ux>x���_z@{���ȧ�Z���Ș���Y�+��<�%MN����2@-T}�?~\�f���������`Y�\�j��C�˶�f����Mg���$|kMe���x���� ��h�Up{����cs�����Q�������^Z�[���J�V@zM�[�L�d%U�((m34�EH����o����e&S�� �����Ш��%�}��u���-����9G����tcy�9(" q�llU��K4����ċ�,#tdt`%�o���(Ei���D�!X�_���� GY{{\-伐����ߴolj�Lk�`<���{ W�i/����[��U���R��l�!r}NY5� �o�9������:#T͕� -------------------------------------------------------------------------------- /data/test_data_01.txt: -------------------------------------------------------------------------------- 1 | 400568534f926c25400568534fe6c4f9400568535038979e4005685350b507e44005685351a813ff4005685352429fac4005685352ee2428400568535401e4084005685354a9a8fa4005685355a6f07e4005685356fc58d34005685359642c454005685359bd9fb3400568535a6a4cb6400568535ac8a044400568535b586d24400568535bb0bcf6400568535c0410c5400568535c64381b400568535cfce007400568535d4d83f4 -------------------------------------------------------------------------------- /data/test_data_02.txt: -------------------------------------------------------------------------------- 1 | http://g-ugc.oovoo.com/nemo-ugc/40056853a6d0a17d.jpg 2 | http://g-ugc.oovoo.com/nemo-ugc/40056853a6897dd2.png 3 | http://g-ugc.oovoo.com/nemo-ugc/40056853af4c2529.jpg 4 | http://g-ugc.oovoo.com/nemo-ugc/40056853af08f8b7.jpg 5 | http://g-ugc.oovoo.com/nemo-ugc/40056853af90292a.png 6 | http://g-ugc.oovoo.com/nemo-ugc/40056853afcb8996.png 7 | http://g-ugc.oovoo.com/nemo-ugc/40056853b1c2b900.jpg 8 | http://g-ugc.oovoo.com/nemo-ugc/40056853b23d01a5.png 9 | http://g-ugc.oovoo.com/nemo-ugc/40056853b2078cc0.jpg 10 | http://g-ugc.oovoo.com/nemo-ugc/40056853b2781477.png -------------------------------------------------------------------------------- /result/20180107_Binary_Data.txt: -------------------------------------------------------------------------------- 1 | Test Data:1100100100001111110110101010001000100001011010001100001000110100110001001100011001100010100010111000 2 | 3 | 4 | Type of Test P-Value Conclusion 5 | 01. Frequency Test (Monobit) 0.109598583399 Random 6 | 02. Frequency Test within a Block 0.109598583399 Random 7 | 03. Run Test 0.500797917887 Random 8 | 04. Longest Run of Ones in a Block 0.0 Non-Random 9 | 05. Binary Matrix Rank Test -1.0 Non-Random 10 | 06. Discrete Fourier Transform (Spectral) Test 0.646355195539 Random 11 | 07. Non-Overlapping Template Matching Test 0.999999999443 Random 12 | 08. Overlapping Template Matching Test nan Non-Random 13 | 09. Maurer's Universal Statistical test -1.0 Non-Random 14 | 10. Linear Complexity Test -1.0 Non-Random 15 | 11. Serial test: 16 | 0.498961087459 Random 17 | 0.498530755297 Random 18 | 12. Approximate Entropy Test 1.0 Random 19 | 13. Cummulative Sums (Forward) Test 0.219194786814 Random 20 | 14. Cummulative Sums (Reverse) Test 0.114866221293 Random 21 | 15. Random Excursions Test: 22 | State Chi Squared P-Value Conclusion 23 | -4 8.17415362647 0.146895259998 Random 24 | -3 10.8628571429 0.0541679551199 Random 25 | -2 9.95238095238 0.0765958095212 Random 26 | -1 2.71428571429 0.743932648724 Random 27 | +1 1.28571428571 0.936394780791 Random 28 | +2 1.85361552028 0.869006618022 Random 29 | +3 8.05828571429 0.153052506984 Random 30 | +4 1.0 0.962565773247 Random 31 | 16. Random Excursions Variant Test: 32 | State COUNTS P-Value Conclusion 33 | -9.0 6 0.948317020766 Random 34 | -8.0 5 0.890230054943 Random 35 | -7.0 2 0.710917113389 Random 36 | -6.0 2 0.687013344233 Random 37 | -5.0 5 0.858586201337 Random 38 | -4.0 7 1.0 Random 39 | -3.0 5 0.811070129334 Random 40 | -2.0 4 0.643428843564 Random 41 | -1.0 5 0.592980098017 Random 42 | +1.0 7 1.0 Random 43 | +2.0 7 1.0 Random 44 | +3.0 4 0.719917853194 Random 45 | -------------------------------------------------------------------------------- /result/20180107_Binary_File.txt: -------------------------------------------------------------------------------- 1 | Test Data File:/Users/stevenkhoang/PycharmProjects/Randomness_Testing/data/data.e 2 | 3 | 4 | Type of Test P-Value Conclusion 5 | 01. Frequency Test (Monobit) 0.953748628528 Random 6 | 02. Frequency Test within a Block 0.211071543702 Random 7 | 03. Run Test 0.56191688503 Random 8 | 04. Longest Run of Ones in a Block 0.718945329899 Random 9 | 05. Binary Matrix Rank Test 0.3061558375306767 Random 10 | 06. Discrete Fourier Transform (Spectral) Test 0.847186705069 Random 11 | 07. Non-Overlapping Template Matching Test 0.0787901326767 Random 12 | 08. Overlapping Template Matching Test 0.110433685414 Random 13 | 09. Maurer's Universal Statistical test 0.282567947826 Random 14 | 10. Linear Complexity Test 0.826334770404 Random 15 | 11. Serial test: 16 | 0.766181646833 Random 17 | 0.462921324096 Random 18 | 12. Approximate Entropy Test 0.700073388115 Random 19 | 13. Cummulative Sums (Forward) Test 0.669886464168 Random 20 | 14. Cummulative Sums (Reverse) Test 0.72426530997 Random 21 | 15. Random Excursions Test: 22 | State Chi Squared P-Value Conclusion 23 | -4 3.83569821299 0.573305694995 Random 24 | -3 7.31870711409 0.197996020218 Random 25 | -2 7.86192725164 0.164011049379 Random 26 | -1 15.6926174497 0.00777872309647 Non-Random 27 | +1 2.43087248322 0.786867905178 Random 28 | +2 4.79890628884 0.440911736646 Random 29 | +3 2.35704053691 0.797853971688 Random 30 | +4 2.4887672642 0.778185785232 Random 31 | 16. Random Excursions Variant Test: 32 | State COUNTS P-Value Conclusion 33 | -9.0 1450 0.858945739825 Random 34 | -8.0 1435 0.794754956255 Random 35 | -7.0 1380 0.576248618468 Random 36 | -6.0 1366 0.493416934086 Random 37 | -5.0 1412 0.633872669141 Random 38 | -4.0 1475 0.917283147792 Random 39 | -3.0 1480 0.934707791835 Random 40 | -2.0 1468 0.816012036618 Random 41 | -1.0 1502 0.826009012833 Random 42 | +1.0 1409 0.137860608909 Random 43 | +2.0 1369 0.200641913855 Random 44 | +3.0 1396 0.441253622156 Random 45 | +4.0 1479 0.939290606068 Random 46 | +5.0 1599 0.505682682169 Random 47 | +6.0 1628 0.44593471065 Random 48 | +7.0 1619 0.512206885616 Random 49 | +8.0 1620 0.538634697777 Random 50 | +9.0 1610 0.593930395822 Random 51 | -------------------------------------------------------------------------------- /result/20180117.txt: -------------------------------------------------------------------------------- 1 | Test Data File:/Users/stevenang/PycharmProjects/randomness_testsuite/data/data.e 2 | 3 | 4 | Type of Test P-Value Conclusion 5 | 01. Frequency Test (Monobit) 0.9537486285283232 Random 6 | 02. Frequency Test within a Block 0.21107154370164066 Random 7 | 03. Run Test 0.5619168850302545 Random 8 | 04. Longest Run of Ones in a Block 0.7189453298987654 Random 9 | 05. Binary Matrix Rank Test 0.3061558375306767 Random 10 | 06. Discrete Fourier Transform (Spectral) Test 0.8471867050687718 Random 11 | 07. Non-Overlapping Template Matching Test 0.07879013267666338 Random 12 | 08. Overlapping Template Matching Test 0.11043368541387631 Random 13 | 09. Maurer's Universal Statistical test 0.282567947825744 Random 14 | 10. Linear Complexity Test 0.8263347704038304 Random 15 | 11. Serial test: 16 | 0.766181646833394 Random 17 | 0.46292132409575854 Random 18 | 12. Approximate Entropy Test 0.7000733881151612 Random 19 | 13. Cummulative Sums (Forward) Test 0.6698864641681423 Random 20 | 14. Cummulative Sums (Reverse) Test 0.7242653099698069 Random 21 | 15. Random Excursions Test: 22 | State Chi Squared P-Value Conclusion 23 | -4 3.8356982129929085 0.5733056949947805 Random 24 | -3 7.318707114093956 0.19799602021827734 Random 25 | -2 7.861927251636425 0.16401104937943733 Random 26 | -1 15.69261744966443 0.007778723096466819 Non-Random 27 | +1 2.4308724832214765 0.7868679051783156 Random 28 | +2 4.7989062888391745 0.44091173664620265 Random 29 | +3 2.3570405369127525 0.7978539716877826 Random 30 | +4 2.4887672641992014 0.7781857852321322 Random 31 | 16. Random Excursions Variant Test: 32 | State COUNTS P-Value Conclusion 33 | -9.0 1450 0.8589457398254003 Random 34 | -8.0 1435 0.7947549562546549 Random 35 | -7.0 1380 0.5762486184682754 Random 36 | -6.0 1366 0.4934169340861271 Random 37 | -5.0 1412 0.6338726691411485 Random 38 | -4.0 1475 0.9172831477915963 Random 39 | -3.0 1480 0.9347077918349618 Random 40 | -2.0 1468 0.8160120366175745 Random 41 | -1.0 1502 0.8260090128330382 Random 42 | +1.0 1409 0.13786060890864768 Random 43 | +2.0 1369 0.20064191385523023 Random 44 | +3.0 1396 0.4412536221564536 Random 45 | +4.0 1479 0.939290606067626 Random 46 | +5.0 1599 0.5056826821687638 Random 47 | +6.0 1628 0.4459347106499899 Random 48 | +7.0 1619 0.5122068856164792 Random 49 | +8.0 1620 0.5386346977772863 Random 50 | +9.0 1610 0.5939303958223099 Random 51 | -------------------------------------------------------------------------------- /result/20180117_URL_Result.txt: -------------------------------------------------------------------------------- 1 | Test Data File:/Users/stevenang/PycharmProjects/randomness_testsuite/data/test_data_02.txt 2 | 3 | Type of Test P-Value Conclusion 4 | 01. Frequency Test (Monobit) 0.033310134921341245 Random 5 | 02. Frequency Test within a Block 0.7542651538756742 Random 6 | 03. Run Test 0.0940470056888701 Random 7 | 04. Longest Run of Ones in a Block 3.380651398651423e-10 Non-Random 8 | 05. Binary Matrix Rank Test -1.0 Non-Random 9 | 06. Discrete Fourier Transform (Spectral) Test 0.010978475273537742 Random 10 | 07. Non-Overlapping Template Matching Test 0.9880245046396146 Random 11 | 08. Overlapping Template Matching Test nan Non-Random 12 | 09. Maurer's Universal Statistical test -1.0 Non-Random 13 | 10. Linear Complexity Test -1.0 Non-Random 14 | 11. Serial test: 15 | 0.0 Non-Random 16 | 0.0 Non-Random 17 | 12. Approximate Entropy Test 0.9776438837438873 Random 18 | 13. Cummulative Sums (Forward) Test 0.06662026949983221 Random 19 | 14. Cummulative Sums (Reverse) Test 0.04821780377885077 Random 20 | 15. Random Excursions Test: 21 | State Chi Squared P-Value Conclusion 22 | -4 5.207792207792208 0.3910510265515671 Random 23 | -3 5.564363636363635 0.3509397816064102 Random 24 | -2 3.7205387205387206 0.5903104834669745 Random 25 | -1 3.909090909090909 0.5625775966348263 Random 26 | +1 11.363636363636363 0.044628250164521314 Random 27 | +2 17.031425364758697 0.00444058191019402 Non-Random 28 | +3 14.469672727272728 0.012885778756621539 Random 29 | +4 9.29699746317822 0.09778837672183648 Random 30 | 16. Random Excursions Variant Test: 31 | State COUNTS P-Value Conclusion 32 | -4.0 1 0.42034493503392867 Random 33 | -3.0 4 0.5045014597713458 Random 34 | -2.0 5 0.46018093544712035 Random 35 | -1.0 5 0.20082512269514552 Random 36 | +1.0 19 0.08808151166219029 Random 37 | +2.0 21 0.21835469056590173 Random 38 | +3.0 17 0.5672694352671082 Random 39 | +4.0 17 0.6287451762738331 Random 40 | +5.0 23 0.3937686346429927 Random 41 | +6.0 19 0.6070705891514693 Random 42 | +7.0 8 0.8591991407511987 Random 43 | +8.0 5 0.7411815058736042 Random 44 | +9.0 7 0.836138661741041 Random 45 | -------------------------------------------------------------------------------- /test_bin_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | from Tools import Tools 3 | 4 | from FrequencyTest import FrequencyTest as ft 5 | from RunTest import RunTest as rt 6 | from Matrix import Matrix as mt 7 | from Spectral import SpectralTest as st 8 | from TemplateMatching import TemplateMatching as tm 9 | from Universal import Universal as ut 10 | from Complexity import ComplexityTest as ct 11 | from Serial import Serial as serial 12 | from ApproximateEntropy import ApproximateEntropy as aet 13 | from CumulativeSum import CumulativeSums as cst 14 | from RandomExcursions import RandomExcursions as ret 15 | 16 | test_type = ['01. Frequency Test (Monobit)', '02. Frequency Test within a Block', '03. Run Test', 17 | '04. Longest Run of Ones in a Block', '05. Binary Matrix Rank Test', 18 | '06. Discrete Fourier Transform (Spectral) Test', 19 | '07. Non-Overlapping Template Matching Test', 20 | '08. Overlapping Template Matching Test', '09. Maurer\'s Universal Statistical test', 21 | '10. Linear Complexity Test', '11. Serial test', '12. Approximate Entropy Test', 22 | '13. Cummulative Sums (Forward) Test', '14. Cummulative Sums (Reverse) Test', 23 | '15. Random Excursions Test', '16. Random Excursions Variant Test'] 24 | 25 | test_function = { 26 | 0:ft.monobit_test, 27 | 1:ft.block_frequency, 28 | 2:rt.run_test, 29 | 3:rt.longest_one_block_test, 30 | 4:mt.binary_matrix_rank_text, 31 | 5:st.spectral_test, 32 | 6:tm.non_overlapping_test, 33 | 7:tm.overlapping_patterns, 34 | 8:ut.statistical_test, 35 | 9:ct.linear_complexity_test, 36 | 10:serial.serial_test, 37 | 11:aet.approximate_entropy_test, 38 | 12:cst.cumulative_sums_test, 39 | 13:cst.cumulative_sums_test, 40 | 14:ret.random_excursions_test, 41 | 15:ret.variant_test 42 | } 43 | 44 | input = b'' 45 | with open(os.path.join(os.getcwd(), 'data', 'test_data.bin'), 'rb') as input_file: 46 | input = input_file.read() 47 | 48 | 49 | binary = Tools.bytes_to_binary(input) 50 | count = 0 51 | 52 | for test in test_function: 53 | print(test_type[count%len(test_type)], test_function[count](binary)) 54 | count += 1 55 | -------------------------------------------------------------------------------- /test_e.py: -------------------------------------------------------------------------------- 1 | import os 2 | from FrequencyTest import FrequencyTest 3 | from RunTest import RunTest 4 | from Matrix import Matrix 5 | from Spectral import SpectralTest 6 | from TemplateMatching import TemplateMatching 7 | from Universal import Universal 8 | from Complexity import ComplexityTest 9 | from Serial import Serial 10 | from ApproximateEntropy import ApproximateEntropy 11 | from CumulativeSum import CumulativeSums 12 | from RandomExcursions import RandomExcursions 13 | 14 | # Open Data File and read the binary data of e 15 | data_path = os.path.join(os.getcwd(), 'data', 'data.e') 16 | handle = open(data_path) 17 | data_list = [] 18 | 19 | for line in handle: 20 | data_list.append(line.strip().rstrip()) 21 | 22 | binary_data = ''.join(data_list) 23 | 24 | print('The statistical test of the Binary Expansion of e') 25 | print('2.01. Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000])) 26 | print('2.02. Block Frequency Test:\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000])) 27 | print('2.03. Run Test:\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000])) 28 | print('2.04. Run Test (Longest Run of Ones): \t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000])) 29 | print('2.05. Binary Matrix Rank Test:\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000])) 30 | print('2.06. Discrete Fourier Transform (Spectral) Test:\t', SpectralTest.spectral_test(binary_data[:1000000])) 31 | print('2.07. Non-overlapping Template Matching Test:\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001')) 32 | print('2.08. Overlappong Template Matching Test: \t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000])) 33 | print('2.09. Universal Statistical Test:\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000])) 34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000])) 35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000])) 36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000])) 37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0)) 38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1)) 39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000]) 40 | print('2.14. Random Excursion Test:') 41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion') 42 | 43 | for item in result: 44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t', 45 | (item[4] >= 0.01)) 46 | 47 | result = RandomExcursions.variant_test(binary_data[:1000000]) 48 | 49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t') 50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion') 51 | for item in result: 52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t', 53 | (item[4] >= 0.01)) -------------------------------------------------------------------------------- /test_pi.py: -------------------------------------------------------------------------------- 1 | import os 2 | from FrequencyTest import FrequencyTest 3 | from RunTest import RunTest 4 | from Matrix import Matrix 5 | from Spectral import SpectralTest 6 | from TemplateMatching import TemplateMatching 7 | from Universal import Universal 8 | from Complexity import ComplexityTest 9 | from Serial import Serial 10 | from ApproximateEntropy import ApproximateEntropy 11 | from CumulativeSum import CumulativeSums 12 | from RandomExcursions import RandomExcursions 13 | 14 | # Open Data File and read the binary data of e 15 | data_path = os.path.join(os.getcwd(), 'data', 'data.pi') 16 | handle = open(data_path) 17 | data_list = [] 18 | 19 | for line in handle: 20 | data_list.append(line.strip().rstrip()) 21 | 22 | binary_data = ''.join(data_list) 23 | 24 | print('The statistical test of the Binary Expansion of PI') 25 | print('2.1. Frequency Test:\t\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000])) 26 | print('2.2. Block Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000])) 27 | print('2.3. Run Test:\t\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000])) 28 | print('2.4. Run Test (Longest Run of Ones): \t\t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000])) 29 | print('2.5. Binary Matrix Rank Test:\t\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000])) 30 | print('2.6. Discrete Fourier Transform (Spectral) Test: \t\t', SpectralTest.spectral_test(binary_data[:1000000])) 31 | print('2.7. Non-overlapping Template Matching Test:\t\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001')) 32 | print('2.8. Overlappong Template Matching Test: \t\t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000])) 33 | print('2.9. Universal Statistical Test:\t\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000])) 34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000])) 35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000])) 36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000])) 37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0)) 38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1)) 39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000]) 40 | print('2.14. Random Excursion Test:') 41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion') 42 | 43 | for item in result: 44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t', 45 | (item[3] >= 0.01)) 46 | 47 | result = RandomExcursions.variant_test(binary_data[:1000000]) 48 | 49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t') 50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion') 51 | for item in result: 52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t', 53 | (item[3] >= 0.01)) -------------------------------------------------------------------------------- /test_sqrt2.py: -------------------------------------------------------------------------------- 1 | import os 2 | from FrequencyTest import FrequencyTest 3 | from RunTest import RunTest 4 | from Matrix import Matrix 5 | from Spectral import SpectralTest 6 | from TemplateMatching import TemplateMatching 7 | from Universal import Universal 8 | from Complexity import ComplexityTest 9 | from Serial import Serial 10 | from ApproximateEntropy import ApproximateEntropy 11 | from CumulativeSum import CumulativeSums 12 | from RandomExcursions import RandomExcursions 13 | 14 | # Open Data File and read the binary data of e 15 | data_path = os.path.join(os.getcwd(), 'data', 'data.sqrt2') 16 | handle = open(data_path) 17 | data_list = [] 18 | 19 | for line in handle: 20 | data_list.append(line.strip().rstrip()) 21 | 22 | binary_data = ''.join(data_list) 23 | 24 | print('The statistical test of the Binary Expansion of SQRT(2)') 25 | print('2.1. Frequency Test:\t\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000])) 26 | print('2.2. Block Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000])) 27 | print('2.3. Run Test:\t\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000])) 28 | print('2.4. Run Test (Longest Run of Ones): \t\t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000])) 29 | print('2.5. Binary Matrix Rank Test:\t\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000])) 30 | print('2.6. Discrete Fourier Transform (Spectral) Test: \t\t', SpectralTest.spectral_test(binary_data[:1000000])) 31 | print('2.7. Non-overlapping Template Matching Test:\t\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001')) 32 | print('2.8. Overlappong Template Matching Test: \t\t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000])) 33 | print('2.9. Universal Statistical Test:\t\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000])) 34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000])) 35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000])) 36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000])) 37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0)) 38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1)) 39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000]) 40 | print('2.14. Random Excursion Test:') 41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion') 42 | 43 | for item in result: 44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t', 45 | (item[3] >= 0.01)) 46 | 47 | result = RandomExcursions.variant_test(binary_data[:1000000]) 48 | 49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t') 50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion') 51 | for item in result: 52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t', 53 | (item[3] >= 0.01)) -------------------------------------------------------------------------------- /test_sqrt3.py: -------------------------------------------------------------------------------- 1 | import os 2 | from FrequencyTest import FrequencyTest 3 | from RunTest import RunTest 4 | from Matrix import Matrix 5 | from Spectral import SpectralTest 6 | from TemplateMatching import TemplateMatching 7 | from Universal import Universal 8 | from Complexity import ComplexityTest 9 | from Serial import Serial 10 | from ApproximateEntropy import ApproximateEntropy 11 | from CumulativeSum import CumulativeSums 12 | from RandomExcursions import RandomExcursions 13 | 14 | # Open Data File and read the binary data of e 15 | data_path = os.path.join(os.getcwd(), 'data', 'data.sqrt3') 16 | handle = open(data_path) 17 | data_list = [] 18 | 19 | for line in handle: 20 | data_list.append(line.strip().rstrip()) 21 | 22 | binary_data = ''.join(data_list) 23 | 24 | print('The statistical test of the Binary Expansion of SQRT(3)') 25 | print('2.1. Frequency Test:\t\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000])) 26 | print('2.2. Block Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000])) 27 | print('2.3. Run Test:\t\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000])) 28 | print('2.4. Run Test (Longest Run of Ones): \t\t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000])) 29 | print('2.5. Binary Matrix Rank Test:\t\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000])) 30 | print('2.6. Discrete Fourier Transform (Spectral) Test: \t\t', SpectralTest.spectral_test(binary_data[:1000000])) 31 | print('2.7. Non-overlapping Template Matching Test:\t\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001')) 32 | print('2.8. Overlappong Template Matching Test: \t\t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000])) 33 | print('2.9. Universal Statistical Test:\t\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000])) 34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000])) 35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000])) 36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000])) 37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0)) 38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1)) 39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000]) 40 | print('2.14. Random Excursion Test:') 41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion') 42 | 43 | for item in result: 44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t', 45 | (item[3] >= 0.01)) 46 | 47 | result = RandomExcursions.variant_test(binary_data[:1000000]) 48 | 49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t') 50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion') 51 | for item in result: 52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t', 53 | (item[3] >= 0.01)) -------------------------------------------------------------------------------- /test_url_01.py: -------------------------------------------------------------------------------- 1 | import os 2 | from Tools import Tools 3 | 4 | from FrequencyTest import FrequencyTest as ft 5 | from RunTest import RunTest as rt 6 | from Matrix import Matrix as mt 7 | from Spectral import SpectralTest as st 8 | from TemplateMatching import TemplateMatching as tm 9 | from Universal import Universal as ut 10 | from Complexity import ComplexityTest as ct 11 | from Serial import Serial as serial 12 | from ApproximateEntropy import ApproximateEntropy as aet 13 | from CumulativeSum import CumulativeSums as cst 14 | from RandomExcursions import RandomExcursions as ret 15 | 16 | test_type = ['01. Frequency Test (Monobit)', '02. Frequency Test within a Block', '03. Run Test', 17 | '04. Longest Run of Ones in a Block', '05. Binary Matrix Rank Test', 18 | '06. Discrete Fourier Transform (Spectral) Test', 19 | '07. Non-Overlapping Template Matching Test', 20 | '08. Overlapping Template Matching Test', '09. Maurer\'s Universal Statistical test', 21 | '10. Linear Complexity Test', '11. Serial test', '12. Approximate Entropy Test', 22 | '13. Cummulative Sums (Forward) Test', '14. Cummulative Sums (Reverse) Test', 23 | '15. Random Excursions Test', '16. Random Excursions Variant Test'] 24 | 25 | test_function = { 26 | 0:ft.monobit_test, 27 | 1:ft.block_frequency, 28 | 2:rt.run_test, 29 | 3:rt.longest_one_block_test, 30 | 4:mt.binary_matrix_rank_text, 31 | 5:st.spectral_test, 32 | 6:tm.non_overlapping_test, 33 | 7:tm.overlapping_patterns, 34 | 8:ut.statistical_test, 35 | 9:ct.linear_complexity_test, 36 | 10:serial.serial_test, 37 | 11:aet.approximate_entropy_test, 38 | 12:cst.cumulative_sums_test, 39 | 13:cst.cumulative_sums_test, 40 | 14:ret.random_excursions_test, 41 | 15:ret.variant_test 42 | } 43 | 44 | handle = open(os.path.join(os.getcwd(), 'data', 'test_data_01.txt')) 45 | count = 0 46 | 47 | for item in handle: 48 | binary = Tools.string_to_binary(item) 49 | print(item, Tools.string_to_binary(item), binary) 50 | count = 0 51 | for test in test_function: 52 | print(test_type[count%len(test_type)], test_function[count](binary)) 53 | count += 1 --------------------------------------------------------------------------------