From 29471d75cb50eb4cea5878b8bd1be25e8150564c Mon Sep 17 00:00:00 2001 From: Emmanuel Odeke Date: Wed, 13 Dec 2017 22:53:02 -0700 Subject: [PATCH 1/4] common: no more relying on math/rand.DefaultSource Fixes https://github.com/tendermint/tmlibs/issues/99 Updates https://github.com/tendermint/tendermint/issues/973 Removed usages of math/rand.DefaultSource in favour of our own source that's seeded with a completely random source and is safe for use in concurrent in multiple goroutines. Also extend some functionality that the stdlib exposes such as * RandPerm * RandIntn * RandInt31 * RandInt63 Also added an integration test whose purpose is to be run as a consistency check to ensure that our results never repeat hence that our internal PRNG is uniquely seeded each time. This integration test can be triggered by setting environment variable: `TENDERMINT_INTEGRATION_TESTS=true` for example ```shell TENDERMINT_INTEGRATION_TESTS=true go test ``` --- common/bit_array.go | 7 ++- common/random.go | 89 +++++++++++++++++++++++++--------- common/random_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 26 deletions(-) create mode 100644 common/random_test.go diff --git a/common/bit_array.go b/common/bit_array.go index 5590fe61b..848763b48 100644 --- a/common/bit_array.go +++ b/common/bit_array.go @@ -3,7 +3,6 @@ package common import ( "encoding/binary" "fmt" - "math/rand" "strings" "sync" ) @@ -212,12 +211,12 @@ func (bA *BitArray) PickRandom() (int, bool) { if length == 0 { return 0, false } - randElemStart := rand.Intn(length) + randElemStart := RandIntn(length) for i := 0; i < length; i++ { elemIdx := ((i + randElemStart) % length) if elemIdx < length-1 { if bA.Elems[elemIdx] > 0 { - randBitStart := rand.Intn(64) + randBitStart := RandIntn(64) for j := 0; j < 64; j++ { bitIdx := ((j + randBitStart) % 64) if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 { @@ -232,7 +231,7 @@ func (bA *BitArray) PickRandom() (int, bool) { if elemBits == 0 { elemBits = 64 } - randBitStart := rand.Intn(elemBits) + randBitStart := RandIntn(elemBits) for j := 0; j < elemBits; j++ { bitIdx := ((j + randBitStart) % elemBits) if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 { diff --git a/common/random.go b/common/random.go index 73bd16356..f0d169e09 100644 --- a/common/random.go +++ b/common/random.go @@ -3,6 +3,7 @@ package common import ( crand "crypto/rand" "math/rand" + "sync" "time" ) @@ -10,6 +11,11 @@ const ( strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters ) +var rng struct { + sync.Mutex + *rand.Rand +} + func init() { b := cRandBytes(8) var seed uint64 @@ -17,7 +23,7 @@ func init() { seed |= uint64(b[i]) seed <<= 8 } - rand.Seed(int64(seed)) + rng.Rand = rand.New(rand.NewSource(int64(seed))) } // Constructs an alphanumeric string of given length. @@ -25,7 +31,7 @@ func RandStr(length int) string { chars := []byte{} MAIN_LOOP: for { - val := rand.Int63() + val := rng.Int63() for i := 0; i < 10; i++ { v := int(val & 0x3f) // rightmost 6 bits if v >= 62 { // only 62 characters in strChars @@ -45,72 +51,98 @@ MAIN_LOOP: } func RandUint16() uint16 { - return uint16(rand.Uint32() & (1<<16 - 1)) + return uint16(RandUint32() & (1<<16 - 1)) } func RandUint32() uint32 { - return rand.Uint32() + rng.Lock() + u32 := rng.Uint32() + rng.Unlock() + return u32 } func RandUint64() uint64 { - return uint64(rand.Uint32())<<32 + uint64(rand.Uint32()) + return uint64(RandUint32())<<32 + uint64(RandUint32()) } func RandUint() uint { - return uint(rand.Int()) + rng.Lock() + i := rng.Int() + rng.Unlock() + return uint(i) } func RandInt16() int16 { - return int16(rand.Uint32() & (1<<16 - 1)) + return int16(RandUint32() & (1<<16 - 1)) } func RandInt32() int32 { - return int32(rand.Uint32()) + return int32(RandUint32()) } func RandInt64() int64 { - return int64(rand.Uint32())<<32 + int64(rand.Uint32()) + return int64(RandUint64()) } func RandInt() int { - return rand.Int() + rng.Lock() + i := rng.Int() + rng.Unlock() + return i +} + +func RandInt31() int32 { + rng.Lock() + i31 := rng.Int31() + rng.Unlock() + return i31 +} + +func RandInt63() int64 { + rng.Lock() + i63 := rng.Int63() + rng.Unlock() + return i63 } // Distributed pseudo-exponentially to test for various cases func RandUint16Exp() uint16 { - bits := rand.Uint32() % 16 + bits := RandUint32() % 16 if bits == 0 { return 0 } n := uint16(1 << (bits - 1)) - n += uint16(rand.Int31()) & ((1 << (bits - 1)) - 1) + n += uint16(RandInt31()) & ((1 << (bits - 1)) - 1) return n } // Distributed pseudo-exponentially to test for various cases func RandUint32Exp() uint32 { - bits := rand.Uint32() % 32 + bits := RandUint32() % 32 if bits == 0 { return 0 } n := uint32(1 << (bits - 1)) - n += uint32(rand.Int31()) & ((1 << (bits - 1)) - 1) + n += uint32(RandInt31()) & ((1 << (bits - 1)) - 1) return n } // Distributed pseudo-exponentially to test for various cases func RandUint64Exp() uint64 { - bits := rand.Uint32() % 64 + bits := RandUint32() % 64 if bits == 0 { return 0 } n := uint64(1 << (bits - 1)) - n += uint64(rand.Int63()) & ((1 << (bits - 1)) - 1) + n += uint64(RandInt63()) & ((1 << (bits - 1)) - 1) return n } func RandFloat32() float32 { - return rand.Float32() + rng.Lock() + f32 := rng.Float32() + rng.Unlock() + return f32 } func RandTime() time.Time { @@ -118,11 +150,24 @@ func RandTime() time.Time { } func RandBytes(n int) []byte { - bs := make([]byte, n) - for i := 0; i < n; i++ { - bs[i] = byte(rand.Intn(256)) - } - return bs + return cRandBytes(n) +} + +// RandIntn returns, as an int, a non-negative pseudo-random number in [0, n). +// It panics if n <= 0 +func RandIntn(n int) int { + rng.Lock() + i := rng.Intn(n) + rng.Unlock() + return i +} + +// RandPerm returns a pseudo-random permutation of n integers in [0, n). +func RandPerm(n int) []int { + rng.Lock() + perm := rng.Perm(n) + rng.Unlock() + return perm } // NOTE: This relies on the os's random number generator. diff --git a/common/random_test.go b/common/random_test.go new file mode 100644 index 000000000..dd803b3f6 --- /dev/null +++ b/common/random_test.go @@ -0,0 +1,108 @@ +package common_test + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/tendermint/tmlibs/common" +) + +// 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) { + if os.Getenv("TENDERMINT_INTEGRATION_TESTS") == "" { + t.Skipf("Can only be run as an integration test") + } + + // The goal of this test is to invoke the + // Rand* tests externally with no repeating results, booted up. + // Any repeated results indicate that the seed is the same or that + // perhaps we are using math/rand directly. + tmpDir, err := ioutil.TempDir("", "rng-tests") + if err != nil { + t.Fatalf("Creating tempDir: %v", err) + } + defer os.RemoveAll(tmpDir) + + outpath := filepath.Join(tmpDir, "main.go") + f, err := os.Create(outpath) + if err != nil { + t.Fatalf("Setting up %q err: %v", outpath, err) + } + f.Write([]byte(integrationTestProgram)) + if err := f.Close(); err != nil { + t.Fatalf("Closing: %v", err) + } + + outputs := make(map[string][]int) + for i := 0; i < 100; i++ { + cmd := exec.Command("go", "run", outpath) + bOutput, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("Run #%d: err: %v output: %s", i, err, bOutput) + continue + } + output := string(bOutput) + 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) + } +} + +const integrationTestProgram = ` +package main + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/tendermint/tmlibs/common" +) + +func main() { + // Set math/rand's Seed so that any direct invocations + // of math/rand will reveal themselves. + rand.Seed(1) + perm := common.RandPerm(10) + blob, _ := json.Marshal(perm) + fmt.Printf("perm: %s\n", blob) + + fmt.Printf("randInt: %d\n", common.RandInt()) + fmt.Printf("randUint: %d\n", common.RandUint()) + fmt.Printf("randIntn: %d\n", common.RandIntn(97)) + fmt.Printf("randInt31: %d\n", common.RandInt31()) + fmt.Printf("randInt32: %d\n", common.RandInt32()) + fmt.Printf("randInt63: %d\n", common.RandInt63()) + fmt.Printf("randInt64: %d\n", common.RandInt64()) + fmt.Printf("randUint32: %d\n", common.RandUint32()) + fmt.Printf("randUint64: %d\n", common.RandUint64()) + fmt.Printf("randUint16Exp: %d\n", common.RandUint16Exp()) + fmt.Printf("randUint32Exp: %d\n", common.RandUint32Exp()) + fmt.Printf("randUint64Exp: %d\n", common.RandUint64Exp()) +}` + +func TestRngConcurrencySafety(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + _ = common.RandUint64() + <-time.After(time.Millisecond * time.Duration(common.RandIntn(100))) + _ = common.RandPerm(3) + }() + } + wg.Wait() +} From b5f465b4ecb6ef85a6ced14728a971570ed477e0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 Dec 2017 00:23:25 -0500 Subject: [PATCH 2/4] common: use names prng and mrand --- common/random.go | 61 +++++++++++++++++++++++-------------------- common/random_test.go | 21 +++++++++++++++ 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/common/random.go b/common/random.go index f0d169e09..b8304e898 100644 --- a/common/random.go +++ b/common/random.go @@ -2,7 +2,7 @@ package common import ( crand "crypto/rand" - "math/rand" + mrand "math/rand" "sync" "time" ) @@ -11,9 +11,11 @@ const ( strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters ) -var rng struct { +// pseudo random number generator. +// seeded with OS randomness (crand) +var prng struct { sync.Mutex - *rand.Rand + *mrand.Rand } func init() { @@ -23,7 +25,7 @@ func init() { seed |= uint64(b[i]) seed <<= 8 } - rng.Rand = rand.New(rand.NewSource(int64(seed))) + prng.Rand = mrand.New(mrand.NewSource(int64(seed))) } // Constructs an alphanumeric string of given length. @@ -31,7 +33,7 @@ func RandStr(length int) string { chars := []byte{} MAIN_LOOP: for { - val := rng.Int63() + val := prng.Int63() for i := 0; i < 10; i++ { v := int(val & 0x3f) // rightmost 6 bits if v >= 62 { // only 62 characters in strChars @@ -55,9 +57,9 @@ func RandUint16() uint16 { } func RandUint32() uint32 { - rng.Lock() - u32 := rng.Uint32() - rng.Unlock() + prng.Lock() + u32 := prng.Uint32() + prng.Unlock() return u32 } @@ -66,9 +68,9 @@ func RandUint64() uint64 { } func RandUint() uint { - rng.Lock() - i := rng.Int() - rng.Unlock() + prng.Lock() + i := prng.Int() + prng.Unlock() return uint(i) } @@ -85,23 +87,23 @@ func RandInt64() int64 { } func RandInt() int { - rng.Lock() - i := rng.Int() - rng.Unlock() + prng.Lock() + i := prng.Int() + prng.Unlock() return i } func RandInt31() int32 { - rng.Lock() - i31 := rng.Int31() - rng.Unlock() + prng.Lock() + i31 := prng.Int31() + prng.Unlock() return i31 } func RandInt63() int64 { - rng.Lock() - i63 := rng.Int63() - rng.Unlock() + prng.Lock() + i63 := prng.Int63() + prng.Unlock() return i63 } @@ -139,9 +141,9 @@ func RandUint64Exp() uint64 { } func RandFloat32() float32 { - rng.Lock() - f32 := rng.Float32() - rng.Unlock() + prng.Lock() + f32 := prng.Float32() + prng.Unlock() return f32 } @@ -149,6 +151,7 @@ func RandTime() time.Time { return time.Unix(int64(RandUint64Exp()), 0) } +// RandBytes returns n random bytes from the OS's source of entropy ie. via crypto/rand. func RandBytes(n int) []byte { return cRandBytes(n) } @@ -156,17 +159,17 @@ func RandBytes(n int) []byte { // RandIntn returns, as an int, a non-negative pseudo-random number in [0, n). // It panics if n <= 0 func RandIntn(n int) int { - rng.Lock() - i := rng.Intn(n) - rng.Unlock() + prng.Lock() + i := prng.Intn(n) + prng.Unlock() return i } // RandPerm returns a pseudo-random permutation of n integers in [0, n). func RandPerm(n int) []int { - rng.Lock() - perm := rng.Perm(n) - rng.Unlock() + prng.Lock() + perm := prng.Perm(n) + prng.Unlock() return perm } diff --git a/common/random_test.go b/common/random_test.go index dd803b3f6..3fe0bbc06 100644 --- a/common/random_test.go +++ b/common/random_test.go @@ -9,9 +9,30 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/tendermint/tmlibs/common" ) +func TestRandStr(t *testing.T) { + l := 243 + s := common.RandStr(l) + assert.Equal(t, l, len(s)) +} + +func TestRandBytes(t *testing.T) { + l := 243 + b := common.RandBytes(l) + assert.Equal(t, l, len(b)) +} + +func TestRandIntn(t *testing.T) { + n := 243 + for i := 0; i < 100; i++ { + x := common.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: From cdc798882326a722040706a87ec0397e7c91d517 Mon Sep 17 00:00:00 2001 From: Emmanuel Odeke Date: Fri, 15 Dec 2017 02:14:05 -0700 Subject: [PATCH 3/4] common: use genius simplification of tests from @ebuchman Massive test simplication for more reliable tests from @ebuchman --- common/random.go | 8 ++- common/random_test.go | 115 +++++++++++++++--------------------------- 2 files changed, 47 insertions(+), 76 deletions(-) diff --git a/common/random.go b/common/random.go index b8304e898..37b8b2773 100644 --- a/common/random.go +++ b/common/random.go @@ -18,14 +18,20 @@ var prng struct { *mrand.Rand } -func init() { +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() +} + +func init() { + reset() } // Constructs an alphanumeric string of given length. diff --git a/common/random_test.go b/common/random_test.go index 3fe0bbc06..bed8e7650 100644 --- a/common/random_test.go +++ b/common/random_test.go @@ -1,34 +1,34 @@ -package common_test +package common import ( - "io/ioutil" - "os" - "os/exec" - "path/filepath" + "bytes" + "encoding/json" + "fmt" + "io" + mrand "math/rand" "sync" "testing" "time" "github.com/stretchr/testify/assert" - "github.com/tendermint/tmlibs/common" ) func TestRandStr(t *testing.T) { l := 243 - s := common.RandStr(l) + s := RandStr(l) assert.Equal(t, l, len(s)) } func TestRandBytes(t *testing.T) { l := 243 - b := common.RandBytes(l) + b := RandBytes(l) assert.Equal(t, l, len(b)) } func TestRandIntn(t *testing.T) { n := 243 for i := 0; i < 100; i++ { - x := common.RandIntn(n) + x := RandIntn(n) assert.True(t, x < n) } } @@ -39,39 +39,12 @@ func TestRandIntn(t *testing.T) { // * https://github.com/tendermint/tmlibs/issues/99 // * https://github.com/tendermint/tendermint/issues/973 func TestUniqueRng(t *testing.T) { - if os.Getenv("TENDERMINT_INTEGRATION_TESTS") == "" { - t.Skipf("Can only be run as an integration test") - } - - // The goal of this test is to invoke the - // Rand* tests externally with no repeating results, booted up. - // Any repeated results indicate that the seed is the same or that - // perhaps we are using math/rand directly. - tmpDir, err := ioutil.TempDir("", "rng-tests") - if err != nil { - t.Fatalf("Creating tempDir: %v", err) - } - defer os.RemoveAll(tmpDir) - - outpath := filepath.Join(tmpDir, "main.go") - f, err := os.Create(outpath) - if err != nil { - t.Fatalf("Setting up %q err: %v", outpath, err) - } - f.Write([]byte(integrationTestProgram)) - if err := f.Close(); err != nil { - t.Fatalf("Closing: %v", err) - } - + buf := new(bytes.Buffer) outputs := make(map[string][]int) for i := 0; i < 100; i++ { - cmd := exec.Command("go", "run", outpath) - bOutput, err := cmd.CombinedOutput() - if err != nil { - t.Errorf("Run #%d: err: %v output: %s", i, err, bOutput) - continue - } - output := string(bOutput) + 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) @@ -80,38 +53,30 @@ func TestUniqueRng(t *testing.T) { } } -const integrationTestProgram = ` -package main - -import ( - "encoding/json" - "fmt" - "math/rand" - - "github.com/tendermint/tmlibs/common" -) - -func main() { - // Set math/rand's Seed so that any direct invocations - // of math/rand will reveal themselves. - rand.Seed(1) - perm := common.RandPerm(10) - blob, _ := json.Marshal(perm) - fmt.Printf("perm: %s\n", blob) - - fmt.Printf("randInt: %d\n", common.RandInt()) - fmt.Printf("randUint: %d\n", common.RandUint()) - fmt.Printf("randIntn: %d\n", common.RandIntn(97)) - fmt.Printf("randInt31: %d\n", common.RandInt31()) - fmt.Printf("randInt32: %d\n", common.RandInt32()) - fmt.Printf("randInt63: %d\n", common.RandInt63()) - fmt.Printf("randInt64: %d\n", common.RandInt64()) - fmt.Printf("randUint32: %d\n", common.RandUint32()) - fmt.Printf("randUint64: %d\n", common.RandUint64()) - fmt.Printf("randUint16Exp: %d\n", common.RandUint16Exp()) - fmt.Printf("randUint32Exp: %d\n", common.RandUint32Exp()) - fmt.Printf("randUint64Exp: %d\n", common.RandUint64Exp()) -}` +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 @@ -120,9 +85,9 @@ func TestRngConcurrencySafety(t *testing.T) { go func() { defer wg.Done() - _ = common.RandUint64() - <-time.After(time.Millisecond * time.Duration(common.RandIntn(100))) - _ = common.RandPerm(3) + _ = RandUint64() + <-time.After(time.Millisecond * time.Duration(RandIntn(100))) + _ = RandPerm(3) }() } wg.Wait() From 8638961f02833def91f743cbccaa2cecdccffa74 Mon Sep 17 00:00:00 2001 From: Emmanuel Odeke Date: Fri, 15 Dec 2017 22:40:12 -0700 Subject: [PATCH 4/4] common: Rand* warnings about cryptographic unsafety Lesson articulated by @jaekwon on why we need 80 bits of entropy at least before we can think of cryptographic safety. math/rand's seed is a max of 64 bits so can never be cryptographically secure. Also added some benchmarks for RandBytes --- common/random.go | 29 +++++++++++++++++++++++++++-- common/random_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/common/random.go b/common/random.go index 37b8b2773..9df55ff81 100644 --- a/common/random.go +++ b/common/random.go @@ -35,6 +35,7 @@ func init() { } // Constructs an alphanumeric string of given length. +// It is not safe for cryptographic usage. func RandStr(length int) string { chars := []byte{} MAIN_LOOP: @@ -58,10 +59,12 @@ MAIN_LOOP: return string(chars) } +// It is not safe for cryptographic usage. func RandUint16() uint16 { return uint16(RandUint32() & (1<<16 - 1)) } +// It is not safe for cryptographic usage. func RandUint32() uint32 { prng.Lock() u32 := prng.Uint32() @@ -69,10 +72,12 @@ func RandUint32() uint32 { return u32 } +// It is not safe for cryptographic usage. func RandUint64() uint64 { return uint64(RandUint32())<<32 + uint64(RandUint32()) } +// It is not safe for cryptographic usage. func RandUint() uint { prng.Lock() i := prng.Int() @@ -80,18 +85,22 @@ func RandUint() uint { return uint(i) } +// It is not safe for cryptographic usage. func RandInt16() int16 { return int16(RandUint32() & (1<<16 - 1)) } +// It is not safe for cryptographic usage. func RandInt32() int32 { return int32(RandUint32()) } +// It is not safe for cryptographic usage. func RandInt64() int64 { return int64(RandUint64()) } +// It is not safe for cryptographic usage. func RandInt() int { prng.Lock() i := prng.Int() @@ -99,6 +108,7 @@ func RandInt() int { return i } +// It is not safe for cryptographic usage. func RandInt31() int32 { prng.Lock() i31 := prng.Int31() @@ -106,6 +116,7 @@ func RandInt31() int32 { return i31 } +// It is not safe for cryptographic usage. func RandInt63() int64 { prng.Lock() i63 := prng.Int63() @@ -114,6 +125,7 @@ func RandInt63() int64 { } // Distributed pseudo-exponentially to test for various cases +// It is not safe for cryptographic usage. func RandUint16Exp() uint16 { bits := RandUint32() % 16 if bits == 0 { @@ -125,6 +137,7 @@ func RandUint16Exp() uint16 { } // Distributed pseudo-exponentially to test for various cases +// It is not safe for cryptographic usage. func RandUint32Exp() uint32 { bits := RandUint32() % 32 if bits == 0 { @@ -136,6 +149,7 @@ func RandUint32Exp() uint32 { } // Distributed pseudo-exponentially to test for various cases +// It is not safe for cryptographic usage. func RandUint64Exp() uint64 { bits := RandUint32() % 64 if bits == 0 { @@ -146,6 +160,7 @@ func RandUint64Exp() uint64 { return n } +// It is not safe for cryptographic usage. func RandFloat32() float32 { prng.Lock() f32 := prng.Float32() @@ -153,17 +168,26 @@ func RandFloat32() float32 { return f32 } +// It is not safe for cryptographic usage. func RandTime() time.Time { 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 { - return cRandBytes(n) + // 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) + } + return bs } // 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. func RandIntn(n int) int { prng.Lock() i := prng.Intn(n) @@ -172,6 +196,7 @@ func RandIntn(n int) int { } // 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) diff --git a/common/random_test.go b/common/random_test.go index bed8e7650..216f2f8bc 100644 --- a/common/random_test.go +++ b/common/random_test.go @@ -92,3 +92,29 @@ func TestRngConcurrencySafety(t *testing.T) { } 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() +}