mirror of
https://github.com/safing/portbase
synced 2025-09-01 10:09:50 +00:00
Revamp crypto/random to feature a feedable CSPRNG
This commit is contained in:
parent
c9f41a65af
commit
9618ae8f5e
14 changed files with 994 additions and 37 deletions
7
crypto/random/doc.go
Normal file
7
crypto/random/doc.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Package random provides a feedable CSPRNG.
|
||||
//
|
||||
// CSPRNG used is fortuna: github.com/seehuhn/fortuna
|
||||
// By default the CSPRNG is fed by two sources:
|
||||
// - OS RNG
|
||||
// - Entropy gathered by context switching
|
||||
package random
|
133
crypto/random/entropy.go
Normal file
133
crypto/random/entropy.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/tevino/abool"
|
||||
|
||||
"github.com/Safing/portbase/config"
|
||||
"github.com/Safing/portbase/container"
|
||||
)
|
||||
|
||||
var (
|
||||
rngFeeder = make(chan []byte, 0)
|
||||
minFeedEntropy config.IntOption
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.Register(&config.Option{
|
||||
Name: "Minimum Feed Entropy",
|
||||
Key: "random.min_feed_entropy",
|
||||
Description: "The minimum amount of entropy before a entropy source is feed to the RNG, in bits.",
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
OptType: config.OptTypeInt,
|
||||
DefaultValue: 256,
|
||||
ValidationRegex: "^[0-9]{3,5}$",
|
||||
})
|
||||
minFeedEntropy = config.GetAsInt("random.min_feed_entropy", 256)
|
||||
}
|
||||
|
||||
// The Feeder is used to feed entropy to the RNG.
|
||||
type Feeder struct {
|
||||
input chan *entropyData
|
||||
entropy int64
|
||||
needsEntropy *abool.AtomicBool
|
||||
buffer *container.Container
|
||||
}
|
||||
|
||||
type entropyData struct {
|
||||
data []byte
|
||||
entropy int
|
||||
}
|
||||
|
||||
// NewFeeder returns a new entropy Feeder.
|
||||
func NewFeeder() *Feeder {
|
||||
new := &Feeder{
|
||||
input: make(chan *entropyData, 0),
|
||||
needsEntropy: abool.NewBool(true),
|
||||
buffer: container.New(),
|
||||
}
|
||||
go new.run()
|
||||
return new
|
||||
}
|
||||
|
||||
// NeedsEntropy returns whether the feeder is currently gathering entropy.
|
||||
func (f *Feeder) NeedsEntropy() bool {
|
||||
return f.needsEntropy.IsSet()
|
||||
}
|
||||
|
||||
// SupplyEntropy supplies entropy to to the Feeder, it will block until the Feeder has read from it.
|
||||
func (f *Feeder) SupplyEntropy(data []byte, entropy int) {
|
||||
f.input <- &entropyData{
|
||||
data: data,
|
||||
entropy: entropy,
|
||||
}
|
||||
}
|
||||
|
||||
// SupplyEntropyIfNeeded supplies entropy to to the Feeder, but will not block if no entropy is currently needed.
|
||||
func (f *Feeder) SupplyEntropyIfNeeded(data []byte, entropy int) {
|
||||
if f.needsEntropy.IsSet() {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case f.input <- &entropyData{
|
||||
data: data,
|
||||
entropy: entropy,
|
||||
}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// SupplyEntropyAsInt supplies entropy to to the Feeder, it will block until the Feeder has read from it.
|
||||
func (f *Feeder) SupplyEntropyAsInt(n int64, entropy int) {
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(n))
|
||||
f.SupplyEntropy(b, entropy)
|
||||
}
|
||||
|
||||
// SupplyEntropyAsIntIfNeeded supplies entropy to to the Feeder, but will not block if no entropy is currently needed.
|
||||
func (f *Feeder) SupplyEntropyAsIntIfNeeded(n int64, entropy int) {
|
||||
if f.needsEntropy.IsSet() { // avoid allocating a slice if possible
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(n))
|
||||
f.SupplyEntropyIfNeeded(b, entropy)
|
||||
}
|
||||
}
|
||||
|
||||
// CloseFeeder stops the feed processing - the responsible goroutine exits.
|
||||
func (f *Feeder) CloseFeeder() {
|
||||
f.input <- nil
|
||||
}
|
||||
|
||||
func (f *Feeder) run() {
|
||||
defer f.needsEntropy.UnSet()
|
||||
|
||||
for {
|
||||
// gather
|
||||
f.needsEntropy.Set()
|
||||
gather:
|
||||
for {
|
||||
select {
|
||||
case newEntropy := <-f.input:
|
||||
if newEntropy != nil {
|
||||
f.buffer.Append(newEntropy.data)
|
||||
f.entropy += int64(newEntropy.entropy)
|
||||
if f.entropy >= minFeedEntropy() {
|
||||
break gather
|
||||
}
|
||||
}
|
||||
case <-shutdownSignal:
|
||||
return
|
||||
}
|
||||
}
|
||||
// feed
|
||||
f.needsEntropy.UnSet()
|
||||
select {
|
||||
case rngFeeder <- f.buffer.CompileData():
|
||||
case <-shutdownSignal:
|
||||
return
|
||||
}
|
||||
f.buffer = container.New()
|
||||
}
|
||||
}
|
73
crypto/random/entropy_test.go
Normal file
73
crypto/random/entropy_test.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFeeder(t *testing.T) {
|
||||
|
||||
// wait for start / first round to complete
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
f := NewFeeder()
|
||||
|
||||
// go through all functions
|
||||
f.NeedsEntropy()
|
||||
f.SupplyEntropy([]byte{0}, 0)
|
||||
f.SupplyEntropyAsInt(0, 0)
|
||||
f.SupplyEntropyIfNeeded([]byte{0}, 0)
|
||||
f.SupplyEntropyAsIntIfNeeded(0, 0)
|
||||
|
||||
// fill entropy
|
||||
f.SupplyEntropyAsInt(0, 65535)
|
||||
|
||||
// check blocking calls
|
||||
|
||||
waitC := make(chan struct{})
|
||||
go func() {
|
||||
f.SupplyEntropy([]byte{0}, 0)
|
||||
close(waitC)
|
||||
}()
|
||||
select {
|
||||
case <-waitC:
|
||||
t.Error("call does not block!")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
waitC = make(chan struct{})
|
||||
go func() {
|
||||
f.SupplyEntropyAsInt(0, 0)
|
||||
close(waitC)
|
||||
}()
|
||||
select {
|
||||
case <-waitC:
|
||||
t.Error("call does not block!")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
// check non-blocking calls
|
||||
|
||||
waitC = make(chan struct{})
|
||||
go func() {
|
||||
f.SupplyEntropyIfNeeded([]byte{0}, 0)
|
||||
close(waitC)
|
||||
}()
|
||||
select {
|
||||
case <-waitC:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Error("call blocks!")
|
||||
}
|
||||
|
||||
waitC = make(chan struct{})
|
||||
go func() {
|
||||
f.SupplyEntropyAsIntIfNeeded(0, 0)
|
||||
close(waitC)
|
||||
}()
|
||||
select {
|
||||
case <-waitC:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Error("call blocks!")
|
||||
}
|
||||
|
||||
}
|
49
crypto/random/fullfeed.go
Normal file
49
crypto/random/fullfeed.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
fullFeedDuration = 1 * time.Millisecond
|
||||
)
|
||||
|
||||
func getFullFeedDuration() time.Duration {
|
||||
|
||||
// full feed every 5x time of reseedAfterSeconds
|
||||
secsUntilFullFeed := reseedAfterSeconds() * 5
|
||||
|
||||
// full feed at most once per minute
|
||||
if secsUntilFullFeed < 60 {
|
||||
secsUntilFullFeed = 60
|
||||
}
|
||||
|
||||
return time.Duration(secsUntilFullFeed * int64(time.Second))
|
||||
}
|
||||
|
||||
func fullFeeder() {
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-time.After(fullFeedDuration):
|
||||
|
||||
rngLock.Lock()
|
||||
feedAll:
|
||||
for {
|
||||
select {
|
||||
case data := <-rngFeeder:
|
||||
rng.Reseed(data)
|
||||
default:
|
||||
break feedAll
|
||||
}
|
||||
}
|
||||
rngLock.Unlock()
|
||||
|
||||
case <-shutdownSignal:
|
||||
return
|
||||
}
|
||||
|
||||
fullFeedDuration = getFullFeedDuration()
|
||||
|
||||
}
|
||||
}
|
13
crypto/random/fullfeed_test.go
Normal file
13
crypto/random/fullfeed_test.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFullFeeder(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
rngFeeder <- []byte{0}
|
||||
}()
|
||||
}
|
||||
}
|
96
crypto/random/get.go
Normal file
96
crypto/random/get.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/config"
|
||||
)
|
||||
|
||||
var (
|
||||
// Reader provides a global instance to read from the RNG.
|
||||
Reader io.Reader
|
||||
|
||||
rngBytesRead int64
|
||||
rngLastFeed = time.Now()
|
||||
|
||||
reseedAfterSeconds config.IntOption
|
||||
reseedAfterBytes config.IntOption
|
||||
)
|
||||
|
||||
// reader provides an io.Reader interface
|
||||
type reader struct{}
|
||||
|
||||
func init() {
|
||||
config.Register(&config.Option{
|
||||
Name: "Reseed after x seconds",
|
||||
Key: "random.reseed_after_seconds",
|
||||
Description: "Number of seconds until reseed",
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
OptType: config.OptTypeInt,
|
||||
DefaultValue: 360, // ten minutes
|
||||
ValidationRegex: "^[0-9]{2,5}$",
|
||||
})
|
||||
reseedAfterSeconds = config.GetAsInt("random.reseed_after_seconds", 360)
|
||||
|
||||
config.Register(&config.Option{
|
||||
Name: "Reseed after x bytes",
|
||||
Key: "random.reseed_after_bytes",
|
||||
Description: "Number of fetched bytes until reseed",
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
OptType: config.OptTypeInt,
|
||||
DefaultValue: 1000000, // one megabyte
|
||||
ValidationRegex: "^[0-9]{0,9}$",
|
||||
})
|
||||
reseedAfterBytes = config.GetAsInt("random.reseed_after_bytes", 1000000)
|
||||
|
||||
Reader = reader{}
|
||||
}
|
||||
|
||||
func checkEntropy() (err error) {
|
||||
if !rngReady {
|
||||
return errors.New("RNG is not ready yet")
|
||||
}
|
||||
if rngBytesRead > reseedAfterBytes() ||
|
||||
int64(time.Now().Sub(rngLastFeed).Seconds()) > reseedAfterSeconds() {
|
||||
select {
|
||||
case r := <-rngFeeder:
|
||||
rng.Reseed(r)
|
||||
rngBytesRead = 0
|
||||
rngLastFeed = time.Now()
|
||||
case <-time.After(1*time.Second):
|
||||
return errors.New("failed to get new entropy")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads random bytes into the supplied byte slice.
|
||||
func Read(b []byte) (n int, err error) {
|
||||
rngLock.Lock()
|
||||
defer rngLock.Unlock()
|
||||
|
||||
if err := checkEntropy(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return copy(b, rng.PseudoRandomData(uint(len(b)))), nil
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface
|
||||
func (r reader) Read(b []byte) (n int, err error) {
|
||||
return Read(b)
|
||||
}
|
||||
|
||||
// Bytes allocates a new byte slice of given length and fills it with random data.
|
||||
func Bytes(n int) ([]byte, error) {
|
||||
rngLock.Lock()
|
||||
defer rngLock.Unlock()
|
||||
|
||||
if err := checkEntropy(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rng.PseudoRandomData(uint(n)), nil
|
||||
}
|
35
crypto/random/osfeeder.go
Normal file
35
crypto/random/osfeeder.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/log"
|
||||
)
|
||||
|
||||
func osFeeder() {
|
||||
feeder := NewFeeder()
|
||||
for {
|
||||
|
||||
// get feed entropy
|
||||
minEntropyBytes := int(minFeedEntropy())/8 + 1
|
||||
if minEntropyBytes < 32 {
|
||||
minEntropyBytes = 64
|
||||
}
|
||||
|
||||
// get entropy
|
||||
osEntropy := make([]byte, minEntropyBytes)
|
||||
n, err := rand.Read(osEntropy)
|
||||
if err != nil {
|
||||
log.Errorf("could not read entropy from os: %s", err)
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
if n != minEntropyBytes {
|
||||
log.Errorf("could not read enough entropy from os: got only %d bytes instead of %d", n, minEntropyBytes)
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
// feed
|
||||
feeder.SupplyEntropy(osEntropy, minEntropyBytes*8)
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// just (mostly) a proxy for now, awesome stuff comes later
|
||||
|
||||
func Int(randSrc io.Reader, max *big.Int) (n *big.Int, err error) {
|
||||
return rand.Int(randSrc, max)
|
||||
}
|
||||
|
||||
func Prime(randSrc io.Reader, bits int) (p *big.Int, err error) {
|
||||
return rand.Prime(randSrc, bits)
|
||||
}
|
||||
|
||||
func Read(b []byte) (n int, err error) {
|
||||
return rand.Read(b)
|
||||
}
|
||||
|
||||
func Bytes(len int) ([]byte, error) {
|
||||
r := make([]byte, len)
|
||||
_, err := Read(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get random data: %s", err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type Reader struct{}
|
||||
|
||||
func (r Reader) Read(b []byte) (n int, err error) {
|
||||
return Read(b)
|
||||
}
|
78
crypto/random/rng.go
Normal file
78
crypto/random/rng.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/aead/serpent"
|
||||
"github.com/seehuhn/fortuna"
|
||||
|
||||
"github.com/Safing/portbase/config"
|
||||
"github.com/Safing/portbase/modules"
|
||||
)
|
||||
|
||||
var (
|
||||
rng *fortuna.Generator
|
||||
rngLock sync.Mutex
|
||||
rngReady = false
|
||||
rngCipherOption config.StringOption
|
||||
|
||||
shutdownSignal = make(chan struct{}, 0)
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("random", prep, Start, stop)
|
||||
|
||||
config.Register(&config.Option{
|
||||
Name: "RNG Cipher",
|
||||
Key: "random.rng_cipher",
|
||||
Description: "Cipher to use for the Fortuna RNG. Requires restart to take effect.",
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
OptType: config.OptTypeString,
|
||||
DefaultValue: "aes",
|
||||
ValidationRegex: "^(aes|serpent)$",
|
||||
})
|
||||
rngCipherOption = config.GetAsString("random.rng_cipher", "aes")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCipher(key []byte) (cipher.Block, error) {
|
||||
cipher := rngCipherOption()
|
||||
switch cipher {
|
||||
case "aes":
|
||||
return aes.NewCipher(key)
|
||||
case "serpent":
|
||||
return serpent.NewCipher(key)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown or unsupported cipher: %s", cipher)
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the RNG. Normally, this should be only called by the portbase/modules package.
|
||||
func Start() (err error) {
|
||||
rngLock.Lock()
|
||||
defer rngLock.Unlock()
|
||||
|
||||
rng = fortuna.NewGenerator(newCipher)
|
||||
rngReady = true
|
||||
|
||||
// random source: OS
|
||||
go osFeeder()
|
||||
|
||||
// random source: goroutine ticks
|
||||
go tickFeeder()
|
||||
|
||||
// full feeder
|
||||
go fullFeeder()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
return nil
|
||||
}
|
45
crypto/random/rng_test.go
Normal file
45
crypto/random/rng_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Safing/portbase/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
prep()
|
||||
Start()
|
||||
}
|
||||
|
||||
func TestRNG(t *testing.T) {
|
||||
key := make([]byte, 16)
|
||||
|
||||
config.SetConfigOption("random.rng_cipher", "aes")
|
||||
_, err := newCipher(key)
|
||||
if err != nil {
|
||||
t.Errorf("failed to create aes cipher: %s", err)
|
||||
}
|
||||
rng.Reseed(key)
|
||||
|
||||
config.SetConfigOption("random.rng_cipher", "serpent")
|
||||
_, err = newCipher(key)
|
||||
if err != nil {
|
||||
t.Errorf("failed to create serpent cipher: %s", err)
|
||||
}
|
||||
rng.Reseed(key)
|
||||
|
||||
b := make([]byte, 32)
|
||||
_, err = Read(b)
|
||||
if err != nil {
|
||||
t.Errorf("Read failed: %s", err)
|
||||
}
|
||||
_, err = Reader.Read(b)
|
||||
if err != nil {
|
||||
t.Errorf("Read failed: %s", err)
|
||||
}
|
||||
|
||||
_, err = Bytes(32)
|
||||
if err != nil {
|
||||
t.Errorf("Bytes failed: %s", err)
|
||||
}
|
||||
}
|
3
crypto/random/test/.gitignore
vendored
Normal file
3
crypto/random/test/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
test
|
||||
*.bin
|
||||
*.txt
|
280
crypto/random/test/README.md
Normal file
280
crypto/random/test/README.md
Normal file
|
@ -0,0 +1,280 @@
|
|||
# Entropy Testing
|
||||
|
||||
In order verify that the random package actually generates random enough entropy/data, this test program holds the core functions that generate entropy as well as some noise makers to simulate a running program.
|
||||
|
||||
Please also note that output from `tickFeeder` is never used directly but fed as entropy to the actual RNG - `fortuna`.
|
||||
|
||||
With `tickFeeder`, to be sure that the delivered entropy is of high enough quality, only 1 bit of entropy is expected per generated byte - ie. we gather 8 times the amount we need. The following test below is run on the raw output.
|
||||
|
||||
To test the quality of entropy, first generate random data with the test program:
|
||||
|
||||
go build
|
||||
|
||||
./test tickfeeder > output.bin # just the additional entropy feed
|
||||
# OR
|
||||
./test fortuna > output.bin # the actual CSPRNG
|
||||
|
||||
ls -lah output.bin # check filesize: should be ~1MB
|
||||
|
||||
Then, run `dieharder`, a random number generator test tool:
|
||||
|
||||
dieharder -a -f output.bin
|
||||
|
||||
Below you can find two test outputs of `dieharder`.
|
||||
Please note that around 5 tests of `dieharder` normally fail. This is expected and even desired.
|
||||
|
||||
`dieharder` output of 22KB of contextswitch (`go version go1.10.3 linux/amd64` on 23.08.2018):
|
||||
|
||||
#=============================================================================#
|
||||
# dieharder version 3.31.1 Copyright 2003 Robert G. Brown #
|
||||
#=============================================================================#
|
||||
rng_name | filename |rands/second|
|
||||
mt19937| output.bin| 1.00e+08 |
|
||||
#=============================================================================#
|
||||
test_name |ntup| tsamples |psamples| p-value |Assessment
|
||||
#=============================================================================#
|
||||
diehard_birthdays| 0| 100| 100|0.75124818| PASSED
|
||||
diehard_operm5| 0| 1000000| 100|0.71642114| PASSED
|
||||
diehard_rank_32x32| 0| 40000| 100|0.66406749| PASSED
|
||||
diehard_rank_6x8| 0| 100000| 100|0.79742497| PASSED
|
||||
diehard_bitstream| 0| 2097152| 100|0.68336079| PASSED
|
||||
diehard_opso| 0| 2097152| 100|0.99670345| WEAK
|
||||
diehard_oqso| 0| 2097152| 100|0.85930861| PASSED
|
||||
diehard_dna| 0| 2097152| 100|0.77857540| PASSED
|
||||
diehard_count_1s_str| 0| 256000| 100|0.27851730| PASSED
|
||||
diehard_count_1s_byt| 0| 256000| 100|0.29570009| PASSED
|
||||
diehard_parking_lot| 0| 12000| 100|0.51526020| PASSED
|
||||
diehard_2dsphere| 2| 8000| 100|0.49199324| PASSED
|
||||
diehard_3dsphere| 3| 4000| 100|0.99008122| PASSED
|
||||
diehard_squeeze| 0| 100000| 100|0.95518110| PASSED
|
||||
diehard_sums| 0| 100| 100|0.00015930| WEAK
|
||||
diehard_runs| 0| 100000| 100|0.50091086| PASSED
|
||||
diehard_runs| 0| 100000| 100|0.44091340| PASSED
|
||||
diehard_craps| 0| 200000| 100|0.77284264| PASSED
|
||||
diehard_craps| 0| 200000| 100|0.71027434| PASSED
|
||||
marsaglia_tsang_gcd| 0| 10000000| 100|0.38138922| PASSED
|
||||
marsaglia_tsang_gcd| 0| 10000000| 100|0.36661590| PASSED
|
||||
sts_monobit| 1| 100000| 100|0.06209802| PASSED
|
||||
sts_runs| 2| 100000| 100|0.82506539| PASSED
|
||||
sts_serial| 1| 100000| 100|0.99198615| PASSED
|
||||
sts_serial| 2| 100000| 100|0.85604831| PASSED
|
||||
sts_serial| 3| 100000| 100|0.06613657| PASSED
|
||||
sts_serial| 3| 100000| 100|0.16787860| PASSED
|
||||
sts_serial| 4| 100000| 100|0.45227401| PASSED
|
||||
sts_serial| 4| 100000| 100|0.43529092| PASSED
|
||||
sts_serial| 5| 100000| 100|0.99912474| WEAK
|
||||
sts_serial| 5| 100000| 100|0.94754128| PASSED
|
||||
sts_serial| 6| 100000| 100|0.98406523| PASSED
|
||||
sts_serial| 6| 100000| 100|0.92895983| PASSED
|
||||
sts_serial| 7| 100000| 100|0.45965410| PASSED
|
||||
sts_serial| 7| 100000| 100|0.64185152| PASSED
|
||||
sts_serial| 8| 100000| 100|0.57922926| PASSED
|
||||
sts_serial| 8| 100000| 100|0.52390292| PASSED
|
||||
sts_serial| 9| 100000| 100|0.82722325| PASSED
|
||||
sts_serial| 9| 100000| 100|0.89384819| PASSED
|
||||
sts_serial| 10| 100000| 100|0.79877889| PASSED
|
||||
sts_serial| 10| 100000| 100|0.49562348| PASSED
|
||||
sts_serial| 11| 100000| 100|0.09217966| PASSED
|
||||
sts_serial| 11| 100000| 100|0.00342361| WEAK
|
||||
sts_serial| 12| 100000| 100|0.60119444| PASSED
|
||||
sts_serial| 12| 100000| 100|0.20420318| PASSED
|
||||
sts_serial| 13| 100000| 100|0.76867489| PASSED
|
||||
sts_serial| 13| 100000| 100|0.35717970| PASSED
|
||||
sts_serial| 14| 100000| 100|0.67364089| PASSED
|
||||
sts_serial| 14| 100000| 100|0.98667204| PASSED
|
||||
sts_serial| 15| 100000| 100|0.24328833| PASSED
|
||||
sts_serial| 15| 100000| 100|0.52098866| PASSED
|
||||
sts_serial| 16| 100000| 100|0.48845863| PASSED
|
||||
sts_serial| 16| 100000| 100|0.61943558| PASSED
|
||||
rgb_bitdist| 1| 100000| 100|0.24694812| PASSED
|
||||
rgb_bitdist| 2| 100000| 100|0.75873723| PASSED
|
||||
rgb_bitdist| 3| 100000| 100|0.28670990| PASSED
|
||||
rgb_bitdist| 4| 100000| 100|0.41966273| PASSED
|
||||
rgb_bitdist| 5| 100000| 100|0.80463973| PASSED
|
||||
rgb_bitdist| 6| 100000| 100|0.44747725| PASSED
|
||||
rgb_bitdist| 7| 100000| 100|0.35848420| PASSED
|
||||
rgb_bitdist| 8| 100000| 100|0.56585089| PASSED
|
||||
rgb_bitdist| 9| 100000| 100|0.23179559| PASSED
|
||||
rgb_bitdist| 10| 100000| 100|0.83369283| PASSED
|
||||
rgb_bitdist| 11| 100000| 100|0.74761235| PASSED
|
||||
rgb_bitdist| 12| 100000| 100|0.50477673| PASSED
|
||||
rgb_minimum_distance| 2| 10000| 1000|0.29527530| PASSED
|
||||
rgb_minimum_distance| 3| 10000| 1000|0.83681186| PASSED
|
||||
rgb_minimum_distance| 4| 10000| 1000|0.85939646| PASSED
|
||||
rgb_minimum_distance| 5| 10000| 1000|0.90229335| PASSED
|
||||
rgb_permutations| 2| 100000| 100|0.99010460| PASSED
|
||||
rgb_permutations| 3| 100000| 100|0.99360922| PASSED
|
||||
rgb_permutations| 4| 100000| 100|0.30113906| PASSED
|
||||
rgb_permutations| 5| 100000| 100|0.60701235| PASSED
|
||||
rgb_lagged_sum| 0| 1000000| 100|0.37080580| PASSED
|
||||
rgb_lagged_sum| 1| 1000000| 100|0.91852932| PASSED
|
||||
rgb_lagged_sum| 2| 1000000| 100|0.74568323| PASSED
|
||||
rgb_lagged_sum| 3| 1000000| 100|0.64070201| PASSED
|
||||
rgb_lagged_sum| 4| 1000000| 100|0.53802729| PASSED
|
||||
rgb_lagged_sum| 5| 1000000| 100|0.67865656| PASSED
|
||||
rgb_lagged_sum| 6| 1000000| 100|0.85161494| PASSED
|
||||
rgb_lagged_sum| 7| 1000000| 100|0.37312323| PASSED
|
||||
rgb_lagged_sum| 8| 1000000| 100|0.17841759| PASSED
|
||||
rgb_lagged_sum| 9| 1000000| 100|0.85795513| PASSED
|
||||
rgb_lagged_sum| 10| 1000000| 100|0.79843176| PASSED
|
||||
rgb_lagged_sum| 11| 1000000| 100|0.21320830| PASSED
|
||||
rgb_lagged_sum| 12| 1000000| 100|0.94709672| PASSED
|
||||
rgb_lagged_sum| 13| 1000000| 100|0.12600611| PASSED
|
||||
rgb_lagged_sum| 14| 1000000| 100|0.26780352| PASSED
|
||||
rgb_lagged_sum| 15| 1000000| 100|0.07862730| PASSED
|
||||
rgb_lagged_sum| 16| 1000000| 100|0.21102254| PASSED
|
||||
rgb_lagged_sum| 17| 1000000| 100|0.82967141| PASSED
|
||||
rgb_lagged_sum| 18| 1000000| 100|0.05818566| PASSED
|
||||
rgb_lagged_sum| 19| 1000000| 100|0.01010140| PASSED
|
||||
rgb_lagged_sum| 20| 1000000| 100|0.17941782| PASSED
|
||||
rgb_lagged_sum| 21| 1000000| 100|0.98442639| PASSED
|
||||
rgb_lagged_sum| 22| 1000000| 100|0.30352772| PASSED
|
||||
rgb_lagged_sum| 23| 1000000| 100|0.56855155| PASSED
|
||||
rgb_lagged_sum| 24| 1000000| 100|0.27280405| PASSED
|
||||
rgb_lagged_sum| 25| 1000000| 100|0.41141889| PASSED
|
||||
rgb_lagged_sum| 26| 1000000| 100|0.25389013| PASSED
|
||||
rgb_lagged_sum| 27| 1000000| 100|0.10313177| PASSED
|
||||
rgb_lagged_sum| 28| 1000000| 100|0.76610028| PASSED
|
||||
rgb_lagged_sum| 29| 1000000| 100|0.97903830| PASSED
|
||||
rgb_lagged_sum| 30| 1000000| 100|0.51216732| PASSED
|
||||
rgb_lagged_sum| 31| 1000000| 100|0.98578832| PASSED
|
||||
rgb_lagged_sum| 32| 1000000| 100|0.95078719| PASSED
|
||||
rgb_kstest_test| 0| 10000| 1000|0.24930712| PASSED
|
||||
dab_bytedistrib| 0| 51200000| 1|0.51100031| PASSED
|
||||
dab_dct| 256| 50000| 1|0.28794956| PASSED
|
||||
Preparing to run test 207. ntuple = 0
|
||||
dab_filltree| 32| 15000000| 1|0.93283449| PASSED
|
||||
dab_filltree| 32| 15000000| 1|0.36488075| PASSED
|
||||
Preparing to run test 208. ntuple = 0
|
||||
dab_filltree2| 0| 5000000| 1|0.94036105| PASSED
|
||||
dab_filltree2| 1| 5000000| 1|0.30118240| PASSED
|
||||
Preparing to run test 209. ntuple = 0
|
||||
dab_monobit2| 12| 65000000| 1|0.00209003| WEAK
|
||||
|
||||
`dieharder` of 1MB of fortuna (`go version go1.10.3 linux/amd64` on 23.08.2018):
|
||||
|
||||
#=============================================================================#
|
||||
# dieharder version 3.31.1 Copyright 2003 Robert G. Brown #
|
||||
#=============================================================================#
|
||||
rng_name | filename |rands/second|
|
||||
mt19937| output.bin| 8.44e+07 |
|
||||
#=============================================================================#
|
||||
test_name |ntup| tsamples |psamples| p-value |Assessment
|
||||
#=============================================================================#
|
||||
diehard_birthdays| 0| 100| 100|0.94302153| PASSED
|
||||
diehard_operm5| 0| 1000000| 100|0.08378380| PASSED
|
||||
diehard_rank_32x32| 0| 40000| 100|0.02062049| PASSED
|
||||
diehard_rank_6x8| 0| 100000| 100|0.43787871| PASSED
|
||||
diehard_bitstream| 0| 2097152| 100|0.15713023| PASSED
|
||||
diehard_opso| 0| 2097152| 100|0.79331996| PASSED
|
||||
diehard_oqso| 0| 2097152| 100|0.54138750| PASSED
|
||||
diehard_dna| 0| 2097152| 100|0.06957205| PASSED
|
||||
diehard_count_1s_str| 0| 256000| 100|0.21653644| PASSED
|
||||
diehard_count_1s_byt| 0| 256000| 100|0.96539542| PASSED
|
||||
diehard_parking_lot| 0| 12000| 100|0.21306362| PASSED
|
||||
diehard_2dsphere| 2| 8000| 100|0.40750466| PASSED
|
||||
diehard_3dsphere| 3| 4000| 100|0.99827314| WEAK
|
||||
diehard_squeeze| 0| 100000| 100|0.70994607| PASSED
|
||||
diehard_sums| 0| 100| 100|0.42729005| PASSED
|
||||
diehard_runs| 0| 100000| 100|0.08118125| PASSED
|
||||
diehard_runs| 0| 100000| 100|0.99226204| PASSED
|
||||
diehard_craps| 0| 200000| 100|0.49803401| PASSED
|
||||
diehard_craps| 0| 200000| 100|0.84011191| PASSED
|
||||
marsaglia_tsang_gcd| 0| 10000000| 100|0.40135552| PASSED
|
||||
marsaglia_tsang_gcd| 0| 10000000| 100|0.53311975| PASSED
|
||||
sts_monobit| 1| 100000| 100|0.96903259| PASSED
|
||||
sts_runs| 2| 100000| 100|0.55734041| PASSED
|
||||
sts_serial| 1| 100000| 100|0.69041819| PASSED
|
||||
sts_serial| 2| 100000| 100|0.61728694| PASSED
|
||||
sts_serial| 3| 100000| 100|0.70299864| PASSED
|
||||
sts_serial| 3| 100000| 100|0.36332027| PASSED
|
||||
sts_serial| 4| 100000| 100|0.57627216| PASSED
|
||||
sts_serial| 4| 100000| 100|0.95046929| PASSED
|
||||
sts_serial| 5| 100000| 100|0.79824554| PASSED
|
||||
sts_serial| 5| 100000| 100|0.62786166| PASSED
|
||||
sts_serial| 6| 100000| 100|0.84103529| PASSED
|
||||
sts_serial| 6| 100000| 100|0.89083859| PASSED
|
||||
sts_serial| 7| 100000| 100|0.69686380| PASSED
|
||||
sts_serial| 7| 100000| 100|0.79436099| PASSED
|
||||
sts_serial| 8| 100000| 100|0.84082295| PASSED
|
||||
sts_serial| 8| 100000| 100|0.95915719| PASSED
|
||||
sts_serial| 9| 100000| 100|0.48200567| PASSED
|
||||
sts_serial| 9| 100000| 100|0.10836112| PASSED
|
||||
sts_serial| 10| 100000| 100|0.45470523| PASSED
|
||||
sts_serial| 10| 100000| 100|0.97608829| PASSED
|
||||
sts_serial| 11| 100000| 100|0.89344380| PASSED
|
||||
sts_serial| 11| 100000| 100|0.31959825| PASSED
|
||||
sts_serial| 12| 100000| 100|0.43415812| PASSED
|
||||
sts_serial| 12| 100000| 100|0.27845148| PASSED
|
||||
sts_serial| 13| 100000| 100|0.50590833| PASSED
|
||||
sts_serial| 13| 100000| 100|0.39585514| PASSED
|
||||
sts_serial| 14| 100000| 100|0.55566778| PASSED
|
||||
sts_serial| 14| 100000| 100|0.57138798| PASSED
|
||||
sts_serial| 15| 100000| 100|0.12315118| PASSED
|
||||
sts_serial| 15| 100000| 100|0.41728831| PASSED
|
||||
sts_serial| 16| 100000| 100|0.23202389| PASSED
|
||||
sts_serial| 16| 100000| 100|0.84883373| PASSED
|
||||
rgb_bitdist| 1| 100000| 100|0.45137388| PASSED
|
||||
rgb_bitdist| 2| 100000| 100|0.93984739| PASSED
|
||||
rgb_bitdist| 3| 100000| 100|0.85148557| PASSED
|
||||
rgb_bitdist| 4| 100000| 100|0.77062397| PASSED
|
||||
rgb_bitdist| 5| 100000| 100|0.79511260| PASSED
|
||||
rgb_bitdist| 6| 100000| 100|0.86150140| PASSED
|
||||
rgb_bitdist| 7| 100000| 100|0.98572979| PASSED
|
||||
rgb_bitdist| 8| 100000| 100|0.73302973| PASSED
|
||||
rgb_bitdist| 9| 100000| 100|0.39660028| PASSED
|
||||
rgb_bitdist| 10| 100000| 100|0.13167592| PASSED
|
||||
rgb_bitdist| 11| 100000| 100|0.87937846| PASSED
|
||||
rgb_bitdist| 12| 100000| 100|0.80619403| PASSED
|
||||
rgb_minimum_distance| 2| 10000| 1000|0.38189429| PASSED
|
||||
rgb_minimum_distance| 3| 10000| 1000|0.21164619| PASSED
|
||||
rgb_minimum_distance| 4| 10000| 1000|0.91875064| PASSED
|
||||
rgb_minimum_distance| 5| 10000| 1000|0.27897081| PASSED
|
||||
rgb_permutations| 2| 100000| 100|0.22927506| PASSED
|
||||
rgb_permutations| 3| 100000| 100|0.80827585| PASSED
|
||||
rgb_permutations| 4| 100000| 100|0.38750474| PASSED
|
||||
rgb_permutations| 5| 100000| 100|0.18938169| PASSED
|
||||
rgb_lagged_sum| 0| 1000000| 100|0.72234187| PASSED
|
||||
rgb_lagged_sum| 1| 1000000| 100|0.28633796| PASSED
|
||||
rgb_lagged_sum| 2| 1000000| 100|0.52961866| PASSED
|
||||
rgb_lagged_sum| 3| 1000000| 100|0.99876080| WEAK
|
||||
rgb_lagged_sum| 4| 1000000| 100|0.39603203| PASSED
|
||||
rgb_lagged_sum| 5| 1000000| 100|0.01004618| PASSED
|
||||
rgb_lagged_sum| 6| 1000000| 100|0.89539065| PASSED
|
||||
rgb_lagged_sum| 7| 1000000| 100|0.55558774| PASSED
|
||||
rgb_lagged_sum| 8| 1000000| 100|0.40063365| PASSED
|
||||
rgb_lagged_sum| 9| 1000000| 100|0.30905028| PASSED
|
||||
rgb_lagged_sum| 10| 1000000| 100|0.31161899| PASSED
|
||||
rgb_lagged_sum| 11| 1000000| 100|0.76729775| PASSED
|
||||
rgb_lagged_sum| 12| 1000000| 100|0.36416009| PASSED
|
||||
rgb_lagged_sum| 13| 1000000| 100|0.21062168| PASSED
|
||||
rgb_lagged_sum| 14| 1000000| 100|0.17580591| PASSED
|
||||
rgb_lagged_sum| 15| 1000000| 100|0.54465457| PASSED
|
||||
rgb_lagged_sum| 16| 1000000| 100|0.39394806| PASSED
|
||||
rgb_lagged_sum| 17| 1000000| 100|0.81572681| PASSED
|
||||
rgb_lagged_sum| 18| 1000000| 100|0.98821505| PASSED
|
||||
rgb_lagged_sum| 19| 1000000| 100|0.86755786| PASSED
|
||||
rgb_lagged_sum| 20| 1000000| 100|0.37832948| PASSED
|
||||
rgb_lagged_sum| 21| 1000000| 100|0.52001140| PASSED
|
||||
rgb_lagged_sum| 22| 1000000| 100|0.83595676| PASSED
|
||||
rgb_lagged_sum| 23| 1000000| 100|0.22643336| PASSED
|
||||
rgb_lagged_sum| 24| 1000000| 100|0.96475696| PASSED
|
||||
rgb_lagged_sum| 25| 1000000| 100|0.49570837| PASSED
|
||||
rgb_lagged_sum| 26| 1000000| 100|0.71327165| PASSED
|
||||
rgb_lagged_sum| 27| 1000000| 100|0.07344404| PASSED
|
||||
rgb_lagged_sum| 28| 1000000| 100|0.86374872| PASSED
|
||||
rgb_lagged_sum| 29| 1000000| 100|0.24892548| PASSED
|
||||
rgb_lagged_sum| 30| 1000000| 100|0.14314375| PASSED
|
||||
rgb_lagged_sum| 31| 1000000| 100|0.27884009| PASSED
|
||||
rgb_lagged_sum| 32| 1000000| 100|0.66637341| PASSED
|
||||
rgb_kstest_test| 0| 10000| 1000|0.13954587| PASSED
|
||||
dab_bytedistrib| 0| 51200000| 1|0.54278716| PASSED
|
||||
dab_dct| 256| 50000| 1|0.71177390| PASSED
|
||||
Preparing to run test 207. ntuple = 0
|
||||
dab_filltree| 32| 15000000| 1|0.51006153| PASSED
|
||||
dab_filltree| 32| 15000000| 1|0.91162889| PASSED
|
||||
Preparing to run test 208. ntuple = 0
|
||||
dab_filltree2| 0| 5000000| 1|0.15507188| PASSED
|
||||
dab_filltree2| 1| 5000000| 1|0.16787382| PASSED
|
||||
Preparing to run test 209. ntuple = 0
|
||||
dab_monobit2| 12| 65000000| 1|0.28347219| PASSED
|
121
crypto/random/test/main.go
Normal file
121
crypto/random/test/main.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/crypto/random"
|
||||
)
|
||||
|
||||
func noise() {
|
||||
// do some aes ctr for noise
|
||||
|
||||
key, _ := hex.DecodeString("6368616e676520746869732070617373")
|
||||
data := []byte("some plaintext x")
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
for {
|
||||
stream.XORKeyStream(data, data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
// generates 1MB and writes to stdout
|
||||
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Printf("usage: ./%s {fortuna|tickfeeder}\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Stderr.WriteString("writing 1MB to stdout, a \".\" will be printed at every 1024 bytes.\n")
|
||||
|
||||
var bytesWritten int
|
||||
|
||||
switch os.Args[1] {
|
||||
case "fortuna":
|
||||
|
||||
random.Start()
|
||||
|
||||
for {
|
||||
b, err := random.Bytes(64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
|
||||
bytesWritten += 64
|
||||
if bytesWritten%1024 == 0 {
|
||||
os.Stderr.WriteString(".")
|
||||
}
|
||||
if bytesWritten%65536 == 0 {
|
||||
fmt.Fprintf(os.Stderr, "\n%d bytes written\n", bytesWritten)
|
||||
}
|
||||
if bytesWritten >= 1000000 {
|
||||
os.Stderr.WriteString("\n")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
case "tickfeeder":
|
||||
|
||||
go noise()
|
||||
|
||||
var value int64
|
||||
var pushes int
|
||||
|
||||
for {
|
||||
time.Sleep(10 * time.Nanosecond)
|
||||
|
||||
value = (value << 1) | (time.Now().UnixNano() % 2)
|
||||
pushes++
|
||||
|
||||
if pushes >= 64 {
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(value))
|
||||
// fmt.Fprintf(os.Stderr, "write: %d\n", value)
|
||||
os.Stdout.Write(b)
|
||||
bytesWritten += 8
|
||||
if bytesWritten%1024 == 0 {
|
||||
os.Stderr.WriteString(".")
|
||||
}
|
||||
if bytesWritten%65536 == 0 {
|
||||
fmt.Fprintf(os.Stderr, "\n%d bytes written\n", bytesWritten)
|
||||
}
|
||||
pushes = 0
|
||||
}
|
||||
|
||||
if bytesWritten >= 1000000 {
|
||||
os.Stderr.WriteString("\n")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
default:
|
||||
fmt.Printf("usage: %s {fortuna|tickfeeder}\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
61
crypto/random/tickfeeder.go
Normal file
61
crypto/random/tickfeeder.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
tickDuration = 1 * time.Millisecond
|
||||
)
|
||||
|
||||
func getTickDuration() time.Duration {
|
||||
|
||||
// be ready in 1/10 time of reseedAfterSeconds
|
||||
msecsAvailable := reseedAfterSeconds() * 100
|
||||
// ex.: reseed after 10 minutes: msecsAvailable = 36000
|
||||
// have full entropy after 5 minutes
|
||||
|
||||
// one tick generates 0,125 bits of entropy
|
||||
ticksNeeded := minFeedEntropy() * 8
|
||||
// ex.: minimum entropy is 256: ticksNeeded = 2048
|
||||
|
||||
// msces between ticks
|
||||
tickMsecs := msecsAvailable / ticksNeeded
|
||||
// ex.: tickMsecs = 17(,578125)
|
||||
|
||||
// use a minimum of 10 msecs per tick for good entropy
|
||||
// it would take 21 seconds to get full 256 bits of entropy with 10msec ticks
|
||||
if tickMsecs < 10 {
|
||||
tickMsecs = 10
|
||||
}
|
||||
|
||||
return time.Duration(tickMsecs * int64(time.Millisecond))
|
||||
}
|
||||
|
||||
// tickFeeder is a really simple entropy feeder that adds the least significant bit of the current nanosecond unixtime to its pool every time it 'ticks'.
|
||||
// The more work the program does, the better the quality, as the internal schedular cannot immediately run the goroutine when it's ready.
|
||||
func tickFeeder() {
|
||||
|
||||
var value int64
|
||||
var pushes int
|
||||
feeder := NewFeeder()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(tickDuration):
|
||||
|
||||
value = (value << 1) | (time.Now().UnixNano() % 2)
|
||||
|
||||
pushes++
|
||||
if pushes >= 64 {
|
||||
feeder.SupplyEntropyAsInt(value, 8)
|
||||
pushes = 0
|
||||
}
|
||||
|
||||
tickDuration = getTickDuration()
|
||||
|
||||
case <-shutdownSignal:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue