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
@@ -0,0 +1,362 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PoolOfEntropy do
|
4
|
+
describe "class methods" do
|
5
|
+
|
6
|
+
describe "#new" do
|
7
|
+
it "should instantiate a default object" do
|
8
|
+
pool = PoolOfEntropy.new
|
9
|
+
pool.should be_a PoolOfEntropy
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should allow setting number of blocks in pool" do
|
13
|
+
[1,3,5,8,13,21,34,55,89,144,233].each do |s|
|
14
|
+
pool = PoolOfEntropy.new( :size => 12 )
|
15
|
+
pool.should be_a PoolOfEntropy
|
16
|
+
num = pool.rand()
|
17
|
+
num.should be_a Float
|
18
|
+
num.should >= 0.0
|
19
|
+
num.should < 1.0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should fail with incorrect :size param" do
|
24
|
+
expect { PoolOfEntropy.new( :size => -12 ) }.to raise_error ArgumentError
|
25
|
+
expect { PoolOfEntropy.new( :size => -1 ) }.to raise_error ArgumentError
|
26
|
+
expect { PoolOfEntropy.new( :size => 0 ) }.to raise_error ArgumentError
|
27
|
+
expect { PoolOfEntropy.new( :size => 257 ) }.to raise_error ArgumentError
|
28
|
+
expect { PoolOfEntropy.new( :size => 1000 ) }.to raise_error ArgumentError
|
29
|
+
expect { PoolOfEntropy.new( :size => '' ) }.to raise_error ArgumentError
|
30
|
+
expect { PoolOfEntropy.new( :size => { :foo => 'bar' } ) }.to raise_error TypeError
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should default to unpredicatble internal state" do
|
34
|
+
pool = PoolOfEntropy.new()
|
35
|
+
pool.should be_a PoolOfEntropy
|
36
|
+
(10..20).map { |x| pool.rand(x) }.should_not == [8, 3, 0, 12, 11, 2, 4, 8, 1, 11, 18]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should accept { :blank => false } as explicit statement of default" do
|
40
|
+
pool = PoolOfEntropy.new( :blank => false )
|
41
|
+
pool.should be_a PoolOfEntropy
|
42
|
+
(10..20).map { |x| pool.rand(x) }.should_not == [8, 3, 0, 12, 11, 2, 4, 8, 1, 11, 18]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should allow an initial blank internal state" do
|
46
|
+
pool = PoolOfEntropy.new( :blank => true )
|
47
|
+
pool.should be_a PoolOfEntropy
|
48
|
+
(10..20).map { |x| pool.rand(x) }.should == [8, 3, 0, 12, 11, 2, 4, 8, 1, 11, 18]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should accept and use :seed array" do
|
52
|
+
pool = PoolOfEntropy.new( :blank => true, :seeds => ['foo'] )
|
53
|
+
pool.should be_a PoolOfEntropy
|
54
|
+
(10..20).map { |x| pool.rand(x) }.should == [9, 1, 3, 7, 8, 12, 14, 5, 11, 5, 6]
|
55
|
+
|
56
|
+
pool = PoolOfEntropy.new( :blank => true, :seeds => ['foo', 'bar'] )
|
57
|
+
pool.should be_a PoolOfEntropy
|
58
|
+
(10..20).map { |x| pool.rand(x) }.should == [8, 1, 5, 8, 2, 7, 11, 8, 8, 13, 14]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
describe "instance methods" do
|
65
|
+
pool_types = [
|
66
|
+
[
|
67
|
+
'default instance',
|
68
|
+
PoolOfEntropy.new
|
69
|
+
],
|
70
|
+
[
|
71
|
+
'instance with 2KB pool size',
|
72
|
+
PoolOfEntropy.new( :size => 32 )
|
73
|
+
],
|
74
|
+
[
|
75
|
+
'instance with 1KB pool size, blank start',
|
76
|
+
PoolOfEntropy.new( :size => 16, :blank => true )
|
77
|
+
],
|
78
|
+
[
|
79
|
+
'instance with default size, seeded',
|
80
|
+
PoolOfEntropy.new( :blank => true, :seeds => ['of change'] )
|
81
|
+
],
|
82
|
+
[
|
83
|
+
'instance cloned from another instance',
|
84
|
+
PoolOfEntropy.new( :size => 3 ).clone
|
85
|
+
],
|
86
|
+
[
|
87
|
+
'instance with maximum size, 16KB pool',
|
88
|
+
PoolOfEntropy.new( :size => 256, :blank => true, :seeds => ['dgeq','dsfsf','dsafsaf'] )
|
89
|
+
],
|
90
|
+
]
|
91
|
+
|
92
|
+
# NB "probability" and "randomness" tests in the following block are very light, just
|
93
|
+
# intended to capture high-level failures in logic. See DIEHARDER_TEST.md for thorough
|
94
|
+
# checks on statistical randomness of PoolOfEntropy::CorePRNG
|
95
|
+
pool_types.each do |pool_name, pool|
|
96
|
+
|
97
|
+
context "using #{pool_name}" do
|
98
|
+
before do
|
99
|
+
pool.clear_all_modifiers
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#clone" do
|
103
|
+
it "should return a deep copy with same state and modifiers" do
|
104
|
+
pool.modify_next( *((0..4).map {|i| "foo" + i.to_s} ) )
|
105
|
+
pool.modify_all( 'bar' )
|
106
|
+
|
107
|
+
pool_copy = pool.clone
|
108
|
+
100.times do
|
109
|
+
pool_copy.rand().should == pool.rand()
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#rand" do
|
115
|
+
|
116
|
+
context "with no param" do
|
117
|
+
it "should call PoolOfEntropy::CorePRNG::read_bytes internally" do
|
118
|
+
allow_any_instance_of( PoolOfEntropy::CorePRNG).
|
119
|
+
to receive( :read_bytes ).and_return( "\x1e\xfe" * 8 )
|
120
|
+
20.times { pool.rand.should == 0.12106507972838931 }
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should return a Float between 0.0 and 1.0" do
|
124
|
+
100.times do
|
125
|
+
num = pool.rand
|
126
|
+
num.should be_a Float
|
127
|
+
num.should >= 0.0
|
128
|
+
num.should < 1.0
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should return a different Float each time (with high probability) " do
|
133
|
+
Set[ *(1..100).map{ pool.rand } ].size.should == 100
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "with an Integer param" do
|
138
|
+
it "should call PoolOfEntropy::CorePRNG::read_bytes internally" do
|
139
|
+
allow_any_instance_of( PoolOfEntropy::CorePRNG).
|
140
|
+
to receive( :read_bytes ).and_return( "\x1e\xfe" * 8 )
|
141
|
+
20.times { pool.rand(20).should == 2 }
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should return an Integer between 0 and x (excluding x)" do
|
145
|
+
[ 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ].each do |x|
|
146
|
+
100.times do
|
147
|
+
num = pool.rand( x )
|
148
|
+
num.should be_a Fixnum
|
149
|
+
num.should >= 0
|
150
|
+
num.should < x
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# This is a very weak test of randomness, see DIEHARDER_TEST.md
|
156
|
+
it "should select values without obvious bias" do
|
157
|
+
Set[ *(1..200).map{ pool.rand( 20 ) } ].size.should == 20
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should return a different Integer each time (with high probability for large x) " do
|
161
|
+
Set[ *(1..100).map{ pool.rand( 2**64 ) } ].size.should == 100
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "with a Range param" do
|
166
|
+
it "should call PoolOfEntropy::CorePRNG::read_bytes internally" do
|
167
|
+
allow_any_instance_of( PoolOfEntropy::CorePRNG).
|
168
|
+
to receive( :read_bytes ).and_return( "\x1e\xfe" * 8 )
|
169
|
+
20.times { pool.rand( 7..28 ).should == 9 }
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should return an Integer that is a member of the range" do
|
173
|
+
[ 1..2, 2..5, 3..8, 5..13, 21..34, 55..89 ].each do |r|
|
174
|
+
100.times do
|
175
|
+
num = pool.rand( r )
|
176
|
+
num.should be_a Fixnum
|
177
|
+
num.should >= r.min
|
178
|
+
num.should <= r.max
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should return a different Integer each time (with high probability for large range) " do
|
184
|
+
Set[ *(1..100).map{ pool.rand( 100000000000..200000000000 ) } ].size.should == 100
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
describe "#modify_next" do
|
193
|
+
it "changes output value from next call to #rand" do
|
194
|
+
pool_copy = pool.clone
|
195
|
+
100.times do
|
196
|
+
modifier = SecureRandom.hex
|
197
|
+
pool_copy.modify_next( modifier ).rand.should_not == pool.rand
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
it "changes output value consistently" do
|
202
|
+
pool_copy = pool.clone
|
203
|
+
100.times do
|
204
|
+
modifier = SecureRandom.hex
|
205
|
+
pool_copy.modify_next( modifier ).rand.should == pool.modify_next( modifier ).rand
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
it "changes next output value but not future ones" do
|
210
|
+
pool_copy = pool.clone
|
211
|
+
100.times do
|
212
|
+
modifier = SecureRandom.hex
|
213
|
+
pool_copy.modify_next( modifier ).rand.should_not == pool.rand
|
214
|
+
pool_copy.rand.should == pool.rand
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
it "can create a 'queue' of modifiers, used in turn" do
|
219
|
+
pool_copy = pool.clone
|
220
|
+
10.times do
|
221
|
+
modifiers = (0..5).map { SecureRandom.hex }
|
222
|
+
|
223
|
+
# Syntax for all-at-once
|
224
|
+
pool_copy.modify_next( *modifiers )
|
225
|
+
modifiers.each do |modifier|
|
226
|
+
pool_copy.rand.should == pool.modify_next( modifier ).rand
|
227
|
+
end
|
228
|
+
# Assert we're back in sync without modifiers
|
229
|
+
pool_copy.rand.should == pool.rand
|
230
|
+
|
231
|
+
# Adding to queue one-at-a-time
|
232
|
+
modifiers.each do |modifier|
|
233
|
+
pool_copy.modify_next( modifier )
|
234
|
+
end
|
235
|
+
modifiers.each do |modifier|
|
236
|
+
pool_copy.rand.should == pool.modify_next( modifier ).rand
|
237
|
+
end
|
238
|
+
# Assert we're back in sync without modifiers
|
239
|
+
pool_copy.rand.should == pool.rand
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end # modify_next
|
243
|
+
|
244
|
+
describe "#modify_all" do
|
245
|
+
it "changes output value from future calls to #rand" do
|
246
|
+
pool_copy = pool.clone
|
247
|
+
10.times do
|
248
|
+
modifier = SecureRandom.hex
|
249
|
+
pool_copy.modify_all( modifier ).rand.should_not == pool.rand
|
250
|
+
10.times do
|
251
|
+
pool_copy.rand.should_not == pool.rand
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
it "changes output value consistently" do
|
257
|
+
pool_copy = pool.clone
|
258
|
+
10.times do
|
259
|
+
modifier = SecureRandom.hex
|
260
|
+
pool_copy.modify_all( modifier ).rand.should == pool.modify_all( modifier ).rand
|
261
|
+
10.times do
|
262
|
+
pool_copy.rand.should == pool.rand
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
it "changes output value consistently with #modify_next" do
|
268
|
+
pool_copy = pool.clone
|
269
|
+
100.times do
|
270
|
+
modifier = SecureRandom.hex
|
271
|
+
pool_copy.modify_all( modifier ).rand.should == pool.modify_next( modifier ).rand
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it "can be reset wih nil modifier" do
|
276
|
+
pool_copy = pool.clone
|
277
|
+
10.times do
|
278
|
+
modifier = SecureRandom.hex
|
279
|
+
pool_copy.modify_all( modifier ).rand.should_not == pool.rand
|
280
|
+
pool_copy.rand.should_not == pool.rand
|
281
|
+
pool_copy.modify_all( nil )
|
282
|
+
10.times do
|
283
|
+
pool_copy.rand.should == pool.rand
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
describe "#clear_all_modifiers" do
|
291
|
+
|
292
|
+
it "removes a queue of 'next' modifiers" do
|
293
|
+
pool_copy = pool.clone
|
294
|
+
10.times do
|
295
|
+
modifiers = (0..5).map { SecureRandom.hex }
|
296
|
+
pool_copy.modify_next( *modifiers )
|
297
|
+
pool_copy.rand.should_not == pool.rand
|
298
|
+
pool_copy.clear_all_modifiers
|
299
|
+
10.times do
|
300
|
+
pool_copy.rand.should == pool.rand
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
it "removes a current 'all' modifier" do
|
306
|
+
pool_copy = pool.clone
|
307
|
+
10.times do
|
308
|
+
modifier = SecureRandom.hex
|
309
|
+
pool_copy.modify_all( modifier ).rand.should_not == pool.rand
|
310
|
+
pool_copy.rand.should_not == pool.rand
|
311
|
+
pool_copy.clear_all_modifiers
|
312
|
+
10.times do
|
313
|
+
pool_copy.rand.should == pool.rand
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
it "removes both 'all' and 'next' modifiers in one go" do
|
319
|
+
pool_copy = pool.clone
|
320
|
+
10.times do
|
321
|
+
modifier = SecureRandom.hex
|
322
|
+
pool_copy.modify_next( *(0..5).map { SecureRandom.hex } )
|
323
|
+
pool_copy.modify_all( modifier ).rand.should_not == pool.rand
|
324
|
+
pool_copy.rand.should_not == pool.rand
|
325
|
+
pool_copy.clear_all_modifiers
|
326
|
+
10.times do
|
327
|
+
pool_copy.rand.should == pool.rand
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe "#add_to_pool" do
|
334
|
+
|
335
|
+
it "alters all future output values" do
|
336
|
+
pool_copy = pool.clone
|
337
|
+
pool_copy.add_to_pool( 'Some user data!' )
|
338
|
+
100.times do
|
339
|
+
pool_copy.rand.should_not == pool.rand
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
it "alters all future output values consistently" do
|
344
|
+
pool_copy = pool.clone
|
345
|
+
|
346
|
+
10.times do
|
347
|
+
user_data = SecureRandom.hex
|
348
|
+
pool_copy.add_to_pool( user_data )
|
349
|
+
pool.add_to_pool( user_data )
|
350
|
+
|
351
|
+
10.times do
|
352
|
+
pool_copy.rand.should == pool.rand
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pool_of_entropy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Neil Slater
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.7.2
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.7.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.13.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.13.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.9.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.9.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coveralls
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.6.7
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.6.7
|
83
|
+
description: PoolOfEntropy is a PRNG based on cryptographic secure PRNGs, intended
|
84
|
+
to bring back the feeling of 'personal luck' that some gamers feel when rolling
|
85
|
+
their own dice.
|
86
|
+
email:
|
87
|
+
- slobo777@gmail.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- ".gitignore"
|
93
|
+
- ".travis.yml"
|
94
|
+
- DIEHARDER_TEST.md
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- lib/pool_of_entropy.rb
|
100
|
+
- lib/pool_of_entropy/core_prng.rb
|
101
|
+
- lib/pool_of_entropy/version.rb
|
102
|
+
- pool_of_entropy.gemspec
|
103
|
+
- spec/core_prng_spec.rb
|
104
|
+
- spec/pool_of_entropy_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
homepage: https://github.com/neilslater/pool_of_entropy
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 2.2.2
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: Random number generator with extra features for gamers.
|
130
|
+
test_files:
|
131
|
+
- spec/core_prng_spec.rb
|
132
|
+
- spec/pool_of_entropy_spec.rb
|
133
|
+
- spec/spec_helper.rb
|
134
|
+
has_rdoc:
|