prysm-pulse/beacon-chain/utils/shuffle.go

73 lines
2.6 KiB
Go
Raw Normal View History

// Package utils defines utility functions for the beacon-chain.
package utils
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/params"
)
// ShuffleIndices returns a list of pseudorandomly sampled
2018-12-09 19:21:23 +00:00
// indices. This is used to shuffle validators on ETH2.0 beacon chain.
func ShuffleIndices(seed common.Hash, indicesList []uint32) ([]uint32, error) {
// Each entropy is consumed from the seed in randBytes chunks.
randBytes := params.BeaconConfig().RandBytes
maxValidatorsPerRandBytes := params.BeaconConfig().MaxNumLog2Validators / randBytes
upperBound := 1<<(randBytes*maxValidatorsPerRandBytes) - 1
// Since we are consuming randBytes of entropy at a time in the loop,
// we have a bias at 2**24, this check defines our max list size and is used to remove the bias.
// more info on modulo bias: https://stackoverflow.com/questions/10984974/why-do-people-say-there-is-modulo-bias-when-using-a-random-number-generator.
2018-12-09 19:21:23 +00:00
if len(indicesList) >= upperBound {
return nil, errors.New("input list exceeded upper bound and reached modulo bias")
}
2018-12-09 19:21:23 +00:00
// Rehash the seed to obtain a new pattern of bytes.
hashSeed := hashutil.Hash(seed[:])
2018-12-09 19:21:23 +00:00
totalCount := len(indicesList)
index := 0
for index < totalCount-1 {
// Iterate through the hashSeed bytes in chunks of size randBytes.
for i := 0; i < 32-(32%int(randBytes)); i += int(randBytes) {
// Determine the number of indices remaining and exit if last index reached.
remaining := totalCount - index
if remaining == 1 {
break
}
// Read randBytes of hashSeed as a maxValidatorsPerRandBytes x randBytes big-endian integer.
randChunk := hashSeed[i : i+int(randBytes)]
var randValue int
for j := 0; j < int(randBytes); j++ {
randValue |= int(randChunk[j])
}
// Sample values greater than or equal to sampleMax will cause
// modulo bias when mapped into the remaining range.
randMax := upperBound - upperBound%remaining
// Perform swap if the consumed entropy will not cause modulo bias.
if randValue < randMax {
// Select replacement index from the current index.
replacementIndex := (randValue % remaining) + index
indicesList[index], indicesList[replacementIndex] = indicesList[replacementIndex], indicesList[index]
index++
}
}
}
2018-12-09 19:21:23 +00:00
return indicesList, nil
}
// SplitIndices splits a list into n pieces.
func SplitIndices(l []uint32, n uint64) [][]uint32 {
var divided [][]uint32
var lSize = uint64(len(l))
for i := uint64(0); i < n; i++ {
start := lSize * i / n
end := lSize * (i + 1) / n
divided = append(divided, l[start:end])
}
return divided
}