github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/stdlibs/crypto/chacha20/rand/rand.gno (about)

     1  package rand
     2  
     3  import (
     4  	"crypto/chacha20/chacha"
     5  	"encoding/binary"
     6  	"math"
     7  	"strconv"
     8  )
     9  
    10  func erase(b []byte) {
    11  	// compiles to memclr
    12  	for i := range b {
    13  		b[i] = 0
    14  	}
    15  }
    16  
    17  func copyAndErase(dst, src []byte) int {
    18  	n := copy(dst, src)
    19  	erase(src[:n])
    20  	return n
    21  }
    22  
    23  // An RNG is a cryptographically-strong RNG constructed from the ChaCha stream
    24  // cipher.
    25  type RNG struct {
    26  	buf    []byte
    27  	n      int
    28  	rounds int
    29  }
    30  
    31  // Read fills b with random data. It always returns len(b), nil.
    32  //
    33  // For performance reasons, calling Read once on a "large" buffer (larger than
    34  // the RNG's internal buffer) will produce different output than calling Read
    35  // multiple times on smaller buffers. If deterministic output is required,
    36  // clients should call Read in a loop; when copying to an io.Writer, use
    37  // io.CopyBuffer instead of io.Copy. Callers should also be aware that b is
    38  // xored with random data, not directly overwritten; this means that the new
    39  // contents of b depend on its previous contents.
    40  func (r *RNG) Read(b []byte) (int, error) {
    41  	if len(b) <= len(r.buf[r.n:]) {
    42  		// can fill b entirely from buffer
    43  		r.n += copyAndErase(b, r.buf[r.n:])
    44  	} else if len(b) <= len(r.buf[r.n:])+len(r.buf[chacha.KeySize:]) {
    45  		// b is larger than current buffer, but can be filled after a reseed
    46  		n := copy(b, r.buf[r.n:])
    47  		chacha.XORKeyStream(r.buf, r.buf, make([]byte, chacha.NonceSize), r.buf[:chacha.KeySize], r.rounds)
    48  		r.n = chacha.KeySize + copyAndErase(b[n:], r.buf[chacha.KeySize:])
    49  	} else {
    50  		// filling b would require multiple reseeds; instead, generate a
    51  		// temporary key, then write directly into b using that key
    52  		tmpKey := make([]byte, chacha.KeySize)
    53  		r.Read(tmpKey)
    54  		chacha.XORKeyStream(b, b, make([]byte, chacha.NonceSize), tmpKey, r.rounds)
    55  		erase(tmpKey)
    56  	}
    57  	return len(b), nil
    58  }
    59  
    60  // Bytes is a helper function that allocates and returns n bytes of random data.
    61  func (r *RNG) Bytes(n int) []byte {
    62  	b := make([]byte, n)
    63  	r.Read(b)
    64  	return b
    65  }
    66  
    67  // Entropy256 is a helper function that returns 256 bits of random data.
    68  func (r *RNG) Entropy256() (entropy [32]byte) {
    69  	r.Read(entropy[:])
    70  	return
    71  }
    72  
    73  // Entropy192 is a helper function that returns 192 bits of random data.
    74  func (r *RNG) Entropy192() (entropy [24]byte) {
    75  	r.Read(entropy[:])
    76  	return
    77  }
    78  
    79  // Entropy128 is a helper function that returns 128 bits of random data.
    80  func (r *RNG) Entropy128() (entropy [16]byte) {
    81  	r.Read(entropy[:])
    82  	return
    83  }
    84  
    85  // Uint64n returns a uniform random uint64 in [0,n). It panics if n == 0.
    86  func (r *RNG) Uint64n(n uint64) uint64 {
    87  	if n == 0 {
    88  		panic("frand: argument to Uint64n is 0")
    89  	}
    90  	// To eliminate modulo bias, keep selecting at random until we fall within
    91  	// a range that is evenly divisible by n.
    92  	// NOTE: since n is at most math.MaxUint64, max is minimized when:
    93  	//    n = math.MaxUint64/2 + 1 -> max = math.MaxUint64 - math.MaxUint64/2
    94  	// This gives an expected 2 tries before choosing a value < max.
    95  	max := math.MaxUint64 - math.MaxUint64%n
    96  	b := make([]byte, 8)
    97  again:
    98  	r.Read(b)
    99  	i := binary.LittleEndian.Uint64(b)
   100  	if i >= max {
   101  		goto again
   102  	}
   103  	return i % n
   104  }
   105  
   106  // Intn returns a uniform random int in [0,n). It panics if n <= 0.
   107  func (r *RNG) Intn(n int) int {
   108  	if n <= 0 {
   109  		panic("frand: argument to Intn is <= 0: " + strconv.Itoa(n))
   110  	}
   111  	// NOTE: since n is at most math.MaxUint64/2, max is minimized when:
   112  	//    n = math.MaxUint64/4 + 1 -> max = math.MaxUint64 - math.MaxUint64/4
   113  	// This gives an expected 1.333 tries before choosing a value < max.
   114  	return int(r.Uint64n(uint64(n)))
   115  }
   116  
   117  /* XXX floats not supported
   118  // Float64 returns a random float64 in [0,1).
   119  func (r *RNG) Float64() float64 {
   120  	return float64(r.Uint64n(1<<53)) / (1 << 53)
   121  }
   122  */
   123  
   124  // Perm returns a random permutation of the integers [0,n). It panics if n < 0.
   125  func (r *RNG) Perm(n int) []int {
   126  	m := make([]int, n)
   127  	for i := 1; i < n; i++ {
   128  		j := r.Intn(i + 1)
   129  		m[i] = m[j]
   130  		m[j] = i
   131  	}
   132  	return m
   133  }
   134  
   135  // Shuffle randomly permutes n elements by repeatedly calling swap in the range
   136  // [0,n). It panics if n < 0.
   137  func (r *RNG) Shuffle(n int, swap func(i, j int)) {
   138  	for i := n - 1; i > 0; i-- {
   139  		swap(i, r.Intn(i+1))
   140  	}
   141  }
   142  
   143  // NewCustom returns a new RNG instance seeded with the provided entropy and
   144  // using the specified buffer size and number of ChaCha rounds. It panics if
   145  // len(seed) != 32, bufsize < 32, or rounds != 8, 12 or 20.
   146  func NewCustom(seed []byte, bufsize int, rounds int) *RNG {
   147  	if len(seed) != chacha.KeySize {
   148  		panic("frand: invalid seed size")
   149  	} else if bufsize < chacha.KeySize {
   150  		panic("frand: bufsize must be at least 32")
   151  	} else if !(rounds == 8 || rounds == 12 || rounds == 20) {
   152  		panic("frand: rounds must be 8, 12, or 20")
   153  	}
   154  	buf := make([]byte, chacha.KeySize+bufsize)
   155  	chacha.XORKeyStream(buf, buf, make([]byte, chacha.NonceSize), seed, rounds)
   156  	return &RNG{
   157  		buf:    buf,
   158  		n:      chacha.KeySize,
   159  		rounds: rounds,
   160  	}
   161  }
   162  
   163  func NewFromSeed(seed []byte) *RNG {
   164  	return NewCustom(seed, 1024, 12)
   165  }
   166  
   167  /* XXX masterRNG deleted to make package pure
   168  // "master" RNG, seeded from crypto/rand; RNGs returned by New derive their seed
   169  // from this RNG. This means we only ever need to read system entropy a single
   170  // time, at startup.
   171  var masterRNG = func() *RNG {
   172  	seed := make([]byte, chacha.KeySize)
   173  	n, err := rand.Read(seed)
   174  	if err != nil || n != len(seed) {
   175  		panic("not enough system entropy to seed master RNG")
   176  	}
   177  	return NewCustom(seed, 1024, 12)
   178  }()
   179  var masterMu sync.Mutex
   180  
   181  // New returns a new RNG instance. The instance is seeded with entropy from
   182  // crypto/rand, albeit indirectly; its seed is generated by a global "master"
   183  // RNG, which itself is seeded from crypto/rand. This means the frand package
   184  // only reads system entropy once, at startup.
   185  func New() *RNG {
   186  	masterMu.Lock()
   187  	defer masterMu.Unlock()
   188  	return NewCustom(masterRNG.Bytes(32), 1024, 12)
   189  }
   190  
   191  // Global versions of each RNG method, leveraging a pool of RNGs.
   192  
   193  var rngpool = sync.Pool{
   194  	New: func() interface{} {
   195  		return New()
   196  	},
   197  }
   198  
   199  // Read fills b with random data. It always returns len(b), nil.
   200  func Read(b []byte) (int, error) {
   201  	r := rngpool.Get().(*RNG)
   202  	defer rngpool.Put(r)
   203  	return r.Read(b)
   204  }
   205  
   206  // Bytes is a helper function that allocates and returns n bytes of random data.
   207  func Bytes(n int) []byte {
   208  	r := rngpool.Get().(*RNG)
   209  	defer rngpool.Put(r)
   210  	return r.Bytes(n)
   211  }
   212  
   213  // Entropy256 is a helper function that returns 256 bits of random data.
   214  func Entropy256() [32]byte {
   215  	r := rngpool.Get().(*RNG)
   216  	defer rngpool.Put(r)
   217  	return r.Entropy256()
   218  }
   219  
   220  // Entropy192 is a helper function that returns 192 bits of random data.
   221  func Entropy192() [24]byte {
   222  	r := rngpool.Get().(*RNG)
   223  	defer rngpool.Put(r)
   224  	return r.Entropy192()
   225  }
   226  
   227  // Entropy128 is a helper function that returns 128 bits of random data.
   228  func Entropy128() [16]byte {
   229  	r := rngpool.Get().(*RNG)
   230  	defer rngpool.Put(r)
   231  	return r.Entropy128()
   232  }
   233  
   234  // Uint64n returns a uniform random uint64 in [0,n). It panics if n == 0.
   235  func Uint64n(n uint64) uint64 {
   236  	r := rngpool.Get().(*RNG)
   237  	defer rngpool.Put(r)
   238  	return r.Uint64n(n)
   239  }
   240  
   241  // Intn returns a uniform random int in [0,n). It panics if n <= 0.
   242  func Intn(n int) int {
   243  	r := rngpool.Get().(*RNG)
   244  	defer rngpool.Put(r)
   245  	return r.Intn(n)
   246  }
   247  
   248  // Float64 returns a random float64 in [0,1).
   249  func Float64() float64 {
   250  	r := rngpool.Get().(*RNG)
   251  	defer rngpool.Put(r)
   252  	return r.Float64()
   253  }
   254  
   255  // Perm returns a random permutation of the integers [0,n). It panics if n < 0.
   256  func Perm(n int) []int {
   257  	r := rngpool.Get().(*RNG)
   258  	defer rngpool.Put(r)
   259  	return r.Perm(n)
   260  }
   261  
   262  // Shuffle randomly permutes n elements by repeatedly calling swap in the range
   263  // [0,n). It panics if n < 0.
   264  func Shuffle(n int, swap func(i, j int)) {
   265  	r := rngpool.Get().(*RNG)
   266  	defer rngpool.Put(r)
   267  	r.Shuffle(n, swap)
   268  }
   269  
   270  // Reader is a global, shared instance of a cryptographically strong pseudo-
   271  // random generator. Reader is safe for concurrent use by multiple goroutines.
   272  var Reader rngReader
   273  
   274  type rngReader struct{}
   275  
   276  func (rngReader) Read(b []byte) (int, error) { return Read(b) }
   277  
   278  */
   279  
   280  // A Source is a math/rand-compatible source of entropy. It is safe for
   281  // concurrent use by multiple goroutines
   282  type Source struct {
   283  	rng *RNG
   284  	// mu  sync.Mutex XXX
   285  }
   286  
   287  // Seed uses the provided seed value to initialize the Source to a
   288  // deterministic state.
   289  func (s *Source) Seed(i int64) {
   290  	// s.mu.Lock()
   291  	// defer s.mu.Unlock()
   292  	seed := make([]byte, chacha.KeySize)
   293  	binary.LittleEndian.PutUint64(seed, uint64(i))
   294  	for i := range s.rng.buf {
   295  		s.rng.buf[i] = 0
   296  	}
   297  	chacha.XORKeyStream(s.rng.buf, s.rng.buf, make([]byte, chacha.NonceSize), seed, s.rng.rounds)
   298  	s.rng.n = chacha.KeySize
   299  }
   300  
   301  // Int63 returns a non-negative random 63-bit integer as an int64.
   302  func (s *Source) Int63() int64 {
   303  	// s.mu.Lock()
   304  	// defer s.mu.Unlock()
   305  	return int64(s.rng.Uint64n(1 << 63))
   306  }
   307  
   308  // Uint64 returns a random 64-bit integer.
   309  func (s *Source) Uint64() uint64 {
   310  	// s.mu.Lock()
   311  	// defer s.mu.Unlock()
   312  	return s.rng.Uint64n(math.MaxUint64)
   313  }
   314  
   315  // NewSource returns a source for use with the math/rand package.
   316  func NewSourceFromSeed(seed []byte) *Source { return &Source{rng: NewFromSeed(seed)} }