# EigenLayer Multichain Protocol Overview The purpose of this document is to give an end-to-end overview of the multichain protocol. The EigenLayer multichain protocol supports the confirmation of AVS tasks across any chain, including Ethereum Mainnet. # Contents [TOC] # Glossary * `Source Chain` - The chain on which EigenLayer stake lives and AVSs contracts are deployed * `Destination Chain` - The chain to which stakes are transported by the multichain protocol. Mainnet is also a destination chain * `AVS` - the entity that deploys and manages `operatorSets` * `OperatorSet` - A grouping of operators in the EigenLayer protocol for a specific AVS * `Certificate` - A proof of a task execution off-chain. It requires a statement being certified (`msgHash`), a signature data of operators that signed the certificate, and a `referenceTimestamp` of the state of operators that confirmed the certificate * `Key Material` - the types of keys that can sign certificates. The multichain protocol supports `ECDSA` and `BN254` * `BN254Certificate` - A certificate for `BN254` keys * `ECDSACertificate` - A certificate for `ECDSA` keys * `Stake Weight` - an AVS-defined representation of operator stakes, represented by a `uint256` array. Examples include: * a single value array can be used for evaluating on purely slashable stake `[slashable_stake]` * an array of 2 values can be used for evaluating on slashable and delegated stake `[slashable_stake, delegated_stake]` * an array of several values can be used for evaluating stake of multiple strategies `[usdc, stETH, EIGEN]` * `Stake Type` - a single value of stake in the `Stake Weight` array * `Operator Table` - a subjective representation of operators’ stakes (ie. `stake weights`) in an operatorSet * `Operator Table Calculator` the AVS-deployed smart contract that calculates an `operator table` for an `operatorSet` * `OperatorSetConfig` - the `maxStalenessPeriod` and `owner` of an operatorSet. This parameter is transported to all `destinationChains`, along with the `operator table` * `maxStalenessPeriod` - the maximum amount of time for which a certificate is valid for a given `operatorSet`. It is validated against the `referenceTimestamp` of a `certificate` * `Generation` - The process of generating a `global table root` * `Global Table Root` (`StakeTableRoot`) - A merkle root that is posted to every destination chain on the `OperatorTableUpdater` contract. Each leaf of this tree contains tables of an operatorSet * `Generator` - The entity that generates a `GlobalTableRoot` * `Transport` - The entity that transports a `GlobalTableRoot` * `Table Update Cadence` - The cadence at which `operator tables` are targetted to be updated on all `destination chains` * `referenceTimestamp` - The timestamp at which an `operatorTable` was updated. This value is the same for all `operatorSets` for a given `global table root` # System Overview Below is an overview of the entire multichain system. ```mermaid classDiagram direction TD namespace AVS-Contracts-on-Ethereum{ class OperatorTableCalculator { StakeCapping StakeWeighting (Multiplier, Oracle) ProtocolVotingPowerCalc } class AVSAdmin { metadataURI Permissions/multisigs/governance verificationDelay transportPayments } class AVSRegistrar { registerOperator deregisterOperator } class SlasherEjector { submitEvidence slashOperator () ejectOperator () } class RegistrationHooks{ RegistrationLogic OperatorCaps Churn Sockets } } namespace Ethereum-EigenLayer-Core{ class AllocationManager { registerForOperatorSets deregisterFromOperatorSets allocateStake deallocateStake slashOperator() } class KeyRegistrar{ registerKey deregisterKey getKey (operator addr) isRegistered (operator addr) } class CrossChainRegistry{ setOperatorTableCalculator getOperatorTableCalculator makeGenerationReservation addTransportDestination calculateOperatorTableBytes() } } namespace Destination Chain{ class OperatorTableUpdater{ confirmGlobalTableRoot updateOperatorTable() } class CertificateVerifier{ n Operator Tables updateOperatorTable() verifyCert (bool) } class AVSConsumer{ requests Operator task receives cert () } } namespace Offchain{ class Operator { consumer input return certificate() } class Transport{ getOperatorTables n calculateOperatorTableBytes calculateGlobalTableRoot() } } AllocationManager --> AVSRegistrar AVSAdmin --> CrossChainRegistry CrossChainRegistry --> OperatorTableCalculator : Calculates Operator Tables AVSRegistrar --> RegistrationHooks RegistrationHooks --> KeyRegistrar SlasherEjector --> AllocationManager : Slash or eject Operator CrossChainRegistry --> Transport : Read Operator tables Transport --> OperatorTableUpdater: Update global table root Transport --> OperatorTableUpdater: Update operator table OperatorTableUpdater --> CertificateVerifier: Update Operator Table Operator --> AVSConsumer : Produces certificate Operator <-- AVSConsumer : Requests task AVS Consumer --> CertificateVerifier : Verifies Certificate ``` The rest of this document breaks down each component into the following pieces: 1. Source Chain Contracts and Configuration 2. Offchain Calculation 3. Destination Chain Tranport and Verification # Source Chain Contracts The source chain is made of two components: 1. AVS contracts 2. EigenLayer Core contracts An AVS will have its stake transported to *destination* chains after it completes the following steps: ```mermaid sequenceDiagram participant AVS participant AVSRegistrar participant OperatorTableCalculator participant AllocationManager participant KeyRegistrar participant CrossChainRegistry AVS->>AVSRegistrar: Deploy AVSRegistrar AVS->>OperatorTableCalculator: Deploy OperatorTableCalculator AVS->>AllocationManager: updateMetadataURI() AVS->>AllocationManager: setAVSRegistrar(AVSRegistrar) AVS->>KeyRegistrar: configureOperatorSet(operatorSet, keyMaterial) AVS->>CrossChainRegistry: createGenerationReservation() ``` The following *write* functions are called in the core protocol * `AllocationManager.updateMetadataURI` to set their MetadataURI * `AllocationManager.setAVSRegistrar` to set the AVSRegistrar contract, to which registrations and deregistrations are propagated * `KeyRegistrar.configureOperatorSet` to set the KeyMaterial for a given operatorSet * `CrossChainRegistry.createGenerationReservation` to register the `operatorTableCalculator` and `operatorSetConfig` ## System Diagram ```mermaid classDiagram direction TD namespace AVS-Contracts-on-Ethereum{ class OperatorTableCalculator { StakeCapping StakeWeighting (Multiplier, Oracle) ProtocolVotingPowerCalc } class AVSAdmin { metadataURI Permissions/multisigs/governance verificationDelay transportPayments } class AVSRegistrar { registerOperator deregisterOperator } class SlasherEjector { submitEvidence slashOperator () ejectOperator () } class RegistrationHooks{ RegistrationLogic OperatorCaps Churn Sockets } } namespace Ethereum-EigenLayer-Core{ class AllocationManager { registerForOperatorSets deregisterFromOperatorSets allocateStake deallocateStake slashOperator() } class KeyRegistrar{ registerKey deregisterKey getKey (operator addr) isRegistered (operator addr) } class CrossChainRegistry{ setOperatorTableCalculator getOperatorTableCalculator makeGenerationReservation addTransportDestination calculateOperatorTableBytes() } } AVSAdmin --> AllocationManager: configure metadata/registrar AllocationManager --> AVSRegistrar: register/deregister operator AVSAdmin --> CrossChainRegistry: register for multichain CrossChainRegistry --> OperatorTableCalculator : Calculates Operator Tables AVSRegistrar --> RegistrationHooks RegistrationHooks --> KeyRegistrar: read key material SlasherEjector --> AllocationManager : Slash or eject Operator ``` ## AVS Contracts The source chain is the *hub* for an AVS and its associated operatorSets. AVSs receive registration/deregistrations from the core `AllocationManager`. ```mermaid classDiagram direction Td namespace AVS-Contracts-on-Ethereum{ class OperatorTableCalculator { StakeCapping StakeWeighting (Multiplier, Oracle) ProtocolVotingPowerCalc } class AVSAdmin { metadataURI Permissions/multisigs/governance verificationDelay transportPayments } class AVSRegistrar { registerOperator deregisterOperator } class SlasherEjector { submitEvidence slashOperator () ejectOperator () } class RegistrationHooks{ RegistrationLogic OperatorCaps Churn Sockets } } AVSRegistrar --> RegistrationHooks ``` *Note: In the above diagram, the only contracts required by AVSs to use the multichain protocol are the `AVSRegistrar` and `OperatorTableCalculator`.* The `AVSAdmin` is the entity that conducts on-chain operations on behalf of the AVS. It can be a multisig, eoa, or governance contract. The `SlasherEjector` and `RegistrationHooks` are shown for completeness for a potential AVS architecture. ### AVS Registrar The AVSRegistrar is the interface between AVSs and the EigenLayer core protocol for managing operator registration and deregistration. It implements a hook to enforce that operators have valid keys registered in the `KeyRegistrar` for a given `operatorSet` before allowing them to register. The `AVSRegistrar` manages multiple operatorSets for a single AVS. For more information on the `AVSRegistrar`, see our [middleware-documentation](https://github.com/Layr-Labs/eigenlayer-middleware/blob/dev/docs/middlewareV2/AVSRegistrar.md). ### Operator Table Calculator The `OperatorTableCalculator` enables the AVSs to define custom `stake weights` for their `operatorSet` members. The [middleware repository](https://github.com/Layr-Labs/eigenlayer-middleware/blob/dev/docs/middlewareV2/OperatorTableCalculator.md) provides two base contracts: `ECDSATableCalculatorBase` and `BN254TableCalculatorBase`. These contracts do the following: 1. Expose a `calculateOperatorTableBytes` for the EigenLabs multi chain protocol to merkelize the `operatorTable` into the `globalTableRoot` 2. Integrate with the `KeyRegistrar` to fetch operator key material, based on the key type of the operatorSet An AVS developer is expected to inherit these abstract contracts and define a custom `stake weight` calculation function via `_getOperatorWeights`. In addition to the stake types described in the glossary, an AVS could build custom calculation methodologies that include: - Capping the stake of an operator - Using oracles to price stake #### ECDSA Table Information For an ECDSA `operatorSet`, the `operatorTableCalculator` will return an array of the following structure in abi-encoded bytes form: ```solidity= /** * @notice A struct that contains information about a single operator * @param pubkey The address of the signing ECDSA key of the operator and not the operator address itself. * This is read from the KeyRegistrar contract. * @param weights The weights of the operator for a single operatorSet * @dev The `weights` array can be defined as a list of arbitrary groupings. For example, * it can be [slashable_stake, delegated_stake, strategy_i_stake, ...] */ struct ECDSAOperatorInfo { address pubkey; uint256[] weights; } ``` #### BN254 Table Information For a BN254 `operatorSet`, the `operatorTableCalculator` will return an array of the following structure in abi-encoded bytes form: ```solidity= /** * @notice A struct that contains information about all operators for a given BN254operatorSet * @param operatorInfoTreeRoot The root of the operatorInfo tree. * @param numOperators The number of operators in the operatorSet. * @param aggregatePubkey The aggregate G1 public key of the operators in the operatorSet. * @param totalWeights The total weights of the operators in the operatorSet. * * @dev The operatorInfoTreeRoot is the root of a merkle tree that contains the operatorInfos for each operator in the operatorSet. * It is calculated on-chain by the `BN254TableCalculator` and used by the `IBN254CertificateVerifier` to verify stakes against the non-signing operators * * @dev Retrieval of the `aggregatePubKey` depends on maintaining a key registry contract, see `KeyRegistrar` for an example implementation. * * @dev The `totalWeights` array should be the same length as each individual `weights` array in `BN254OperatorInfo`. */ struct BN254OperatorSetInfo { bytes32 operatorInfoTreeRoot; uint256 numOperators; BN254.G1Point aggregatePubkey; uint256[] totalWeights; } ``` :::info BN254 Table Calculators do not return information for each operator to take advantage of aggregation functionality of BLS keys ::: Unlike ECDSA operatorSets, BN254 operatorSets do not have to return the key information for each operator. This property results in **BN254 operatorSets being more gas-efficient than ECDSA operatorSets for large operator counts**. Although signatures are aggregated, stake information still needs to be transported to enable `stake weight`-based certificate verifications. The `operatorInfoTreeRoot` is a merkle root with leaves that store the stake and key material for a single operator: ```solidity= /** * @notice A struct that contains information about a single operator for a given BN254 operatorSet * @param pubkey The G1 public key of the operator. * @param weights The weights of the operator for a single operatorSet. * @dev The `weights` array can be defined as a list of arbitrary groupings. For example, * it can be [slashable_stake, delegated_stake, strategy_i_stake, ...] */ struct BN254OperatorInfo { BN254.G1Point pubkey; uint256[] weights; } ``` The merkle tree is structured as follows: ```mermaid flowchart TB R((OperatorInfoTreeRoot)) R --> N0((Internal Node 0-1)) R --> N1((Internal Node 2-3)) N0 --> L0[[Leaf 0<br/>BN254OperatorInfo #0]] N0 --> L1[[Leaf 1<br/>BN254OperatorInfo #1]] N1 --> L2[[Leaf 2<br/>BN254OperatorInfo #2]] N1 --> L3[[Leaf 3<br/>BN254OperatorInfo #3]] ``` For more information on how we utilize this data structure, see [BN254 Certificate Verification](#BN254) for more information. ## EigenLayer Core Contracts The EigenLayer core contracts are utilized by AVSs to: 1. Receive registration/deregistration requests 2. Store operator `Key Material` 3. Register to the multichain protocol ```mermaid classDiagram direction Td namespace Ethereum-EigenLayer-Core{ class AllocationManager { registerForOperatorSets deregisterFromOperatorSets allocateStake deallocateStake slashOperator() } class KeyRegistrar{ registerKey deregisterKey getKey (operator addr) isRegistered (operator addr) } class CrossChainRegistry{ setOperatorTableCalculator getOperatorTableCalculator makeGenerationReservation addTransportDestination calculateOperatorTableBytes() } } ``` ### KeyRegistrar The KeyRegistrar manages cryptographic keys for operators across different operator sets. It supports both ECDSA and BN254 key types and ensures global uniqueness of keys across all operator sets. The purpose of this contract is to provide trusted, protocol-controlled code, for operators to register key material to `operatorSets`. In addition, it enables AVSs to not need to deploy their own key registrars. In the context of the multichain protocol, the `KeyRegistrar`: 1. Is called by the `OperatorTableCalculator` to fetch keys 2. Is called by the `CrossChainRegistry` to fetch the `KeyType` of an `operatorSet` For more information, see our [`KeyRegistrar` documentation](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/docs/permissions/KeyRegistrar.md). ### AllocationManager The `AllocationManager` is the entrypoint for all operator<>AVS interactions. For the multichain protocol, the `AllocationManager`: 1. *May* be called by the `OperatorTableCalculator` to check `operatorSet` membership 2. *May* be called by the `OperatorTableCalculator` to `stake weights` and strategy composition of an `OperatorSet` 3. Calls into the `AVSRegistrar` to propagate operator registrations and deregistraitons 4. Is called by the `CrossChainRegistry` to enforce existence of an `operatorSet` 5. Is indexed by the offchain calculation for [`event-driven`](#Event-Driven-Updates-Delta-Updates) table updates on Operator Registration, Deregistration, and Slashing. See ### CrossChainRegistry The `CrossChainRegistry` manages the registration/deregistration of operatorSets to the multichain protocol. The contract also exposes read-only functions to calculate an operator table, which is used offchain by the `Generator` to generate a `GlobalTableRoot` and `Transporter` to transport the operator table. For the multichain protocol the `CrossChainRegistry`: 1. Is called by an AVS to register an `operatorSet` for transport via `CrossChainRegistry.createGenerationReservation` 2. Is called by the `Generator` to get the operator tables (in bytes) for each `operatorSet` 3. Is called by the `Transporter` to get the `destination chains` to transport operator tables to 4. Is called by the `Eigen Labs` to whitelist chain IDs for transport :::info Creating a generation reservation in the `CrossChainRegistry` transports an `operatorSet` to all `destination chains` by default. No payment is required for the MVP ::: # Offchain Calculation **The goal of the offchain calculation is to calculate the global table root**. The `globalTableRoot` is a merkle root whose leaves are the bytes of each `operatorSet's` `operatorTable`. The `Generator` is an `EigenLabs-run` entity that calculates the `GlobalTableRoot`. **It is represented as an `operatorSet` within the `OperatorTableCalculator`, but does not have any stake. It can be though of as a "ghost" `operatorSet`.** Eventually, the `Generator` will be transitioned to an operatorSet with sufficient decentralization and stake backing. :::info The entire system operates on `safe` Ethereum blocks ::: ## Generation Flow Chart ```mermaid sequenceDiagram participant GEN as Generator participant ELT as Transporter participant CCR as CrossChainRegistry %% ELT 1: Eigen Labs Operator gets calculator GEN-->>CCR: calculateOperatorTableBytes %% Step 2: Get the certificate GEN-->>ELT: Signed globalOperatorTableRoot response ``` The `Generator` is signs off on the `GlobalTableRoot` with it's BN254 keys. Note that the `Generator` is an `operatorSet` of size one. :::info The `Certificate` that the `Generator` creates is a `BN254Certificate`. We reuse the certificate verification logic of AVS task confirmation to validate the `GlobalTableRoot. ` ::: Once the `Generator` signs off on the `GlobalTableRoot`, it can be retrieved by the `Transporter` to [transport stakes](#Transport) ## Generation Every `Table Update Cadence` (daily on testnet/mainnet), the `Generator` does the following: 1. Call `CrossChainRegistry.getActiveGenerationReservations` to get each `operatorSet` to be included in the `GlobalTableRoot` 2. Call `CrossChainRegistry.calculateOperatorTableBytes()` for each `operatorSet` with an active generation reservation 3. Calculate the `globalTableRoot` by merkleizing each `operatorSets` `operatorTable` 4. Create a `BN254Certificate` signing off on the `globalTableRoot` ### System Diagram The tree for the `globalTableRoot` is: ```mermaid flowchart TB R((GlobalTableRoot)) R --> N0((Internal Node 0-1)) R --> N1((Internal Node 2-3)) N0 --> L0[[Leaf 0<br/>OperatorSet Table #0]] N0 --> L1[[Leaf 1<br/>OperatorSet Table #1]] N1 --> L2[[Leaf 2<br/>OperatorSet Table #2]] N1 --> L3[[Leaf 3<br/>OperatorSet Table #3]] ``` Where `operatorSetTable` is the bytes given by the `CrossChainRegistry`: ```solidity= /** * @notice Calculates the operatorTableBytes for a given operatorSet * @param operatorSet the operatorSet to calculate the operator table for * @return the encoded operatorTableBytes containing: * - operatorSet details * - curve type from KeyRegistrar * - operator set configuration * - calculated operator table from the calculator contract * @dev This function aggregates data from multiple sources for cross-chain transport */ function calculateOperatorTableBytes( OperatorSet calldata operatorSet ) external view returns (bytes memory) { return abi.encode( operatorSet, keyRegistrar.getOperatorSetCurveType(operatorSet), getOperatorSetConfig(operatorSet), getOperatorTableCalculator(operatorSet).calculateOperatorTableBytes(operatorSet) ); } ``` The code for the generation protocol is in our [multichain-repo](https://github.com/Layr-Labs/multichain-go/tree/master). ## Event Driven Updates (Delta Updates) `OperatorTable's` are [transported](#Transport) at a set cadence. To provide sufficient liveness guarantees for AVSs, the `GlobalTableRoot` is also updated in the case of the following events being emitted for *any* operatorSet from the `AllocatioManager`: * `OperatorSlashed` * `OperatorAddedToOperatorSet` * `OperatorRemovedFromOperatorSet` See our [transport repo](https://github.com/Layr-Labs/multichain-transporter/blob/master/pkg/transporter/transporter.go#L121) for how we ingest and transport. # Destination Chain Transport and Verification The destination chain receives transported `OperatorTable's` and enables consumers of an AVS to validate tasks. ```mermaid classDiagram direction TD namespace Destination Chain{ class OperatorTableUpdater{ confirmGlobalTableRoot updateOperatorTable() } class CertificateVerifier{ n Operator Tables updateOperatorTable() verifyCert (bool) } class AVSConsumer{ requests Operator task receives cert () } } namespace Offchain{ class Operator { consumer input return certificate() } class Transport{ getOperatorTables n calculateOperatorTableBytes calculateGlobalTableRoot() } } Transport --> OperatorTableUpdater: Update global table root Transport --> OperatorTableUpdater: Update operator table OperatorTableUpdater --> CertificateVerifier: Update Operator Table Operator --> AVSConsumer : Produces certificate Operator <-- AVSConsumer : Requests task AVS Consumer --> CertificateVerifier : Verifies Certificate ``` ## Transport The transporter has two jobs: 1. Transport the `GlobalTableRoot` 2. Update the `operatorTables` for each `operatorSet` with a merkle proof against the `GlobalTableRoot` ```mermaid sequenceDiagram participant GEN as Generator participant ELT as Transporter participant CCR as CrossChainRegistry participant OTU as OperatorTableUpdater (supported chains) participant CV as CertificateVerifier (supported chains) %% Step 1: Get the certificate GEN-->>ELT: Signed globalOperatorTableRoot response ELT-->>CCR: Get supported destination chains %% Step 2: Certificate Submitted to Ethereum ELT->>OTU: confirmGlobalTableRoot(certificate, referenceTimestamp, root) on all chains OTU-->>CV: verifyCertificate(globalTableRoot certificate) CV-->>OTU: isValid note over OTU: Stores globalTableRoot %% Step 3: Transport Table ELT->> OTU: updateOperatorTable(operatorSet, proof) OTU-->CV: updateOperatorTable ``` ### OperatorTableUpdater The `OperatorTableUpdater` is responsible for recieving calls to updating the `GlobalTableRoot` and updating operator tables from merkle proofs against the `GlobalTableRoot`. Both write functions are permissionless. The contract is deployed on every destination chain. The contract maintains a set of valid global table roots that are confirmed by a designated generator, and allows updating individual operator tables by providing merkle proofs against these roots. For more information, see our [contract documentation](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/docs/multichain/destination/OperatorTableUpdater.md). ### Transport - `GlobalTableRoot` The `Transporter` will retrieve a `BN254Certificate` with the `GlobalTableRoot` from the `Generator`. Upon retrieval, the `Transporter` will transport the `GlobalTableRoot` to each `DestinationChain` by calling `OperatorTableUpdater.updateGlobalTableRoot()`. In order for the `globalTableRoot` to be stored, the `BN254Certificate` must be successfully verified by the `BN254CertificateVerifier`. Once stored, the `Transporter` can proceed to trasnport all `OperatorTables`. ### Transport - `OperatorTable` Once the `GlobalTableRoot` has been updated, the `Transporter` will call `OperatorTableUpdater.updateOperatorTable` for each operatorSet to update. This function will route calls to the `BN254CertificateVerifier` and `ECDSACertificateVerifier` depending on the key-type of the `operatorSet`. :::warning If an operatorSet is too large to be updated in one-transaction, it will not be transported by the multichain protocol. To prevent griefing for the MVP, Eigenlabs will gate which operatorSets will have their operator tables transported. ::: Every day at 2PM UTC, `Transporter` updates the all operatorSets up-to-date. In the case of an event-driven table update, the `Transporter` will only update the tables for `operatorSets` which emit Registration, Deregistration, or Slash events. ## Verification Verification is the final step of the multichain protocol. Once the `OperatorTable` is updated, `Certificates` can be verified against ### Verification Types The contract supports 3 verification functions: 1. `verifyCertificate`: Verifies a certificate and returns an array of `stakeWeights`. Each index corresponds to the total stake that has signed off for the `stake type` 2. `verifyCertificateProportional(uint16[] totalStakeProportionThresholds)`: Verifies a certificate and returns true if each `stake type` has the requisite percentage signed off in the provided proportions array 3. `verifyCertificateNominal(uint256[] totalStakeNominalThresholds)`: Verifies a certificate and returns true if each `stake type` has the given amount signed off in the provided thresholds array For more information, see the [contract docs](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/docs/multichain/destination/CertificateVerifier.md). ### BN254 As mentioned [above](#BN254-Table-Information), `BN254` Certificate verification takes advantage of aggregate signatures to minimize the amount of data transported. The cost is further decreased by passing in a merkle root that contains all operator stake information. For gas efficiency, we assume that a majority of operators will sign a certificate. Thus, upon verification, only the *non-signer* stakes are proven via merkle proofs against the operator info tree root. See our [contract documentaiton](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/docs/multichain/destination/CertificateVerifier.md#caching-mechanism) for details.