Part 3: Annotated Specification

Types, Constants, Presets, and Configuration


The distinction between "constants", "presets", and "configuration values" is not always clear, and things have moved back and forth between the sections at times1. In essence, "constants" are things that are expected never to change for the beacon chain, no matter what fork or test network it is running.


Name Value
FAR_FUTURE_EPOCH Epoch(2**64 - 1)

The very first slot number for the beacon chain is zero.

Perhaps this seems uncontroversial, but it actually featured heavily in the Great Signedness Wars mentioned previously. The issue was that calculations on unsigned integers might have negative intermediate values, which would cause problems. A proposed work-around for this was to start the chain at a non-zero slot number. It was initially set to 2^19, then 2^63, then 2^32, and finally back to zero. In my humble opinion, this madness only confirms that we should have been using signed integers all along.


As above. When the chain starts, it starts at epoch zero.


A candidate for the dullest constant. It's used as a default initialiser for validators' activation and exit times before they are properly set. No epoch number will ever be bigger than this one.


DEPOSIT_CONTRACT_TREE_DEPTH specifies the size of the (sparse) Merkle tree used by the Eth1 deposit contract to store deposits made. With a value of 32, this allows for 2322^{32} = 4.3 billion deposits. Given that the minimum deposit it 1 Ether, that number is clearly enough.

Since deposit receipts contain Merkle proofs, their size depends on the value of this constant.


As an optimisation to Casper FFG – the process by which finality is conferred on epochs – the beacon chain uses a "kk-finality" rule. We will describe this more fully when we look at processing justification and finalisation. For now, this constant is just the number of bits we need to store in state to implement kk-finality. With k=2k = 2, we track the justification status of the last four epochs.


This array is just a convenient way to access the various weights given to different validator achievements when calculating rewards. The three weights are defined under incentivization weights, and each weight corresponds to a flag stored in state and defined under participation flag indices.


Endianness refers to the order of bytes in the binary representation of a number: most significant byte first is big-endian; least significant byte first is little-endian. For the most part these details are hidden by compilers, and we don't need to worry about endianness. But endianness matters when converting between integers and bytes, which is relevant to shuffling and proposer selection, the RANDAO, and when serialising with SSZ.

The spec began life as big-endian, but the Nimbus team from Status successfully lobbied for it to be changed to little-endian to better match processor hardware implementations, and the endianness of WASM. SSZ was changed first, and then the rest of the spec followed.

Participation flag indices

Name Value

Validators making attestations that get included on-chain are rewarded for three things:

  • getting attestations included with the correct source checkpoint within 5 slots (integer_squareroot(SLOTS_PER_EPOCH));
  • getting attestations included with the correct target checkpoint within 32 slots (SLOTS_PER_EPOCH); and,
  • getting attestations included with the correct head within 1 slot (MIN_ATTESTATION_INCLUSION_DELAY), basically immediately.

These flags are temporarily recorded in the BeaconState when attestations are processed, then used at the ends of epochs to update finality and to calculate validator rewards for making attestations.

The mechanism for rewarding timely inclusion of attestations (thus penalising late attestations) differs between Altair and Phase 0. In Phase 0, attestations included within 32 slots would receive the full reward for the votes they got correct (source, target, head), plus a declining reward based on the delay in inclusion: 12\frac{1}{2} for a two slot delay, 13\frac{1}{3} for a three slot delay, and so on. With Altair, for each vote, we now have a cliff before which the validator receives the full reward and after which a penalty. The cliffs differ in duration, which is intended to more accurately target incentives at behaviours that genuinely help the chain (there is little value in rewarding a correct head vote made 30 slots late, for example). See get_attestation_participation_flag_indices() for how this is implemented in code.

Incentivization weights

Name Value

These weights are used to calculate the reward earned by a validator for performing its duties. There are five duties in total. Three relate to making attestations: attesting to the source epoch, attesting to the target epoch, and attesting to the head block. There are also rewards for proposing blocks, and for participating in sync committees. Note that the sum of five the weights is equal to WEIGHT_DENOMINATOR.

On a long-term average, a validator can expect to earn a total amount of get_base_reward() per epoch, with these weights being the relative portions for each of the duties comprising that total. Proposing blocks and participating in sync committees do not happen in every epoch, but are randomly assigned, so over small periods of time validator earnings may differ from get_base_reward().

The apportioning of rewards was overhauled in the Altair upgrade to better reflect the importance of each activity within the protocol. The total reward amount remains the same, but sync committee rewards were added, and the relative weights were adjusted. Previously, the weights corresponded to 16 for correct source, 16 for correct target, 16 for correct head, 14 for inclusion (equivalent to correct source), and 2 for block proposals. The factor of four increase in the proposer reward addressed a long-standing spec bug.

A piechart of the proportion of a validator's total reward derived from each of the micro-rewards

The proportion of the total reward derived from each of the micro-rewards.

Withdrawal Prefixes

Name Value

Withdrawal prefixes relate to the withdrawal credentials provided when deposits are made for validators. The withdrawal credential is a commitment to a private key that may be used later to withdraw funds from the validator's balance on the beacon chain.

Two ways to specify the withdrawal credentials are currently available, versioned with these prefixes, with others such as 0x02 and 0x03 under discussion.

These withdrawal credential prefixes are not yet significant in the core beacon chain spec, but will become significant when withdrawals are enabled in a future upgrade. The withdrawal credentials data is not consensus-critical, and future withdrawal credential types can be added without a hard fork. There are suggestions as to how existing credentials might be changed between methods which would be consensus critical.

The presence of these prefixes in the spec indicates a "social consensus" among the dev teams and protocol designers that we will in future support these methods for making withdrawals.

See the Withdrawals section for discussion on what the mechanism might look like.


The beacon chain launched with only BLS-style withdrawal credentials available, so all early stakers used this. The 0x00 prefix on the credential distinguishes this type from the others: it replaces the first byte of the hash of the BLS public key that corresponds to the BLS private key of the staker.

With this type of credential, in addition to a BLS signing key, stakers need a second BLS key that they will later use for withdrawals. The credential registered in the deposit data is the 32 byte SHA256 hash of the validators withdrawal public key, with the first byte set to BLS_WITHDRAWAL_PREFIX.


Eth1 withdrawal credentials are much simpler, and were adopted once it became clear that Ethereum 2.0 would not be using a BLS-based address scheme for accounts at any time soon. These provide a commitment that stakers will be able to withdraw their beacon chain funds to a normal Ethereum account (possibly a contract account) at a future date.

Domain types

Name Value
DOMAIN_BEACON_PROPOSER DomainType('0x00000000')
DOMAIN_BEACON_ATTESTER DomainType('0x01000000')
DOMAIN_RANDAO DomainType('0x02000000')
DOMAIN_DEPOSIT DomainType('0x03000000')
DOMAIN_VOLUNTARY_EXIT DomainType('0x04000000')
DOMAIN_SELECTION_PROOF DomainType('0x05000000')
DOMAIN_AGGREGATE_AND_PROOF DomainType('0x06000000')
DOMAIN_SYNC_COMMITTEE DomainType('0x07000000')

These domain types are used in three ways: for seeds, for signatures, and for selecting aggregators.

As seeds

When random numbers are required in-protocol, one way they are generated is by hashing the RANDAO mix with other quantities, one of them being a domain type (see get_seed()). The original motivation was to avoid occasional collisions between Phase 0 committees and Phase 1 persistent committees, back when they were a thing. So, when computing the beacon block proposer, DOMAIN_BEACON_PROPOSER is hashed into the seed, when computing attestation committees, DOMAIN_BEACON_ATTESTER is hashed in, and when computing sync committees, DOMAIN_SYNC_COMMITTEE is hashed in.

As signatures

In addition, as a cryptographic nicety, each of the protocol's signature types is augmented with the appropriate domain before being signed:

  • Signed block proposals incorporate DOMAIN_BEACON_PROPOSER
  • Signed attestations incorporate DOMAIN_BEACON_ATTESTER
  • RANDAO reveals are BLS signatures, and use DOMAIN_RANDAO
  • Deposit data messages incorporate DOMAIN_DEPOSIT
  • Validator voluntary exit messages incorporate DOMAIN_VOLUNTARY_EXIT
  • Sync committee signatures incorporate DOMAIN_SYNC_COMMITTEE

In each case, except for deposits, the fork version is also incorporated before signing. Deposits are valid across forks, but other messages are not. Note that this would allow validators to participate, if they wish, in two independent forks of the beacon chain without fear of being slashed.

Aggregator selection

The remaining four types, suffixed _PROOF are not used directly in the beacon chain specification. They were introduced to implement attestation subnet validations for denial of service resistance. The technique was extended to sync committees with the Altair upgrade.

Briefly, at each slot, validators are selected to aggregate attestations from their committees. The selection is done based on the validator's signature over the slot number, mixing in DOMAIN_SELECTION_PROOF. The validator then signs the whole aggregated attestation, including the previous signature as proof that it was selected to be a validator, using DOMAIN_AGGREGATE_AND_PROOF. And similarly for sync committees. In this way, everything is verifiable and attributable, making it hard to flood the network with fake messages.

These four are not part of the consensus-critical state-transition, but are nonetheless important to the healthy functioning of the chain.

This mechanism is described in the Phase 0 honest validator spec for attestation aggregation, and in the Altair honest validator spec for sync committee aggregation.


Name Value
G2_POINT_AT_INFINITY BLSSignature(b'\xc0' + b'\x00' * 95)

This is the compressed serialisation of the "point at infinity", the identity point, of the G2 group of the BLS12-381 curve that we are using for signatures. Note that it is in big-endian format (unlike all other constants in the spec).

It was introduced as a convenience when verifying aggregate signatures that contain no public keys in eth_fast_aggregate_verify(). The underlying FastAggregateVerify function from the BLS signature standard would reject these.

G2_POINT_AT_INFINITY is described in the separate BLS Extensions document, but included here for convenience.

  1. See Issue 2390 for discussion and a rationale for the current categorisation into constants, presets, and configuration variables.

Created by Ben Edgington. Licensed under CC BY-SA 4.0. Published 2023-07-01 13:17 UTC. Commit 8fa708b.