super_random 2.0.210126 → 3.1.230114

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2480d12416526288677a0a9203d7e63ae666eb70b59fc792cc1a67bc2c35978a
4
- data.tar.gz: 52d9c2e334770c1498f6afffc1bc967db38686a1d5098882f2b1290bbf5f1e60
3
+ metadata.gz: da086dfbbc9f424f5c8f7f35e38fc1be67633986797aea732af695c0ed4a8b67
4
+ data.tar.gz: ed4274a6e0b5b113c37c9adc4b20f9f96eacdeb5798e79235d023b6c5683f847
5
5
  SHA512:
6
- metadata.gz: 3dcb0cb30658abd969e2930cc60bc9d0ff33bca7b4dc7ee0e418c43ba6f9815d52c7af4c21346320ff7941422e2ff8fbbb75a3e0453c9172edf3f05e5a0609ff
7
- data.tar.gz: c0b24a58b9a236992b615a3bad666af0d5377575dabed3fd760cc3b2857904f9bec41ed3ee0f186c11820979c0bea50cf4f52de580ff6a576d3dc253a2644f43
6
+ metadata.gz: 78284485c7c649b00f1916d6a7b946f9673ab223f01f8b28bc227975da9154ccbc87bfb1af76eb36a0ad378c4c222c9135b239cad57b655f02d6860c146f9205
7
+ data.tar.gz: bab65a30405474b92539bfffdbd82fca278882c0be7d45f74d12c57d2c0f7ffc874cfd7e9ba0c0d1cc5b949d909adf298aa036b9974cf35c8dd248340f929be3
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SuperRandom
2
2
 
3
- * [VERSION 2.0.210126](https://github.com/carlosjhr64/super_random/releases)
3
+ * [VERSION 3.1.230114](https://github.com/carlosjhr64/super_random/releases)
4
4
  * [github](https://github.com/carlosjhr64/super_random)
5
5
  * [rubygems](https://rubygems.org/gems/super_random)
6
6
 
@@ -8,51 +8,82 @@
8
8
 
9
9
  You can't get more random than random, but you can try really, really, really hard.
10
10
 
11
- SuperRandom combines three online real random services to create a more perfect random byte.
11
+ `SuperRandom` combines sources of entropy to generate super-random bytes!
12
12
 
13
13
  ## INSTALL:
14
14
 
15
15
  $ gem install super_random
16
16
 
17
17
  ## SYNOPSIS:
18
-
19
18
  ```ruby
20
19
  require 'super_random'
20
+ SuperRandom::VERSION #=> "3.1.230114"
21
+ SuperRandom::DEFAULT_SOURCES #~> www.random.org
21
22
  super_random = SuperRandom.new
23
+ super_random.sources #~> www.random.org
24
+ super_random.bytes #~> ^\[\d+(, \d+){63}\]$
25
+ # The source_count attribute gives the number of sources successfully used.
26
+ super_random.source_count #=> 1
27
+ # The byte_count attribute gives the total bytes digested from sources.
28
+ super_random.byte_count #=> 210
29
+ super_random.hexadecimal #~> ^\h{128}$
30
+ super_random.random_number(100.0) #~> ^\d{1,2}\.\d+$
31
+ super_random.random_number(100) #~> ^\d{1,2}$
32
+ # Because of a 1 minute rate limit, subsequent source counts are zero:
33
+ super_random.source_count #=> 0
34
+ super_random.byte_count #=> 0
35
+ # Snapshots from your webcam can be a good entropy source:
36
+ super_random = SuperRandom.new('/var/lib/motion/lastsnap.jpg')
37
+ super_random.sources #=> ["/var/lib/motion/lastsnap.jpg"]
38
+ # Dice!
39
+ d6 = super_random.dice(6)
40
+ d6.roll #~> ^[123456]$
41
+ ```
42
+ ## METHODOLOGY:
22
43
 
23
- super_random.bytes(32) #~> ^\[\d+(, \d+){31}\]$
24
- # Example:
25
- # [142, 36, 107, 199, 1, 222, 69, 238, 130, 159, 236, 201, 199,
26
- # 33, 237, 166, 189, 166, 95, 246, 111, 103, 113, 126, 27, 31,
27
- # 244, 215, 200, 60, 255, 184]
44
+ `SuperRandom` uses `OpenUri` to read your sources, which
45
+ can be http, https, or ftp URLs.
46
+ `Digest::SHA2.new(512)` is used to digest your sources.
47
+ Finally, `SecureRandom.bytes` are fed to the digest as a fail safe.
48
+ This generates the final 64 random bytes.
28
49
 
29
- sleep 1 # rate limit to be nice
30
- super_random.hexadecimal(32) #~> ^\h{64}$
31
- # Example:
32
- # "3e0dffe42c08b849dc3c1290e7aa87dff4ad3037b29694136786a4db1e3efab8"
50
+ ## SOURCES:
33
51
 
34
- sleep 1
35
- super_random.random_number(100.0) #~> ^\d{1,2}\.\d+$
36
- # Example:
37
- # 16.882225652425537
38
-
39
- sleep 1
40
- super_random.random_number(100) #~> ^\d{1,2}$
41
- # Example:
42
- # 85
43
-
44
- # The "services" attribute gives the number of online services used.
45
- # It's possible for a service to fail.
46
- # Ultimately, SuperRandom uses SecureRandom as a failsafe.
47
- super_random.services #=> 3
48
- super_random.randomness #=> 3.0
49
- ```
52
+ I could only find one good source expressly for this purpose, `www.random.org`.
53
+ Another good one is `qrng.anu.edu.au`, but you'll need an API key.
54
+ Consider using:
50
55
 
56
+ * Snapshots from your webcam
57
+ * List of market spot prices
58
+ * Weather forecasts
59
+ * News or micro-logging feeds
60
+
61
+ Be very nice about your calls,
62
+ specially when you're not using the source as intended.
63
+ `SuperRandom` enforces a one minute rate limit in the use of sources.
64
+ Here are ways to set custom sources:
65
+ ```ruby
66
+ # Sources specified in the constructor:
67
+ super_random = SuperRandom.new('/var/lib/motion/lastsnap.jpg', 'https://wttr.in')
68
+ super_random.sources
69
+ #=> ["/var/lib/motion/lastsnap.jpg", "https://wttr.in"]
70
+
71
+ # Sources appended on the instance:
72
+ super_random.sources.append 'https://text.npr.org'
73
+ super_random.sources
74
+ #=> ["/var/lib/motion/lastsnap.jpg", "https://wttr.in", "https://text.npr.org"]
75
+
76
+ # Sources appended to the DEFAULT_SOURCES
77
+ SuperRandom::DEFAULT_SOURCES.append 'https://coinmarketcap.com'
78
+ super_random = SuperRandom.new
79
+ super_random.sources
80
+ #~> random.org.*coinmarketcap.com
81
+ ```
51
82
  ## LICENSE:
52
83
 
53
84
  (The MIT License)
54
85
 
55
- Copyright (c) 2021 carlosjhr64
86
+ Copyright (c) 2023 carlosjhr64
56
87
 
57
88
  Permission is hereby granted, free of charge, to any person obtaining
58
89
  a copy of this software and associated documentation files (the
data/lib/super_random.rb CHANGED
@@ -1,15 +1,135 @@
1
- # Standard Libraries
2
1
  require 'timeout'
3
2
  require 'securerandom'
4
- require 'net/http'
5
- require 'json'
3
+ require 'open-uri'
4
+ require 'digest'
5
+ # Requires:
6
+ #`ruby`
6
7
 
7
8
  class SuperRandom
8
- VERSION = '2.0.210126'
9
- # This Gem
10
- require 'super_random/services'
11
- require 'super_random/generator'
12
- end
9
+ VERSION = '3.1.230114'
10
+ DEFAULT_SOURCES = [
11
+ 'https://www.random.org/strings/?num=10&len=20&digits=on&upperalpha=on&loweralpha=on&unique=on&format=plain&rnd=new',
12
+ ]
13
+ MUTEX = Mutex.new
14
+ DIV = 128.times.inject(''){|h,_|h<<'f'}.to_i(16)
15
+ RATE_LIMIT = 60 # One minute, be nice!
16
+ @@LAST_TIME = Time.now.to_i - RATE_LIMIT - 1
13
17
 
14
- # Requires:
15
- #`ruby`
18
+ attr_accessor :first_timeout, :second_timeout, :nevermind
19
+ attr_reader :sources, :source_count, :byte_count
20
+ def initialize(*sources)
21
+ @sources = sources.empty? ? DEFAULT_SOURCES.dup : sources
22
+ @byte_count,@source_count = 0,0
23
+ @first_timeout,@second_timeout,@nevermind = 3,6,true
24
+ end
25
+
26
+ # Returns 64 random bytes(at least from SecureRandom's)
27
+ def bytes
28
+ @byte_count,@source_count = 0,0
29
+ _do_updates if Time.now.to_i - @@LAST_TIME > RATE_LIMIT
30
+ _get_bytes
31
+ end
32
+
33
+ def hexadecimal
34
+ bytes.map{_1.to_s(16).rjust(2,'0')}.join
35
+ end
36
+
37
+ def integer
38
+ hexadecimal.to_i(16)
39
+ end
40
+
41
+ def float
42
+ Rational(integer, DIV).to_f
43
+ end
44
+
45
+ def float2
46
+ # An alternate way to generate a float but...
47
+ hex = hexadecimal
48
+ x = hex[0...64].to_i(16)
49
+ y = hex[64..128].to_i(16)
50
+ # ...what distribution is this?
51
+ Rational(x,y).to_f
52
+ rescue ZeroDivisionError
53
+ # Fat chance!
54
+ return 0 if x == 0
55
+ 1.0/0.0
56
+ end
57
+
58
+ def random_number(scale=1.0)
59
+ case scale
60
+ when Float
61
+ return scale * float
62
+ when Integer
63
+ return ((integer + SecureRandom.random_number(scale)) % scale)
64
+ end
65
+ raise "rand(scale Integer|Float)"
66
+ end
67
+ alias rand random_number
68
+
69
+ class Dice
70
+ private def set_big
71
+ @big = @rng.integer + SecureRandom.random_number(@sides)
72
+ end
73
+ def initialize(sides, minimum:1, rng:SuperRandom.new)
74
+ @sides,@minimum,@rng = sides,minimum,rng
75
+ set_big
76
+ end
77
+ def roll
78
+ @big,roll = @big.divmod(@sides)
79
+ return roll+@minimum
80
+ ensure
81
+ set_big unless @big>0
82
+ end
83
+ end
84
+ def dice(sides, minimum:1)
85
+ Dice.new(sides, minimum:minimum, rng:self)
86
+ end
87
+
88
+ private
89
+
90
+ SHASUM = Digest::SHA2.new(512)
91
+ def _get_bytes
92
+ MUTEX.synchronize do
93
+ SHASUM.update SecureRandom.bytes(64)
94
+ SHASUM.digest.chars.map{_1.ord}
95
+ end
96
+ ensure
97
+ MUTEX.synchronize{SHASUM.update SecureRandom.bytes(64)}
98
+ end
99
+
100
+ def _update_with(source)
101
+ URI.open(source) do |tmp|
102
+ MUTEX.synchronize do
103
+ tmp.each_line do |line|
104
+ SHASUM.update line
105
+ @byte_count += line.bytesize
106
+ end
107
+ @source_count += 1
108
+ end
109
+ end
110
+ rescue
111
+ $stderr.puts $!
112
+ end
113
+
114
+ def _do_updates
115
+ @@LAST_TIME = Time.now.to_i
116
+ threads = @sources.inject([]){|t,s|t.push Thread.new{_update_with s}}
117
+ begin
118
+ # Initially, would like to get them all.
119
+ Timeout.timeout(@first_timeout){threads.each{_1.join}}
120
+ rescue Timeout::Error
121
+ begin
122
+ Timeout.timeout(@second_timeout) do
123
+ # But at this point,
124
+ # would like to get at least one.
125
+ while @services<1 and threads.any?{_1.alive?}
126
+ Thread.pass
127
+ end
128
+ end
129
+ rescue Timeout::Error
130
+ Mutex.sychronize{threads.each{_1.exit}} # Exit each thread
131
+ raise $! unless @nevermind # Go on if never-minding
132
+ end
133
+ end
134
+ end
135
+ end
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: super_random
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.210126
4
+ version: 3.1.230114
5
5
  platform: ruby
6
6
  authors:
7
- - carlosjhr64
8
- autorequire:
7
+ - CarlosJHR64
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-26 00:00:00.000000000 Z
11
+ date: 2023-01-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  You can't get more random than random, but you can try really, really, really hard.
15
15
 
16
- SuperRandom combines three online real random services to create a more perfect random byte.
16
+ `SuperRandom` combines sources of entropy to generate super-random bytes!
17
17
  email: carlosjhr64@gmail.com
18
18
  executables: []
19
19
  extensions: []
@@ -21,13 +21,11 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - README.md
23
23
  - lib/super_random.rb
24
- - lib/super_random/generator.rb
25
- - lib/super_random/services.rb
26
24
  homepage: https://github.com/carlosjhr64/super_random
27
25
  licenses:
28
26
  - MIT
29
27
  metadata: {}
30
- post_install_message:
28
+ post_install_message:
31
29
  rdoc_options: []
32
30
  require_paths:
33
31
  - lib
@@ -42,9 +40,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
40
  - !ruby/object:Gem::Version
43
41
  version: '0'
44
42
  requirements:
45
- - 'ruby: ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]'
46
- rubygems_version: 3.2.3
47
- signing_key:
43
+ - 'ruby: ruby 3.2.0 (2022-12-25 revision a528908271) [aarch64-linux]'
44
+ rubygems_version: 3.4.3
45
+ signing_key:
48
46
  specification_version: 4
49
47
  summary: You can't get more random than random, but you can try really, really, really
50
48
  hard.
@@ -1,99 +0,0 @@
1
- class SuperRandom
2
- DEFAULT_BYTES = 32
3
-
4
- attr_accessor :first_timeout, :second_timeout, :nevermind
5
- attr_reader :randomness, :services
6
-
7
- def initialize
8
- @first_timeout = 3
9
- @second_timeout = 6
10
- @nevermind = true
11
- @randomness = 0.0
12
- @services = 0
13
- end
14
-
15
- def bytes(n=DEFAULT_BYTES)
16
- @randomness = 0.0
17
- @services = 0
18
-
19
- m = SuperRandom.services
20
- a = Array.new m.length
21
- t = Array.new m.length
22
- m.each_with_index do |k,i|
23
- t[i] = Thread.new{ a[i] = SuperRandom.send(k, n) }
24
- end
25
-
26
- begin
27
- Timeout.timeout(@first_timeout) do
28
- # Initially, would like to get them all.
29
- t.each{_1.join}
30
- end
31
- rescue Timeout::Error
32
- begin
33
- Timeout.timeout(@second_timeout) do
34
- # But at this point,
35
- # would like to get at least one.
36
- while a.all?{_1.nil?} and t.any?{_1.alive?}
37
- Thread.pass
38
- end
39
- end
40
- rescue Timeout::Error
41
- # If we don't care that we got nothing, go on.
42
- raise $! unless @nevermind
43
- end
44
- end
45
-
46
- r = Array.new n
47
- n.times{|i| r[i] = SecureRandom.random_number(256)}
48
-
49
- a.each do |b|
50
- next if b.nil?
51
- l = b.length
52
- @randomness += l.to_f/n.to_f
53
- @services += 1
54
- n.times{|i|r[i]=(r[i]+b[i%l])%256}
55
- end
56
-
57
- return r
58
- end
59
-
60
- def hexadecimal(n=DEFAULT_BYTES)
61
- bytes(n).map{|i|i.to_s(16).rjust(2,'0')}.join
62
- end
63
-
64
- def random_number(scale=1.0, minbytes=6, maxbytes=[minbytes,DEFAULT_BYTES].max)
65
- case scale
66
- when Float
67
- div = minbytes.times.inject(''){|s,i| s+'FF'}.to_i(16)
68
- den = hexadecimal(minbytes).to_i(16)
69
- return scale * den.to_f / div.to_f
70
- when Integer
71
- n = n0 = Math.log(scale, 256).ceil
72
- e = e0 = 256**n
73
- r = r0 = e0 % scale
74
- while r > 0
75
- n0 += 1
76
- e0 *= 256
77
- r0 = e0 % scale
78
- if r0 <= r
79
- # break if repeating pattern with big enough integer
80
- break if r0 == r and n0 > minbytes
81
- r,n,e = r0,n0,e0
82
- end
83
- break if n0 >= maxbytes
84
- end
85
- max = (e/scale)*scale
86
- loop do
87
- number = hexadecimal(n).to_i(16)
88
- return number % scale if number < max
89
- # On a relatively small chance that we're above max...
90
- if @nevermind
91
- warn "using SecureRandom.random_number(#{scale})"
92
- return SecureRandom.random_number(scale)
93
- end
94
- end
95
- end
96
- raise "rand(scale Integer|Float)"
97
- end
98
- alias rand random_number
99
- end
@@ -1,45 +0,0 @@
1
- class SuperRandom
2
- def self.services
3
- [:quantum, :atmospheric, :hotbits]
4
- end
5
-
6
- # https://qrng.anu.edu.au/
7
- # https://qrng.anu.edu.au/contact/api-documentation/
8
- def self.quantum(n)
9
- s = Net::HTTP.get(URI(
10
- "https://qrng.anu.edu.au/API/jsonI.php?length=#{n}&type=uint8"))
11
- a = JSON.parse(s)['data']
12
- raise unless a.is_a?(Array) and a.length==n
13
- raise unless a.all?{|i| i.is_a?(Integer) and i.between?(0,255)}
14
- return a
15
- rescue StandardError
16
- warn "quantum (qrng.anu.edu.au) failed."
17
- return nil
18
- end
19
-
20
- # https://www.random.org/
21
- # https://www.random.org/integers/
22
- def self.atmospheric(n)
23
- s = Net::HTTP.get(URI(
24
- "https://www.random.org/integers/?num=#{n}&min=0&max=255&col=1&base=10&format=plain&rnd=new"))
25
- a = s.strip.split(/\s+/).map{|j|j.to_i}
26
- raise unless a.length==n
27
- raise unless a.all?{|i| i.between?(0,255)}
28
- return a
29
- rescue StandardError
30
- warn "atmospheric (www.random.org) failed."
31
- return nil
32
- end
33
-
34
- # https://www.fourmilab.ch/hotbits/
35
- def self.hotbits(n, k='Pseudorandom')
36
- s = Net::HTTP.get(URI(
37
- "https://www.fourmilab.ch/cgi-bin/Hotbits.api?nbytes=#{n}&fmt=bin&apikey=#{k}"))
38
- a = s.bytes
39
- raise unless a.length==n
40
- return a
41
- rescue StandardError
42
- warn "hotbits (www.fourmilab.ch) failed."
43
- return nil
44
- end
45
- end