blob: d74b98d5dc983c289e20cca966e84dbdcb93b61e [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""Generating random variables by using an entropy buffer.
A class for generating random numbers and permutations, by fetching entropy
from a file.
"""
import logging as log
import sys
from math import ceil as _ceil
from math import log as _log
class strong_random():
def __init__(self):
"""Initialize an instance.
Entropy buffer is empty by default.
"""
self.buffer = []
self.buffername = ""
def load(self, input_file):
"""Load entropy buffer from an external file.
Currently only supports numpy array of 8-bit values.
"""
if self.buffername == input_file:
log.error("Entropy buffer " + input_file + " can't be loaded twice.")
sys.exit(1)
with open(input_file, 'r') as fp:
for line in fp:
x = int(line)
if x in range(256):
self.buffer.append(x)
else:
log.error("Read value not in range 0 - 255.")
sys.exit(1)
self.buffername = input_file
def size(self):
"""Size of the buffer.
"""
return len(self.buffer)
def printstatus(self):
"""Prints the number of remaining bytes in the buffer.
May be useful for testing and debugging.
"""
if self.isempty():
print('Buffer is empty.')
else:
print("Buffer remaining:", len(self.buffer), "bytes.")
def isempty(self):
"""Checks if the buffer is empty.
"""
return (not self.buffer)
def fetchbyte(self):
"""Fetches the next byte from the buffer.
Used by getrandbits().
If the buffer is already empty, raises an error and exits.
"""
if self.isempty():
log.error("Entropy buffer empty.")
sys.exit(1)
else:
random_byte = self.buffer.pop(0)
return (random_byte)
def getrandbits(self, n_bits):
"""Fetches n_bits next bits from a buffer.
Always fetches a whole number of bytes. Unused bits are discarded.
"""
bitsleft = n_bits
R = 0
while (bitsleft > 0):
random_byte = int(self.fetchbyte())
if (bitsleft > 8):
R = (R << 8) | random_byte
bitsleft = bitsleft - 8
else:
random_byte >>= 8 - bitsleft
R = (R << bitsleft) | random_byte
bitsleft = 0
assert R >= 0
return (R)
def randbelow(self, n):
"""Generates a random integer smaller than n.
Uses getrandbits() to generate enough random bits. Repeats until the
resulting number is smaller than n. The probability of this is more
than 50% in each loop iteration.
The amount of consumed randomness is not fixed.
"""
length_bits = _ceil(_log(n, 2))
R = self.getrandbits(length_bits)
while R >= n:
R = self.getrandbits(length_bits)
return (R)
def shuffle(self, x):
"""Shuffles a list x.
Uses the Fisher-Yates algorithm.
Each possible permutation of x is generated with equal probability.
"""
length_list = len(x)
for i in reversed(range(length_list)):
j = self.randbelow(i + 1)
x[i], x[j] = x[j], x[i]
# ----------------------------------------------------------------------
# Create one instance, and export its methods as module-level functions.
# The functions share state across all uses.
_inst = strong_random()
load = _inst.load
size = _inst.size
isempty = _inst.isempty
printstatus = _inst.printstatus
getrandbits = _inst.getrandbits
randbelow = _inst.randbelow
shuffle = _inst.shuffle