Skip to content

Commit

Permalink
docs: update reg coord to include pubkey registration and service man… (
Browse files Browse the repository at this point in the history
#116)

* docs: update reg coord to include pubkey registration and service manager usage

* docs: add service manager to tech docs intro

* fix: update table of contents

* docs: add docs for BLSSignatureChecker and OperatorStateRetriever

* docs: address feedback
  • Loading branch information
wadealexc authored Dec 28, 2023
1 parent 9aa6eb5 commit ebe657e
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 33 deletions.
201 changes: 200 additions & 1 deletion docs/BLSSignatureChecker.md
Original file line number Diff line number Diff line change
@@ -1 +1,200 @@
TODO!
[core-docs-m2]: https://github.com/Layr-Labs/eigenlayer-contracts/tree/m2-mainnet/docs
[core-dmgr-docs]: https://github.com/Layr-Labs/eigenlayer-contracts/blob/m2-mainnet/docs/core/DelegationManager.md
[core-dmgr-register]: https://github.com/Layr-Labs/eigenlayer-contracts/blob/m2-mainnet/docs/core/DelegationManager.md#registeroperatortoavs
[core-dmgr-deregister]: https://github.com/Layr-Labs/eigenlayer-contracts/blob/m2-mainnet/docs/core/DelegationManager.md#deregisteroperatorfromavs

[eigenda-service-manager]: https://github.com/Layr-Labs/eigenda/blob/m2-mainnet-contracts/contracts/src/core/EigenDAServiceManager.sol

## BLSSignatureChecker

| File | Type | Proxy |
| -------- | -------- | -------- |
| [`BLSSignatureChecker.sol`](../src/BLSSignatureChecker.sol) | Singleton | Transparent proxy |
| [`OperatorStateRetriever.sol`](../src/OperatorStateRetriever.sol) | Singleton | None |

`BLSSignatureChecker` and `OperatorStateRetriever` perform (respectively) the onchain and offchain portions of BLS signature validation for the aggregate of a quorum's registered Operators.

The `OperatorStateRetriever` has various view methods intended to be called by offchain infrastructure in order to prepare a call to `BLSSignatureChecker.checkSignatures`. These methods traverse the state histories kept by the various registry contracts (see [./RegistryCoordinator.md](./RegistryCoordinator.md)) to query states at specific block numbers.

These historical states are then used within `BLSSignatureChecker` to validate a BLS signature formed from an aggregated subset of the Operators registered for one or more quorums at some specific block number.

#### High-level Concepts

This document organizes methods according to the following themes (click each to be taken to the relevant section):
* [Onchain](#onchain)
* [Offchain](#offchain)
* [System Configuration](#system-configuration)

---

### Onchain

#### `BLSSignatureChecker.checkSignatures`

```solidity
function checkSignatures(
bytes32 msgHash,
bytes calldata quorumNumbers,
uint32 referenceBlockNumber,
NonSignerStakesAndSignature memory params
)
public
view
returns (QuorumStakeTotals memory, bytes32)
struct NonSignerStakesAndSignature {
uint32[] nonSignerQuorumBitmapIndices;
BN254.G1Point[] nonSignerPubkeys;
BN254.G1Point[] quorumApks;
BN254.G2Point apkG2;
BN254.G1Point sigma;
uint32[] quorumApkIndices;
uint32[] totalStakeIndices;
uint32[][] nonSignerStakeIndices;
}
struct QuorumStakeTotals {
uint96[] signedStakeForQuorum;
uint96[] totalStakeForQuorum;
}
```

The goal of this method is to allow an AVS to validate a BLS signature formed from the aggregate pubkey ("apk") of Operators registered in one or more quorums at some `referenceBlockNumber`.

Some notes on method parameters:
* `referenceBlockNumber` is the reason each registry contract keeps historical states: so that lookups can be performed on each registry's info at a particular block. This is important because Operators may sign some data on behalf of an AVS, then deregister from one or more of the AVS's quorums. Historical states allow signature validation to be performed against a "fixed point" in AVS/quorum history.
* `quorumNumbers` is used to perform signature validation across one *or more* quorums. Also, Operators may be registered for more than one quorum - and for each quorum an Operator is registered for, that Operator's pubkey is included in that quorum's apk within the `BLSApkRegistry`. This means that, when calculating an apk across multiple `quorumNumbers`, Operators registered for more than one of these quorums will have their pubkey included more than once in the total apk.
* `params` contains both a signature from all signing Operators, as well as several fields that identify registered, non-signing Operators. While non-signing Operators haven't contributed to the signature, but need to be accounted for because, as Operators registered for one or more signing quorums, their public keys are included in that quorum's apk. Essentially, in order to validate the signature, nonsigners' public keys need to be subtracted out from the total apk to derive the apk that actually signed the message.

This method performs the following steps. Note that each step involves lookups of historical state from `referenceBlockNumber`, but the writing in this section will use the present tense because adding "at the `referenceBlockNumber`" everywhere gets confusing. Steps follow:
1. Calculate the *total nonsigner apk*, an aggregate pubkey composed of all nonsigner pubkeys. For each nonsigner:
* Query the `RegistryCoordinator` to get the nonsigner's registered quorums.
* Multiply the nonsigner's pubkey by the number of quorums in `quorumNumbers` the nonsigner is registered for.
* Add the result to the *total nonsigner apk*.
2. Calculate the negative of the *total nonsigner apk*.
3. For each quorum:
* Query the `BLSApkRegistry` to get the *quorum apk*: the aggregate pubkey of all Operators registered for that quorum.
* Add the *quorum apk* to the *total nonsigner apk*. This effectively subtracts out any pubkeys belonging to nonsigning Operators in the quorum, leaving only pubkeys of signing Operators. We'll call the result the *total signing apk*.
* Query the `StakeRegistry` to get the total stake for the quorum.
* For each nonsigner, if the nonsigner is registered for the quorum, query the `StakeRegistry` for their stake and subtract it from the total. This leaves only stake belonging to signing Operators.
4. Use the `msgHash`, the *total signing apk*, `params.apkG2`, and `params.sigma` to validate the BLS signature.
5. Return the total stake and signing stakes for each quorum, along with a hash identifying the `referenceBlockNumber` and non-signers

*Entry Points* (EigenDA):
* Called by [`EigenDAServiceManager.confirmBatch`][eigenda-service-manager]

*Requirements*:
* Input validation:
* Quorum-related fields MUST have equal lengths: `quorumNumbers`, `params.quorumApks`, `params.quorumApkIndices`, `params.totalStakeIndices`, `params.nonSignerStakeIndices`
* Nonsigner-related fields MUST have equal lengths: `params.nonSignerPubkeys`, `params.nonSignerQuorumBitmapIndices`
* `referenceBlockNumber` MUST NOT be greater than `block.number`
* `quorumNumbers` MUST be an ordered list of valid, initialized quorums
* `params.nonSignerPubkeys` MUST ONLY contain unique pubkeys, in ascending order of their pubkey hash
* For each quorum:
* If stale stakes are forbidden (see [`BLSSignatureChecker.setStaleStakesForbidden`](#blssignaturecheckersetstalestakesforbidden)), check the last `quorumUpdateBlockNumber` is within `DelegationManager.withdrawalDelayBlocks` of `referenceBlockNumber`. This references a value in the EigenLayer core contracts - see [EigenLayer core docs][core-docs-m2] for more info.
* Validate that each `params.quorumApks` corresponds to the quorum's apk at the `referenceBlockNumber`
* For each historical state lookup, the `referenceBlockNumber` and provided index MUST point to a valid historical entry:
* `referenceBlockNumber` MUST come after the entry's `updateBlockNumber`
* The entry's `nextUpdateBlockNumber` MUST EITHER be 0, OR greater than `referenceBlockNumber`

---

### Offchain

These methods perform very gas-heavy lookups through various registry states, and are called by offchain infrastructure to construct calldata for a call to `BLSSignatureChecker.checkSignatures`:
* [`OperatorStateRetriever.getOperatorState (operatorId)`](#operatorstateretrievergetoperatorstate-operatorid)
* [`OperatorStateRetriever.getOperatorState (quorumNumbers)`](#operatorstateretrievergetoperatorstate-quorumnumbers)
* [`OperatorStateRetriever.getCheckSignaturesIndices`](#operatorstateretrievergetchecksignaturesindices)

#### `OperatorStateRetriever.getOperatorState (operatorId)`

```solidity
function getOperatorState(
IRegistryCoordinator registryCoordinator,
bytes32 operatorId,
uint32 blockNumber
)
external
view
returns (uint256, Operator[][] memory)
struct Operator {
bytes32 operatorId;
uint96 stake;
}
```

Traverses history in the `RegistryCoordinator`, `IndexRegistry`, and `StakeRegistry` to retrieve information on an Operator (given by `operatorId`) and the quorums they are registered for at a specific `blockNumber`. Returns:
* `uint256`: a bitmap of the quorums the Operator was registered for at `blockNumber`
* `Operator[][]`: For each of the quorums mentioned above, this is a list of the Operators registered for that quorum at `blockNumber`, containing each Operator's `operatorId` and `stake`.

#### `OperatorStateRetriever.getOperatorState (quorumNumbers)`

```solidity
function getOperatorState(
IRegistryCoordinator registryCoordinator,
bytes memory quorumNumbers,
uint32 blockNumber
)
public
view
returns(Operator[][] memory)
```

Traverses history in the `RegistryCoordinator`, `IndexRegistry`, and `StakeRegistry` to retrieve information on the Operator set registered for each quorum in `quorumNumbers` at `blockNumber`. Returns:
* `Operator[][]`: For each quorum in `quorumNumbers`, this is a list of the Operators registered for that quorum at `blockNumber`, containing each Operator's `operatorId` and `stake`.

#### `OperatorStateRetriever.getCheckSignaturesIndices`

```solidity
function getCheckSignaturesIndices(
IRegistryCoordinator registryCoordinator,
uint32 referenceBlockNumber,
bytes calldata quorumNumbers,
bytes32[] calldata nonSignerOperatorIds
)
external
view
returns (CheckSignaturesIndices memory)
struct CheckSignaturesIndices {
uint32[] nonSignerQuorumBitmapIndices;
uint32[] quorumApkIndices;
uint32[] totalStakeIndices;
uint32[][] nonSignerStakeIndices; // nonSignerStakeIndices[quorumNumberIndex][nonSignerIndex]
}
```

Traverses histories in the `RegistryCoordinator`, `IndexRegistry`, `StakeRegistry`, and `BLSApkRegistry` to retrieve information on one or more quorums' Operator sets and nonsigning Operators at a given `referenceBlockNumber`.

The return values are all "indices," because of the linear historical state each registry keeps. Offchain code calls this method to compute indices into historical state, which later is leveraged for cheap lookups in `BLSSignatureChecker.checkSignatures` (rather than traversing over the history during an onchain operation).

For each quorum, this returns:
* `uint32[] nonSignerQuorumBitmapIndices`: The indices in `RegistryCoordinator._operatorBitmapHistory` where each nonsigner's registered quorum bitmap can be found at `referenceBlockNumber`. Length is equal to the number of nonsigners included in `nonSignerOperatorIds`
* `uint32[] quorumApkIndices`: The indices in `BLSApkRegistry.apkHistory` where the quorum's apk can be found at `referenceBlockNumber`. Length is equal to the number of quorums in `quorumNumbers`.
* `uint32[] totalStakeIndices`: The indices in `StakeRegistry._totalStakeHistory` where each quorum's total stake can be found at `referenceBlockNumber`. Length is equal to the number of quorums in `quorumNumbers`.
* `uint32[][] nonSignerStakeIndices`: For each quorum, a list of the indices of each nonsigner's `StakeRegistry.operatorStakeHistory` entry at `referenceBlockNumber`. Length is equal to the number of quorums in `quorumNumbers`, and each sub-list is equal in length to the number of nonsigners in `nonSignerOperatorIds` registered for that quorum at `referenceBlockNumber`

---

### System Configuration

#### `BLSSignatureChecker.setStaleStakesForbidden`

```solidity
function setStaleStakesForbidden(
bool value
)
external
onlyCoordinatorOwner
```

This method allows the `RegistryCoordinator` Owner to update `staleStakesForbidden` in the `BLSSignatureChecker`. If stale stakes are forbidden, `BLSSignatureChecker.checkSignatures` will perform an additional check when querying each quorum's apk, Operator stakes, and total stakes.

This additional check requires that each quorum was updated within a certain block window of the `referenceBlockNumber` passed into `BLSSignatureChecker.checkSignatures`.

*Effects*:
* Sets `staleStakesForbidden` to `value`

*Requirements*:
* Caller MUST be the `RegistryCoordinator` Owner
15 changes: 14 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ For more information on EigenDA, check out the repo: [Layr-Labs/eigenda][eigenda
* [State Histories](#state-histories)
* [Hooking Into EigenLayer Core](#hooking-into-eigenlayer-core)
* [System Components](#system-components)
* [Service Manager](#service-manager)
* [Registries](#registries)
* [BLSSignatureChecker](#blssignaturechecker)

Expand Down Expand Up @@ -105,6 +106,18 @@ Eventually, operator slashing and payment for services will be part of the middl

### System Components

#### Service Manager

| Code | Type | Proxy |
| -------- | -------- | -------- |
| [`ServiceManagerBase.sol`](../src/ServiceManagerBase.sol) | Singleton | Transparent proxy |

The Service Manager contract serves as the AVS's address relative to EigenLayer core contracts. When operators register for/deregister from the AVS, the Service Manager forwards this request to the DelegationManager (see [Hooking Into EigenLayer Core](#hooking-into-eigenlayer-core) above).

It also contains a few convenience methods used to query operator information by the frontend.

See full documentation in [`ServiceManagerBase.md`](./ServiceManagerBase.md).

#### Registries

| Code | Type | Proxy |
Expand Down Expand Up @@ -136,4 +149,4 @@ The BLSSignatureChecker verifies signatures made by the aggregate pubkeys ("Apk"

The `OperatorStateRetriever` is used by offchain code to query the `RegistryCoordinator` (and its registries) for information that will ultimately be passed into `BLSSignatureChecker.checkSignatures`.

See full documentation in [`BLSSignatureChecker.md`](./BLSSignatureChecker.md).
See full documentation for both of these contracts in [`BLSSignatureChecker.md`](./BLSSignatureChecker.md).
17 changes: 13 additions & 4 deletions docs/RegistryCoordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ These methods allow operators to register for/deregister from one or more quorum
function registerOperator(
bytes calldata quorumNumbers,
string calldata socket,
IBLSApkRegistry.PubkeyRegistrationParams calldata params,
SignatureWithSaltAndExpiry memory operatorSignature
)
external
Expand All @@ -53,12 +54,16 @@ Registers the caller as an Operator for one or more quorums, as long as registra
* `StakeRegistry.registerOperator`
* `IndexRegistry.registerOperator`

If the Operator was not currently registered for any quorums, this method will register the Operator to the AVS in the EigenLayer core contracts (`DelegationManager.registerOperatorToAVS`), passing in the provided `operatorSignature`. See the [`DelegationManager` docs][core-dmgr-docs] for more details.
If the Operator has never registered for any of this AVS's quorums before, they need to register a BLS public key to participate in AVS signing events. In this case, this method will automatically pass `params` to the `BLSApkRegistry` to perform public key registration. The registered pubkey hash becomes the Operator's unique operator id, used to identify them in many places in the middleware contracts.

If the Operator was not currently registered for any quorums, this method will register the Operator to the AVS in the EigenLayer core contracts via the `ServiceManagerBase`.

*Effects*:
* If the Operator has never registered for the AVS before:
* Registers their BLS pubkey in the `BLSApkRegistry` (see [`BLSApkRegistry.registerBLSPublicKey`](./registries/BLSApkRegistry.md#registerblspublickey))
* If the Operator was not currently registered for any quorums:
* Updates their status to `REGISTERED`
* Registers them in the core contracts (see [`DelegationManager.registerOperatorToAVS`][core-dmgr-register])
* Registers them in the core contracts (see [`ServiceManagerBase.registerOperatorToAVS`](./ServiceManagerBase.md#registeroperatortoavs))
* Adds the new quorums to the Operator's current registered quorums, and updates the Operator's bitmap history
* See [`BLSApkRegistry.registerOperator`](./registries/BLSApkRegistry.md#registeroperator)
* See [`StakeRegistry.registerOperator`](./registries/StakeRegistry.md#registeroperator)
Expand All @@ -71,7 +76,7 @@ If the Operator was not currently registered for any quorums, this method will r
* `quorumNumbers` MUST contain at least one valid quorum
* `quorumNumbers` MUST NOT contain any quorums the Operator is already registered for
* If the Operator was not currently registered for any quorums:
* See [`DelegationManager.registerOperatorToAVS`][core-dmgr-register]
* See [`ServiceManagerBase.registerOperatorToAVS`](./ServiceManagerBase.md#registeroperatortoavs)
* See [`BLSApkRegistry.registerOperator`](./registries/BLSApkRegistry.md#registeroperator)
* See [`StakeRegistry.registerOperator`](./registries/StakeRegistry.md#registeroperator)
* See [`IndexRegistry.registerOperator`](./registries/IndexRegistry.md#registeroperator)
Expand All @@ -83,6 +88,7 @@ If the Operator was not currently registered for any quorums, this method will r
function registerOperatorWithChurn(
bytes calldata quorumNumbers,
string calldata socket,
IBLSApkRegistry.PubkeyRegistrationParams calldata params,
OperatorKickParam[] calldata operatorKickParams,
SignatureWithSaltAndExpiry memory churnApproverSignature,
SignatureWithSaltAndExpiry memory operatorSignature
Expand Down Expand Up @@ -121,7 +127,9 @@ function deregisterOperator(
Allows an Operator to deregister themselves from one or more quorums.

*Effects*:
* If the Operator is no longer registered for any quorums, updates their status to `DEREGISTERED`
* If the Operator is no longer registered for any quorums:
* Updates their status to `DEREGISTERED`
* Deregisters them in the core contracts (see [`ServiceManagerBase.deregisterOperatorFromAVS`](./ServiceManagerBase.md#deregisteroperatorfromavs))
* Removes the new quorums from the Operator's current registered quorums, and updates the Operator's bitmap history
* See [`BLSApkRegistry.deregisterOperator`](./registries/BLSApkRegistry.md#deregisteroperator)
* See [`StakeRegistry.deregisterOperator`](./registries/StakeRegistry.md#deregisteroperator)
Expand All @@ -133,6 +141,7 @@ Allows an Operator to deregister themselves from one or more quorums.
* `quorumNumbers` MUST be an ordered array of quorum numbers, with no entry exceeding the current `quorumCount`
* `quorumNumbers` MUST contain at least one valid quorum
* `quorumNumbers` MUST ONLY contain bits that are also set in the Operator's current registered quorum bitmap
* See [`ServiceManagerBase.deregisterOperatorFromAVS`](./ServiceManagerBase.md#deregisteroperatorfromavs)
* See [`BLSApkRegistry.deregisterOperator`](./registries/BLSApkRegistry.md#deregisteroperator)
* See [`StakeRegistry.deregisterOperator`](./registries/StakeRegistry.md#deregisteroperator)
* See [`IndexRegistry.deregisterOperator`](./registries/IndexRegistry.md#deregisteroperator)
Expand Down
Loading

0 comments on commit ebe657e

Please sign in to comment.