Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avs docs #251

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/images/registry_architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions docs/middleware/BLSOperatorStateRetriever.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# BLSOperatorStateRetriever

This contract is deployed once and is intended to be used by all AVSs that use the provided registry contracts. It is used as a utility for offchain AVS actors to fetch the details of AVSs operators, their stakes in the StakeRegistry, and their indices in the IndexRegistry.

## Flows

This contract is a bunch of view functions that are very gas expensive, they are meant to only be called by offchain actors.

### getOperatorState

This gets the ordered list of operators and their stakes for the provided quorum numbers for the AVS with the provided registry coordinator at the provided block number

There is an overloaded version of this function that takes an operator id and uses the quorum numbers they were registered for instead of having the caller provide quorum numbers.

### getCheckSignaturesIndices

This function is particularly called by AVS aggregators, who get BLS signatures from AVS operators to confirm their signature onchain. Using the provided block number, registry coordinator, quorum numbers, and non signing operator ids, it returns the correct various indices required to confirm signatures with the [BLSSignatureChecker](./BLSSignatureChecker.md).

## Upstream Dependencies

Again, this is called by offchain actors during requests from AVS offchain actors. For example, in EigenDA, a disperser sends data to operators, and those operators are expected to call functions in this contract to make sure they have received the correct amount of data, among other things.
36 changes: 36 additions & 0 deletions docs/middleware/BLSPubkeyRegistry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# BLSPubkeyRegistry

This contract is a registry that keeps track of aggregate public key hashes of the quorums of an AVS over time. AVSs that want access to the aggregate public keys of their operator set over time should integrate this registry with their RegistryCoordinator.

## Flows

### registerOperator

The RegistryCoordinator for the AVS makes a call to the BLSPubkeyRegistry to register an operator with a certain public key for a certain set of quorums. The BLSPubkeyRegistry verifies that the operator in fact owns the public key by [making a call to the BLSPublicKeyCompendium](./BLSPublicKeyCompendium.md#integrations). It then, for each quorum the operator is registering for, adds the operator's public key to the aggregate quorum public key, ends the active block range for the previous aggregate public key, and begins the active block range for the new aggregate quorum public key. Updates are stored using the following struct:
```solidity
/// @notice Data structure used to track the history of the Aggregate Public Key of all operators
struct ApkUpdate {
// first 24 bytes of keccak256(apk_x, apk_y)
bytes24 apkHash;
// block number at which the update occurred
uint32 updateBlockNumber;
// block number at which the next update occurred
uint32 nextUpdateBlockNumber;
}
```

The aggregate quorum public key stored in the contract is also overwritten with the new aggregate quorum public key.

The function also returns the hash of the operator's public key as it may be used as is for the operator in the AVS. The [BLSRegistryCoordinator](./BLSRegistryCoordinatorWithIndices.md) uses the hash of the operator's public key as the operator's identifier (operator id) since it lowers gas costs in the [BLSSignatureChecker](./BLSSignatureChecker.md).

### deregisterOperator

The RegistryCoordinator for the AVS makes a call to the BLSPubkeyRegistry to deregister an operator with a certain public key for a certain set of quorums. The BLSPubkeyRegistry verifies that the operator in fact owns the public key by [making a call to the BLSPublicKeyCompendium](./BLSPublicKeyCompendium.md#integrations). It then, for each quorum the operator is registering for, subtracts the operator's public key from the aggregate quorum public key, ends the active block range for the previous aggregate public key, and begins the active block range for the new aggregate quorum public key.

The aggregate quorum public key stored in the contract is also overwritten with the new aggregate quorum public key.

Note that the contract does not check that the quorums that the operator's public key is being subtracted from are a subset of the quorums the operator is registered for, that logic is expected to be done in the RegistryCoordinator.

## Upstream Dependencies

The main integration with the BLSPublicKeyRegistry is used by the AVSs [BLSSignatureChecker](./BLSSignatureChecker.md). An offchain actor provides a public key, a quorum id, and an index in the array of aggregate quorum public key hashes, and the AVS's signature checker verifies that a certain quorum's aggregate public key hash at a certain block number was in fact the hash of the provided public key. Look at `getApkHashForQuorumAtBlockNumberFromIndex`.
27 changes: 27 additions & 0 deletions docs/middleware/BLSPublicKeyCompendium.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# BLSPublicKeyCompendium

This contract is shared by all AVSs and serves as a single place for operators to connect their execution layer address to a bn254 public key. This contract also prevents [rogue key attacks](https://xn--2-umb.com/22/bls-signatures/#rogue-key-attack).

## Flows

There is only one flow for this contract, which is a call from an operator to register a bn254 public key as controlled by their execution layer address.

### Cryptographic Verification

Operators provide the contract with
- A BLS signature $\sigma \in \mathbb{G}_1$ of $M = (msg.sender, chain.id, \text{"EigenLayer\_BN254\_Pubkey\_Registration"})$
- Their public keys $pk_1 \in \mathbb{G}_1$ and $pk_2 \in \mathbb{G}_2$

The contract then
- Calculates $\gamma = keccak256(\sigma, pk_1, pk_2, M)$
- Verifies the paring $e(\sigma + \gamma pk_1, [1]_2) = e(H(m) + \gamma[1]_1, pk_2)$

This verifies that the operator owns the secret key corresponding to the public keys and that the $pk_1$ and $pk_2$ have the same discrete logarithm according to their respective curve's generators.

We do this particular verification because aggregation of public keys and hasing to the curve is cheap in $\mathbb{G}_1$ on ethereum, and the above scheme allows for both! (aggregation to be done in the [BLSSignatureChecker](./BLSSignatureChecker.md)) More detailed notes exist [here](https://geometry.xyz/notebook/Optimized-BLS-multisignatures-on-EVM).

The contract then stores a map from the execution layer address to the hash of the operator's $\mathbb{G}_1$ public key and the other way around.

### Upstream Dependencies

The [BLSPubkeyRegistry](./BLSPubkeyRegistry.md) looks up the public key hashes in this contract when operators register with a certain public key.
86 changes: 86 additions & 0 deletions docs/middleware/BLSRegistryCoordinatorWithIndices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# BLSRegistryCoordinatorWithIndices

This contract is deployed for every AVS and serves as the main entrypoint for operators to register and deregister from the AVS. In addition, it is where the AVS defines its operator churn parameters.

## Flows

### registerOperator

When registering the operator must provide
1. The quorums they are registering for
2. Their BLS public key that they registered with the [BLSPubkeyCompendium](./BLSPublicKeyCompendium.md)
3. The socket (ip:port) at which AVS offchain actors should make requests

The RegistryCoordinator then
1. Registers the operator's BLS public key with the [BLSPubkeyRegistry](BLSPubkeyRegistry.md) and notes the hash of their public key as their operator id
2. Registers the operator with the [StakeRegistry](./StakeRegistry.md)
3. Registers the operator with the [IndexRegistry](./IndexRegistry.md)
4. Stores the quorum bitmap of the operator using the following struct:
```
/**
* @notice Data structure for storing info on quorum bitmap updates where the `quorumBitmap` is the bitmap of the
* quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber`
* @dev nextUpdateBlockNumber is initialized to 0 for the latest update
*/
struct QuorumBitmapUpdate {
uint32 updateBlockNumber;
uint32 nextUpdateBlockNumber;
uint192 quorumBitmap;
}
```

Operators can be registered for certain quorums and later register for other (non-overlapping) quorums.

### If quorum full

The following struct is defined for each quorum
```
/**
* @notice Data structure for storing operator set params for a given quorum. Specifically the
* `maxOperatorCount` is the maximum number of operators that can be registered for the quorum
* `kickBIPsOfOperatorStake` is the multiple (in basis points) of stake that a new operator must have, as compared the operator that they are kicking out of the quorum
* `kickBIPsOfTotalStake` is the fraction (in basis points) of the total stake of the quorum that an operator needs to be below to be kicked.
*/
struct OperatorSetParam {
uint32 maxOperatorCount;
uint16 kickBIPsOfOperatorStake;
uint16 kickBIPsOfTotalStake;
}
```

If any of the quorums is full (number of operators in it is `maxOperatorCount`), the newly registering operator must provide the public key of another operator to be kicked. The new and kicked operator must satisfy the two conditions:
1. the new operator has an amount of stake that is at least `kickBIPsOfOperatorStake` multiple of the kicked operator's stake
2. the kicked operator has less than `kickBIPsOfTotalStake` fraction of the quorum's total stake

The provided operators are deregistered from the respective quorums that are full which the registering operator is registering for. Since the operators being kicked may not be the operators with the least stake, the RegistryCoordinator requires that the provided operators are signed off by a permissioned address called a `churnApprover`. Note that the quorum operator caps are due to the cost of BLS signature (dis)aggregation onchain.

Operators register with a list of
```
/**
* @notice Data structure for the parameters needed to kick an operator from a quorum with number `quorumNumber`, used during registration churn.
* Specifically the `operator` is the address of the operator to kick, `pubkey` is the BLS public key of the operator,
*/
struct OperatorKickParam {
uint8 quorumNumber;
address operator;
BN254.G1Point pubkey;
}
```
For each quorum they need to kick operators from. This list, along with the id of the registering operator needs to be signed (along with a salt and expiry) by an actor known as the *churnApprover*. Operators will make a request to the churnApprover offchain before registering for their signature, if needed.

### deregisterOperator

When deregistering, an operator provides
1. The quorums they registered for
2. Their BLS public key
3. The ids of the operators that must swap indices with the [deregistering operator in the IndexRegistry](./IndexRegistry.md#deregisteroperator).

The RegistryCoordinator then deregisters the operator with the BLSPubkeyRegistry, StakeRegistry, and IndexRegistry. It then ends the block range for its stored quorum bitmap for the operator.

Operators can deregister from a subset of quorums that they are registered for.

## Upstream Dependencies

Operators register and deregister with the AVS for certain quorums through this contract.

EigenLabs intends to run the EigenDA churnApprover.
44 changes: 44 additions & 0 deletions docs/middleware/BLSSignatureChecker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# BLSSignatureChecker

This contract is deployed per AVS. It verifies the signatures of operators in an efficient way given the rest of the registry architecture. A lot of EigenLayer AVSs can be summarized as a quorum signature on a message and slashing if some quality of that message and other state is true.

## Flows

### checkSignatures
```
function checkSignatures(
bytes32 msgHash,
bytes calldata quorumNumbers,
uint32 referenceBlockNumber,
NonSignerStakesAndSignature memory nonSignerStakesAndSignature
)

struct NonSignerStakesAndSignature {
uint32[] nonSignerQuorumBitmapIndices;
BN254.G1Point[] nonSignerPubkeys;
BN254.G1Point[] quorumApks;
BN254.G2Point apkG2;
BN254.G1Point sigma;
uint32[] quorumApkIndices;
uint32[] totalStakeIndices;
uint32[][] nonSignerStakeIndices; // nonSignerStakeIndices[quorumNumberIndex][nonSignerIndex]
}
```
This function is called by a AVS aggregator when confirming that a `msgHash` is signed by certain `quorumNumbers`.

The function calculates the sum (`apk`) of the aggregate public keys for all the quorums in question using the provided `quorumApkIndices` which point to hashes of the quorum aggregate public keys at the `referenceBlockNumber` of the signature of which `quorumApks` are the preimages. Since there may be nodes from the quorums that don't sign, the `nonSignerPubkeys` are subtracted from `apk`. There is a detail here that since an operator may serve more than one of the quorums in question, the number of quorums in `quorumNumbers` that the nonsigner served at the `referenceBlockNumber` is calculated using the provided `nonSignerQuorumBitmapIndices` and their public key is multiplied by the number before it is subtracted from `apk`. This gets rid of the duplicate additions of their public key because their public key is in more than one of the added `quorumApks`. Now the contract has `apk` set to the claimed aggregate public key of the signers.

Next, the contract fetches the total stakes of each of the `quorumNumbers` at the `referenceBlockNumber` using the provided `totalStakeIndices`. The stakes of each of the nonsigners for each of the quorums are fetched using `nonSignerStakeIndices` and subtracted from the total stakes. Now the contract has the claimed signing stake for each of the quorums.

Finally, the contract does a similar check to the [BLSPublicKeyCompendium](./BLSPublicKeyCompendium.md):

- Calculates $\gamma = keccak256(apk, apkG2, sigma)$
- Verifies the paring $e(\sigma + \gamma apk, [1]_2) = e(H(msgHash) + \gamma[1]_1, apkG2)$

More detailed notes exist on the signature check [here](https://geometry.xyz/notebook/Optimized-BLS-multisignatures-on-EVM).

If it checks out, the contract returns the stake that signed the message for each quorum and the hash of the reference block number and the list of public key hashes of the nonsigners for future use.

## Upstream Dependencies

AVSs are expected to use this contract's method for their specific tasks. For example, EigenDA uses this function in their contracts when confirming batches of blobs on their DA layer onchain.
39 changes: 39 additions & 0 deletions docs/middleware/IndexRegistry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# IndexRegistry

This contract assigns each operator an index (0 indexed) within each of its quorums. If a quorum has $n$ operators, each operator will be assigned an index $0$ through $n-1$. This contract is used for AVSs that need a common ordering among all operators in a quorum that is accessible onchain. For example, this will be used in proofs of custody by EigenDA. This contract also keeps a list of all operators that have ever joined the AVS for convenience purposes in offchain software that are out of scope for this document.

## Flows

### registerOperator

The RegistryCoordinator for the AVS makes call to the IndexRegistry to register an operator for a certain set of quorums. The IndexRegistry will assign the next index in each of the quorums the operator is registering for to the operator storing the following struct:
```solidity
// struct used to give definitive ordering to operators at each blockNumber.
// NOTE: this struct is slightly abused for also storing the total number of operators for each quorum over time
struct OperatorIndexUpdate {
// blockNumber number from which `index` was the operators index
// the operator's index or the total number of operators at a `blockNumber` is the first entry such that `blockNumber >= entry.fromBlockNumber`
uint32 fromBlockNumber;
// index of the operator in array of operators, or the total number of operators if in the 'totalOperatorsHistory'
// index = type(uint32).max = OPERATOR_DEREGISTERED_INDEX implies the operator was deregistered
uint32 index;
}
```

The IndexRegistry also adds the operator's id to the append only list of operators that have registered for the middleware and it stores the total number of operators after the registering operator has registered for each of the quorums the operator is registering for by pushing the above struct to a growing array.

### deregisterOperator

The RegistryCoordinator for the AVS makes call to the IndexRegistry to deregister an operator for a certain set of quorums. The RegistryCoordinator provides a witness of the ids of the operators that have the greatest index in each of the quorums that the operator is deregistering from. The IndexRegistry then, for each quorum the operator is deregistering from,
1. Decrements the total number of operators in the quorum
2. Makes sure the provided "greatest index operator id" in fact has the greatest index in the quorum by checking it against the total number of operators in the quorum
3. Sets the index of the "greatest index operator" to the index of the deregistering operator
4. Sets the index of the deregistering operator to `OPERATOR_DEREGISTERED_INDEX = type(uint32).max`

Steps 3 and 4 are done via pushing the above struct to a growing array that is kept track of for each operator.

Note that the contract does not check that the quorums that the operator is being deregistered from are a subset of the quorums the operator is registered for, that logic is expected to be done in the RegistryCoordinator.

## Upstream Dependencies

The [BLSOperatorStateRetriever](./BLSOperatorStateRetriever.md) uses the globally ordered list of all operators every registered for the AVS to serve information about the active operator set for the AVS to offchain nodes.
Loading
Loading