common/random.go supports seeding and *Rand (#121)

* common/random.go supports seeding and *Rand
* Ensure determinism
This commit is contained in:
Jae Kwon
2018-03-17 04:28:53 -07:00
committed by GitHub
parent d289c9286e
commit 536c27de8e
2 changed files with 198 additions and 93 deletions

View File

@ -13,34 +13,138 @@ const (
// pseudo random number generator. // pseudo random number generator.
// seeded with OS randomness (crand) // seeded with OS randomness (crand)
var prng struct {
type Rand struct {
sync.Mutex sync.Mutex
*mrand.Rand *mrand.Rand
} }
func reset() { var grand *Rand
b := cRandBytes(8)
var seed uint64
for i := 0; i < 8; i++ {
seed |= uint64(b[i])
seed <<= 8
}
prng.Lock()
prng.Rand = mrand.New(mrand.NewSource(int64(seed)))
prng.Unlock()
}
func init() { func init() {
reset() grand = New()
grand.init()
}
func New() *Rand {
rand := &Rand{}
rand.init()
return rand
}
func (r *Rand) init() {
bz := cRandBytes(8)
var seed uint64
for i := 0; i < 8; i++ {
seed |= uint64(bz[i])
seed <<= 8
}
r.reset(int64(seed))
}
func (r *Rand) reset(seed int64) {
r.Rand = mrand.New(mrand.NewSource(seed))
}
//----------------------------------------
// Global functions
func Seed(seed int64) {
grand.Seed(seed)
}
func RandStr(length int) string {
return grand.RandStr(length)
}
func RandUint16() uint16 {
return grand.RandUint16()
}
func RandUint32() uint32 {
return grand.RandUint32()
}
func RandUint64() uint64 {
return grand.RandUint64()
}
func RandUint() uint {
return grand.RandUint()
}
func RandInt16() int16 {
return grand.RandInt16()
}
func RandInt32() int32 {
return grand.RandInt32()
}
func RandInt64() int64 {
return grand.RandInt64()
}
func RandInt() int {
return grand.RandInt()
}
func RandInt31() int32 {
return grand.RandInt31()
}
func RandInt63() int64 {
return grand.RandInt63()
}
func RandUint16Exp() uint16 {
return grand.RandUint16Exp()
}
func RandUint32Exp() uint32 {
return grand.RandUint32Exp()
}
func RandUint64Exp() uint64 {
return grand.RandUint64Exp()
}
func RandFloat32() float32 {
return grand.RandFloat32()
}
func RandTime() time.Time {
return grand.RandTime()
}
func RandBytes(n int) []byte {
return grand.RandBytes(n)
}
func RandIntn(n int) int {
return grand.RandIntn(n)
}
func RandPerm(n int) []int {
return grand.RandPerm(n)
}
//----------------------------------------
// Rand methods
func (r *Rand) Seed(seed int64) {
r.Lock()
r.reset(seed)
r.Unlock()
} }
// Constructs an alphanumeric string of given length. // Constructs an alphanumeric string of given length.
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandStr(length int) string { func (r *Rand) RandStr(length int) string {
chars := []byte{} chars := []byte{}
MAIN_LOOP: MAIN_LOOP:
for { for {
val := RandInt63() val := r.RandInt63()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
v := int(val & 0x3f) // rightmost 6 bits v := int(val & 0x3f) // rightmost 6 bits
if v >= 62 { // only 62 characters in strChars if v >= 62 { // only 62 characters in strChars
@ -60,127 +164,127 @@ MAIN_LOOP:
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint16() uint16 { func (r *Rand) RandUint16() uint16 {
return uint16(RandUint32() & (1<<16 - 1)) return uint16(r.RandUint32() & (1<<16 - 1))
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint32() uint32 { func (r *Rand) RandUint32() uint32 {
prng.Lock() r.Lock()
u32 := prng.Uint32() u32 := r.Uint32()
prng.Unlock() r.Unlock()
return u32 return u32
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint64() uint64 { func (r *Rand) RandUint64() uint64 {
return uint64(RandUint32())<<32 + uint64(RandUint32()) return uint64(r.RandUint32())<<32 + uint64(r.RandUint32())
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint() uint { func (r *Rand) RandUint() uint {
prng.Lock() r.Lock()
i := prng.Int() i := r.Int()
prng.Unlock() r.Unlock()
return uint(i) return uint(i)
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandInt16() int16 { func (r *Rand) RandInt16() int16 {
return int16(RandUint32() & (1<<16 - 1)) return int16(r.RandUint32() & (1<<16 - 1))
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandInt32() int32 { func (r *Rand) RandInt32() int32 {
return int32(RandUint32()) return int32(r.RandUint32())
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandInt64() int64 { func (r *Rand) RandInt64() int64 {
return int64(RandUint64()) return int64(r.RandUint64())
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandInt() int { func (r *Rand) RandInt() int {
prng.Lock() r.Lock()
i := prng.Int() i := r.Int()
prng.Unlock() r.Unlock()
return i return i
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandInt31() int32 { func (r *Rand) RandInt31() int32 {
prng.Lock() r.Lock()
i31 := prng.Int31() i31 := r.Int31()
prng.Unlock() r.Unlock()
return i31 return i31
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandInt63() int64 { func (r *Rand) RandInt63() int64 {
prng.Lock() r.Lock()
i63 := prng.Int63() i63 := r.Int63()
prng.Unlock() r.Unlock()
return i63 return i63
} }
// Distributed pseudo-exponentially to test for various cases // Distributed pseudo-exponentially to test for various cases
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint16Exp() uint16 { func (r *Rand) RandUint16Exp() uint16 {
bits := RandUint32() % 16 bits := r.RandUint32() % 16
if bits == 0 { if bits == 0 {
return 0 return 0
} }
n := uint16(1 << (bits - 1)) n := uint16(1 << (bits - 1))
n += uint16(RandInt31()) & ((1 << (bits - 1)) - 1) n += uint16(r.RandInt31()) & ((1 << (bits - 1)) - 1)
return n return n
} }
// Distributed pseudo-exponentially to test for various cases // Distributed pseudo-exponentially to test for various cases
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint32Exp() uint32 { func (r *Rand) RandUint32Exp() uint32 {
bits := RandUint32() % 32 bits := r.RandUint32() % 32
if bits == 0 { if bits == 0 {
return 0 return 0
} }
n := uint32(1 << (bits - 1)) n := uint32(1 << (bits - 1))
n += uint32(RandInt31()) & ((1 << (bits - 1)) - 1) n += uint32(r.RandInt31()) & ((1 << (bits - 1)) - 1)
return n return n
} }
// Distributed pseudo-exponentially to test for various cases // Distributed pseudo-exponentially to test for various cases
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandUint64Exp() uint64 { func (r *Rand) RandUint64Exp() uint64 {
bits := RandUint32() % 64 bits := r.RandUint32() % 64
if bits == 0 { if bits == 0 {
return 0 return 0
} }
n := uint64(1 << (bits - 1)) n := uint64(1 << (bits - 1))
n += uint64(RandInt63()) & ((1 << (bits - 1)) - 1) n += uint64(r.RandInt63()) & ((1 << (bits - 1)) - 1)
return n return n
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandFloat32() float32 { func (r *Rand) RandFloat32() float32 {
prng.Lock() r.Lock()
f32 := prng.Float32() f32 := r.Float32()
prng.Unlock() r.Unlock()
return f32 return f32
} }
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandTime() time.Time { func (r *Rand) RandTime() time.Time {
return time.Unix(int64(RandUint64Exp()), 0) return time.Unix(int64(r.RandUint64Exp()), 0)
} }
// RandBytes returns n random bytes from the OS's source of entropy ie. via crypto/rand. // RandBytes returns n random bytes from the OS's source of entropy ie. via crypto/rand.
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandBytes(n int) []byte { func (r *Rand) RandBytes(n int) []byte {
// cRandBytes isn't guaranteed to be fast so instead // cRandBytes isn't guaranteed to be fast so instead
// use random bytes generated from the internal PRNG // use random bytes generated from the internal PRNG
bs := make([]byte, n) bs := make([]byte, n)
for i := 0; i < len(bs); i++ { for i := 0; i < len(bs); i++ {
bs[i] = byte(RandInt() & 0xFF) bs[i] = byte(r.RandInt() & 0xFF)
} }
return bs return bs
} }
@ -188,19 +292,19 @@ func RandBytes(n int) []byte {
// RandIntn returns, as an int, a non-negative pseudo-random number in [0, n). // RandIntn returns, as an int, a non-negative pseudo-random number in [0, n).
// It panics if n <= 0. // It panics if n <= 0.
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandIntn(n int) int { func (r *Rand) RandIntn(n int) int {
prng.Lock() r.Lock()
i := prng.Intn(n) i := r.Intn(n)
prng.Unlock() r.Unlock()
return i return i
} }
// RandPerm returns a pseudo-random permutation of n integers in [0, n). // RandPerm returns a pseudo-random permutation of n integers in [0, n).
// It is not safe for cryptographic usage. // It is not safe for cryptographic usage.
func RandPerm(n int) []int { func (r *Rand) RandPerm(n int) []int {
prng.Lock() r.Lock()
perm := prng.Perm(n) perm := r.Perm(n)
prng.Unlock() r.Unlock()
return perm return perm
} }

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
mrand "math/rand" mrand "math/rand"
"sync" "sync"
"testing" "testing"
@ -33,37 +32,38 @@ func TestRandIntn(t *testing.T) {
} }
} }
// It is essential that these tests run and never repeat their outputs // Test to make sure that we never call math.rand().
// lest we've been pwned and the behavior of our randomness is controlled. // We do this by ensuring that outputs are deterministic.
// See Issues: func TestDeterminism(t *testing.T) {
// * https://github.com/tendermint/tmlibs/issues/99 var firstOutput string
// * https://github.com/tendermint/tendermint/issues/973
func TestUniqueRng(t *testing.T) { // Set math/rand's seed for the sake of debugging this test.
buf := new(bytes.Buffer) // (It isn't strictly necessary).
outputs := make(map[string][]int) mrand.Seed(1)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
testThemAll(buf) output := testThemAll()
output := buf.String() if i == 0 {
buf.Reset() firstOutput = output
runs, seen := outputs[output] } else {
if seen { if firstOutput != output {
t.Errorf("Run #%d's output was already seen in previous runs: %v", i, runs) t.Errorf("Run #%d's output was different from first run.\nfirst: %v\nlast: %v",
i, firstOutput, output)
}
} }
outputs[output] = append(outputs[output], i)
} }
} }
func testThemAll(out io.Writer) { func testThemAll() string {
// Reset the internal PRNG
reset()
// Set math/rand's Seed so that any direct invocations // Such determinism.
// of math/rand will reveal themselves. grand.reset(1)
mrand.Seed(1)
// Use it.
out := new(bytes.Buffer)
perm := RandPerm(10) perm := RandPerm(10)
blob, _ := json.Marshal(perm) blob, _ := json.Marshal(perm)
fmt.Fprintf(out, "perm: %s\n", blob) fmt.Fprintf(out, "perm: %s\n", blob)
fmt.Fprintf(out, "randInt: %d\n", RandInt()) fmt.Fprintf(out, "randInt: %d\n", RandInt())
fmt.Fprintf(out, "randUint: %d\n", RandUint()) fmt.Fprintf(out, "randUint: %d\n", RandUint())
fmt.Fprintf(out, "randIntn: %d\n", RandIntn(97)) fmt.Fprintf(out, "randIntn: %d\n", RandIntn(97))
@ -76,6 +76,7 @@ func testThemAll(out io.Writer) {
fmt.Fprintf(out, "randUint16Exp: %d\n", RandUint16Exp()) fmt.Fprintf(out, "randUint16Exp: %d\n", RandUint16Exp())
fmt.Fprintf(out, "randUint32Exp: %d\n", RandUint32Exp()) fmt.Fprintf(out, "randUint32Exp: %d\n", RandUint32Exp())
fmt.Fprintf(out, "randUint64Exp: %d\n", RandUint64Exp()) fmt.Fprintf(out, "randUint64Exp: %d\n", RandUint64Exp())
return out.String()
} }
func TestRngConcurrencySafety(t *testing.T) { func TestRngConcurrencySafety(t *testing.T) {