// Package randstream provides a fast, unbounded stream of pseudorandom // bytes as an io.Reader. It backs `sutil generate random`. // // It exists because reading /dev/urandom is slow: operating system random // devices are built for small requests of key material, not for filling // disks or producing gigabytes of test data. A Reader instead expands a // 32-byte key with the ChaCha8 generator from math/rand/v2, which runs at // close to memory bandwidth and never blocks. // // When Params.Seed is set, the key is SHA-256 of the seed string and the // stream is fully deterministic: the same seed always produces the same // bytes, which makes test data reproducible. When Seed is empty, the key // is read from crypto/rand (the operating system's secure random source, // /dev/urandom or its equivalent) and the stream is unpredictable. // // The output is not meant to be used as cryptographic key material; use // crypto/rand for that. package randstream import ( "crypto/rand" "crypto/sha256" "fmt" mathrand "math/rand/v2" ) // KeySize is the size in bytes of the key that seeds the stream. const KeySize = 32 // Params configures New. type Params struct { // Seed, when non-empty, makes the stream deterministic: the same seed // always yields the same byte sequence. When empty, the stream is // keyed from crypto/rand and differs on every call to New. Seed string } // Reader is an unbounded stream of pseudorandom bytes. Read never returns // an error and never returns fewer bytes than requested. A Reader is not // safe for concurrent use. type Reader struct { generator *mathrand.ChaCha8 } // New returns a Reader configured by p. func New(p Params) (*Reader, error) { key, err := keyFromParams(p) if err != nil { return nil, err } return &Reader{generator: mathrand.NewChaCha8(key)}, nil } // Read fills b with pseudorandom bytes. It always returns len(b) and a nil // error. func (r *Reader) Read(b []byte) (int, error) { return r.generator.Read(b) } // keyFromParams derives the ChaCha8 key: SHA-256 of the seed string, or // fresh bytes from crypto/rand when no seed is given. func keyFromParams(p Params) ([KeySize]byte, error) { if p.Seed != "" { return sha256.Sum256([]byte(p.Seed)), nil } var key [KeySize]byte _, err := rand.Read(key[:]) if err != nil { return key, fmt.Errorf("randstream: reading key from crypto/rand: %w", err) } return key, nil }