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 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.0"
6
+ - jruby-19mode
7
+ - jruby-head
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Dependencies are in pool_of_entropy.gemspec
4
+ gemspec
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,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "PoolOfEntropy unit tests"
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = "spec/*_spec.rb"
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => [:test]
@@ -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