From f83857868585a154288a30aee4570411528fe646 Mon Sep 17 00:00:00 2001 From: Erik Westra Date: Sun, 2 Mar 2025 13:29:03 +0100 Subject: [PATCH 1/3] Migrate math/rand to math/rand/v2 Signed-off-by: Erik Westra --- ulid.go | 6 ++++-- ulid_test.go | 31 +++++++++++++++++++------------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/ulid.go b/ulid.go index 77e9ddd..4019244 100644 --- a/ulid.go +++ b/ulid.go @@ -22,7 +22,7 @@ import ( "io" "math" "math/bits" - "math/rand" + "math/rand/v2" "sync" "time" ) @@ -133,7 +133,9 @@ func MustNewDefault(t time.Time) ULID { } var defaultEntropy = func() io.Reader { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) + seed := [32]byte{} + binary.LittleEndian.PutUint64(seed[:8], uint64(time.Now().UnixNano())) + rng := rand.NewChaCha8(seed) return &LockedMonotonicReader{MonotonicReader: Monotonic(rng, 0)} }() diff --git a/ulid_test.go b/ulid_test.go index 5ca70ae..c230266 100644 --- a/ulid_test.go +++ b/ulid_test.go @@ -16,10 +16,11 @@ package ulid_test import ( "bytes" crand "crypto/rand" + "encoding/binary" "fmt" "io" "math" - "math/rand" + "math/rand/v2" "strings" "testing" "testing/iotest" @@ -31,9 +32,11 @@ import ( func ExampleULID() { t := time.Unix(1000000, 0) - entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) + rng := createChaCha8RNG(t) + + entropy := ulid.Monotonic(rng, 0) fmt.Println(ulid.MustNew(ulid.Timestamp(t), entropy)) - // Output: 0000XSNJG0MQJHBF4QX1EFD6Y3 + // Output: 0000XSNJG02VZD8JHMTREXTNAH } func TestNew(t *testing.T) { @@ -418,9 +421,8 @@ func TestULIDTime(t *testing.T) { t.Errorf("got err %v, want %v", got, want) } - rng := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < 1e6; i++ { - ms := uint64(rng.Int63n(int64(maxTime))) + ms := uint64(rand.Int64N(int64(maxTime))) var id ulid.ULID if err := id.SetTime(ms); err != nil { @@ -587,13 +589,12 @@ func TestScan(t *testing.T) { } func TestMonotonic(t *testing.T) { - now := ulid.Now() for _, e := range []struct { name string mk func() io.Reader }{ {"cryptorand", func() io.Reader { return crand.Reader }}, - {"mathrand", func() io.Reader { return rand.New(rand.NewSource(int64(now))) }}, + {"mathrand", func() io.Reader { return createChaCha8RNG(time.Now()) }}, } { for _, inc := range []uint64{ 0, @@ -655,7 +656,7 @@ func TestMonotonicSafe(t *testing.T) { t.Parallel() var ( - rng = rand.New(rand.NewSource(time.Now().UnixNano())) + rng = createChaCha8RNG(time.Now()) safe = &ulid.LockedMonotonicReader{MonotonicReader: ulid.Monotonic(rng, 0)} t0 = ulid.Timestamp(time.Now()) ) @@ -689,7 +690,7 @@ func TestMonotonicSafe(t *testing.T) { func TestULID_Bytes(t *testing.T) { tt := time.Unix(1000000, 0) - entropy := ulid.Monotonic(rand.New(rand.NewSource(tt.UnixNano())), 0) + entropy := ulid.Monotonic(createChaCha8RNG(tt), 0) id := ulid.MustNew(ulid.Timestamp(tt), entropy) bid := id.Bytes() bid[len(bid)-1]++ @@ -698,6 +699,12 @@ func TestULID_Bytes(t *testing.T) { } } +func createChaCha8RNG(t time.Time) *rand.ChaCha8 { + seed := [32]byte{} + binary.LittleEndian.PutUint64(seed[:8], uint64(t.UnixNano())) + return rand.NewChaCha8(seed) +} + func BenchmarkNew(b *testing.B) { benchmarkMakeULID(b, func(timestamp uint64, entropy io.Reader) { _, _ = ulid.New(timestamp, entropy) @@ -714,7 +721,7 @@ func benchmarkMakeULID(b *testing.B, f func(uint64, io.Reader)) { b.ReportAllocs() b.SetBytes(int64(len(ulid.ULID{}))) - rng := rand.New(rand.NewSource(time.Now().UnixNano())) + rng := createChaCha8RNG(time.Now()) for _, tc := range []struct { name string @@ -768,7 +775,7 @@ func BenchmarkMustParse(b *testing.B) { } func BenchmarkString(b *testing.B) { - entropy := rand.New(rand.NewSource(time.Now().UnixNano())) + entropy := createChaCha8RNG(time.Now()) id := ulid.MustNew(123456, entropy) b.SetBytes(int64(len(id))) b.ResetTimer() @@ -778,7 +785,7 @@ func BenchmarkString(b *testing.B) { } func BenchmarkMarshal(b *testing.B) { - entropy := rand.New(rand.NewSource(time.Now().UnixNano())) + entropy := createChaCha8RNG(time.Now()) buf := make([]byte, ulid.EncodedSize) id := ulid.MustNew(123456, entropy) From 1cc528165b21fb40e1e31c2220a7350fe4ebd43e Mon Sep 17 00:00:00 2001 From: Erik Westra Date: Sun, 2 Mar 2025 22:33:25 +0100 Subject: [PATCH 2/3] Update README with rand/v2 examples and explanation. Signed-off-by: Erik Westra --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f6db0af..fcdd077 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ performance, cryptographic security, etc., use the [ulid.Make](https://pkg.go.dev/github.com/oklog/ulid/v2#Make) helper function. This function calls [time.Now](https://pkg.go.dev/time#Now) to get a timestamp, and uses a source of entropy which is process-global, -[pseudo-random](https://pkg.go.dev/math/rand), and +[pseudo-random](https://pkg.go.dev/math/rand/v2), and [monotonic](https://pkg.go.dev/github.com/oklog/ulid/v2#LockedMonotonicReader). ```go @@ -69,7 +69,11 @@ More advanced use cases should utilize [ulid.New](https://pkg.go.dev/github.com/oklog/ulid/v2#New). ```go -entropy := rand.New(rand.NewSource(time.Now().UnixNano())) + +seed := [32]byte{} +binary.LittleEndian.PutUint64(seed[:8], uint64(time.Now().UnixNano())) +entropy := rand.NewChaCha8(seed) // This uses math/rand/v2. + ms := ulid.Timestamp(time.Now()) fmt.Println(ulid.New(ms, entropy)) // 01G65Z755AFWAKHE12NY0CQ9FH @@ -77,7 +81,7 @@ fmt.Println(ulid.New(ms, entropy)) Care should be taken when providing a source of entropy. -The above example utilizes [math/rand.Rand](https://pkg.go.dev/math/rand#Rand), +The above example utilizes [math/rand/v2.Rand](https://pkg.go.dev/math/rand/v2#Rand), which is not safe for concurrent use by multiple goroutines. Consider alternatives such as [x/exp/rand](https://pkg.go.dev/golang.org/x/exp/rand#LockedSource). From 3801dbfe20cda62c09c9b77516a9bd4e9786e512 Mon Sep 17 00:00:00 2001 From: Erik Westra Date: Mon, 17 Mar 2025 15:02:20 +0100 Subject: [PATCH 3/3] migrate cli to math/rand/v2 Signed-off-by: Erik Westra --- cmd/ulid/main.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/ulid/main.go b/cmd/ulid/main.go index 50e62cc..a3f7822 100644 --- a/cmd/ulid/main.go +++ b/cmd/ulid/main.go @@ -2,8 +2,9 @@ package main import ( cryptorand "crypto/rand" + "encoding/binary" "fmt" - mathrand "math/rand" + mathrand "math/rand/v2" "os" "strings" "time" @@ -65,9 +66,9 @@ func main() { func generate(quick, zero bool) { entropy := cryptorand.Reader if quick { - seed := time.Now().UnixNano() - source := mathrand.NewSource(seed) - entropy = mathrand.New(source) + seed := [32]byte{} + binary.LittleEndian.PutUint64(seed[:8], uint64(time.Now().UnixNano())) + entropy = mathrand.NewChaCha8(seed) } if zero { entropy = zeroReader{}