super_random 2.0.210126 → 3.0.230113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +57 -29
- data/lib/super_random.rb +90 -10
- metadata +9 -11
- data/lib/super_random/generator.rb +0 -99
- data/lib/super_random/services.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 313ffa9d968e2c7b511fc80ec9a0b6c3de755fe0dd9ed51531448e9f8768d393
|
4
|
+
data.tar.gz: 43cae93ca8d339bd61018fa3b93806426ad7a8bc77161a52309e23b99f0aeedc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 262c9131cc290b6df1cb1b68b54aaa2fb0cd60b6288c33facd07761e1f80802747a0555db7ec7edb9dd5146ecd088c463b5347a5dce2f2da263518964740e3a0
|
7
|
+
data.tar.gz: 352bcace20e9b6f2250c55000a69d2fa51c5f8db3b8a0f7c7474be068d2698d0622d7759c9acfd3cb2e4fa2c2b0fb3a81bfd60fee7390562c1b0078f001f4c84
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SuperRandom
|
2
2
|
|
3
|
-
* [VERSION
|
3
|
+
* [VERSION 3.0.230113](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,79 @@
|
|
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
|
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.0.230113"
|
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
|
+
```
|
39
|
+
## METHODOLOGY:
|
22
40
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
41
|
+
`SuperRandom` uses `OpenUri` to read your sources, which
|
42
|
+
can be http, https, or ftp URLs.
|
43
|
+
`Digest::SHA2.new(512)` is used to digest your sources.
|
44
|
+
Finally, `SecureRandom.bytes` are fed to the digest as a fail safe.
|
45
|
+
This generates the final 64 random bytes.
|
28
46
|
|
29
|
-
|
30
|
-
super_random.hexadecimal(32) #~> ^\h{64}$
|
31
|
-
# Example:
|
32
|
-
# "3e0dffe42c08b849dc3c1290e7aa87dff4ad3037b29694136786a4db1e3efab8"
|
47
|
+
## SOURCES:
|
33
48
|
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
```
|
49
|
+
I could only find one good source expressly for this purpose, `www.random.org`.
|
50
|
+
Another good one is `qrng.anu.edu.au`, but you'll need an API key.
|
51
|
+
Consider using:
|
50
52
|
|
53
|
+
* Snapshots from your webcam
|
54
|
+
* List of market spot prices
|
55
|
+
* Weather forecasts
|
56
|
+
* News or micro-logging feeds
|
57
|
+
|
58
|
+
Be very nice about your calls,
|
59
|
+
specially when you're not using the source as intended.
|
60
|
+
`SuperRandom` enforces a one minute rate limit in the use of sources.
|
61
|
+
Here are ways to set custom sources:
|
62
|
+
```ruby
|
63
|
+
# Sources specified in the constructor:
|
64
|
+
super_random = SuperRandom.new('/var/lib/motion/lastsnap.jpg', 'https://wttr.in')
|
65
|
+
super_random.sources
|
66
|
+
#=> ["/var/lib/motion/lastsnap.jpg", "https://wttr.in"]
|
67
|
+
|
68
|
+
# Sources appended on the instance:
|
69
|
+
super_random.sources.append 'https://text.npr.org'
|
70
|
+
super_random.sources
|
71
|
+
#=> ["/var/lib/motion/lastsnap.jpg", "https://wttr.in", "https://text.npr.org"]
|
72
|
+
|
73
|
+
# Sources appended to the DEFAULT_SOURCES
|
74
|
+
SuperRandom::DEFAULT_SOURCES.append 'https://coinmarketcap.com'
|
75
|
+
super_random = SuperRandom.new
|
76
|
+
super_random.sources
|
77
|
+
#~> random.org.*coinmarketcap.com
|
78
|
+
```
|
51
79
|
## LICENSE:
|
52
80
|
|
53
81
|
(The MIT License)
|
54
82
|
|
55
|
-
Copyright (c)
|
83
|
+
Copyright (c) 2023 carlosjhr64
|
56
84
|
|
57
85
|
Permission is hereby granted, free of charge, to any person obtaining
|
58
86
|
a copy of this software and associated documentation files (the
|
data/lib/super_random.rb
CHANGED
@@ -1,15 +1,95 @@
|
|
1
|
-
# Standard Libraries
|
2
1
|
require 'timeout'
|
3
2
|
require 'securerandom'
|
4
|
-
require '
|
5
|
-
require '
|
3
|
+
require 'open-uri'
|
4
|
+
require 'digest'
|
5
|
+
# Requires:
|
6
|
+
#`ruby`
|
6
7
|
|
7
8
|
class SuperRandom
|
8
|
-
VERSION = '
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
VERSION = '3.0.230113'
|
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
|
-
|
15
|
-
|
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 random_number(scale=1.0)
|
38
|
+
case scale
|
39
|
+
when Float
|
40
|
+
return scale * Rational(hexadecimal.to_i(16), DIV)
|
41
|
+
when Integer
|
42
|
+
return ((hexadecimal.to_i(16) + SecureRandom.random_number(scale)) % scale)
|
43
|
+
end
|
44
|
+
raise "rand(scale Integer|Float)"
|
45
|
+
end
|
46
|
+
alias rand random_number
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
SHASUM = Digest::SHA2.new(512)
|
51
|
+
def _get_bytes
|
52
|
+
MUTEX.synchronize do
|
53
|
+
SHASUM.update SecureRandom.bytes(64)
|
54
|
+
SHASUM.digest.chars.map{_1.ord}
|
55
|
+
end
|
56
|
+
ensure
|
57
|
+
MUTEX.synchronize{SHASUM.update SecureRandom.bytes(64)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def _update_with(source)
|
61
|
+
URI.open(source) do |tmp|
|
62
|
+
MUTEX.synchronize do
|
63
|
+
tmp.each_line do |line|
|
64
|
+
SHASUM.update line
|
65
|
+
@byte_count += line.bytesize
|
66
|
+
end
|
67
|
+
@source_count += 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue
|
71
|
+
$stderr.puts $!
|
72
|
+
end
|
73
|
+
|
74
|
+
def _do_updates
|
75
|
+
@@LAST_TIME = Time.now.to_i
|
76
|
+
threads = @sources.inject([]){|t,s|t.push Thread.new{_update_with s}}
|
77
|
+
begin
|
78
|
+
# Initially, would like to get them all.
|
79
|
+
Timeout.timeout(@first_timeout){threads.each{_1.join}}
|
80
|
+
rescue Timeout::Error
|
81
|
+
begin
|
82
|
+
Timeout.timeout(@second_timeout) do
|
83
|
+
# But at this point,
|
84
|
+
# would like to get at least one.
|
85
|
+
while @services<1 and threads.any?{_1.alive?}
|
86
|
+
Thread.pass
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue Timeout::Error
|
90
|
+
Mutex.sychronize{threads.each{_1.exit}} # Exit each thread
|
91
|
+
raise $! unless @nevermind # Go on if never-minding
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
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:
|
4
|
+
version: 3.0.230113
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- CarlosJHR64
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-13 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
|
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
|
46
|
-
rubygems_version: 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
|