Browse Source

Merge pull request #100 from tendermint/internal-randSource

common: no more relying on math/rand.DefaultSource
pull/1842/head
Anton Kaliaev 7 years ago
committed by GitHub
parent
commit
4ddf212286
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 223 additions and 25 deletions
  1. +3
    -4
      common/bit_array.go
  2. +100
    -21
      common/random.go
  3. +120
    -0
      common/random_test.go

+ 3
- 4
common/bit_array.go View File

@ -3,7 +3,6 @@ package common
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"math/rand"
"strings" "strings"
"sync" "sync"
) )
@ -212,12 +211,12 @@ func (bA *BitArray) PickRandom() (int, bool) {
if length == 0 { if length == 0 {
return 0, false return 0, false
} }
randElemStart := rand.Intn(length)
randElemStart := RandIntn(length)
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
elemIdx := ((i + randElemStart) % length) elemIdx := ((i + randElemStart) % length)
if elemIdx < length-1 { if elemIdx < length-1 {
if bA.Elems[elemIdx] > 0 { if bA.Elems[elemIdx] > 0 {
randBitStart := rand.Intn(64)
randBitStart := RandIntn(64)
for j := 0; j < 64; j++ { for j := 0; j < 64; j++ {
bitIdx := ((j + randBitStart) % 64) bitIdx := ((j + randBitStart) % 64)
if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 { if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 {
@ -232,7 +231,7 @@ func (bA *BitArray) PickRandom() (int, bool) {
if elemBits == 0 { if elemBits == 0 {
elemBits = 64 elemBits = 64
} }
randBitStart := rand.Intn(elemBits)
randBitStart := RandIntn(elemBits)
for j := 0; j < elemBits; j++ { for j := 0; j < elemBits; j++ {
bitIdx := ((j + randBitStart) % elemBits) bitIdx := ((j + randBitStart) % elemBits)
if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 { if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 {


+ 100
- 21
common/random.go View File

@ -2,7 +2,8 @@ package common
import ( import (
crand "crypto/rand" crand "crypto/rand"
"math/rand"
mrand "math/rand"
"sync"
"time" "time"
) )
@ -10,22 +11,36 @@ const (
strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters
) )
func init() {
// pseudo random number generator.
// seeded with OS randomness (crand)
var prng struct {
sync.Mutex
*mrand.Rand
}
func reset() {
b := cRandBytes(8) b := cRandBytes(8)
var seed uint64 var seed uint64
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
seed |= uint64(b[i]) seed |= uint64(b[i])
seed <<= 8 seed <<= 8
} }
rand.Seed(int64(seed))
prng.Lock()
prng.Rand = mrand.New(mrand.NewSource(int64(seed)))
prng.Unlock()
}
func init() {
reset()
} }
// Constructs an alphanumeric string of given length. // Constructs an alphanumeric string of given length.
// It is not safe for cryptographic usage.
func RandStr(length int) string { func RandStr(length int) string {
chars := []byte{} chars := []byte{}
MAIN_LOOP: MAIN_LOOP:
for { for {
val := rand.Int63()
val := prng.Int63()
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
@ -44,87 +59,151 @@ MAIN_LOOP:
return string(chars) return string(chars)
} }
// It is not safe for cryptographic usage.
func RandUint16() uint16 { func RandUint16() uint16 {
return uint16(rand.Uint32() & (1<<16 - 1))
return uint16(RandUint32() & (1<<16 - 1))
} }
// It is not safe for cryptographic usage.
func RandUint32() uint32 { func RandUint32() uint32 {
return rand.Uint32()
prng.Lock()
u32 := prng.Uint32()
prng.Unlock()
return u32
} }
// It is not safe for cryptographic usage.
func RandUint64() uint64 { func RandUint64() uint64 {
return uint64(rand.Uint32())<<32 + uint64(rand.Uint32())
return uint64(RandUint32())<<32 + uint64(RandUint32())
} }
// It is not safe for cryptographic usage.
func RandUint() uint { func RandUint() uint {
return uint(rand.Int())
prng.Lock()
i := prng.Int()
prng.Unlock()
return uint(i)
} }
// It is not safe for cryptographic usage.
func RandInt16() int16 { func RandInt16() int16 {
return int16(rand.Uint32() & (1<<16 - 1))
return int16(RandUint32() & (1<<16 - 1))
} }
// It is not safe for cryptographic usage.
func RandInt32() int32 { func RandInt32() int32 {
return int32(rand.Uint32())
return int32(RandUint32())
} }
// It is not safe for cryptographic usage.
func RandInt64() int64 { func RandInt64() int64 {
return int64(rand.Uint32())<<32 + int64(rand.Uint32())
return int64(RandUint64())
} }
// It is not safe for cryptographic usage.
func RandInt() int { func RandInt() int {
return rand.Int()
prng.Lock()
i := prng.Int()
prng.Unlock()
return i
}
// It is not safe for cryptographic usage.
func RandInt31() int32 {
prng.Lock()
i31 := prng.Int31()
prng.Unlock()
return i31
}
// It is not safe for cryptographic usage.
func RandInt63() int64 {
prng.Lock()
i63 := prng.Int63()
prng.Unlock()
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.
func RandUint16Exp() uint16 { func RandUint16Exp() uint16 {
bits := rand.Uint32() % 16
bits := RandUint32() % 16
if bits == 0 { if bits == 0 {
return 0 return 0
} }
n := uint16(1 << (bits - 1)) n := uint16(1 << (bits - 1))
n += uint16(rand.Int31()) & ((1 << (bits - 1)) - 1)
n += uint16(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.
func RandUint32Exp() uint32 { func RandUint32Exp() uint32 {
bits := rand.Uint32() % 32
bits := RandUint32() % 32
if bits == 0 { if bits == 0 {
return 0 return 0
} }
n := uint32(1 << (bits - 1)) n := uint32(1 << (bits - 1))
n += uint32(rand.Int31()) & ((1 << (bits - 1)) - 1)
n += uint32(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.
func RandUint64Exp() uint64 { func RandUint64Exp() uint64 {
bits := rand.Uint32() % 64
bits := RandUint32() % 64
if bits == 0 { if bits == 0 {
return 0 return 0
} }
n := uint64(1 << (bits - 1)) n := uint64(1 << (bits - 1))
n += uint64(rand.Int63()) & ((1 << (bits - 1)) - 1)
n += uint64(RandInt63()) & ((1 << (bits - 1)) - 1)
return n return n
} }
// It is not safe for cryptographic usage.
func RandFloat32() float32 { func RandFloat32() float32 {
return rand.Float32()
prng.Lock()
f32 := prng.Float32()
prng.Unlock()
return f32
} }
// It is not safe for cryptographic usage.
func RandTime() time.Time { func RandTime() time.Time {
return time.Unix(int64(RandUint64Exp()), 0) return time.Unix(int64(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 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) bs := make([]byte, n)
for i := 0; i < n; i++ {
bs[i] = byte(rand.Intn(256))
for i := 0; i < len(bs); i++ {
bs[i] = byte(RandInt() & 0xFF)
} }
return bs return bs
} }
// 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()
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()
return perm
}
// NOTE: This relies on the os's random number generator. // NOTE: This relies on the os's random number generator.
// For real security, we should salt that with some seed. // For real security, we should salt that with some seed.
// See github.com/tendermint/go-crypto for a more secure reader. // See github.com/tendermint/go-crypto for a more secure reader.


+ 120
- 0
common/random_test.go View File

@ -0,0 +1,120 @@
package common
import (
"bytes"
"encoding/json"
"fmt"
"io"
mrand "math/rand"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRandStr(t *testing.T) {
l := 243
s := RandStr(l)
assert.Equal(t, l, len(s))
}
func TestRandBytes(t *testing.T) {
l := 243
b := RandBytes(l)
assert.Equal(t, l, len(b))
}
func TestRandIntn(t *testing.T) {
n := 243
for i := 0; i < 100; i++ {
x := RandIntn(n)
assert.True(t, x < n)
}
}
// 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)
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)
}
outputs[output] = append(outputs[output], i)
}
}
func testThemAll(out io.Writer) {
// Reset the internal PRNG
reset()
// Set math/rand's Seed so that any direct invocations
// of math/rand will reveal themselves.
mrand.Seed(1)
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))
fmt.Fprintf(out, "randInt31: %d\n", RandInt31())
fmt.Fprintf(out, "randInt32: %d\n", RandInt32())
fmt.Fprintf(out, "randInt63: %d\n", RandInt63())
fmt.Fprintf(out, "randInt64: %d\n", RandInt64())
fmt.Fprintf(out, "randUint32: %d\n", RandUint32())
fmt.Fprintf(out, "randUint64: %d\n", RandUint64())
fmt.Fprintf(out, "randUint16Exp: %d\n", RandUint16Exp())
fmt.Fprintf(out, "randUint32Exp: %d\n", RandUint32Exp())
fmt.Fprintf(out, "randUint64Exp: %d\n", RandUint64Exp())
}
func TestRngConcurrencySafety(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_ = RandUint64()
<-time.After(time.Millisecond * time.Duration(RandIntn(100)))
_ = RandPerm(3)
}()
}
wg.Wait()
}
func BenchmarkRandBytes10B(b *testing.B) {
benchmarkRandBytes(b, 10)
}
func BenchmarkRandBytes100B(b *testing.B) {
benchmarkRandBytes(b, 100)
}
func BenchmarkRandBytes1KiB(b *testing.B) {
benchmarkRandBytes(b, 1024)
}
func BenchmarkRandBytes10KiB(b *testing.B) {
benchmarkRandBytes(b, 10*1024)
}
func BenchmarkRandBytes100KiB(b *testing.B) {
benchmarkRandBytes(b, 100*1024)
}
func BenchmarkRandBytes1MiB(b *testing.B) {
benchmarkRandBytes(b, 1024*1024)
}
func benchmarkRandBytes(b *testing.B, n int) {
for i := 0; i < b.N; i++ {
_ = RandBytes(n)
}
b.ReportAllocs()
}

Loading…
Cancel
Save