blob: d00732d42f1a3a6655a2ece37fa30bfdbfc62fe9 [file] [log] [blame]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cryptotest provides deterministic random source testing.
package cryptotest
import (
cryptorand "crypto/rand"
"internal/byteorder"
"io"
mathrand "math/rand/v2"
"sync"
"testing"
// Import unsafe and crypto/rand, which imports crypto/internal/rand,
// for the crypto/internal/rand.SetTestingReader go:linkname.
_ "crypto/rand"
_ "unsafe"
)
//go:linkname randSetTestingReader crypto/internal/rand.SetTestingReader
func randSetTestingReader(r io.Reader)
//go:linkname testingCheckParallel testing.checkParallel
func testingCheckParallel(t *testing.T)
// SetGlobalRandom sets a global, deterministic cryptographic randomness source
// for the duration of test t. It affects crypto/rand, and all implicit sources
// of cryptographic randomness in the crypto/... packages.
//
// SetGlobalRandom may be called multiple times in the same test to reset the
// random stream or change the seed.
//
// Because SetGlobalRandom affects the whole process, it cannot be used in
// parallel tests or tests with parallel ancestors.
//
// Note that the way cryptographic algorithms use randomness is generally not
// specified and may change over time. Thus, if a test expects a specific output
// from a cryptographic function, it may fail in the future even if it uses
// SetGlobalRandom.
//
// SetGlobalRandom is not supported when building against the Go Cryptographic
// Module v1.0.0 (i.e. when [crypto/fips140.Version] returns "v1.0.0").
func SetGlobalRandom(t *testing.T, seed uint64) {
if t == nil {
panic("cryptotest: SetGlobalRandom called with a nil *testing.T")
}
if !testing.Testing() {
panic("cryptotest: SetGlobalRandom used in a non-test binary")
}
testingCheckParallel(t)
var s [32]byte
byteorder.LEPutUint64(s[:8], seed)
r := &lockedReader{r: mathrand.NewChaCha8(s)}
randSetTestingReader(r)
previous := cryptorand.Reader
cryptorand.Reader = r
t.Cleanup(func() {
cryptorand.Reader = previous
randSetTestingReader(nil)
})
}
type lockedReader struct {
sync.Mutex
r *mathrand.ChaCha8
}
func (lr *lockedReader) Read(b []byte) (n int, err error) {
lr.Lock()
defer lr.Unlock()
return lr.r.Read(b)
}