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 }