github.com/onflow/flow-go/crypto@v0.24.8/random/rand.go (about) 1 package random 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 ) 7 8 // Rand is a pseudo random number generator 9 // All methods update the internal state of the PRG 10 // which makes the PRGs implementing this interface 11 // non concurrent-safe. 12 type Rand interface { 13 // Read fills the input slice with random bytes. 14 Read([]byte) 15 16 // UintN returns a random number between 0 and N (exclusive) 17 UintN(uint64) uint64 18 19 // Permutation returns a permutation of the set [0,n-1] 20 // the theoretical output space grows very fast with (!n) so that input (n) should be chosen carefully 21 // to make sure the function output space covers a big chunk of the theoretical outputs. 22 // The function errors if the parameter is a negative integer. 23 Permutation(n int) ([]int, error) 24 25 // SubPermutation returns the m first elements of a permutation of [0,n-1] 26 // the theoretical output space can be large (n!/(n-m)!) so that the inputs should be chosen carefully 27 // to make sure the function output space covers a big chunk of the theoretical outputs. 28 // The function errors if the parameter is a negative integer. 29 SubPermutation(n int, m int) ([]int, error) 30 31 // Shuffle permutes an ordered data structure of an arbitrary type in place. The main use-case is 32 // permuting slice or array elements. (n) is the size of the data structure. 33 // the theoretical output space grows very fast with the slice size (n!) so that input (n) should be chosen carefully 34 // to make sure the function output space covers a big chunk of the theoretical outputs. 35 // The function errors if any of the parameters is a negative integer. 36 Shuffle(n int, swap func(i, j int)) error 37 38 // Samples picks (m) random ordered elements of a data structure of an arbitrary type of total size (n). The (m) elements are placed 39 // in the indices 0 to (m-1) with in place swapping. The data structure ends up being a permutation of the initial (n) elements. 40 // While the sampling of the (m) elements is pseudo-uniformly random, there is no guarantee about the uniformity of the permutation of 41 // the (n) elements. The function Shuffle should be used in case the entire (n) elements need to be shuffled. 42 // The main use-case of the data structure is a slice or array. 43 // The theoretical output space grows very fast with the slice size (n!/(n-m)!) so that inputs should be chosen carefully 44 // to make sure the function output space covers a big chunk of the theoretical outputs. 45 // The function errors if any of the parameters is a negative integer. 46 Samples(n int, m int, swap func(i, j int)) error 47 48 // Store returns the internal state of the random generator. 49 // The internal state can be used as a seed input for the function 50 // Restore to restore an identical PRG (with the same internal state) 51 Store() []byte 52 } 53 54 // randCore is PRG providing the core Read function of a PRG. 55 // All other Rand methods use the core Read method. 56 // 57 // In order to add a new Rand implementation, 58 // it should be enough to implement randCore. 59 type randCore interface { 60 // Read fills the input slice with random bytes. 61 Read([]byte) 62 } 63 64 // genericPRG implements all the Rand methods using the embedded randCore method. 65 // All implementations of the Rand interface should embed the genericPRG struct. 66 type genericPRG struct { 67 randCore 68 // buffer used by UintN function to avoid extra memory allocation 69 uintnBuffer [8]byte 70 } 71 72 // UintN returns an uint64 pseudo-random number in [0,n-1], 73 // using `p` as an entropy source. 74 // The function panics if input `n` is zero. 75 func (p *genericPRG) UintN(n uint64) uint64 { 76 if n == 0 { 77 panic("input to UintN can't be 0") 78 } 79 // the max returned random is n-1 80 max := n - 1 81 // count the size of max in bytes 82 size := 0 83 for tmp := max; tmp != 0; tmp >>= 8 { 84 size++ 85 } 86 // get the bit size of max 87 mask := uint64(0) 88 for max&mask != max { 89 mask = (mask << 1) | 1 90 } 91 92 // For a better uniformity of the result, loop till a sample is less or equal to `max`. 93 // This means the function might take longer time to output a random. 94 // Using the size of `max` in bits helps the loop end earlier. 95 // (a different approach would be to pull at least 128 bits from the random source 96 // and use big number modular reduction by `n`) 97 random := n 98 for random > max { 99 p.Read(p.uintnBuffer[:size]) // adjust to the size of max in bytes 100 random = binary.LittleEndian.Uint64(p.uintnBuffer[:]) 101 random &= mask // adjust to the size of max in bits 102 } 103 104 return random 105 } 106 107 // Permutation returns a permutation of the set [0,n-1]. 108 // It implements Fisher-Yates Shuffle (inside-out variant) using `p` as a random source. 109 // The output space grows very fast with (!n) so that input `n` should be chosen carefully 110 // to guarantee a good uniformity of the output. 111 // 112 // O(n) space and O(n) time. 113 func (p *genericPRG) Permutation(n int) ([]int, error) { 114 if n < 0 { 115 return nil, fmt.Errorf("population size cannot be negative") 116 } 117 items := make([]int, n) 118 for i := 0; i < n; i++ { 119 j := p.UintN(uint64(i + 1)) 120 items[i] = items[j] 121 items[j] = i 122 } 123 return items, nil 124 } 125 126 // SubPermutation returns the `m` first elements of a permutation of [0,n-1]. 127 // 128 // It implements Fisher-Yates Shuffle using `p` as a source of randoms. 129 // 130 // O(n) space and O(n) time 131 func (p *genericPRG) SubPermutation(n int, m int) ([]int, error) { 132 if m < 0 { 133 return nil, fmt.Errorf("sample size cannot be negative") 134 } 135 if n < m { 136 return nil, fmt.Errorf("sample size (%d) cannot be larger than entire population (%d)", m, n) 137 } 138 // condition n >= 0 is enforced by function Permutation(n) 139 items, _ := p.Permutation(n) 140 return items[:m], nil 141 } 142 143 // Shuffle permutes the given slice in place. 144 // 145 // It implements Fisher-Yates Shuffle using `p` as a source of randoms. 146 // 147 // O(1) space and O(n) time 148 func (p *genericPRG) Shuffle(n int, swap func(i, j int)) error { 149 if n < 0 { 150 return fmt.Errorf("population size cannot be negative") 151 } 152 return p.Samples(n, n, swap) 153 } 154 155 // Samples picks randomly m elements out of n elemnts and places them 156 // in random order at indices [0,m-1], the swapping being implemented in place. 157 // 158 // It implements the first (m) elements of Fisher-Yates Shuffle using `p` as a source of randoms. 159 // 160 // O(1) space and O(m) time 161 func (p *genericPRG) Samples(n int, m int, swap func(i, j int)) error { 162 if m < 0 { 163 return fmt.Errorf("sample size cannot be negative") 164 } 165 if n < m { 166 return fmt.Errorf("sample size (%d) cannot be larger than entire population (%d)", m, n) 167 } 168 for i := 0; i < m; i++ { 169 j := p.UintN(uint64(n - i)) 170 swap(i, i+int(j)) 171 } 172 return nil 173 }