diff --git a/common/random.go b/common/random.go index ca71b614..b945a88e 100644 --- a/common/random.go +++ b/common/random.go @@ -13,34 +13,138 @@ const ( // pseudo random number generator. // seeded with OS randomness (crand) -var prng struct { + +type Rand struct { sync.Mutex *mrand.Rand } -func reset() { - 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() -} +var grand *Rand 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. // It is not safe for cryptographic usage. -func RandStr(length int) string { +func (r *Rand) RandStr(length int) string { chars := []byte{} MAIN_LOOP: for { - val := RandInt63() + val := r.RandInt63() for i := 0; i < 10; i++ { v := int(val & 0x3f) // rightmost 6 bits if v >= 62 { // only 62 characters in strChars @@ -60,127 +164,127 @@ MAIN_LOOP: } // It is not safe for cryptographic usage. -func RandUint16() uint16 { - return uint16(RandUint32() & (1<<16 - 1)) +func (r *Rand) RandUint16() uint16 { + return uint16(r.RandUint32() & (1<<16 - 1)) } // It is not safe for cryptographic usage. -func RandUint32() uint32 { - prng.Lock() - u32 := prng.Uint32() - prng.Unlock() +func (r *Rand) RandUint32() uint32 { + r.Lock() + u32 := r.Uint32() + r.Unlock() return u32 } // It is not safe for cryptographic usage. -func RandUint64() uint64 { - return uint64(RandUint32())<<32 + uint64(RandUint32()) +func (r *Rand) RandUint64() uint64 { + return uint64(r.RandUint32())<<32 + uint64(r.RandUint32()) } // It is not safe for cryptographic usage. -func RandUint() uint { - prng.Lock() - i := prng.Int() - prng.Unlock() +func (r *Rand) RandUint() uint { + r.Lock() + i := r.Int() + r.Unlock() return uint(i) } // It is not safe for cryptographic usage. -func RandInt16() int16 { - return int16(RandUint32() & (1<<16 - 1)) +func (r *Rand) RandInt16() int16 { + return int16(r.RandUint32() & (1<<16 - 1)) } // It is not safe for cryptographic usage. -func RandInt32() int32 { - return int32(RandUint32()) +func (r *Rand) RandInt32() int32 { + return int32(r.RandUint32()) } // It is not safe for cryptographic usage. -func RandInt64() int64 { - return int64(RandUint64()) +func (r *Rand) RandInt64() int64 { + return int64(r.RandUint64()) } // It is not safe for cryptographic usage. -func RandInt() int { - prng.Lock() - i := prng.Int() - prng.Unlock() +func (r *Rand) RandInt() int { + r.Lock() + i := r.Int() + r.Unlock() return i } // It is not safe for cryptographic usage. -func RandInt31() int32 { - prng.Lock() - i31 := prng.Int31() - prng.Unlock() +func (r *Rand) RandInt31() int32 { + r.Lock() + i31 := r.Int31() + r.Unlock() return i31 } // It is not safe for cryptographic usage. -func RandInt63() int64 { - prng.Lock() - i63 := prng.Int63() - prng.Unlock() +func (r *Rand) RandInt63() int64 { + r.Lock() + i63 := r.Int63() + r.Unlock() return i63 } // Distributed pseudo-exponentially to test for various cases // It is not safe for cryptographic usage. -func RandUint16Exp() uint16 { - bits := RandUint32() % 16 +func (r *Rand) RandUint16Exp() uint16 { + bits := r.RandUint32() % 16 if bits == 0 { return 0 } n := uint16(1 << (bits - 1)) - n += uint16(RandInt31()) & ((1 << (bits - 1)) - 1) + n += uint16(r.RandInt31()) & ((1 << (bits - 1)) - 1) return n } // Distributed pseudo-exponentially to test for various cases // It is not safe for cryptographic usage. -func RandUint32Exp() uint32 { - bits := RandUint32() % 32 +func (r *Rand) RandUint32Exp() uint32 { + bits := r.RandUint32() % 32 if bits == 0 { return 0 } n := uint32(1 << (bits - 1)) - n += uint32(RandInt31()) & ((1 << (bits - 1)) - 1) + n += uint32(r.RandInt31()) & ((1 << (bits - 1)) - 1) return n } // Distributed pseudo-exponentially to test for various cases // It is not safe for cryptographic usage. -func RandUint64Exp() uint64 { - bits := RandUint32() % 64 +func (r *Rand) RandUint64Exp() uint64 { + bits := r.RandUint32() % 64 if bits == 0 { return 0 } n := uint64(1 << (bits - 1)) - n += uint64(RandInt63()) & ((1 << (bits - 1)) - 1) + n += uint64(r.RandInt63()) & ((1 << (bits - 1)) - 1) return n } // It is not safe for cryptographic usage. -func RandFloat32() float32 { - prng.Lock() - f32 := prng.Float32() - prng.Unlock() +func (r *Rand) RandFloat32() float32 { + r.Lock() + f32 := r.Float32() + r.Unlock() return f32 } // It is not safe for cryptographic usage. -func RandTime() time.Time { - return time.Unix(int64(RandUint64Exp()), 0) +func (r *Rand) RandTime() time.Time { + return time.Unix(int64(r.RandUint64Exp()), 0) } // RandBytes returns n random bytes from the OS's source of entropy ie. via crypto/rand. // 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 // use random bytes generated from the internal PRNG bs := make([]byte, n) for i := 0; i < len(bs); i++ { - bs[i] = byte(RandInt() & 0xFF) + bs[i] = byte(r.RandInt() & 0xFF) } 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). // It panics if n <= 0. // It is not safe for cryptographic usage. -func RandIntn(n int) int { - prng.Lock() - i := prng.Intn(n) - prng.Unlock() +func (r *Rand) RandIntn(n int) int { + r.Lock() + i := r.Intn(n) + r.Unlock() return i } // RandPerm returns a pseudo-random permutation of n integers in [0, n). // It is not safe for cryptographic usage. -func RandPerm(n int) []int { - prng.Lock() - perm := prng.Perm(n) - prng.Unlock() +func (r *Rand) RandPerm(n int) []int { + r.Lock() + perm := r.Perm(n) + r.Unlock() return perm } diff --git a/common/random_test.go b/common/random_test.go index 216f2f8b..b58b4a13 100644 --- a/common/random_test.go +++ b/common/random_test.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "io" mrand "math/rand" "sync" "testing" @@ -33,37 +32,38 @@ func TestRandIntn(t *testing.T) { } } -// It is essential that these tests run and never repeat their outputs -// lest we've been pwned and the behavior of our randomness is controlled. -// See Issues: -// * https://github.com/tendermint/tmlibs/issues/99 -// * https://github.com/tendermint/tendermint/issues/973 -func TestUniqueRng(t *testing.T) { - buf := new(bytes.Buffer) - outputs := make(map[string][]int) +// Test to make sure that we never call math.rand(). +// We do this by ensuring that outputs are deterministic. +func TestDeterminism(t *testing.T) { + var firstOutput string + + // Set math/rand's seed for the sake of debugging this test. + // (It isn't strictly necessary). + mrand.Seed(1) + for i := 0; i < 100; i++ { - testThemAll(buf) - output := buf.String() - buf.Reset() - runs, seen := outputs[output] - if seen { - t.Errorf("Run #%d's output was already seen in previous runs: %v", i, runs) + output := testThemAll() + if i == 0 { + firstOutput = output + } else { + if firstOutput != output { + 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) { - // Reset the internal PRNG - reset() +func testThemAll() string { - // Set math/rand's Seed so that any direct invocations - // of math/rand will reveal themselves. - mrand.Seed(1) + // Such determinism. + grand.reset(1) + + // Use it. + out := new(bytes.Buffer) perm := RandPerm(10) blob, _ := json.Marshal(perm) fmt.Fprintf(out, "perm: %s\n", blob) - fmt.Fprintf(out, "randInt: %d\n", RandInt()) fmt.Fprintf(out, "randUint: %d\n", RandUint()) 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, "randUint32Exp: %d\n", RandUint32Exp()) fmt.Fprintf(out, "randUint64Exp: %d\n", RandUint64Exp()) + return out.String() } func TestRngConcurrencySafety(t *testing.T) {