pool_of_entropy 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](http://badge.fury.io/rb/pool_of_entropy)
|
3
|
+
[](http://travis-ci.org/neilslater/pool_of_entropy)
|
4
|
+
[](https://coveralls.io/r/neilslater/pool_of_entropy?branch=master)
|
5
|
+
[](https://codeclimate.com/github/neilslater/pool_of_entropy)
|
6
|
+
[](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
|