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  }