From 5eedcff0420f8c4acd4f9dc0bf63111eb168a38a Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 2 Jun 2020 17:26:17 -0700 Subject: [PATCH] add rand package, utilities for math/rand. --- rand/rand.go | 49 +++++++++++++++++++++++++++++++ rand/rand_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 rand/rand.go create mode 100644 rand/rand_test.go diff --git a/rand/rand.go b/rand/rand.go new file mode 100644 index 0000000..d299512 --- /dev/null +++ b/rand/rand.go @@ -0,0 +1,49 @@ +// Package rand contains utilities for interacting with math/rand, including +// seeding from a random sed. +package rand + +import ( + "crypto/rand" + "encoding/binary" + mrand "math/rand" +) + +// CryptoUint64 generates a cryptographically-secure 64-bit integer. +func CryptoUint64() (uint64, error) { + bs := make([]byte, 8) + _, err := rand.Read(bs) + if err != nil { + return 0, err + } + + return binary.BigEndian.Uint64(bs), nil +} + +// Seed initialises the non-cryptographic PRNG with a random, +// cryptographically secure value. This is done just as a good +// way to make this random. The returned 64-bit value is the seed. +func Seed() (uint64, error) { + seed, err := CryptoUint64() + if err != nil { + return 0, err + } + + // NB: this is permitted. + mrand.Seed(int64(seed)) + return seed, nil +} + +// Int is a wrapper for math.Int so only one package needs to be imported. +func Int() int { + return mrand.Int() +} + +// Intn is a wrapper for math.Intn so only one package needs to be imported. +func Intn(max int) int { + return mrand.Intn(max) +} + +// Intn2 returns a random value between min and max, inclusive. +func Intn2(min, max int) int { + return Intn(max-min) + min +} diff --git a/rand/rand_test.go b/rand/rand_test.go new file mode 100644 index 0000000..4b616ad --- /dev/null +++ b/rand/rand_test.go @@ -0,0 +1,74 @@ +package rand + +import ( + "fmt" + mrand "math/rand" + "testing" +) + +func TestCryptoUint64(t *testing.T) { + n1, err := CryptoUint64() + if err != nil { + t.Fatal(err) + } + + n2, err := CryptoUint64() + if err != nil { + t.Fatal(err) + } + + // This has such a low chance of occurring that it's likely to be + // indicative of a bad CSPRNG. + if n1 == n2 { + t.Fatalf("repeated random uint64s: %d", n1) + } +} + +func TestIntn(t *testing.T) { + expected := []int{3081, 4887, 4847, 1059, 3081} + mrand.Seed(1) + for i := 0; i < 5; i++ { + n := Intn2(1000, 5000) + + if n != expected[i] { + fmt.Printf("invalid sequence at %d: expected %d, have %d", i, expected[i], n) + } + } +} + +func TestSeed(t *testing.T) { + seed1, err := Seed() + if err != nil { + t.Fatal(err) + } + + var seed2 uint64 + n1 := Int() + tries := 0 + + for { + seed2, err = Seed() + if err != nil { + t.Fatal(err) + } + + if seed1 != seed2 { + break + } + + tries++ + + if tries > 3 { + t.Fatal("can't generate two unique seeds") + } + } + + n2 := Int() + + // Again, this not impossible, merely statistically improbably and a + // potential canary for RNG issues. + if n1 == n2 { + t.Fatalf("repeated integers fresh from two unique seeds: %d/%d -> %d", + seed1, seed2, n1) + } +}