forked from decred/dcrd
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The github.com/decred/dcrd/crypto/rand module provides an alternative to the standard library's math/rand, math/rand/v2, and crypto/rand packages. It implements a package-global fast userspace CSPRNG that never errors after initial seeding at init time with the ability to create additional PRNGs without locking overhead if needed. In addition to providing random bytes, the PRNG is also capable of generating cryptographically secure integers with uniform distribution, and provides a Fisher-Yates shuffle function that can be used to shuffle slices with random indexes.
- Loading branch information
Showing
6 changed files
with
518 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright (c) 2024 The Decred developers | ||
// Use of this source code is governed by an ISC | ||
// license that can be found in the LICENSE file. | ||
|
||
package rand | ||
|
||
import ( | ||
"io" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// Reader returns the default cryptographically secure userspace PRNG that is | ||
// periodically reseeded with entropy obtained from crypto/rand. | ||
// The returned Reader is safe for concurrent access. | ||
func Reader() io.Reader { | ||
return globalRand | ||
} | ||
|
||
type lockingPRNG struct { | ||
*PRNG | ||
mu sync.Mutex | ||
} | ||
|
||
var globalRand *lockingPRNG | ||
|
||
func init() { | ||
p, err := NewPRNG() | ||
if err != nil { | ||
panic(err) | ||
} | ||
globalRand = &lockingPRNG{PRNG: p} | ||
} | ||
|
||
func (p *lockingPRNG) Read(s []byte) (n int, err error) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
return p.PRNG.Read(s) | ||
} | ||
|
||
// Read fills b with random bytes obtained from the default userspace PRNG. | ||
func Read(b []byte) { | ||
// Mutex is acquired by (*lockingPRNG).Read. | ||
globalRand.Read(b) | ||
} | ||
|
||
// Uint32 returns a uniform random uint32. | ||
func Uint32() uint32 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Uint32() | ||
} | ||
|
||
// Uint64 returns a uniform random uint64. | ||
func Uint64() uint64 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Uint64() | ||
} | ||
|
||
// Uint32n returns a random uint32 in range [0,n) without modulo bias. | ||
func Uint32n(n uint32) uint32 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Uint32n(n) | ||
} | ||
|
||
// Uint64n returns a random uint32 in range [0,n) without modulo bias. | ||
func Uint64n(n uint64) uint64 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Uint64n(n) | ||
} | ||
|
||
// Int32 returns a random 31-bit non-negative integer as an int32 without | ||
// modulo bias. | ||
func Int32() int32 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Int32() | ||
} | ||
|
||
// Int32n returns, as an int32, a random 31-bit non-negative integer in [0,n) | ||
// without modulo bias. | ||
// Panics if n <= 0. | ||
func Int32n(n int32) int32 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Int32n(n) | ||
} | ||
|
||
// Int64 returns a random 63-bit non-negative integer as an int64 without | ||
// modulo bias. | ||
func Int64() int64 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Int64() | ||
} | ||
|
||
// Int64n returns, as an int64, a random 63-bit non-negative integer in [0,n) | ||
// without modulo bias. | ||
// Panics if n <= 0. | ||
func Int64n(n int64) int64 { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Int64n(n) | ||
} | ||
|
||
// Int returns a non-negative integer without bias. | ||
func Int() int { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Int() | ||
} | ||
|
||
// IntN returns, as an int, a random non-negative integer in [0,n) without | ||
// modulo bias. | ||
// Panics if n <= 0. | ||
func IntN(n int) int { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.IntN(n) | ||
} | ||
|
||
// UintN returns, as an uint, a random integer in [0,n) without modulo bias. | ||
func UintN(n uint) uint { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.UintN(n) | ||
} | ||
|
||
// Duration returns a random duration in [0,n) without modulo bias. | ||
// Panics if n <= 0. | ||
func Duration(n time.Duration) time.Duration { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
return globalRand.Duration(n) | ||
} | ||
|
||
// Shuffle randomizes the order of n elements by swapping the elements at | ||
// indexes i and j. | ||
// Panics if n < 0. | ||
func Shuffle(n int, swap func(i, j int)) { | ||
globalRand.mu.Lock() | ||
defer globalRand.mu.Unlock() | ||
|
||
globalRand.Shuffle(n, swap) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright (c) 2024 The Decred developers | ||
// Use of this source code is governed by an ISC | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package rand implements a fast userspace CSPRNG that is periodically | ||
// reseeded with entropy obtained from crypto/rand. The PRNG can be used to | ||
// obtain random bytes as well as generating uniformly-distributed integers in | ||
// a full or limited range. | ||
// | ||
// The default global PRNG will never panic after package init and is safe for | ||
// concurrent access. Additional PRNGs which avoid the locking overhead can | ||
// be created by calling NewPRNG. | ||
package rand |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module github.com/decred/dcrd/crypto/rand | ||
|
||
go 1.18 | ||
|
||
require golang.org/x/crypto v0.24.0 | ||
|
||
require golang.org/x/sys v0.21.0 // indirect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= | ||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= | ||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= | ||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright (c) 2024 The Decred developers | ||
// Use of this source code is governed by an ISC | ||
// license that can be found in the LICENSE file. | ||
|
||
package rand | ||
|
||
import ( | ||
cryptorand "crypto/rand" | ||
"encoding/binary" | ||
"math/bits" | ||
"time" | ||
|
||
"golang.org/x/crypto/chacha20" | ||
) | ||
|
||
const ( | ||
maxCipherRead = 4 * 1024 * 1024 // 4 MiB | ||
maxCipherDuration = 20 * time.Second | ||
) | ||
|
||
// nonce implements a 12-byte little endian counter suitable for use as an | ||
// incrementing ChaCha20 nonce. | ||
type nonce [chacha20.NonceSize]byte | ||
|
||
func (n *nonce) inc() { | ||
n0 := binary.LittleEndian.Uint32(n[0:4]) | ||
n1 := binary.LittleEndian.Uint32(n[4:8]) | ||
n2 := binary.LittleEndian.Uint32(n[8:12]) | ||
|
||
var carry uint32 | ||
n0, carry = bits.Add32(n0, 1, carry) | ||
n1, carry = bits.Add32(n1, 0, carry) | ||
n2, _ = bits.Add32(n2, 0, carry) | ||
|
||
binary.LittleEndian.PutUint32(n[0:4], n0) | ||
binary.LittleEndian.PutUint32(n[4:8], n1) | ||
binary.LittleEndian.PutUint32(n[8:12], n2) | ||
} | ||
|
||
// PRNG is a cryptographically secure pseudorandom number generator capable of | ||
// generating random bytes and integers. PRNG methods are not safe for | ||
// concurrent access. | ||
type PRNG struct { | ||
key [chacha20.KeySize]byte | ||
nonce nonce | ||
cipher chacha20.Cipher | ||
read int | ||
t time.Time | ||
} | ||
|
||
// NewPRNG returns a seeded PRNG. | ||
func NewPRNG() (*PRNG, error) { | ||
p := new(PRNG) | ||
err := p.seed() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return p, nil | ||
} | ||
|
||
// seed reseeds the prng with kernel and existing cipher entropy, if the | ||
// cipher has been originally seeded. | ||
// Only returns an error during initial seeding if a crypto/rand read errors. | ||
func (p *PRNG) seed() error { | ||
_, err := cryptorand.Read(p.key[:]) | ||
if err != nil && p.t.IsZero() { | ||
return err | ||
} | ||
p.cipher.XORKeyStream(p.key[:], p.key[:]) | ||
|
||
// never errors with correct key and nonce sizes | ||
cipher, _ := chacha20.NewUnauthenticatedCipher(p.key[:], p.nonce[:]) | ||
p.cipher = *cipher | ||
p.nonce.inc() | ||
p.read = 0 | ||
p.t = time.Now().Add(maxCipherDuration) | ||
return nil | ||
} | ||
|
||
// Read fills s with len(s) of cryptographically-secure random bytes. | ||
// Read never errors. | ||
func (p *PRNG) Read(s []byte) (n int, err error) { | ||
if time.Now().After(p.t) { | ||
// Reseed the cipher. | ||
// The panic will never be hit except by calling the Read | ||
// method on the zero PRNG value and if crypto/rand read fails. | ||
// Creating the PRNG properly with NewPRNG will return nil and an | ||
// error if the first seeding fails. | ||
// Later calls to seed will never return an error. | ||
if err := p.seed(); err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
for p.read+len(s) > maxCipherRead { | ||
l := maxCipherRead - p.read | ||
p.cipher.XORKeyStream(s[:l], s[:l]) | ||
p.seed() | ||
n += l | ||
s = s[l:] | ||
} | ||
p.cipher.XORKeyStream(s, s) | ||
p.read += len(s) | ||
n += len(s) | ||
return | ||
} |
Oops, something went wrong.