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

fix(avs): prevent BLS12-381 replay attacks #314

Merged
merged 14 commits into from
Feb 24, 2025
Merged
6 changes: 2 additions & 4 deletions precompiles/avs/IAVSManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,12 @@ interface IAVSManager {
/// @param sender The external address for calling this method.
/// @param avsAddress The address of AVS.
/// @param pubKey the public keys of the operator
/// @param pubkeyRegistrationSignature the public keys of the operator
/// @param pubkeyRegistrationMessageHash the public keys of the operator
/// @param pubKeyRegistrationSignature the bls signature of the operator
function registerBLSPublicKey(
address sender,
address avsAddress,
bytes calldata pubKey,
bytes calldata pubkeyRegistrationSignature,
bytes calldata pubkeyRegistrationMessageHash
bytes calldata pubKeyRegistrationSignature
) external returns (bool success);

/// @dev operatorSubmitTask , this function enables a operator submit a task result.
Expand Down
7 changes: 1 addition & 6 deletions precompiles/avs/abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1021,12 +1021,7 @@
},
{
"internalType": "bytes",
"name": "pubkeyRegistrationSignature",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "pubkeyRegistrationMessageHash",
"name": "pubKeyRegistrationSignature",
"type": "bytes"
}
],
Expand Down
9 changes: 2 additions & 7 deletions precompiles/avs/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,9 @@ func (p Precompile) RegisterBLSPublicKey(
if !ok {
return nil, fmt.Errorf(exocmn.ErrContractInputParamOrType, 3, "[]byte", pubKeyRegistrationSignature)
}
blsParams.PubkeyRegistrationSignature = pubKeyRegistrationSignature

pubKeyRegistrationMessageHash, ok := args[4].([]byte)
if !ok {
return nil, fmt.Errorf(exocmn.ErrContractInputParamOrType, 4, "[]byte", pubKeyRegistrationMessageHash)
}
blsParams.PubkeyRegistrationMessageHash = pubKeyRegistrationMessageHash
blsParams.PubKeyRegistrationSignature = pubKeyRegistrationSignature

// validates key format by itself so we don't need to do it before this.
err := p.avsKeeper.RegisterBLSPublicKey(ctx, blsParams)
if err != nil {
return nil, err
Expand Down
239 changes: 232 additions & 7 deletions x/avs/keeper/avs_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package keeper_test

import (
"fmt"
"math/big"
"strings"
"time"

testutiltx "github.com/ExocoreNetwork/exocore/testutil/tx"
"github.com/ethereum/go-ethereum/crypto"
"github.com/prysmaticlabs/prysm/v4/crypto/bls/blst"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"
"github.com/ethereum/go-ethereum/common"

"github.com/ExocoreNetwork/exocore/x/avs/types"
avstypes "github.com/ExocoreNetwork/exocore/x/avs/types"
delegationtypes "github.com/ExocoreNetwork/exocore/x/delegation/types"
epochstypes "github.com/ExocoreNetwork/exocore/x/epochs/types"
operatorTypes "github.com/ExocoreNetwork/exocore/x/operator/types"
Expand Down Expand Up @@ -78,7 +83,7 @@ func (suite *AVSTestSuite) TestUpdateAVSInfo_Register() {
avsParams := &types.AVSRegisterOrDeregisterParams{
AvsName: avsName,
AvsAddress: common.HexToAddress(avsAddres),
Action: avstypes.RegisterAction,
Action: types.RegisterAction,
RewardContractAddress: common.HexToAddress(rewardAddress),
AvsOwnerAddresses: avsOwnerAddress,
AssetIDs: assetIDs,
Expand Down Expand Up @@ -110,7 +115,7 @@ func (suite *AVSTestSuite) TestUpdateAVSInfo_DeRegister() {
avsParams := &types.AVSRegisterOrDeregisterParams{
AvsName: avsName,
AvsAddress: common.HexToAddress(avsAddress),
Action: avstypes.DeRegisterAction,
Action: types.DeRegisterAction,
AvsOwnerAddresses: avsOwnerAddress,
AssetIDs: assetIDs,
MinSelfDelegation: uint64(10),
Expand All @@ -123,10 +128,11 @@ func (suite *AVSTestSuite) TestUpdateAVSInfo_DeRegister() {
suite.Error(err)
suite.Contains(err.Error(), types.ErrUnregisterNonExistent.Error())

avsParams.Action = avstypes.RegisterAction
avsParams.Action = types.RegisterAction
err = suite.App.AVSManagerKeeper.UpdateAVSInfo(suite.Ctx, avsParams)
suite.NoError(err)
info, err := suite.App.AVSManagerKeeper.GetAVSInfo(suite.Ctx, avsAddress)
suite.NoError(err)
suite.Equal(strings.ToLower(avsAddress), info.GetInfo().AvsAddress)

epoch, _ := suite.App.EpochsKeeper.GetEpochInfo(suite.Ctx, epochstypes.DayEpochID)
Expand All @@ -138,22 +144,24 @@ func (suite *AVSTestSuite) TestUpdateAVSInfo_DeRegister() {
suite.Equal(epoch.CurrentEpoch, epochEnd+1)
}

avsParams.Action = avstypes.DeRegisterAction
avsParams.Action = types.DeRegisterAction
avsParams.CallerAddress, err = sdk.AccAddressFromBech32("exo13h6xg79g82e2g2vhjwg7j4r2z2hlncelwutkjr")
suite.NoError(err)
err = suite.App.AVSManagerKeeper.UpdateAVSInfo(suite.Ctx, avsParams)
suite.NoError(err)
info, err = suite.App.AVSManagerKeeper.GetAVSInfo(suite.Ctx, avsAddress)
suite.Error(err)
suite.Contains(err.Error(), types.ErrNoKeyInTheStore.Error())
suite.Nil(info)
}

func (suite *AVSTestSuite) TestUpdateAVSInfoWithOperator_Register() {
avsAddress := suite.avsAddress
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())

operatorParams := &avstypes.OperatorOptParams{
operatorParams := &types.OperatorOptParams{
AvsAddress: avsAddress,
Action: avstypes.RegisterAction,
Action: types.RegisterAction,
OperatorAddress: operatorAddress,
}
// operator Not Exist
Expand Down Expand Up @@ -195,3 +203,220 @@ func (suite *AVSTestSuite) TestAddressSwitch() {
commonAddress := common.Address(accAddress)
suite.Equal(common.HexToAddress("0x8dF46478a83Ab2a429979391E9546A12AfF9E33f"), commonAddress)
}

func (suite *AVSTestSuite) TestRegisterBLSPublicKey() {
type testCase struct {
name string
setupParams func() *types.BlsParams
errorContains string
}

testCases := []testCase{
{
name: "successful registration",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())

return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
},
{
name: "reuse BLS key - same operator + avs",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())

params := types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
err = suite.App.AVSManagerKeeper.RegisterBLSPublicKey(suite.Ctx, &params)
suite.NoError(err)
return &params
},
errorContains: errorsmod.Wrap(
types.ErrAlreadyExists,
"a key has already been set for this operator and avs",
).Error(),
},
{
name: "reuse BLS key - different operator + same avs",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())

params := types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
err = suite.App.AVSManagerKeeper.RegisterBLSPublicKey(suite.Ctx, &params)
suite.NoError(err)

anotherOperatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg = fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), anotherOperatorAddress.String())
hashedMsg = crypto.Keccak256Hash([]byte(msg))
sig = privateKey.Sign(hashedMsg.Bytes())

return &types.BlsParams{
OperatorAddress: anotherOperatorAddress,
AvsAddress: params.AvsAddress,
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
errorContains: errorsmod.Wrap(
types.ErrAlreadyExists,
"this BLS key is already in use",
).Error(),
},
{
name: "wrong chain ID",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, "onemorechain_211", operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())

return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
errorContains: types.ErrSigNotMatchPubKey.Error(),
},
{
name: "wrong operator address in signature",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
anotherOperatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), anotherOperatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())

return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
errorContains: types.ErrSigNotMatchPubKey.Error(),
},
{
name: "mismatched BLS key",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())
// generate a different private key
anotherPrivateKey, err := blst.RandKey()
suite.NoError(err)
anotherPublicKey := anotherPrivateKey.PublicKey()
return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
// provide a different public key than the one which signed it, so that verification fails
PubKey: anotherPublicKey.Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
errorContains: types.ErrSigNotMatchPubKey.Error(),
},
{
name: "invalid public key format",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())
return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: []byte("invalid"),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
errorContains: types.ErrParsePubKey.Error(),
},
{
name: "invalid signature format",
setupParams: func() *types.BlsParams {
privateKey, err := blst.RandKey()
suite.NoError(err)
operatorAddress := sdk.AccAddress(utiltx.GenerateAddress().Bytes())
return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: []byte("invalid"),
}
},
errorContains: types.ErrSigNotMatchPubKey.Error(),
},
{
name: "empty operator address",
setupParams: func() *types.BlsParams {
operatorAddress := sdk.AccAddress{}
privateKey, err := blst.RandKey()
suite.NoError(err)
msg := fmt.Sprintf(types.BLSMessageToSign, types.ChainIDWithoutRevision(suite.Ctx.ChainID()), operatorAddress.String())
hashedMsg := crypto.Keccak256Hash([]byte(msg))
sig := privateKey.Sign(hashedMsg.Bytes())
return &types.BlsParams{
OperatorAddress: operatorAddress,
AvsAddress: testutiltx.GenerateAddress(),
PubKey: privateKey.PublicKey().Marshal(),
PubKeyRegistrationSignature: sig.Marshal(),
}
},
errorContains: types.ErrSigNotMatchPubKey.Error(),
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
params := tc.setupParams()
err := suite.App.AVSManagerKeeper.RegisterBLSPublicKey(suite.Ctx, params)

if tc.errorContains != "" {
suite.Error(err)
suite.Contains(err.Error(), tc.errorContains)
} else {
suite.NoError(err)
}
})
}
}
Loading
Loading