TrueRNG v3を使って[0,1)の一様乱数を生成する
TrueRNG v3を使って0から1の一様乱数を生成します。
TrueRNG v3

TrueRNGはハードウェア乱数生成器です。
半導体接合のアバランシェ効果を使用して、真の乱数を生成します。 生成速度が400 kbits/secondを超える低コストのUSBハードウェア乱数ジェネレーターです。
動作確認
公式ページのコード1とTrueRNG Utilities2を参考に動作を確認します。
外部モジュールとして pySerial を使います。
from serial import Serial
from serial.tools import list_portsデバイスの接続確認
シリアルポート一覧からデバイスが認識しているかを確認します。
ports_avaiable = list_ports.comports()
戻り値はListPortInfoオブジェクトを含むリストです。
このオブジェクトはシリアルポートに関する情報を持っています。[*1]
デバイスの確認には.hwidを使います。TrueRNGはVID:PID=04D8:F5FEらしいです。
また、ポートの確認には.deviceを使います。(.portは存在しません。)
devices = [p.device for p in ports_avaiable if '04D8:F5FE' in p.hwid]
port = devices[0] if devices else None
複数のデバイスを検知した場合に備えdevice[0]でアクセスしています。
list_ports.comports()による返り値は特定の順番で返ってくる訳ではないので注意が必要です。
動作確認
serial.Serialを使ってTrueRNGとシリアル通信を行っていきます。
with Serial(port=port, timeout=10, dsrdtr=True) as ser:
# Open the serial port if it isn't open
if not ser.isOpen():
ser.open()
ser.reset_input_buffer() # ノイズデータがある場合があるのでバッファをクリア
try:
before = time.perf_counter()
x = ser.read(102400) # <- (1) データの取得
after = time.perf_counter()
except:
print('Read Failed!')
else: # then
kbps = 8*len(x) / ((after - before) * 1000)
print(f'{round(kbps, 2)} kbit/s')
(1) TrueRNGからread()を使って、値を取得します。bytesが返ってきます。
b'\xf2\xb62$\x94y .... \x88'
100回の試行結果をまとめました。
| func | kbit/s |
|---|---|
| mean | 403.106 |
| max | 403.180 |
| min | 403.032 |
生成速度が400kbit/sを超えると書いてあるので、読み取り自体は問題なさそうです。
乱数の生成
0から1の一様乱数を生成します。
TrueRNGから得られる値はbytesであり、これを変換していく必要があります。
今回はrandom.SystemRandomを参考にしていきます。
SystemRandom
random.SystemRandom.randomは0から1の一様乱数を返します。
print(SystemRandom().random())
>>> 0.6643365002539736
random.SystemRandomの実装を確認します。
# random.py
...
from os import urandom as _urandom
...
BPF = 53 # Number of bits in a float
RECIP_BPF = 2**-BPF
...
class SystemRandom(Random):
def random(self):
return (int.from_bytes(_urandom(7), 'big') >> 3) * RECIP_BPF # <- (1)
(1) os.urandomを使用し、乱数の生成を行っていることがわかります。
実装
os.urandom(size)はsize バイトからなるランダムな文字列(bytes)を返します。
つまり、os.urandom(7)の代わりにser.read(7)することで、0から1の一様乱数を生成できます。
with Serial(port=self.port,timeout=1, dsrdtr=True) as ser:
...
data = ser.read(7)
r = (int.from_bytes(data, 'big') >> 3) * 2**-53
print(r)
>>> 0.23952311969869522解説
(int.from_bytes(_urandom(7), 'big') >> 3) * 2**-53
これを少し書き換えます。(見やすくするために無駄なスペースを使用しています。)
1 value_raw = _urandom(7)
2 value_int = int.from_bytes(value_raw, 'big')
3 value_shift = value_int >> 3
4 value_rand = value_shift * 2**-53
-
_urandom(7)を使って、7byte(=56bit)の値を回収します。 -
int.from_bytesを使って、バイト列(value_raw)の整数表現を回収します。 -
>>3を使って、3bit分右にシフトします。これで53bitになっています。(53bitなので 0~9007199254740991 の整数になるはずです。)
-
2**-53[*2]を掛ける(2**53で割る)ことで、乱数の範囲を[0,1)にします。
value_raw -> b'\xdaL@\xf5dM\xff'
value_int -> 61445386801532415 -> 11011010010011000100000011110101011001000100110111111111
value_shift -> 7680673350191551 -> 11011010010011000100000011110101011001000100110111111
value_rand -> 0.8527260398007498
doubleにおける仮数部の精度は53ビットであるため、53bit全てを乱数で埋め、シフト>>3と掛け算*2**-53をおこなっていると考えられます。[*2]
おまけ
ListPortInfoのインデックス
下位互換性のためにインデックスを使って、3個(port, desc, hwid)だけにアクセスできます。
# serial/tools/list_ports_common.py
def __getitem__(self, index):
"""Item access: backwards compatible -> (port, desc, hwid)"""
if index == 0:
return self.device
elif index == 1:
return self.description
elif index == 2:
return self.hwid
else:
raise IndexError('{} > 2'.format(index))2**53
2**53 -> 100000000000000000000000000000000000000000000000000000
2**53 - 1 -> 11111111111111111111111111111111111111111111111111111
15. 浮動小数点演算、その問題と制限 | Python ドキュメント
os.urandom
os.urandomはOSによって動作が異なります。
random.SystemRandom.randomには以下のような説明があります。
such as /dev/urandom on Unix or CryptGenRandom on Windows
この動作については、stackoverflowの回答に分かりやすい説明がありました。
How exactly does random.random() work in python? | stackoverflow
os.urandomとrandom.randomについては次の記事に書きます。(多分)
ホワイトニング
TrueRNG v3ではホワイトニングが改善されているそうです。
...解析の手がかりとなるパターンをそのデータから取り除いておきたい。パターンを取り除く処理はホワイトニングと呼ばれる。
C/C++セキュアプログラミングクックブック VOLUME 2
v2ではXORが使用されていたようです。
What’s the whitening algorithm? | ubld.it
v3で改善されたと書かれていますが、実際に何が使用されているかを発見できませんでした。
フォーラムにv3のホワイトニングについて質問がありましたが、回答はありませんでした。
Supported Modes
TrueRNG pro系はモード変更が可能ですが、TrueRNGは変更できないようです。
TrueRNGproの商品ページにサポートしているモードが記載されています。
TrueRNGpro – USB Random Number Generator | ubld.it
RAW Binaryモードを使用することでホワイトニングされていない値を取得できるようです。
参考文献
2016年にPython2系で書かれたコードです。TrueRNGpro用のコードです。 How-to use TrueRNGpro with Python in Windows 10 and Linux | ubld.it
Python3系で書かれたコードです。公式ページにGitHubへのリンクがありました。TrueRNG Utilities | GitHub