Switch hash functions, prevent underflow & verify that shuffling works (#149)

* prevent balance underflow, per spec implementation note

* verify that permutation shuffing works: add YAML dependency to be used for ingesting test cases; switch from blake2 to keccak256 to match EF test cases; remove inefficient naive-spec-version of shuffling algorithm now that protolambda's can be tested directly
This commit is contained in:
Dustin Brody 2019-03-01 23:50:01 +00:00 committed by Jacek Sieka
parent 68a39a21be
commit 81d5becc7b
7 changed files with 111 additions and 104 deletions

View File

@ -22,7 +22,8 @@ requires "nim >= 0.19.0",
"serialization",
"json_serialization",
"json_rpc",
"chronos"
"chronos",
"yaml"
### Helper functions
proc test(name: string, defaultLang = "c") =

View File

@ -121,6 +121,10 @@ func exit_validator*(state: var BeaconState,
validator.exit_epoch = get_entry_exit_effect_epoch(get_current_epoch(state))
func reduce_balance*(balance: var uint64, amount: uint64) =
# Not in spec, but useful to avoid underflow.
balance -= min(amount, balance)
func slash_validator*(state: var BeaconState, index: ValidatorIndex) =
## Slash the validator with index ``index``.
## Note that this function mutates ``state``.
@ -140,7 +144,7 @@ func slash_validator*(state: var BeaconState, index: ValidatorIndex) =
WHISTLEBLOWER_REWARD_QUOTIENT
state.validator_balances[whistleblower_index] += whistleblower_reward
state.validator_balances[index] -= whistleblower_reward
reduce_balance(state.validator_balances[index], whistleblower_reward)
validator.slashed_epoch = get_current_epoch(state)
# Spec bug in v0.3.0, fixed since: it has LATEST_PENALIZED_EXIT_LENGTH

View File

@ -21,7 +21,7 @@
# `eth2hash`, and it outputs a `Eth2Digest`. Easy to sed :)
import
nimcrypto/[blake2, hash], eth/common/eth_types_json_serialization,
nimcrypto/[keccak, hash], eth/common/eth_types_json_serialization,
hashes
export
@ -29,7 +29,7 @@ export
type
Eth2Digest* = MDigest[32 * 8] ## `hash32` from spec
Eth2Hash* = blake2_512 ## Context for hash function
Eth2Hash* = keccak256 ## Context for hash function
func shortLog*(x: Eth2Digest): string =
($x)[0..7]

View File

@ -12,73 +12,11 @@ import
../ssz,
./crypto, ./datatypes, ./digest, ./helpers
# TODO remove once there are test vectors to check with directly
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_permuted_index
func get_permuted_index_spec(index: uint64, list_size: uint64, seed: Eth2Digest): uint64 =
## Return `p(index)` in a pseudorandom permutation `p` of `0...list_size-1`
## with ``seed`` as entropy.
##
## Utilizes 'swap or not' shuffling found in
## https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf
## See the 'generalized domain' algorithm on page 3.
result = index
var pivot_buffer: array[(32+1), byte]
var source_buffer: array[(32+1+4), byte]
for round in 0 ..< SHUFFLE_ROUND_COUNT:
pivot_buffer[0..31] = seed.data
let round_bytes1 = int_to_bytes1(round)[0]
pivot_buffer[32] = round_bytes1
let
pivot = bytes_to_int(eth2hash(pivot_buffer).data[0..7]) mod list_size
flip = (pivot - result) mod list_size
position = max(result, flip)
## Tradeoff between slicing (if reusing one larger buffer) and additional
## copies here of seed and `int_to_bytes1(round)`.
source_buffer[0..31] = seed.data
source_buffer[32] = round_bytes1
source_buffer[33..36] = int_to_bytes4(position div 256)
let
source = eth2hash(source_buffer).data
byte_value = source[(position mod 256) div 8]
bit = (byte_value shr (position mod 8)) mod 2
if bit != 0:
result = flip
# TODO remove once there are test vectors to check with directly
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling
func get_shuffling_spec*(seed: Eth2Digest, validators: openArray[Validator],
epoch: Epoch): seq[seq[ValidatorIndex]] =
## Shuffles ``validators`` into crosslink committees seeded by ``seed`` and
## ``slot``.
## Returns a list of ``SLOTS_PER_EPOCH * committees_per_slot`` committees where
## each committee is itself a list of validator indices.
let
active_validator_indices = get_active_validator_indices(validators, epoch)
committees_per_epoch = get_epoch_committee_count(
len(active_validator_indices)).int
shuffled_active_validator_indices = mapIt(
active_validator_indices,
active_validator_indices[get_permuted_index_spec(
it, len(active_validator_indices).uint64, seed).int])
# Split the shuffled list into committees_per_epoch pieces
result = split(shuffled_active_validator_indices, committees_per_epoch)
assert result.len() == committees_per_epoch # what split should do..
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_permuted_index
func get_shuffling*(seed: Eth2Digest,
validators: openArray[Validator],
epoch: Epoch
): seq[seq[ValidatorIndex]] =
func get_shuffled_seq*(seed: Eth2Digest,
validators: openArray[Validator],
): seq[ValidatorIndex] =
## Via https://github.com/protolambda/eth2-shuffle/blob/master/shuffle.go
## Shuffles ``validators`` into crosslink committees seeded by ``seed`` and
## ``slot``.
@ -87,11 +25,7 @@ func get_shuffling*(seed: Eth2Digest,
##
## Invert the inner/outer loops from the spec, essentially. Most useful
## hash result re-use occurs within a round.
let
active_validator_indices = get_active_validator_indices(validators, epoch)
list_size = active_validator_indices.len.uint64
committees_per_epoch = get_epoch_committee_count(
len(active_validator_indices)).int
let list_size = validators.len.uint64
var
# Share these buffers.
pivot_buffer: array[(32+1), byte]
@ -116,7 +50,7 @@ func get_shuffling*(seed: Eth2Digest,
## Only need to run, per round, position div 256 hashes, so precalculate
## them. This consumes memory, but for low-memory devices, it's possible
## to mitigate by some light LRU caching and similar.
for reduced_position in 0 ..< list_size.int div 256:
for reduced_position in 0 ..< sources.len:
source_buffer[33..36] = int_to_bytes4(reduced_position.uint64)
sources[reduced_position] = eth2hash(source_buffer)
@ -124,9 +58,9 @@ func get_shuffling*(seed: Eth2Digest,
## efficiency gains exist in caching and re-using data.
for index in 0 ..< list_size.int:
let
cur_idx_permutated = shuffled_active_validator_indices[index]
flip = (pivot - cur_idx_permutated.uint64) mod list_size
position = max(cur_idx_permutated, flip.int)
cur_idx_permuted = shuffled_active_validator_indices[index]
flip = ((list_size + pivot) - cur_idx_permuted.uint64) mod list_size
position = max(cur_idx_permuted, flip.int)
let
source = sources[position div 256].data
@ -136,8 +70,26 @@ func get_shuffling*(seed: Eth2Digest,
if bit != 0:
shuffled_active_validator_indices[index] = flip.ValidatorIndex
result = shuffled_active_validator_indices
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling
func get_shuffling*(seed: Eth2Digest,
validators: openArray[Validator],
epoch: Epoch
): seq[seq[ValidatorIndex]] =
## This function is factored to facilitate testing with
## https://github.com/ethereum/eth2.0-test-generators/tree/master/permutated_index
## test vectors, which the split of get_shuffling obfuscates.
let
active_validator_indices = get_active_validator_indices(validators, epoch)
committees_per_epoch = get_epoch_committee_count(
len(active_validator_indices)).int
# Split the shuffled list into committees_per_epoch pieces
result = split(shuffled_active_validator_indices, committees_per_epoch)
result = split(
get_shuffled_seq(seed, validators),
committees_per_epoch)
assert result.len() == committees_per_epoch # what split should do..
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_previous_epoch_committee_count

View File

@ -10,7 +10,7 @@
import
endians, typetraits, options, algorithm,
eth/common, nimcrypto/blake2,
eth/common, nimcrypto/keccak,
./spec/[crypto, datatypes, digest]
# ################### Helper functions ###################################

View File

@ -125,7 +125,7 @@ func slashValidator(state: var BeaconState, index: ValidatorIndex) =
whistleblower_reward = get_effective_balance(state, index) div
WHISTLEBLOWER_REWARD_QUOTIENT
state.validator_balances[whistleblower_index] += whistleblower_reward
state.validator_balances[index] -= whistleblower_reward
reduce_balance(state.validator_balances[index], whistleblower_reward)
validator.slashed_epoch = get_current_epoch(state)
# v0.3.0 spec bug, fixed later, involving renamed constants. Use v0.3.0 name.
@ -397,8 +397,9 @@ proc processTransfers(state: var BeaconState, blck: BeaconBlock,
notice "Transfer: incorrect signature"
return false
state.validator_balances[
transfer.from_field.int] -= transfer.amount + transfer.fee
reduce_balance(
state.validator_balances[transfer.from_field.int],
transfer.amount + transfer.fee)
state.validator_balances[transfer.to.int] += transfer.amount
state.validator_balances[
get_beacon_proposer_index(state, state.slot)] += transfer.fee
@ -543,7 +544,7 @@ func process_slashings(state: var BeaconState) =
min(total_penalties * 3, total_balance) div total_balance,
get_effective_balance(state, index.ValidatorIndex) div
MIN_PENALTY_QUOTIENT)
state.validator_balances[index] -= penalty
reduce_balance(state.validator_balances[index], penalty)
func process_exit_queue(state: var BeaconState) =
## Process the exit queue.
@ -804,8 +805,8 @@ func processEpoch(state: var BeaconState) =
for v in active_validator_indices:
if v notin attesters:
# TODO underflows?
statePtr.validator_balances[v] -= base_reward(statePtr[], v)
reduce_balance(
statePtr.validator_balances[v], base_reward(statePtr[], v))
if epochs_since_finality <= 4'u64:
# Case 1: epochs_since_finality <= 4
@ -833,24 +834,28 @@ func processEpoch(state: var BeaconState) =
else:
# Case 2: epochs_since_finality > 4
for index in active_validator_indices:
# TODO underflows?
if index notin previous_epoch_attester_indices:
state.validator_balances[index] -=
inactivity_penalty(state, index, epochs_since_finality)
reduce_balance(
state.validator_balances[index],
inactivity_penalty(state, index, epochs_since_finality))
if index notin previous_epoch_boundary_attester_indices:
state.validator_balances[index] -=
inactivity_penalty(state, index, epochs_since_finality)
reduce_balance(
state.validator_balances[index],
inactivity_penalty(state, index, epochs_since_finality))
if index notin previous_epoch_head_attester_indices:
state.validator_balances[index] -= base_reward(state, index)
reduce_balance(
state.validator_balances[index], base_reward(state, index))
if state.validator_registry[index].slashed_epoch <= current_epoch:
state.validator_balances[index] -=
reduce_balance(
state.validator_balances[index],
2'u64 * inactivity_penalty(
state, index, epochs_since_finality) + base_reward(state, index)
state, index, epochs_since_finality) + base_reward(state, index))
if index in previous_epoch_attester_indices:
state.validator_balances[index] -=
reduce_balance(
state.validator_balances[index],
base_reward(state, index) -
base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY div
inclusion_distance(state, index)
inclusion_distance(state, index))
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#attestation-inclusion
block:
@ -872,8 +877,8 @@ func processEpoch(state: var BeaconState) =
total_attesting_balance(crosslink_committee) div
get_total_balance(state, crosslink_committee.committee)
else:
# TODO underflows?
state.validator_balances[index] -= base_reward(state, index)
reduce_balance(
state.validator_balances[index], base_reward(state, index))
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#ejections
process_ejections(state)

View File

@ -16,11 +16,19 @@ func sumCommittees(v: openArray[seq[ValidatorIndex]], reqCommitteeLen: int): int
assert x.len == reqCommitteeLen
inc result, x.len
func checkPermutation(index: int, list_size: uint64,
permuted_index: int, seed: Eth2Digest): bool =
let
validators = repeat(
Validator(exit_epoch: FAR_FUTURE_EPOCH), list_size)
s = get_shuffled_seq(seed, validators)
s[index] == permuted_index.ValidatorIndex
suite "Validators":
## For now just test that we can compile and execute block processing with mock data.
## https://github.com/status-im/nim-beacon-chain/issues/1
## https://github.com/sigp/lighthouse/blob/ba548e49a52687a655c61b443b6835d79c6d4236/beacon_chain/validator_shuffling/src/shuffle.rs
test "Smoke validator shuffling":
test "Validator shuffling":
let
num_validators = 32*1024
validators = repeat(
@ -28,13 +36,50 @@ suite "Validators":
exit_epoch: FAR_FUTURE_EPOCH
), num_validators)
s = get_shuffling(Eth2Digest(), validators, 0)
#s_spec = get_shuffling_spec(Eth2Digest(), validators, 0)
#s = get_shuffling_spec(Eth2Digest(), validators, 0)
committees = get_epoch_committee_count(len(validators)).int
check:
## Enable checking equivalence of spec and optimized versions.
## TODO enable checking against YAML test vectors
#s == s_spec
# def b(s): return "Eth2Digest(data: [0x" + "'u8, 0x".join((s[i:i+2] for i in range(0, 64, 2))) + "'u8])"
# TODO read YAML file directly from test case generator
checkPermutation(0, 1, 0, Eth2Digest(data: [0xc0'u8, 0xc7'u8, 0xf2'u8, 0x26'u8, 0xfb'u8, 0xd5'u8, 0x74'u8, 0xa8'u8, 0xc6'u8, 0x3d'u8, 0xc2'u8, 0x68'u8, 0x64'u8, 0xc2'u8, 0x78'u8, 0x33'u8, 0xea'u8, 0x93'u8, 0x1e'u8, 0x7c'u8, 0x70'u8, 0xb3'u8, 0x44'u8, 0x09'u8, 0xba'u8, 0x76'u8, 0x5f'u8, 0x3d'u8, 0x20'u8, 0x31'u8, 0x63'u8, 0x3d'u8]))
checkPermutation(0, 2, 0, Eth2Digest(data: [0xb2'u8, 0x04'u8, 0x20'u8, 0xb2'u8, 0xb7'u8, 0xb1'u8, 0xc6'u8, 0x46'u8, 0x00'u8, 0xcb'u8, 0xe9'u8, 0x62'u8, 0x54'u8, 0x40'u8, 0x52'u8, 0xd0'u8, 0xbb'u8, 0xe1'u8, 0x3d'u8, 0xa4'u8, 0x03'u8, 0x95'u8, 0x0d'u8, 0x19'u8, 0x8d'u8, 0x4f'u8, 0x4e'u8, 0xa2'u8, 0x87'u8, 0x62'u8, 0x95'u8, 0x3f'u8]))
checkPermutation(1, 2, 0, Eth2Digest(data: [0x11'u8, 0xf1'u8, 0x32'u8, 0x2c'u8, 0x3a'u8, 0x4c'u8, 0xfc'u8, 0xe2'u8, 0x0e'u8, 0xfb'u8, 0x7d'u8, 0x7e'u8, 0xca'u8, 0x50'u8, 0x29'u8, 0x14'u8, 0x70'u8, 0x04'u8, 0x3d'u8, 0x6e'u8, 0x8a'u8, 0x2c'u8, 0x62'u8, 0x95'u8, 0x6e'u8, 0x68'u8, 0x75'u8, 0x71'u8, 0x60'u8, 0x7d'u8, 0x3f'u8, 0x0e'u8]))
checkPermutation(0, 3, 2, Eth2Digest(data: [0x5b'u8, 0xd0'u8, 0xaf'u8, 0x3f'u8, 0x74'u8, 0xfe'u8, 0x69'u8, 0x86'u8, 0xbb'u8, 0x99'u8, 0xb3'u8, 0xec'u8, 0xc0'u8, 0xea'u8, 0x15'u8, 0xa4'u8, 0x03'u8, 0x45'u8, 0x6c'u8, 0xe7'u8, 0x08'u8, 0xc0'u8, 0x5c'u8, 0xee'u8, 0xed'u8, 0xdc'u8, 0x0a'u8, 0x42'u8, 0x05'u8, 0xca'u8, 0xf0'u8, 0x72'u8]))
checkPermutation(1, 3, 1, Eth2Digest(data: [0xba'u8, 0x06'u8, 0xff'u8, 0x9b'u8, 0xde'u8, 0x03'u8, 0xf3'u8, 0x7e'u8, 0xdd'u8, 0xea'u8, 0xcb'u8, 0x26'u8, 0x1a'u8, 0x51'u8, 0x10'u8, 0x96'u8, 0x76'u8, 0xd5'u8, 0x49'u8, 0xc1'u8, 0xbe'u8, 0xa3'u8, 0xb8'u8, 0x1e'u8, 0xdd'u8, 0x82'u8, 0xdf'u8, 0x68'u8, 0xcc'u8, 0x03'u8, 0xa9'u8, 0x7f'u8]))
checkPermutation(2, 3, 2, Eth2Digest(data: [0xf5'u8, 0x8a'u8, 0x89'u8, 0x70'u8, 0xc6'u8, 0x3c'u8, 0xa8'u8, 0x6d'u8, 0xd3'u8, 0xb8'u8, 0xb8'u8, 0xa6'u8, 0x15'u8, 0x30'u8, 0x2e'u8, 0xc0'u8, 0x6c'u8, 0xdd'u8, 0xea'u8, 0x12'u8, 0x79'u8, 0xbf'u8, 0x4a'u8, 0x27'u8, 0x25'u8, 0xc7'u8, 0x81'u8, 0xce'u8, 0x6a'u8, 0xba'u8, 0x34'u8, 0x8d'u8]))
checkPermutation(0, 1024, 1005, Eth2Digest(data: [0x38'u8, 0x35'u8, 0x56'u8, 0xe2'u8, 0x3f'u8, 0xcb'u8, 0x9e'u8, 0x73'u8, 0xc2'u8, 0x3a'u8, 0xd3'u8, 0x3c'u8, 0xfb'u8, 0x50'u8, 0xf4'u8, 0xc0'u8, 0x98'u8, 0xf4'u8, 0x96'u8, 0x88'u8, 0xa8'u8, 0x4b'u8, 0x12'u8, 0x8c'u8, 0x28'u8, 0x85'u8, 0x96'u8, 0x0e'u8, 0x5f'u8, 0x1b'u8, 0x39'u8, 0x82'u8]))
checkPermutation(1023, 1024, 934, Eth2Digest(data: [0x2e'u8, 0xe5'u8, 0xda'u8, 0xb3'u8, 0x0a'u8, 0xd1'u8, 0x58'u8, 0x0c'u8, 0xda'u8, 0xbb'u8, 0x17'u8, 0x5a'u8, 0x4b'u8, 0x15'u8, 0x12'u8, 0xca'u8, 0xc5'u8, 0x56'u8, 0x68'u8, 0x66'u8, 0xd6'u8, 0x5a'u8, 0x15'u8, 0xe9'u8, 0xe2'u8, 0x2c'u8, 0x84'u8, 0x44'u8, 0xf4'u8, 0x60'u8, 0xc9'u8, 0xdc'u8]))
checkPermutation(3925, 4040, 32, Eth2Digest(data: [0x34'u8, 0xa3'u8, 0xc1'u8, 0x3f'u8, 0x21'u8, 0x1e'u8, 0x63'u8, 0xc5'u8, 0x6e'u8, 0x9e'u8, 0x11'u8, 0x87'u8, 0xf3'u8, 0x1a'u8, 0x56'u8, 0xa4'u8, 0x23'u8, 0x0d'u8, 0x8d'u8, 0x5b'u8, 0xf5'u8, 0xe5'u8, 0x84'u8, 0xf0'u8, 0xe4'u8, 0xfe'u8, 0x93'u8, 0x94'u8, 0x6a'u8, 0xf9'u8, 0x1c'u8, 0xce'u8]))
checkPermutation(885, 2417, 1822, Eth2Digest(data: [0x13'u8, 0x46'u8, 0xe3'u8, 0x97'u8, 0x08'u8, 0x15'u8, 0x10'u8, 0x71'u8, 0x54'u8, 0xb5'u8, 0x8b'u8, 0x1e'u8, 0xff'u8, 0x41'u8, 0x1b'u8, 0xfc'u8, 0xa3'u8, 0x34'u8, 0x2e'u8, 0xa0'u8, 0xd8'u8, 0x28'u8, 0x2a'u8, 0x86'u8, 0x30'u8, 0x4d'u8, 0x79'u8, 0xd6'u8, 0x2d'u8, 0x5f'u8, 0x3c'u8, 0x52'u8]))
checkPermutation(840, 1805, 808, Eth2Digest(data: [0x08'u8, 0x10'u8, 0xc1'u8, 0x04'u8, 0xb7'u8, 0x5e'u8, 0x25'u8, 0xbf'u8, 0x89'u8, 0xc0'u8, 0x06'u8, 0x6d'u8, 0xee'u8, 0xbc'u8, 0x34'u8, 0x61'u8, 0x93'u8, 0x7f'u8, 0xc0'u8, 0xe7'u8, 0x2a'u8, 0xe0'u8, 0x4e'u8, 0xe7'u8, 0x4f'u8, 0x24'u8, 0x56'u8, 0x16'u8, 0xc1'u8, 0x57'u8, 0x18'u8, 0xdf'u8]))
checkPermutation(881, 1788, 582, Eth2Digest(data: [0x34'u8, 0xad'u8, 0xb3'u8, 0x5f'u8, 0x3f'u8, 0xc2'u8, 0x88'u8, 0x0d'u8, 0x22'u8, 0x0e'u8, 0x52'u8, 0x01'u8, 0x20'u8, 0xa0'u8, 0x32'u8, 0xbb'u8, 0xaa'u8, 0x0f'u8, 0x4b'u8, 0xd7'u8, 0xa5'u8, 0xfc'u8, 0xf1'u8, 0xc2'u8, 0x26'u8, 0x9d'u8, 0xe2'u8, 0x10'u8, 0x75'u8, 0xe7'u8, 0xa4'u8, 0x64'u8]))
checkPermutation(1362, 1817, 1018, Eth2Digest(data: [0xc9'u8, 0xb0'u8, 0xc7'u8, 0x6e'u8, 0x11'u8, 0xf4'u8, 0xc3'u8, 0xc3'u8, 0xc3'u8, 0x8b'u8, 0x44'u8, 0x7a'u8, 0xca'u8, 0x53'u8, 0x52'u8, 0xd9'u8, 0x31'u8, 0x32'u8, 0xad'u8, 0x56'u8, 0x78'u8, 0xda'u8, 0x42'u8, 0x0c'u8, 0xa2'u8, 0xe6'u8, 0x9d'u8, 0x92'u8, 0x58'u8, 0x8e'u8, 0x0f'u8, 0xba'u8]))
checkPermutation(28, 111, 0, Eth2Digest(data: [0x29'u8, 0x31'u8, 0x45'u8, 0xc3'u8, 0x1a'u8, 0xeb'u8, 0x3e'u8, 0xb2'u8, 0x9c'u8, 0xcd'u8, 0xf3'u8, 0x32'u8, 0x7d'u8, 0x0f'u8, 0x3d'u8, 0xd4'u8, 0x59'u8, 0x2c'u8, 0xdf'u8, 0xb2'u8, 0xfa'u8, 0xd3'u8, 0x70'u8, 0x32'u8, 0x29'u8, 0xc6'u8, 0xc2'u8, 0xe7'u8, 0x20'u8, 0xdc'u8, 0x79'u8, 0x2f'u8]))
checkPermutation(959, 2558, 2094, Eth2Digest(data: [0xc9'u8, 0xf4'u8, 0xc5'u8, 0xfb'u8, 0xb2'u8, 0xa3'u8, 0x97'u8, 0xfd'u8, 0x8e'u8, 0xa3'u8, 0x6d'u8, 0xbf'u8, 0xce'u8, 0xc0'u8, 0xd7'u8, 0x33'u8, 0xd0'u8, 0xaf'u8, 0x7e'u8, 0xc3'u8, 0xa0'u8, 0x3d'u8, 0x78'u8, 0x9a'u8, 0x66'u8, 0x23'u8, 0x1f'u8, 0x3b'u8, 0xc7'u8, 0xca'u8, 0xfa'u8, 0x5e'u8]))
checkPermutation(887, 2406, 831, Eth2Digest(data: [0x56'u8, 0x57'u8, 0x29'u8, 0xe0'u8, 0xd5'u8, 0xde'u8, 0x52'u8, 0x4e'u8, 0x6d'u8, 0xee'u8, 0x54'u8, 0xd1'u8, 0xb8'u8, 0xb5'u8, 0x88'u8, 0x2a'u8, 0xd8'u8, 0xe5'u8, 0x5c'u8, 0x18'u8, 0xa3'u8, 0x04'u8, 0x62'u8, 0xac'u8, 0x02'u8, 0xc4'u8, 0xbb'u8, 0x86'u8, 0xc2'u8, 0x7d'u8, 0x26'u8, 0xcb'u8]))
checkPermutation(3526, 3674, 3531, Eth2Digest(data: [0x29'u8, 0x51'u8, 0x39'u8, 0x5b'u8, 0x1a'u8, 0x1b'u8, 0xbd'u8, 0xa8'u8, 0xd5'u8, 0x3b'u8, 0x77'u8, 0x6c'u8, 0x7f'u8, 0xc8'u8, 0xbd'u8, 0xad'u8, 0x60'u8, 0x30'u8, 0xde'u8, 0x94'u8, 0x3c'u8, 0x4e'u8, 0x3f'u8, 0x93'u8, 0x82'u8, 0x02'u8, 0xac'u8, 0x55'u8, 0x3f'u8, 0x44'u8, 0x38'u8, 0x1d'u8]))
checkPermutation(978, 3175, 2257, Eth2Digest(data: [0x74'u8, 0xaa'u8, 0xc2'u8, 0x35'u8, 0x23'u8, 0xcb'u8, 0x45'u8, 0xb7'u8, 0xee'u8, 0x52'u8, 0xd5'u8, 0xd2'u8, 0xf7'u8, 0xb2'u8, 0xd2'u8, 0x4e'u8, 0xbc'u8, 0x6b'u8, 0xf2'u8, 0xd6'u8, 0x3e'u8, 0xf1'u8, 0x89'u8, 0xef'u8, 0xcc'u8, 0xab'u8, 0xc4'u8, 0xa1'u8, 0x6b'u8, 0xb1'u8, 0x7c'u8, 0xd8'u8]))
checkPermutation(37, 231, 48, Eth2Digest(data: [0xe4'u8, 0x08'u8, 0x3e'u8, 0x61'u8, 0xb3'u8, 0x19'u8, 0x31'u8, 0xba'u8, 0xd6'u8, 0x62'u8, 0x39'u8, 0x27'u8, 0x58'u8, 0xe8'u8, 0xbc'u8, 0x30'u8, 0xa4'u8, 0xce'u8, 0x7b'u8, 0x26'u8, 0xb6'u8, 0x89'u8, 0x7c'u8, 0x22'u8, 0x21'u8, 0xa3'u8, 0x35'u8, 0x8f'u8, 0x25'u8, 0xfd'u8, 0xc1'u8, 0xd8'u8]))
checkPermutation(340, 693, 234, Eth2Digest(data: [0x80'u8, 0x89'u8, 0xc1'u8, 0xf2'u8, 0x42'u8, 0xaa'u8, 0x48'u8, 0xc6'u8, 0x61'u8, 0x11'u8, 0x80'u8, 0xf2'u8, 0x21'u8, 0xc1'u8, 0x20'u8, 0xe9'u8, 0x30'u8, 0xad'u8, 0xee'u8, 0xca'u8, 0xf3'u8, 0x08'u8, 0x4b'u8, 0x2b'u8, 0x85'u8, 0xf9'u8, 0xb1'u8, 0xdf'u8, 0xeb'u8, 0xe3'u8, 0x4f'u8, 0x63'u8]))
checkPermutation(0, 9, 1, Eth2Digest(data: [0x7f'u8, 0xda'u8, 0x0a'u8, 0xb6'u8, 0xa7'u8, 0x46'u8, 0xb6'u8, 0xb0'u8, 0x20'u8, 0x6f'u8, 0xeb'u8, 0xb8'u8, 0x25'u8, 0x98'u8, 0x91'u8, 0xe0'u8, 0xe6'u8, 0xf8'u8, 0x8b'u8, 0xf5'u8, 0x21'u8, 0x43'u8, 0xb2'u8, 0x0d'u8, 0x6c'u8, 0x78'u8, 0xca'u8, 0xf7'u8, 0xca'u8, 0xf8'u8, 0xe7'u8, 0xb3'u8]))
checkPermutation(200, 1108, 952, Eth2Digest(data: [0x87'u8, 0xb2'u8, 0x10'u8, 0xd0'u8, 0x00'u8, 0xb5'u8, 0xf5'u8, 0x7e'u8, 0x98'u8, 0x34'u8, 0x38'u8, 0x8d'u8, 0x4b'u8, 0xc2'u8, 0xb8'u8, 0x6a'u8, 0xe8'u8, 0xb3'u8, 0x13'u8, 0x83'u8, 0xfa'u8, 0x10'u8, 0xa3'u8, 0x4b'u8, 0x02'u8, 0x95'u8, 0x46'u8, 0xc2'u8, 0xeb'u8, 0xab'u8, 0xb8'u8, 0x07'u8]))
checkPermutation(1408, 1531, 584, Eth2Digest(data: [0x06'u8, 0x70'u8, 0xa7'u8, 0x8b'u8, 0x38'u8, 0xe0'u8, 0x41'u8, 0x9a'u8, 0xae'u8, 0xad'u8, 0x5d'u8, 0x1c'u8, 0xc8'u8, 0xf4'u8, 0x0f'u8, 0x58'u8, 0x04'u8, 0x4b'u8, 0x70'u8, 0x76'u8, 0xce'u8, 0xd8'u8, 0x19'u8, 0x3c'u8, 0x08'u8, 0xb5'u8, 0x80'u8, 0xdd'u8, 0x95'u8, 0xa1'u8, 0x35'u8, 0x55'u8]))
checkPermutation(1704, 1863, 1022, Eth2Digest(data: [0xdb'u8, 0xf7'u8, 0x86'u8, 0x65'u8, 0x19'u8, 0x0a'u8, 0x61'u8, 0x33'u8, 0x19'u8, 0x1e'u8, 0x91'u8, 0xab'u8, 0x35'u8, 0xb1'u8, 0x10'u8, 0x6e'u8, 0x89'u8, 0x84'u8, 0xdf'u8, 0xc0'u8, 0xdf'u8, 0xa3'u8, 0x60'u8, 0x18'u8, 0x00'u8, 0x4f'u8, 0x88'u8, 0x0b'u8, 0x43'u8, 0x1c'u8, 0x2a'u8, 0x14'u8]))
checkPermutation(793, 3938, 2607, Eth2Digest(data: [0x54'u8, 0xbf'u8, 0x01'u8, 0x92'u8, 0x29'u8, 0x2f'u8, 0xfa'u8, 0xe0'u8, 0xbf'u8, 0x39'u8, 0xb3'u8, 0x9f'u8, 0x12'u8, 0xe0'u8, 0x54'u8, 0x0b'u8, 0x97'u8, 0x59'u8, 0x1a'u8, 0xf0'u8, 0xa2'u8, 0x98'u8, 0x0d'u8, 0x32'u8, 0xf2'u8, 0x77'u8, 0xbd'u8, 0x33'u8, 0x20'u8, 0x13'u8, 0x95'u8, 0xd3'u8]))
checkPermutation(14, 28, 10, Eth2Digest(data: [0x43'u8, 0x05'u8, 0x44'u8, 0x17'u8, 0xc6'u8, 0x05'u8, 0x64'u8, 0x04'u8, 0xc5'u8, 0x86'u8, 0xc9'u8, 0x07'u8, 0xdf'u8, 0xc5'u8, 0xfc'u8, 0xeb'u8, 0x66'u8, 0xeb'u8, 0xef'u8, 0x54'u8, 0x1d'u8, 0x14'u8, 0x3b'u8, 0x00'u8, 0xa3'u8, 0xb6'u8, 0x76'u8, 0xf3'u8, 0xc0'u8, 0xfb'u8, 0xf4'u8, 0xc5'u8]))
checkPermutation(2909, 3920, 726, Eth2Digest(data: [0x5e'u8, 0xab'u8, 0xf2'u8, 0x89'u8, 0xfd'u8, 0xcf'u8, 0xe0'u8, 0xa3'u8, 0xab'u8, 0xa3'u8, 0x3a'u8, 0x18'u8, 0x5f'u8, 0xb1'u8, 0xa4'u8, 0xae'u8, 0x2f'u8, 0x2b'u8, 0x6f'u8, 0x78'u8, 0xda'u8, 0xf6'u8, 0x1f'u8, 0x5d'u8, 0x35'u8, 0x69'u8, 0x71'u8, 0xe0'u8, 0xcb'u8, 0x27'u8, 0x02'u8, 0x07'u8]))
checkPermutation(1943, 1959, 1292, Eth2Digest(data: [0xca'u8, 0x86'u8, 0x32'u8, 0x2d'u8, 0xb5'u8, 0x69'u8, 0x27'u8, 0xd7'u8, 0x27'u8, 0x10'u8, 0x1e'u8, 0x31'u8, 0xc9'u8, 0x3f'u8, 0x61'u8, 0x6f'u8, 0x74'u8, 0x63'u8, 0x17'u8, 0xd2'u8, 0x9a'u8, 0xa1'u8, 0x0d'u8, 0x88'u8, 0xf3'u8, 0x71'u8, 0x59'u8, 0x29'u8, 0x63'u8, 0xde'u8, 0x92'u8, 0xaa'u8]))
checkPermutation(1647, 2094, 1805, Eth2Digest(data: [0x3c'u8, 0xfe'u8, 0x27'u8, 0x42'u8, 0x30'u8, 0xa1'u8, 0x12'u8, 0xbc'u8, 0x68'u8, 0x61'u8, 0x48'u8, 0x82'u8, 0x64'u8, 0x53'u8, 0x39'u8, 0xfd'u8, 0xa2'u8, 0xf1'u8, 0x34'u8, 0x50'u8, 0x1a'u8, 0x04'u8, 0x20'u8, 0x79'u8, 0xd6'u8, 0x20'u8, 0xec'u8, 0x65'u8, 0xcf'u8, 0x8d'u8, 0x3f'u8, 0xa6'u8]))
checkPermutation(1012, 1877, 216, Eth2Digest(data: [0x7b'u8, 0x5f'u8, 0xf8'u8, 0xa8'u8, 0x48'u8, 0xaf'u8, 0x32'u8, 0xd8'u8, 0x5c'u8, 0x6d'u8, 0x37'u8, 0xc2'u8, 0x6e'u8, 0x61'u8, 0xa5'u8, 0x7e'u8, 0x96'u8, 0x78'u8, 0x0f'u8, 0xce'u8, 0xbc'u8, 0x35'u8, 0x0a'u8, 0xd1'u8, 0x84'u8, 0x5e'u8, 0x83'u8, 0xfe'u8, 0x5e'u8, 0x46'u8, 0x79'u8, 0xac'u8]))
checkPermutation(35, 2081, 1458, Eth2Digest(data: [0x40'u8, 0x69'u8, 0x1a'u8, 0xa3'u8, 0x1a'u8, 0x49'u8, 0xc2'u8, 0x39'u8, 0x1e'u8, 0x02'u8, 0x5e'u8, 0xc2'u8, 0x72'u8, 0xc8'u8, 0x12'u8, 0x51'u8, 0x0c'u8, 0xb0'u8, 0x7c'u8, 0x05'u8, 0x5f'u8, 0x62'u8, 0x01'u8, 0xe8'u8, 0x44'u8, 0x79'u8, 0x49'u8, 0x93'u8, 0x26'u8, 0x33'u8, 0x06'u8, 0x28'u8]))
checkPermutation(1136, 2189, 1579, Eth2Digest(data: [0x31'u8, 0xa0'u8, 0xde'u8, 0xb2'u8, 0xc8'u8, 0xc5'u8, 0xf8'u8, 0x09'u8, 0xf4'u8, 0x13'u8, 0xb7'u8, 0xa3'u8, 0x6e'u8, 0xc6'u8, 0x80'u8, 0xee'u8, 0x8b'u8, 0x19'u8, 0xbb'u8, 0xb9'u8, 0xa3'u8, 0x9c'u8, 0x4e'u8, 0x20'u8, 0x73'u8, 0x26'u8, 0x15'u8, 0x58'u8, 0x64'u8, 0xbc'u8, 0x8b'u8, 0xe5'u8]))
checkPermutation(1775, 3434, 707, Eth2Digest(data: [0x92'u8, 0xf3'u8, 0x0d'u8, 0x85'u8, 0x56'u8, 0x38'u8, 0x2b'u8, 0x72'u8, 0xa5'u8, 0x79'u8, 0x7d'u8, 0xb8'u8, 0x11'u8, 0x48'u8, 0x6e'u8, 0x7a'u8, 0x21'u8, 0x3e'u8, 0x01'u8, 0x45'u8, 0xd6'u8, 0xc9'u8, 0x46'u8, 0xe5'u8, 0x12'u8, 0x1a'u8, 0xa6'u8, 0xa8'u8, 0xf7'u8, 0x61'u8, 0xd1'u8, 0x64'u8]))
checkPermutation(1109, 2010, 433, Eth2Digest(data: [0x09'u8, 0x3f'u8, 0xb9'u8, 0x76'u8, 0xf2'u8, 0x49'u8, 0x73'u8, 0x61'u8, 0x89'u8, 0x70'u8, 0x12'u8, 0xdf'u8, 0xa6'u8, 0xdc'u8, 0x01'u8, 0x90'u8, 0x09'u8, 0xed'u8, 0xa2'u8, 0xe4'u8, 0x8b'u8, 0xbe'u8, 0xb4'u8, 0xb7'u8, 0xc5'u8, 0x6d'u8, 0x4a'u8, 0xa5'u8, 0xda'u8, 0x7d'u8, 0x5f'u8, 0x87'u8]))
checkPermutation(359, 538, 115, Eth2Digest(data: [0xa7'u8, 0x9b'u8, 0x35'u8, 0xbe'u8, 0xac'u8, 0xbe'u8, 0x48'u8, 0xc6'u8, 0x62'u8, 0xd6'u8, 0x08'u8, 0x84'u8, 0xc7'u8, 0x04'u8, 0x04'u8, 0x00'u8, 0x24'u8, 0xc5'u8, 0x5a'u8, 0xb8'u8, 0x79'u8, 0xe5'u8, 0xf6'u8, 0x15'u8, 0x21'u8, 0x01'u8, 0x3c'u8, 0x5f'u8, 0x45'u8, 0xeb'u8, 0x3b'u8, 0x70'u8]))
checkPermutation(1259, 1473, 1351, Eth2Digest(data: [0x02'u8, 0xc5'u8, 0x3c'u8, 0x9c'u8, 0x6d'u8, 0xdf'u8, 0x25'u8, 0x97'u8, 0x16'u8, 0xff'u8, 0x02'u8, 0xe4'u8, 0x9a'u8, 0x29'u8, 0x4e'u8, 0xba'u8, 0x33'u8, 0xe4'u8, 0xad'u8, 0x25'u8, 0x5d'u8, 0x7e'u8, 0x90'u8, 0xdb'u8, 0xef'u8, 0xdb'u8, 0xc9'u8, 0x91'u8, 0xad'u8, 0xf6'u8, 0x03'u8, 0xe5'u8]))
checkPermutation(2087, 2634, 1497, Eth2Digest(data: [0xa5'u8, 0xa4'u8, 0xc5'u8, 0x7c'u8, 0x57'u8, 0x05'u8, 0xec'u8, 0x69'u8, 0x7a'u8, 0x74'u8, 0xe6'u8, 0xc7'u8, 0x16'u8, 0x11'u8, 0x91'u8, 0xb1'u8, 0x8f'u8, 0x58'u8, 0xca'u8, 0x88'u8, 0x2a'u8, 0x0f'u8, 0xcc'u8, 0x18'u8, 0xf6'u8, 0x8d'u8, 0xc3'u8, 0xb5'u8, 0x7a'u8, 0x1a'u8, 0xa5'u8, 0xb6'u8]))
checkPermutation(2069, 2511, 1837, Eth2Digest(data: [0xe7'u8, 0x05'u8, 0x1e'u8, 0xbc'u8, 0x07'u8, 0xf2'u8, 0xe7'u8, 0xb4'u8, 0xd4'u8, 0xb2'u8, 0x8f'u8, 0x48'u8, 0xd1'u8, 0xe4'u8, 0x2d'u8, 0x7b'u8, 0x9d'u8, 0xce'u8, 0xc3'u8, 0x1c'u8, 0x24'u8, 0x0c'u8, 0xa6'u8, 0xe1'u8, 0xa0'u8, 0xc0'u8, 0x61'u8, 0x39'u8, 0xcc'u8, 0xfc'u8, 0x4b'u8, 0x8f'u8]))
checkPermutation(1660, 3932, 3046, Eth2Digest(data: [0x86'u8, 0x87'u8, 0xc0'u8, 0x29'u8, 0xff'u8, 0xc4'u8, 0x43'u8, 0x87'u8, 0x95'u8, 0x27'u8, 0xa6'u8, 0x4c'u8, 0x31'u8, 0xb7'u8, 0xac'u8, 0xbb'u8, 0x38'u8, 0xab'u8, 0x6e'u8, 0x34'u8, 0x37'u8, 0x79'u8, 0xd0'u8, 0xb2'u8, 0xc6'u8, 0xe2'u8, 0x50'u8, 0x04'u8, 0x6f'u8, 0xdb'u8, 0x9d'u8, 0xe8'u8]))
checkPermutation(379, 646, 32, Eth2Digest(data: [0x17'u8, 0xe8'u8, 0x54'u8, 0xf4'u8, 0xe8'u8, 0x04'u8, 0x01'u8, 0x34'u8, 0x5e'u8, 0x13'u8, 0xf7'u8, 0x2a'u8, 0xf4'u8, 0x5b'u8, 0x22'u8, 0x1c'u8, 0x9f'u8, 0x7a'u8, 0x84'u8, 0x0f'u8, 0x6a'u8, 0x8c'u8, 0x13'u8, 0x28'u8, 0xdd'u8, 0xf9'u8, 0xc9'u8, 0xca'u8, 0x9a'u8, 0x08'u8, 0x83'u8, 0x79'u8]))
s.len == committees
# 32k validators: SLOTS_PER_EPOCH slots * committee_count_per_slot =
# get_epoch_committee_count committees.