pool_of_entropy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +7 -0
- data/DIEHARDER_TEST.md +156 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +151 -0
- data/Rakefile +10 -0
- data/lib/pool_of_entropy.rb +160 -0
- data/lib/pool_of_entropy/core_prng.rb +161 -0
- data/lib/pool_of_entropy/version.rb +3 -0
- data/pool_of_entropy.gemspec +26 -0
- data/spec/core_prng_spec.rb +541 -0
- data/spec/pool_of_entropy_spec.rb +362 -0
- data/spec/spec_helper.rb +7 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 455d13c29630cf9a7255e9d4f76d1ee8ac7dd32b
|
4
|
+
data.tar.gz: 4b61829c13593ed3aa534d6ca462b04ca7139b66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f14bf6fdfc0466312c67608ca9bbb966ce5fbef1166d690d8baef622d4311fa9d8fa473bf5f5d292fdef38da9e8e08a5f2ef39ca20de677d59a9bb8ba9e6233f
|
7
|
+
data.tar.gz: acc6a94d9bd4f2e436d783489863fcf0e9bd97c3be5f25033b676aefdde9f6136f7e3c58bfd7013e845f3530a4cfa6aa718b3c9261683f88d1f68c972af959d2
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/DIEHARDER_TEST.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# Results of dieharder randomness test
|
2
|
+
|
3
|
+
See http://www.phy.duke.edu/~rgb/General/dieharder.php for details about
|
4
|
+
the dieharder utility.
|
5
|
+
|
6
|
+
The test consumes a large amount of data, several orders of magnitude
|
7
|
+
more than the target use of PoolOfEntropy. It is a thorough test
|
8
|
+
for hidden bias, short cycles and patterns that may occur as faults
|
9
|
+
in "weak" PRNGs. No test of randomness can be 100% certain though.
|
10
|
+
|
11
|
+
The test PASS received implies PoolOfEntropy is free from a variety
|
12
|
+
of detectable faults that would prevent it being used as a source of
|
13
|
+
statistically random numbers. Not all PRNGs pass these tests -
|
14
|
+
for instance, C's standard library rand() fails most of them.
|
15
|
+
All modern cryptographic PRNGs should be expected to pass,
|
16
|
+
as should Ruby's built-in rand() - an implementation of the Mersenne
|
17
|
+
Twister algorithm.
|
18
|
+
|
19
|
+
## Date run: 9 May 2014
|
20
|
+
|
21
|
+
Completing all the tests took roughly 3 days.
|
22
|
+
|
23
|
+
## Command used
|
24
|
+
|
25
|
+
PoolOfEntropy::CorePRNG was used in its default, simplest state to
|
26
|
+
create a continuous stream of pseudo-random bytes, and this was
|
27
|
+
fed into the dieharder test running the "all tests" option:
|
28
|
+
|
29
|
+
ruby -rpool_of_entropy -e \
|
30
|
+
'r=PoolOfEntropy::CorePRNG.new;loop do;print r.read_bytes;end' \
|
31
|
+
| dieharder -g 200 -a
|
32
|
+
|
33
|
+
## Report from dieharder
|
34
|
+
|
35
|
+
#=============================================================================#
|
36
|
+
# dieharder version 3.31.1 Copyright 2003 Robert G. Brown #
|
37
|
+
#=============================================================================#
|
38
|
+
rng_name |rands/second| Seed |
|
39
|
+
stdin_input_raw| 2.40e+05 |3475765104|
|
40
|
+
#=============================================================================#
|
41
|
+
test_name |ntup| tsamples |psamples| p-value |Assessment
|
42
|
+
#=============================================================================#
|
43
|
+
diehard_birthdays| 0| 100| 100|0.70127033| PASSED
|
44
|
+
diehard_operm5| 0| 1000000| 100|0.49443406| PASSED
|
45
|
+
diehard_rank_32x32| 0| 40000| 100|0.58788457| PASSED
|
46
|
+
diehard_rank_6x8| 0| 100000| 100|0.08410111| PASSED
|
47
|
+
diehard_bitstream| 0| 2097152| 100|0.97953152| PASSED
|
48
|
+
diehard_opso| 0| 2097152| 100|0.97485622| PASSED
|
49
|
+
diehard_oqso| 0| 2097152| 100|0.70397562| PASSED
|
50
|
+
diehard_dna| 0| 2097152| 100|0.76551419| PASSED
|
51
|
+
diehard_count_1s_str| 0| 256000| 100|0.95727516| PASSED
|
52
|
+
diehard_count_1s_byt| 0| 256000| 100|0.42576929| PASSED
|
53
|
+
diehard_parking_lot| 0| 12000| 100|0.08478625| PASSED
|
54
|
+
diehard_2dsphere| 2| 8000| 100|0.27022174| PASSED
|
55
|
+
diehard_3dsphere| 3| 4000| 100|0.37750039| PASSED
|
56
|
+
diehard_squeeze| 0| 100000| 100|0.84447931| PASSED
|
57
|
+
diehard_sums| 0| 100| 100|0.07148373| PASSED
|
58
|
+
diehard_runs| 0| 100000| 100|0.84129944| PASSED
|
59
|
+
diehard_runs| 0| 100000| 100|0.01135930| PASSED
|
60
|
+
diehard_craps| 0| 200000| 100|0.08034019| PASSED
|
61
|
+
diehard_craps| 0| 200000| 100|0.62929296| PASSED
|
62
|
+
marsaglia_tsang_gcd| 0| 10000000| 100|0.56434071| PASSED
|
63
|
+
marsaglia_tsang_gcd| 0| 10000000| 100|0.28872723| PASSED
|
64
|
+
sts_monobit| 1| 100000| 100|0.80991806| PASSED
|
65
|
+
sts_runs| 2| 100000| 100|0.91086458| PASSED
|
66
|
+
sts_serial| 1| 100000| 100|0.88204963| PASSED
|
67
|
+
sts_serial| 2| 100000| 100|0.77896528| PASSED
|
68
|
+
sts_serial| 3| 100000| 100|0.99012882| PASSED
|
69
|
+
sts_serial| 3| 100000| 100|0.57917534| PASSED
|
70
|
+
sts_serial| 4| 100000| 100|0.29749662| PASSED
|
71
|
+
sts_serial| 4| 100000| 100|0.91618340| PASSED
|
72
|
+
sts_serial| 5| 100000| 100|0.61310658| PASSED
|
73
|
+
sts_serial| 5| 100000| 100|0.70187732| PASSED
|
74
|
+
sts_serial| 6| 100000| 100|0.63483611| PASSED
|
75
|
+
sts_serial| 6| 100000| 100|0.63638375| PASSED
|
76
|
+
sts_serial| 7| 100000| 100|0.87933422| PASSED
|
77
|
+
sts_serial| 7| 100000| 100|0.92340602| PASSED
|
78
|
+
sts_serial| 8| 100000| 100|0.08379368| PASSED
|
79
|
+
sts_serial| 8| 100000| 100|0.30058991| PASSED
|
80
|
+
sts_serial| 9| 100000| 100|0.47909085| PASSED
|
81
|
+
sts_serial| 9| 100000| 100|0.99388217| PASSED
|
82
|
+
sts_serial| 10| 100000| 100|0.62723409| PASSED
|
83
|
+
sts_serial| 10| 100000| 100|0.04085355| PASSED
|
84
|
+
sts_serial| 11| 100000| 100|0.40703003| PASSED
|
85
|
+
sts_serial| 11| 100000| 100|0.07446698| PASSED
|
86
|
+
sts_serial| 12| 100000| 100|0.45945558| PASSED
|
87
|
+
sts_serial| 12| 100000| 100|0.50603459| PASSED
|
88
|
+
sts_serial| 13| 100000| 100|0.19977550| PASSED
|
89
|
+
sts_serial| 13| 100000| 100|0.22347592| PASSED
|
90
|
+
sts_serial| 14| 100000| 100|0.68014498| PASSED
|
91
|
+
sts_serial| 14| 100000| 100|0.38635200| PASSED
|
92
|
+
sts_serial| 15| 100000| 100|0.44640327| PASSED
|
93
|
+
sts_serial| 15| 100000| 100|0.65586709| PASSED
|
94
|
+
sts_serial| 16| 100000| 100|0.97546459| PASSED
|
95
|
+
sts_serial| 16| 100000| 100|0.84301307| PASSED
|
96
|
+
rgb_bitdist| 1| 100000| 100|0.28629993| PASSED
|
97
|
+
rgb_bitdist| 2| 100000| 100|0.56483445| PASSED
|
98
|
+
rgb_bitdist| 3| 100000| 100|0.62133888| PASSED
|
99
|
+
rgb_bitdist| 4| 100000| 100|0.81437794| PASSED
|
100
|
+
rgb_bitdist| 5| 100000| 100|0.98632962| PASSED
|
101
|
+
rgb_bitdist| 6| 100000| 100|0.97669342| PASSED
|
102
|
+
rgb_bitdist| 7| 100000| 100|0.30828120| PASSED
|
103
|
+
rgb_bitdist| 8| 100000| 100|0.66955561| PASSED
|
104
|
+
rgb_bitdist| 9| 100000| 100|0.94528798| PASSED
|
105
|
+
rgb_bitdist| 10| 100000| 100|0.90457257| PASSED
|
106
|
+
rgb_bitdist| 11| 100000| 100|0.85673195| PASSED
|
107
|
+
rgb_bitdist| 12| 100000| 100|0.22076625| PASSED
|
108
|
+
rgb_minimum_distance| 2| 10000| 1000|0.53379172| PASSED
|
109
|
+
rgb_minimum_distance| 3| 10000| 1000|0.43597963| PASSED
|
110
|
+
rgb_minimum_distance| 4| 10000| 1000|0.31259419| PASSED
|
111
|
+
rgb_minimum_distance| 5| 10000| 1000|0.61615392| PASSED
|
112
|
+
rgb_permutations| 2| 100000| 100|0.95732489| PASSED
|
113
|
+
rgb_permutations| 3| 100000| 100|0.78107574| PASSED
|
114
|
+
rgb_permutations| 4| 100000| 100|0.64034376| PASSED
|
115
|
+
rgb_permutations| 5| 100000| 100|0.89199822| PASSED
|
116
|
+
rgb_lagged_sum| 0| 1000000| 100|0.63905487| PASSED
|
117
|
+
rgb_lagged_sum| 1| 1000000| 100|0.16144850| PASSED
|
118
|
+
rgb_lagged_sum| 2| 1000000| 100|0.66997505| PASSED
|
119
|
+
rgb_lagged_sum| 3| 1000000| 100|0.54744094| PASSED
|
120
|
+
rgb_lagged_sum| 4| 1000000| 100|0.15684876| PASSED
|
121
|
+
rgb_lagged_sum| 5| 1000000| 100|0.06950708| PASSED
|
122
|
+
rgb_lagged_sum| 6| 1000000| 100|0.04118395| PASSED
|
123
|
+
rgb_lagged_sum| 7| 1000000| 100|0.62558494| PASSED
|
124
|
+
rgb_lagged_sum| 8| 1000000| 100|0.06955632| PASSED
|
125
|
+
rgb_lagged_sum| 9| 1000000| 100|0.80323801| PASSED
|
126
|
+
rgb_lagged_sum| 10| 1000000| 100|0.95324347| PASSED
|
127
|
+
rgb_lagged_sum| 11| 1000000| 100|0.92104340| PASSED
|
128
|
+
rgb_lagged_sum| 12| 1000000| 100|0.68759225| PASSED
|
129
|
+
rgb_lagged_sum| 13| 1000000| 100|0.21127858| PASSED
|
130
|
+
rgb_lagged_sum| 14| 1000000| 100|0.97290617| PASSED
|
131
|
+
rgb_lagged_sum| 15| 1000000| 100|0.33770624| PASSED
|
132
|
+
rgb_lagged_sum| 16| 1000000| 100|0.14037461| PASSED
|
133
|
+
rgb_lagged_sum| 17| 1000000| 100|0.42891060| PASSED
|
134
|
+
rgb_lagged_sum| 18| 1000000| 100|0.01741981| PASSED
|
135
|
+
rgb_lagged_sum| 19| 1000000| 100|0.01825942| PASSED
|
136
|
+
rgb_lagged_sum| 20| 1000000| 100|0.82574915| PASSED
|
137
|
+
rgb_lagged_sum| 21| 1000000| 100|0.55886738| PASSED
|
138
|
+
rgb_lagged_sum| 22| 1000000| 100|0.96301438| PASSED
|
139
|
+
rgb_lagged_sum| 23| 1000000| 100|0.35504422| PASSED
|
140
|
+
rgb_lagged_sum| 24| 1000000| 100|0.86742325| PASSED
|
141
|
+
rgb_lagged_sum| 25| 1000000| 100|0.97050104| PASSED
|
142
|
+
rgb_lagged_sum| 26| 1000000| 100|0.45785583| PASSED
|
143
|
+
rgb_lagged_sum| 27| 1000000| 100|0.91905828| PASSED
|
144
|
+
rgb_lagged_sum| 28| 1000000| 100|0.53348048| PASSED
|
145
|
+
rgb_lagged_sum| 29| 1000000| 100|0.19985210| PASSED
|
146
|
+
rgb_lagged_sum| 30| 1000000| 100|0.63540309| PASSED
|
147
|
+
rgb_lagged_sum| 31| 1000000| 100|0.88671466| PASSED
|
148
|
+
rgb_lagged_sum| 32| 1000000| 100|0.87499608| PASSED
|
149
|
+
rgb_kstest_test| 0| 10000| 1000|0.94490633| PASSED
|
150
|
+
dab_bytedistrib| 0| 51200000| 1|0.21636554| PASSED
|
151
|
+
dab_dct| 256| 50000| 1|0.35659461| PASSED
|
152
|
+
dab_filltree| 32| 15000000| 1|0.49053255| PASSED
|
153
|
+
dab_filltree| 32| 15000000| 1|0.24508716| PASSED
|
154
|
+
dab_filltree2| 0| 5000000| 1|0.27633380| PASSED
|
155
|
+
dab_filltree2| 1| 5000000| 1|0.93106328| PASSED
|
156
|
+
dab_monobit2| 12| 65000000| 1|0.21748673| PASSED
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Neil Slater
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# PoolOfEntropy
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/pool_of_entropy.png)](http://badge.fury.io/rb/pool_of_entropy)
|
3
|
+
[![Build Status](https://travis-ci.org/neilslater/pool_of_entropy.png?branch=master)](http://travis-ci.org/neilslater/pool_of_entropy)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/neilslater/pool_of_entropy/badge.png?branch=master)](https://coveralls.io/r/neilslater/pool_of_entropy?branch=master)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/neilslater/pool_of_entropy.png)](https://codeclimate.com/github/neilslater/pool_of_entropy)
|
6
|
+
[![Dependency Status](https://gemnasium.com/neilslater/pool_of_entropy.png)](https://gemnasium.com/neilslater/pool_of_entropy)
|
7
|
+
|
8
|
+
PoolOfEntropy is a pseudo random number generator (PRNG) based on secure hashes,
|
9
|
+
intended to bring back the feeling of 'personal luck' that some gamers may feel when rolling
|
10
|
+
their *own* dice. An instance of the PoolOfEntropy class could be assigned to a player, or
|
11
|
+
to each die in a game, and it can be influenced (similar to throwing a die differently), or
|
12
|
+
personalised by feeding in arbitrary data (e.g. a picture of the player, a favourite saying).
|
13
|
+
It can handle these influences whilst remaining unbiased and fair on each roll.
|
14
|
+
|
15
|
+
PoolOfEntropy is *probably* secure when used appropriately. However, cryptographic security is
|
16
|
+
not its purpose. The core purpose is for playing with random number generation and non-standard
|
17
|
+
sources of entropy. The choice of name is supposed to reflect this.
|
18
|
+
|
19
|
+
If you are looking for a secure PRNG in Ruby, good for generating session codes or
|
20
|
+
server-side secrets, use the standard library SecureRandom.
|
21
|
+
|
22
|
+
If you think that rolling all your dice on an anonymous server has removed a little bit of soul
|
23
|
+
from your game sessions, or if you want to generate unbiased random numbers using input from your
|
24
|
+
laptop's microphone or mobile's accellerometer as a source, then PoolOfEntropy might be for you.
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
gem 'pool_of_entropy'
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install pool_of_entropy
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
Create a new generator:
|
43
|
+
|
44
|
+
pool = PoolOfEntropy.new
|
45
|
+
|
46
|
+
Get a random number:
|
47
|
+
|
48
|
+
pool.rand( 20 )
|
49
|
+
|
50
|
+
Influence the next random number (but not any others):
|
51
|
+
|
52
|
+
pool.modify_next( 'I hope this works! Whatever!' )
|
53
|
+
pool.rand( 20 )
|
54
|
+
|
55
|
+
# Also
|
56
|
+
pool.modify_next( 'I hope this works! Whatever!' ).rand( 20 )
|
57
|
+
|
58
|
+
Influence all random numbers until change mind (but do not alter internal state):
|
59
|
+
|
60
|
+
pool.modify_all( 'Gawds help me in my hour of need!' )
|
61
|
+
|
62
|
+
# All these are modified in same way, the two modifier types "stack"
|
63
|
+
pool.rand( 20 )
|
64
|
+
pool.rand( 20 )
|
65
|
+
pool.modify_next( 'And I really mean it!' ).rand( 20 )
|
66
|
+
|
67
|
+
Alter internal state of pool (aka customise or "collect entropy"):
|
68
|
+
|
69
|
+
pool.update_state( 'Purple is my favourite colour.' )
|
70
|
+
|
71
|
+
## Rationale
|
72
|
+
|
73
|
+
### Properties of Software Random Numbers
|
74
|
+
|
75
|
+
Software PRNGs available are designed to produce data that cannot be statistically separated
|
76
|
+
from an ideal unbiased "truly random" source. There are quite a few algorithms that can do that,
|
77
|
+
with differing degrees of success. Current best-in-class generators are pretty good at creating
|
78
|
+
psuedo random data that has no discernable pattern.
|
79
|
+
|
80
|
+
Generators used for games also need another trait - they need to be unpredictable to the end users.
|
81
|
+
Often that is not strictly true in the academic sense, for example a well-informed user with enough
|
82
|
+
time and skill could predict the next output from Ruby's rand() method. However, when this really
|
83
|
+
needs to be true, you can use a Crytogaphically Secure PRNG (CSPRNG). Ruby's SecureRandom cannot
|
84
|
+
be predicted from outside the system. Part of how CSPRNGs achieve unpredicatbility is by collecting
|
85
|
+
entropy from sources within the computer - this might be timing of events from network cards and
|
86
|
+
keyboard presses, or by sampling from deliberately noisy circuits.
|
87
|
+
|
88
|
+
### Properties of Dice
|
89
|
+
|
90
|
+
A humble 6-sided die achieves similar statistics and unpredictability, but in a different way.
|
91
|
+
Unbiased statistical randomness is achieved by making the shape and density as regular as possible.
|
92
|
+
It also assumes the user has "rolled well enough", which is quite tricky to define, but obviously just
|
93
|
+
placing the die with the number you want facing up does not count.
|
94
|
+
|
95
|
+
Unpredictability for the physical die comes from lack of precise control. Imperfections and
|
96
|
+
microscopic details of where the die is rolling have a large impact. Quantum mechanics may
|
97
|
+
also have an impact if a die collides and bounces enough. Also, a huge influence is how the
|
98
|
+
die is thrown, and that comes from the person throwing it. No-one can control their nerves
|
99
|
+
and muscles to a degree where they "roll well enough" but can consciously choose the
|
100
|
+
exact result on the die. However, the impulse you give to a die when you throw it caused
|
101
|
+
by your nerves, bones and muscles. This gives many people a feeling of agency and relationship
|
102
|
+
to the end result. It may just be a random number, but in some sense it is *your random
|
103
|
+
number because you generated it*.
|
104
|
+
|
105
|
+
### Normal PRNGs Used To Simulate Dice
|
106
|
+
|
107
|
+
When it comes to finding computer-based sources of randomness, you will find many systems
|
108
|
+
that excel at producing results that are statistically random. Ruby's rand() is already
|
109
|
+
very good at that. Computer-based PRNGs can apparently be made closer to ideal unbiased
|
110
|
+
randomness than physical dice (or at least beyond any realistic ability to measure it).
|
111
|
+
|
112
|
+
Truly unpredictable sources are also easy enough to find. They make themselves unpredictable
|
113
|
+
by collecting entropy from sources on the machines where they run, that no-one can predict.
|
114
|
+
|
115
|
+
However, there has been a cost to the user's agency. If I was playing a game
|
116
|
+
using one of these sources, even though it was fair in the sense that the outcomes could
|
117
|
+
well be the same, it gives me the same feeling as if another player was rolling all the dice.
|
118
|
+
In a role-playing game, it feels the same as if the DM was rolling all the dice. Now sometimes
|
119
|
+
and for some (many/most?) people that's OK. But other times, part of the fun is in rolling
|
120
|
+
the dice yourself. I would be happy rolling computer dice, but only if somehow it was
|
121
|
+
"me" rolling them.
|
122
|
+
|
123
|
+
### What PoolOfEntropy Attempts To Do
|
124
|
+
|
125
|
+
That is the mission of this gem: To create a simple PRNG where the results are connected as much
|
126
|
+
as possible to the end user. This has to be achieved without compromising the
|
127
|
+
good features of fairness and unpredictability that PRNGs and CSPRNGs have in general.
|
128
|
+
|
129
|
+
Luckily this can be achieved using an approach that many CSPRNGs already use - using a
|
130
|
+
data source to seed number generation. The main difference between PoolOfEntropy and
|
131
|
+
regular CSPRNGs used to protect your computer on the internet is how this "entropy" is sourced.
|
132
|
+
In a secure system, entropy is sourced from multiple places - anywhere that data can be
|
133
|
+
gathered that an imagined attacker will have a hard time guessing the value. In PoolOfEntropy
|
134
|
+
this is subverted - the end user supplies any data they like, and the gem treats it
|
135
|
+
as "entropy". Technically, if you were an attacker, this would not be called entropy at
|
136
|
+
all (because you know it) - however, to the machinery of the PRNG, or to me as a fellow
|
137
|
+
player in a dice game, it counts just fine.
|
138
|
+
|
139
|
+
By default PoolOfEntropy objects start off with some machine-collected entropy from SecureRandom
|
140
|
+
to avoid trivial attacks (of always using the dice in the exact same way). You could view this
|
141
|
+
as representing the environment or the die itself (all the scratches and imperfections that
|
142
|
+
you cannot control, and have no influence over). Or, under an honour system of not repeating
|
143
|
+
yourself you can switch off that default.
|
144
|
+
|
145
|
+
## Contributing
|
146
|
+
|
147
|
+
1. Fork it ( http://github.com/<my-github-username>/pool_of_entropy/fork )
|
148
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
149
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
151
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require "pool_of_entropy/version"
|
2
|
+
require "pool_of_entropy/core_prng"
|
3
|
+
|
4
|
+
# This class models a random number generator that can mix user input into
|
5
|
+
# the generation mechanism in a few different ways.
|
6
|
+
#
|
7
|
+
# An object of the class has an internal state for generating numbers, plus
|
8
|
+
# holds processed user data for "mixing" into the output.
|
9
|
+
#
|
10
|
+
# @example Using default internal state, initialised using SecureRandom.random_bytes
|
11
|
+
# prng = PoolOfEntropy.new
|
12
|
+
# prng.rand( 20 )
|
13
|
+
# # E.g. => 12
|
14
|
+
#
|
15
|
+
# @example A customised PRNG, seeded with some user data, using webcam for "true" randomness
|
16
|
+
# prng = PoolOfEntropy.new :size => 4, :blank => true, :seeds = [ 'My Name' ]
|
17
|
+
# loop do
|
18
|
+
# prng.add_to_pool( Webcam.image.bytes ) # Imagined Webcam interface
|
19
|
+
# prng.rand( 20 )
|
20
|
+
# # E.g. => 12
|
21
|
+
# sleep 5
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
|
25
|
+
class PoolOfEntropy
|
26
|
+
|
27
|
+
# Creates a new random number source. All parameters are optional.
|
28
|
+
# @param [Hash] options
|
29
|
+
# @option options [Integer] :size, number of 512-bit (64 byte) blocks to use as internal state, defaults to 1
|
30
|
+
# @option options [Boolean] :blank, if true then initial state is all zeroes, otherwise use SecureRandom
|
31
|
+
# @option options [Array<String>] :seeds, if provided these are sent to #add_to_pool during initialize
|
32
|
+
# @return [PoolOfEntropy]
|
33
|
+
def initialize options = {}
|
34
|
+
unless options.is_a? Hash
|
35
|
+
raise TypeError, "Expecting an options hash, got #{options.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
size = 1
|
39
|
+
if options[:size]
|
40
|
+
size = Integer( options[:size] )
|
41
|
+
if size < 1 || size > 256
|
42
|
+
raise ArgumentError, "Size of pool must be in Range 1..256, got #{size}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
initial_state = if options[:blank]
|
47
|
+
"\x0" * size * 64
|
48
|
+
else
|
49
|
+
SecureRandom.random_bytes( size * 64 )
|
50
|
+
end
|
51
|
+
|
52
|
+
@core_prng = CorePRNG.new( size, initial_state )
|
53
|
+
|
54
|
+
if options[:seeds]
|
55
|
+
unless options[:seeds].is_a? Array
|
56
|
+
raise TypeError, "Expected value for :seeds to be an Array, got #{options[:seeds].inspect}"
|
57
|
+
end
|
58
|
+
options[:seeds].each do |seed|
|
59
|
+
add_to_pool( seed )
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@next_modifier_queue = []
|
64
|
+
@fixed_modifier = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# Cloning creates a deep copy with identical PRNG state and modifiers
|
68
|
+
# @return [PoolOfEntropy]
|
69
|
+
def clone
|
70
|
+
Marshal.load( Marshal.dump( self ) )
|
71
|
+
end
|
72
|
+
|
73
|
+
# Same functionality as Kernel#rand or Random#rand, but using
|
74
|
+
# current pool state to generate number, and including zero, one or
|
75
|
+
# two modifiers that are in effect.
|
76
|
+
# @param [Integer,Range] max if 0 then will return a Float
|
77
|
+
# @return [Float,Fixnum,Bignum] type depends on value of max
|
78
|
+
def rand max = 0
|
79
|
+
if max.is_a? Range
|
80
|
+
bottom = max.first
|
81
|
+
top = max.last
|
82
|
+
return( nil ) if top < bottom
|
83
|
+
return bottom + generate_integer( ( top - bottom + 1 ) )
|
84
|
+
else
|
85
|
+
effective_max = max.to_i.abs
|
86
|
+
if effective_max == 0
|
87
|
+
return generate_float
|
88
|
+
else
|
89
|
+
return generate_integer( effective_max )
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Stores the hash of one or more string modifiers that will be used
|
95
|
+
# just once each to modify results of calls to #rand. Temporary "next"
|
96
|
+
# modifiers and the "all" modifier are combined if both are in effect.
|
97
|
+
# Modifiers change the end result of a call to #rand(), but do *not*
|
98
|
+
# affect the internal state of the data pool used by the generator.
|
99
|
+
# @param [Array<String>] modifiers
|
100
|
+
# @return [PoolOfEntropy] self
|
101
|
+
def modify_next *modifiers
|
102
|
+
modifiers.each do |modifier|
|
103
|
+
if modifier.nil?
|
104
|
+
@next_modifier_queue << nil
|
105
|
+
else
|
106
|
+
@next_modifier_queue << Digest::SHA512.digest( modifier.to_s )
|
107
|
+
end
|
108
|
+
end
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Stores the hash of a single string modifier that will be used
|
113
|
+
# to modify results of calls to #rand, until this modifier is
|
114
|
+
# reset. Temporary "next" modifiers and the "all" modifier are
|
115
|
+
# combined if both are in effect. Modifiers change the end result
|
116
|
+
# of a call to #rand(), but do *not*
|
117
|
+
# affect the internal state of the data pool used by the generator.
|
118
|
+
# @param [String,nil] modifier
|
119
|
+
# @return [PoolOfEntropy] self
|
120
|
+
def modify_all modifier
|
121
|
+
@fixed_modifier = modifier
|
122
|
+
unless @fixed_modifier.nil?
|
123
|
+
@fixed_modifier = Digest::SHA512.digest( @fixed_modifier.to_s )
|
124
|
+
end
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Changes the internal state of the data pool used by the generator,
|
129
|
+
# by "mixing in" user-supplied data. This affects all future values
|
130
|
+
# from #rand() and cannot be undone.
|
131
|
+
# @param [String] data
|
132
|
+
# @return [PoolOfEntropy] self
|
133
|
+
def add_to_pool data
|
134
|
+
@core_prng.update( data )
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# Empties the "next" modifier queue and clears the "all" modifier.
|
139
|
+
# @return [PoolOfEntropy] self
|
140
|
+
def clear_all_modifiers
|
141
|
+
@next_modifier_queue = []
|
142
|
+
@fixed_modifier = nil
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def use_adjustments
|
149
|
+
[ @fixed_modifier, @next_modifier_queue.shift ].compact
|
150
|
+
end
|
151
|
+
|
152
|
+
def generate_float
|
153
|
+
@core_prng.read_float( *use_adjustments )
|
154
|
+
end
|
155
|
+
|
156
|
+
def generate_integer max
|
157
|
+
@core_prng.generate_integer( max, *use_adjustments )
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|