super_random 2.0.210126 → 3.0.230113
Sign up to get free protection for your applications and to get access to all the features.
- 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
|