-
Notifications
You must be signed in to change notification settings - Fork 103
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
feat: experimental ecdsa registries #123
Conversation
comments are in a poor state no interface or separated storage definitions eliminated historical storage behavior is otherwise changed ~minimally from BLS version
basically combines the RegistryCoordinator, IndexRegistry, and APK registry functionality removes historical storage comments and documentation are definitely incorrect
again no interface or historical storage comments are definitely wrong signatory record hash may no longer be useful implementation might have critical safety errors this just queries the current stakes and adds together signing stakes
classic i <> j mix up, ya know? there could definitely be more of these lurking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like an awesome first start, thanks for this.
One big area of complexity that would be nice to get rid of is the churner, but unsure if there are alternatives that have been considered that could work better with this simpler registry?
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service | ||
* @notice This is the contract for checking the validity of aggregate operator signatures. | ||
*/ | ||
contract BLSSignatureChecker { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update contract name. also ctrl-f "aggregate" and "bls" and remove any reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this would need a lot of cleanup w/r/t naming and comments, in particular
/// @notice the ServiceManager for this AVS, which forwards calls onto EigenLayer's core contracts | ||
IServiceManager public immutable serviceManager; | ||
/// @notice the Stake Registry contract that will keep track of operators' stakes | ||
IStakeRegistry public immutable stakeRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ECDSAStakeRegistry does not implement IStakeRegistry. Should this be ECDSAStakeRegistry instead of IStakeRegistry? Or should ECDSAStakeRegistry implement IStakeRegistry?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was super liberal with removing inheritance for the sake of expediency here.
It's very possible the integration between these contracts straight-up doesn't work rn, but it would be pretty quick to check if the ECDSAStakeRegistry implements all the functions we're calling from the IStakeRegistry interface (I think it does?)
If you want this developed further, then the path forward isn't immediately clear to me.
Perhaps making a new IECDSAStakeRegistry
interface or something would make sense, or these changes could inform reducing the scope of the IStakeRegistry
interface so both can fit into it, and then the more specific IECDSAStakeRegistry
and IBLSStakeRegistry
interfaces (if these need to exist at all!) can both inherit from this minimal interface.
* @return quorumStakeTotals is the struct containing the total and signed stake for each quorum | ||
* @return signatoryRecordHash is the hash of the signatory record, which is used for fraud proofs | ||
*/ | ||
function checkSignatures( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementing the ecdsa version of inc-sq using these contracts and just realized this doesn't have a referenceBlockNumber
like the bls version. Does this mean that the ecdsa stakeregistry doesn't track weight histories and can only validate against current weights?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this quick + hacked-together version I completely removed histories, so yes, you are exactly correct
function checkSignatures( | ||
bytes32 msgHash, | ||
bytes calldata quorumNumbers, | ||
bytes32[] memory signerIds, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we be calling signerIds
signerAddresses
instead?
They are verified in
checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signature)
and passed as the signer (an address)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine by me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also realizing we might need an ECDSAOperatorStateRetriever?
yes, this would also likely be a practical or even necessary addition. |
// the id of the operator, which is likely the keccak256 hash of the operator's public key if using BLSRegistry | ||
bytes32 operatorId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still a bit confused as to whether this should be stored as a bytes32 or an address.
which is likely the keccak256 hash of the operator's public key if using BLSRegistry
shows that this was copied from the BLSRegistry. Pretty convinced this should be address here right? (hash of ecdsa pubkey IS the ecdsa address by defn)
} | ||
|
||
// check the operator's signature | ||
EIP1271SignatureUtils.checkSignature_EIP1271(address(uint160(uint256(signerIds[i]))), msgHash, signatures[i]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
related to my previous comments, if we store the operatorId as operatorAddr instead (literally type address
instead of bytes32
), we don't need to do this conversion here (address(uint160(uint256))
). Afaik there's no other use for storing the operatorId as a bytes32 instead of an address?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed it in my branch and everything checked out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for bombarding you @ChaoticWalrus with my every passing thoughts :P
Seems like the main problem I'm facing right now is resulting from the combination of:
- no "epoch" history (only the current state of oeprators is stored in contracts)
- no "epoch" timeline - aka permissionless entry/exit, at any moment
So any avs would be totally broken if they start aggregating signatures offchain (think eigenDA) and then some operator joins/leaves.
This is related to the new avs-sync proposal I've done for churning operators. Basically there seems to be an epoch spectrum:
"fluid" (reactive) epochs -------------------------> hard epochs (after P&S)
current eigenDA bft chain with epoch-based consensus
If we are moving to a hard-epoch based system after P&S, feels like eigenDA will also need to follow suit and adapt (but maybe not?). I was thinking new operators joining could just be added to the next epoch's list, and we change the list pointer (similar to doing a green-blue deployment) at the epoch boundary... but maybe this doesn't work for smart contracts as it would require another tx for all the operators?
Would like some help thinking through this in more detail whenever you have time.
* @title A `RegistryCoordinator` that has three registries: | ||
* 1) a `StakeRegistry` that keeps track of operators' stakes | ||
* 2) a `BLSApkRegistry` that keeps track of operators' BLS public keys and aggregate BLS public keys for each quorum | ||
* 3) an `IndexRegistry` that keeps track of an ordered list of operators for each quorum |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was trying to add an OperatorStateRetriever for ecdsa inc-sq and realized its no longer possible (without the indexRegistry) to get a list of operators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahahahah 😅
OK guess I went a little too hard on removing things.
good to point out the need for this (or equivalent)
but if we are doing a "historyless" implementation then we could likely have a much-more-minimal version of the IndexRegistry
or you could just say "this is designed for users to have to index the operators off-chain" and the equivalent OperatorStateRetriever would require you to input specific operator addresses
You have a ton of ideas here; agreed we should chat about them.
Why do you think this? If you submit a bunch of ECDSA signatures at once and an operator isn't registered, just add 0 to the total "stake/weight" here (or otherwise skip some calculation steps). If an operator newly registered and you don't have their signature...there's no issue I can see at all. |
wdym by "just add 0"? where would this be? in the contract? or in the aggregator offchain code? |
ahhhh I missed this latter point but you're totally right. But TBH I think these issues might be acceptable -- just gather 60% of the signatures instead of 50% or w/e and the probability that your tx reverts is tiny |
I implemented an ecdsa aggregation service in services/ecdsa/aggregation/agg.go and started wondering whether there's a way to do ecdsa verification without having a centralized aggregator, aka every operator just signs and sends their signature to the contract. Seems like this would require more gas because each time a new signature comes the contract needs to calculate and store the current stake, until it meets threshold. With update histories like I used in that PR (which uses the ecdsa-with-history-poc branch) the contract would also need to be passed for each validator its update history indices to fetch its correct stake, and we'd have to make sure all operators are using the same referenceBlockNumber. Just curious in general what are people's thoughts around this, since aggregating in the smart contract seems like an easy way to get rid of the centralized aggregator role (wihout having to deal with a p2p network and leader aggregating - what happens if a task is not finished aggregating and then leader changes, etc). I think gnosis safes also work with a centralized aggregator though? |
Technically Gnosis Safes support both, but their UI uses a centralized aggregator (at least by default). |
I think this was a useful exercise but am not seeing taking this particular branch/PR further, at least for months+. |
Extremely rough draft contracts that I threw together in ~90 minutes.
Do NOT use these contracts without some kind of review.
Comments in the contracts are definitely a bit of a mess -- see the commit messages for a brief summary of work here.