vendredi 26 juin 2020

How to (actually) correctly implement a binary symmetric channel in Python?

I'm looking to implement a binary symmetric channel in Python, preferably using Numpy. I want my results to be reproducible (i.e. depend on a seed of my choice only) and also a solution that, while it need not be state of the art in this regard, wouldn't make a cryptographer bang his head against the wall (to be slightly more precise, I'm looking for something at least as good as a Mersenne Twister in terms of statistical properties). Of course it's always nice to have a solution that's efficient.

I am aware of this topic with the same name, the answer to which uses floating point comparison together with numpy.random.random(). I'm certainly no expert but I believe our poor cryptographer will get a bad headache from this, even though such a solution might be fine for most practical applications.

There is another reason why a new answer to this question is in order: in modern Numpy, the use of numpy.random.random and similar functions is discouraged. Rather, one should use a 'numpy.random.Generator' instance to draw random numbers from, allowing one to choose a suitable PRNG. However, this introduces the difficulty of combining my (perhaps stupid) wish of being able to fix a seed for each individual use of the BSC as well as not having to instantiate a random number generator every time (which is computationally expensive). How does one do this correctly?

Just to show roughly what I'm trying to do, here are two very naive, inefficient, old-fashioined and cryptographically dubious solutions:

# (tested with python 3.7.7 and numpy 1.18.1)
import numpy as np


def worse_BSC(bits, err_rate: float, seed: int):
    """
    Binary symmetric channel
    :param bits: Numpy array of booleans 
        (obviously booleans are not the best choice, I'm not sure how terrible it is exactly)
    :param err_rate: floating point number between 0. and 1. inclusive.
    :param seed: Random seed
    :return: Numpy array. Bits subjected to Binary symmetric channel.
    """
    np.random.seed(seed)  # bad: using a legacy function and setting a global state.
    return bits ^ np.random.choice(a=[True, False], size=bits.shape, p=[err_rate, 1 - err_rate])

# -----------------------------------------------------------------
from numpy.random import MT19937, RandomState, SeedSequence


def bad_BSC(bits, err_rate: float, seed: int):
    """Everything as above. Is this ok? Does it have a huge unnecessary performance penalty?"""
    rs = RandomState(MT19937(SeedSequence(seed)))  # np.random.seed doc says this is "recommended".
    # Pycharm complains for some reason that MT19937 has an unexpected type (the code works of course)
    return bits ^ rs.choice(a=[True, False], size=bits.shape, p=[err_rate, 1 - err_rate])



Aucun commentaire:

Enregistrer un commentaire