Files
x/internal/randstream/randstream.go
T

77 lines
2.4 KiB
Go

// 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
}