Class: SuperRandom

Inherits:
Object
  • Object
show all
Defined in:
lib/super_random.rb,
lib/super_random/services.rb,
lib/super_random/generator.rb

Constant Summary collapse

VERSION =
'2.0.210126'
DEFAULT_BYTES =
32

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSuperRandom

Returns a new instance of SuperRandom.



7
8
9
10
11
12
13
# File 'lib/super_random/generator.rb', line 7

def initialize
  @first_timeout = 3
  @second_timeout = 6
  @nevermind = true
  @randomness = 0.0
  @services = 0
end

Instance Attribute Details

#first_timeoutObject

Returns the value of attribute first_timeout.



4
5
6
# File 'lib/super_random/generator.rb', line 4

def first_timeout
  @first_timeout
end

#nevermindObject

Returns the value of attribute nevermind.



4
5
6
# File 'lib/super_random/generator.rb', line 4

def nevermind
  @nevermind
end

#randomnessObject (readonly)

Returns the value of attribute randomness.



5
6
7
# File 'lib/super_random/generator.rb', line 5

def randomness
  @randomness
end

#second_timeoutObject

Returns the value of attribute second_timeout.



4
5
6
# File 'lib/super_random/generator.rb', line 4

def second_timeout
  @second_timeout
end

#servicesObject (readonly)

Returns the value of attribute services.



5
6
7
# File 'lib/super_random/generator.rb', line 5

def services
  @services
end

Class Method Details

.atmospheric(n) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/super_random/services.rb', line 22

def self.atmospheric(n)
  s = Net::HTTP.get(URI(
    "https://www.random.org/integers/?num=#{n}&min=0&max=255&col=1&base=10&format=plain&rnd=new"))
  a = s.strip.split(/\s+/).map{|j|j.to_i}
  raise unless a.length==n
  raise unless a.all?{|i| i.between?(0,255)}
  return a
rescue StandardError
  warn "atmospheric (www.random.org) failed."
  return nil
end

.hotbits(n, k = 'Pseudorandom') ⇒ Object



35
36
37
38
39
40
41
42
43
44
# File 'lib/super_random/services.rb', line 35

def self.hotbits(n, k='Pseudorandom')
  s = Net::HTTP.get(URI(
    "https://www.fourmilab.ch/cgi-bin/Hotbits.api?nbytes=#{n}&fmt=bin&apikey=#{k}"))
  a = s.bytes
  raise unless a.length==n
  return a
rescue StandardError
  warn "hotbits (www.fourmilab.ch) failed."
  return nil
end

.quantum(n) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/super_random/services.rb', line 8

def self.quantum(n)
  s = Net::HTTP.get(URI(
    "https://qrng.anu.edu.au/API/jsonI.php?length=#{n}&type=uint8"))
  a = JSON.parse(s)['data']
  raise unless a.is_a?(Array) and a.length==n
  raise unless a.all?{|i| i.is_a?(Integer) and i.between?(0,255)}
  return a
rescue StandardError
  warn "quantum (qrng.anu.edu.au) failed."
  return nil
end

.servicesObject



2
3
4
# File 'lib/super_random/services.rb', line 2

def self.services
  [:quantum, :atmospheric, :hotbits]
end

Instance Method Details

#bytes(n = DEFAULT_BYTES) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/super_random/generator.rb', line 15

def bytes(n=DEFAULT_BYTES)
  @randomness = 0.0
  @services = 0

  m = SuperRandom.services
  a = Array.new m.length
  t = Array.new m.length
  m.each_with_index do |k,i|
    t[i] = Thread.new{ a[i] = SuperRandom.send(k, n) }
  end

  begin
    Timeout.timeout(@first_timeout) do
      # Initially, would like to get them all.
      t.each{_1.join}
    end
  rescue Timeout::Error
    begin
      Timeout.timeout(@second_timeout) do
        # But at this point,
        # would like to get at least one.
        while a.all?{_1.nil?} and t.any?{_1.alive?}
          Thread.pass
        end
      end
    rescue Timeout::Error
      # If we don't care that we got nothing, go on.
      raise $! unless @nevermind
    end
  end

  r = Array.new n
  n.times{|i| r[i] = SecureRandom.random_number(256)}

  a.each do |b|
    next if b.nil?
    l = b.length
    @randomness += l.to_f/n.to_f
    @services += 1
    n.times{|i|r[i]=(r[i]+b[i%l])%256}
  end

  return r
end

#hexadecimal(n = DEFAULT_BYTES) ⇒ Object



60
61
62
# File 'lib/super_random/generator.rb', line 60

def hexadecimal(n=DEFAULT_BYTES)
  bytes(n).map{|i|i.to_s(16).rjust(2,'0')}.join
end

#random_number(scale = 1.0, minbytes = 6, maxbytes = [minbytes,DEFAULT_BYTES].max) ⇒ Object Also known as: rand



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/super_random/generator.rb', line 64

def random_number(scale=1.0, minbytes=6, maxbytes=[minbytes,DEFAULT_BYTES].max)
  case scale
  when Float
    div = minbytes.times.inject(''){|s,i| s+'FF'}.to_i(16)
    den = hexadecimal(minbytes).to_i(16)
    return  scale * den.to_f / div.to_f
  when Integer
    n = n0 = Math.log(scale, 256).ceil
    e = e0 = 256**n
    r = r0 = e0 % scale
    while r > 0
      n0 += 1
      e0 *= 256
      r0 = e0 % scale
      if r0 <= r
        # break if repeating pattern with big enough integer
        break if r0 == r and n0 > minbytes
        r,n,e = r0,n0,e0
      end
      break if n0 >= maxbytes
    end
    max = (e/scale)*scale
    loop do
      number = hexadecimal(n).to_i(16)
      return number % scale if number < max
      # On a relatively small chance that we're above max...
      if @nevermind
        warn "using SecureRandom.random_number(#{scale})"
        return SecureRandom.random_number(scale)
      end
    end
  end
  raise "rand(scale Integer|Float)"
end