Skip to content

Commit

Permalink
feat: add bls signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
j75689 committed Jul 6, 2023
1 parent ef98eb2 commit 4b4b6a3
Show file tree
Hide file tree
Showing 18 changed files with 759 additions and 320 deletions.
567 changes: 357 additions & 210 deletions api/cosmos/staking/v1beta1/tx.pulsar.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/keys/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The pass backend requires GnuPG: https://gnupg.org/
RenameKeyCommand(),
ParseKeyStringCommand(),
MigrateCommand(),
SignMsgKeysCmd(),
)

cmd.PersistentFlags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
Expand Down
49 changes: 49 additions & 0 deletions client/keys/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package keys

import (
"encoding/hex"
"errors"

"github.com/cometbft/cometbft/crypto/tmhash"
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
)

// SignMsgKeysCmd returns the Cobra Command for signing messages with the private key of a given name.
func SignMsgKeysCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "sign",
Short: "Sign message",
Long: "Return a signature from their associated name and address private key.",
RunE: runSignMsgCmd,
}

cmd.Flags().String(flags.FlagFrom, "", "Name or address of private key with which to sign")
return cmd
}

func runSignMsgCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("invalid number of arguments")
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

_, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From)
if err != nil {
return err
}

sig, _, err := clientCtx.Keyring.Sign(name, tmhash.Sum([]byte(args[0])))
if err != nil {
return err
}

cmd.Println(hex.EncodeToString(sig))
return nil
}
4 changes: 3 additions & 1 deletion proto/cosmos/staking/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ message MsgCreateValidator {
string relayer_address = 9 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string challenger_address = 10 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string bls_key = 11;
string bls_proof = 12;
}

// MsgCreateValidatorResponse defines the Msg/CreateValidator response type.
Expand Down Expand Up @@ -98,7 +99,8 @@ message MsgEditValidator {

string relayer_address = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string challenger_address = 6 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string bls_key = 7; // The BLS pubkey for the authorized relayer/challenger
string bls_key = 7; // The BLS pubkey for the authorized relayer/challenger
string bls_proof = 8;
}

// MsgEditValidatorResponse defines the Msg/EditValidator response type.
Expand Down
14 changes: 14 additions & 0 deletions testutil/sims/address_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/eth/ethsecp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -144,6 +145,19 @@ func CreateTestPubKeys(numPubKeys int) []cryptotypes.PubKey {
return publicKeys
}

// CreateTestAccounts returns number of PubKey, PrivKey
func CreateTestAccounts(num int) ([]cryptotypes.PubKey, []cryptotypes.PrivKey) {
var publicKeys = make([]cryptotypes.PubKey, 0, num)
var privateKeys = make([]cryptotypes.PrivKey, 0, num)
for i := 0; i < num; i++ {
privKey, _ := ethsecp256k1.GenPrivKey()
publicKeys = append(publicKeys, privKey.PubKey())
privateKeys = append(privateKeys, privKey)
}

return publicKeys, privateKeys
}

// NewPubKeyFromHex returns a PubKey from a hex string.
func NewPubKeyFromHex(pk string) (res cryptotypes.PubKey) {
pkBytes, err := hex.DecodeString(pk)
Expand Down
11 changes: 9 additions & 2 deletions x/genutil/client/cli/gentx.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path/filepath"

tmtypes "github.com/cometbft/cometbft/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
"github.com/spf13/cobra"

Expand All @@ -33,9 +34,9 @@ func GenTxCmd(mbm module.BasicManager, txEncCfg client.TxEncodingConfig, genBalI
fsCreateValidator, defaultsDesc := cli.CreateValidatorMsgFlagSet(ipDefault)

cmd := &cobra.Command{
Use: "gentx [key_name] [amount] [validator] [relayer] [challenger] [blskey]",
Use: "gentx [key_name] [amount] [validator] [relayer] [challenger] [blskey] [blsProof]",
Short: "Generate a genesis tx carrying a self delegation",
Args: cobra.ExactArgs(6),
Args: cobra.ExactArgs(7),
Long: fmt.Sprintf(`Generate a genesis transaction that creates a validator with a self-delegation,
that is signed by the key in the Keyring referenced by a given name. A node ID and Bech32 consensus
pubkey may optionally be provided. If they are omitted, they will be retrieved from the priv_validator.json
Expand All @@ -47,6 +48,7 @@ $ %s gentx my-key-name 1000000stake \
0x6D967dc83b625603c963713eABd5B43A281E595e \
0xcdd393723f1Af81faa3F3c87B51dAB72B6c68154 \
ac1e598ae0ccbeeaafa31bc6faefa85c2ae3138699cac79169cd718f1a38445201454ec092a86f200e08a15266bdc6e9 \
b68b819c2d431bd8ea800326bbcd91bbbbec5404b8f456b23c87de368c7f48507e7be120f32354ebf3df38c2b5808cebd4c07254f0b4626007c6d46fc05b260901 \
--home=/path/to/home/dir --keyring-backend=os --chain-id=greenfield_9000-1 \
--moniker="myvalidator" \
--commission-max-change-rate=0.01 \
Expand Down Expand Up @@ -172,12 +174,17 @@ $ %s gentx my-key-name 1000000stake \
if len(blsPk) != 2*sdk.BLSPubKeyLength {
return fmt.Errorf("invalid bls pubkey")
}
blsProof := args[6]
if len(blsProof) != 2*ethcrypto.SignatureLength {
return fmt.Errorf("invalid bls proof")
}

createValCfg.Validator = validator
createValCfg.Delegator = addr
createValCfg.Relayer = relayer
createValCfg.Challenger = challenger
createValCfg.BlsKey = blsPk
createValCfg.BLSProof = blsProof

// create a 'create-validator' message
txBldr, msg, err := cli.BuildCreateValidatorMsg(clientCtx, createValCfg, txFactory, true)
Expand Down
6 changes: 3 additions & 3 deletions x/slashing/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ func TestBeginBlocker(t *testing.T) {

ctx := app.BaseApp.NewContext(false, tmproto.Header{})

pks := simtestutil.CreateTestPubKeys(1)
pks, pvs := simtestutil.CreateTestAccounts(1)
simtestutil.AddTestAddrsFromPubKeys(bankKeeper, stakingKeeper, ctx, pks, stakingKeeper.TokensFromConsensusPower(ctx, 200))
addr, pk := sdk.AccAddress(pks[0].Address()), pks[0]
addr, pk, pv := sdk.AccAddress(pks[0].Address()), pks[0], pvs[0]
tstaking := stakingtestutil.NewHelper(t, ctx, stakingKeeper)

// bond the validator
power := int64(100)
amt := tstaking.CreateValidatorWithValPower(addr, pk, power, true)
amt := tstaking.CreateValidatorWithValPower(addr, pk, pv, power, true)
staking.EndBlocker(ctx, stakingKeeper)
require.Equal(
t, bankKeeper.GetAllBalances(ctx, addr),
Expand Down
6 changes: 5 additions & 1 deletion x/slashing/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/tmhash"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -79,11 +80,14 @@ func TestSlashingMsgs(t *testing.T) {
commission := stakingtypes.NewCommissionRates(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec())
blsSecretKey, _ := bls.RandKey()
blsPk := hex.EncodeToString(blsSecretKey.PublicKey().Marshal())
blsProofBuf, err := priv1.Sign(tmhash.Sum(blsSecretKey.PublicKey().Marshal()))
require.NoError(t, err)
blsProof := hex.EncodeToString(blsProofBuf)

createValidatorMsg, err := stakingtypes.NewMsgCreateValidator(
addr1, valKey.PubKey(),
bondCoin, description, commission, sdk.OneInt(),
addr1, addr1, addr1, addr1, blsPk,
addr1, addr1, addr1, addr1, blsPk, blsProof,
)
require.NoError(t, err)

Expand Down
2 changes: 2 additions & 0 deletions x/staking/client/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
FlagAddressRelayer = "addr-relayer"
FlagAddressChallenger = "addr-challenger"
FlagBlsKey = "bls-key"
FlagBlsProof = "bls-proof"
)

// common flagsets to add to various functions
Expand Down Expand Up @@ -96,6 +97,7 @@ func FlagSetRelayerAddress() *flag.FlagSet {
func FlagSetBlsKey() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.String(FlagBlsKey, "", "The bls pubkey of the validator")
fs.String(FlagBlsProof, "", "The bls proof of the validator")
return fs
}

Expand Down
6 changes: 4 additions & 2 deletions x/staking/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,11 @@ func NewEditValidatorCmd() *cobra.Command {
}

blsPk, _ := cmd.Flags().GetString(FlagBlsKey)
blsProof, _ := cmd.Flags().GetString(FlagBlsProof)

msg := types.NewMsgEditValidator(
valAddr, description, newRate, newMinSelfDelegation,
relayer, challenger, blsPk,
relayer, challenger, blsPk, blsProof,
)

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
Expand Down Expand Up @@ -497,6 +498,7 @@ type TxCreateValidatorConfig struct {
Relayer sdk.AccAddress
Challenger sdk.AccAddress
BlsKey string
BLSProof string
}

func PrepareConfigForTxCreateValidator(flagSet *flag.FlagSet, moniker, nodeID, chainID string, valPubKey cryptotypes.PubKey) (TxCreateValidatorConfig, error) {
Expand Down Expand Up @@ -636,7 +638,7 @@ func BuildCreateValidatorMsg(clientCtx client.Context, config TxCreateValidatorC
msg, err := types.NewMsgCreateValidator(
config.Validator, config.PubKey,
amount, description, commissionRates, minSelfDelegation,
from, config.Delegator, config.Relayer, config.Challenger, config.BlsKey)
from, config.Delegator, config.Relayer, config.Challenger, config.BlsKey, config.BLSProof)
if err != nil {
return txBldr, msg, err
}
Expand Down
67 changes: 66 additions & 1 deletion x/staking/keeper/msg_server.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package keeper

import (
"bytes"
"context"
"encoding/hex"
"strconv"
"time"

"github.com/armon/go-metrics"
"github.com/cometbft/cometbft/crypto/tmhash"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"

sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -106,6 +111,19 @@ func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateVa
return nil, types.ErrValidatorBlsKeyExists
}

// check to see if the bls proof is signed from operator
blsProof, err := hex.DecodeString(msg.BlsProof)
if err != nil {
return nil, types.ErrValidatorInvalidBlsProof
}
ok, err = k.CheckBlsProof(ctx, blsPk, blsProof, valAddr)
if err != nil {
return nil, types.ErrValidatorInvalidBlsProof
}
if !ok {
return nil, types.ErrValidatorInvalidBlsProof
}

bondDenom := k.BondDenom(ctx)
if msg.Value.Denom != bondDenom {
return nil, sdkerrors.Wrapf(
Expand Down Expand Up @@ -289,11 +307,23 @@ func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValida
}

// replace bls pubkey
if len(msg.BlsKey) != 0 {
if len(msg.BlsKey) != 0 && len(msg.BlsProof) != 0 {
blsPk, err := hex.DecodeString(msg.BlsKey)
if err != nil || len(blsPk) != sdk.BLSPubKeyLength {
return nil, types.ErrValidatorInvalidBlsKey
}
// check to see if the bls proof is signed from operator
blsProof, err := hex.DecodeString(msg.BlsProof)
if err != nil {
return nil, types.ErrValidatorInvalidBlsProof
}
ok, err := k.CheckBlsProof(ctx, blsPk, blsProof, validator.GetOperator())
if err != nil {
return nil, types.ErrValidatorInvalidBlsProof
}
if !ok {
return nil, types.ErrValidatorInvalidBlsProof
}
if tmpValidator, found := k.GetValidatorByBlsKey(ctx, blsPk); found {
if tmpValidator.OperatorAddress != validator.OperatorAddress {
return nil, types.ErrValidatorBlsKeyExists
Expand Down Expand Up @@ -628,3 +658,38 @@ func (ms msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdatePara

return &types.MsgUpdateParamsResponse{}, nil
}

// CheckBlsProof checks the BLS signature of the validator
func (ms msgServer) CheckBlsProof(goCtx context.Context, blsPk, sig []byte, valAddr sdk.Address) (bool, error) {
sigHash := tmhash.Sum(blsPk)
if len(sig) != ethcrypto.SignatureLength {
return false, sdkerrors.Wrapf(sdkerrors.ErrorInvalidSigner, "signature length (actual: %d) doesn't match typical [R||S||V] signature 65 bytes", len(sig))
}
if sig[ethcrypto.RecoveryIDOffset] == 27 || sig[ethcrypto.RecoveryIDOffset] == 28 {
sig[ethcrypto.RecoveryIDOffset] -= 27
}
pubKeyBytes, err := secp256k1.RecoverPubkey(sigHash, sig)
if err != nil {
return false, sdkerrors.Wrap(err, "failed to recover pubKey from sig")
}
ecPubKey, err := ethcrypto.UnmarshalPubkey(pubKeyBytes)
if err != nil {
return false, sdkerrors.Wrap(err, "failed to unmarshal recovered pubKey")
}
pubKeyAddr := ethcrypto.PubkeyToAddress(*ecPubKey)
if !bytes.Equal(pubKeyAddr.Bytes(), valAddr.Bytes()) {
return false, sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "pubKey %s is different from approval pubKey %s", pubKeyAddr, valAddr)
}
recoveredSignerAcc := sdk.AccAddress(pubKeyAddr.Bytes())
if !recoveredSignerAcc.Equals(valAddr) {
return false, sdkerrors.Wrapf(sdkerrors.ErrorInvalidSigner, "failed to verify validator %s signature", recoveredSignerAcc)
}

// VerifySignature of ethsecp256k1 accepts 64 byte signature [R||S]
// WARNING! Under NO CIRCUMSTANCES try to use pubKey.VerifySignature there
if !secp256k1.VerifySignature(pubKeyBytes, sigHash, sig[:len(sig)-1]) {
return false, sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "unable to verify validator signature of EIP712 typed data")
}

return true, nil
}
7 changes: 5 additions & 2 deletions x/staking/simulation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math/rand"

"github.com/cometbft/cometbft/crypto/tmhash"
"github.com/prysmaticlabs/prysm/crypto/bls"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand Down Expand Up @@ -161,11 +162,13 @@ func SimulateMsgCreateValidator(ak types.AccountKeeper, bk types.BankKeeper, k *

blsSecretKey, _ := bls.RandKey()
blsPk := hex.EncodeToString(blsSecretKey.PublicKey().Marshal())
blsProofBuf, _ := simAccount.PrivKey.Sign(tmhash.Sum(blsSecretKey.PublicKey().Marshal()))
blsProof := hex.EncodeToString(blsProofBuf)

msg, err := types.NewMsgCreateValidator(
address, simAccount.ConsKey.PubKey(),
selfDelegation, description, commission, sdk.OneInt(),
address, address, address, address, blsPk,
address, address, address, address, blsPk, blsProof,
)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to create CreateValidator message"), nil, err
Expand Down Expand Up @@ -226,7 +229,7 @@ func SimulateMsgEditValidator(ak types.AccountKeeper, bk types.BankKeeper, k *ke
simtypes.RandStringOfLength(r, 10),
)

msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil, address, address, "")
msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil, address, address, "", "")

txCtx := simulation.OperationInput{
R: r,
Expand Down
Loading

0 comments on commit 4b4b6a3

Please sign in to comment.