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

Samlaf/bls aggregation service #24

Merged
merged 12 commits into from
Oct 17, 2023
4 changes: 2 additions & 2 deletions crypto/bls/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (a *StdSignatureAggregator) AggregateSignatures(
aggSigs[ind] = &Signature{sig.Deserialize(sig.Serialize())}
aggPubKeys[ind] = op.PubkeyG2.Deserialize(op.PubkeyG2.Serialize())
} else {
aggSigs[ind].Add(sig.G1Point)
aggSigs[ind].Add(sig)
shrimalmadhur marked this conversation as resolved.
Show resolved Hide resolved
aggPubKeys[ind].Add(op.PubkeyG2)
}

Expand Down Expand Up @@ -203,7 +203,7 @@ func (a *StdSignatureAggregator) AggregateSignatures(

// 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)
aggSigs[0].Add(aggSigs[i])
}

// Aggregate the aggregated public keys. We reuse the first aggregated public key as the accumulator
Expand Down
29 changes: 25 additions & 4 deletions crypto/bls/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,20 @@ func NewG1Point(x, y *big.Int) *G1Point {
}
}

func NewZeroG1Point() *G1Point {
return NewG1Point(big.NewInt(0), big.NewInt(0))
}

// Add another G1 point to this one
func (p *G1Point) Add(p2 *G1Point) {
func (p *G1Point) Add(p2 *G1Point) *G1Point {
shrimalmadhur marked this conversation as resolved.
Show resolved Hide resolved
p.G1Affine.Add(p.G1Affine, p2.G1Affine)
return p
}

// Sub another G1 point from this one
func (p *G1Point) Sub(p2 *G1Point) {
func (p *G1Point) Sub(p2 *G1Point) *G1Point {
p.G1Affine.Sub(p.G1Affine, p2.G1Affine)
return p
}

// VerifyEquivalence verifies G1Point is equivalent the G2Point
Expand Down Expand Up @@ -90,14 +96,20 @@ func NewG2Point(X, Y [2]*big.Int) *G2Point {
}
}

func NewZeroG2Point() *G2Point {
return NewG2Point([2]*big.Int{big.NewInt(0), big.NewInt(0)}, [2]*big.Int{big.NewInt(0), big.NewInt(0)})
}

// Add another G2 point to this one
func (p *G2Point) Add(p2 *G2Point) {
func (p *G2Point) Add(p2 *G2Point) *G2Point {
p.G2Affine.Add(p.G2Affine, p2.G2Affine)
return p
}

// Sub another G2 point from this one
func (p *G2Point) Sub(p2 *G2Point) {
func (p *G2Point) Sub(p2 *G2Point) *G2Point {
p.G2Affine.Sub(p.G2Affine, p2.G2Affine)
return p
}

func (p *G2Point) Serialize() []byte {
Expand All @@ -112,6 +124,15 @@ type Signature struct {
*G1Point `json:"g1_point"`
}

func NewZeroSignature() *Signature {
return &Signature{NewZeroG1Point()}
}

func (s *Signature) Add(otherS *Signature) *Signature {
s.G1Point.Add(otherS.G1Point)
samlaf marked this conversation as resolved.
Show resolved Hide resolved
return s
}

// Verify a message against a public key
func (s *Signature) Verify(pubkey *G2Point, message [32]byte) (bool, error) {
ok, err := bn254utils.VerifySig(s.G1Affine, pubkey.G2Affine, message)
Expand Down
11 changes: 8 additions & 3 deletions services/avsregistry/avsregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package avsregistry
import (
"context"

"github.com/Layr-Labs/eigensdk-go/crypto/bls"
blsoperatorstateretrievar "github.com/Layr-Labs/eigensdk-go/contracts/bindings/BLSOperatorStateRetriever"
"github.com/Layr-Labs/eigensdk-go/types"
)

Expand All @@ -12,6 +12,11 @@ import (
type AvsRegistryService interface {
// GetOperatorsAvsState returns the state of an avs wrt to a list of quorums at a certain block.
// The state includes the operatorId, pubkey, and staking amount in each quorum.
GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNumber) (map[types.OperatorId]types.OperatorAvsState, map[types.QuorumNum]*bls.G1Point, error)
GetOperatorPubkeys(ctx context.Context, operatorId types.OperatorId) (types.OperatorPubkeys, error)
GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.OperatorId]types.OperatorAvsState, error)
// GetQuorumsAvsStateAtBlock returns the aggregated data for a list of quorums at a certain block.
// The aggregated data includes the aggregated pubkey and total stake in each quorum.
// This information is derivable from the Operators Avs State (returned from GetOperatorsAvsStateAtBlock), but this function is provided for convenience.
GetQuorumsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.QuorumNum]types.QuorumAvsState, error)
// GetCheckSignaturesIndices returns the registry indices of the nonsigner operators specified by nonSignerOperatorIds who were registered at referenceBlockNumber.
GetCheckSignaturesIndices(ctx context.Context, referenceBlockNumber types.BlockNum, quorumNumbers []types.QuorumNum, nonSignerOperatorIds []types.OperatorId) (blsoperatorstateretrievar.BLSOperatorStateRetrieverCheckSignaturesIndices, error)
}
60 changes: 39 additions & 21 deletions services/avsregistry/avsregistry_chaincaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"github.com/Layr-Labs/eigensdk-go/types"
)

// AvsRegistryService is a wrapper around AvsRegistryReader that transforms the data into
samlaf marked this conversation as resolved.
Show resolved Hide resolved
// nicer golang types that are easier to work with
type AvsRegistryServiceChainCaller struct {
avsregistry.AvsRegistryReader
elReader elcontracts.ELReader
avsRegistryReader avsregistry.AvsRegistryReader
pubkeyCompendiumService pcservice.PubkeyCompendiumService
logger logging.Logger
}
Expand All @@ -24,19 +26,19 @@ var _ AvsRegistryService = (*AvsRegistryServiceChainCaller)(nil)
func NewAvsRegistryServiceChainCaller(avsRegistryReader avsregistry.AvsRegistryReader, elReader elcontracts.ELReader, pubkeyCompendiumService pcservice.PubkeyCompendiumService, logger logging.Logger) *AvsRegistryServiceChainCaller {
return &AvsRegistryServiceChainCaller{
elReader: elReader,
avsRegistryReader: avsRegistryReader,
AvsRegistryReader: avsRegistryReader,
pubkeyCompendiumService: pubkeyCompendiumService,
logger: logger,
}
}

func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNumber) (map[types.OperatorId]types.OperatorAvsState, map[types.QuorumNum]*bls.G1Point, error) {
func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.OperatorId]types.OperatorAvsState, error) {
operatorsAvsState := make(map[types.OperatorId]types.OperatorAvsState)
// Get operator state for each quorum by querying BLSOperatorStateRetriever (this call is why this service implementation is called ChainCaller)
operatorsStakesInQuorums, err := ar.avsRegistryReader.GetOperatorsStakeInQuorumsAtBlock(ctx, quorumNumbers, blockNumber)
operatorsStakesInQuorums, err := ar.AvsRegistryReader.GetOperatorsStakeInQuorumsAtBlock(ctx, quorumNumbers, blockNumber)
if err != nil {
ar.logger.Error("Failed to get operator state", "err", err, "service", "AvsRegistryServiceChainCaller")
return nil, nil, err
return nil, err
}
numquorums := len(quorumNumbers)
if len(operatorsStakesInQuorums) != numquorums {
Expand All @@ -45,10 +47,10 @@ func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context

for quorumIdx, quorumNum := range quorumNumbers {
for _, operator := range operatorsStakesInQuorums[quorumIdx] {
pubkeys, err := ar.GetOperatorPubkeys(ctx, operator.OperatorId)
pubkeys, err := ar.getOperatorPubkeys(ctx, operator.OperatorId)
if err != nil {
ar.logger.Error("Failed find pubkeys for operator while building operatorsAvsState", "err", err, "service", "AvsRegistryServiceChainCaller")
return nil, nil, err
return nil, err
}
if operatorAvsState, ok := operatorsAvsState[operator.OperatorId]; ok {
operatorAvsState.StakePerQuorum[quorumNum] = operator.Stake
Expand All @@ -66,28 +68,44 @@ func (ar *AvsRegistryServiceChainCaller) GetOperatorsAvsStateAtBlock(ctx context
}
}

aggG1PubkeyPerQuorum := make(map[types.QuorumNum]*bls.G1Point)
return operatorsAvsState, nil
}

func (ar *AvsRegistryServiceChainCaller) GetQuorumsAvsStateAtBlock(ctx context.Context, quorumNumbers []types.QuorumNum, blockNumber types.BlockNum) (map[types.QuorumNum]types.QuorumAvsState, error) {
operatorsAvsState, err := ar.GetOperatorsAvsStateAtBlock(ctx, quorumNumbers, blockNumber)
if err != nil {
ar.logger.Error("Failed to get operator state", "err", err, "service", "AvsRegistryServiceChainCaller")
return nil, err
}
quorumsAvsState := make(map[types.QuorumNum]types.QuorumAvsState)
for _, quorumNum := range quorumNumbers {
aggG1Pubkey := bls.NewG1Point(big.NewInt(0), big.NewInt(0))
aggPubkeyG1 := bls.NewG1Point(big.NewInt(0), big.NewInt(0))
totalStake := big.NewInt(0)
for _, operator := range operatorsAvsState {
// only include operators that have a stake in this quorum
if operator.StakePerQuorum != nil {
aggG1Pubkey.Add(operator.Pubkeys.G1Pubkey)
if stake, ok := operator.StakePerQuorum[quorumNum]; ok {
aggPubkeyG1.Add(operator.Pubkeys.G1Pubkey)
totalStake.Add(totalStake, stake)
}
}
aggG1PubkeyPerQuorum[quorumNum] = aggG1Pubkey
quorumsAvsState[quorumNum] = types.QuorumAvsState{
QuorumNumber: quorumNum,
AggPubkeyG1: aggPubkeyG1,
TotalStake: totalStake,
BlockNumber: blockNumber,
}
}
return operatorsAvsState, aggG1PubkeyPerQuorum, nil
return quorumsAvsState, nil
}

func (ar *AvsRegistryServiceChainCaller) GetOperatorPubkeys(ctx context.Context, operatorId types.OperatorId) (types.OperatorPubkeys, error) {
// TODO(samlaf): This is a temporary hack until we implement GetOperatorAddr on the BLSpubkeyregistry contract (shortly)
// we need operatorId -> operatorAddr so that we can query the pubkeyCompendiumService
// this inverse mapping (only operatorAddr->operatorId is stored in registryCoordinator) is not stored,
// but we know that the current implementation uses the hash of the G1 pubkey as the operatorId,
// and the pubkeycompendium contract stores the mapping G1pubkeyHash -> operatorAddr
// When the above PR is merged, we should change this to instead call GetOperatorAddressFromOperatorId on the avsRegistryReader
// and not hardcode the definition of the operatorId here
// getOperatorPubkeys is a temporary hack until we implement GetOperatorAddr on the BLSpubkeyregistry contract
// TODO(samlaf): we need operatorId -> operatorAddr so that we can query the pubkeyCompendiumService
// this inverse mapping (only operatorAddr->operatorId is stored in registryCoordinator) is not stored,
// but we know that the current implementation uses the hash of the G1 pubkey as the operatorId,
// and the pubkeycompendium contract stores the mapping G1pubkeyHash -> operatorAddr
// When the above PR is merged, we should change this to instead call GetOperatorAddressFromOperatorId on the avsRegistryReader
// and not hardcode the definition of the operatorId here
func (ar *AvsRegistryServiceChainCaller) getOperatorPubkeys(ctx context.Context, operatorId types.OperatorId) (types.OperatorPubkeys, error) {
operatorAddr, err := ar.elReader.GetOperatorAddressFromPubkeyHash(ctx, operatorId)
if err != nil {
ar.logger.Error("Failed to get operator address from pubkey hash", "err", err, "service", "AvsRegistryServiceChainCaller")
Expand Down
88 changes: 77 additions & 11 deletions services/avsregistry/avsregistry_chaincaller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type testOperator struct {
pubkeys types.OperatorPubkeys
}

func TestAvsRegistryServiceChainCaller_GetOperatorPubkeys(t *testing.T) {
func TestAvsRegistryServiceChainCaller_getOperatorPubkeys(t *testing.T) {
logger := logging.NewNoopLogger()
testOperator := testOperator{
operatorAddr: common.HexToAddress("0x1"),
Expand Down Expand Up @@ -67,7 +67,7 @@ func TestAvsRegistryServiceChainCaller_GetOperatorPubkeys(t *testing.T) {
service := NewAvsRegistryServiceChainCaller(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium, logger)

// Call the GetOperatorPubkeys method with the test operator address
gotOperatorPubkeys, gotErr := service.GetOperatorPubkeys(context.Background(), tt.queryOperatorId)
gotOperatorPubkeys, gotErr := service.getOperatorPubkeys(context.Background(), tt.queryOperatorId)
if tt.wantErr != gotErr {
t.Fatalf("GetOperatorPubkeys returned wrong error. Got: %v, want: %v.", gotErr, tt.wantErr)
}
Expand All @@ -93,15 +93,14 @@ func TestAvsRegistryServiceChainCaller_GetOperatorsAvsState(t *testing.T) {
name string
mocksInitializationFunc func(*chainiomocks.MockAvsRegistryReader, *chainiomocks.MockELReader, *servicemocks.MockPubkeyCompendiumService)
queryQuorumNumbers []types.QuorumNum
queryBlockNum types.BlockNumber
queryBlockNum types.BlockNum
wantErr error
wantOperatorsAvsStateDict map[types.OperatorId]types.OperatorAvsState
wantAggG1PubkeyPerQuorum map[types.QuorumNum]*bls.G1Point
}{
{
name: "should return operatorsAvsState",
mocksInitializationFunc: func(mockAvsRegistryReader *chainiomocks.MockAvsRegistryReader, mockElReader *chainiomocks.MockELReader, mockPubkeyCompendiumService *servicemocks.MockPubkeyCompendiumService) {
mockAvsRegistryReader.EXPECT().GetOperatorsStakeInQuorumsAtBlock(context.Background(), []types.QuorumNum{1}, types.BlockNumber(1)).Return([][]blsoperatorstateretrievar.BLSOperatorStateRetrieverOperator{
mockAvsRegistryReader.EXPECT().GetOperatorsStakeInQuorumsAtBlock(context.Background(), []types.QuorumNum{1}, types.BlockNum(1)).Return([][]blsoperatorstateretrievar.BLSOperatorStateRetrieverOperator{
{
{
OperatorId: testOperator.operatorId,
Expand All @@ -123,9 +122,6 @@ func TestAvsRegistryServiceChainCaller_GetOperatorsAvsState(t *testing.T) {
BlockNumber: 1,
},
},
wantAggG1PubkeyPerQuorum: map[types.QuorumNum]*bls.G1Point{
1: bls.NewG1Point(big.NewInt(1), big.NewInt(1)),
},
},
}

Expand All @@ -144,15 +140,85 @@ func TestAvsRegistryServiceChainCaller_GetOperatorsAvsState(t *testing.T) {
service := NewAvsRegistryServiceChainCaller(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium, logger)

// Call the GetOperatorPubkeys method with the test operator address
gotOperatorsAvsStateDict, aggG1PubkeyPerQuorum, gotErr := service.GetOperatorsAvsStateAtBlock(context.Background(), tt.queryQuorumNumbers, tt.queryBlockNum)
gotOperatorsAvsStateDict, gotErr := service.GetOperatorsAvsStateAtBlock(context.Background(), tt.queryQuorumNumbers, tt.queryBlockNum)
if tt.wantErr != gotErr {
t.Fatalf("GetOperatorsAvsState returned wrong error. Got: %v, want: %v.", gotErr, tt.wantErr)
}
if tt.wantErr == nil && !reflect.DeepEqual(tt.wantOperatorsAvsStateDict, gotOperatorsAvsStateDict) {
t.Fatalf("GetOperatorsAvsState returned wrong operatorsAvsStateDict. Got: %v, want: %v.", gotOperatorsAvsStateDict, tt.wantOperatorsAvsStateDict)
}
if tt.wantErr == nil && !reflect.DeepEqual(tt.wantAggG1PubkeyPerQuorum, aggG1PubkeyPerQuorum) {
t.Fatalf("GetOperatorsAvsState returned wrong aggG1PubkeyPerQuorum. Got: %v, want: %v.", aggG1PubkeyPerQuorum, tt.wantAggG1PubkeyPerQuorum)
})
}
}

func TestAvsRegistryServiceChainCaller_GetQuorumsAvsState(t *testing.T) {
logger := logging.NewNoopLogger()
testOperator := testOperator{
operatorAddr: common.HexToAddress("0x1"),
operatorId: types.OperatorId{1},
pubkeys: types.OperatorPubkeys{
G1Pubkey: bls.NewG1Point(big.NewInt(1), big.NewInt(1)),
G2Pubkey: bls.NewG2Point([2]*big.Int{big.NewInt(1), big.NewInt(1)}, [2]*big.Int{big.NewInt(1), big.NewInt(1)}),
},
}

var tests = []struct {
shrimalmadhur marked this conversation as resolved.
Show resolved Hide resolved
name string
mocksInitializationFunc func(*chainiomocks.MockAvsRegistryReader, *chainiomocks.MockELReader, *servicemocks.MockPubkeyCompendiumService)
queryQuorumNumbers []types.QuorumNum
queryBlockNum types.BlockNum
wantErr error
wantQuorumsAvsStateDict map[types.QuorumNum]types.QuorumAvsState
}{
{
name: "should return operatorsAvsState",
mocksInitializationFunc: func(mockAvsRegistryReader *chainiomocks.MockAvsRegistryReader, mockElReader *chainiomocks.MockELReader, mockPubkeyCompendiumService *servicemocks.MockPubkeyCompendiumService) {
mockAvsRegistryReader.EXPECT().GetOperatorsStakeInQuorumsAtBlock(context.Background(), []types.QuorumNum{1}, types.BlockNum(1)).Return([][]blsoperatorstateretrievar.BLSOperatorStateRetrieverOperator{
{
{
OperatorId: testOperator.operatorId,
Stake: big.NewInt(123),
},
},
}, nil)
mockElReader.EXPECT().GetOperatorAddressFromPubkeyHash(context.Background(), testOperator.operatorId).Return(testOperator.operatorAddr, nil)
mockPubkeyCompendiumService.EXPECT().GetOperatorPubkeys(context.Background(), testOperator.operatorAddr).Return(testOperator.pubkeys, true)
},
queryQuorumNumbers: []types.QuorumNum{1},
queryBlockNum: 1,
wantErr: nil,
wantQuorumsAvsStateDict: map[types.QuorumNum]types.QuorumAvsState{
1: types.QuorumAvsState{
QuorumNumber: types.QuorumNum(1),
TotalStake: big.NewInt(123),
AggPubkeyG1: bls.NewG1Point(big.NewInt(1), big.NewInt(1)),
BlockNumber: 1,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create mocks
mockCtrl := gomock.NewController(t)
mockAvsRegistryReader := chainiomocks.NewMockAvsRegistryReader(mockCtrl)
mockElReader := chainiomocks.NewMockELReader(mockCtrl)
mockPubkeyCompendium := servicemocks.NewMockPubkeyCompendiumService(mockCtrl)

if tt.mocksInitializationFunc != nil {
tt.mocksInitializationFunc(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium)
}
// Create a new instance of the avsregistry service
service := NewAvsRegistryServiceChainCaller(mockAvsRegistryReader, mockElReader, mockPubkeyCompendium, logger)

// Call the GetOperatorPubkeys method with the test operator address
aggG1PubkeyPerQuorum, gotErr := service.GetQuorumsAvsStateAtBlock(context.Background(), tt.queryQuorumNumbers, tt.queryBlockNum)
if tt.wantErr != gotErr {
t.Fatalf("GetOperatorsAvsState returned wrong error. Got: %v, want: %v.", gotErr, tt.wantErr)
}
if tt.wantErr == nil && !reflect.DeepEqual(tt.wantQuorumsAvsStateDict, aggG1PubkeyPerQuorum) {
t.Fatalf("GetOperatorsAvsState returned wrong aggG1PubkeyPerQuorum. Got: %v, want: %v.", aggG1PubkeyPerQuorum, tt.wantQuorumsAvsStateDict)
}
})
}
Expand Down
Loading