-
Notifications
You must be signed in to change notification settings - Fork 169
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
Core/v2 #806
Open
mooselumph
wants to merge
4
commits into
Layr-Labs:master
Choose a base branch
from
mooselumph:core/v2
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,788
−30
Open
Core/v2 #806
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,366 @@ | ||
package corev2 | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"math/big" | ||
"slices" | ||
"sort" | ||
|
||
"github.com/Layr-Labs/eigenda/chainio" | ||
"github.com/Layr-Labs/eigenda/crypto/ecc/bn254" | ||
"github.com/Layr-Labs/eigensdk-go/logging" | ||
gethcommon "github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
lru "github.com/hashicorp/golang-lru/v2" | ||
) | ||
|
||
const percentMultiplier = 100 | ||
|
||
const maxNumOperatorAddresses = 300 | ||
|
||
var ( | ||
ErrPubKeysNotEqual = errors.New("public keys are not equal") | ||
ErrInsufficientEthSigs = errors.New("insufficient eth signatures") | ||
ErrAggPubKeyNotValid = errors.New("aggregated public key is not valid") | ||
ErrAggSigNotValid = errors.New("aggregated signature is not valid") | ||
) | ||
|
||
type SigningMessage struct { | ||
Signature *bn254.Signature | ||
Operator OperatorID | ||
BatchHeaderHash [32]byte | ||
// Undefined if this value <= 0. | ||
AttestationLatencyMs float64 | ||
Err error | ||
} | ||
|
||
// QuorumResult contains the quorum ID and the amount signed for the quorum | ||
type QuorumResult struct { | ||
QuorumID QuorumID | ||
// PercentSigned is percentage of the total stake for the quorum that signed for a particular batch. | ||
PercentSigned uint8 | ||
} | ||
|
||
// QuorumAttestation contains the results of aggregating signatures from a set of operators by quorums | ||
// It also returns map of all signers across all quorums | ||
type QuorumAttestation struct { | ||
// QuorumAggPubKeys contains the aggregated public keys for all of the operators each quorum, | ||
// including those that did not sign | ||
QuorumAggPubKey map[QuorumID]*bn254.G1Point | ||
// SignersAggPubKey is the aggregated public key for all of the operators that signed the message by each quorum | ||
SignersAggPubKey map[QuorumID]*bn254.G2Point | ||
// AggSignature is the aggregated signature for all of the operators that signed the message for each quorum, mirroring the | ||
// SignersAggPubKey. | ||
AggSignature map[QuorumID]*bn254.Signature | ||
// QuorumResults contains the quorum ID and the amount signed for each quorum | ||
QuorumResults map[QuorumID]*QuorumResult | ||
// SignerMap contains the operator IDs that signed the message | ||
SignerMap map[OperatorID]bool | ||
} | ||
|
||
// SignatureAggregation contains the results of aggregating signatures from a set of operators across multiple quorums | ||
type SignatureAggregation struct { | ||
// NonSigners contains the public keys of the operators that did not sign the message | ||
NonSigners []*bn254.G1Point | ||
// QuorumAggPubKeys contains the aggregated public keys for all of the operators each quorum, | ||
// Including those that did not sign | ||
QuorumAggPubKeys map[QuorumID]*bn254.G1Point | ||
// AggPubKey is the aggregated public key for all of the operators that signed the message, | ||
// further aggregated across the quorums; operators signing for multiple quorums will be included in | ||
// the aggregation multiple times | ||
AggPubKey *bn254.G2Point | ||
// AggSignature is the aggregated signature for all of the operators that signed the message, mirroring the | ||
// AggPubKey. | ||
AggSignature *bn254.Signature | ||
// QuorumResults contains the quorum ID and the amount signed for each quorum | ||
QuorumResults map[QuorumID]*QuorumResult | ||
} | ||
|
||
// SignatureAggregator is an interface for aggregating the signatures returned by DA nodes so that they can be verified by the DA contract | ||
type SignatureAggregator interface { | ||
// ReceiveSignatures blocks until it receives a response for each operator in the operator state via messageChan, and then returns the attestation result by quorum. | ||
ReceiveSignatures(ctx context.Context, state *chainio.IndexedOperatorState, message [32]byte, messageChan chan SigningMessage) (*QuorumAttestation, error) | ||
// AggregateSignatures takes attestation result by quorum and aggregates the signatures across them. | ||
// If the aggregated signature is invalid, an error is returned. | ||
AggregateSignatures(ctx context.Context, ics chainio.IndexedChainState, referenceBlockNumber uint, quorumAttestation *QuorumAttestation, quorumIDs []QuorumID) (*SignatureAggregation, error) | ||
} | ||
|
||
type StdSignatureAggregator struct { | ||
Logger logging.Logger | ||
ChainReader chainio.Reader | ||
// OperatorAddresses contains the ethereum addresses of the operators corresponding to their operator IDs | ||
OperatorAddresses *lru.Cache[OperatorID, gethcommon.Address] | ||
} | ||
|
||
func NewStdSignatureAggregator(logger logging.Logger, reader chainio.Reader) (*StdSignatureAggregator, error) { | ||
operatorAddrs, err := lru.New[OperatorID, gethcommon.Address](maxNumOperatorAddresses) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &StdSignatureAggregator{ | ||
Logger: logger.With("component", "SignatureAggregator"), | ||
ChainReader: reader, | ||
OperatorAddresses: operatorAddrs, | ||
}, nil | ||
} | ||
|
||
var _ SignatureAggregator = (*StdSignatureAggregator)(nil) | ||
|
||
func (a *StdSignatureAggregator) ReceiveSignatures(ctx context.Context, state *chainio.IndexedOperatorState, message [32]byte, messageChan chan SigningMessage) (*QuorumAttestation, error) { | ||
quorumIDs := make([]QuorumID, 0, len(state.AggKeys)) | ||
for quorumID := range state.Operators { | ||
quorumIDs = append(quorumIDs, quorumID) | ||
} | ||
slices.Sort(quorumIDs) | ||
|
||
if len(quorumIDs) == 0 { | ||
return nil, errors.New("the number of quorums must be greater than zero") | ||
} | ||
|
||
// Ensure all quorums are found in state | ||
for _, id := range quorumIDs { | ||
_, found := state.Operators[id] | ||
if !found { | ||
return nil, errors.New("quorum not found") | ||
} | ||
} | ||
|
||
stakeSigned := make(map[QuorumID]*big.Int, len(quorumIDs)) | ||
for _, quorumID := range quorumIDs { | ||
stakeSigned[quorumID] = big.NewInt(0) | ||
} | ||
aggSigs := make(map[QuorumID]*bn254.Signature, len(quorumIDs)) | ||
aggPubKeys := make(map[QuorumID]*bn254.G2Point, len(quorumIDs)) | ||
signerMap := make(map[OperatorID]bool) | ||
|
||
// Aggregate Signatures | ||
numOperators := len(state.IndexedOperators) | ||
|
||
for numReply := 0; numReply < numOperators; numReply++ { | ||
var err error | ||
r := <-messageChan | ||
operatorIDHex := chainio.GetOperatorHex(r.Operator) | ||
operatorAddr, ok := a.OperatorAddresses.Get(r.Operator) | ||
if !ok && a.ChainReader != nil { | ||
operatorAddr, err = a.ChainReader.OperatorIDToAddress(ctx, r.Operator) | ||
if err != nil { | ||
a.Logger.Error("failed to get operator address from registry", "operatorID", operatorIDHex) | ||
operatorAddr = gethcommon.Address{} | ||
} else { | ||
a.OperatorAddresses.Add(r.Operator, operatorAddr) | ||
} | ||
} else if !ok { | ||
operatorAddr = gethcommon.Address{} | ||
} | ||
|
||
socket := "" | ||
if op, ok := state.IndexedOperators[r.Operator]; ok { | ||
socket = op.Socket | ||
} | ||
batchHeaderHashHex := hex.EncodeToString(r.BatchHeaderHash[:]) | ||
if r.Err != nil { | ||
a.Logger.Warn("error returned from messageChan", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket, "batchHeaderHash", batchHeaderHashHex, "attestationLatencyMs", r.AttestationLatencyMs, "err", r.Err) | ||
continue | ||
} | ||
|
||
op, found := state.IndexedOperators[r.Operator] | ||
if !found { | ||
a.Logger.Error("Operator not found in state", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket) | ||
continue | ||
} | ||
|
||
// Verify Signature | ||
sig := r.Signature | ||
ok = sig.Verify(op.PubkeyG2, message) | ||
if !ok { | ||
a.Logger.Error("signature is not valid", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket, "pubkey", hexutil.Encode(op.PubkeyG2.Serialize())) | ||
continue | ||
} | ||
|
||
operatorQuorums := make([]uint8, 0, len(quorumIDs)) | ||
for _, quorumID := range quorumIDs { | ||
// Get stake amounts for operator | ||
ops := state.Operators[quorumID] | ||
opInfo, ok := ops[r.Operator] | ||
// If operator is not in quorum, skip | ||
if !ok { | ||
continue | ||
} | ||
operatorQuorums = append(operatorQuorums, quorumID) | ||
|
||
signerMap[r.Operator] = true | ||
|
||
// Add to stake signed | ||
stakeSigned[quorumID].Add(stakeSigned[quorumID], opInfo.Stake) | ||
|
||
// Add to agg signature | ||
if aggSigs[quorumID] == nil { | ||
aggSigs[quorumID] = &bn254.Signature{G1Point: sig.Clone()} | ||
aggPubKeys[quorumID] = op.PubkeyG2.Clone() | ||
} else { | ||
aggSigs[quorumID].Add(sig.G1Point) | ||
aggPubKeys[quorumID].Add(op.PubkeyG2) | ||
} | ||
} | ||
a.Logger.Info("received signature from operator", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket, "quorumIDs", fmt.Sprint(operatorQuorums), "batchHeaderHash", batchHeaderHashHex, "attestationLatencyMs", r.AttestationLatencyMs) | ||
} | ||
|
||
// Aggregate Non signer Pubkey Id | ||
nonSignerKeys := make([]*bn254.G1Point, 0) | ||
nonSignerOperatorIds := make([]OperatorID, 0) | ||
|
||
for id, op := range state.IndexedOperators { | ||
_, found := signerMap[id] | ||
if !found { | ||
nonSignerKeys = append(nonSignerKeys, op.PubkeyG1) | ||
nonSignerOperatorIds = append(nonSignerOperatorIds, id) | ||
} | ||
} | ||
|
||
quorumAggPubKeys := make(map[QuorumID]*bn254.G1Point, len(quorumIDs)) | ||
|
||
// Validate the amount signed and aggregate signatures for each quorum | ||
quorumResults := make(map[QuorumID]*QuorumResult) | ||
|
||
for _, quorumID := range quorumIDs { | ||
// Check that quorum has sufficient stake | ||
percent := GetSignedPercentage(state.OperatorState, quorumID, stakeSigned[quorumID]) | ||
quorumResults[quorumID] = &QuorumResult{ | ||
QuorumID: quorumID, | ||
PercentSigned: percent, | ||
} | ||
|
||
if percent == 0 { | ||
a.Logger.Warn("no stake signed for quorum", "quorumID", quorumID) | ||
continue | ||
} | ||
|
||
// Verify that the aggregated public key for the quorum matches the on-chain quorum aggregate public key sans non-signers of the quorum | ||
quorumAggKey := state.AggKeys[quorumID] | ||
quorumAggPubKeys[quorumID] = quorumAggKey | ||
|
||
signersAggKey := quorumAggKey.Clone() | ||
for opInd, nsk := range nonSignerKeys { | ||
ops := state.Operators[quorumID] | ||
if _, ok := ops[nonSignerOperatorIds[opInd]]; ok { | ||
signersAggKey.Sub(nsk) | ||
} | ||
} | ||
|
||
if aggPubKeys[quorumID] == nil { | ||
return nil, ErrAggPubKeyNotValid | ||
} | ||
|
||
ok, err := signersAggKey.VerifyEquivalence(aggPubKeys[quorumID]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !ok { | ||
return nil, ErrPubKeysNotEqual | ||
} | ||
|
||
// Verify the aggregated signature for the quorum | ||
ok = aggSigs[quorumID].Verify(aggPubKeys[quorumID], message) | ||
if !ok { | ||
return nil, ErrAggSigNotValid | ||
} | ||
} | ||
|
||
return &QuorumAttestation{ | ||
QuorumAggPubKey: quorumAggPubKeys, | ||
SignersAggPubKey: aggPubKeys, | ||
AggSignature: aggSigs, | ||
QuorumResults: quorumResults, | ||
SignerMap: signerMap, | ||
}, nil | ||
} | ||
|
||
func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, ics chainio.IndexedChainState, referenceBlockNumber uint, quorumAttestation *QuorumAttestation, quorumIDs []QuorumID) (*SignatureAggregation, error) { | ||
// Aggregate the aggregated signatures. We reuse the first aggregated signature as the accumulator | ||
var aggSig *bn254.Signature | ||
for _, quorumID := range quorumIDs { | ||
sig := quorumAttestation.AggSignature[quorumID] | ||
if aggSig == nil { | ||
aggSig = &bn254.Signature{G1Point: sig.G1Point.Clone()} | ||
} else { | ||
aggSig.Add(sig.G1Point) | ||
} | ||
} | ||
|
||
// Aggregate the aggregated public keys. We reuse the first aggregated public key as the accumulator | ||
var aggPubKey *bn254.G2Point | ||
for _, quorumID := range quorumIDs { | ||
apk := quorumAttestation.SignersAggPubKey[quorumID] | ||
if aggPubKey == nil { | ||
aggPubKey = apk.Clone() | ||
} else { | ||
aggPubKey.Add(apk) | ||
} | ||
} | ||
|
||
nonSignerKeys := make([]*bn254.G1Point, 0) | ||
indexedOperatorState, err := ics.GetIndexedOperatorState(ctx, referenceBlockNumber, quorumIDs) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for id, op := range indexedOperatorState.IndexedOperators { | ||
_, found := quorumAttestation.SignerMap[id] | ||
if !found { | ||
nonSignerKeys = append(nonSignerKeys, op.PubkeyG1) | ||
} | ||
} | ||
|
||
// sort non signer keys according to how it's checked onchain | ||
// ref: https://github.com/Layr-Labs/eigenlayer-middleware/blob/m2-mainnet/src/BLSSignatureChecker.sol#L99 | ||
sort.Slice(nonSignerKeys, func(i, j int) bool { | ||
hash1 := nonSignerKeys[i].Hash() | ||
hash2 := nonSignerKeys[j].Hash() | ||
// sort in accending order | ||
return bytes.Compare(hash1[:], hash2[:]) == -1 | ||
}) | ||
|
||
quorumAggKeys := make(map[QuorumID]*bn254.G1Point, len(quorumIDs)) | ||
for _, quorumID := range quorumIDs { | ||
quorumAggKeys[quorumID] = quorumAttestation.QuorumAggPubKey[quorumID] | ||
} | ||
|
||
quorumResults := make(map[QuorumID]*QuorumResult, len(quorumIDs)) | ||
for _, quorumID := range quorumIDs { | ||
quorumResults[quorumID] = quorumAttestation.QuorumResults[quorumID] | ||
} | ||
|
||
return &SignatureAggregation{ | ||
NonSigners: nonSignerKeys, | ||
QuorumAggPubKeys: quorumAggKeys, | ||
AggPubKey: aggPubKey, | ||
AggSignature: aggSig, | ||
QuorumResults: quorumResults, | ||
}, nil | ||
|
||
} | ||
|
||
func GetStakeThreshold(state *chainio.OperatorState, quorum QuorumID, quorumThreshold uint8) *big.Int { | ||
|
||
// Get stake threshold | ||
quorumThresholdBig := new(big.Int).SetUint64(uint64(quorumThreshold)) | ||
stakeThreshold := new(big.Int) | ||
stakeThreshold.Mul(quorumThresholdBig, state.Totals[quorum].Stake) | ||
stakeThreshold = RoundUpDivideBig(stakeThreshold, new(big.Int).SetUint64(percentMultiplier)) | ||
|
||
return stakeThreshold | ||
} | ||
|
||
func GetSignedPercentage(state *chainio.OperatorState, quorum QuorumID, stakeAmount *big.Int) uint8 { | ||
|
||
stakeAmount = stakeAmount.Mul(stakeAmount, new(big.Int).SetUint64(percentMultiplier)) | ||
quorumThresholdBig := stakeAmount.Div(stakeAmount, state.Totals[quorum].Stake) | ||
|
||
quorumThreshold := uint8(quorumThresholdBig.Uint64()) | ||
|
||
return quorumThreshold | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
plural versus singular:
QuorumAggPubKeys
?I'm also thinking if there's better ways to arrange these structs (
QuorumResult
,QuorumAttestation
, andSignatureAggregation
). It probably doesn't make material differences though