github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/utils/rand/rand.go (about)

     1  // Package rand is a wrapper around `crypto/rand` that uses the system RNG underneath
     2  // to extract secure entropy.
     3  //
     4  // It implements useful tools that are not exported by the `crypto/rand` package.
     5  // This package should be used instead of `math/rand` for any use-case requiring
     6  // a secure randomness. It provides similar APIs to the ones provided by `math/rand`.
     7  // This package does not implement any determinstic RNG (Pseudo-RNG) based on
     8  // user input seeds. For the deterministic use-cases please use `github.com/onflow/crypto/random`.
     9  //
    10  // Functions in this package may return an error if the underlying system implementation fails
    11  // to read new randoms. When that happens, this package considers it an irrecoverable exception.
    12  package rand
    13  
    14  import (
    15  	"crypto/rand"
    16  	"encoding/binary"
    17  	"fmt"
    18  )
    19  
    20  // Uint64 returns a random uint64.
    21  //
    22  // It returns:
    23  //   - (0, exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
    24  //   - (random, nil) otherwise
    25  func Uint64() (uint64, error) {
    26  	// allocate a new memory at each call. Another possibility
    27  	// is to use a global variable but that would make the package non thread safe
    28  	buffer := make([]byte, 8)
    29  	if _, err := rand.Read(buffer); err != nil { // checking err in crypto/rand.Read is enough
    30  		return 0, fmt.Errorf("crypto/rand read failed: %w", err)
    31  	}
    32  	r := binary.LittleEndian.Uint64(buffer)
    33  	return r, nil
    34  }
    35  
    36  // Uint64n returns a random uint64 strictly less than `n`.
    37  // `n` has to be a strictly positive integer.
    38  //
    39  // It returns:
    40  //   - (0, exception) if `n==0`
    41  //   - (0, exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
    42  //   - (random, nil) otherwise
    43  func Uint64n(n uint64) (uint64, error) {
    44  	if n == 0 {
    45  		return 0, fmt.Errorf("n should be strictly positive, got %d", n)
    46  	}
    47  	// the max returned random is n-1 > 0
    48  	max := n - 1
    49  	// count the bytes size of max
    50  	size := 0
    51  	for tmp := max; tmp != 0; tmp >>= 8 {
    52  		size++
    53  	}
    54  	// get the bit size of max
    55  	mask := uint64(0)
    56  	for max&mask != max {
    57  		mask = (mask << 1) | 1
    58  	}
    59  
    60  	// allocate a new memory at each call. Another possibility
    61  	// is to use a global variable but that would make the package non thread safe
    62  	buffer := make([]byte, 8)
    63  
    64  	// Using 64 bits of random and reducing modulo n does not guarantee a high uniformity
    65  	// of the result.
    66  	// For a better uniformity, loop till a sample is less or equal to `max`.
    67  	// This means the function might take longer time to output a random.
    68  	// Using the size of `max` in bits helps the loop end earlier (the algo stops after one loop
    69  	// with more than 50%)
    70  	// a different approach would be to pull at least 128 bits from the random source
    71  	// and use big number modular reduction by `n`.
    72  	random := n
    73  	for random > max {
    74  		if _, err := rand.Read(buffer[:size]); err != nil { // checking err in crypto/rand.Read is enough
    75  			return 0, fmt.Errorf("crypto/rand read failed: %w", err)
    76  		}
    77  		random = binary.LittleEndian.Uint64(buffer)
    78  		random &= mask // adjust to the size of max in bits
    79  	}
    80  	return random, nil
    81  }
    82  
    83  // Uint32 returns a random uint32.
    84  //
    85  // It returns:
    86  //   - (0, exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
    87  //   - (random, nil) otherwise
    88  func Uint32() (uint32, error) {
    89  	// for 64-bits machines, doing 64 bits operations and then casting
    90  	// should be faster than dealing with 32 bits operations
    91  	r, err := Uint64()
    92  	return uint32(r), err
    93  }
    94  
    95  // Uint32n returns a random uint32 strictly less than `n`.
    96  // `n` has to be a strictly positive integer.
    97  //
    98  // It returns an error:
    99  //   - (0, exception) if `n==0`
   100  //   - (0, exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
   101  //   - (random, nil) otherwise
   102  func Uint32n(n uint32) (uint32, error) {
   103  	r, err := Uint64n(uint64(n))
   104  	return uint32(r), err
   105  }
   106  
   107  // Uint returns a random uint.
   108  //
   109  // It returns:
   110  //   - (0, exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
   111  //   - (random, nil) otherwise
   112  func Uint() (uint, error) {
   113  	r, err := Uint64()
   114  	return uint(r), err
   115  }
   116  
   117  // Uintn returns a random uint strictly less than `n`.
   118  // `n` has to be a strictly positive integer.
   119  //
   120  // It returns an error:
   121  //   - (0, exception) if `n==0`
   122  //   - (0, exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
   123  //   - (random, nil) otherwise
   124  func Uintn(n uint) (uint, error) {
   125  	r, err := Uint64n(uint64(n))
   126  	return uint(r), err
   127  }
   128  
   129  // Shuffle permutes a data structure in place
   130  // based on the provided `swap` function.
   131  // It is not deterministic.
   132  //
   133  // It implements Fisher-Yates Shuffle using crypto/rand as a source of randoms.
   134  // It uses O(1) space and O(n) time
   135  //
   136  // It returns:
   137  //   - (exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
   138  //   - (nil) otherwise
   139  func Shuffle(n uint, swap func(i, j uint)) error {
   140  	return Samples(n, n, swap)
   141  }
   142  
   143  // Samples picks randomly `m` elements out of `n` elements in a data structure
   144  // and places them in random order at indices [0,m-1],
   145  // the swapping being implemented in place. The data structure is defined
   146  // by the `swap` function itself.
   147  // Sampling is not deterministic like the other functions of the package.
   148  //
   149  // It implements the first `m` elements of Fisher-Yates Shuffle using
   150  // crypto/rand as a source of randoms. `m` has to be less or equal to `n`.
   151  // It uses O(1) space and O(m) time
   152  //
   153  // It returns:
   154  //   - (exception) if `n < m`
   155  //   - (exception) if crypto/rand fails to provide entropy which is likely a result of a system error.
   156  //   - (nil) otherwise
   157  func Samples(n uint, m uint, swap func(i, j uint)) error {
   158  	if n < m {
   159  		return fmt.Errorf("sample size (%d) cannot be larger than entire population (%d)", m, n)
   160  	}
   161  	for i := uint(0); i < m; i++ {
   162  		j, err := Uintn(n - i)
   163  		if err != nil {
   164  			return err
   165  		}
   166  		swap(i, i+j)
   167  	}
   168  	return nil
   169  }