Created
June 17, 2022 17:23
-
-
Save ItsDrike/ecb6a062a72f724fcf1d442922a0d939 to your computer and use it in GitHub Desktop.
Linear Feedback Shift Register (LSFR) random number generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# Linear Feedback Shift Register (LFSR) Random Number generator We should be | |
# able to go through all of the possible states from our initial one before we | |
# start repeating. This means that we can have 2^n-1 unique numbers, n being | |
# the number of bits in our state before we start repeating numbers. | |
# Warning: This algorithm is NOT cryptographically secure, because given enough | |
# outputted bytes, by solving a bunch of linear equations and recompute the | |
# LFSR generator bits. If we do need something cryptographically secure, we | |
# could combine 2 LFSR streams XORed together, and they don't always shift at | |
# the same time, we could achieve something a lot more chaotic and very hard to | |
# predict. | |
def pcb(number: int): | |
""" | |
A debug function for printing a number represented in a binary form, in a | |
compressed manner, this is only really useful if the number is expected to | |
have a lot of repeated bits (100000000001 would be 1x1+10x0+1x1) | |
""" | |
compressed_binary = [] | |
binary_form = bin(number)[2:] | |
last_bit = binary_form[0] # To avoid 0x0 or 0x1 always use starting last bit | |
bit_amount = 0 | |
for bit in binary_form: | |
if bit == last_bit: | |
bit_amount += 1 | |
else: | |
compressed_binary.append(f"{bit_amount}x{last_bit}") | |
last_bit = bit | |
bit_amount = 1 | |
compressed_binary.append(f"{bit_amount}x{last_bit}") | |
return "+".join(compressed_binary) | |
def lfsr(state: int, number_size: int, taps: list[int]) -> int: | |
""" | |
LFSR random number generator algorithm. | |
- `state` is the initial "seed" of our algorithm | |
- `taps` are the bit positions which will be used in the XOR | |
- `number_size` is the size for the number to be generated in bits | |
The returned number is composed of `number_size` of bits, produced by the | |
algorithm given the `state` and `taps`. | |
""" | |
output = 0 | |
state_binary_length = len(bin(state)) - 2 | |
max_nonrepeating = 2 ** state_binary_length - 1 | |
if number_size > max_nonrepeating: | |
print( | |
f"Warning, with {state_binary_length} binary state length, you " | |
f"only have {max_nonrepeating} non-repeating random numbers, you " | |
f"requested {number_size} numbers, which means some of the bits in it " | |
"will follow a repeated sequence." | |
) | |
for i in range(number_size): | |
# Get the first bit, which will be our random output | |
rand = state & 1 | |
# Print the state and bit output for debugging purposes | |
print(f"#{i + 1} {state=:04b}, out={rand:01b}") | |
# Leftshift the output to make space for a new ending bit and use OR to | |
# set the bit depending on rand | |
output = (output << 1) | rand | |
# Take the XOR of the tapped bits by running XOR on the state and the | |
# right-shifted state, and taking the last digit of that. | |
# For example: | |
# 1001 is our original state | |
# 0100 is the right bitshifted state | |
# 1101 is the XOR result for taps=[1] (works for state=0b1001) | |
newbit = state | |
for tap_bit_pos in taps: | |
newbit = newbit ^ (state >> tap_bit_pos) | |
# We can now use result & 1 to only obtain the last bit allowing us to | |
# only run XOR between last 2 bits of current state | |
newbit = newbit & 1 | |
# Bitshift the state to the right and set the first bit of it to the | |
# computed newbit value | |
state = (state >> 1) | (newbit << (state_binary_length - 1)) | |
return output | |
# Define some parameters to be used for the LFSR function | |
# The state used in the start, can't be only zeroes | |
#start_state = 0b1001 | |
# For state of 1001, the taps (positions to get XORed) are just [1] | |
# taps = [1] | |
# Will produce 1000...0001 of 128 bits long guaranteeing 2^128 - 1 | |
# non-repeating pseudo-random number sequence | |
start_state = (1 << 127) | 1 | |
taps = [1, 2, 7] | |
# The desired size (in bits) of the generated random number | |
number_size = 24 | |
if __name__ == "__main__": | |
out = lfsr(start_state, number_size, taps=taps) | |
binary_out = f"{out:b}".zfill(number_size) | |
print(f"Obtained random number: {out} (binary form: {binary_out}") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment