This document contains the specification of the SSV
module.
The Beacon node interface
is a composition of all interfaces below along with a method to get a Beacon Network object.
It comprises the interface, as an API style, between the operator implementation and the Beacon Network.
Its purpose is to provide a set of functions that the operator will use to interact with the Beacon Nodes, both for requesting data objects, and states, and for submitting signed objects to the network.
---
title: Beacon Node Interface
---
flowchart LR
Validator([Validator])
BeaconNode{Beacon Node Interface}
BeaconNetwork[(Beacon Network)]
Validator-->|Request Data|BeaconNode
BeaconNode-->|Returns Data|Validator
Validator-->|Submits Data|BeaconNode
BeaconNode---BeaconNetwork
Each interface below will provide methods for getting and submitting data related to their duty type.
DomainCalls
is an interface with a single method DomainData
used to return a signature domain.
For each different duty, Ethereum adds a specific salt, the DomainType
, to the hash of the object to be signed. Therefore, for different duty types, equal objects would have different hashes and, therefore, signatures. This is a common technique to prevent the Rainbow attack to hashing functions.
Similarly, SSV also defines a DomainType
, defined by the network and its fork version, to be included in the signing process.
ProposerCalls
is an interface used for the Proposer duty. It defines functions for getting Beacon blocks, blinded Beacon blocks (blocks with only a transaction root instead of a full transactions list) and for submitting these blocks to the node.
AttesterCalls
is an interface for the Attestation duty. It defines functions for getting the attestation data and for submitting it to the node.
AggregatorCalls
is an interface for the Aggregator duty. It has functions to get an AggregateAndProof object, that contains:
- Aggregator index (validator index)
- aggregate attestation
- BLSSignature selection proof,
and to submit a signed aggregator message.
SyncCommitteeCalls
is an interface for the Sync Committee's duty. It has a method to get beacon block roots and another method to submit a signed sync committee message.
SyncCommitteeCalls
is an interface for the Sync committee aggregator duty. It has:
- a predicate to check if it's an aggregator,
- a function to get the subnet ID for a certain subcommittee index,
- a function to get the Contributions object and another one to submit the signed object.
The runner is an entity responsible for performing validator duties for an operator. For that,
- it will collaborate with other operators to create signatures and
- it will count on the Beacon node to get and send data to the blockchain.
For each duty, there is a specific runner since each duty requires a different sequence of steps, or subprotocol, to be followed by the operators. In general, it comprises three phases:
- Pre-consensus: a threshold signature is created on some structure for later getting specific data from the Beacon node.
- Consensus: decides on the data to send back to the Beacon node.
- Post-consensus: generates a threshold signature on the decided data and sends s signed object to the Beacon node.
The Runner interface
establishes every method that a runner of any type should have. It includes functions to process pre-consensus, consensus and post-consensus methods and to execute duties.
The BaseRunner
structure represents a common ground for all different types of runners. It's comprised by:
- a state,
- share that contains information about the validators the node runs, and their committees.
- committee member that contains infomration about the operator itself seperated from the validators it runs.
- a QBFT controller to manage consensus instances,
- the Beacon network, its Beacon role and the highest decided slot number.
RunnerState
stores the state of the runner during its execution. The state is composed of:
- a duty type,
- the duty-decided output,
- the consensus instance that decided the output and
- partial signature containers for the pre-consensus and post-consensus steps.
The PartialSigContainer
structure stores partial signatures and performs validator signature reconstruction after a quorum is reached.
The reconstruction uses Lagrange Interpolation to reconstruct a message signed with the shared secret.
This is crucial for DVT technology since it provides a way to construct a validator signature without any party ever having possession of the private key. This is accomplished by Adi Shamir Secret Sharing and the mathematical associative property of signing in BLS signatures.
---
title: Signature reconstruction
---
flowchart LR
op1([Operator 1])
op2([Operator 2])
op3([Operator 3])
op4([Operator 4])
container[(PartialSigContainer)]
signature{{Validator Signature}}
op1-->|partial signature|container
op2-->|partial signature|container
op3-->|partial signature|container
op4-->|partial signature|container
container-->|Lagrange Interpolation|signature
To propose a block
, the validator must:
- Fetch the head of the chain using the fork choice rule.
- Construct a Beacon block.
- Sign the beacon block and broadcast it.
Block creation is abstracted by the Beacon Node interface and the operator can just fetch the block from it.
However, there is a block field, randao_reveal
, which is a signature of the validator over the epoch number. Thus, for the Beacon node to return the block, it needs to receive the signature first.
Thus the overall steps are:
- Produce the randao_reveal by broadcasting and collecting partial signatures over the epoch number.
- Send rando_reveal and get the Beacon block from the Beacon node interface.
- Run consensus on the duty and block data.
- Produce the signed Beacon block, by broadcasting and collecting partial signatures, and send it to the Beacon node.
---
title: Block proposer steps
---
%%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
flowchart LR
randao_cell(Construct Randao Signature)
beaconnode(Get Beacon Block)
Consensus(Consensus)
signedBlock(Construct Block Signature)
send(Send Signed Block to Beacon Node)
randao_cell-->beaconnode
beaconnode-->Consensus
Consensus-->signedBlock
signedBlock-->send
This runner creates several beacon objects from one consensus.
For the attestation duty
and sync committee duty
, the validator should construct an AttestationData, which is composed by:
- a slot,
- committee index,
- a beacon block root (for the LMD GHOST vote)
- a source and target checkpoint (for the FFG vote)
From the AttestationData it can create an Attestation and SyncCommitteeMessage.
The operator can rely on the Beacon node to get the AttestationData object.
The overall steps are:
- Get an AttestationData from the Beacon node.
- Reach a consensus on the duty and the attestation data.
- Construct the validator signature for the Attestation and Sync Committee objects and send it back to the Beacon node.
---
title: steps
---
%%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
flowchart LR
attestationData(Get AttestationData)
Consensus(Consensus)
attestation(Construct Attestations Signatures)
SyncCommittee(Construct Sync Committees Signatures)
send(Send Attestations to Beacon Node)
sendSync(Send Sync Committees to Beacon Node)
attestationData-->Consensus
Consensus-->attestation
Consensus-->SyncCommittee
attestation-->send
SyncCommittee-->sendSync
For the aggregator duty
, the validator must collect AttestationData from other attesters that are similar to its own created. Then, it should create an AggregateAndProof object, sign it and broadcast a SignedAggregateAndProof to the network.
The AggregateAndProof object can be obtained by the Beacon node. However, one of its fields is the validator's signature over the slot value (selection_proof
). Thus, this field must be created and delivered to the Beacon node in order to get the AggregateAndProof object.
Thus, the overall steps are:
- Construct the validator signature over the slot value (selection_proof).
- Send it to the Beacon node and request the AggregateAndProof object.
- Do consensus over the duty and the data.
- Construct the validator signature on the consensus output.
- Construct the SignedAggregateAndProof object and send it to the Beacon node.
---
title: Aggregator steps
---
%%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
flowchart LR
selectionProof(Construct selection_proof)
aggregateAndProof(Get AggregateAndProof)
consensus(Consensus)
signedAggregateAndProof(Construct SignedAggregateAndProof)
send(Send to Beacon node)
selectionProof-->aggregateAndProof
aggregateAndProof-->consensus
consensus-->signedAggregateAndProof
signedAggregateAndProof-->send
Similarly to an attestation aggregator, a validator with a Sync Committee Aggregator
duty should collect SyncCommitteeMessages, aggregate them into a SyncCommitteeContribution and send it to the network.
To know if a validator is an aggregator for the current sync committee slot, it must compute its signature over a SyncAggregatorSelectionData (selection proof
) and use it as input of a function is_sync_committee_aggregator.
Using the SyncCommitteContribution data, it constructs a ContributionAndProof message, signs it to create a SignedContributionAndProof and sends it to the network.
The Beacon node provides the is_sync_committee_aggregator function and the ContributionAndProof object.
Thus, the operator's steps for this duty are:
- Compute the validator signature over SyncAggregatorSelectionData (selection proof).
- Ask the Beacon node if it's an aggregator. If not, stop.
- If so, get the ContributionAndProof object with the Beacon node.
- Compute the validator signature to create a SignedContributionAndProof and send it to the Beacon node.
---
title: Sync committee aggregator steps
---
%%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
flowchart LR
selectionProof(Selection Proof)
checkAggregator(Check if it's aggregator)
contribution(Get Contribution)
contributionSignature(Construct validator signature)
send(Send to Beacon node)
selectionProof-->checkAggregator
checkAggregator-->contribution
contribution-->contributionSignature
contributionSignature-->send
NOTE: a validator may be part of multiple sync committees and, therefore, it should check if it's an aggregator for every committee that it's part of.
NOTE: This is not an actual validator duty. However, here, it's considered a duty for MEV-Boost purpose, to set the validator's fee_recipient and gas_limit preferences.
To register a validator
, it needs to create the operator needs to:
- Get the ValidatorRegistration object.
- Compute the validator signature over the object.
- Submit the validator registration to the Beacon node.
---
title: Validator registration steps
---
%%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
flowchart LR
registrationObject(Get ValidatorRegistration)
signature(Compute signature)
send(Submit to Beacon node)
registrationObject-->signature
signature-->send
The Validator
entity represents a share of a validator that participates in the Ethereum Beacon chain consensus. Note that it's not the validator itself, but actually a virtual representation of a validator that the real operator entity will use.
The operator will use this structure to execute validator-related duties, managed by DutyRunners.
DutyRunners
is a map: Beacon roles (as proposer, attester, etc) → Runner. Each duty type has its unique runner.