# 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.