From 39ce6a07a549acb52911760d989e201a45e145d2 Mon Sep 17 00:00:00 2001 From: Nikesh Bajaj Date: Tue, 3 Jan 2023 12:42:23 +0000 Subject: [PATCH] updating version 1.0.7 Included Galois LFSR, improved documentation --- LICENSE | 42 +- MANIFEST.in | 33 +- README.md | 1129 ++++++----- Version | 2 +- build/lib/pylfsr/__init__.py | 56 +- build/lib/pylfsr/pylfsr.py | 2369 +++++++++++++++-------- build/lib/pylfsr/seq_generators.py | 361 ++++ build/lib/pylfsr/utils.py | 248 +++ dist/pylfsr-1.0.7-py3-none-any.whl | Bin 0 -> 28034 bytes dist/pylfsr-1.0.7.tar.gz | Bin 0 -> 32114 bytes examples.py | 138 +- examples/example1.py | 86 +- examples/example_LFSR1.py | 86 +- examples/example_LFSR2.py | 86 +- pylfsr.egg-info/PKG-INFO | 760 ++++++-- pylfsr.egg-info/SOURCES.txt | 4 +- pylfsr.egg-info/requires.txt | 1 + pylfsr/__init__.py | 56 +- pylfsr/pylfsr.py | 2810 +++++++++++++++------------- pylfsr/seq_generators.py | 361 ++++ pylfsr/utils.py | 248 +++ requirements.txt | 4 +- setup.py | 112 +- 23 files changed, 5838 insertions(+), 3154 deletions(-) create mode 100644 build/lib/pylfsr/seq_generators.py create mode 100644 build/lib/pylfsr/utils.py create mode 100644 dist/pylfsr-1.0.7-py3-none-any.whl create mode 100644 dist/pylfsr-1.0.7.tar.gz create mode 100644 pylfsr/seq_generators.py create mode 100644 pylfsr/utils.py diff --git a/LICENSE b/LICENSE index 7acd2b5..d1d53bc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) [2021] [Nikesh Bajaj] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) [2022] [Nikesh Bajaj] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in index 2db5166..cd08ad5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,17 +1,16 @@ - -include *.md -include LICENSE.txt -include requirements.txt -include pylfsr/__init__.py - -recursive-include pylfsr *.py -recursive-include pylfsr *.txt -recursive-include examples *.py -recursive-include examples *.ipynb -recursive-include *.ipynb - - -### Exclude - -recursive-exclude * __pycache__ -recursive-exclude *.yml + +include *.md +include LICENSE +include Version +include requirements.txt +include pylfsr/__init__.py +include pylfsr/primitive_polynomials_GF2_dict.txt + +recursive-include pylfsr *.py +recursive-include pylfsr *.txt +recursive-include examples *.py + +### Exclude + +recursive-exclude * __pycache__ +recursive-exclude *.yml diff --git a/README.md b/README.md index 5bfda61..45671de 100644 --- a/README.md +++ b/README.md @@ -1,567 +1,562 @@ -# LFSR -Linear Feedback Shift Register - - -## Links: **[Github Page](http://nikeshbajaj.github.io/Linear_Feedback_Shift_Register/)** | **[Documentation](https://lfsr.readthedocs.io/)** | **[Github](https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register)** | **[PyPi - project](https://pypi.org/project/pylfsr/)** | _ **Installation:** [pip install pylfsr](https://pypi.org/project/pylfsr/) ------ - - -![CircleCI](https://img.shields.io/circleci/build/github/Nikeshbajaj/Linear_Feedback_Shift_Register) -[![Documentation Status](https://readthedocs.org/projects/lfsr/badge/?version=latest)](https://lfsr.readthedocs.io/en/latest/?badge=latest) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![PyPI version fury.io](https://badge.fury.io/py/pylfsr.svg)](https://pypi.org/project/pylfsr/) -[![PyPI pyversions](https://img.shields.io/pypi/pyversions/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) -[![GitHub release](https://img.shields.io/github/release/nikeshbajaj/Linear_Feedback_Shift_Register.svg)](https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/releases) -[![PyPI format](https://img.shields.io/pypi/format/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) -[![PyPI implementation](https://img.shields.io/pypi/implementation/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) -[![HitCount](http://hits.dwyl.io/nikeshbajaj/pylfsr.svg)](http://hits.dwyl.io/nikeshbajaj/Linear_Feedback_Shift_Register) -![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/Nikeshbajaj/Linear_Feedback_Shift_Register/1.0.1) -![GitHub issues](https://img.shields.io/github/issues-raw/Nikeshbajaj/Linear_Feedback_Shift_Register) -![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/Nikeshbajaj/Linear_Feedback_Shift_Register) -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Nikeshbajaj/Linear_Feedback_Shift_Register.svg)](http://isitmaintained.com/project/Nikeshbajaj/Linear_Feedback_Shift_Register "Average time to resolve an issue") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/Nikeshbajaj/Linear_Feedback_Shift_Register.svg)](http://isitmaintained.com/project/Nikeshbajaj/Linear_Feedback_Shift_Register "Percentage of issues still open") - -[![PyPI download month](https://img.shields.io/pypi/dm/pylfsr.svg)](https://pypi.org/project/pylfsr/) -[![PyPI download week](https://img.shields.io/pypi/dw/pylfsr.svg)](https://pypi.org/project/pylfsr/) -[![Hits-of-Code](https://hitsofcode.com/github/Nikeshbajaj/Linear_Feedback_Shift_Register)](https://hitsofcode.com/github/Nikeshbajaj/Linear_Feedback_Shift_Register/view) - -[![Generic badge](https://img.shields.io/badge/pip%20install-pylfsr-blue.svg)](https://pypi.org/project/pylfsr/) -[![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](mailto:n.bajaj@qmul.ac.uk) - -![PyPI - Downloads](https://img.shields.io/pypi/dm/spkit?style=social) -![CircleCI](https://img.shields.io/circleci/build/github/Nikeshbajaj/Linear_Feedback_Shift_Register?style=social) - -

- - -

- - - -[![Generic badge](https://img.shields.io/badge/pip%20install-pylfsr-blue.svg)](https://pypi.org/project/pylfsr/) - -

- - -

- - - ------ -## Table of contents -- [**New Updates**](#new-updates) -- [**Installation**](#installation) -- [**Examples**](#examples) - - [**5-bit LFSR**](#example-1-5-bit-lfsr-with-feedback-polynomial-x5--x2--1) - - [**Vizualize each state**](#example-3--to-visualize-the-process-with-3-bit-lfsr-with-default-counter_start_zero--true) - - [**Plot your LFSR**](#visulizeplot-your-lfsr) - - [**Test properties of LFSR**](#example-6--testing-the-properties) -- [**A5/1 GSM Stream Cipher**](#a51-gsm-stream-cipher-generator) -- [**Geffe Genegerator**](#geffe-generator) -- [**Matlab Implementation**](#matlab) -- [**Cite As**](#cite-as) ------ - -## New Updates -## Plot Your LFSR with pylfsr -

- - -

- -## Updates: - **Version: 1.0.7:** - - **Added Galois Configuration** - - Improved Documentation - - **Computing LZ complexity** - - **Version: 1.0.6:** - - Fixed the bugs (1) missing initial bit (2) exception - - **Added test properties of LFSR** - - **(1) Balance Property** - - **(2) Runlength Property** - - **(3) Autocorrelation Property** - - **Ploting function to display LFSR** - - **A5/1 GSM Stream Ciper Generator** - - **Geffe Generator** - - -# Installation - -## Requirement : *numpy*, *matplotlib* - -### with pip - -``` -pip install pylfsr -``` - - -### Build from the source -Download the repository or clone it with git, after cd in directory build it from source with - -``` -python setup.py install -``` - -## Examples -### **Example 1**: 5-bit LFSR with feedback polynomial *x^5 + x^2 + 1* - -``` -# import LFSR -import numpy as np -from pylfsr import LFSR - -L = LFSR() - -# print the info -L.info() - -5 bit LFSR with feedback polynomial x^5 + x^2 + 1 -Expected Period (if polynomial is primitive) = 31 -Current : -State : [1 1 1 1 1] -Count : 0 -Output bit : -1 -feedback bit : -1 -``` - - -``` -L.next() -L.runKCycle(10) -L.runFullCycle() -L.info() -``` - -### Example 2**: 5-bit LFSR with custum state and feedback polynomial - -``` -state = [0,0,0,1,0] -fpoly = [5,4,3,2] -L = LFSR(fpoly=fpoly,initstate =state, verbose=True) -L.info() -tempseq = L.runKCycle(10) -L.set(fpoly=[5,3]) -``` - -### Example 3 ## To visualize the process with 3-bit LFSR, with default counter_start_zero = True -``` -state = [1,1,1] -fpoly = [3,2] -L = LFSR(initstate=state,fpoly=fpoly) -print('count \t state \t\toutbit \t seq') -print('-'*50) -for _ in range(15): - print(L.count,L.state,'',L.outbit,L.seq,sep='\t') - L.next() -print('-'*50) -print('Output: ',L.seq) -``` -Output : - -``` -count state outbit seq --------------------------------------------------- -0 [1 1 1] -1 [-1] -1 [0 1 1] 1 [1] -2 [0 0 1] 1 [1 1] -3 [1 0 0] 1 [1 1 1] -4 [0 1 0] 0 [1 1 1 0] -5 [1 0 1] 0 [1 1 1 0 0] -6 [1 1 0] 1 [1 1 1 0 0 1] -7 [1 1 1] 0 [1 1 1 0 0 1 0] -8 [0 1 1] 1 [1 1 1 0 0 1 0 1] -9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] -10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] -11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] -12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] -13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] -14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] --------------------------------------------------- -Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] -``` - -### Example 4 ## To visualize the process with 3-bit LFSR, with default counter_start_zero = False -``` -state = [1,1,1] -fpoly = [3,2] -L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) -print('count \t state \t\toutbit \t seq') -print('-'*50) -for _ in range(15): - print(L.count,L.state,'',L.outbit,L.seq,sep='\t') - L.next() -print('-'*50) -print('Output: ',L.seq) -``` - -Output -``` -count state outbit seq --------------------------------------------------- -1 [1 1 1] 1 [1] -2 [0 1 1] 1 [1 1] -3 [0 0 1] 1 [1 1 1] -4 [1 0 0] 0 [1 1 1 0] -5 [0 1 0] 0 [1 1 1 0 0] -6 [1 0 1] 1 [1 1 1 0 0 1] -7 [1 1 0] 0 [1 1 1 0 0 1 0] -8 [1 1 1] 1 [1 1 1 0 0 1 0 1] -9 [0 1 1] 1 [1 1 1 0 0 1 0 1 1] -10 [0 0 1] 1 [1 1 1 0 0 1 0 1 1 1] -11 [1 0 0] 0 [1 1 1 0 0 1 0 1 1 1 0] -12 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0] -13 [1 0 1] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] -14 [1 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] --------------------------------------------------- -Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] -``` - -## Visualize & Plot LFSR -``` -L.Viz(show=False, show_labels=False,title='R1') - -``` - -

- -

- -### Dynamic plot - Animation in notebook -``` -%matplotlib notebook -L = LFSR(initstate=[1,0,1,0,1],fpoly=[5,4,3,2],counter_start_zero=False) -fig, ax = plt.subplots(figsize=(8,3)) -for _ in range(35): - ax.clear() - L.Viz(ax=ax, title='R1') - plt.ylim([-0.1,None]) - #plt.tight_layout() - L.next() - fig.canvas.draw() - plt.pause(0.1) - -``` -

- -

- - -## Example 5 ## 23 bit LFSR with custum state and feedback polynomial -``` -fpoly = [23,19] -L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) -L1.info() -``` -Output -``` -23 bit LFSR with feedback polynomial x^23 + x^19 + 1 -Expected Period (if polynomial is primitive) = 8388607 -Current : - State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] - Count : 0 - Output bit : -1 - feedback bit : -1 -``` -``` -seq = L1.runKCycle(100) -``` - -```seq -array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, -1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, -1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) -``` -## Example 6 ## testing the properties -``` -state = [1,1,1,1,0] -fpoly = [5,3] -L = LFSR(initstate=state,fpoly=fpoly) -result = L.test_properties(verbose=2) -``` -Output -``` -1. Periodicity ------------------- - - Expected period = 2^M-1 = 31 - - Pass?: True - -2. Balance Property -------------------- - - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) - - Pass?: True - -3. Runlength Property -------------------- - - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] - - Runs: [8 4 2 1 1] - - Pass?: True - -4. Autocorrelation Property -------------------- - - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else - - Pass?: True - -================== -Passed all the tests -================== -``` -

- -

- -Testing individual property -``` -# get a full period sequence -p = L.getFullPeriod() -p -array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, - 0, 1, 0, 0, 1, 0, 1, 1, 0]) -``` - -``` -L.balance_property(p.copy()) -# (True, (16, 15)) - -L.runlength_property(p.copy()) -# (True, array([8, 4, 2, 1, 1])) - -L.autocorr_property(p.copy())[0] -#True -``` - -## Example 7 ## testing the properties for non-primitive polynomial -``` -state = [1,1,1,1,0] -fpoly = [5,1] -L = LFSR(initstate=state,fpoly=fpoly) -result = L.test_properties(verbose=1) -``` -Output -``` -1. Periodicity ------------------- - - Expected period = 2^M-1 = 31 - - Pass?: False - -2. Balance Property -------------------- - - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) - - Pass?: False - -3. Runlength Property -------------------- - - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] - - Runs: [10 2 1 1 2] - - Pass?: False - -4. Autocorrelation Property -------------------- - - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else - - Pass?: False - -================== -Failed one or more tests, check if feedback polynomial is primitive polynomial -================== -``` -

- -

- - -### Example 8**: Get the feedback polynomial or list -Reference : http://www.partow.net/programming/polynomials/index.html - -``` -L = LFSR() -# list of 5-bit feedback polynomials -fpoly = L.get_fpolyList(m=5) - -# list of all feedback polynomials as a dictionary -fpolyDict = L.get_fpolyList() -``` - - -### Changing feedback polynomial in between -``` -L.changeFpoly(newfpoly =[23,14],reset=False) -seq1 = L.runKCycle(20) - -# Change after 20 clocks -L.changeFpoly(newfpoly =[23,9],reset=False) -seq2 = L.runKCycle(20) -``` - -# Generators -# A5/1 GSM Stream cipher generator -

- -

- -Ref: https://en.wikipedia.org/wiki/A5/1 - -``` -import numpy as np -import matplotlib.pyplot as plt -from pylfsr import A5_1 - -A5 = A5_1(key='random') -print('key: ',A5.key) -A5.R1.Viz(title='R1') -A5.R2.Viz(title='R2') -A5.R3.Viz(title='R3') - -print('key: ',A5.key) -print() -print('count \t cbit\t\tclk\t R1_R2_R3\toutbit \t seq') -print('-'*80) -for _ in range(15): - print(A5.count,A5.getCbits(),A5.clock_bit,A5.getLastbits(),A5.outbit,A5.getSeq(),sep='\t') - A5.next() -print('-'*80) -print('Output: ',A5.seq) - -A5.runKCycle(1000) -A5.getSeq() - -``` - - -## Enhanced A5/1 - -Reference Article: **Enhancement of A5/1**: https://doi.org/10.1109/ETNCC.2011.5958486 - -

- -

- - -``` -# Three LFSRs initialzed with 'ones' though they are intialized with encription key -R1 = LFSR(fpoly = [19,18,17,14]) -R2 = LFSR(fpoly = [23,22,21,8]) -R3 = LFSR(fpoly = [22,21]) - -# clocking bits -b1 = R1.state[8] -b2 = R3.state[10] -b3 = R3.state[10] - -``` - - - -# Geffe Generator -

- -

- -Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. - Chaper 16 - -``` -import numpy as np -import matplotlib.pyplot as plt -from pylfsr import Geffe, LFSR - -kLFSR = [LFSR(initstate='random') for _ in range(8)] # List of 8 5-bit LFSRs with default feedback polynomial and random initial state -cLFSR = LFSR(initstate='random') # A 5-bit LFSR with for selecting one of 8 output at a time - -GG = Geffe(kLFSR_list=kLFSR, cLFSR=cLFSR) - -print('key: ',GG.getState()) -print() -for _ in range(50): - print(GG.count,GG.m_count,GG.outbit_k,GG.sel_k,GG.outbit,GG.getSeq(),sep='\t') - GG.next() - -GG.runKCycle(1000) -GG.getSeq() -``` - - - - -_______________________________________________________________________________________________ - -# MATLAB -## For matlab files download it from here -Folder : https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/tree/master/matlabfiles - -**Description** -Genrate randon binary sequence using LFSR for any given feedback taps (polynomial), -This will also check Three fundamental Property of LFSR -1. Balance Property -2. Runlength Property -3. Autocorrelation Property - -**This MATLAB Code work for any length of LFSR with given taps (feedback polynomial) -Universal, There are three files LFSRv1.m an LFSRv2.m, LFSRv3.m** - -### LFSRv1 -This function will return all the states of LFSR and will check Three fundamental Property of LFSR -(1) Balance Property (2) Runlength Property (3) Autocorrelation Property - -#### EXAMPLE -``` -s=[1 1 0 0 1] -t=[5 2] -[seq c] =LFSRv1(s,t) -``` - -### LFSRv2 -This function will return only generated sequence will all the states of LFSR, no verification of properties are done -here. Use this function to avoid verification each time you execute the program. -#### EXAMPLE -``` -s=[1 1 0 0 1] -t=[5 2] -[seq c] =LFSRv2(s,t) -``` - -### LFSRv3 (faster) -*seq = LFSRv3(s,t,N)* -this function generates N bit sequence only. This is faster then other two functions, as this does not gives each state of LFSR - -#### EXAMPLE -``` -s=[1 1 0 0 1] -t=[5 2] -seq =LFSRv3(s,t,50) -``` - - - -## Tips -* If you want to use this function in middle of any program, use LFSRv2 or LFSRv1 with verification =0. -* If you want to make it fast for long length of LFSR,use LFSRv3.m - -______________________________________ - -# Cite As -``` -@software{nikesh_bajaj_2021_4726667, - author = {Nikesh Bajaj}, - title = {Nikeshbajaj/Linear\_Feedback\_Shift\_Register: 1.0.6}, - month = apr, - year = 2021, - publisher = {Zenodo}, - version = {1.0.6}, - doi = {10.5281/zenodo.4726667}, - url = {https://doi.org/10.5281/zenodo.4726667} -} -``` - - -# Contacts: - -If any doubt, confusion or feedback please contact me -* **Nikesh Bajaj** -* http://nikeshbajaj.in -* n.bajaj@imperial.ac.uk -* n.bajaj@qmul.ac.uk -* bajaj[dot]nikkey [AT]gmail? -### PhD Student: Queen Mary University of London & University of Genoa -______________________________________ - +# LFSR -Linear Feedback Shift Register + + +## Links: **[Github Page](http://nikeshbajaj.github.io/Linear_Feedback_Shift_Register/)** | **[Documentation](https://lfsr.readthedocs.io/)** | **[Github](https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register)** | **[PyPi - project](https://pypi.org/project/pylfsr/)** | _ **Installation:** [pip install pylfsr](https://pypi.org/project/pylfsr/) +----- + + +![CircleCI](https://img.shields.io/circleci/build/github/Nikeshbajaj/Linear_Feedback_Shift_Register) +[![Documentation Status](https://readthedocs.org/projects/lfsr/badge/?version=latest)](https://lfsr.readthedocs.io/en/latest/?badge=latest) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![PyPI version fury.io](https://badge.fury.io/py/pylfsr.svg)](https://pypi.org/project/pylfsr/) +[![PyPI pyversions](https://img.shields.io/pypi/pyversions/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) +[![GitHub release](https://img.shields.io/github/release/nikeshbajaj/Linear_Feedback_Shift_Register.svg)](https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/releases) +[![PyPI format](https://img.shields.io/pypi/format/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) +[![PyPI implementation](https://img.shields.io/pypi/implementation/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) +[![HitCount](http://hits.dwyl.io/nikeshbajaj/pylfsr.svg)](http://hits.dwyl.io/nikeshbajaj/Linear_Feedback_Shift_Register) +![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/Nikeshbajaj/Linear_Feedback_Shift_Register/1.0.1) +![GitHub issues](https://img.shields.io/github/issues-raw/Nikeshbajaj/Linear_Feedback_Shift_Register) +![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/Nikeshbajaj/Linear_Feedback_Shift_Register) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Nikeshbajaj/Linear_Feedback_Shift_Register.svg)](http://isitmaintained.com/project/Nikeshbajaj/Linear_Feedback_Shift_Register "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/Nikeshbajaj/Linear_Feedback_Shift_Register.svg)](http://isitmaintained.com/project/Nikeshbajaj/Linear_Feedback_Shift_Register "Percentage of issues still open") + +[![PyPI download month](https://img.shields.io/pypi/dm/pylfsr.svg)](https://pypi.org/project/pylfsr/) +[![PyPI download week](https://img.shields.io/pypi/dw/pylfsr.svg)](https://pypi.org/project/pylfsr/) +[![Hits-of-Code](https://hitsofcode.com/github/Nikeshbajaj/Linear_Feedback_Shift_Register)](https://hitsofcode.com/github/Nikeshbajaj/Linear_Feedback_Shift_Register/view) + +[![Generic badge](https://img.shields.io/badge/pip%20install-pylfsr-blue.svg)](https://pypi.org/project/pylfsr/) +[![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](mailto:n.bajaj@qmul.ac.uk) + +![PyPI - Downloads](https://img.shields.io/pypi/dm/spkit?style=social) +![CircleCI](https://img.shields.io/circleci/build/github/Nikeshbajaj/Linear_Feedback_Shift_Register?style=social) + +

+ + +

+ + + +[![Generic badge](https://img.shields.io/badge/pip%20install-pylfsr-blue.svg)](https://pypi.org/project/pylfsr/) + +

+ + +

+ + + +----- +## Table of contents +- [**New Updates**](#new-updates) +- [**Installation**](#installation) +- [**Examples**](#examples) + - [**5-bit LFSR**](#example-1-5-bit-lfsr-with-feedback-polynomial-x5--x2--1) + - [**Vizualize each state**](#example-3--to-visualize-the-process-with-3-bit-lfsr-with-default-counter_start_zero--true) + - [**Plot your LFSR**](#visulizeplot-your-lfsr) + - [**Test properties of LFSR**](#example-6--testing-the-properties) +- [**A5/1 GSM Stream Cipher**](#a51-gsm-stream-cipher-generator) +- [**Geffe Genegerator**](#geffe-generator) +- [**Matlab Implementation**](#matlab) +- [**Cite As**](#cite-as) +----- + +## New Updates +## Plot Your LFSR with pylfsr +

+ + +

+ +## Updates: + **Version: 1.0.7:** + - **Added Galois Configuration** + - Improved Documentation + - **Computing LZ complexity** + + **Version: 1.0.6:** + - Fixed the bugs (1) missing initial bit (2) exception + - **Added test properties of LFSR** + - **(1) Balance Property** + - **(2) Runlength Property** + - **(3) Autocorrelation Property** + - **Ploting function to display LFSR** + - **A5/1 GSM Stream Ciper Generator** + - **Geffe Generator** + + +# Installation + +## Requirement : *numpy*, *matplotlib* + +### with pip + +``` +pip install pylfsr +``` + + +### Build from the source +Download the repository or clone it with git, after cd in directory build it from source with + +``` +python setup.py install +``` + +## Examples +### **Example 1**: 5-bit LFSR with feedback polynomial *x^5 + x^2 + 1* + +``` +# import LFSR +import numpy as np +from pylfsr import LFSR + +L = LFSR() + +# print the info +L.info() + +5 bit LFSR with feedback polynomial x^5 + x^2 + 1 +Expected Period (if polynomial is primitive) = 31 +Current : +State : [1 1 1 1 1] +Count : 0 +Output bit : -1 +feedback bit : -1 +``` + + +``` +L.next() +L.runKCycle(10) +L.runFullCycle() +L.info() +``` + +### Example 2**: 5-bit LFSR with custum state and feedback polynomial + +``` +state = [0,0,0,1,0] +fpoly = [5,4,3,2] +L = LFSR(fpoly=fpoly,initstate =state, verbose=True) +L.info() +tempseq = L.runKCycle(10) +L.set(fpoly=[5,3]) +``` + +### Example 3 ## To visualize the process with 3-bit LFSR, with default counter_start_zero = True +``` +state = [1,1,1] +fpoly = [3,2] +L = LFSR(initstate=state,fpoly=fpoly) +print('count \t state \t\toutbit \t seq') +print('-'*50) +for _ in range(15): + print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + L.next() +print('-'*50) +print('Output: ',L.seq) +``` +Output : + +``` +count state outbit seq +-------------------------------------------------- +0 [1 1 1] -1 [-1] +1 [0 1 1] 1 [1] +2 [0 0 1] 1 [1 1] +3 [1 0 0] 1 [1 1 1] +4 [0 1 0] 0 [1 1 1 0] +5 [1 0 1] 0 [1 1 1 0 0] +6 [1 1 0] 1 [1 1 1 0 0 1] +7 [1 1 1] 0 [1 1 1 0 0 1 0] +8 [0 1 1] 1 [1 1 1 0 0 1 0 1] +9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] +10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] +11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] +12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] +13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] +14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] +-------------------------------------------------- +Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] +``` + +### Example 4 ## To visualize the process with 3-bit LFSR, with default counter_start_zero = False +``` +state = [1,1,1] +fpoly = [3,2] +L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) +print('count \t state \t\toutbit \t seq') +print('-'*50) +for _ in range(15): + print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + L.next() +print('-'*50) +print('Output: ',L.seq) +``` + +Output +``` +count state outbit seq +-------------------------------------------------- +1 [1 1 1] 1 [1] +2 [0 1 1] 1 [1 1] +3 [0 0 1] 1 [1 1 1] +4 [1 0 0] 0 [1 1 1 0] +5 [0 1 0] 0 [1 1 1 0 0] +6 [1 0 1] 1 [1 1 1 0 0 1] +7 [1 1 0] 0 [1 1 1 0 0 1 0] +8 [1 1 1] 1 [1 1 1 0 0 1 0 1] +9 [0 1 1] 1 [1 1 1 0 0 1 0 1 1] +10 [0 0 1] 1 [1 1 1 0 0 1 0 1 1 1] +11 [1 0 0] 0 [1 1 1 0 0 1 0 1 1 1 0] +12 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0] +13 [1 0 1] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] +14 [1 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] +-------------------------------------------------- +Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] +``` + +## Visualize & Plot LFSR +``` +L.Viz(show=False, show_labels=False,title='R1') + +``` + +

+ +

+ +### Dynamic plot - Animation in notebook +``` +%matplotlib notebook +L = LFSR(initstate=[1,0,1,0,1],fpoly=[5,4,3,2],counter_start_zero=False) +fig, ax = plt.subplots(figsize=(8,3)) +for _ in range(35): + ax.clear() + L.Viz(ax=ax, title='R1') + plt.ylim([-0.1,None]) + #plt.tight_layout() + L.next() + fig.canvas.draw() + plt.pause(0.1) + +``` +

+ +

+ + +## Example 5 ## 23 bit LFSR with custum state and feedback polynomial +``` +fpoly = [23,19] +L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) +L1.info() +``` +Output +``` +23 bit LFSR with feedback polynomial x^23 + x^19 + 1 +Expected Period (if polynomial is primitive) = 8388607 +Current : + State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] + Count : 0 + Output bit : -1 + feedback bit : -1 +``` +``` +seq = L1.runKCycle(100) +``` + +```seq +array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, +1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, +1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) +``` +## Example 6 ## testing the properties +``` +state = [1,1,1,1,0] +fpoly = [5,3] +L = LFSR(initstate=state,fpoly=fpoly) +result = L.test_properties(verbose=2) +``` +Output +``` +1. Periodicity +------------------ + - Expected period = 2^M-1 = 31 + - Pass?: True + +2. Balance Property +------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) + - Pass?: True + +3. Runlength Property +------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [8 4 2 1 1] + - Pass?: True + +4. Autocorrelation Property +------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: True + +================== +Passed all the tests +================== +``` +

+ +

+ +Testing individual property +``` +# get a full period sequence +p = L.getFullPeriod() +p +array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 1, 1, 0]) +``` + +``` +L.balance_property(p.copy()) +# (True, (16, 15)) + +L.runlength_property(p.copy()) +# (True, array([8, 4, 2, 1, 1])) + +L.autocorr_property(p.copy())[0] +#True +``` + +## Example 7 ## testing the properties for non-primitive polynomial +``` +state = [1,1,1,1,0] +fpoly = [5,1] +L = LFSR(initstate=state,fpoly=fpoly) +result = L.test_properties(verbose=1) +``` +Output +``` +1. Periodicity +------------------ + - Expected period = 2^M-1 = 31 + - Pass?: False + +2. Balance Property +------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) + - Pass?: False + +3. Runlength Property +------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [10 2 1 1 2] + - Pass?: False + +4. Autocorrelation Property +------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: False + +================== +Failed one or more tests, check if feedback polynomial is primitive polynomial +================== +``` +

+ +

+ + +### Example 8**: Get the feedback polynomial or list +Reference : http://www.partow.net/programming/polynomials/index.html + +``` +L = LFSR() +# list of 5-bit feedback polynomials +fpoly = L.get_fpolyList(m=5) + +# list of all feedback polynomials as a dictionary +fpolyDict = L.get_fpolyList() +``` + + +### Changing feedback polynomial in between +``` +L.changeFpoly(newfpoly =[23,14],reset=False) +seq1 = L.runKCycle(20) + +# Change after 20 clocks +L.changeFpoly(newfpoly =[23,9],reset=False) +seq2 = L.runKCycle(20) +``` + +# Generators +# A5/1 GSM Stream cipher generator +

+ +

+ +Ref: https://en.wikipedia.org/wiki/A5/1 + +``` +import numpy as np +import matplotlib.pyplot as plt +from pylfsr import A5_1 + +A5 = A5_1(key='random') +print('key: ',A5.key) +A5.R1.Viz(title='R1') +A5.R2.Viz(title='R2') +A5.R3.Viz(title='R3') + +print('key: ',A5.key) +print() +print('count \t cbit\t\tclk\t R1_R2_R3\toutbit \t seq') +print('-'*80) +for _ in range(15): + print(A5.count,A5.getCbits(),A5.clock_bit,A5.getLastbits(),A5.outbit,A5.getSeq(),sep='\t') + A5.next() +print('-'*80) +print('Output: ',A5.seq) + +A5.runKCycle(1000) +A5.getSeq() + +``` + + +## Enhanced A5/1 + +Reference Article: **Enhancement of A5/1**: https://doi.org/10.1109/ETNCC.2011.5958486 + +

+ +

+ + +``` +# Three LFSRs initialzed with 'ones' though they are intialized with encription key +R1 = LFSR(fpoly = [19,18,17,14]) +R2 = LFSR(fpoly = [23,22,21,8]) +R3 = LFSR(fpoly = [22,21]) + +# clocking bits +b1 = R1.state[8] +b2 = R3.state[10] +b3 = R3.state[10] + +``` + + +# Geffe Generator +

+ +

+ +Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. + Chaper 16 + +``` +import numpy as np +import matplotlib.pyplot as plt +from pylfsr import Geffe, LFSR + +kLFSR = [LFSR(initstate='random') for _ in range(8)] # List of 8 5-bit LFSRs with default feedback polynomial and random initial state +cLFSR = LFSR(initstate='random') # A 5-bit LFSR with for selecting one of 8 output at a time + +GG = Geffe(kLFSR_list=kLFSR, cLFSR=cLFSR) + +print('key: ',GG.getState()) +print() +for _ in range(50): + print(GG.count,GG.m_count,GG.outbit_k,GG.sel_k,GG.outbit,GG.getSeq(),sep='\t') + GG.next() + +GG.runKCycle(1000) +GG.getSeq() +``` + +_______________________________________________________________________________________________ + +# MATLAB +## For matlab files download it from here +Folder : https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/tree/master/matlabfiles + +**Description** +Genrate randon binary sequence using LFSR for any given feedback taps (polynomial), +This will also check Three fundamental Property of LFSR +1. Balance Property +2. Runlength Property +3. Autocorrelation Property + +**This MATLAB Code work for any length of LFSR with given taps (feedback polynomial) -Universal, There are three files LFSRv1.m an LFSRv2.m, LFSRv3.m** + +### LFSRv1 +This function will return all the states of LFSR and will check Three fundamental Property of LFSR +(1) Balance Property (2) Runlength Property (3) Autocorrelation Property + +#### EXAMPLE +``` +s=[1 1 0 0 1] +t=[5 2] +[seq c] =LFSRv1(s,t) +``` + +### LFSRv2 +This function will return only generated sequence will all the states of LFSR, no verification of properties are done +here. Use this function to avoid verification each time you execute the program. +#### EXAMPLE +``` +s=[1 1 0 0 1] +t=[5 2] +[seq c] =LFSRv2(s,t) +``` + +### LFSRv3 (faster) +*seq = LFSRv3(s,t,N)* +this function generates N bit sequence only. This is faster then other two functions, as this does not gives each state of LFSR + +#### EXAMPLE +``` +s=[1 1 0 0 1] +t=[5 2] +seq =LFSRv3(s,t,50) +``` + + + +## Tips +* If you want to use this function in middle of any program, use LFSRv2 or LFSRv1 with verification =0. +* If you want to make it fast for long length of LFSR,use LFSRv3.m + +______________________________________ + +# Cite As +``` +@software{nikesh_bajaj_2021_4726667, + author = {Nikesh Bajaj}, + title = {Nikeshbajaj/Linear\_Feedback\_Shift\_Register: 1.0.6}, + month = apr, + year = 2021, + publisher = {Zenodo}, + version = {1.0.6}, + doi = {10.5281/zenodo.4726667}, + url = {https://doi.org/10.5281/zenodo.4726667} +} +``` + + +# Contacts: + +If any doubt, confusion or feedback please contact me +* **Nikesh Bajaj** +* http://nikeshbajaj.in +* n.bajaj@imperial.ac.uk +* n.bajaj@qmul.ac.uk +* bajaj[dot]nikkey[AT]gmail[dot]? +### PhD Student: Queen Mary University of London & University of Genoa +______________________________________ diff --git a/Version b/Version index af0b7dd..f5760c2 100644 --- a/Version +++ b/Version @@ -1 +1 @@ -1.0.6 +1.0.7 diff --git a/build/lib/pylfsr/__init__.py b/build/lib/pylfsr/__init__.py index b034eea..ab15f8a 100644 --- a/build/lib/pylfsr/__init__.py +++ b/build/lib/pylfsr/__init__.py @@ -1,22 +1,34 @@ -name = "Linear Feedback Shift Register" - -# PEP0440 compatible formatted version, see: -# https://www.python.org/dev/peps/pep-0440/ -# -# Generic release markers: -# X.Y -# X.Y.Z # For bugfix releases -# -# Admissible pre-release markers: -# X.YaN # Alpha release -# X.YbN # Beta release -# X.YrcN # Release Candidate -# X.Y # Final release -# -# Dev branch marker is: 'X.Y.devN' where N is an integer. -# - -__version__ = '1.0.5' -__author__ = 'Nikesh Bajaj' - -from .pylfsr import LFSR +''' +Author @ Nikesh Bajaj +Version : 1.0.7 +Contact: n.bajaj@qmul.ac.uk + : n.bajaj@imperial.ac.uk + : http://nikeshbajaj.in +-----changelog------------------- +first created : Date: 22 Oct 2017 +Updated on : 29 Apr 2021 (version:1.0.6) + : fixed bugs (1) not counting first outbit correctly (2) Exception in info method + : added test properties (1) Balance (2) Runlength (3) Autocorrelation + : improved functionalities + : added Viz function + : added A5/1 and Geffe Generator +Updated on : 03 Jan 2023 (version:1.0.7) + : Added Galois Configuration for LFSR + : fixed bugs, improved documentation +''' + +from __future__ import absolute_import, division, print_function + +name = "Linear Feedback Shift Register" + +__version__ = '1.0.7' +__author__ = 'Nikesh Bajaj' + +import sys, os + +sys.path.append(os.path.dirname(__file__)) + +from .pylfsr import (LFSR, PlotLFSR, dispLFSR) +from .seq_generators import (A5_1, Geffe, Geffe3) +from .utils import (lempel_ziv_patterns, lempel_ziv_complexity, get_fpolyList, get_Ifpoly) +from .utils import (pretty_print, print_list, progbar, deprecated) diff --git a/build/lib/pylfsr/pylfsr.py b/build/lib/pylfsr/pylfsr.py index 9c84a4f..6edc2fc 100644 --- a/build/lib/pylfsr/pylfsr.py +++ b/build/lib/pylfsr/pylfsr.py @@ -1,813 +1,1556 @@ -import numpy as np -''' -Author @ Nikesh Bajaj -first created : Date: 22 Oct 2017 -Updated on : 21 Apr 2021 - : fixed bugs (1) not counting first outbit correctly (2) Exception in info method - : added test properties (1) Balance (2) Runlength (3) Autocorrelation -Version : 1.0.5 -Contact: n.bajaj@qmul.ac.uk - : http://nikeshbajaj.in -''' - -class LFSR(): - ''' - Linear Feedback Shift Register - - class LFSR(fpoly=[5,2],initstate='ones',verbose=False) - - Parameters - ---------- - initstate : binary np.array (row vector) or str ='ones' or 'random', optional (default = 'ones')) - Initial state of LFSR. initstate can not be all zeros. - default ='ones' - Initial state is intialized with ones and length of register is equal to - degree of feedback polynomial - if state='rand' - Initial state is intialized with random binary sequence of length equal to - degree of feedback polynomial - - fpoly : List, optional (default=[5,2]) - Feedback polynomial, it has to be primitive polynomial of GF(2) field, for valid output of LFSR - to get the list of feedback polynomials check method 'get_fpolyList' - or check Refeferece: - Ref: List of some primitive polynomial over GF(2)can be found at - http://www.partow.net/programming/polynomials/index.html - http://www.ams.org/journals/mcom/1962-16-079/S0025-5718-1962-0148256-1/S0025-5718-1962-0148256-1.pdf - http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf - counter_start_zero: bool (default = True), whether to start counter with 0 or 1. If True, initial outbit is - set to -1, so is feedbackbit, until first .next() clock is excecuted. This initial output is not stacked in - seq. The output sequence should be same, in anycase, for example if you need run 10 cycles, using runKCycle(10) methed. - Verbose : boolean, optional (default=False) - if True, state of LFSR will be printed at every cycle(iteration) - - Attributes - ---------- - count : int - Count the cycle, starts with 0 if counter_start_zero True, else starts with 1 - seq : np.array shape =(count,) - Output sequence stored in seq since first cycle - if -1, no cycle has been excecuted, count=0 when counter_start_zero is True - else last bit of initial state - - outbit : binary bit - Current output bit, - Last bit of current state - if -1, no cycle has been excecuted, count =0, when counter_start_zero is True - - feedbackbit : binary bit - if -1, no cycle has been excecuted, count =0, when counter_start_zero is True - M : int - length of LFSR, M-bit LFSR - - expectedPeriod : int (also saved as T) - Expected period of sequence - if feedback polynomial is primitive and irreducible (as per reference) - period will be 2^M -1 - T : int (also saved as expectedPeriod) - Expected period of sequence - if feedback polynomial is primitive and irreducible (as per reference) - period will be 2^M -1 - feedpoly : str - feedback polynomial - - - Examples - -------- - >>> import numpy as np - >>> from pylfsr import LFSR - - ## Example 1 ## 5 bit LFSR with x^5 + x^2 + 1 - >>> L = LFSR() - >>> L.info() - 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [1 1 1 1 1] - Count : 0 - Output bit : -1 - feedback bit : -1 - - >>> L.next() - 1 - - >>> L.runKCycle(10) - array([1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) - - >>> L.runFullCycle() # doctest: +NORMALIZE_WHITESPACE - array([1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, - 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) - - >>> L.info() # doctest: +NORMALIZE_WHITESPACE - 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [0 0 1 0 0] - Count : 42 - Output bit : 1 - feedback bit : 0 - Output Sequence 111110011010010000101011101100011111001101 - - >>> tempseq = L.runKCycle(10000) # generate 10000 bits from current state - - ## Example 2 ## 5 bit LFSR with custum state and feedback polynomial - >>> state = np.array([0,0,0,1,0]) - >>> fpoly = [5,4,3,2] - >>> L1 = LFSR(fpoly=fpoly,initstate =state, verbose=True) - >>> L1.info() # doctest: +NORMALIZE_WHITESPACE - 5 bit LFSR with feedback polynomial x^5 + x^4 + x^3 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [0 0 0 1 0] - Count : 0 - Output bit : -1 - feedback bit : -1 - >>> tempseq = L1.runKCycle(10) - S: [0 0 0 1 0] - S: [1 0 0 0 1] - S: [1 1 0 0 0] - S: [1 1 1 0 0] - S: [0 1 1 1 0] - S: [1 0 1 1 1] - S: [1 1 0 1 1] - S: [1 1 1 0 1] - S: [1 1 1 1 0] - S: [1 1 1 1 1] - >>> tempseq - array([0, 1, 0, 0, 0, 1, 1, 1, 0, 1]) - - >>>L1.set(fpoly=[5,3]) - - ## Example 3 ## TO visualize the process with 3-bit LFSR, with default counter_start_zero = True - >>> state = [1,1,1] - >>> fpoly = [3,2] - >>> L = LFSR(initstate=state,fpoly=fpoly) - >>> print('count \t state \t\toutbit \t seq') - >>> print('-'*50) - >>> for _ in range(15): - >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') - >>> L.next() - >>> print('-'*50) - >>> print('Output: ',L.seq) - count state outbit seq - -------------------------------------------------- - 0 [1 1 1] -1 [-1] - 1 [0 1 1] 1 [1] - 2 [0 0 1] 1 [1 1] - 3 [1 0 0] 1 [1 1 1] - 4 [0 1 0] 0 [1 1 1 0] - 5 [1 0 1] 0 [1 1 1 0 0] - 6 [1 1 0] 1 [1 1 1 0 0 1] - 7 [1 1 1] 0 [1 1 1 0 0 1 0] - 8 [0 1 1] 1 [1 1 1 0 0 1 0 1] - 9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] - 10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] - 11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] - 12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] - 13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] - 14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] - -------------------------------------------------- - Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] - - ## Example 4 ## TO visualize the process with 3-bit LFSR, with default counter_start_zero = False - >>> state = [1,1,1] - >>> fpoly = [3,2] - >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) - >>> print('count \t state \t\toutbit \t seq') - >>> print('-'*50) - >>> for _ in range(15): - >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') - >>> L.next() - >>> print('-'*50) - >>> print('Output: ',L.seq) - count state outbit seq - -------------------------------------------------- - 0 [1 1 1] -1 [-1] - 1 [0 1 1] 1 [1] - 2 [0 0 1] 1 [1 1] - 3 [1 0 0] 1 [1 1 1] - 4 [0 1 0] 0 [1 1 1 0] - 5 [1 0 1] 0 [1 1 1 0 0] - 6 [1 1 0] 1 [1 1 1 0 0 1] - 7 [1 1 1] 0 [1 1 1 0 0 1 0] - 8 [0 1 1] 1 [1 1 1 0 0 1 0 1] - 9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] - 10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] - 11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] - 12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] - 13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] - 14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] - -------------------------------------------------- - Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] - - ## Example 5 ## 23 bit LFSR with custum state and feedback polynomial - - >>> fpoly = [23,19] - >>> L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) - >>> L1.info() - 23 bit LFSR with feedback polynomial x^23 + x^19 + 1 - Expected Period (if polynomial is primitive) = 8388607 - Current : - State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] - Count : 0 - Output bit : -1 - feedback bit : -1 - - >>> seq = L1.runKCycle(100) - >>> seq - array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, - 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) - - ## Example 6 ## testing the properties - >>> state = [1,1,1,1,0] - >>> fpoly = [5,3] - >>> L = LFSR(initstate=state,fpoly=fpoly) - >>> L.info() - 5 bit LFSR with feedback polynomial x^5 + x^3 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [1 1 1 1 0] - Count : 0 - Output bit : -1 - feedback bit : -1 - - >>>result = L.test_properties(verbose=1) - 1. Periodicity - ------------------ - - Expected period = 2^M-1 = 31 - - Pass?: True - - 2. Balance Property - ------------------- - - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) - - Pass?: True - - 3. Runlength Property - ------------------- - - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] - - Runs: [8 4 2 1 1] - - Pass?: True - - 4. Autocorrelation Property - ------------------- - - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else - - Pass?: True - - ================== - Passed all the tests - ================== - - - >>> p = L.getFullPeriod() - >>> p - array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, - 0, 1, 0, 0, 1, 0, 1, 1, 0]) - - >>> L.balance_property(p.copy()) - (True, (16, 15)) - - >>> L.runlength_property(p.copy()) - (True, array([8, 4, 2, 1, 1])) - - >>> L.autocorr_property(p.copy())[0] - True - - ## Example 7 ## testing the properties for non-primitive polynomial - >>> state = [1,1,1,1,0] - >>> fpoly = [5,1] - >>> L = LFSR(initstate=state,fpoly=fpoly) - >>> result = L.test_properties(verbose=1) - 1. Periodicity - ------------------ - - Expected period = 2^M-1 = 31 - - Pass?: False - - 2. Balance Property - ------------------- - - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) - - Pass?: False - - 3. Runlength Property - ------------------- - - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] - - Runs: [10 2 1 1 2] - - Pass?: False - - 4. Autocorrelation Property - ------------------- - - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else - - Pass?: False - - ================== - Failed one or more tests, check if feedback polynomial is primitive polynomial - ================== - ''' - - def __init__(self, fpoly=[5, 2], initstate='ones', verbose=False,counter_start_zero=True): - if isinstance(initstate, str): - if initstate == 'ones': - initstate = np.ones(np.max(fpoly)) - elif initstate == 'random': - initstate = np.random.randint(0, 2, np.max(fpoly)) - else: - raise Exception('Unknown intial state') - if isinstance(initstate, list): - initstate = np.array(initstate) - - self.initstate = initstate - self.fpoly = fpoly - self.state = initstate.astype(int) - #self.skip_first = skip_first - self.counter_start_zero = counter_start_zero - self.count = 0 if counter_start_zero else 1 - self.seq = np.array([-1]) if counter_start_zero else np.array([self.state[-1]]) - self.outbit = -1 if counter_start_zero else self.state[-1] - self.feedbackbit = -1 if counter_start_zero else self.state[-1] - self.verbose = verbose - self.M = self.initstate.shape[0] - self.expectedPeriod = 2**self.M - 1 - self.T = 2**self.M - 1 - self.fpoly.sort(reverse=True) - feed = ' ' - for i in range(len(self.fpoly)): - feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' - feed = feed + '1' - self.feedpoly = feed - - self.check() - - def info(self): - ''' - Display the information about LFSR with current state of variables - ''' - print('%d bit LFSR with feedback polynomial %s' % (self.M, self.feedpoly)) - print('Expected Period (if polynomial is primitive) = ', self.expectedPeriod) - print('Current :') - print(' State : ', self.state) - print(' Count : ', self.count) - print(' Output bit : ', self.outbit) - print(' feedback bit : ', self.feedbackbit) - if self.count > 0 and self.count < 1000: - print(' Output Sequence %s' % (''.join([str(int(x)) for x in self.seq]))) - - def check(self): - ''' - Check if - - degree of feedback polynomial <= length of LFSR >=1 - - given intistate of LFSR is correct - ''' - if np.max(self.fpoly) > self.initstate.shape[0] or np.min(self.fpoly) < 1 or len(self.fpoly) < 2: - raise ValueError('Wrong feedback polynomial') - if len(self.initstate.shape) > 1 and (self.initstate.shape[0] != 1 or self.initstate.shape[1] != 1): - raise ValueError('Size of intial state vector should be one diamensional') - else: - self.initstate = np.squeeze(self.initstate) - assert np.sum(self.initstate>1) + np.sum(self.initstate<0)==0 # test if initial state is binary, 1s and 0s - - def set(self, fpoly, state='ones'): - ''' - Set feedback polynomial and state - - Parameters - ---------- - fpoly : list - feedback polynomial like [5,4,3,2] - - state : np.array, like np.array([1,0,0,1,1]) - default ='ones' - Initial state is intialized with ones and length of register is equal to - degree of feedback polynomial - if state='rand' - Initial state is intialized with random binary sequence of length equal to - degree of feedback polynomial - ''' - self.__init__(fpoly=fpoly, initstate=state) - - def reset(self): - ''' - Reseting LFSR to its initial state and count - ''' - self.__init__(initstate=self.initstate, fpoly=self.fpoly,counter_start_zero=self.counter_start_zero ) - - def changeFpoly(self, newfpoly, reset=False): - ''' - Changing Feedback polynomial : Useful to change feedback polynomial in between as in A5/1 stream cipher - - Parameters - ---------- - newfpoly : list like, [5,4,2,1] - changing the feedback polynomial - - reset : boolean default=False - if True, reset all the Parameters: count and seq etc .... - if False, leave the LFSR as it is only change the feedback polynomial - for further use, as used in - 'Enhancement of A5/1: Using variable feedback polynomials of LFSR' - https://doi.org/10.1109/ETNCC.2011.5958486 - ''' - newfpoly.sort(reverse=True) - self.fpoly = newfpoly - feed = ' ' - for i in range(len(self.fpoly)): - feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' - feed = feed + '1' - self.feedpoly = feed - - self.check() - if reset: - self.reset() - - def next(self): - ''' - Run one cycle on LFSR with given feedback polynomial and - update the count, state, feedback bit, output bit and seq - - Returns - ------- - output bit : binary - ''' - if self.verbose: print('S: ', self.state) - if self.counter_start_zero: - if self.count ==0: - self.seq = np.array([self.state[-1]]) - else: - self.seq = np.append(self.seq, self.state[-1]) - - b = np.logical_xor(self.state[self.fpoly[0] - 1], self.state[self.fpoly[1] - 1]) - if len(self.fpoly) > 2: - for i in range(2, len(self.fpoly)): - b = np.logical_xor(self.state[self.fpoly[i] - 1], b) - - self.outbit = self.state[-1] - self.state = np.roll(self.state, 1) - self.feedbackbit = b * 1 - self.state[0] = self.feedbackbit - - if not(self.counter_start_zero): - if self.count ==0: - self.seq = np.array([self.state[-1]]) - else: - self.seq = np.append(self.seq, self.state[-1]) - - self.count += 1 - - return self.outbit - - def runFullCycle(self): - ''' - Run a full cycle (T = 2^M-1) on LFSR from current state - - Returns - ------- - seq : binary output sequence since start: shape = (count,) - ''' - temp = [self.next() for i in range(self.expectedPeriod)] - return self.seq - - def getFullPeriod(self): - ''' - Get a seq of a full period from LSFR, by executing next() method T times. - The current state of LFSR is used to generate T bits. - - Returns - ------- - seq (T bits), binary output sequence of last T bits - ''' - seq = np.array([self.next() for i in range(self.expectedPeriod)]) - return seq - - def runKCycle(self, k): - ''' - Run k cycles and update all the Parameters - - Parameters - ---------- - k : int - - Returns - ------- - tempseq : shape =(k,), output binary sequence of k cycles - ''' - tempseq = [self.next() for i in range(k)] - return np.array(tempseq) - - def _loadFpolyList(self): - import os - fname = 'primitive_polynomials_GF2_dict.txt' - fname = os.path.join(os.path.dirname(__file__), fname) - try: - f = open(fname, "rb") - lines = f.readlines() - f.close() - self.fpolyList = eval(lines[0].decode()) - except: - raise Exception("File named:'{}' Not Found!!! \n try again, after downloading file from github save it in lfsr directory".format(fname)) - - def get_fpolyList(self,m=None): - ''' - Get the list of primitive polynomials as feedback polynomials for m-bit LFSR. - Only list of primary primitive polynomials are retuned, not full list (half list), since for each primary primitive polynomial - an image polymial can be computed using 'get_Ifpoly' method - - Parameters - ---------- - m: 1 1 and m < 32: - return self.fpolyList[m] - else: - print('Wrong input m. m should be int 1 < m < 32 or None') - - def get_Ifpoly(self,fpoly): - ''' - Get image of feebback polynomial - Get the image of primitive polynomial - Parameters - ---------- - fpoly: polynomial as list e.g. [5,2] for x^5 + x^2 + 1 - : should be a valid primitive polynomial - - Returns - ------- - ifpoly: polynomial as list e.g. [5,3] for x^5 + x^3 + 1 - - ''' - if isinstance(fpoly, list) or (isinstance(fpoly, numpy.ndarray) and len(fpoly.shape)==1): - fpoly = list(fpoly) - fpoly.sort(reverse=True) - ifpoly = [fpoly[0]] +[fpoly[0]-ff for ff in fpoly[1:]] - ifpoly.sort(reverse=True) - return ifpoly - else: - print('Not a valid form of feedback polynomial') - - def test_properties(self,verbose=1): - p1 = self.getFullPeriod() - p2 = self.getFullPeriod() - r1 = np.mean(p1==p2)==1 - - r2, (N1s,N0s) = self.balance_property(p1.copy()) - - r3,runs = self.runlength_property(p1.copy(),verbose=0) - - r4,(shift,rxx) = self.autocorr_property(p1.copy(),plot=False) - - result = bool(np.prod([r1,r2,r3,r4])) - - if verbose: - print('1. Periodicity') - print('------------------') - print(' - Expected period = 2^M-1 =',self.expectedPeriod) - print(' - Pass?: ',r1) - print('') - print('2. Balance Property') - print('-------------------') - print(' - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = ',(N1s, N0s)) - print(' - Pass?: ',r2) - print('') - print('3. Runlength Property') - print('-------------------') - print(' - Number of Runs in a period should be of specific order, e.g. [4,2,1,1]') - print(' - Runs: ',runs) - print(' - Pass?: ',r3) - print('') - print('4. Autocorrelation Property') - print('-------------------') - print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else') - if verbose>1: - print(' - Rxx(k): ',rxx) - try: - import matplotlib.pyplot as plt - except: - raise('Error loading matplotlib, either install it or set verbose<2') - plt.plot(shift,rxx) - plt.xlabel('shift (k)') - plt.ylabel(r'$R_{xx}(k)$') - plt.axhline(y=0,color='k',ls=':',lw=0.5) - plt.xlim(shift[0],shift[-1]) - plt.title('Autocorrelation') - plt.grid(alpha=0.4) - plt.show() - print(' - Pass?: ',r4) - print('\n\n') - print('==================') - if result: - print('Passed all the tests') - else: - print('Failed one or more tests, check if feedback polynomial is primitive polynomial') - print('==================') - return result - - def test_p(self,p,verbose=1): - ''' - Test all the three properties for seq p : - (1) Balance Property - (2) Runlegth Property - (3) Autocorrelation Property - - - Parameters - ---------- - p : array-like, a sequence of a period from LFSR - verbose = 0 : no printing details - = 1 : print details - = 2 : print and plot more details - Returns - ------- - result: bool, True if all three are satisfied else False - ''' - r1,(N1s,N0s) = self.balance_property(p.copy()) - r2,runs = self.runlength_property(p.copy(),verbose=0) - r3,(shift,rxx) = self.autocorr_property(p.copy(),plot=False) - result = bool(np.prod([r1,r2,r3])) - if verbose: - print('1. Balance Property') - print('-------------------') - print(' - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = ',(N1s, N0s)) - print(' - Pass?: ',r1) - print('') - print('2. Runlength Property') - print('-------------------') - print(' - Number of Runs in a period should be of specific order, e.g. [4,2,1,1]') - print(' - Runs: ',runs) - print(' - Pass?: ',r2) - print('') - print('3. Autocorrelation Property') - print('-------------------') - print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else') - if verbose>1: - print(' - Rxx(k): ',rxx) - try: - import matplotlib.pyplot as plt - except: - raise('Error loading matplotlib, either install it or set verbose<2') - plt.plot(shift,rxx) - plt.xlabel('shift (k)') - plt.ylabel(r'$R_{xx}(k)$') - plt.axhline(y=0,color='k',ls=':',lw=0.5) - plt.xlim(shift[0],shift[-1]) - plt.title('Autocorrelation') - plt.grid(alpha=0.4) - plt.show() - print(' - Pass?: ',r3) - print('\n\n') - print('==================') - if result: - print('Passed all three tests') - else: - print('Failed one or more tests') - print('==================') - - return result - - def balance_property(self,p): - ''' - Balance Property: In a period of LFSR with a valid feedback polynomial, - the number of 1s should be equal to number of 0s +1 - '' N1s == N0s + 1 '' - Test balance property for a given full period of seq, p. - - Parameters - ---------- - p: array-like, a sequence of a period from LFSR - - - Returns - ------- - result: bool, True if seq p satisfies Balance Property else False - (N1s, N0s): tuple, number of 1s and number of 0s - ''' - N1s = np.sum(p==1) - N0s = np.sum(p==0) - result = N1s == N0s+1 - return result, (N1s,N0s) - - def runlength_property(self,p,verbose=0): - ''' - Run Length Property: In a period of LSFR with valid feedback polynomial, - the number of runs of different length are in specific order. - '' - number of (M-k) bit runs = ⌈ 2^(k-1) ⌉ , for k = 0 to M-1 - '' - where ⌈ ⌉ is a ceiling function - That is, for M bit LFSR, - - number of M bit runs : 1 - - number of (M-1) bit runs : 1 - - number of (M-2) bit runs : 2 - - number of (M-3) bit runs : 4 - ... - so on - - Parameters - ---------- - p: array-like, a sequence of a period from LFSR - - Returns - ------- - result: bool, True if seq p satisfies Run Length Property else False - runs: list, list of runs - ''' - T = len(p) - if verbose>1: print(p) - while p[0]==p[-1]: p = np.roll(p,1) - - if verbose>1: print(p) - if p[-1]==0: - p = np.append(p,1) - else: - p = np.append(p,0) - if verbose>1: print(p) - - i=0 - runs = np.zeros(T).astype(int) - for k in range(T): - if p[k]==p[k+1]: - i=i+1 - else: - runs[i]=runs[i]+1 - i=0 - if verbose>1: print(runs) - runs = runs[:max(np.where(runs)[0])+1] - if verbose: print('Runs : ',runs) - - l = len(runs) - pp=0 - for k in range(len(runs)-2): - if runs[k]==2*runs[k+1]: - pp=pp+1 - if runs[-2]==runs[-1]: pp=pp+1 - - result = False - - if pp==len(runs)-1: result = True - if verbose>1: - if result: print('Pass') - else: print('Fail') - return result, runs - - def autocorr_property(self,p,plot=False): - ''' - Autocorrelation Property: For sequence of period T of LSFR with valid feedback polynomial, - the autocorrelation is a noise like, that is, 1 with zero (or T) lag (shift), -1/T (almost zero) else. - - unlike usual, for binary, the correlation value between two sequence of same length bx, by is computed as follow; - match = sum(bx == by) (number of mataches) - mismatch = sum(bx!= by) (number of mismatches) - '' - rxy = (match - mismatch)/ length(bx) - '' - - Parameters - ---------- - p: array-like, a sequence of a period from LFSR - - plot: bool (default False), if True, it will plot the autocorrelation function, - which will require matplotlib library. Turn it of if matplotlib is not installed - - Returns - ------- - result: bool, True if seq p satisfies Autocorrelation Property else False - (shift, rxx): tuple of sequence of shift corresponding autocorrelation values - ''' - T = len(p) - px = p.copy() - rxx = np.zeros(2*T+1) - for k in range(2*T+1): - py = np.roll(p.copy(),k) - r = px==py - rxx[k] = (np.sum(r==1) - np.sum(r==0))/T - - result = False - if np.prod(np.isclose(rxx[1:T],-1/T)): - result = True - - shift = np.arange(-T,T+1) - if plot: - try: - import matplotlib.pyplot as plt - except: - raise('Error loading matplotlib, either install it or set plot=False') - plt.plot(shift,rxx) - plt.xlabel('shift (k)') - plt.ylabel(r'$R_{xx}(k)$') - plt.axhline(y=0,color='k',ls=':',lw=0.5) - plt.xlim(shift[0],shift[-1]) - plt.title('Autocorrelation') - plt.grid(alpha=0.4) - plt.show() - return result, (shift,rxx) - -if __name__ == '__main__': - import doctest - doctest.testmod() +''' +Author @ Nikesh Bajaj +Version : 1.0.7 +Contact: n.bajaj@qmul.ac.uk + : n.bajaj@imperial.ac.uk + : http://nikeshbajaj.in +-----changelog------------------- +first created : Date: 22 Oct 2017 +Updated on : 29 Apr 2021 (version:1.0.6) + : fixed bugs (1) not counting first outbit correctly (2) Exception in info method + : added test properties (1) Balance (2) Runlength (3) Autocorrelation + : improved functionalities + : added Viz function + : added A5/1 and Geffe Generator +Updated on : 03 Jan 2023 (version:1.0.7) + : Added Galois Configuration for LFSR + : fixed bugs, improved documentation +''' + +from __future__ import absolute_import, division, print_function +name = "LFSR " +import sys + +if sys.version_info[:2] < (3, 3): + old_print = print + def print(*args, **kwargs): + flush = kwargs.pop('flush', False) + old_print(*args, **kwargs) + if flush: + file = kwargs.get('file', sys.stdout) + # Why might file=None? IDK, but it works for print(i, file=None) + file.flush() if file is not None else sys.stdout.flush() + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +from .utils import deprecated, progbar + +class LFSR(): + ''' + Linear Feedback Shift Register + + class LFSR(fpoly=[5,2],initstate='ones',verbose=False) + + Parameters + ---------- + + fpoly : List, optional (default=[5,2]) + Feedback polynomial, it has to be primitive polynomial of GF(2) field, for valid output of LFSR + + Example: for 5-bit LFSR, fpoly=[5,2], [5,3], [5,4,3,2], etc + : for M-bit LFSR fpoly = [M,...] + + to get the list of feedback polynomials check method 'get_fpolyList' + or check Refeferece: + Ref: List of some primitive polynomial over GF(2)can be found at + http://www.partow.net/programming/polynomials/index.html + http://www.ams.org/journals/mcom/1962-16-079/S0025-5718-1962-0148256-1/S0025-5718-1962-0148256-1.pdf + http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf + + + initstate : binary np.array (row vector) or str ='ones' or 'random', optional (default = 'ones')) + Initial state vector of LFSR. initstate can not be all zeros. + + default ='ones' + Initial state is intialized with ones and length of register is equal to + degree of feedback polynomial + if state='rand' or 'random' + Initial state is intialized with random binary sequence of length equal to + degree of feedback polynomial + if passed as list or numpy array + initstate = [1,1,0,0,1] + + Theoretically the length initial state vector should be equal to order of polynomial (M), however, it can easily be bigger than that + which is why all the validation of state vector and fpoly allows bigger length of state vector, however small state vector will raise an error. + + counter_start_zero: bool (default = True), whether to start counter with 0 or 1. If True, initial outbit is + set to -1, so is feedbackbit, until first .next() clock is excecuted. This initial output is not stacked in + seq. The output sequence should be same, in anycase, for example if you need run 10 cycles, using runKCycle(10) methed. + + verbose : boolean, optional (default=False) + if True, state of LFSR will be printed at every cycle(iteration) + + conf: str {'fibonacci', 'galois'}, default conf='fibonacci' + : configuration mode of LFSR, either fabonacci or galoisi. + : Example of 16-bit LFSR: + fibonacci: https://en.wikipedia.org/wiki/Linear-feedback_shift_register#/media/File:LFSR-F16.svg + Galois: https://en.wikipedia.org/wiki/Linear-feedback_shift_register#/media/File:LFSR-G16.svg + + seq_bit_index: int, index of shift register for output sequence. Default=-1, which means the last register + : seq_bit_index can varies from -M to M-1,for M-bit LFSR. For example 5-bit LFSR, seq_bit_index=-5,-4,-3,-2,-1, 0, 1, 2, 3, 4 + : seq_bit_index=-1, means output sequence is taken out from last Register, -2, second last, + + + Attributes + ---------- + count : int + Count the cycle, starts with 0 if counter_start_zero True, else starts with 1 + seq : np.array shape =(count,) + Output sequence stored in seq since first cycle + if -1, no cycle has been excecuted, count=0 when counter_start_zero is True + else last bit of initial state + + outbit : binary bit + Current output bit, + Last bit of current state + if -1, no cycle has been excecuted, count =0, when counter_start_zero is True + + feedbackbit : binary bit + if -1, no cycle has been excecuted, count =0, when counter_start_zero is True + + M : int + length of LFSR, M-bit LFSR + + expectedPeriod : int (also saved as T) + Expected period of sequence + if feedback polynomial is primitive and irreducible (as per reference) + period will be 2^M -1 + + T : int (also saved as expectedPeriod) + Expected period of sequence + if feedback polynomial is primitive and irreducible (as per reference) + period will be 2^M -1 + + feedpoly : str + feedback polynomial + + + Methods + ------- + + | Clocking (running LFSR):: + - next() : running one cycle + - runKCycle(k) : running k cycles + - runFullPeriod(): running a full period of cylces + + | Deprecated methods:: + - runFullCycle() : + - set() : set fpoly and initialstate + - changeFpoly(newfpoly) : change fpoly + - change_conf(conf) : change configuration + + | Setters:: + - reset() :reset to initial settings + - set_fpoly(fpoly) : change/set fpoly + - set_conf(conf) : change/set configuration + - set_state(state) : change/set state + - set_seq_bit_index(bit_index) : change/set seq_bit_index + + | Getters:: + - getFullPeriod() : get a period + - get_fPoly() : get feedback polynomial + - get_initState() : get initial state + - get_currentState() : get current state + - getState() : get current state as string + - get_outputSeq() : get output sequence + - getSeq() : get output sequence as string + - get_period() : get period + - get_expectedPeriod() : get expected period + - get_count() : get counter + + | Testing Properties + - test_properties() : Test all the properties for a valid LFSR + - balance_property(p) : Test Balance property for a given sequence p + - runlength_property(p): Test Runlength property for a given sequence p + - autocorr_property(p) : Test Autocorrelation property for a given sequence p + - test_p(p) :Test three properties for a given sequence p + + | Displaying:: + - info(): Display all the attribuates of LFSR + - Viz() : Display LFSR as a figure with a current state of LSFR with feedback polynomials and given configuration + + + Examples:: + ========== + # For more detailed and updated examples, please check - https://lfsr.readthedocs.io/ + #------------------------------------------------------- + >>> import numpy as np + >>> from pylfsr import LFSR + + ## Example ## 5 bit LFSR with x^5 + x^2 + 1 + >>> L = LFSR() #default fpoly=[5,2], initstate='ones' + >>> L = LFSR(fpoly=[5,2], initstate='ones') + + ### run one cycle + >>> L.next() + 1 + + ### run 10 cycles + >>> L.runKCycle(10) + array([1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) + + ### run one period of cycles + #>>> L.runFullCycle() # Depreciated + >>> L.runFullPeriod() # doctest: +NORMALIZE_WHITESPACE + array([1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) + + ## Displaying Info + >>> L.info() + 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [1 1 1 1 1] + Count : 0 + Output bit : -1 + feedback bit : -1 + + + ## Displaying Info with print + >>>print(L) + LFSR ( x^5 + x^2 + 1) + ================================================== + initstate = [1. 1. 1. 1. 1.] + fpoly = [5, 2] + conf = fibonacci + order = 5 + expectedPeriod = 31 + seq_bit_index = -1 + count = 1 + state = [0 1 1 1 1] + outbit = 1 + feedbackbit = 0 + seq = [1] + counter_start_zero = True + + + ## Displaying Info with repr + >>>repr(L) + "LFSR('fpoly'=[5, 2], 'initstate'=ones,'conf'=fibonacci, 'seq_bit_index'=-1,'verbose'=False, 'counter_start_zero'=True)" + + + >>> L.info() # doctest: +NORMALIZE_WHITESPACE + 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [0 0 1 0 0] + Count : 42 + Output bit : 1 + feedback bit : 0 + Output Sequence 111110011010010000101011101100011111001101 + + + ## Example ## 5-bit LFSR with custom state vector and feedback polynomial + >>> state = np.array([0,0,0,1,0]) + >>> fpoly = [5,4,3,2] + >>> L1 = LFSR(fpoly=fpoly,initstate =state, verbose=True) + >>> L1.info() # doctest: +NORMALIZE_WHITESPACE + 5 bit LFSR with feedback polynomial x^5 + x^4 + x^3 + x^2 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [0 0 0 1 0] + Count : 0 + Output bit : -1 + feedback bit : -1 + + ### run 10000 cycles + >>> tempseq = L.runKCycle(10000) # generate 10000 bits from current state + + ### verbosity ON + >>>state = np.array([0,0,0,1,0]) + >>>fpoly = [5,4,3,2] + >>>L1 = LFSR(fpoly=fpoly,initstate=state, verbose=True) + >>> tempseq = L1.runKCycle(10) + S: [0 0 0 1 0] + S: [1 0 0 0 1] + S: [1 1 0 0 0] + S: [1 1 1 0 0] + S: [0 1 1 1 0] + S: [1 0 1 1 1] + S: [1 1 0 1 1] + S: [1 1 1 0 1] + S: [1 1 1 1 0] + S: [1 1 1 1 1] + >>> tempseq + array([0, 1, 0, 0, 0, 1, 1, 1, 0, 1]) + + + ## Example ## TO visualize the process with 3-bit LFSR, with default counter_start_zero = True + >>> state = [1,1,1] + >>> fpoly = [3,2] + >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=True) + >>> print('count \t state \t\toutbit \t seq') + >>> print('-'*50) + >>> for _ in range(15): + >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + >>> L.next() + >>> print('-'*50) + >>> print('Output: ',L.seq) + count state outbit seq + -------------------------------------------------- + 0 [1 1 1] -1 [-1] + 1 [0 1 1] 1 [1] + 2 [0 0 1] 1 [1 1] + 3 [1 0 0] 1 [1 1 1] + 4 [0 1 0] 0 [1 1 1 0] + 5 [1 0 1] 0 [1 1 1 0 0] + 6 [1 1 0] 1 [1 1 1 0 0 1] + 7 [1 1 1] 0 [1 1 1 0 0 1 0] + 8 [0 1 1] 1 [1 1 1 0 0 1 0 1] + 9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] + 10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] + 11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] + 12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] + 13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] + 14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] + -------------------------------------------------- + Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] + + ## Example ## To visualize the process with 3-bit LFSR, with counter_start_zero = False + >>> state = [1,1,1] + >>> fpoly = [3,2] + >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) + >>> print('count \t state \t\toutbit \t seq') + >>> print('-'*50) + >>> for _ in range(15): + >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + >>> L.next() + >>> print('-'*50) + >>> print('Output: ',L.seq) + count state outbit seq + -------------------------------------------------- + 1 [1 1 1] 1 [1] + 2 [0 1 1] 1 [1 1] + 3 [0 0 1] 1 [1 1 1] + 4 [1 0 0] 0 [1 1 1 0] + 5 [0 1 0] 0 [1 1 1 0 0] + 6 [1 0 1] 1 [1 1 1 0 0 1] + 7 [1 1 0] 0 [1 1 1 0 0 1 0] + 8 [1 1 1] 1 [1 1 1 0 0 1 0 1] + 9 [0 1 1] 1 [1 1 1 0 0 1 0 1 1] + 10 [0 0 1] 1 [1 1 1 0 0 1 0 1 1 1] + 11 [1 0 0] 0 [1 1 1 0 0 1 0 1 1 1 0] + 12 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0] + 13 [1 0 1] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] + 14 [1 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] + -------------------------------------------------- + Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] + + ## Example ## To visualize LFSR + L.Viz(show=False, show_labels=False,title='R1') + + + ## Galois Configuration + L = LFSR(initstate='ones',fpoly=[5,3],conf='galois') + L.Viz() + + + ## Example ## Change/Set conf in between + >>>L1 = LFSR(initstate='ones',fpoly=[5,3],conf='fibonacci') + >>>L1.set_fpoly(fpoly=[5,3]) + >>>L1.set_state(state=[1,1,0,0,1]) + >>>L1.set_conf(conf='galois') + + ## Example ## 23 bit LFSR with custum state and feedback polynomial + + >>> fpoly = [23,19] + >>> L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) + >>> L1.info() + 23 bit LFSR with feedback polynomial x^23 + x^19 + 1 + Expected Period (if polynomial is primitive) = 8388607 + Current : + State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] + Count : 0 + Output bit : -1 + feedback bit : -1 + + >>> seq = L1.runKCycle(100) + >>> seq + array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) + + >>> #L.changeFpoly(newfpoly =[23,21]) + >>> L.set_fpoly(fpoly =[23,21]) + >>> seq1 = L.runKCycle(20) + + ## Example ## testing the properties + >>> state = [1,1,1,1,0] + >>> fpoly = [5,3] + >>> L = LFSR(initstate=state,fpoly=fpoly) + >>> L.info() + 5 bit LFSR with feedback polynomial x^5 + x^3 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [1 1 1 1 0] + Count : 0 + Output bit : -1 + feedback bit : -1 + + >>>result = L.test_properties(verbose=1) + 1. Periodicity + ------------------ + - Expected period = 2^M-1 = 31 + - Pass?: True + + 2. Balance Property + ------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) + - Pass?: True + + 3. Runlength Property + ------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [8 4 2 1 1] + - Pass?: True + + 4. Autocorrelation Property + ------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: True + + ================== + Passed all the tests + ================== + + >>> p = L.getFullPeriod() + >>> p + array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 1, 1, 0]) + + >>> L.balance_property(p.copy()) + (True, (16, 15)) + + >>> L.runlength_property(p.copy()) + (True, array([8, 4, 2, 1, 1])) + + >>> L.autocorr_property(p.copy())[0] + True + + ## Example 7 ## testing the properties for non-primitive polynomial + >>> state = [1,1,1,1,0] + >>> fpoly = [5,1] + >>> L = LFSR(initstate=state,fpoly=fpoly) + >>> result = L.test_properties(verbose=1) + 1. Periodicity + ------------------ + - Expected period = 2^M-1 = 31 + - Pass?: False + + 2. Balance Property + ------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) + - Pass?: False + + 3. Runlength Property + ------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [10 2 1 1 2] + - Pass?: False + + 4. Autocorrelation Property + ------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: False + + ================== + Failed one or more tests, check if feedback polynomial is primitive polynomial + ================== + ''' + + def __init__(self, fpoly=[5, 2], initstate='ones', conf='fibonacci',seq_bit_index=-1,verbose=False,counter_start_zero=True): + + self._initstate = initstate + if isinstance(initstate, str): + if initstate == 'ones': + initstate = np.ones(np.max(fpoly)) + elif initstate == 'random' or initstate == 'rand': + initstate = np.random.randint(0, 2, np.max(fpoly)) + else: + raise ValueError('Unknown initial state') + if isinstance(initstate, list): + initstate = np.array(initstate) + + self.initstate = initstate + self.fpoly = fpoly + self.state = initstate.astype(int) + + # Configuration can be either 'fibonacci' or 'galois' + self.conf = conf + + # sequence bit: sequence to be taken from, default -1, last bit of LFSR + self.seq_bit_index = seq_bit_index + + #self.skip_first = skip_first + self.counter_start_zero = counter_start_zero + self.count = 0 if counter_start_zero else 1 + + + self.verbose = verbose + self.update() + self.check() + self.seq = np.array([-1]) if counter_start_zero else np.array([self.state[self.seq_bit_index]]) + self.outbit = -1 if counter_start_zero else self.state[self.seq_bit_index] + self.feedbackbit = -1 if counter_start_zero else self.state[self.seq_bit_index] + + def update(self): + ''' + Updatating order, period and feedpoly string + ''' + self.fpoly.sort(reverse=True) + feed = ' ' + for i in range(len(self.fpoly)): + feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' + feed = feed + '1' + self.feedpoly = feed + + self.M = np.max(self.fpoly) + self.order = self.M + self.expectedPeriod = 2**self.M - 1 + self.T = 2**self.M - 1 + + def check(self): + ''' + Check if + - degree of feedback polynomial <= length of LFSR >=1 + - given intistate of LFSR is correct + - configuration is valid + - output sequence bit index + + ''' + + # Check Feedback Polynomial + # ------------------------ + if np.max(self.fpoly) > self.initstate.shape[0] or np.min(self.fpoly) < 1 or len(self.fpoly) < 2: + raise ValueError('Invalid feedback polynomial: Order of feedback polynomial can not be less than 2 or greater than length of state vector. \n Polynomial also can not have negative or zeros powers') + + if len(set(self.fpoly))!=len(self.fpoly): + raise ValueError('Invalid feedback polynomial: feedback polynomial vector should have unique powers') + + # Check Initial State + # ------------------------ + if len(self.initstate.shape) > 1 and (self.initstate.shape[0] != 1 or self.initstate.shape[1] != 1): + raise ValueError('Invalid Initial state vector: Size of intial state vector should be one diamensional') + else: + self.initstate = np.squeeze(self.initstate) + + if np.sum(self.initstate==1)+np.sum(self.initstate==0) != len(self.initstate): + raise ValueError('Invalid Initial state vector: Initial state vector should be binary, i.e., 0s and 1s') + if np.sum(self.initstate==0) == len(self.initstate): + raise ValueError('Invalid Initial state vector: Initial state vector can not be All Zeros') + + assert np.sum(self.initstate>1) + np.sum(self.initstate<0)==0 # test if initial state is binary, 1s and 0s + + # Check Configuration + # ------------------------ + # Configuration can be either 'fibonacci' or 'galois' + if self.conf not in ['fibonacci','galois']: + raise ValueError('Not valid configuration, "conf" should be either "fibonacci" or "galois"') + + if self.conf=='galois' and np.max(self.fpoly)!=len(self.state): + raise ValueError('Wrong length of state vector for Galois configuration. For Galois configuration, length of state vector should be same as order of feedback polynomial ') + + + # Check Output sequence bit index + # ------------------------ + if self.seq_bit_index not in list(range(-np.max(self.fpoly), np.max(self.fpoly))): + raise IndexError('Output sequence can be taken from one of the register only [%d,%d), index = %d provided: Out of bounds index' % (-np.max(self.fpoly), np.max(self.fpoly), self.seq_bit_index)) + + def check_state(self): + ''' + check if current state vector is valid + ''' + + # Check Initial State + # ------------------------ + if len(self.state.shape) > 1 and (self.state.shape[0] != 1 or self.state.shape[1] != 1): + raise ValueError('Invalid state vector: Size of state vector should be one diamensional') + + if np.sum(self.state==1)+np.sum(self.state==0) != len(self.state): + raise ValueError('Invalid state vector: state vector should be binary, i.e., 0s and 1s') + + if np.sum(self.state==0) == len(self.state): + raise ValueError('Invalid state vector: State vector can not be All Zeros') + + if self.conf=='galois' and np.max(self.fpoly)!=len(self.state): + raise ValueError('Wrong length of state vector for Galois configuration. For Galois configuration, length of state vector should be same as order of feedback polynomial ') + + assert np.sum(self.state>1) + np.sum(self.state<0)==0 # test if initial state is binary, 1s and 0s + + def info(self): + ''' + Display the information about LFSR with current state of variables + ''' + print('%d-bit LFSR with feedback polynomial %s with' % (self.M, self.feedpoly)) + print('Expected Period (if polynomial is primitive) = ', self.expectedPeriod) + if self.seq_bit_index>-1: + print('Computing configuration is set to %s with output sequence taken from %d-th register' % (self.conf.capitalize(), self.seq_bit_index+1)) + else: + print('Computing configuration is set to %s with output sequence taken from %d-th (%d) register' % (self.conf.capitalize(), self.seq_bit_index%self.M +1, self.seq_bit_index)) + print('Current :') + print(' State : ', self.state) + print(' Count : ', self.count) + print(' Output bit : ', self.outbit) + print(' feedback bit : ', self.feedbackbit) + if self.count > 0 and self.count < 1000: + print(' Output Sequence: %s' % (''.join([str(int(x)) for x in self.seq]))) + + def __repr__(self): + fmt = f"LFSR('fpoly'={self.fpoly}, 'initstate'={self._initstate.astype(int).tolist() if isinstance(self._initstate,np.ndarray) else self._initstate}," +\ + f"'conf'={self.conf}, 'seq_bit_index'={self.seq_bit_index},'verbose'={self.verbose}, 'counter_start_zero'={self.counter_start_zero})" + return fmt + + def __str__(self): + fmt = f"LFSR ({self.feedpoly})\n" + fmt = fmt+ f"{'='*50}\n" + param = ['initstate', 'fpoly', 'conf', 'order', 'expectedPeriod', 'seq_bit_index', ] + param = param + ['count','state','outbit','feedbackbit','seq','counter_start_zero'] + + for key in param: + if key in self.__dict__: + fmt = fmt+f"{key}{' '*(10-len(key))}\t=\t{self.__dict__[key]}\n" + return fmt + + def next(self,verbose=False): + ''' + Run one cycle on LFSR with given feedback polynomial and + update the count, state, feedback bit, output bit and seq + + Returns + ------- + output bit : binary + ''' + if self.verbose or verbose: + print('S: ', self.state) + + if self.counter_start_zero: + self.outbit = self.state[self.seq_bit_index] + if self.count ==0: + self.seq = np.array([self.state[self.seq_bit_index]]) + else: + self.seq = np.append(self.seq, self.state[self.seq_bit_index]) + + if self.conf=='fibonacci': + b = np.logical_xor(self.state[self.fpoly[0] - 1], self.state[self.fpoly[1] - 1]) + if len(self.fpoly) > 2: + for i in range(2, len(self.fpoly)): + b = np.logical_xor(self.state[self.fpoly[i] - 1], b) + + #self.outbit = self.state[-1] + self.state = np.roll(self.state, 1) + self.feedbackbit = b * 1 + self.state[0] = self.feedbackbit + else: + #self.conf=='galois': + self.feedbackbit = self.state[0] + self.state = np.roll(self.state, -1) + for k in self.fpoly[1:]: + self.state[k-1] = np.logical_xor(self.state[k-1], self.feedbackbit) + + if not(self.counter_start_zero): + self.outbit = self.state[self.seq_bit_index] + if self.count ==0: + self.seq = np.array([self.outbit]) + else: + self.seq = np.append(self.seq, self.outbit) + + self.count += 1 + + return self.outbit + + def runKCycle(self, k, verbose=False): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + if verbose: + self.verbose = False + tempseq = [] + for i in range(k): + ProgBar(i,k,title=f' {k}-cycles') + tempseq.append(self.next()) + else: + tempseq = [self.next() for _ in range(k)] + return np.array(tempseq) + + @deprecated('due to misnomer, use "runFullPeriod" instead') + def runFullCycle(self): + ''' + NOTE: Will be deprecated in future version due to misnomer, use "runFullPeriod" instead + + Run a full cycle (T = 2^M-1) on LFSR from current state + + Returns + ------- + seq : binary output sequence since start: shape = (count,) + ''' + temp = [self.next() for _ in range(self.expectedPeriod)] + return self.seq + + def runFullPeriod(self,verbose=False): + ''' + Run a full period of cycles (T = 2^M-1) on LFSR from current state + + Returns + ------- + seq : binary output sequence since start: shape = (count,) + ''' + if verbose: + self.verbose = False + tempseq = [] + for i in range(self.expectedPeriod): + ProgBar(i,self.expectedPeriod,title=f' {self.expectedPeriod}-cycles') + tempseq.append(self.next()) + else: + temp = [self.next() for _ in range(self.expectedPeriod)] + return self.seq + + def reset(self): + ''' + Reseting LFSR to its initial state and count + ''' + self.__init__(initstate=self.initstate,fpoly=self.fpoly,counter_start_zero=self.counter_start_zero,conf=self.conf,seq_bit_index=self.seq_bit_index) + + @deprecated('Use "set_fpoly" and "set_state" instead') + def set(self, fpoly, state='ones', enforce=False): + ''' + NOTE: Will be deprecated in future version, use "set_fpoly" and "set_state" instead + + Set feedback polynomial and state + + Parameters + ---------- + fpoly : list + feedback polynomial like [5,4,3,2] + + state : np.array, like np.array([1,0,0,1,1]) + default ='ones' + Initial state is intialized with ones and length of register is equal to + degree of feedback polynomial + if state='rand' + Initial state is intialized with random binary sequence of length equal to + degree of feedback polynomial + enforce: bool (defaule=False) + : test if (1) new polynomial is for same LFSR or not (2) state vector is same length or not + : Setting enforce=True, allows the change of feedback polynomial from M-bit to K-bit LFSR, for example changing from 5-bit to 3-bit etc + in that case, make sure to change initial state vector accordingly + ''' + if not enforce: + # Order of new feedback polynomial should be same as previous + # if change in size and order was intantional, set enforce=True + assert np.max(fpoly)==np.max(self.fpoly) + if not isinstance(state,str): + # Length of new state vector should be same as previous + # if change in size and order was intantional, set enforce=True + assert len(state)==len(self.state) + + self.__init__(fpoly=fpoly, initstate=state,counter_start_zero=self.counter_start_zero,conf=self.conf,seq_bit_index=self.seq_bit_index) + + @deprecated('due to inconsitancy in naming, use "set_fpoly" instead') + def changeFpoly(self, newfpoly, reset=False,enforce=False): + ''' + NOTE: Will be deprecated in future version, use "set_fpoly" instead + + Changing Feedback polynomial : Useful to change feedback polynomial in between as in A5/1 stream cipher + + Parameters + ---------- + newfpoly : list like, [5,4,2,1] + changing the feedback polynomial + + reset : boolean default=False + if True, reset all the Parameters: count and seq etc .... + if False, leave the LFSR as it is only change the feedback polynomial + for further use, as used in + 'Enhancement of A5/1: Using variable feedback polynomials of LFSR' + https://doi.org/10.1109/ETNCC.2011.5958486 + enforce: bool (defaule=False) + : test if new polynomial is for same LFSR or not + + ''' + if not enforce: + # Order of new feedback polynomial should be same as previous + #if change in size and order was intantional, set enforce=True + assert np.max(newfpoly)==np.max(self.fpoly) + + newfpoly.sort(reverse=True) + self.fpoly = newfpoly + self.update() + self.check() + if reset: self.reset() + + @deprecated('due to inconsitancy in naming, use "set_conf" instead') + def change_conf(self,conf): + assert conf in ['fibonacci', 'galois'] + self.conf = conf + self.check() + + def set_fpoly(self, fpoly, reset=False,enforce=False): + ''' + Set Feedback polynomial + ------------------------ + Useful to change feedback polynomial in between as in A5/1 stream cipher, to increase the complexity + + Parameters + ---------- + fpoly : list like, [5,4,2,1], should be same + changing the feedback polynomial + + reset : boolean default=False + if True, reset all the Parameters: count and seq etc .... + if False, leave the LFSR as it is only change the feedback polynomial + for further use, as used in + 'Enhancement of A5/1: Using variable feedback polynomials of LFSR' + https://doi.org/10.1109/ETNCC.2011.5958486 + + enforce: bool (defaule=False) + : test if new polynomial is for same LFSR or not + : Setting enforce=True allows to change feedback polynomial from M-bit to any other bit, given that state vector is changed accordingly. + : check details of initstate in help(LFSR) doc + + ''' + if not enforce: + # Order of new feedback polynomial should be same as previous + #if change in size and order was intantional, set enforce=True + assert np.max(fpoly)==np.max(self.fpoly) + + fpoly.sort(reverse=True) + + self.fpoly = fpoly + feed = ' ' + for i in range(len(self.fpoly)): + feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' + feed = feed + '1' + self.feedpoly = feed + + self.update() + self.check() + if reset: self.reset() + + def set_conf(self,conf, reset=False): + ''' + Set Configuration + ----------------- + Change configuration, useful to change configuration in between + + Parameters + ---------- + conf: str {'fibonacci', 'galois'}, default conf='fibonacci' + + reset : boolean default=False + if True, reset all the Parameters: count and seq etc .... + if False, leave the LFSR as it is only change configuration + + ''' + assert conf in ['fibonacci', 'galois'] + self.conf = conf + self.check() + if reset: self.reset() + + def set_state(self,state,return_state=False,enforce=False): + ''' + Set Current state + ----------------- + + Parameters + ---------- + state: str, list or np.array + : if str state='ones' or state='random' + : if list or np.array, it should be binary and length equal to max of polynomial degree + return_state: bool, if True, return state vector. Useful when state='random' is passed, to keep track of newly inilized state vector + enforce: bool (defaule=False) + : test if new state vector has same length as old one + : + + ''' + if isinstance(state, str): + if state == 'ones': + state = np.ones(np.max(self.fpoly)) + elif state == 'random': + state = np.random.randint(0, 2, np.max(self.fpoly)) + else: + raise ValueError('Unknown state: only ones or random is allowed') + + if isinstance(state, list): + state = np.array(state).astype(int) + + if not enforce: + # Length of new state vector should be same as previous + # if change in size and order was intantional, set enforce=True + assert len(state)== len(self.state) + self.state = state + self.check_state() + self.update() + if return_state: return self.state + + def set_seq_bit_index(self,bit_index): + ''' + Set Output bit index: for output sequence as index + -------------------------------------------------- + + seq_bit_index: int in range from -M to M-1 + + ''' + if bit_index not in list(range(-np.max(self.fpoly), np.max(self.fpoly))): + raise IndexError('Output sequence can be taken from one of the register only, index = %d out of bounds' % (bit_index)) + + # Index of draw outout sequence should be in bounds to state vector + self.state[bit_index] + + self.seq_bit_index = bit_index + + def getFullPeriod(self): + ''' + Get a seq of a full period from LSFR, by executing next() method T times. + The current state of LFSR is used to generate T bits. + + Calling this function also update the count, current state and output sequence of main LFSR object + + Returns + ------- + seq (T bits), binary output sequence of last T bits + ''' + seq = np.array([self.next() for _ in range(self.expectedPeriod)]) + return seq + + def get_fPoly(self): + '''get feedback polynomial''' + return self.fpoly + + def get_initState(self): + '''get initial state of LFSR''' + return self.initstate + + def get_currentState(self): + '''get current state of LFSR''' + return self.state + + def get_outputSeq(self): + '''get output sequence as array''' + return self.seq + + def get_period(self): + '''get period of sequence''' + return self.T + + def get_expectedPeriod(self): + '''get period of sequence''' + return self.expectedPeriod + + def get_count(self): + '''get counter value''' + return self.count + + def _loadFpolyList(self): + import os + fname = 'primitive_polynomials_GF2_dict.txt' + fname = os.path.join(os.path.dirname(__file__), fname) + try: + f = open(fname, "rb") + lines = f.readlines() + f.close() + self.fpolyList = eval(lines[0].decode()) + except: + raise Exception("File named:'{}' Not Found!!! \n try again, after downloading file from github save it in lfsr directory".format(fname)) + + def get_fpolyList(self,m=None): + ''' + Get the list of primitive polynomials as feedback polynomials for m-bit LFSR. + Only list of primary primitive polynomials are retuned, not full list (half list), since for each primary primitive polynomial + an image polymial can be computed using 'get_Ifpoly' method + + Parameters + ---------- + m: 1 1 and m < 32: + return self.fpolyList[m] + else: + print('Wrong input m. m should be int 1 < m < 32 or None') + + @staticmethod + def get_Ifpoly(fpoly): + ''' + Get image of feebback polynomial + Get the image of primitive polynomial + Parameters + ---------- + fpoly: polynomial as list e.g. [5,2] for x^5 + x^2 + 1 + : should be a valid primitive polynomial + + Returns + ------- + ifpoly: polynomial as list e.g. [5,3] for x^5 + x^3 + 1 + + ''' + if isinstance(fpoly, list) or (isinstance(fpoly, numpy.ndarray) and len(fpoly.shape)==1): + fpoly = list(fpoly) + fpoly.sort(reverse=True) + ifpoly = [fpoly[0]] +[fpoly[0]-ff for ff in fpoly[1:]] + ifpoly.sort(reverse=True) + return ifpoly + else: + print('Not a valid form of feedback polynomial') + + def test_properties(self,verbose=1): + p1 = self.getFullPeriod() + p2 = self.getFullPeriod() + + r1 = np.mean(p1==p2)==1 + r2, (N1s, N0s) = self.balance_property(p1.copy()) + r3, runs = self.runlength_property(p1.copy(),verbose=0) + r4, (shift, rxx) = self.autocorr_property(p1.copy(),plot=False) + + result = bool(np.prod([r1,r2,r3,r4])) + + if verbose: + print('1. Periodicity') + print('------------------') + print(' - Expected period = 2^M-1 =',self.expectedPeriod) + print(' - Pass?: ',r1) + print('') + print('2. Balance Property') + print('-------------------') + print(' - Number of 1s = Number of 0s+1 (in a period)') + print(' - #1s = ',N1s,'\t#0s = ', N0s, ':=',N1s,'= 1 +',N0s) + print(' - Pass?: ',r2) + print('') + print('3. Runlength Property') + print('-------------------') + print(' - Number of Runs of different lengths in a period should be of specific order, e.g. [4,2,1,1], that is 4 runs of length 1, 2 runs of length 2 and so on ..') + print(' - Runs: ',runs) + print(' - Pass?: ',r3) + print('') + print('4. Autocorrelation Property') + print('-------------------') + print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else \n') + if verbose>1: + print(' - Rxx(k): ',rxx.round(3)) + try: + import matplotlib.pyplot as plt + except: + raise('Error loading matplotlib, either install it or set verbose<2') + plt.plot(shift,rxx) + plt.xlabel('shift (k)') + plt.ylabel(r'$R_{xx}(k)$') + plt.axhline(y=0,color='k',ls=':',lw=0.5) + plt.xlim(shift[0],shift[-1]) + plt.title('Autocorrelation') + plt.grid(alpha=0.4) + plt.show() + print(' - Pass?: ',r4) + print('\n\n') + print('==================') + if result: + print('Passed all the tests') + else: + print('Failed one or more tests, check if feedback polynomial is primitive polynomial') + print('==================') + return result + + def test_p(self,p,verbose=1): + ''' + Test all the three properties for seq p : + (1) Balance Property + (2) Runlegth Property + (3) Autocorrelation Property + + + Parameters + ---------- + p : array-like, a sequence of a period from LFSR + verbose = 0 : no printing details + = 1 : print details + = 2 : print and plot more details + Returns + ------- + result: bool, True if all three are satisfied else False + ''' + r1,(N1s,N0s) = self.balance_property(p.copy()) + r2,runs = self.runlength_property(p.copy(),verbose=0) + r3,(shift,rxx) = self.autocorr_property(p.copy(),plot=False) + result = bool(np.prod([r1,r2,r3])) + if verbose: + print('1. Balance Property') + print('-------------------') + print(' - Number of 1s = Number of 0s+1 (in a period)') + print(' - #1s = ',N1s,'\t#0s = ', N0s, ':=',N1s,'= 1 +',N0s) + print(' - Pass?: ',r1) + print('') + print('2. Runlength Property') + print('-------------------') + print(' - Number of Runs in a period should be of specific order, e.g. [4,2,1,1]') + print(' - Runs: ',runs) + print(' - Pass?: ',r2) + print('') + print('3. Autocorrelation Property') + print('-------------------') + print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else') + if verbose>1: + print(' - Rxx(k): ',rxx.round(3)) + try: + import matplotlib.pyplot as plt + except: + raise('Error loading matplotlib, either install it or set verbose<2') + plt.plot(shift,rxx) + plt.xlabel('shift (k)') + plt.ylabel(r'$R_{xx}(k)$') + plt.axhline(y=0,color='k',ls=':',lw=0.5) + plt.xlim(shift[0],shift[-1]) + plt.title('Autocorrelation') + plt.grid(alpha=0.4) + plt.show() + print(' - Pass?: ',r3) + print('\n\n') + print('==================') + if result: + print('Passed all three tests') + else: + print('Failed one or more tests') + print('==================') + + return result + + def balance_property(self,p): + ''' + Balance Property: In a period of LFSR with a valid feedback polynomial, + the number of 1s should be equal to number of 0s +1 + '' N1s == N0s + 1 '' + Test balance property for a given full period of seq, p. + + Parameters + ---------- + p: array-like, a sequence of a period from LFSR + + + Returns + ------- + result: bool, True if seq p satisfies Balance Property else False + (N1s, N0s): tuple, number of 1s and number of 0s + ''' + N1s = np.sum(p==1) + N0s = np.sum(p==0) + result = N1s == N0s+1 + return result, (N1s,N0s) + + def runlength_property(self,p,verbose=0): + ''' + Run Length Property: In a period of LSFR with valid feedback polynomial, + the number of runs of different length are in specific order. + '' + number of (M-k) bit runs = ⌈ 2^(k-1) ⌉ , for k = 0 to M-1 + '' + where ⌈ ⌉ is a ceiling function + That is, for M bit LFSR, + - number of M bit runs : 1 + - number of (M-1) bit runs : 1 + - number of (M-2) bit runs : 2 + - number of (M-3) bit runs : 4 + ... + so on + + Parameters + ---------- + p: array-like, a sequence of a period from LFSR + + Returns + ------- + result: bool, True if seq p satisfies Run Length Property else False + runs: list, list of runs + ''' + T = len(p) + if verbose>1: print(p) + + if len(set(p))>1: + while p[0]==p[-1]: p = np.roll(p,1) + + if verbose>1: print(p) + if p[-1]==0: + p = np.append(p,1) + else: + p = np.append(p,0) + if verbose>1: print(p) + + i=0 + runs = np.zeros(T).astype(int) + for k in range(T): + if p[k]==p[k+1]: + i=i+1 + else: + runs[i]=runs[i]+1 + i=0 + if verbose>1: print(runs) + runs = runs[:max(np.where(runs)[0])+1] + if verbose: print('Runs : ',runs) + + l = len(runs) + pp=0 + for k in range(len(runs)-2): + if runs[k]==2*runs[k+1]: + pp=pp+1 + if runs[-2]==runs[-1]: pp=pp+1 + + result = False + + if pp==len(runs)-1: result = True + if verbose>1: + if result: print('Pass') + else: print('Fail') + return result, runs + + def autocorr_property(self,p,plot=False): + ''' + Autocorrelation Property: For sequence of period T of LSFR with valid feedback polynomial, + the autocorrelation is a noise like, that is, 1 with zero (or T) lag (shift), -1/T (almost zero) else. + + unlike usual, for binary, the correlation value between two sequence of same length bx, by is computed as follow; + match = sum(bx == by) (number of mataches) + mismatch = sum(bx!= by) (number of mismatches) + '' + rxy = (match - mismatch)/ length(bx) + '' + + Parameters + ---------- + p: array-like, a sequence of a period from LFSR + + plot: bool (default False), if True, it will plot the autocorrelation function, + which will require matplotlib library. Turn it of if matplotlib is not installed + + Returns + ------- + result: bool, True if seq p satisfies Autocorrelation Property else False + (shift, rxx): tuple of sequence of shift corresponding autocorrelation values + ''' + T = len(p) + px = p.copy() + rxx = np.zeros(2*T+1) + for k in range(2*T+1): + py = np.roll(p.copy(),k) + r = px==py + rxx[k] = (np.sum(r==1) - np.sum(r==0))/T + + result = False + if np.prod(np.isclose(rxx[1:T],-1/T)): + result = True + + shift = np.arange(-T,T+1) + if plot: + try: + import matplotlib.pyplot as plt + except: + raise('Error loading matplotlib, either install it or set plot=False') + plt.plot(shift,rxx) + plt.xlabel('shift (k)') + plt.ylabel(r'$R_{xx}(k)$') + plt.axhline(y=0,color='k',ls=':',lw=0.5) + plt.xlim(shift[0],shift[-1]) + plt.title('Autocorrelation') + plt.grid(alpha=0.4) + plt.show() + return result, (shift,rxx) + + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + + @staticmethod + def arr2str(arr): + return ''.join(arr.copy().astype(str)) + + def Viz(self,ax=None,show=True,fs=25,show_labels=False,title='',title_loc='left',box_color='lightblue',alpha=0.5, + output_arrow_color='C2',output_arrow_style='h',show_outseq=True): + + ''' + Display LFSR + ------------- + + ax: axis to plot, if None, new axis will be created, (default None) + show: if True, plt.show() will be excecuted, (default True) + fs: fontsize (default 25) + show_label: if true, will display names + title: str, title of figure, default '', + title_loc, alignment of title, 'left', 'right', 'center', (default 'left') + ''' + state = self.state + fpoly = self.fpoly + seq = self.getSeq() if show_outseq else '' + outbit = self.outbit + feedbit = self.feedbackbit + conf = self.conf + out_bit_index = self.seq_bit_index + + + dispLFSR(state=state,fpoly=fpoly,conf=conf,seq=seq,out_bit_index=out_bit_index, + ob=outbit,fb=feedbit,fs=fs,ax=ax,show_labels=show_labels,title=title, + title_loc=title_loc,box_color=box_color,alpha=alpha,output_arrow_color=output_arrow_color,output_arrow_style=output_arrow_style) + #PlotLFSR(state,fpoly,seq=seq,ob=outbit,fb=feedbit,fs=fs,ax=ax,show_labels=show_labels,title=title,title_loc=title_loc, + # box_color=box_color,alpha=alpha) + if show: plt.show() + + +def drawR(ax,x=0,y=0,s=1,alpha=0.5,color='lightblue',linewidth=1, edgecolor='k',): + rect = patches.Rectangle((x-s/2, y-s/2), s, s, linewidth=linewidth, edgecolor=edgecolor, facecolor=color,alpha=alpha) + ax.add_patch(rect) + +def PlotLFSR(state,fpoly,conf='fibonacci',seq='',ob=None,fb=None,fs=25,ax=None,show_labels=False,title='',title_loc='left', + box_color='lightblue',alpha=0.5): + ''' + ----- Plot LFSR ---- + state: current state of LFSR + fpoly: feedback polynomial of LFSR + seq: str, output sequence + ob: output bit + fb: feedback bit + ax: axis to plot, if None, new axis will be created, (default None) + + show: if True, plt.show() will be excecuted, (default True) + fs: fontsize (default 25) + show_label: if true, will display names + title: str, title of figure, default '', + title_loc, alignment of title, 'left', 'right', 'center', (default 'left') + box_color: color of register box, default='lightblue' + ''' + M = len(state) + ym = 3.5 + if ax is None: + fig, ax = plt.subplots(figsize=(M+5,ym)) + + s=1 + xs = 3 + ys = ym-1 + last_x= xs + + if conf=='fibonacci': + for k in range(M): + x,y = xs+k,ys + ax.text(x,y,str(state[k]),ha='center',va = 'center',fontsize=fs) + + if k==0: + x1, y1 = [x-1.5*s, x-s/2], [y, y] + ax.plot(x1, y1,marker = '>',color='k',markevery=(1,1),ms=10) + + if k+1 in fpoly: + x1, y1 = [x, x], [y-s/2, y-1.5*s] + ax.plot(x1, y1,marker = '.',color='k') + ax.plot(x,y-1.5*s,marker = '+',color='b',ms=15,mew=3) + ax.plot(x,y-1.5*s,marker = 'o',color='b',ms=15,mfc='none',mew=2) + if last_x-M and out_bit_index<=M: + out_bit_index=1+(out_bit_index)%M + + #print(out_bit_index) + + output_arr_plot=False + + if conf=='fibonacci': + for k in range(M): + x,y = xs+k,ys + ax.text(x,y,str(state[k]),ha='center',va = 'center',fontsize=fs) + + if k==0: + x1, y1 = [x-1.5*s, x-s/2], [y, y] + ax.plot(x1, y1,marker = '>',color='k',markevery=(1,1),ms=10) + + if k+1 in fpoly: + x1, y1 = [x, x], [y-s/2, y-1.5*s] + ax.plot(x1, y1,marker = '.',color='k') + ax.plot(x1[0],y-1*s,marker = 'v',color='k',ms=8,mew=3) + + ax.plot(x,y-1.5*s,marker = '+',color='b',ms=15,mew=3) + ax.plot(x,y-1.5*s,marker = 'o',color='b',ms=15,mfc='none',mew=2) + + if last_x 1)*1 + return self.outbit + + def getLastbits(self): + return [self.R1.state[-1],self.R2.state[-1],self.R3.state[-1]] + def getCbits(self): + return [self.R1.state[8],self.R2.state[10],self.R3.state[10]] + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + def arr2str(self,arr): + return ''.join(arr.copy().astype(str)) + def runKCycle(self, k): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + tempseq = [self.next() for i in range(k)] + return np.array(tempseq) + +class Geffe(): + ''' + Geffe Generator + --------------- + Combining K LFSR in non-linear manner + linear complexity + + Parameters + ---------- + K+1 LFSRs + + kLFSR_list: list of K LFSR, output of one of these is choosen at any time, depending on cLFSR + cLFSR: clocking LFSR + + K should be power of 2. 2,4,8,... 128 + + Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. + Chaper 16 + + + Example + -------- + + import numpy as np + import matplotlib.pyplot as plt + from pylfsr import Geffe, LFSR + + kLFSR = [LFSR(initstate='random') for _ in range(8)] + cLFSR = LFSR(initstate='random') + + GG = Geffe(kLFSR_list=kLFSR, cLFSR=cLFSR) + + print('key: ',GG.getState()) + print() + for _ in range(50): + print(GG.count,GG.m_count,GG.outbit_k,GG.sel_k,GG.outbit,GG.getSeq(),sep='\t') + GG.next() + + GG.runKCycle(1000) + GG.getSeq() + ''' + def __init__(self,kLFSR_list,cLFSR): + + self.K = len(kLFSR_list) + assert isinstance(cLFSR,LFSR) + assert [isinstance(Rk,LFSR) for Rk in kLFSR_list] + assert self.K>1 + assert (self.K & (self.K-1) == 0) and self.K != 0 + #K (list of LFSR) should be power of 2 + + self.m = np.log2(self.K).astype(int) + + self.kLFSR_list = kLFSR_list + self.cLFSR = cLFSR + + self.count=0 + self.m_count =0 + self.seq =np.array([]) + self.outbit = -1 + self.sel_k = -1 + self.outbit_k = [Rk.state[-1] for Rk in self.kLFSR_list] + self.state = np.hstack([R.state for R in self.kLFSR_list+[self.cLFSR]]) + self.state_k = np.hstack([R.state for R in self.kLFSR_list]) + self.state_c = self.cLFSR.state + + + def getSel(self): + sel = self.cLFSR.runKCycle(self.m) + self.m_count+=self.m + sel = ''.join(sel.astype(str)) + return int(sel, 2) + def next(self): + if self.count: + _ = [Rk.next() for Rk in self.kLFSR_list] + + self.outbit_k = [Rk.state[-1] for Rk in self.kLFSR_list] + self.sel_k = self.getSel() + self.outbit = self.outbit_k[self.sel_k] + + self.seq = np.append(self.seq,self.outbit).astype(int) + + self.state = np.hstack([R.state for R in self.kLFSR_list+[self.cLFSR]]) + self.state_k = np.hstack([R.state for R in self.kLFSR_list]) + self.state_c = self.cLFSR.state + + self.count+=1 + return self.outbit + + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + def arr2str(self,arr): + return ''.join(arr.copy().astype(str)) + + def runKCycle(self, k): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + tempseq = [self.next() for i in range(k)] + return np.array(tempseq) + +class Geffe3(): + ''' + Geffe Generator + --------------- + Combining three LFSR in non-linear manner + linear complexity: If the LFSRs have lengths n1, n2, and n3, respectively, then the linear + complexity of the generator is = (n1 + 1)n2 + n1n3 + + output bit at any time is + + b = (r1 ^ r2) • ((¬ r1) ^ r3) + + where r1,r2,r3 are the outbit of three LFSRs respectively + + Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. + Chaper 16 + + ''' + def __init__(self,R1,R2,R3): + + assert isinstance(R1,LFSR) + assert isinstance(R2,LFSR) + assert isinstance(R3,LFSR) + + self.R1 = R1 + self.R2 = R2 + self.R3 = R3 + self.count=0 + self.seq =[] + self.state = np.r_[self.R1.state, self.R2.state,self.R3.state] + self.next() + + def next(self): + if self.count: + self.R1.next() + self.R2.next() + self.R3.next() + self.r1 = self.R1.state[-1] + self.r2 = self.R2.state[-1] + self.r3 = self.R3.state[-1] + + b1 = np.logical_and(self.r1,self.r2) + b2 = np.logical_and(not(self.r1),self.r2) + self.outbit = np.logical_xor(b1,b2)*1 + + self.seq = np.append(self.seq,self.outbit).astype(int) + + self.state = np.r_[self.R1.state, self.R2.state,self.R3.state] + self.count+=1 + return self.outbit + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + def arr2str(self,arr): + return ''.join(arr.copy().astype(str)) + + def runKCycle(self, k): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + tempseq = [self.next() for i in range(k)] + return np.array(tempseq) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/build/lib/pylfsr/utils.py b/build/lib/pylfsr/utils.py new file mode 100644 index 0000000..2c8ef3d --- /dev/null +++ b/build/lib/pylfsr/utils.py @@ -0,0 +1,248 @@ +''' +Utilities for LFSR +--------------------------- +Author @ Nikesh Bajaj +Date: 03 Jan 2023 +Version : 1.0.7 +Github : https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register +Contact: n.bajaj@qmul.ac.uk +''' +from __future__ import absolute_import, division, print_function +name = "PyLFSR | utils" +import sys +import numpy as np +import functools, inspect, warnings + +if sys.version_info[:2] < (3, 3): + old_print = print + def print(*args, **kwargs): + flush = kwargs.pop('flush', False) + old_print(*args, **kwargs) + if flush: + file = kwargs.get('file', sys.stdout) + # Why might file=None? IDK, but it works for print(i, file=None) + file.flush() if file is not None else sys.stdout.flush() + + +string_types = (type(b''), type(u'')) + +def deprecated(reason): + """ + This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used. + """ + + if isinstance(reason, string_types): + + # The @deprecated is used with a 'reason'. + # + # .. code-block:: python + # + # @deprecated("please, use another function") + # def old_function(x, y): + # pass + + def decorator(func1): + + if inspect.isclass(func1): + fmt1 = "class {name} will be deprecated in future version, {reason}." + else: + fmt1 = "function {name} will be deprecated in future version, {reason}." + + @functools.wraps(func1) + def new_func1(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + fmt1.format(name=func1.__name__, reason=reason), + category=DeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', DeprecationWarning) + return func1(*args, **kwargs) + + return new_func1 + + return decorator + + elif inspect.isclass(reason) or inspect.isfunction(reason): + + # The @deprecated is used without any 'reason'. + # + # .. code-block:: python + # + # @deprecated + # def old_function(x, y): + # pass + + func2 = reason + + if inspect.isclass(func2): + fmt2 = "class {name} will be deprecated in future version." + else: + fmt2 = "function {name} will be deprecated in future version." + + @functools.wraps(func2) + def new_func2(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + fmt2.format(name=func2.__name__), + category=DeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', DeprecationWarning) + return func2(*args, **kwargs) + + return new_func2 + + else: + raise TypeError(repr(type(reason))) + +A=['\\','-','/','|'] + +def progbar(i,N,title='',style=2,L=100,selfTerminate=True,delta=None): + + pf = int(100*(i+1)/float(N)) + st = ' '*(3-len(str(pf))) + str(pf) +'%|' + + if L==50: + pb = '#'*int(pf//2)+' '*(L-int(pf//2))+'|' + else: + L = 100 + pb = '#'*pf+' '*(L-pf)+'|' + if style==1: + print(st+A[i%len(A)]+'|'+pb+title,end='\r', flush=True) + elif style==2: + print(st+pb+str(N)+'\\'+str(i+1)+'|'+title,end='\r', flush=True) + if pf>=100 and selfTerminate: + print('\nDone..') + +def print_list(L,n=3,sep='\t\t'): + L = [str(l) for l in L] + mlen = np.max([len(ll) for ll in L]) + for k in range(0,len(L)-n,n): + print(sep.join([L[ki] +' '*(mlen-len(L[ki])) for ki in range(k,k+n)])) + if k+n 1 and m < 32: + return fpolyList[m] + else: + print('Wrong input m. m should be int 1 < m < 32 or None. For greater than 32 order, please check following refrences') + #Ref: List of some primitive polynomial over GF(2)can be found at + print(" - http://www.partow.net/programming/polynomials/index.html") + print(" - http://www.ams.org/journals/mcom/1962-16-079/S0025-5718-1962-0148256-1/S0025-5718-1962-0148256-1.pdf") + print(" - http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf") + +def get_Ifpoly(fpoly): + ''' + Get image of feebback polynomial + Get the image of primitive polynomial + Parameters + ---------- + fpoly: polynomial as list e.g. [5,2] for x^5 + x^2 + 1 + : should be a valid primitive polynomial + + Returns + ------- + ifpoly: polynomial as list e.g. [5,3] for x^5 + x^3 + 1 + + ''' + if isinstance(fpoly, list) or (isinstance(fpoly, numpy.ndarray) and len(fpoly.shape)==1): + fpoly = list(fpoly) + fpoly.sort(reverse=True) + ifpoly = [fpoly[0]] +[fpoly[0]-ff for ff in fpoly[1:]] + ifpoly.sort(reverse=True) + return ifpoly + else: + print('Not a valid form of feedback polynomial') + +def lempel_ziv_patterns(seq): + r"""Lempel-Ziv patterns. + It is defined as a set of different patterns exists in a given sequence. + + As an example: + s = '1001111011000010' + patterns ==> 1, 0, 01, 11, 10, 110, 00, 010 + """ + + if isinstance(seq, (list,np.ndarray)): + seq = ''.join(seq.copy().astype(str)) + + patterns = set() + n = len(seq) + + i,k = 0, 1 + while i + k<=len(seq): + pattern = seq[i: i + k] + if pattern in patterns: + k += 1 + else: + patterns.add(pattern) + i += k + k = 1 + return patterns + +def lempel_ziv_complexity(seq): + r"""Lempel-Ziv Complexity. + It is defined as the number of different patterns exists in a given stream. + + As an example: + s = '1001111011000010' + patterns ==> 1, 0, 01, 11, 10, 110, 00, 010 + #patterns = 8 + """ + return len(lempel_ziv_patterns(seq)) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/dist/pylfsr-1.0.7-py3-none-any.whl b/dist/pylfsr-1.0.7-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..4f3e3c9696f5b8a484c8c1e481ef17893a389d29 GIT binary patch literal 28034 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 - -

- - - # Python - - - ### Requirement : *numpy* - - ## Installation - - ### with pip - - ``` - pip install pylfsr - ``` - - ### Build from the source - Download the repository or clone it with git, after cd in directory build it from source with - - ``` - python setup.py install - ``` - - ## Examples - ### **Example 1**: 5-bit LFSR with feedback polynomial *x^5 + x^2 + 1* - - ``` - # import LFSR - import numpy as np - from pylfsr import LFSR - - L = LFSR() - - # print the info - L.info() - - 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [1 1 1 1 1] - Count : 0 - Output bit : -1 - feedback bit : -1 - ``` - - - ``` - L.next() - L.runKCycle(10) - L.runFullCycle() - L.info() - ``` - - ### Example 2**: 5-bit LFSR with custum state and feedback polynomial - - ``` - state = [0,0,0,1,0] - fpoly = [5,4,3,2] - L = LFSR(fpoly=fpoly,initstate =state, verbose=True) - L.info() - tempseq = L.runKCycle(10) - L.set(fpoly=[5,3]) - ``` - - ### Example 3**: 23-bit LFSR with custum state and feedback polynomial - - ``` - L = LFSR(fpoly=[23,18],initstate ='random',verbose=True) - L.info() - L.runKCycle(10) - L.info() - seq = L.seq - ``` - - ### Example 4**: Get the feedback polynomial or list - Reference : http://www.partow.net/programming/polynomials/index.html - - ``` - L = LFSR() - # list of 5-bit feedback polynomials - fpoly = L.get_fpolyList(m=5) - - # list of all feedback polynomials as a dictionary - fpolyDict = L.get_fpolyList() - ``` - - - ### Changing feedback polynomial in between - ``` - L.changeFpoly(newfpoly =[23,14],reset=False) - seq1 = L.runKCycle(20) - - # Change after 20 clocks - L.changeFpoly(newfpoly =[23,9],reset=False) - seq2 = L.runKCycle(20) - ``` - - ### For A5/1 GSM Stream cipher generator - Reference Article: **Enhancement of A5/1**: https://doi.org/10.1109/ETNCC.2011.5958486 - - ``` - # Three LFSRs initialzed with 'ones' though they are intialized with encription key - R1 = LFSR(fpoly = [19,18,17,14]) - R2 = LFSR(fpoly = [23,22,21,8]) - R3 = LFSR(fpoly = [22,21]) - - # clocking bits - b1 = R1.state[8] - b2 = R1.state[10] - b3 = R1.state[10] - - ``` - _______________________________________________________________________________________________ - - # MATLAB - - Folder : https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/tree/master/matlabfiles - - **Description** - Genrate randon binary sequence using LFSR for any given feedback taps (polynomial), - This will also check Three fundamental Property of LFSR - 1. Balance Property - 2. Runlength Property - 3. Autocorrelation Property - - **This MATLAB Code work for any length of LFSR with given taps (feedback polynomial) -Universal, There are three files LFSRv1.m an LFSRv2.m, LFSRv3.m** - - ### LFSRv1 - This function will return all the states of LFSR and will check Three fundamental Property of LFSR - (1) Balance Property (2) Runlength Property (3) Autocorrelation Property - - #### EXAMPLE - ``` - s=[1 1 0 0 1] - t=[5 2] - [seq c] =LFSRv1(s,t) - ``` - - ### LFSRv2 - This function will return only generated sequence will all the states of LFSR, no verification of properties are done - here. Use this function to avoid verification each time you execute the program. - #### EXAMPLE - ``` - s=[1 1 0 0 1] - t=[5 2] - [seq c] =LFSRv2(s,t) - ``` - - ### LFSRv3 (faster) - *seq = LFSRv3(s,t,N)* - this function generates N bit sequence only. This is faster then other two functions, as this does not gives each state of LFSR - - #### EXAMPLE - ``` - s=[1 1 0 0 1] - t=[5 2] - seq =LFSRv3(s,t,50) - ``` - - - - ## Tips - * If you want to use this function in middle of any program, use LFSRv2 or LFSRv1 with verification =0. - * If you want to make it fast for long length of LFSR,use LFSRv3.m - - ______________________________________ - - # Contacts: - - If any doubt, confusion or feedback please contact me - * **Nikesh Bajaj** - * http://nikeshbajaj.in - * n.bajaj@qmul.ac.uk - * bajaj[dot]nikkey [AT]gmail[dot]com - ### PhD Student: Queen Mary University of London & University of Genoa - ______________________________________ - - +Project-URL: Documentation, https://lfsr.readthedocs.io +Project-URL: Say Thanks!, https://github.com/Nikeshbajaj +Project-URL: Source, https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register +Project-URL: Tracker, https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/issues Keywords: lfsr linear-feedback-shift-register random generator gf(2) -Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 @@ -212,4 +29,569 @@ Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Games/Entertainment :: Puzzle Games Classifier: Topic :: Communications +Classifier: Development Status :: 5 - Production/Stable Description-Content-Type: text/markdown +License-File: LICENSE + +# LFSR -Linear Feedback Shift Register + + +## Links: **[Github Page](http://nikeshbajaj.github.io/Linear_Feedback_Shift_Register/)** | **[Documentation](https://lfsr.readthedocs.io/)** | **[Github](https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register)** | **[PyPi - project](https://pypi.org/project/pylfsr/)** | _ **Installation:** [pip install pylfsr](https://pypi.org/project/pylfsr/) +----- + + +![CircleCI](https://img.shields.io/circleci/build/github/Nikeshbajaj/Linear_Feedback_Shift_Register) +[![Documentation Status](https://readthedocs.org/projects/lfsr/badge/?version=latest)](https://lfsr.readthedocs.io/en/latest/?badge=latest) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![PyPI version fury.io](https://badge.fury.io/py/pylfsr.svg)](https://pypi.org/project/pylfsr/) +[![PyPI pyversions](https://img.shields.io/pypi/pyversions/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) +[![GitHub release](https://img.shields.io/github/release/nikeshbajaj/Linear_Feedback_Shift_Register.svg)](https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/releases) +[![PyPI format](https://img.shields.io/pypi/format/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) +[![PyPI implementation](https://img.shields.io/pypi/implementation/pylfsr.svg)](https://pypi.python.org/pypi/pylfsr/) +[![HitCount](http://hits.dwyl.io/nikeshbajaj/pylfsr.svg)](http://hits.dwyl.io/nikeshbajaj/Linear_Feedback_Shift_Register) +![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/Nikeshbajaj/Linear_Feedback_Shift_Register/1.0.1) +![GitHub issues](https://img.shields.io/github/issues-raw/Nikeshbajaj/Linear_Feedback_Shift_Register) +![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/Nikeshbajaj/Linear_Feedback_Shift_Register) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Nikeshbajaj/Linear_Feedback_Shift_Register.svg)](http://isitmaintained.com/project/Nikeshbajaj/Linear_Feedback_Shift_Register "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/Nikeshbajaj/Linear_Feedback_Shift_Register.svg)](http://isitmaintained.com/project/Nikeshbajaj/Linear_Feedback_Shift_Register "Percentage of issues still open") + +[![PyPI download month](https://img.shields.io/pypi/dm/pylfsr.svg)](https://pypi.org/project/pylfsr/) +[![PyPI download week](https://img.shields.io/pypi/dw/pylfsr.svg)](https://pypi.org/project/pylfsr/) +[![Hits-of-Code](https://hitsofcode.com/github/Nikeshbajaj/Linear_Feedback_Shift_Register)](https://hitsofcode.com/github/Nikeshbajaj/Linear_Feedback_Shift_Register/view) + +[![Generic badge](https://img.shields.io/badge/pip%20install-pylfsr-blue.svg)](https://pypi.org/project/pylfsr/) +[![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](mailto:n.bajaj@qmul.ac.uk) + +![PyPI - Downloads](https://img.shields.io/pypi/dm/spkit?style=social) +![CircleCI](https://img.shields.io/circleci/build/github/Nikeshbajaj/Linear_Feedback_Shift_Register?style=social) + +

+ + +

+ + + +[![Generic badge](https://img.shields.io/badge/pip%20install-pylfsr-blue.svg)](https://pypi.org/project/pylfsr/) + +

+ + +

+ + + +----- +## Table of contents +- [**New Updates**](#new-updates) +- [**Installation**](#installation) +- [**Examples**](#examples) + - [**5-bit LFSR**](#example-1-5-bit-lfsr-with-feedback-polynomial-x5--x2--1) + - [**Vizualize each state**](#example-3--to-visualize-the-process-with-3-bit-lfsr-with-default-counter_start_zero--true) + - [**Plot your LFSR**](#visulizeplot-your-lfsr) + - [**Test properties of LFSR**](#example-6--testing-the-properties) +- [**A5/1 GSM Stream Cipher**](#a51-gsm-stream-cipher-generator) +- [**Geffe Genegerator**](#geffe-generator) +- [**Matlab Implementation**](#matlab) +- [**Cite As**](#cite-as) +----- + +## New Updates +## Plot Your LFSR with pylfsr +

+ + +

+ +## Updates: + **Version: 1.0.7:** + - **Added Galois Configuration** + - Improved Documentation + - **Computing LZ complexity** + + **Version: 1.0.6:** + - Fixed the bugs (1) missing initial bit (2) exception + - **Added test properties of LFSR** + - **(1) Balance Property** + - **(2) Runlength Property** + - **(3) Autocorrelation Property** + - **Ploting function to display LFSR** + - **A5/1 GSM Stream Ciper Generator** + - **Geffe Generator** + + +# Installation + +## Requirement : *numpy*, *matplotlib* + +### with pip + +``` +pip install pylfsr +``` + + +### Build from the source +Download the repository or clone it with git, after cd in directory build it from source with + +``` +python setup.py install +``` + +## Examples +### **Example 1**: 5-bit LFSR with feedback polynomial *x^5 + x^2 + 1* + +``` +# import LFSR +import numpy as np +from pylfsr import LFSR + +L = LFSR() + +# print the info +L.info() + +5 bit LFSR with feedback polynomial x^5 + x^2 + 1 +Expected Period (if polynomial is primitive) = 31 +Current : +State : [1 1 1 1 1] +Count : 0 +Output bit : -1 +feedback bit : -1 +``` + + +``` +L.next() +L.runKCycle(10) +L.runFullCycle() +L.info() +``` + +### Example 2**: 5-bit LFSR with custum state and feedback polynomial + +``` +state = [0,0,0,1,0] +fpoly = [5,4,3,2] +L = LFSR(fpoly=fpoly,initstate =state, verbose=True) +L.info() +tempseq = L.runKCycle(10) +L.set(fpoly=[5,3]) +``` + +### Example 3 ## To visualize the process with 3-bit LFSR, with default counter_start_zero = True +``` +state = [1,1,1] +fpoly = [3,2] +L = LFSR(initstate=state,fpoly=fpoly) +print('count \t state \t\toutbit \t seq') +print('-'*50) +for _ in range(15): + print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + L.next() +print('-'*50) +print('Output: ',L.seq) +``` +Output : + +``` +count state outbit seq +-------------------------------------------------- +0 [1 1 1] -1 [-1] +1 [0 1 1] 1 [1] +2 [0 0 1] 1 [1 1] +3 [1 0 0] 1 [1 1 1] +4 [0 1 0] 0 [1 1 1 0] +5 [1 0 1] 0 [1 1 1 0 0] +6 [1 1 0] 1 [1 1 1 0 0 1] +7 [1 1 1] 0 [1 1 1 0 0 1 0] +8 [0 1 1] 1 [1 1 1 0 0 1 0 1] +9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] +10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] +11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] +12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] +13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] +14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] +-------------------------------------------------- +Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] +``` + +### Example 4 ## To visualize the process with 3-bit LFSR, with default counter_start_zero = False +``` +state = [1,1,1] +fpoly = [3,2] +L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) +print('count \t state \t\toutbit \t seq') +print('-'*50) +for _ in range(15): + print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + L.next() +print('-'*50) +print('Output: ',L.seq) +``` + +Output +``` +count state outbit seq +-------------------------------------------------- +1 [1 1 1] 1 [1] +2 [0 1 1] 1 [1 1] +3 [0 0 1] 1 [1 1 1] +4 [1 0 0] 0 [1 1 1 0] +5 [0 1 0] 0 [1 1 1 0 0] +6 [1 0 1] 1 [1 1 1 0 0 1] +7 [1 1 0] 0 [1 1 1 0 0 1 0] +8 [1 1 1] 1 [1 1 1 0 0 1 0 1] +9 [0 1 1] 1 [1 1 1 0 0 1 0 1 1] +10 [0 0 1] 1 [1 1 1 0 0 1 0 1 1 1] +11 [1 0 0] 0 [1 1 1 0 0 1 0 1 1 1 0] +12 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0] +13 [1 0 1] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] +14 [1 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] +-------------------------------------------------- +Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] +``` + +## Visualize & Plot LFSR +``` +L.Viz(show=False, show_labels=False,title='R1') + +``` + +

+ +

+ +### Dynamic plot - Animation in notebook +``` +%matplotlib notebook +L = LFSR(initstate=[1,0,1,0,1],fpoly=[5,4,3,2],counter_start_zero=False) +fig, ax = plt.subplots(figsize=(8,3)) +for _ in range(35): + ax.clear() + L.Viz(ax=ax, title='R1') + plt.ylim([-0.1,None]) + #plt.tight_layout() + L.next() + fig.canvas.draw() + plt.pause(0.1) + +``` +

+ +

+ + +## Example 5 ## 23 bit LFSR with custum state and feedback polynomial +``` +fpoly = [23,19] +L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) +L1.info() +``` +Output +``` +23 bit LFSR with feedback polynomial x^23 + x^19 + 1 +Expected Period (if polynomial is primitive) = 8388607 +Current : + State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] + Count : 0 + Output bit : -1 + feedback bit : -1 +``` +``` +seq = L1.runKCycle(100) +``` + +```seq +array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, +1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, +1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) +``` +## Example 6 ## testing the properties +``` +state = [1,1,1,1,0] +fpoly = [5,3] +L = LFSR(initstate=state,fpoly=fpoly) +result = L.test_properties(verbose=2) +``` +Output +``` +1. Periodicity +------------------ + - Expected period = 2^M-1 = 31 + - Pass?: True + +2. Balance Property +------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) + - Pass?: True + +3. Runlength Property +------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [8 4 2 1 1] + - Pass?: True + +4. Autocorrelation Property +------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: True + +================== +Passed all the tests +================== +``` +

+ +

+ +Testing individual property +``` +# get a full period sequence +p = L.getFullPeriod() +p +array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 1, 1, 0]) +``` + +``` +L.balance_property(p.copy()) +# (True, (16, 15)) + +L.runlength_property(p.copy()) +# (True, array([8, 4, 2, 1, 1])) + +L.autocorr_property(p.copy())[0] +#True +``` + +## Example 7 ## testing the properties for non-primitive polynomial +``` +state = [1,1,1,1,0] +fpoly = [5,1] +L = LFSR(initstate=state,fpoly=fpoly) +result = L.test_properties(verbose=1) +``` +Output +``` +1. Periodicity +------------------ + - Expected period = 2^M-1 = 31 + - Pass?: False + +2. Balance Property +------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) + - Pass?: False + +3. Runlength Property +------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [10 2 1 1 2] + - Pass?: False + +4. Autocorrelation Property +------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: False + +================== +Failed one or more tests, check if feedback polynomial is primitive polynomial +================== +``` +

+ +

+ + +### Example 8**: Get the feedback polynomial or list +Reference : http://www.partow.net/programming/polynomials/index.html + +``` +L = LFSR() +# list of 5-bit feedback polynomials +fpoly = L.get_fpolyList(m=5) + +# list of all feedback polynomials as a dictionary +fpolyDict = L.get_fpolyList() +``` + + +### Changing feedback polynomial in between +``` +L.changeFpoly(newfpoly =[23,14],reset=False) +seq1 = L.runKCycle(20) + +# Change after 20 clocks +L.changeFpoly(newfpoly =[23,9],reset=False) +seq2 = L.runKCycle(20) +``` + +# Generators +# A5/1 GSM Stream cipher generator +

+ +

+ +Ref: https://en.wikipedia.org/wiki/A5/1 + +``` +import numpy as np +import matplotlib.pyplot as plt +from pylfsr import A5_1 + +A5 = A5_1(key='random') +print('key: ',A5.key) +A5.R1.Viz(title='R1') +A5.R2.Viz(title='R2') +A5.R3.Viz(title='R3') + +print('key: ',A5.key) +print() +print('count \t cbit\t\tclk\t R1_R2_R3\toutbit \t seq') +print('-'*80) +for _ in range(15): + print(A5.count,A5.getCbits(),A5.clock_bit,A5.getLastbits(),A5.outbit,A5.getSeq(),sep='\t') + A5.next() +print('-'*80) +print('Output: ',A5.seq) + +A5.runKCycle(1000) +A5.getSeq() + +``` + + +## Enhanced A5/1 + +Reference Article: **Enhancement of A5/1**: https://doi.org/10.1109/ETNCC.2011.5958486 + +

+ +

+ + +``` +# Three LFSRs initialzed with 'ones' though they are intialized with encription key +R1 = LFSR(fpoly = [19,18,17,14]) +R2 = LFSR(fpoly = [23,22,21,8]) +R3 = LFSR(fpoly = [22,21]) + +# clocking bits +b1 = R1.state[8] +b2 = R3.state[10] +b3 = R3.state[10] + +``` + + +# Geffe Generator +

+ +

+ +Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. + Chaper 16 + +``` +import numpy as np +import matplotlib.pyplot as plt +from pylfsr import Geffe, LFSR + +kLFSR = [LFSR(initstate='random') for _ in range(8)] # List of 8 5-bit LFSRs with default feedback polynomial and random initial state +cLFSR = LFSR(initstate='random') # A 5-bit LFSR with for selecting one of 8 output at a time + +GG = Geffe(kLFSR_list=kLFSR, cLFSR=cLFSR) + +print('key: ',GG.getState()) +print() +for _ in range(50): + print(GG.count,GG.m_count,GG.outbit_k,GG.sel_k,GG.outbit,GG.getSeq(),sep='\t') + GG.next() + +GG.runKCycle(1000) +GG.getSeq() +``` + +_______________________________________________________________________________________________ + +# MATLAB +## For matlab files download it from here +Folder : https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/tree/master/matlabfiles + +**Description** +Genrate randon binary sequence using LFSR for any given feedback taps (polynomial), +This will also check Three fundamental Property of LFSR +1. Balance Property +2. Runlength Property +3. Autocorrelation Property + +**This MATLAB Code work for any length of LFSR with given taps (feedback polynomial) -Universal, There are three files LFSRv1.m an LFSRv2.m, LFSRv3.m** + +### LFSRv1 +This function will return all the states of LFSR and will check Three fundamental Property of LFSR +(1) Balance Property (2) Runlength Property (3) Autocorrelation Property + +#### EXAMPLE +``` +s=[1 1 0 0 1] +t=[5 2] +[seq c] =LFSRv1(s,t) +``` + +### LFSRv2 +This function will return only generated sequence will all the states of LFSR, no verification of properties are done +here. Use this function to avoid verification each time you execute the program. +#### EXAMPLE +``` +s=[1 1 0 0 1] +t=[5 2] +[seq c] =LFSRv2(s,t) +``` + +### LFSRv3 (faster) +*seq = LFSRv3(s,t,N)* +this function generates N bit sequence only. This is faster then other two functions, as this does not gives each state of LFSR + +#### EXAMPLE +``` +s=[1 1 0 0 1] +t=[5 2] +seq =LFSRv3(s,t,50) +``` + + + +## Tips +* If you want to use this function in middle of any program, use LFSRv2 or LFSRv1 with verification =0. +* If you want to make it fast for long length of LFSR,use LFSRv3.m + +______________________________________ + +# Cite As +``` +@software{nikesh_bajaj_2021_4726667, + author = {Nikesh Bajaj}, + title = {Nikeshbajaj/Linear\_Feedback\_Shift\_Register: 1.0.6}, + month = apr, + year = 2021, + publisher = {Zenodo}, + version = {1.0.6}, + doi = {10.5281/zenodo.4726667}, + url = {https://doi.org/10.5281/zenodo.4726667} +} +``` + + +# Contacts: + +If any doubt, confusion or feedback please contact me +* **Nikesh Bajaj** +* http://nikeshbajaj.in +* n.bajaj@imperial.ac.uk +* n.bajaj@qmul.ac.uk +* bajaj[dot]nikkey[AT]gmail[dot]? +### PhD Student: Queen Mary University of London & University of Genoa +______________________________________ diff --git a/pylfsr.egg-info/SOURCES.txt b/pylfsr.egg-info/SOURCES.txt index 65c3ac9..4892570 100644 --- a/pylfsr.egg-info/SOURCES.txt +++ b/pylfsr.egg-info/SOURCES.txt @@ -1,7 +1,7 @@ LICENSE -LICENSE.txt MANIFEST.in README.md +Version requirements.txt setup.py examples/example1.py @@ -10,6 +10,8 @@ examples/example_LFSR2.py pylfsr/__init__.py pylfsr/primitive_polynomials_GF2_dict.txt pylfsr/pylfsr.py +pylfsr/seq_generators.py +pylfsr/utils.py pylfsr.egg-info/PKG-INFO pylfsr.egg-info/SOURCES.txt pylfsr.egg-info/dependency_links.txt diff --git a/pylfsr.egg-info/requires.txt b/pylfsr.egg-info/requires.txt index 24ce15a..aa094d9 100644 --- a/pylfsr.egg-info/requires.txt +++ b/pylfsr.egg-info/requires.txt @@ -1 +1,2 @@ numpy +matplotlib diff --git a/pylfsr/__init__.py b/pylfsr/__init__.py index b034eea..ab15f8a 100644 --- a/pylfsr/__init__.py +++ b/pylfsr/__init__.py @@ -1,22 +1,34 @@ -name = "Linear Feedback Shift Register" - -# PEP0440 compatible formatted version, see: -# https://www.python.org/dev/peps/pep-0440/ -# -# Generic release markers: -# X.Y -# X.Y.Z # For bugfix releases -# -# Admissible pre-release markers: -# X.YaN # Alpha release -# X.YbN # Beta release -# X.YrcN # Release Candidate -# X.Y # Final release -# -# Dev branch marker is: 'X.Y.devN' where N is an integer. -# - -__version__ = '1.0.5' -__author__ = 'Nikesh Bajaj' - -from .pylfsr import LFSR +''' +Author @ Nikesh Bajaj +Version : 1.0.7 +Contact: n.bajaj@qmul.ac.uk + : n.bajaj@imperial.ac.uk + : http://nikeshbajaj.in +-----changelog------------------- +first created : Date: 22 Oct 2017 +Updated on : 29 Apr 2021 (version:1.0.6) + : fixed bugs (1) not counting first outbit correctly (2) Exception in info method + : added test properties (1) Balance (2) Runlength (3) Autocorrelation + : improved functionalities + : added Viz function + : added A5/1 and Geffe Generator +Updated on : 03 Jan 2023 (version:1.0.7) + : Added Galois Configuration for LFSR + : fixed bugs, improved documentation +''' + +from __future__ import absolute_import, division, print_function + +name = "Linear Feedback Shift Register" + +__version__ = '1.0.7' +__author__ = 'Nikesh Bajaj' + +import sys, os + +sys.path.append(os.path.dirname(__file__)) + +from .pylfsr import (LFSR, PlotLFSR, dispLFSR) +from .seq_generators import (A5_1, Geffe, Geffe3) +from .utils import (lempel_ziv_patterns, lempel_ziv_complexity, get_fpolyList, get_Ifpoly) +from .utils import (pretty_print, print_list, progbar, deprecated) diff --git a/pylfsr/pylfsr.py b/pylfsr/pylfsr.py index b8483cd..6edc2fc 100644 --- a/pylfsr/pylfsr.py +++ b/pylfsr/pylfsr.py @@ -1,1254 +1,1556 @@ -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.patches as patches -''' -Author @ Nikesh Bajaj -first created : Date: 22 Oct 2017 -Updated on : 29 Apr 2021 - : fixed bugs (1) not counting first outbit correctly (2) Exception in info method - : added test properties (1) Balance (2) Runlength (3) Autocorrelation - : improved functionalities - : added Viz function - : added A5/1 and Geffe Generator -Version : 1.0.6 -Contact: n.bajaj@qmul.ac.uk - : http://nikeshbajaj.in -''' - -class LFSR(): - ''' - Linear Feedback Shift Register - - class LFSR(fpoly=[5,2],initstate='ones',verbose=False) - - Parameters - ---------- - initstate : binary np.array (row vector) or str ='ones' or 'random', optional (default = 'ones')) - Initial state of LFSR. initstate can not be all zeros. - default ='ones' - Initial state is intialized with ones and length of register is equal to - degree of feedback polynomial - if state='rand' - Initial state is intialized with random binary sequence of length equal to - degree of feedback polynomial - - fpoly : List, optional (default=[5,2]) - Feedback polynomial, it has to be primitive polynomial of GF(2) field, for valid output of LFSR - to get the list of feedback polynomials check method 'get_fpolyList' - or check Refeferece: - Ref: List of some primitive polynomial over GF(2)can be found at - http://www.partow.net/programming/polynomials/index.html - http://www.ams.org/journals/mcom/1962-16-079/S0025-5718-1962-0148256-1/S0025-5718-1962-0148256-1.pdf - http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf - counter_start_zero: bool (default = True), whether to start counter with 0 or 1. If True, initial outbit is - set to -1, so is feedbackbit, until first .next() clock is excecuted. This initial output is not stacked in - seq. The output sequence should be same, in anycase, for example if you need run 10 cycles, using runKCycle(10) methed. - Verbose : boolean, optional (default=False) - if True, state of LFSR will be printed at every cycle(iteration) - - Attributes - ---------- - count : int - Count the cycle, starts with 0 if counter_start_zero True, else starts with 1 - seq : np.array shape =(count,) - Output sequence stored in seq since first cycle - if -1, no cycle has been excecuted, count=0 when counter_start_zero is True - else last bit of initial state - - outbit : binary bit - Current output bit, - Last bit of current state - if -1, no cycle has been excecuted, count =0, when counter_start_zero is True - - feedbackbit : binary bit - if -1, no cycle has been excecuted, count =0, when counter_start_zero is True - M : int - length of LFSR, M-bit LFSR - - expectedPeriod : int (also saved as T) - Expected period of sequence - if feedback polynomial is primitive and irreducible (as per reference) - period will be 2^M -1 - T : int (also saved as expectedPeriod) - Expected period of sequence - if feedback polynomial is primitive and irreducible (as per reference) - period will be 2^M -1 - feedpoly : str - feedback polynomial - - - Examples - -------- - >>> import numpy as np - >>> from pylfsr import LFSR - - ## Example 1 ## 5 bit LFSR with x^5 + x^2 + 1 - >>> L = LFSR() - >>> L.info() - 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [1 1 1 1 1] - Count : 0 - Output bit : -1 - feedback bit : -1 - - >>> L.next() - 1 - - >>> L.runKCycle(10) - array([1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) - - >>> L.runFullCycle() # doctest: +NORMALIZE_WHITESPACE - array([1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, - 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) - - >>> L.info() # doctest: +NORMALIZE_WHITESPACE - 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [0 0 1 0 0] - Count : 42 - Output bit : 1 - feedback bit : 0 - Output Sequence 111110011010010000101011101100011111001101 - - >>> tempseq = L.runKCycle(10000) # generate 10000 bits from current state - - ## Example 2 ## 5 bit LFSR with custum state and feedback polynomial - >>> state = np.array([0,0,0,1,0]) - >>> fpoly = [5,4,3,2] - >>> L1 = LFSR(fpoly=fpoly,initstate =state, verbose=True) - >>> L1.info() # doctest: +NORMALIZE_WHITESPACE - 5 bit LFSR with feedback polynomial x^5 + x^4 + x^3 + x^2 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [0 0 0 1 0] - Count : 0 - Output bit : -1 - feedback bit : -1 - >>> tempseq = L1.runKCycle(10) - S: [0 0 0 1 0] - S: [1 0 0 0 1] - S: [1 1 0 0 0] - S: [1 1 1 0 0] - S: [0 1 1 1 0] - S: [1 0 1 1 1] - S: [1 1 0 1 1] - S: [1 1 1 0 1] - S: [1 1 1 1 0] - S: [1 1 1 1 1] - >>> tempseq - array([0, 1, 0, 0, 0, 1, 1, 1, 0, 1]) - - >>>L1.set(fpoly=[5,3]) - - ## Example 3 ## TO visualize the process with 3-bit LFSR, with default counter_start_zero = True - >>> state = [1,1,1] - >>> fpoly = [3,2] - >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=True) - >>> print('count \t state \t\toutbit \t seq') - >>> print('-'*50) - >>> for _ in range(15): - >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') - >>> L.next() - >>> print('-'*50) - >>> print('Output: ',L.seq) - count state outbit seq - -------------------------------------------------- - 0 [1 1 1] -1 [-1] - 1 [0 1 1] 1 [1] - 2 [0 0 1] 1 [1 1] - 3 [1 0 0] 1 [1 1 1] - 4 [0 1 0] 0 [1 1 1 0] - 5 [1 0 1] 0 [1 1 1 0 0] - 6 [1 1 0] 1 [1 1 1 0 0 1] - 7 [1 1 1] 0 [1 1 1 0 0 1 0] - 8 [0 1 1] 1 [1 1 1 0 0 1 0 1] - 9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] - 10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] - 11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] - 12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] - 13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] - 14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] - -------------------------------------------------- - Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] - - ## Example 4.1 ## To visualize the process with 3-bit LFSR, with counter_start_zero = False - >>> state = [1,1,1] - >>> fpoly = [3,2] - >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) - >>> print('count \t state \t\toutbit \t seq') - >>> print('-'*50) - >>> for _ in range(15): - >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') - >>> L.next() - >>> print('-'*50) - >>> print('Output: ',L.seq) - count state outbit seq - -------------------------------------------------- - 1 [1 1 1] 1 [1] - 2 [0 1 1] 1 [1 1] - 3 [0 0 1] 1 [1 1 1] - 4 [1 0 0] 0 [1 1 1 0] - 5 [0 1 0] 0 [1 1 1 0 0] - 6 [1 0 1] 1 [1 1 1 0 0 1] - 7 [1 1 0] 0 [1 1 1 0 0 1 0] - 8 [1 1 1] 1 [1 1 1 0 0 1 0 1] - 9 [0 1 1] 1 [1 1 1 0 0 1 0 1 1] - 10 [0 0 1] 1 [1 1 1 0 0 1 0 1 1 1] - 11 [1 0 0] 0 [1 1 1 0 0 1 0 1 1 1 0] - 12 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0] - 13 [1 0 1] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] - 14 [1 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] - -------------------------------------------------- - Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] - - ## Example 4.2 ## To visualize LFSR - L.Viz(show=False, show_labels=False,title='R1') - - ## Example 5 ## 23 bit LFSR with custum state and feedback polynomial - - >>> fpoly = [23,19] - >>> L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) - >>> L1.info() - 23 bit LFSR with feedback polynomial x^23 + x^19 + 1 - Expected Period (if polynomial is primitive) = 8388607 - Current : - State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] - Count : 0 - Output bit : -1 - feedback bit : -1 - - >>> seq = L1.runKCycle(100) - >>> seq - array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, - 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) - - >>> L.changeFpoly(newfpoly =[23,21]) - >>> seq1 = L.runKCycle(20) - - ## Example 6 ## testing the properties - >>> state = [1,1,1,1,0] - >>> fpoly = [5,3] - >>> L = LFSR(initstate=state,fpoly=fpoly) - >>> L.info() - 5 bit LFSR with feedback polynomial x^5 + x^3 + 1 - Expected Period (if polynomial is primitive) = 31 - Current : - State : [1 1 1 1 0] - Count : 0 - Output bit : -1 - feedback bit : -1 - - >>>result = L.test_properties(verbose=1) - 1. Periodicity - ------------------ - - Expected period = 2^M-1 = 31 - - Pass?: True - - 2. Balance Property - ------------------- - - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) - - Pass?: True - - 3. Runlength Property - ------------------- - - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] - - Runs: [8 4 2 1 1] - - Pass?: True - - 4. Autocorrelation Property - ------------------- - - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else - - Pass?: True - - ================== - Passed all the tests - ================== - - - >>> p = L.getFullPeriod() - >>> p - array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, - 0, 1, 0, 0, 1, 0, 1, 1, 0]) - - >>> L.balance_property(p.copy()) - (True, (16, 15)) - - >>> L.runlength_property(p.copy()) - (True, array([8, 4, 2, 1, 1])) - - >>> L.autocorr_property(p.copy())[0] - True - - ## Example 7 ## testing the properties for non-primitive polynomial - >>> state = [1,1,1,1,0] - >>> fpoly = [5,1] - >>> L = LFSR(initstate=state,fpoly=fpoly) - >>> result = L.test_properties(verbose=1) - 1. Periodicity - ------------------ - - Expected period = 2^M-1 = 31 - - Pass?: False - - 2. Balance Property - ------------------- - - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) - - Pass?: False - - 3. Runlength Property - ------------------- - - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] - - Runs: [10 2 1 1 2] - - Pass?: False - - 4. Autocorrelation Property - ------------------- - - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else - - Pass?: False - - ================== - Failed one or more tests, check if feedback polynomial is primitive polynomial - ================== - ''' - - def __init__(self, fpoly=[5, 2], initstate='ones', verbose=False,counter_start_zero=True): - if isinstance(initstate, str): - if initstate == 'ones': - initstate = np.ones(np.max(fpoly)) - elif initstate == 'random': - initstate = np.random.randint(0, 2, np.max(fpoly)) - else: - raise Exception('Unknown intial state') - if isinstance(initstate, list): - initstate = np.array(initstate) - - self.initstate = initstate - self.fpoly = fpoly - self.state = initstate.astype(int) - #self.skip_first = skip_first - self.counter_start_zero = counter_start_zero - self.count = 0 if counter_start_zero else 1 - self.seq = np.array([-1]) if counter_start_zero else np.array([self.state[-1]]) - self.outbit = -1 if counter_start_zero else self.state[-1] - self.feedbackbit = -1 if counter_start_zero else self.state[-1] - self.verbose = verbose - self.M = self.initstate.shape[0] - self.expectedPeriod = 2**self.M - 1 - self.T = 2**self.M - 1 - self.fpoly.sort(reverse=True) - feed = ' ' - for i in range(len(self.fpoly)): - feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' - feed = feed + '1' - self.feedpoly = feed - - self.check() - - def info(self): - ''' - Display the information about LFSR with current state of variables - ''' - print('%d bit LFSR with feedback polynomial %s' % (self.M, self.feedpoly)) - print('Expected Period (if polynomial is primitive) = ', self.expectedPeriod) - print('Current :') - print(' State : ', self.state) - print(' Count : ', self.count) - print(' Output bit : ', self.outbit) - print(' feedback bit : ', self.feedbackbit) - if self.count > 0 and self.count < 1000: - print(' Output Sequence %s' % (''.join([str(int(x)) for x in self.seq]))) - - def check(self): - ''' - Check if - - degree of feedback polynomial <= length of LFSR >=1 - - given intistate of LFSR is correct - ''' - if np.max(self.fpoly) > self.initstate.shape[0] or np.min(self.fpoly) < 1 or len(self.fpoly) < 2: - raise ValueError('Wrong feedback polynomial') - if len(self.initstate.shape) > 1 and (self.initstate.shape[0] != 1 or self.initstate.shape[1] != 1): - raise ValueError('Size of intial state vector should be one diamensional') - else: - self.initstate = np.squeeze(self.initstate) - assert np.sum(self.initstate>1) + np.sum(self.initstate<0)==0 # test if initial state is binary, 1s and 0s - - def set(self, fpoly, state='ones'): - ''' - Set feedback polynomial and state - - Parameters - ---------- - fpoly : list - feedback polynomial like [5,4,3,2] - - state : np.array, like np.array([1,0,0,1,1]) - default ='ones' - Initial state is intialized with ones and length of register is equal to - degree of feedback polynomial - if state='rand' - Initial state is intialized with random binary sequence of length equal to - degree of feedback polynomial - ''' - self.__init__(fpoly=fpoly, initstate=state) - - def reset(self): - ''' - Reseting LFSR to its initial state and count - ''' - self.__init__(initstate=self.initstate, fpoly=self.fpoly,counter_start_zero=self.counter_start_zero ) - - def changeFpoly(self, newfpoly, reset=False): - ''' - Changing Feedback polynomial : Useful to change feedback polynomial in between as in A5/1 stream cipher - - Parameters - ---------- - newfpoly : list like, [5,4,2,1] - changing the feedback polynomial - - reset : boolean default=False - if True, reset all the Parameters: count and seq etc .... - if False, leave the LFSR as it is only change the feedback polynomial - for further use, as used in - 'Enhancement of A5/1: Using variable feedback polynomials of LFSR' - https://doi.org/10.1109/ETNCC.2011.5958486 - ''' - newfpoly.sort(reverse=True) - self.fpoly = newfpoly - feed = ' ' - for i in range(len(self.fpoly)): - feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' - feed = feed + '1' - self.feedpoly = feed - - self.check() - if reset: - self.reset() - - def next(self): - ''' - Run one cycle on LFSR with given feedback polynomial and - update the count, state, feedback bit, output bit and seq - - Returns - ------- - output bit : binary - ''' - if self.verbose: - print('S: ', self.state) - if self.counter_start_zero: - self.outbit = self.state[-1] - if self.count ==0: - self.seq = np.array([self.state[-1]]) - else: - self.seq = np.append(self.seq, self.state[-1]) - - b = np.logical_xor(self.state[self.fpoly[0] - 1], self.state[self.fpoly[1] - 1]) - if len(self.fpoly) > 2: - for i in range(2, len(self.fpoly)): - b = np.logical_xor(self.state[self.fpoly[i] - 1], b) - - #self.outbit = self.state[-1] - self.state = np.roll(self.state, 1) - self.feedbackbit = b * 1 - self.state[0] = self.feedbackbit - - if not(self.counter_start_zero): - self.outbit = self.state[-1] - if self.count ==0: - self.seq = np.array([self.state[-1]]) - else: - self.seq = np.append(self.seq, self.state[-1]) - - self.count += 1 - - return self.outbit - - def runFullCycle(self): - ''' - Run a full cycle (T = 2^M-1) on LFSR from current state - - Returns - ------- - seq : binary output sequence since start: shape = (count,) - ''' - temp = [self.next() for i in range(self.expectedPeriod)] - return self.seq - - def getFullPeriod(self): - ''' - Get a seq of a full period from LSFR, by executing next() method T times. - The current state of LFSR is used to generate T bits. - - Returns - ------- - seq (T bits), binary output sequence of last T bits - ''' - seq = np.array([self.next() for i in range(self.expectedPeriod)]) - return seq - - def runKCycle(self, k): - ''' - Run k cycles and update all the Parameters - - Parameters - ---------- - k : int - - Returns - ------- - tempseq : shape =(k,), output binary sequence of k cycles - ''' - tempseq = [self.next() for i in range(k)] - return np.array(tempseq) - - def _loadFpolyList(self): - import os - fname = 'primitive_polynomials_GF2_dict.txt' - fname = os.path.join(os.path.dirname(__file__), fname) - try: - f = open(fname, "rb") - lines = f.readlines() - f.close() - self.fpolyList = eval(lines[0].decode()) - except: - raise Exception("File named:'{}' Not Found!!! \n try again, after downloading file from github save it in lfsr directory".format(fname)) - - def get_fpolyList(self,m=None): - ''' - Get the list of primitive polynomials as feedback polynomials for m-bit LFSR. - Only list of primary primitive polynomials are retuned, not full list (half list), since for each primary primitive polynomial - an image polymial can be computed using 'get_Ifpoly' method - - Parameters - ---------- - m: 1 1 and m < 32: - return self.fpolyList[m] - else: - print('Wrong input m. m should be int 1 < m < 32 or None') - - def get_Ifpoly(self,fpoly): - ''' - Get image of feebback polynomial - Get the image of primitive polynomial - Parameters - ---------- - fpoly: polynomial as list e.g. [5,2] for x^5 + x^2 + 1 - : should be a valid primitive polynomial - - Returns - ------- - ifpoly: polynomial as list e.g. [5,3] for x^5 + x^3 + 1 - - ''' - if isinstance(fpoly, list) or (isinstance(fpoly, numpy.ndarray) and len(fpoly.shape)==1): - fpoly = list(fpoly) - fpoly.sort(reverse=True) - ifpoly = [fpoly[0]] +[fpoly[0]-ff for ff in fpoly[1:]] - ifpoly.sort(reverse=True) - return ifpoly - else: - print('Not a valid form of feedback polynomial') - - def test_properties(self,verbose=1): - p1 = self.getFullPeriod() - p2 = self.getFullPeriod() - r1 = np.mean(p1==p2)==1 - - r2, (N1s,N0s) = self.balance_property(p1.copy()) - - r3,runs = self.runlength_property(p1.copy(),verbose=0) - - r4,(shift,rxx) = self.autocorr_property(p1.copy(),plot=False) - - result = bool(np.prod([r1,r2,r3,r4])) - - if verbose: - print('1. Periodicity') - print('------------------') - print(' - Expected period = 2^M-1 =',self.expectedPeriod) - print(' - Pass?: ',r1) - print('') - print('2. Balance Property') - print('-------------------') - print(' - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = ',(N1s, N0s)) - print(' - Pass?: ',r2) - print('') - print('3. Runlength Property') - print('-------------------') - print(' - Number of Runs in a period should be of specific order, e.g. [4,2,1,1]') - print(' - Runs: ',runs) - print(' - Pass?: ',r3) - print('') - print('4. Autocorrelation Property') - print('-------------------') - print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else') - if verbose>1: - print(' - Rxx(k): ',rxx) - try: - import matplotlib.pyplot as plt - except: - raise('Error loading matplotlib, either install it or set verbose<2') - plt.plot(shift,rxx) - plt.xlabel('shift (k)') - plt.ylabel(r'$R_{xx}(k)$') - plt.axhline(y=0,color='k',ls=':',lw=0.5) - plt.xlim(shift[0],shift[-1]) - plt.title('Autocorrelation') - plt.grid(alpha=0.4) - plt.show() - print(' - Pass?: ',r4) - print('\n\n') - print('==================') - if result: - print('Passed all the tests') - else: - print('Failed one or more tests, check if feedback polynomial is primitive polynomial') - print('==================') - return result - - def test_p(self,p,verbose=1): - ''' - Test all the three properties for seq p : - (1) Balance Property - (2) Runlegth Property - (3) Autocorrelation Property - - - Parameters - ---------- - p : array-like, a sequence of a period from LFSR - verbose = 0 : no printing details - = 1 : print details - = 2 : print and plot more details - Returns - ------- - result: bool, True if all three are satisfied else False - ''' - r1,(N1s,N0s) = self.balance_property(p.copy()) - r2,runs = self.runlength_property(p.copy(),verbose=0) - r3,(shift,rxx) = self.autocorr_property(p.copy(),plot=False) - result = bool(np.prod([r1,r2,r3])) - if verbose: - print('1. Balance Property') - print('-------------------') - print(' - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = ',(N1s, N0s)) - print(' - Pass?: ',r1) - print('') - print('2. Runlength Property') - print('-------------------') - print(' - Number of Runs in a period should be of specific order, e.g. [4,2,1,1]') - print(' - Runs: ',runs) - print(' - Pass?: ',r2) - print('') - print('3. Autocorrelation Property') - print('-------------------') - print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else') - if verbose>1: - print(' - Rxx(k): ',rxx) - try: - import matplotlib.pyplot as plt - except: - raise('Error loading matplotlib, either install it or set verbose<2') - plt.plot(shift,rxx) - plt.xlabel('shift (k)') - plt.ylabel(r'$R_{xx}(k)$') - plt.axhline(y=0,color='k',ls=':',lw=0.5) - plt.xlim(shift[0],shift[-1]) - plt.title('Autocorrelation') - plt.grid(alpha=0.4) - plt.show() - print(' - Pass?: ',r3) - print('\n\n') - print('==================') - if result: - print('Passed all three tests') - else: - print('Failed one or more tests') - print('==================') - - return result - - def balance_property(self,p): - ''' - Balance Property: In a period of LFSR with a valid feedback polynomial, - the number of 1s should be equal to number of 0s +1 - '' N1s == N0s + 1 '' - Test balance property for a given full period of seq, p. - - Parameters - ---------- - p: array-like, a sequence of a period from LFSR - - - Returns - ------- - result: bool, True if seq p satisfies Balance Property else False - (N1s, N0s): tuple, number of 1s and number of 0s - ''' - N1s = np.sum(p==1) - N0s = np.sum(p==0) - result = N1s == N0s+1 - return result, (N1s,N0s) - - def runlength_property(self,p,verbose=0): - ''' - Run Length Property: In a period of LSFR with valid feedback polynomial, - the number of runs of different length are in specific order. - '' - number of (M-k) bit runs = ⌈ 2^(k-1) ⌉ , for k = 0 to M-1 - '' - where ⌈ ⌉ is a ceiling function - That is, for M bit LFSR, - - number of M bit runs : 1 - - number of (M-1) bit runs : 1 - - number of (M-2) bit runs : 2 - - number of (M-3) bit runs : 4 - ... - so on - - Parameters - ---------- - p: array-like, a sequence of a period from LFSR - - Returns - ------- - result: bool, True if seq p satisfies Run Length Property else False - runs: list, list of runs - ''' - T = len(p) - if verbose>1: print(p) - while p[0]==p[-1]: p = np.roll(p,1) - - if verbose>1: print(p) - if p[-1]==0: - p = np.append(p,1) - else: - p = np.append(p,0) - if verbose>1: print(p) - - i=0 - runs = np.zeros(T).astype(int) - for k in range(T): - if p[k]==p[k+1]: - i=i+1 - else: - runs[i]=runs[i]+1 - i=0 - if verbose>1: print(runs) - runs = runs[:max(np.where(runs)[0])+1] - if verbose: print('Runs : ',runs) - - l = len(runs) - pp=0 - for k in range(len(runs)-2): - if runs[k]==2*runs[k+1]: - pp=pp+1 - if runs[-2]==runs[-1]: pp=pp+1 - - result = False - - if pp==len(runs)-1: result = True - if verbose>1: - if result: print('Pass') - else: print('Fail') - return result, runs - - def autocorr_property(self,p,plot=False): - ''' - Autocorrelation Property: For sequence of period T of LSFR with valid feedback polynomial, - the autocorrelation is a noise like, that is, 1 with zero (or T) lag (shift), -1/T (almost zero) else. - - unlike usual, for binary, the correlation value between two sequence of same length bx, by is computed as follow; - match = sum(bx == by) (number of mataches) - mismatch = sum(bx!= by) (number of mismatches) - '' - rxy = (match - mismatch)/ length(bx) - '' - - Parameters - ---------- - p: array-like, a sequence of a period from LFSR - - plot: bool (default False), if True, it will plot the autocorrelation function, - which will require matplotlib library. Turn it of if matplotlib is not installed - - Returns - ------- - result: bool, True if seq p satisfies Autocorrelation Property else False - (shift, rxx): tuple of sequence of shift corresponding autocorrelation values - ''' - T = len(p) - px = p.copy() - rxx = np.zeros(2*T+1) - for k in range(2*T+1): - py = np.roll(p.copy(),k) - r = px==py - rxx[k] = (np.sum(r==1) - np.sum(r==0))/T - - result = False - if np.prod(np.isclose(rxx[1:T],-1/T)): - result = True - - shift = np.arange(-T,T+1) - if plot: - try: - import matplotlib.pyplot as plt - except: - raise('Error loading matplotlib, either install it or set plot=False') - plt.plot(shift,rxx) - plt.xlabel('shift (k)') - plt.ylabel(r'$R_{xx}(k)$') - plt.axhline(y=0,color='k',ls=':',lw=0.5) - plt.xlim(shift[0],shift[-1]) - plt.title('Autocorrelation') - plt.grid(alpha=0.4) - plt.show() - return result, (shift,rxx) - - def getSeq(self): - return ''.join(self.seq.copy().astype(str)) - def getState(self): - return ''.join(self.state.copy().astype(str)) - def arr2str(self,arr): - return ''.join(arr.copy().astype(str)) - def Viz(self,ax=None,show=True,fs=25,show_labels=False,title='',title_loc='left',box_color='lightblue',alpha=0.5): - ''' - ax: axis to plot, if None, new axis will be created, (default None) - show: if True, plt.show() will be excecuted, (default True) - fs: fontsize (default 25) - show_label: if true, will display names - title: str, title of figure, default '', - title_loc, alignment of title, 'left', 'right', 'center', (default 'left') - ''' - state = self.state - fpoly = self.fpoly - seq = self.getSeq() - outbit = self.outbit - feedbit = self.feedbackbit - PlotLFSR(state,fpoly,seq=seq,ob=outbit,fb=feedbit,fs=fs,ax=ax,show_labels=show_labels,title=title,title_loc=title_loc,box_color=box_color,alpha=alpha) - if show: plt.show() - - - -def drawR(ax,x=0,y=0,s=1,alpha=0.5,color='lightblue'): - rect = patches.Rectangle((x-s/2, y-s/2), s, s, linewidth=1, edgecolor='k', facecolor=color,alpha=alpha) - ax.add_patch(rect) -def PlotLFSR(state,fpoly,seq='',ob=None,fb=None,fs=25,ax=None,show_labels=False,title='',title_loc='left',box_color='lightblue',alpha=0.5): - ''' - ----- Plot LFSR ---- - state: current state of LFSR - fpoly: feedback polynomial of LFSR - seq: str, output sequence - ob: output bit - fb: feedback bit - ax: axis to plot, if None, new axis will be created, (default None) - - show: if True, plt.show() will be excecuted, (default True) - fs: fontsize (default 25) - show_label: if true, will display names - title: str, title of figure, default '', - title_loc, alignment of title, 'left', 'right', 'center', (default 'left') - box_color: color of register box, default='lightblue' - ''' - M = len(state) - ym = 3.5 - if ax is None: - fig, ax = plt.subplots(figsize=(M+5,ym)) - - s=1 - xs = 3 - ys = ym-1 - last_x= xs - - for k in range(M): - x,y = xs+k,ys - ax.text(x,y,str(state[k]),ha='center',va = 'center',fontsize=fs) - - if k==0: - x1, y1 = [x-1.5*s, x-s/2], [y, y] - ax.plot(x1, y1,marker = '>',color='k',markevery=(1,1),ms=10) - - if k+1 in fpoly: - x1, y1 = [x, x], [y-s/2, y-1.5*s] - ax.plot(x1, y1,marker = '.',color='k') - ax.plot(x,y-1.5*s,marker = '+',color='b',ms=15,mew=3) - ax.plot(x,y-1.5*s,marker = 'o',color='b',ms=15,mfc='none',mew=2) - if last_x 1)*1 - return self.outbit - - def getLastbits(self): - return [self.R1.state[-1],self.R2.state[-1],self.R3.state[-1]] - def getCbits(self): - return [self.R1.state[8],self.R2.state[10],self.R3.state[10]] - def getSeq(self): - return ''.join(self.seq.copy().astype(str)) - def getState(self): - return ''.join(self.state.copy().astype(str)) - def arr2str(self,arr): - return ''.join(arr.copy().astype(str)) - def runKCycle(self, k): - ''' - Run k cycles and update all the Parameters - - Parameters - ---------- - k : int - - Returns - ------- - tempseq : shape =(k,), output binary sequence of k cycles - ''' - tempseq = [self.next() for i in range(k)] - return np.array(tempseq) - -class Geffe(): - ''' - Geffe Generator - --------------- - Combining K LFSR in non-linear manner - linear complexity - - Parameters - ---------- - K+1 LFSRs - - kLFSR_list: list of K LFSR, output of one of these is choosen at any time, depending on cLFSR - cLFSR: clocking LFSR - - K should be power of 2. 2,4,8,... 128 - - Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. - Chaper 16 - - - Example - -------- - - import numpy as np - import matplotlib.pyplot as plt - from pylfsr import Geffe, LFSR - - kLFSR = [LFSR(initstate='random') for _ in range(8)] - cLFSR = LFSR(initstate='random') - - GG = Geffe(kLFSR_list=kLFSR, cLFSR=cLFSR) - - print('key: ',GG.getState()) - print() - for _ in range(50): - print(GG.count,GG.m_count,GG.outbit_k,GG.sel_k,GG.outbit,GG.getSeq(),sep='\t') - GG.next() - - GG.runKCycle(1000) - GG.getSeq() - ''' - def __init__(self,kLFSR_list,cLFSR): - - self.K = len(kLFSR_list) - assert isinstance(cLFSR,LFSR) - assert [isinstance(Rk,LFSR) for Rk in kLFSR_list] - assert self.K>1 - assert (self.K & (self.K-1) == 0) and self.K != 0 - #K (list of LFSR) should be power of 2 - - self.m = np.log2(self.K).astype(int) - - self.kLFSR_list = kLFSR_list - self.cLFSR = cLFSR - - self.count=0 - self.m_count =0 - self.seq =np.array([]) - self.outbit = -1 - self.sel_k = -1 - self.outbit_k = [Rk.state[-1] for Rk in self.kLFSR_list] - self.state = np.hstack([R.state for R in self.kLFSR_list+[self.cLFSR]]) - self.state_k = np.hstack([R.state for R in self.kLFSR_list]) - self.state_c = self.cLFSR.state - - - def getSel(self): - sel = self.cLFSR.runKCycle(self.m) - self.m_count+=self.m - sel = ''.join(sel.astype(str)) - return int(sel, 2) - def next(self): - if self.count: - _ = [Rk.next() for Rk in self.kLFSR_list] - - self.outbit_k = [Rk.state[-1] for Rk in self.kLFSR_list] - self.sel_k = self.getSel() - self.outbit = self.outbit_k[self.sel_k] - - self.seq = np.append(self.seq,self.outbit).astype(int) - - self.state = np.hstack([R.state for R in self.kLFSR_list+[self.cLFSR]]) - self.state_k = np.hstack([R.state for R in self.kLFSR_list]) - self.state_c = self.cLFSR.state - - self.count+=1 - return self.outbit - - def getSeq(self): - return ''.join(self.seq.copy().astype(str)) - def getState(self): - return ''.join(self.state.copy().astype(str)) - def arr2str(self,arr): - return ''.join(arr.copy().astype(str)) - - def runKCycle(self, k): - ''' - Run k cycles and update all the Parameters - - Parameters - ---------- - k : int - - Returns - ------- - tempseq : shape =(k,), output binary sequence of k cycles - ''' - tempseq = [self.next() for i in range(k)] - return np.array(tempseq) - -class Geffe3(): - ''' - Geffe Generator - --------------- - Combining three LFSR in non-linear manner - linear complexity: If the LFSRs have lengths n1, n2, and n3, respectively, then the linear - complexity of the generator is = (n1 + 1)n2 + n1n3 - - output bit at any time is - - b = (r1 ^ r2) • ((¬ r1) ^ r3) - - where r1,r2,r3 are the outbit of three LFSRs respectively - - Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. - Chaper 16 - - ''' - def __init__(self,R1,R2,R3): - - assert isinstance(R1,LFSR) - assert isinstance(R2,LFSR) - assert isinstance(R3,LFSR) - - self.R1 = R1 - self.R2 = R2 - self.R3 = R3 - self.count=0 - self.seq =[] - self.state = np.r_[self.R1.state, self.R2.state,self.R3.state] - self.next() - - def next(self): - if self.count: - self.R1.next() - self.R2.next() - self.R3.next() - self.r1 = self.R1.state[-1] - self.r2 = self.R2.state[-1] - self.r3 = self.R3.state[-1] - - b1 = np.logical_and(self.r1,self.r2) - b2 = np.logical_and(not(self.r1),self.r2) - self.outbit = np.logical_xor(b1,b2)*1 - - self.seq = np.append(self.seq,self.outbit).astype(int) - - self.state = np.r_[self.R1.state, self.R2.state,self.R3.state] - self.count+=1 - return self.outbit - def getSeq(self): - return ''.join(self.seq.copy().astype(str)) - def getState(self): - return ''.join(self.state.copy().astype(str)) - def arr2str(self,arr): - return ''.join(arr.copy().astype(str)) - - def runKCycle(self, k): - ''' - Run k cycles and update all the Parameters - - Parameters - ---------- - k : int - - Returns - ------- - tempseq : shape =(k,), output binary sequence of k cycles - ''' - tempseq = [self.next() for i in range(k)] - return np.array(tempseq) - - -if __name__ == '__main__': - import doctest - doctest.testmod() +''' +Author @ Nikesh Bajaj +Version : 1.0.7 +Contact: n.bajaj@qmul.ac.uk + : n.bajaj@imperial.ac.uk + : http://nikeshbajaj.in +-----changelog------------------- +first created : Date: 22 Oct 2017 +Updated on : 29 Apr 2021 (version:1.0.6) + : fixed bugs (1) not counting first outbit correctly (2) Exception in info method + : added test properties (1) Balance (2) Runlength (3) Autocorrelation + : improved functionalities + : added Viz function + : added A5/1 and Geffe Generator +Updated on : 03 Jan 2023 (version:1.0.7) + : Added Galois Configuration for LFSR + : fixed bugs, improved documentation +''' + +from __future__ import absolute_import, division, print_function +name = "LFSR " +import sys + +if sys.version_info[:2] < (3, 3): + old_print = print + def print(*args, **kwargs): + flush = kwargs.pop('flush', False) + old_print(*args, **kwargs) + if flush: + file = kwargs.get('file', sys.stdout) + # Why might file=None? IDK, but it works for print(i, file=None) + file.flush() if file is not None else sys.stdout.flush() + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +from .utils import deprecated, progbar + +class LFSR(): + ''' + Linear Feedback Shift Register + + class LFSR(fpoly=[5,2],initstate='ones',verbose=False) + + Parameters + ---------- + + fpoly : List, optional (default=[5,2]) + Feedback polynomial, it has to be primitive polynomial of GF(2) field, for valid output of LFSR + + Example: for 5-bit LFSR, fpoly=[5,2], [5,3], [5,4,3,2], etc + : for M-bit LFSR fpoly = [M,...] + + to get the list of feedback polynomials check method 'get_fpolyList' + or check Refeferece: + Ref: List of some primitive polynomial over GF(2)can be found at + http://www.partow.net/programming/polynomials/index.html + http://www.ams.org/journals/mcom/1962-16-079/S0025-5718-1962-0148256-1/S0025-5718-1962-0148256-1.pdf + http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf + + + initstate : binary np.array (row vector) or str ='ones' or 'random', optional (default = 'ones')) + Initial state vector of LFSR. initstate can not be all zeros. + + default ='ones' + Initial state is intialized with ones and length of register is equal to + degree of feedback polynomial + if state='rand' or 'random' + Initial state is intialized with random binary sequence of length equal to + degree of feedback polynomial + if passed as list or numpy array + initstate = [1,1,0,0,1] + + Theoretically the length initial state vector should be equal to order of polynomial (M), however, it can easily be bigger than that + which is why all the validation of state vector and fpoly allows bigger length of state vector, however small state vector will raise an error. + + counter_start_zero: bool (default = True), whether to start counter with 0 or 1. If True, initial outbit is + set to -1, so is feedbackbit, until first .next() clock is excecuted. This initial output is not stacked in + seq. The output sequence should be same, in anycase, for example if you need run 10 cycles, using runKCycle(10) methed. + + verbose : boolean, optional (default=False) + if True, state of LFSR will be printed at every cycle(iteration) + + conf: str {'fibonacci', 'galois'}, default conf='fibonacci' + : configuration mode of LFSR, either fabonacci or galoisi. + : Example of 16-bit LFSR: + fibonacci: https://en.wikipedia.org/wiki/Linear-feedback_shift_register#/media/File:LFSR-F16.svg + Galois: https://en.wikipedia.org/wiki/Linear-feedback_shift_register#/media/File:LFSR-G16.svg + + seq_bit_index: int, index of shift register for output sequence. Default=-1, which means the last register + : seq_bit_index can varies from -M to M-1,for M-bit LFSR. For example 5-bit LFSR, seq_bit_index=-5,-4,-3,-2,-1, 0, 1, 2, 3, 4 + : seq_bit_index=-1, means output sequence is taken out from last Register, -2, second last, + + + Attributes + ---------- + count : int + Count the cycle, starts with 0 if counter_start_zero True, else starts with 1 + seq : np.array shape =(count,) + Output sequence stored in seq since first cycle + if -1, no cycle has been excecuted, count=0 when counter_start_zero is True + else last bit of initial state + + outbit : binary bit + Current output bit, + Last bit of current state + if -1, no cycle has been excecuted, count =0, when counter_start_zero is True + + feedbackbit : binary bit + if -1, no cycle has been excecuted, count =0, when counter_start_zero is True + + M : int + length of LFSR, M-bit LFSR + + expectedPeriod : int (also saved as T) + Expected period of sequence + if feedback polynomial is primitive and irreducible (as per reference) + period will be 2^M -1 + + T : int (also saved as expectedPeriod) + Expected period of sequence + if feedback polynomial is primitive and irreducible (as per reference) + period will be 2^M -1 + + feedpoly : str + feedback polynomial + + + Methods + ------- + + | Clocking (running LFSR):: + - next() : running one cycle + - runKCycle(k) : running k cycles + - runFullPeriod(): running a full period of cylces + + | Deprecated methods:: + - runFullCycle() : + - set() : set fpoly and initialstate + - changeFpoly(newfpoly) : change fpoly + - change_conf(conf) : change configuration + + | Setters:: + - reset() :reset to initial settings + - set_fpoly(fpoly) : change/set fpoly + - set_conf(conf) : change/set configuration + - set_state(state) : change/set state + - set_seq_bit_index(bit_index) : change/set seq_bit_index + + | Getters:: + - getFullPeriod() : get a period + - get_fPoly() : get feedback polynomial + - get_initState() : get initial state + - get_currentState() : get current state + - getState() : get current state as string + - get_outputSeq() : get output sequence + - getSeq() : get output sequence as string + - get_period() : get period + - get_expectedPeriod() : get expected period + - get_count() : get counter + + | Testing Properties + - test_properties() : Test all the properties for a valid LFSR + - balance_property(p) : Test Balance property for a given sequence p + - runlength_property(p): Test Runlength property for a given sequence p + - autocorr_property(p) : Test Autocorrelation property for a given sequence p + - test_p(p) :Test three properties for a given sequence p + + | Displaying:: + - info(): Display all the attribuates of LFSR + - Viz() : Display LFSR as a figure with a current state of LSFR with feedback polynomials and given configuration + + + Examples:: + ========== + # For more detailed and updated examples, please check - https://lfsr.readthedocs.io/ + #------------------------------------------------------- + >>> import numpy as np + >>> from pylfsr import LFSR + + ## Example ## 5 bit LFSR with x^5 + x^2 + 1 + >>> L = LFSR() #default fpoly=[5,2], initstate='ones' + >>> L = LFSR(fpoly=[5,2], initstate='ones') + + ### run one cycle + >>> L.next() + 1 + + ### run 10 cycles + >>> L.runKCycle(10) + array([1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) + + ### run one period of cycles + #>>> L.runFullCycle() # Depreciated + >>> L.runFullPeriod() # doctest: +NORMALIZE_WHITESPACE + array([1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1]) + + ## Displaying Info + >>> L.info() + 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [1 1 1 1 1] + Count : 0 + Output bit : -1 + feedback bit : -1 + + + ## Displaying Info with print + >>>print(L) + LFSR ( x^5 + x^2 + 1) + ================================================== + initstate = [1. 1. 1. 1. 1.] + fpoly = [5, 2] + conf = fibonacci + order = 5 + expectedPeriod = 31 + seq_bit_index = -1 + count = 1 + state = [0 1 1 1 1] + outbit = 1 + feedbackbit = 0 + seq = [1] + counter_start_zero = True + + + ## Displaying Info with repr + >>>repr(L) + "LFSR('fpoly'=[5, 2], 'initstate'=ones,'conf'=fibonacci, 'seq_bit_index'=-1,'verbose'=False, 'counter_start_zero'=True)" + + + >>> L.info() # doctest: +NORMALIZE_WHITESPACE + 5 bit LFSR with feedback polynomial x^5 + x^2 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [0 0 1 0 0] + Count : 42 + Output bit : 1 + feedback bit : 0 + Output Sequence 111110011010010000101011101100011111001101 + + + ## Example ## 5-bit LFSR with custom state vector and feedback polynomial + >>> state = np.array([0,0,0,1,0]) + >>> fpoly = [5,4,3,2] + >>> L1 = LFSR(fpoly=fpoly,initstate =state, verbose=True) + >>> L1.info() # doctest: +NORMALIZE_WHITESPACE + 5 bit LFSR with feedback polynomial x^5 + x^4 + x^3 + x^2 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [0 0 0 1 0] + Count : 0 + Output bit : -1 + feedback bit : -1 + + ### run 10000 cycles + >>> tempseq = L.runKCycle(10000) # generate 10000 bits from current state + + ### verbosity ON + >>>state = np.array([0,0,0,1,0]) + >>>fpoly = [5,4,3,2] + >>>L1 = LFSR(fpoly=fpoly,initstate=state, verbose=True) + >>> tempseq = L1.runKCycle(10) + S: [0 0 0 1 0] + S: [1 0 0 0 1] + S: [1 1 0 0 0] + S: [1 1 1 0 0] + S: [0 1 1 1 0] + S: [1 0 1 1 1] + S: [1 1 0 1 1] + S: [1 1 1 0 1] + S: [1 1 1 1 0] + S: [1 1 1 1 1] + >>> tempseq + array([0, 1, 0, 0, 0, 1, 1, 1, 0, 1]) + + + ## Example ## TO visualize the process with 3-bit LFSR, with default counter_start_zero = True + >>> state = [1,1,1] + >>> fpoly = [3,2] + >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=True) + >>> print('count \t state \t\toutbit \t seq') + >>> print('-'*50) + >>> for _ in range(15): + >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + >>> L.next() + >>> print('-'*50) + >>> print('Output: ',L.seq) + count state outbit seq + -------------------------------------------------- + 0 [1 1 1] -1 [-1] + 1 [0 1 1] 1 [1] + 2 [0 0 1] 1 [1 1] + 3 [1 0 0] 1 [1 1 1] + 4 [0 1 0] 0 [1 1 1 0] + 5 [1 0 1] 0 [1 1 1 0 0] + 6 [1 1 0] 1 [1 1 1 0 0 1] + 7 [1 1 1] 0 [1 1 1 0 0 1 0] + 8 [0 1 1] 1 [1 1 1 0 0 1 0 1] + 9 [0 0 1] 1 [1 1 1 0 0 1 0 1 1] + 10 [1 0 0] 1 [1 1 1 0 0 1 0 1 1 1] + 11 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0] + 12 [1 0 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0] + 13 [1 1 0] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] + 14 [1 1 1] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] + -------------------------------------------------- + Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] + + ## Example ## To visualize the process with 3-bit LFSR, with counter_start_zero = False + >>> state = [1,1,1] + >>> fpoly = [3,2] + >>> L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False) + >>> print('count \t state \t\toutbit \t seq') + >>> print('-'*50) + >>> for _ in range(15): + >>> print(L.count,L.state,'',L.outbit,L.seq,sep='\t') + >>> L.next() + >>> print('-'*50) + >>> print('Output: ',L.seq) + count state outbit seq + -------------------------------------------------- + 1 [1 1 1] 1 [1] + 2 [0 1 1] 1 [1 1] + 3 [0 0 1] 1 [1 1 1] + 4 [1 0 0] 0 [1 1 1 0] + 5 [0 1 0] 0 [1 1 1 0 0] + 6 [1 0 1] 1 [1 1 1 0 0 1] + 7 [1 1 0] 0 [1 1 1 0 0 1 0] + 8 [1 1 1] 1 [1 1 1 0 0 1 0 1] + 9 [0 1 1] 1 [1 1 1 0 0 1 0 1 1] + 10 [0 0 1] 1 [1 1 1 0 0 1 0 1 1 1] + 11 [1 0 0] 0 [1 1 1 0 0 1 0 1 1 1 0] + 12 [0 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0] + 13 [1 0 1] 1 [1 1 1 0 0 1 0 1 1 1 0 0 1] + 14 [1 1 0] 0 [1 1 1 0 0 1 0 1 1 1 0 0 1 0] + -------------------------------------------------- + Output: [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1] + + ## Example ## To visualize LFSR + L.Viz(show=False, show_labels=False,title='R1') + + + ## Galois Configuration + L = LFSR(initstate='ones',fpoly=[5,3],conf='galois') + L.Viz() + + + ## Example ## Change/Set conf in between + >>>L1 = LFSR(initstate='ones',fpoly=[5,3],conf='fibonacci') + >>>L1.set_fpoly(fpoly=[5,3]) + >>>L1.set_state(state=[1,1,0,0,1]) + >>>L1.set_conf(conf='galois') + + ## Example ## 23 bit LFSR with custum state and feedback polynomial + + >>> fpoly = [23,19] + >>> L1 = LFSR(fpoly=fpoly,initstate ='ones', verbose=False) + >>> L1.info() + 23 bit LFSR with feedback polynomial x^23 + x^19 + 1 + Expected Period (if polynomial is primitive) = 8388607 + Current : + State : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] + Count : 0 + Output bit : -1 + feedback bit : -1 + + >>> seq = L1.runKCycle(100) + >>> seq + array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]) + + >>> #L.changeFpoly(newfpoly =[23,21]) + >>> L.set_fpoly(fpoly =[23,21]) + >>> seq1 = L.runKCycle(20) + + ## Example ## testing the properties + >>> state = [1,1,1,1,0] + >>> fpoly = [5,3] + >>> L = LFSR(initstate=state,fpoly=fpoly) + >>> L.info() + 5 bit LFSR with feedback polynomial x^5 + x^3 + 1 + Expected Period (if polynomial is primitive) = 31 + Current : + State : [1 1 1 1 0] + Count : 0 + Output bit : -1 + feedback bit : -1 + + >>>result = L.test_properties(verbose=1) + 1. Periodicity + ------------------ + - Expected period = 2^M-1 = 31 + - Pass?: True + + 2. Balance Property + ------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (16, 15) + - Pass?: True + + 3. Runlength Property + ------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [8 4 2 1 1] + - Pass?: True + + 4. Autocorrelation Property + ------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: True + + ================== + Passed all the tests + ================== + + >>> p = L.getFullPeriod() + >>> p + array([0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 1, 1, 0]) + + >>> L.balance_property(p.copy()) + (True, (16, 15)) + + >>> L.runlength_property(p.copy()) + (True, array([8, 4, 2, 1, 1])) + + >>> L.autocorr_property(p.copy())[0] + True + + ## Example 7 ## testing the properties for non-primitive polynomial + >>> state = [1,1,1,1,0] + >>> fpoly = [5,1] + >>> L = LFSR(initstate=state,fpoly=fpoly) + >>> result = L.test_properties(verbose=1) + 1. Periodicity + ------------------ + - Expected period = 2^M-1 = 31 + - Pass?: False + + 2. Balance Property + ------------------- + - Number of 1s = Number of 0s+1 (in a period): (N1s,N0s) = (17, 14) + - Pass?: False + + 3. Runlength Property + ------------------- + - Number of Runs in a period should be of specific order, e.g. [4,2,1,1] + - Runs: [10 2 1 1 2] + - Pass?: False + + 4. Autocorrelation Property + ------------------- + - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else + - Pass?: False + + ================== + Failed one or more tests, check if feedback polynomial is primitive polynomial + ================== + ''' + + def __init__(self, fpoly=[5, 2], initstate='ones', conf='fibonacci',seq_bit_index=-1,verbose=False,counter_start_zero=True): + + self._initstate = initstate + if isinstance(initstate, str): + if initstate == 'ones': + initstate = np.ones(np.max(fpoly)) + elif initstate == 'random' or initstate == 'rand': + initstate = np.random.randint(0, 2, np.max(fpoly)) + else: + raise ValueError('Unknown initial state') + if isinstance(initstate, list): + initstate = np.array(initstate) + + self.initstate = initstate + self.fpoly = fpoly + self.state = initstate.astype(int) + + # Configuration can be either 'fibonacci' or 'galois' + self.conf = conf + + # sequence bit: sequence to be taken from, default -1, last bit of LFSR + self.seq_bit_index = seq_bit_index + + #self.skip_first = skip_first + self.counter_start_zero = counter_start_zero + self.count = 0 if counter_start_zero else 1 + + + self.verbose = verbose + self.update() + self.check() + self.seq = np.array([-1]) if counter_start_zero else np.array([self.state[self.seq_bit_index]]) + self.outbit = -1 if counter_start_zero else self.state[self.seq_bit_index] + self.feedbackbit = -1 if counter_start_zero else self.state[self.seq_bit_index] + + def update(self): + ''' + Updatating order, period and feedpoly string + ''' + self.fpoly.sort(reverse=True) + feed = ' ' + for i in range(len(self.fpoly)): + feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' + feed = feed + '1' + self.feedpoly = feed + + self.M = np.max(self.fpoly) + self.order = self.M + self.expectedPeriod = 2**self.M - 1 + self.T = 2**self.M - 1 + + def check(self): + ''' + Check if + - degree of feedback polynomial <= length of LFSR >=1 + - given intistate of LFSR is correct + - configuration is valid + - output sequence bit index + + ''' + + # Check Feedback Polynomial + # ------------------------ + if np.max(self.fpoly) > self.initstate.shape[0] or np.min(self.fpoly) < 1 or len(self.fpoly) < 2: + raise ValueError('Invalid feedback polynomial: Order of feedback polynomial can not be less than 2 or greater than length of state vector. \n Polynomial also can not have negative or zeros powers') + + if len(set(self.fpoly))!=len(self.fpoly): + raise ValueError('Invalid feedback polynomial: feedback polynomial vector should have unique powers') + + # Check Initial State + # ------------------------ + if len(self.initstate.shape) > 1 and (self.initstate.shape[0] != 1 or self.initstate.shape[1] != 1): + raise ValueError('Invalid Initial state vector: Size of intial state vector should be one diamensional') + else: + self.initstate = np.squeeze(self.initstate) + + if np.sum(self.initstate==1)+np.sum(self.initstate==0) != len(self.initstate): + raise ValueError('Invalid Initial state vector: Initial state vector should be binary, i.e., 0s and 1s') + if np.sum(self.initstate==0) == len(self.initstate): + raise ValueError('Invalid Initial state vector: Initial state vector can not be All Zeros') + + assert np.sum(self.initstate>1) + np.sum(self.initstate<0)==0 # test if initial state is binary, 1s and 0s + + # Check Configuration + # ------------------------ + # Configuration can be either 'fibonacci' or 'galois' + if self.conf not in ['fibonacci','galois']: + raise ValueError('Not valid configuration, "conf" should be either "fibonacci" or "galois"') + + if self.conf=='galois' and np.max(self.fpoly)!=len(self.state): + raise ValueError('Wrong length of state vector for Galois configuration. For Galois configuration, length of state vector should be same as order of feedback polynomial ') + + + # Check Output sequence bit index + # ------------------------ + if self.seq_bit_index not in list(range(-np.max(self.fpoly), np.max(self.fpoly))): + raise IndexError('Output sequence can be taken from one of the register only [%d,%d), index = %d provided: Out of bounds index' % (-np.max(self.fpoly), np.max(self.fpoly), self.seq_bit_index)) + + def check_state(self): + ''' + check if current state vector is valid + ''' + + # Check Initial State + # ------------------------ + if len(self.state.shape) > 1 and (self.state.shape[0] != 1 or self.state.shape[1] != 1): + raise ValueError('Invalid state vector: Size of state vector should be one diamensional') + + if np.sum(self.state==1)+np.sum(self.state==0) != len(self.state): + raise ValueError('Invalid state vector: state vector should be binary, i.e., 0s and 1s') + + if np.sum(self.state==0) == len(self.state): + raise ValueError('Invalid state vector: State vector can not be All Zeros') + + if self.conf=='galois' and np.max(self.fpoly)!=len(self.state): + raise ValueError('Wrong length of state vector for Galois configuration. For Galois configuration, length of state vector should be same as order of feedback polynomial ') + + assert np.sum(self.state>1) + np.sum(self.state<0)==0 # test if initial state is binary, 1s and 0s + + def info(self): + ''' + Display the information about LFSR with current state of variables + ''' + print('%d-bit LFSR with feedback polynomial %s with' % (self.M, self.feedpoly)) + print('Expected Period (if polynomial is primitive) = ', self.expectedPeriod) + if self.seq_bit_index>-1: + print('Computing configuration is set to %s with output sequence taken from %d-th register' % (self.conf.capitalize(), self.seq_bit_index+1)) + else: + print('Computing configuration is set to %s with output sequence taken from %d-th (%d) register' % (self.conf.capitalize(), self.seq_bit_index%self.M +1, self.seq_bit_index)) + print('Current :') + print(' State : ', self.state) + print(' Count : ', self.count) + print(' Output bit : ', self.outbit) + print(' feedback bit : ', self.feedbackbit) + if self.count > 0 and self.count < 1000: + print(' Output Sequence: %s' % (''.join([str(int(x)) for x in self.seq]))) + + def __repr__(self): + fmt = f"LFSR('fpoly'={self.fpoly}, 'initstate'={self._initstate.astype(int).tolist() if isinstance(self._initstate,np.ndarray) else self._initstate}," +\ + f"'conf'={self.conf}, 'seq_bit_index'={self.seq_bit_index},'verbose'={self.verbose}, 'counter_start_zero'={self.counter_start_zero})" + return fmt + + def __str__(self): + fmt = f"LFSR ({self.feedpoly})\n" + fmt = fmt+ f"{'='*50}\n" + param = ['initstate', 'fpoly', 'conf', 'order', 'expectedPeriod', 'seq_bit_index', ] + param = param + ['count','state','outbit','feedbackbit','seq','counter_start_zero'] + + for key in param: + if key in self.__dict__: + fmt = fmt+f"{key}{' '*(10-len(key))}\t=\t{self.__dict__[key]}\n" + return fmt + + def next(self,verbose=False): + ''' + Run one cycle on LFSR with given feedback polynomial and + update the count, state, feedback bit, output bit and seq + + Returns + ------- + output bit : binary + ''' + if self.verbose or verbose: + print('S: ', self.state) + + if self.counter_start_zero: + self.outbit = self.state[self.seq_bit_index] + if self.count ==0: + self.seq = np.array([self.state[self.seq_bit_index]]) + else: + self.seq = np.append(self.seq, self.state[self.seq_bit_index]) + + if self.conf=='fibonacci': + b = np.logical_xor(self.state[self.fpoly[0] - 1], self.state[self.fpoly[1] - 1]) + if len(self.fpoly) > 2: + for i in range(2, len(self.fpoly)): + b = np.logical_xor(self.state[self.fpoly[i] - 1], b) + + #self.outbit = self.state[-1] + self.state = np.roll(self.state, 1) + self.feedbackbit = b * 1 + self.state[0] = self.feedbackbit + else: + #self.conf=='galois': + self.feedbackbit = self.state[0] + self.state = np.roll(self.state, -1) + for k in self.fpoly[1:]: + self.state[k-1] = np.logical_xor(self.state[k-1], self.feedbackbit) + + if not(self.counter_start_zero): + self.outbit = self.state[self.seq_bit_index] + if self.count ==0: + self.seq = np.array([self.outbit]) + else: + self.seq = np.append(self.seq, self.outbit) + + self.count += 1 + + return self.outbit + + def runKCycle(self, k, verbose=False): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + if verbose: + self.verbose = False + tempseq = [] + for i in range(k): + ProgBar(i,k,title=f' {k}-cycles') + tempseq.append(self.next()) + else: + tempseq = [self.next() for _ in range(k)] + return np.array(tempseq) + + @deprecated('due to misnomer, use "runFullPeriod" instead') + def runFullCycle(self): + ''' + NOTE: Will be deprecated in future version due to misnomer, use "runFullPeriod" instead + + Run a full cycle (T = 2^M-1) on LFSR from current state + + Returns + ------- + seq : binary output sequence since start: shape = (count,) + ''' + temp = [self.next() for _ in range(self.expectedPeriod)] + return self.seq + + def runFullPeriod(self,verbose=False): + ''' + Run a full period of cycles (T = 2^M-1) on LFSR from current state + + Returns + ------- + seq : binary output sequence since start: shape = (count,) + ''' + if verbose: + self.verbose = False + tempseq = [] + for i in range(self.expectedPeriod): + ProgBar(i,self.expectedPeriod,title=f' {self.expectedPeriod}-cycles') + tempseq.append(self.next()) + else: + temp = [self.next() for _ in range(self.expectedPeriod)] + return self.seq + + def reset(self): + ''' + Reseting LFSR to its initial state and count + ''' + self.__init__(initstate=self.initstate,fpoly=self.fpoly,counter_start_zero=self.counter_start_zero,conf=self.conf,seq_bit_index=self.seq_bit_index) + + @deprecated('Use "set_fpoly" and "set_state" instead') + def set(self, fpoly, state='ones', enforce=False): + ''' + NOTE: Will be deprecated in future version, use "set_fpoly" and "set_state" instead + + Set feedback polynomial and state + + Parameters + ---------- + fpoly : list + feedback polynomial like [5,4,3,2] + + state : np.array, like np.array([1,0,0,1,1]) + default ='ones' + Initial state is intialized with ones and length of register is equal to + degree of feedback polynomial + if state='rand' + Initial state is intialized with random binary sequence of length equal to + degree of feedback polynomial + enforce: bool (defaule=False) + : test if (1) new polynomial is for same LFSR or not (2) state vector is same length or not + : Setting enforce=True, allows the change of feedback polynomial from M-bit to K-bit LFSR, for example changing from 5-bit to 3-bit etc + in that case, make sure to change initial state vector accordingly + ''' + if not enforce: + # Order of new feedback polynomial should be same as previous + # if change in size and order was intantional, set enforce=True + assert np.max(fpoly)==np.max(self.fpoly) + if not isinstance(state,str): + # Length of new state vector should be same as previous + # if change in size and order was intantional, set enforce=True + assert len(state)==len(self.state) + + self.__init__(fpoly=fpoly, initstate=state,counter_start_zero=self.counter_start_zero,conf=self.conf,seq_bit_index=self.seq_bit_index) + + @deprecated('due to inconsitancy in naming, use "set_fpoly" instead') + def changeFpoly(self, newfpoly, reset=False,enforce=False): + ''' + NOTE: Will be deprecated in future version, use "set_fpoly" instead + + Changing Feedback polynomial : Useful to change feedback polynomial in between as in A5/1 stream cipher + + Parameters + ---------- + newfpoly : list like, [5,4,2,1] + changing the feedback polynomial + + reset : boolean default=False + if True, reset all the Parameters: count and seq etc .... + if False, leave the LFSR as it is only change the feedback polynomial + for further use, as used in + 'Enhancement of A5/1: Using variable feedback polynomials of LFSR' + https://doi.org/10.1109/ETNCC.2011.5958486 + enforce: bool (defaule=False) + : test if new polynomial is for same LFSR or not + + ''' + if not enforce: + # Order of new feedback polynomial should be same as previous + #if change in size and order was intantional, set enforce=True + assert np.max(newfpoly)==np.max(self.fpoly) + + newfpoly.sort(reverse=True) + self.fpoly = newfpoly + self.update() + self.check() + if reset: self.reset() + + @deprecated('due to inconsitancy in naming, use "set_conf" instead') + def change_conf(self,conf): + assert conf in ['fibonacci', 'galois'] + self.conf = conf + self.check() + + def set_fpoly(self, fpoly, reset=False,enforce=False): + ''' + Set Feedback polynomial + ------------------------ + Useful to change feedback polynomial in between as in A5/1 stream cipher, to increase the complexity + + Parameters + ---------- + fpoly : list like, [5,4,2,1], should be same + changing the feedback polynomial + + reset : boolean default=False + if True, reset all the Parameters: count and seq etc .... + if False, leave the LFSR as it is only change the feedback polynomial + for further use, as used in + 'Enhancement of A5/1: Using variable feedback polynomials of LFSR' + https://doi.org/10.1109/ETNCC.2011.5958486 + + enforce: bool (defaule=False) + : test if new polynomial is for same LFSR or not + : Setting enforce=True allows to change feedback polynomial from M-bit to any other bit, given that state vector is changed accordingly. + : check details of initstate in help(LFSR) doc + + ''' + if not enforce: + # Order of new feedback polynomial should be same as previous + #if change in size and order was intantional, set enforce=True + assert np.max(fpoly)==np.max(self.fpoly) + + fpoly.sort(reverse=True) + + self.fpoly = fpoly + feed = ' ' + for i in range(len(self.fpoly)): + feed = feed + 'x^' + str(self.fpoly[i]) + ' + ' + feed = feed + '1' + self.feedpoly = feed + + self.update() + self.check() + if reset: self.reset() + + def set_conf(self,conf, reset=False): + ''' + Set Configuration + ----------------- + Change configuration, useful to change configuration in between + + Parameters + ---------- + conf: str {'fibonacci', 'galois'}, default conf='fibonacci' + + reset : boolean default=False + if True, reset all the Parameters: count and seq etc .... + if False, leave the LFSR as it is only change configuration + + ''' + assert conf in ['fibonacci', 'galois'] + self.conf = conf + self.check() + if reset: self.reset() + + def set_state(self,state,return_state=False,enforce=False): + ''' + Set Current state + ----------------- + + Parameters + ---------- + state: str, list or np.array + : if str state='ones' or state='random' + : if list or np.array, it should be binary and length equal to max of polynomial degree + return_state: bool, if True, return state vector. Useful when state='random' is passed, to keep track of newly inilized state vector + enforce: bool (defaule=False) + : test if new state vector has same length as old one + : + + ''' + if isinstance(state, str): + if state == 'ones': + state = np.ones(np.max(self.fpoly)) + elif state == 'random': + state = np.random.randint(0, 2, np.max(self.fpoly)) + else: + raise ValueError('Unknown state: only ones or random is allowed') + + if isinstance(state, list): + state = np.array(state).astype(int) + + if not enforce: + # Length of new state vector should be same as previous + # if change in size and order was intantional, set enforce=True + assert len(state)== len(self.state) + self.state = state + self.check_state() + self.update() + if return_state: return self.state + + def set_seq_bit_index(self,bit_index): + ''' + Set Output bit index: for output sequence as index + -------------------------------------------------- + + seq_bit_index: int in range from -M to M-1 + + ''' + if bit_index not in list(range(-np.max(self.fpoly), np.max(self.fpoly))): + raise IndexError('Output sequence can be taken from one of the register only, index = %d out of bounds' % (bit_index)) + + # Index of draw outout sequence should be in bounds to state vector + self.state[bit_index] + + self.seq_bit_index = bit_index + + def getFullPeriod(self): + ''' + Get a seq of a full period from LSFR, by executing next() method T times. + The current state of LFSR is used to generate T bits. + + Calling this function also update the count, current state and output sequence of main LFSR object + + Returns + ------- + seq (T bits), binary output sequence of last T bits + ''' + seq = np.array([self.next() for _ in range(self.expectedPeriod)]) + return seq + + def get_fPoly(self): + '''get feedback polynomial''' + return self.fpoly + + def get_initState(self): + '''get initial state of LFSR''' + return self.initstate + + def get_currentState(self): + '''get current state of LFSR''' + return self.state + + def get_outputSeq(self): + '''get output sequence as array''' + return self.seq + + def get_period(self): + '''get period of sequence''' + return self.T + + def get_expectedPeriod(self): + '''get period of sequence''' + return self.expectedPeriod + + def get_count(self): + '''get counter value''' + return self.count + + def _loadFpolyList(self): + import os + fname = 'primitive_polynomials_GF2_dict.txt' + fname = os.path.join(os.path.dirname(__file__), fname) + try: + f = open(fname, "rb") + lines = f.readlines() + f.close() + self.fpolyList = eval(lines[0].decode()) + except: + raise Exception("File named:'{}' Not Found!!! \n try again, after downloading file from github save it in lfsr directory".format(fname)) + + def get_fpolyList(self,m=None): + ''' + Get the list of primitive polynomials as feedback polynomials for m-bit LFSR. + Only list of primary primitive polynomials are retuned, not full list (half list), since for each primary primitive polynomial + an image polymial can be computed using 'get_Ifpoly' method + + Parameters + ---------- + m: 1 1 and m < 32: + return self.fpolyList[m] + else: + print('Wrong input m. m should be int 1 < m < 32 or None') + + @staticmethod + def get_Ifpoly(fpoly): + ''' + Get image of feebback polynomial + Get the image of primitive polynomial + Parameters + ---------- + fpoly: polynomial as list e.g. [5,2] for x^5 + x^2 + 1 + : should be a valid primitive polynomial + + Returns + ------- + ifpoly: polynomial as list e.g. [5,3] for x^5 + x^3 + 1 + + ''' + if isinstance(fpoly, list) or (isinstance(fpoly, numpy.ndarray) and len(fpoly.shape)==1): + fpoly = list(fpoly) + fpoly.sort(reverse=True) + ifpoly = [fpoly[0]] +[fpoly[0]-ff for ff in fpoly[1:]] + ifpoly.sort(reverse=True) + return ifpoly + else: + print('Not a valid form of feedback polynomial') + + def test_properties(self,verbose=1): + p1 = self.getFullPeriod() + p2 = self.getFullPeriod() + + r1 = np.mean(p1==p2)==1 + r2, (N1s, N0s) = self.balance_property(p1.copy()) + r3, runs = self.runlength_property(p1.copy(),verbose=0) + r4, (shift, rxx) = self.autocorr_property(p1.copy(),plot=False) + + result = bool(np.prod([r1,r2,r3,r4])) + + if verbose: + print('1. Periodicity') + print('------------------') + print(' - Expected period = 2^M-1 =',self.expectedPeriod) + print(' - Pass?: ',r1) + print('') + print('2. Balance Property') + print('-------------------') + print(' - Number of 1s = Number of 0s+1 (in a period)') + print(' - #1s = ',N1s,'\t#0s = ', N0s, ':=',N1s,'= 1 +',N0s) + print(' - Pass?: ',r2) + print('') + print('3. Runlength Property') + print('-------------------') + print(' - Number of Runs of different lengths in a period should be of specific order, e.g. [4,2,1,1], that is 4 runs of length 1, 2 runs of length 2 and so on ..') + print(' - Runs: ',runs) + print(' - Pass?: ',r3) + print('') + print('4. Autocorrelation Property') + print('-------------------') + print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else \n') + if verbose>1: + print(' - Rxx(k): ',rxx.round(3)) + try: + import matplotlib.pyplot as plt + except: + raise('Error loading matplotlib, either install it or set verbose<2') + plt.plot(shift,rxx) + plt.xlabel('shift (k)') + plt.ylabel(r'$R_{xx}(k)$') + plt.axhline(y=0,color='k',ls=':',lw=0.5) + plt.xlim(shift[0],shift[-1]) + plt.title('Autocorrelation') + plt.grid(alpha=0.4) + plt.show() + print(' - Pass?: ',r4) + print('\n\n') + print('==================') + if result: + print('Passed all the tests') + else: + print('Failed one or more tests, check if feedback polynomial is primitive polynomial') + print('==================') + return result + + def test_p(self,p,verbose=1): + ''' + Test all the three properties for seq p : + (1) Balance Property + (2) Runlegth Property + (3) Autocorrelation Property + + + Parameters + ---------- + p : array-like, a sequence of a period from LFSR + verbose = 0 : no printing details + = 1 : print details + = 2 : print and plot more details + Returns + ------- + result: bool, True if all three are satisfied else False + ''' + r1,(N1s,N0s) = self.balance_property(p.copy()) + r2,runs = self.runlength_property(p.copy(),verbose=0) + r3,(shift,rxx) = self.autocorr_property(p.copy(),plot=False) + result = bool(np.prod([r1,r2,r3])) + if verbose: + print('1. Balance Property') + print('-------------------') + print(' - Number of 1s = Number of 0s+1 (in a period)') + print(' - #1s = ',N1s,'\t#0s = ', N0s, ':=',N1s,'= 1 +',N0s) + print(' - Pass?: ',r1) + print('') + print('2. Runlength Property') + print('-------------------') + print(' - Number of Runs in a period should be of specific order, e.g. [4,2,1,1]') + print(' - Runs: ',runs) + print(' - Pass?: ',r2) + print('') + print('3. Autocorrelation Property') + print('-------------------') + print(' - Autocorrelation of a period should be noise-like, specifically, 1 at k=0, -1/m everywhere else') + if verbose>1: + print(' - Rxx(k): ',rxx.round(3)) + try: + import matplotlib.pyplot as plt + except: + raise('Error loading matplotlib, either install it or set verbose<2') + plt.plot(shift,rxx) + plt.xlabel('shift (k)') + plt.ylabel(r'$R_{xx}(k)$') + plt.axhline(y=0,color='k',ls=':',lw=0.5) + plt.xlim(shift[0],shift[-1]) + plt.title('Autocorrelation') + plt.grid(alpha=0.4) + plt.show() + print(' - Pass?: ',r3) + print('\n\n') + print('==================') + if result: + print('Passed all three tests') + else: + print('Failed one or more tests') + print('==================') + + return result + + def balance_property(self,p): + ''' + Balance Property: In a period of LFSR with a valid feedback polynomial, + the number of 1s should be equal to number of 0s +1 + '' N1s == N0s + 1 '' + Test balance property for a given full period of seq, p. + + Parameters + ---------- + p: array-like, a sequence of a period from LFSR + + + Returns + ------- + result: bool, True if seq p satisfies Balance Property else False + (N1s, N0s): tuple, number of 1s and number of 0s + ''' + N1s = np.sum(p==1) + N0s = np.sum(p==0) + result = N1s == N0s+1 + return result, (N1s,N0s) + + def runlength_property(self,p,verbose=0): + ''' + Run Length Property: In a period of LSFR with valid feedback polynomial, + the number of runs of different length are in specific order. + '' + number of (M-k) bit runs = ⌈ 2^(k-1) ⌉ , for k = 0 to M-1 + '' + where ⌈ ⌉ is a ceiling function + That is, for M bit LFSR, + - number of M bit runs : 1 + - number of (M-1) bit runs : 1 + - number of (M-2) bit runs : 2 + - number of (M-3) bit runs : 4 + ... + so on + + Parameters + ---------- + p: array-like, a sequence of a period from LFSR + + Returns + ------- + result: bool, True if seq p satisfies Run Length Property else False + runs: list, list of runs + ''' + T = len(p) + if verbose>1: print(p) + + if len(set(p))>1: + while p[0]==p[-1]: p = np.roll(p,1) + + if verbose>1: print(p) + if p[-1]==0: + p = np.append(p,1) + else: + p = np.append(p,0) + if verbose>1: print(p) + + i=0 + runs = np.zeros(T).astype(int) + for k in range(T): + if p[k]==p[k+1]: + i=i+1 + else: + runs[i]=runs[i]+1 + i=0 + if verbose>1: print(runs) + runs = runs[:max(np.where(runs)[0])+1] + if verbose: print('Runs : ',runs) + + l = len(runs) + pp=0 + for k in range(len(runs)-2): + if runs[k]==2*runs[k+1]: + pp=pp+1 + if runs[-2]==runs[-1]: pp=pp+1 + + result = False + + if pp==len(runs)-1: result = True + if verbose>1: + if result: print('Pass') + else: print('Fail') + return result, runs + + def autocorr_property(self,p,plot=False): + ''' + Autocorrelation Property: For sequence of period T of LSFR with valid feedback polynomial, + the autocorrelation is a noise like, that is, 1 with zero (or T) lag (shift), -1/T (almost zero) else. + + unlike usual, for binary, the correlation value between two sequence of same length bx, by is computed as follow; + match = sum(bx == by) (number of mataches) + mismatch = sum(bx!= by) (number of mismatches) + '' + rxy = (match - mismatch)/ length(bx) + '' + + Parameters + ---------- + p: array-like, a sequence of a period from LFSR + + plot: bool (default False), if True, it will plot the autocorrelation function, + which will require matplotlib library. Turn it of if matplotlib is not installed + + Returns + ------- + result: bool, True if seq p satisfies Autocorrelation Property else False + (shift, rxx): tuple of sequence of shift corresponding autocorrelation values + ''' + T = len(p) + px = p.copy() + rxx = np.zeros(2*T+1) + for k in range(2*T+1): + py = np.roll(p.copy(),k) + r = px==py + rxx[k] = (np.sum(r==1) - np.sum(r==0))/T + + result = False + if np.prod(np.isclose(rxx[1:T],-1/T)): + result = True + + shift = np.arange(-T,T+1) + if plot: + try: + import matplotlib.pyplot as plt + except: + raise('Error loading matplotlib, either install it or set plot=False') + plt.plot(shift,rxx) + plt.xlabel('shift (k)') + plt.ylabel(r'$R_{xx}(k)$') + plt.axhline(y=0,color='k',ls=':',lw=0.5) + plt.xlim(shift[0],shift[-1]) + plt.title('Autocorrelation') + plt.grid(alpha=0.4) + plt.show() + return result, (shift,rxx) + + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + + @staticmethod + def arr2str(arr): + return ''.join(arr.copy().astype(str)) + + def Viz(self,ax=None,show=True,fs=25,show_labels=False,title='',title_loc='left',box_color='lightblue',alpha=0.5, + output_arrow_color='C2',output_arrow_style='h',show_outseq=True): + + ''' + Display LFSR + ------------- + + ax: axis to plot, if None, new axis will be created, (default None) + show: if True, plt.show() will be excecuted, (default True) + fs: fontsize (default 25) + show_label: if true, will display names + title: str, title of figure, default '', + title_loc, alignment of title, 'left', 'right', 'center', (default 'left') + ''' + state = self.state + fpoly = self.fpoly + seq = self.getSeq() if show_outseq else '' + outbit = self.outbit + feedbit = self.feedbackbit + conf = self.conf + out_bit_index = self.seq_bit_index + + + dispLFSR(state=state,fpoly=fpoly,conf=conf,seq=seq,out_bit_index=out_bit_index, + ob=outbit,fb=feedbit,fs=fs,ax=ax,show_labels=show_labels,title=title, + title_loc=title_loc,box_color=box_color,alpha=alpha,output_arrow_color=output_arrow_color,output_arrow_style=output_arrow_style) + #PlotLFSR(state,fpoly,seq=seq,ob=outbit,fb=feedbit,fs=fs,ax=ax,show_labels=show_labels,title=title,title_loc=title_loc, + # box_color=box_color,alpha=alpha) + if show: plt.show() + + +def drawR(ax,x=0,y=0,s=1,alpha=0.5,color='lightblue',linewidth=1, edgecolor='k',): + rect = patches.Rectangle((x-s/2, y-s/2), s, s, linewidth=linewidth, edgecolor=edgecolor, facecolor=color,alpha=alpha) + ax.add_patch(rect) + +def PlotLFSR(state,fpoly,conf='fibonacci',seq='',ob=None,fb=None,fs=25,ax=None,show_labels=False,title='',title_loc='left', + box_color='lightblue',alpha=0.5): + ''' + ----- Plot LFSR ---- + state: current state of LFSR + fpoly: feedback polynomial of LFSR + seq: str, output sequence + ob: output bit + fb: feedback bit + ax: axis to plot, if None, new axis will be created, (default None) + + show: if True, plt.show() will be excecuted, (default True) + fs: fontsize (default 25) + show_label: if true, will display names + title: str, title of figure, default '', + title_loc, alignment of title, 'left', 'right', 'center', (default 'left') + box_color: color of register box, default='lightblue' + ''' + M = len(state) + ym = 3.5 + if ax is None: + fig, ax = plt.subplots(figsize=(M+5,ym)) + + s=1 + xs = 3 + ys = ym-1 + last_x= xs + + if conf=='fibonacci': + for k in range(M): + x,y = xs+k,ys + ax.text(x,y,str(state[k]),ha='center',va = 'center',fontsize=fs) + + if k==0: + x1, y1 = [x-1.5*s, x-s/2], [y, y] + ax.plot(x1, y1,marker = '>',color='k',markevery=(1,1),ms=10) + + if k+1 in fpoly: + x1, y1 = [x, x], [y-s/2, y-1.5*s] + ax.plot(x1, y1,marker = '.',color='k') + ax.plot(x,y-1.5*s,marker = '+',color='b',ms=15,mew=3) + ax.plot(x,y-1.5*s,marker = 'o',color='b',ms=15,mfc='none',mew=2) + if last_x-M and out_bit_index<=M: + out_bit_index=1+(out_bit_index)%M + + #print(out_bit_index) + + output_arr_plot=False + + if conf=='fibonacci': + for k in range(M): + x,y = xs+k,ys + ax.text(x,y,str(state[k]),ha='center',va = 'center',fontsize=fs) + + if k==0: + x1, y1 = [x-1.5*s, x-s/2], [y, y] + ax.plot(x1, y1,marker = '>',color='k',markevery=(1,1),ms=10) + + if k+1 in fpoly: + x1, y1 = [x, x], [y-s/2, y-1.5*s] + ax.plot(x1, y1,marker = '.',color='k') + ax.plot(x1[0],y-1*s,marker = 'v',color='k',ms=8,mew=3) + + ax.plot(x,y-1.5*s,marker = '+',color='b',ms=15,mew=3) + ax.plot(x,y-1.5*s,marker = 'o',color='b',ms=15,mfc='none',mew=2) + + if last_x 1)*1 + return self.outbit + + def getLastbits(self): + return [self.R1.state[-1],self.R2.state[-1],self.R3.state[-1]] + def getCbits(self): + return [self.R1.state[8],self.R2.state[10],self.R3.state[10]] + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + def arr2str(self,arr): + return ''.join(arr.copy().astype(str)) + def runKCycle(self, k): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + tempseq = [self.next() for i in range(k)] + return np.array(tempseq) + +class Geffe(): + ''' + Geffe Generator + --------------- + Combining K LFSR in non-linear manner + linear complexity + + Parameters + ---------- + K+1 LFSRs + + kLFSR_list: list of K LFSR, output of one of these is choosen at any time, depending on cLFSR + cLFSR: clocking LFSR + + K should be power of 2. 2,4,8,... 128 + + Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. + Chaper 16 + + + Example + -------- + + import numpy as np + import matplotlib.pyplot as plt + from pylfsr import Geffe, LFSR + + kLFSR = [LFSR(initstate='random') for _ in range(8)] + cLFSR = LFSR(initstate='random') + + GG = Geffe(kLFSR_list=kLFSR, cLFSR=cLFSR) + + print('key: ',GG.getState()) + print() + for _ in range(50): + print(GG.count,GG.m_count,GG.outbit_k,GG.sel_k,GG.outbit,GG.getSeq(),sep='\t') + GG.next() + + GG.runKCycle(1000) + GG.getSeq() + ''' + def __init__(self,kLFSR_list,cLFSR): + + self.K = len(kLFSR_list) + assert isinstance(cLFSR,LFSR) + assert [isinstance(Rk,LFSR) for Rk in kLFSR_list] + assert self.K>1 + assert (self.K & (self.K-1) == 0) and self.K != 0 + #K (list of LFSR) should be power of 2 + + self.m = np.log2(self.K).astype(int) + + self.kLFSR_list = kLFSR_list + self.cLFSR = cLFSR + + self.count=0 + self.m_count =0 + self.seq =np.array([]) + self.outbit = -1 + self.sel_k = -1 + self.outbit_k = [Rk.state[-1] for Rk in self.kLFSR_list] + self.state = np.hstack([R.state for R in self.kLFSR_list+[self.cLFSR]]) + self.state_k = np.hstack([R.state for R in self.kLFSR_list]) + self.state_c = self.cLFSR.state + + + def getSel(self): + sel = self.cLFSR.runKCycle(self.m) + self.m_count+=self.m + sel = ''.join(sel.astype(str)) + return int(sel, 2) + def next(self): + if self.count: + _ = [Rk.next() for Rk in self.kLFSR_list] + + self.outbit_k = [Rk.state[-1] for Rk in self.kLFSR_list] + self.sel_k = self.getSel() + self.outbit = self.outbit_k[self.sel_k] + + self.seq = np.append(self.seq,self.outbit).astype(int) + + self.state = np.hstack([R.state for R in self.kLFSR_list+[self.cLFSR]]) + self.state_k = np.hstack([R.state for R in self.kLFSR_list]) + self.state_c = self.cLFSR.state + + self.count+=1 + return self.outbit + + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + def arr2str(self,arr): + return ''.join(arr.copy().astype(str)) + + def runKCycle(self, k): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + tempseq = [self.next() for i in range(k)] + return np.array(tempseq) + +class Geffe3(): + ''' + Geffe Generator + --------------- + Combining three LFSR in non-linear manner + linear complexity: If the LFSRs have lengths n1, n2, and n3, respectively, then the linear + complexity of the generator is = (n1 + 1)n2 + n1n3 + + output bit at any time is + + b = (r1 ^ r2) • ((¬ r1) ^ r3) + + where r1,r2,r3 are the outbit of three LFSRs respectively + + Ref: Schneier, Bruce. Applied cryptography: protocols, algorithms, and source code in C. john wiley & sons, 2007. + Chaper 16 + + ''' + def __init__(self,R1,R2,R3): + + assert isinstance(R1,LFSR) + assert isinstance(R2,LFSR) + assert isinstance(R3,LFSR) + + self.R1 = R1 + self.R2 = R2 + self.R3 = R3 + self.count=0 + self.seq =[] + self.state = np.r_[self.R1.state, self.R2.state,self.R3.state] + self.next() + + def next(self): + if self.count: + self.R1.next() + self.R2.next() + self.R3.next() + self.r1 = self.R1.state[-1] + self.r2 = self.R2.state[-1] + self.r3 = self.R3.state[-1] + + b1 = np.logical_and(self.r1,self.r2) + b2 = np.logical_and(not(self.r1),self.r2) + self.outbit = np.logical_xor(b1,b2)*1 + + self.seq = np.append(self.seq,self.outbit).astype(int) + + self.state = np.r_[self.R1.state, self.R2.state,self.R3.state] + self.count+=1 + return self.outbit + def getSeq(self): + return ''.join(self.seq.copy().astype(str)) + def getState(self): + return ''.join(self.state.copy().astype(str)) + def arr2str(self,arr): + return ''.join(arr.copy().astype(str)) + + def runKCycle(self, k): + ''' + Run k cycles and update all the Parameters + + Parameters + ---------- + k : int + + Returns + ------- + tempseq : shape =(k,), output binary sequence of k cycles + ''' + tempseq = [self.next() for i in range(k)] + return np.array(tempseq) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/pylfsr/utils.py b/pylfsr/utils.py new file mode 100644 index 0000000..2c8ef3d --- /dev/null +++ b/pylfsr/utils.py @@ -0,0 +1,248 @@ +''' +Utilities for LFSR +--------------------------- +Author @ Nikesh Bajaj +Date: 03 Jan 2023 +Version : 1.0.7 +Github : https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register +Contact: n.bajaj@qmul.ac.uk +''' +from __future__ import absolute_import, division, print_function +name = "PyLFSR | utils" +import sys +import numpy as np +import functools, inspect, warnings + +if sys.version_info[:2] < (3, 3): + old_print = print + def print(*args, **kwargs): + flush = kwargs.pop('flush', False) + old_print(*args, **kwargs) + if flush: + file = kwargs.get('file', sys.stdout) + # Why might file=None? IDK, but it works for print(i, file=None) + file.flush() if file is not None else sys.stdout.flush() + + +string_types = (type(b''), type(u'')) + +def deprecated(reason): + """ + This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used. + """ + + if isinstance(reason, string_types): + + # The @deprecated is used with a 'reason'. + # + # .. code-block:: python + # + # @deprecated("please, use another function") + # def old_function(x, y): + # pass + + def decorator(func1): + + if inspect.isclass(func1): + fmt1 = "class {name} will be deprecated in future version, {reason}." + else: + fmt1 = "function {name} will be deprecated in future version, {reason}." + + @functools.wraps(func1) + def new_func1(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + fmt1.format(name=func1.__name__, reason=reason), + category=DeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', DeprecationWarning) + return func1(*args, **kwargs) + + return new_func1 + + return decorator + + elif inspect.isclass(reason) or inspect.isfunction(reason): + + # The @deprecated is used without any 'reason'. + # + # .. code-block:: python + # + # @deprecated + # def old_function(x, y): + # pass + + func2 = reason + + if inspect.isclass(func2): + fmt2 = "class {name} will be deprecated in future version." + else: + fmt2 = "function {name} will be deprecated in future version." + + @functools.wraps(func2) + def new_func2(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + fmt2.format(name=func2.__name__), + category=DeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', DeprecationWarning) + return func2(*args, **kwargs) + + return new_func2 + + else: + raise TypeError(repr(type(reason))) + +A=['\\','-','/','|'] + +def progbar(i,N,title='',style=2,L=100,selfTerminate=True,delta=None): + + pf = int(100*(i+1)/float(N)) + st = ' '*(3-len(str(pf))) + str(pf) +'%|' + + if L==50: + pb = '#'*int(pf//2)+' '*(L-int(pf//2))+'|' + else: + L = 100 + pb = '#'*pf+' '*(L-pf)+'|' + if style==1: + print(st+A[i%len(A)]+'|'+pb+title,end='\r', flush=True) + elif style==2: + print(st+pb+str(N)+'\\'+str(i+1)+'|'+title,end='\r', flush=True) + if pf>=100 and selfTerminate: + print('\nDone..') + +def print_list(L,n=3,sep='\t\t'): + L = [str(l) for l in L] + mlen = np.max([len(ll) for ll in L]) + for k in range(0,len(L)-n,n): + print(sep.join([L[ki] +' '*(mlen-len(L[ki])) for ki in range(k,k+n)])) + if k+n 1 and m < 32: + return fpolyList[m] + else: + print('Wrong input m. m should be int 1 < m < 32 or None. For greater than 32 order, please check following refrences') + #Ref: List of some primitive polynomial over GF(2)can be found at + print(" - http://www.partow.net/programming/polynomials/index.html") + print(" - http://www.ams.org/journals/mcom/1962-16-079/S0025-5718-1962-0148256-1/S0025-5718-1962-0148256-1.pdf") + print(" - http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf") + +def get_Ifpoly(fpoly): + ''' + Get image of feebback polynomial + Get the image of primitive polynomial + Parameters + ---------- + fpoly: polynomial as list e.g. [5,2] for x^5 + x^2 + 1 + : should be a valid primitive polynomial + + Returns + ------- + ifpoly: polynomial as list e.g. [5,3] for x^5 + x^3 + 1 + + ''' + if isinstance(fpoly, list) or (isinstance(fpoly, numpy.ndarray) and len(fpoly.shape)==1): + fpoly = list(fpoly) + fpoly.sort(reverse=True) + ifpoly = [fpoly[0]] +[fpoly[0]-ff for ff in fpoly[1:]] + ifpoly.sort(reverse=True) + return ifpoly + else: + print('Not a valid form of feedback polynomial') + +def lempel_ziv_patterns(seq): + r"""Lempel-Ziv patterns. + It is defined as a set of different patterns exists in a given sequence. + + As an example: + s = '1001111011000010' + patterns ==> 1, 0, 01, 11, 10, 110, 00, 010 + """ + + if isinstance(seq, (list,np.ndarray)): + seq = ''.join(seq.copy().astype(str)) + + patterns = set() + n = len(seq) + + i,k = 0, 1 + while i + k<=len(seq): + pattern = seq[i: i + k] + if pattern in patterns: + k += 1 + else: + patterns.add(pattern) + i += k + k = 1 + return patterns + +def lempel_ziv_complexity(seq): + r"""Lempel-Ziv Complexity. + It is defined as the number of different patterns exists in a given stream. + + As an example: + s = '1001111011000010' + patterns ==> 1, 0, 01, 11, 10, 110, 00, 010 + #patterns = 8 + """ + return len(lempel_ziv_patterns(seq)) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/requirements.txt b/requirements.txt index aa094d9..ae19c5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -numpy -matplotlib +numpy +matplotlib diff --git a/setup.py b/setup.py index 77eb3b1..a4485a1 100644 --- a/setup.py +++ b/setup.py @@ -1,48 +1,64 @@ -import setuptools -import os - -with open("README.md", "r") as fh: - long_description = fh.read() - -top_dir, _ = os.path.split(os.path.abspath(__file__)) - -with open(os.path.join(top_dir, 'Version')) as f: - version = f.readline().strip() - -setuptools.setup( - name="pylfsr", - version= version, - author="Nikesh Bajaj", - author_email="nikkeshbajaj@gmail.com", - description="Linear Feedback Shift Register", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register", - download_url = 'https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/tarball/' + version, - packages=setuptools.find_packages(), - license = 'MIT', - keywords = 'lfsr linear-feedback-shift-register random generator gf(2)', - classifiers=[ - "Programming Language :: Python :: 3", - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Natural Language :: English', - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Education', - 'Intended Audience :: Telecommunications Industry', - 'Topic :: Security :: Cryptography', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Games/Entertainment :: Puzzle Games', - 'Topic :: Communications', - - - ], - include_package_data=True, - install_requires=['numpy', 'matplotlib'] -) +import setuptools +import os + +with open("README.md", "r") as fh: + long_description = fh.read() + +top_dir, _ = os.path.split(os.path.abspath(__file__)) + +with open(os.path.join(top_dir, 'Version')) as f: + version = f.readline().strip() + +if os.path.isfile(os.path.join(top_dir, 'Version')): + with open(os.path.join(top_dir, 'Version')) as f: + version = f.readline().strip() +else: + import urllib + Vpath = 'https://raw.githubusercontent.com/Nikeshbajaj/Linear_Feedback_Shift_Register/master/Version' + version = urllib.request.urlopen(Vpath).read().strip().decode("utf-8") + +setuptools.setup( + name="pylfsr", + version= version, + author="Nikesh Bajaj", + author_email="nikkeshbajaj@gmail.com", + description="Linear Feedback Shift Register", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register", + download_url = 'https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/tarball/' + version, + packages=setuptools.find_packages(), + license = 'MIT', + keywords = 'lfsr linear-feedback-shift-register random generator gf(2)', + classifiers=[ + "Programming Language :: Python :: 3", + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Natural Language :: English', + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Education', + 'Intended Audience :: Telecommunications Industry', + 'Topic :: Security :: Cryptography', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Games/Entertainment :: Puzzle Games', + 'Topic :: Communications', + + 'Development Status :: 5 - Production/Stable', + + + ], + project_urls={ + 'Documentation': 'https://lfsr.readthedocs.io', + 'Say Thanks!': 'https://github.com/Nikeshbajaj', + 'Source': 'https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register', + 'Tracker': 'https://github.com/Nikeshbajaj/Linear_Feedback_Shift_Register/issues', + }, + include_package_data=True, + install_requires=['numpy', 'matplotlib'] +)