Class: PoolOfEntropy

Inherits:
Object
  • Object
show all
Defined in:
lib/pool_of_entropy.rb,
lib/pool_of_entropy/version.rb,
lib/pool_of_entropy/core_prng.rb

Overview

This class models a random number generator that can mix user input into the generation mechanism in a few different ways.

An object of the class has an internal state for generating numbers, plus holds processed user data for "mixing" into the output.

Examples:

Using default internal state, initialised using SecureRandom.random_bytes

prng = PoolOfEntropy.new
prng.rand( 20 )
# E.g. => 12

A customised PRNG, seeded with some user data, using webcam for "true" randomness

prng = PoolOfEntropy.new :size => 4, :blank => true, :seeds = [ 'My Name' ]
loop do
  prng.add_to_pool( Webcam.image.bytes ) # Imagined Webcam interface
  prng.rand( 20 )
  # E.g. => 12
  sleep 5
end

Defined Under Namespace

Classes: CorePRNG

Constant Summary collapse

VERSION =
'0.0.5'

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ PoolOfEntropy

Creates a new random number source. All parameters are optional.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :size, (Integer)

    number of 512-bit (64 byte) blocks to use as internal state, defaults to 1

  • :blank, (Boolean)

    if true then initial state is all zeroes, otherwise use SecureRandom

  • :seeds, (Array<String>)

    if provided these are sent to #add_to_pool during initialize

Raises:

  • (TypeError)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/pool_of_entropy.rb', line 32

def initialize(options = {})
  raise TypeError, "Expecting an options hash, got #{options.inspect}" unless options.is_a? Hash

  size = size_from_options(options)

  initial_state = state_from_options(options, size)

  @core_prng = CorePRNG.new(size, initial_state)

  seed_from_options(options)

  @next_modifier_queue = []
  @fixed_modifier = nil
end

Instance Method Details

#add_to_pool(data) ⇒ PoolOfEntropy

Changes the internal state of the data pool used by the generator, by "mixing in" user-supplied data. This affects all future values from #rand() and cannot be undone.

Parameters:

  • data (String)

Returns:



110
111
112
113
# File 'lib/pool_of_entropy.rb', line 110

def add_to_pool(data)
  @core_prng.update(data)
  self
end

#clear_all_modifiersPoolOfEntropy

Empties the "next" modifier queue and clears the "all" modifier.

Returns:



117
118
119
120
121
# File 'lib/pool_of_entropy.rb', line 117

def clear_all_modifiers
  @next_modifier_queue = []
  @fixed_modifier = nil
  self
end

#clonePoolOfEntropy

Cloning creates a deep copy with identical PRNG state and modifiers

Returns:



49
50
51
52
53
54
55
# File 'lib/pool_of_entropy.rb', line 49

def clone
  copy = super
  copy.instance_variable_set(:@core_prng, @core_prng.clone)
  copy.instance_variable_set(:@fixed_modifier, @fixed_modifier.clone) if @fixed_modifier
  copy.instance_variable_set(:@next_modifier_queue, @next_modifier_queue.map(&:clone))
  copy
end

#modify_all(modifier) ⇒ PoolOfEntropy

Stores the hash of a single string modifier that will be used to modify results of calls to #rand, until this modifier is reset. Temporary "next" modifiers and the "all" modifier are combined if both are in effect. Modifiers change the end result of a call to #rand(), but do not affect the internal state of the data pool used by the generator.

Parameters:

  • modifier (String, nil)

Returns:



99
100
101
102
103
# File 'lib/pool_of_entropy.rb', line 99

def modify_all(modifier)
  @fixed_modifier = modifier
  @fixed_modifier = Digest::SHA512.digest(@fixed_modifier.to_s) unless @fixed_modifier.nil?
  self
end

#modify_next(*modifiers) ⇒ PoolOfEntropy

Stores the hash of one or more string modifiers that will be used just once each to modify results of calls to #rand. Temporary "next" modifiers and the "all" modifier are combined if both are in effect. Modifiers change the end result of a call to #rand(), but do not affect the internal state of the data pool used by the generator.

Parameters:

  • modifiers (Array<String>)

Returns:



80
81
82
83
84
85
86
87
88
89
# File 'lib/pool_of_entropy.rb', line 80

def modify_next(*modifiers)
  modifiers.each do |modifier|
    @next_modifier_queue << if modifier.nil?
                              nil
                            else
                              Digest::SHA512.digest(modifier.to_s)
                            end
  end
  self
end

#rand(max = 0) ⇒ Float, ...

Same functionality as Kernel#rand or Random#rand, but using current pool state to generate number, and including zero, one or two modifiers that are in effect.

Parameters:

  • max (Integer, Range) (defaults to: 0)

    if 0 then will return a Float

Returns:

  • (Float, Fixnum, Bignum)

    type depends on value of max



62
63
64
65
66
67
68
69
70
71
# File 'lib/pool_of_entropy.rb', line 62

def rand(max = 0)
  return rand_from_range(max) if max.is_a? Range

  effective_max = max.to_i.abs
  if effective_max.zero?
    generate_float
  else
    generate_integer(effective_max)
  end
end