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

Filter empty quorums when confirming batch #581

Merged
merged 3 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
137 changes: 100 additions & 37 deletions core/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"math/big"
"slices"
"sort"

"github.com/Layr-Labs/eigensdk-go/logging"
Expand All @@ -32,13 +33,30 @@ type SigningMessage struct {
Err error
}

// SignatureAggregation contains the results of aggregating signatures from a set of operators
// 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]*G1Point
// SignersAggPubKey is the aggregated public key for all of the operators that signed the message by each quorum
SignersAggPubKey map[QuorumID]*G2Point
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why pubkey is G2 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It aggregates operator's PubKeyG2

// AggSignature is the aggregated signature for all of the operators that signed the message for each quorum, mirroring the
// SignersAggPubKey.
AggSignature map[QuorumID]*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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not per quorum, should it be left at in SignatureAggregation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's a bit awkward because it's the only field not divided into quorums. But it's easier to track this map at the stage of receiving signatures. I added a comment in the struct to clarify what this field is

}

// 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 []*G1Point
// QuorumAggPubKeys contains the aggregated public keys for all of the operators each quorum,
// Including those that did not sign
QuorumAggPubKeys []*G1Point
QuorumAggPubKeys map[QuorumID]*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
Expand All @@ -48,16 +66,15 @@ type SignatureAggregation struct {
AggSignature *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
}

// 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 {

// AggregateSignatures blocks until it receives a response for each operator in the operator state via messageChan, and then returns the aggregated signature.
// 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 *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, state *IndexedOperatorState, quorumIDs []QuorumID, message [32]byte, messageChan chan SigningMessage) (*SignatureAggregation, error)
AggregateSignatures(ctx context.Context, ics IndexedChainState, referenceBlockNumber uint, quorumAttestation *QuorumAttestation, quorumIDs []QuorumID) (*SignatureAggregation, error)
}

type StdSignatureAggregator struct {
Expand All @@ -82,8 +99,12 @@ func NewStdSignatureAggregator(logger logging.Logger, transactor Transactor) (*S

var _ SignatureAggregator = (*StdSignatureAggregator)(nil)

func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state *IndexedOperatorState, quorumIDs []QuorumID, message [32]byte, messageChan chan SigningMessage) (*SignatureAggregation, error) {
// TODO: Add logging
func (a *StdSignatureAggregator) ReceiveSignatures(ctx context.Context, state *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")
Expand All @@ -97,13 +118,12 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
}
}

stakeSigned := make([]*big.Int, len(quorumIDs))
for ind := range quorumIDs {
stakeSigned[ind] = big.NewInt(0)
stakeSigned := make(map[QuorumID]*big.Int, len(quorumIDs))
for _, quorumID := range quorumIDs {
stakeSigned[quorumID] = big.NewInt(0)
}
aggSigs := make([]*Signature, len(quorumIDs))
aggPubKeys := make([]*G2Point, len(quorumIDs))

aggSigs := make(map[QuorumID]*Signature, len(quorumIDs))
aggPubKeys := make(map[QuorumID]*G2Point, len(quorumIDs))
signerMap := make(map[OperatorID]bool)

// Aggregate Signatures
Expand Down Expand Up @@ -151,7 +171,7 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
}

operatorQuorums := make([]uint8, 0, len(quorumIDs))
for ind, quorumID := range quorumIDs {
for _, quorumID := range quorumIDs {
// Get stake amounts for operator
ops := state.Operators[quorumID]
opInfo, ok := ops[r.Operator]
Expand All @@ -164,15 +184,15 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
signerMap[r.Operator] = true

// Add to stake signed
stakeSigned[ind].Add(stakeSigned[ind], opInfo.Stake)
stakeSigned[quorumID].Add(stakeSigned[quorumID], opInfo.Stake)

// Add to agg signature
if aggSigs[ind] == nil {
aggSigs[ind] = &Signature{sig.Clone()}
aggPubKeys[ind] = op.PubkeyG2.Clone()
if aggSigs[quorumID] == nil {
aggSigs[quorumID] = &Signature{sig.Clone()}
aggPubKeys[quorumID] = op.PubkeyG2.Clone()
} else {
aggSigs[ind].Add(sig.G1Point)
aggPubKeys[ind].Add(op.PubkeyG2)
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)
Expand All @@ -190,22 +210,22 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
}
}

quorumAggPubKeys := make([]*G1Point, len(quorumIDs))
quorumAggPubKeys := make(map[QuorumID]*G1Point, len(quorumIDs))

// Validate the amount signed and aggregate signatures for each quorum
quorumResults := make(map[QuorumID]*QuorumResult)

for ind, quorumID := range quorumIDs {
for _, quorumID := range quorumIDs {
// Check that quorum has sufficient stake
percent := GetSignedPercentage(state.OperatorState, quorumID, stakeSigned[ind])
percent := GetSignedPercentage(state.OperatorState, quorumID, stakeSigned[quorumID])
quorumResults[quorumID] = &QuorumResult{
QuorumID: quorumID,
PercentSigned: percent,
}

// 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[ind] = quorumAggKey
quorumAggPubKeys[quorumID] = quorumAggKey

signersAggKey := quorumAggKey.Clone()
for opInd, nsk := range nonSignerKeys {
Expand All @@ -215,11 +235,11 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
}
}

if aggPubKeys[ind] == nil {
if aggPubKeys[quorumID] == nil {
return nil, ErrAggSigNotValid
}

ok, err := signersAggKey.VerifyEquivalence(aggPubKeys[ind])
ok, err := signersAggKey.VerifyEquivalence(aggPubKeys[quorumID])
if err != nil {
return nil, err
}
Expand All @@ -228,20 +248,54 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
}

// Verify the aggregated signature for the quorum
ok = aggSigs[ind].Verify(aggPubKeys[ind], message)
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 IndexedChainState, referenceBlockNumber uint, quorumAttestation *QuorumAttestation, quorumIDs []QuorumID) (*SignatureAggregation, error) {
// Aggregate the aggregated signatures. We reuse the first aggregated signature as the accumulator
for i := 1; i < len(aggSigs); i++ {
aggSigs[0].Add(aggSigs[i].G1Point)
var aggSig *Signature
for _, quorumID := range quorumIDs {
sig := quorumAttestation.AggSignature[quorumID]
if aggSig == nil {
aggSig = &Signature{sig.G1Point.Clone()}
} else {
aggSig.Add(sig.G1Point)
}
}

// Aggregate the aggregated public keys. We reuse the first aggregated public key as the accumulator
for i := 1; i < len(aggPubKeys); i++ {
aggPubKeys[0].Add(aggPubKeys[i])
var aggPubKey *G2Point
for _, quorumID := range quorumIDs {
apk := quorumAttestation.SignersAggPubKey[quorumID]
if aggPubKey == nil {
aggPubKey = apk.Clone()
} else {
aggPubKey.Add(apk)
}
}

nonSignerKeys := make([]*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
Expand All @@ -253,13 +307,22 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, state
return bytes.Compare(hash1[:], hash2[:]) == -1
})

quorumAggKeys := make(map[QuorumID]*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: quorumAggPubKeys,
AggPubKey: aggPubKeys[0],
AggSignature: aggSigs[0],
QuorumAggPubKeys: quorumAggKeys,
AggPubKey: aggPubKey,
AggSignature: aggSig,
QuorumResults: quorumResults,
SignerMap: signerMap,
}, nil

}
Expand Down
Loading
Loading