From d065cdd9d7f1219fece38cb678d1233566cf530b Mon Sep 17 00:00:00 2001 From: Cirrus Gai Date: Wed, 5 Feb 2025 10:18:15 +0800 Subject: [PATCH] feat: BLS keystore improvement (#467) Closes #336 --------- Co-authored-by: wonjoon <39115630+wnjoon@users.noreply.github.com> Co-authored-by: dongsam --- CHANGELOG.md | 3 +- Makefile | 5 + app/app.go | 64 ++-- app/app_test.go | 27 +- app/build_tags.go | 5 + app/encoding.go | 8 +- app/include_upgrade_e2e.go | 9 + app/keepers/keepers.go | 11 +- app/signer/bls.go | 209 +++++++++++++ app/signer/bls_test.go | 58 ++++ app/signer/private.go | 83 ++++- {privval => app/signer}/types.go | 14 +- app/signer/util.go | 22 ++ app/test_helpers.go | 42 ++- cmd/babylond/cmd/bls/create_bls_key.go | 85 ----- cmd/babylond/cmd/cmd_test.go | 1 + cmd/babylond/cmd/create_bls_key.go | 57 ++++ cmd/babylond/cmd/flags.go | 2 + cmd/babylond/cmd/genhelpers/gentx.go | 41 ++- cmd/babylond/cmd/genhelpers/gentx_test.go | 42 ++- cmd/babylond/cmd/init.go | 40 +++ cmd/babylond/cmd/migrate_bls_key.go | 134 ++++++++ cmd/babylond/cmd/migrate_bls_key_test.go | 134 ++++++++ cmd/babylond/cmd/root.go | 65 ++-- cmd/babylond/cmd/testnet.go | 4 +- cmd/babylond/cmd/testnet_test.go | 7 +- cmd/babylond/cmd/validate_genesis_test.go | 6 +- contrib/images/Makefile | 2 +- crypto/erc2335/erc2335.go | 62 ++++ crypto/erc2335/erc2335_test.go | 94 ++++++ go.mod | 2 + go.sum | 19 ++ privval/file.go | 296 ------------------ test/e2e/initialization/config.go | 7 +- test/e2e/initialization/export.go | 1 + test/e2e/initialization/node.go | 59 ++-- test/replay/driver.go | 45 +-- testutil/datagen/btc_blockchain.go | 2 +- testutil/datagen/genesiskey.go | 30 +- testutil/datagen/init_val.go | 57 ++-- testutil/helper/helper.go | 38 +-- testutil/keeper/checkpointing.go | 2 +- testutil/mocks/bls_signer.go | 91 ------ .../mocks/checkpointing_expected_keepers.go | 15 + testutil/signer/private.go | 46 ++- x/checkpointing/client/cli/tx_test.go | 29 +- x/checkpointing/client/cli/utils.go | 20 +- x/checkpointing/genesis_test.go | 4 +- x/checkpointing/keeper/bls_signer.go | 24 +- x/checkpointing/keeper/keeper.go | 21 +- x/checkpointing/keeper/msg_server_test.go | 4 +- x/checkpointing/keeper/registration_state.go | 15 +- x/checkpointing/types/errors.go | 1 + x/checkpointing/types/expected_keepers.go | 1 + x/checkpointing/types/msgs_test.go | 4 +- x/checkpointing/types/pop_test.go | 7 +- x/checkpointing/types/types.go | 6 + x/checkpointing/vote_ext.go | 28 +- x/checkpointing/vote_ext_test.go | 75 ----- x/epoching/keeper/staking_functions.go | 4 + 60 files changed, 1418 insertions(+), 871 deletions(-) create mode 100644 app/build_tags.go create mode 100644 app/include_upgrade_e2e.go create mode 100644 app/signer/bls.go create mode 100644 app/signer/bls_test.go rename {privval => app/signer}/types.go (77%) create mode 100644 app/signer/util.go delete mode 100644 cmd/babylond/cmd/bls/create_bls_key.go create mode 100644 cmd/babylond/cmd/create_bls_key.go create mode 100644 cmd/babylond/cmd/init.go create mode 100644 cmd/babylond/cmd/migrate_bls_key.go create mode 100644 cmd/babylond/cmd/migrate_bls_key_test.go create mode 100644 crypto/erc2335/erc2335.go create mode 100644 crypto/erc2335/erc2335_test.go delete mode 100644 privval/file.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5a2397e..72ff3b0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,11 +44,12 @@ randomness commit at `TestBTCRewardsDistribution`. - [#391](https://github.com/babylonlabs-io/babylon/pull/391) Fix e2e `TestBTCRewardsDistribution` flunky check of rewards - [#458](https://github.com/babylonlabs-io/babylon/pull/458) Set `CosmosProvider` functions as public +- [#467](https://github.com/babylonlabs-io/babylon/pull/467) BLS keystore improvement ### State Machine Breaking - [#402](https://github.com/babylonlabs-io/babylon/pull/402) **Babylon multi-staking support**. -This PR contains a series of PRs on multi-staking support and BTC stakingintegration. +This PR contains a series of PRs on multi-staking support and BTC staking integration. - [#457](https://github.com/babylonlabs-io/babylon/pull/457) Remove staking msg server and update gentx to generate `MsgWrappedCreateValidator` diff --git a/Makefile b/Makefile index e15381e9c..69afd143b 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,11 @@ else BUILD_TAGS += mainnet endif +# Handles the inclusion of e2e upgrade in binary +ifeq (e2e_upgrade,$(findstring e2e_upgrade,$(BABYLON_BUILD_OPTIONS))) + BUILD_TAGS += e2e_upgrade +endif + # DB backend selection ifeq (cleveldb,$(findstring cleveldb,$(BABYLON_BUILD_OPTIONS))) ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=cleveldb diff --git a/app/app.go b/app/app.go index b4c6933eb..daca1c9f2 100644 --- a/app/app.go +++ b/app/app.go @@ -23,36 +23,6 @@ import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/babylonlabs-io/babylon/app/ante" - appkeepers "github.com/babylonlabs-io/babylon/app/keepers" - appparams "github.com/babylonlabs-io/babylon/app/params" - "github.com/babylonlabs-io/babylon/app/signer" - "github.com/babylonlabs-io/babylon/app/upgrades" - "github.com/babylonlabs-io/babylon/client/docs" - bbn "github.com/babylonlabs-io/babylon/types" - "github.com/babylonlabs-io/babylon/x/btccheckpoint" - btccheckpointtypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" - "github.com/babylonlabs-io/babylon/x/btclightclient" - btclightclienttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" - "github.com/babylonlabs-io/babylon/x/btcstaking" - btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/babylonlabs-io/babylon/x/btcstkconsumer" - bsctypes "github.com/babylonlabs-io/babylon/x/btcstkconsumer/types" - "github.com/babylonlabs-io/babylon/x/checkpointing" - checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" - "github.com/babylonlabs-io/babylon/x/epoching" - epochingtypes "github.com/babylonlabs-io/babylon/x/epoching/types" - "github.com/babylonlabs-io/babylon/x/finality" - finalitytypes "github.com/babylonlabs-io/babylon/x/finality/types" - "github.com/babylonlabs-io/babylon/x/incentive" - incentivekeeper "github.com/babylonlabs-io/babylon/x/incentive/keeper" - incentivetypes "github.com/babylonlabs-io/babylon/x/incentive/types" - "github.com/babylonlabs-io/babylon/x/mint" - minttypes "github.com/babylonlabs-io/babylon/x/mint/types" - "github.com/babylonlabs-io/babylon/x/monitor" - monitortypes "github.com/babylonlabs-io/babylon/x/monitor/types" - "github.com/babylonlabs-io/babylon/x/zoneconcierge" - zctypes "github.com/babylonlabs-io/babylon/x/zoneconcierge/types" abci "github.com/cometbft/cometbft/abci/types" cmtos "github.com/cometbft/cometbft/libs/os" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -119,6 +89,36 @@ import ( ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" "github.com/spf13/cast" + + "github.com/babylonlabs-io/babylon/app/ante" + appkeepers "github.com/babylonlabs-io/babylon/app/keepers" + appparams "github.com/babylonlabs-io/babylon/app/params" + "github.com/babylonlabs-io/babylon/app/upgrades" + "github.com/babylonlabs-io/babylon/client/docs" + bbn "github.com/babylonlabs-io/babylon/types" + "github.com/babylonlabs-io/babylon/x/btccheckpoint" + btccheckpointtypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" + "github.com/babylonlabs-io/babylon/x/btclightclient" + btclightclienttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" + "github.com/babylonlabs-io/babylon/x/btcstaking" + btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + "github.com/babylonlabs-io/babylon/x/btcstkconsumer" + bsctypes "github.com/babylonlabs-io/babylon/x/btcstkconsumer/types" + "github.com/babylonlabs-io/babylon/x/checkpointing" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" + "github.com/babylonlabs-io/babylon/x/epoching" + epochingtypes "github.com/babylonlabs-io/babylon/x/epoching/types" + "github.com/babylonlabs-io/babylon/x/finality" + finalitytypes "github.com/babylonlabs-io/babylon/x/finality/types" + "github.com/babylonlabs-io/babylon/x/incentive" + incentivekeeper "github.com/babylonlabs-io/babylon/x/incentive/keeper" + incentivetypes "github.com/babylonlabs-io/babylon/x/incentive/types" + "github.com/babylonlabs-io/babylon/x/mint" + minttypes "github.com/babylonlabs-io/babylon/x/mint/types" + "github.com/babylonlabs-io/babylon/x/monitor" + monitortypes "github.com/babylonlabs-io/babylon/x/monitor/types" + "github.com/babylonlabs-io/babylon/x/zoneconcierge" + zctypes "github.com/babylonlabs-io/babylon/x/zoneconcierge/types" ) const ( @@ -210,7 +210,7 @@ func NewBabylonApp( loadLatest bool, skipUpgradeHeights map[int64]bool, invCheckPeriod uint, - privSigner *signer.PrivSigner, + blsSigner *checkpointingtypes.BlsSigner, appOpts servertypes.AppOptions, wasmOpts []wasmkeeper.Option, baseAppOptions ...func(*baseapp.BaseApp), @@ -261,7 +261,7 @@ func NewBabylonApp( homePath, invCheckPeriod, skipUpgradeHeights, - privSigner, + *blsSigner, appOpts, wasmConfig, wasmOpts, diff --git a/app/app_test.go b/app/app_test.go index 859167ba2..fcc8d3006 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -5,21 +5,27 @@ import ( "testing" "cosmossdk.io/log" - "github.com/babylonlabs-io/babylon/testutil/signer" abci "github.com/cometbft/cometbft/abci/types" dbm "github.com/cosmos/cosmos-db" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" stktypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" + + testsigner "github.com/babylonlabs-io/babylon/testutil/signer" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) func TestBabylonBlockedAddrs(t *testing.T) { db := dbm.NewMemDB() - signer, _ := signer.SetupTestPrivSigner() + + tbs, err := testsigner.SetupTestBlsSigner() + require.NoError(t, err) + blsSigner := checkpointingtypes.BlsSigner(tbs) + logger := log.NewTestLogger(t) - app := NewBabylonAppWithCustomOptions(t, false, signer, SetupOptions{ + app := NewBabylonAppWithCustomOptions(t, false, blsSigner, SetupOptions{ Logger: logger, DB: db, InvCheckPeriod: 0, @@ -42,7 +48,7 @@ func TestBabylonBlockedAddrs(t *testing.T) { ) } - _, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{ + _, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{ Height: 1, }) require.NoError(t, err) @@ -58,7 +64,7 @@ func TestBabylonBlockedAddrs(t *testing.T) { true, map[int64]bool{}, 0, - signer, + &blsSigner, TmpAppOptions(), EmptyWasmOpts, ) @@ -73,11 +79,14 @@ func TestGetMaccPerms(t *testing.T) { func TestUpgradeStateOnGenesis(t *testing.T) { db := dbm.NewMemDB() - privSigner, err := signer.SetupTestPrivSigner() + + tbs, err := testsigner.SetupTestBlsSigner() require.NoError(t, err) + blsSigner := checkpointingtypes.BlsSigner(tbs) + logger := log.NewTestLogger(t) - app := NewBabylonAppWithCustomOptions(t, false, privSigner, SetupOptions{ + app := NewBabylonAppWithCustomOptions(t, false, blsSigner, SetupOptions{ Logger: logger, DB: db, InvCheckPeriod: 0, @@ -98,10 +107,10 @@ func TestUpgradeStateOnGenesis(t *testing.T) { func TestStakingRouterDisabled(t *testing.T) { db := dbm.NewMemDB() - signer, _ := signer.SetupTestPrivSigner() + tbs, _ := testsigner.SetupTestBlsSigner() logger := log.NewTestLogger(t) - app := NewBabylonAppWithCustomOptions(t, false, signer, SetupOptions{ + app := NewBabylonAppWithCustomOptions(t, false, tbs, SetupOptions{ Logger: logger, DB: db, InvCheckPeriod: 0, diff --git a/app/build_tags.go b/app/build_tags.go new file mode 100644 index 000000000..547e015a1 --- /dev/null +++ b/app/build_tags.go @@ -0,0 +1,5 @@ +package app + +// This variable is only set true +// when build tag is set to "e2e_upgrade" +var IsE2EUpgradeBuildFlag bool diff --git a/app/encoding.go b/app/encoding.go index d67c63bc2..766295be0 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -12,6 +12,7 @@ import ( appparams "github.com/babylonlabs-io/babylon/app/params" "github.com/babylonlabs-io/babylon/testutil/signer" bbn "github.com/babylonlabs-io/babylon/types" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) // TmpAppOptions returns an app option with tmp dir and btc network @@ -27,8 +28,11 @@ func TmpAppOptions() simsutils.AppOptionsMap { return appOpts } +// NewTmpBabylonApp returns a new BabylonApp func NewTmpBabylonApp() *BabylonApp { - signer, _ := signer.SetupTestPrivSigner() + tbs, _ := signer.SetupTestBlsSigner() + blsSigner := checkpointingtypes.BlsSigner(tbs) + return NewBabylonApp( log.NewNopLogger(), dbm.NewMemDB(), @@ -36,7 +40,7 @@ func NewTmpBabylonApp() *BabylonApp { true, map[int64]bool{}, 0, - signer, + &blsSigner, TmpAppOptions(), []wasmkeeper.Option{}) } diff --git a/app/include_upgrade_e2e.go b/app/include_upgrade_e2e.go new file mode 100644 index 000000000..8acf7f9c5 --- /dev/null +++ b/app/include_upgrade_e2e.go @@ -0,0 +1,9 @@ +//go:build e2e_upgrade + +package app + +// init is used to include v1 upgrade testnet data +// it is also used for e2e testing +func init() { + IsE2EUpgradeBuildFlag = true +} diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 9596f8b65..c0da5ac1e 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -17,9 +17,6 @@ import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - mintkeeper "github.com/babylonlabs-io/babylon/x/mint/keeper" - minttypes "github.com/babylonlabs-io/babylon/x/mint/types" - "github.com/babylonlabs-io/babylon/x/zoneconcierge" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/runtime" @@ -62,7 +59,6 @@ import ( ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" appparams "github.com/babylonlabs-io/babylon/app/params" - "github.com/babylonlabs-io/babylon/app/signer" bbn "github.com/babylonlabs-io/babylon/types" owasm "github.com/babylonlabs-io/babylon/wasmbinding" btccheckpointkeeper "github.com/babylonlabs-io/babylon/x/btccheckpoint/keeper" @@ -81,8 +77,11 @@ import ( finalitytypes "github.com/babylonlabs-io/babylon/x/finality/types" incentivekeeper "github.com/babylonlabs-io/babylon/x/incentive/keeper" incentivetypes "github.com/babylonlabs-io/babylon/x/incentive/types" + mintkeeper "github.com/babylonlabs-io/babylon/x/mint/keeper" + minttypes "github.com/babylonlabs-io/babylon/x/mint/types" monitorkeeper "github.com/babylonlabs-io/babylon/x/monitor/keeper" monitortypes "github.com/babylonlabs-io/babylon/x/monitor/types" + "github.com/babylonlabs-io/babylon/x/zoneconcierge" zckeeper "github.com/babylonlabs-io/babylon/x/zoneconcierge/keeper" zctypes "github.com/babylonlabs-io/babylon/x/zoneconcierge/types" ) @@ -173,7 +172,7 @@ func (ak *AppKeepers) InitKeepers( homePath string, invCheckPeriod uint, skipUpgradeHeights map[int64]bool, - privSigner *signer.PrivSigner, + blsSigner checkpointingtypes.BlsSigner, appOpts servertypes.AppOptions, wasmConfig wasmtypes.WasmConfig, wasmOpts []wasmkeeper.Option, @@ -266,7 +265,7 @@ func (ak *AppKeepers) InitKeepers( checkpointingKeeper := checkpointingkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[checkpointingtypes.StoreKey]), - privSigner.WrappedPV, + blsSigner, epochingKeeper, ) diff --git a/app/signer/bls.go b/app/signer/bls.go new file mode 100644 index 000000000..94c4d4073 --- /dev/null +++ b/app/signer/bls.go @@ -0,0 +1,209 @@ +package signer + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + + cmtcfg "github.com/cometbft/cometbft/config" + cmtcrypto "github.com/cometbft/cometbft/crypto" + cmtjson "github.com/cometbft/cometbft/libs/json" + cmtos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/libs/tempfile" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonlabs-io/babylon/crypto/bls12381" + "github.com/babylonlabs-io/babylon/crypto/erc2335" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" +) + +var _ checkpointingtypes.BlsSigner = &BlsKey{} + +const ( + DefaultBlsKeyName = "bls_key.json" // Default file name for BLS key + DefaultBlsPasswordName = "bls_password.txt" // Default file name for BLS password +) + +var ( + defaultBlsKeyFilePath = filepath.Join(cmtcfg.DefaultConfigDir, DefaultBlsKeyName) // Default file path for BLS key + defaultBlsPasswordPath = filepath.Join(cmtcfg.DefaultConfigDir, DefaultBlsPasswordName) // Default file path for BLS password +) + +// Bls is a wrapper around BlsKey +type Bls struct { + // Key is a structure containing bls12381 keys, + // paths of both key and password files, + // and delegator address + Key BlsKey +} + +// BlsKey is a wrapper containing bls12381 keys, +// paths of both key and password files, and delegator address. +type BlsKey struct { + PubKey bls12381.PublicKey `json:"bls_pub_key"` // Public Key of BLS + PrivKey bls12381.PrivateKey `json:"bls_priv_key"` // Private Key of BLS + filePath string // File Path of BLS Key + passwordPath string // File Path of BLS Password +} + +// NewBls returns a new Bls. +// if private key is nil, it will panic +func NewBls(privKey bls12381.PrivateKey, keyFilePath, passwordFilePath string) *Bls { + if privKey == nil { + panic("BLS private key should not be nil") + } + return &Bls{ + Key: BlsKey{ + PubKey: privKey.PubKey(), + PrivKey: privKey, + filePath: keyFilePath, + passwordPath: passwordFilePath, + }, + } +} + +// GenBls returns a new Bls after saving it to the file. +func GenBls(keyFilePath, passwordFilePath, password string) *Bls { + pv := NewBls(bls12381.GenPrivKey(), keyFilePath, passwordFilePath) + pv.Key.Save(password) + return pv +} + +// LoadBls returns a Bls after loading the erc2335 type of structure +// from the file and decrypt it using a password. +func LoadBls(keyFilePath, passwordFilePath string) *Bls { + passwordBytes, err := os.ReadFile(passwordFilePath) + if err != nil { + cmtos.Exit(fmt.Sprintf("failed to read BLS password file: %v", err.Error())) + } + password := string(passwordBytes) + + keystore, err := erc2335.LoadKeyStore(keyFilePath) + if err != nil { + cmtos.Exit(fmt.Sprintf("failed to read erc2335 keystore: %v", err.Error())) + } + + // decrypt bls key from erc2335 type of structure + privKey, err := erc2335.Decrypt(keystore, password) + if err != nil { + cmtos.Exit(fmt.Sprintf("failed to decrypt BLS key: %v", err.Error())) + } + + blsPrivKey := bls12381.PrivateKey(privKey) + return &Bls{ + Key: BlsKey{ + PubKey: blsPrivKey.PubKey(), + PrivKey: blsPrivKey, + filePath: keyFilePath, + passwordPath: passwordFilePath, + }, + } +} + +// NewBlsPassword returns a password from the user prompt. +func NewBlsPassword() string { + inBuf := bufio.NewReader(os.Stdin) + password, err := input.GetString("Enter your bls password", inBuf) + if err != nil { + cmtos.Exit("failed to get BLS password") + } + return password +} + +// Save saves the bls12381 key to the file. +// The file stores an erc2335 structure containing the encrypted bls private key. +func (k *BlsKey) Save(password string) { + // encrypt the bls12381 key to erc2335 type + erc2335BlsKey, err := erc2335.Encrypt(k.PrivKey, k.PubKey.Bytes(), password) + if err != nil { + panic(fmt.Errorf("failed to encrypt BLS key: %w", err)) + } + + // Parse the encrypted key back to Erc2335KeyStore structure + var keystore erc2335.Erc2335KeyStore + if err := json.Unmarshal(erc2335BlsKey, &keystore); err != nil { + panic(fmt.Errorf("failed to unmarshal BLS key: %w", err)) + } + + // convert keystore to json + jsonBytes, err := json.MarshalIndent(keystore, "", " ") + if err != nil { + panic(fmt.Errorf("failed to marshal BLS key: %w", err)) + } + + // write generated erc2335 keystore to file + if err := tempfile.WriteFileAtomic(k.filePath, jsonBytes, 0600); err != nil { + panic(fmt.Errorf("failed to write BLS key: %w", err)) + } + + // save used password to file + if err := tempfile.WriteFileAtomic(k.passwordPath, []byte(password), 0600); err != nil { + panic(fmt.Errorf("failed to write BLS password: %w", err)) + } +} + +// ExportGenBls writes a {address, bls_pub_key, pop, and pub_key} into a json file +func ExportGenBls(valAddress sdk.ValAddress, cmtPrivKey cmtcrypto.PrivKey, blsPrivKey bls12381.PrivateKey, filePath string) (outputFileName string, err error) { + if !cmtos.FileExists(filePath) { + return outputFileName, fmt.Errorf("input file %s does not exists", filePath) + } + + validatorKey, err := NewValidatorKeys(cmtPrivKey, blsPrivKey) + if err != nil { + return outputFileName, fmt.Errorf("failed to create validator keys: %w", err) + } + + pubkey, err := codec.FromCmtPubKeyInterface(validatorKey.ValPubkey) + if err != nil { + return outputFileName, fmt.Errorf("failed to convert validator public key: %w", err) + } + + genbls, err := checkpointingtypes.NewGenesisKey(valAddress, &validatorKey.BlsPubkey, validatorKey.PoP, pubkey) + if err != nil { + return outputFileName, fmt.Errorf("failed to create genesis key: %w", err) + } + + jsonBytes, err := cmtjson.MarshalIndent(genbls, "", " ") + if err != nil { + return outputFileName, fmt.Errorf("failed to marshal genesis key: %w", err) + } + + outputFileName = filepath.Join(filePath, fmt.Sprintf("gen-bls-%s.json", valAddress.String())) + if err := tempfile.WriteFileAtomic(outputFileName, jsonBytes, 0600); err != nil { + return outputFileName, fmt.Errorf("failed to write file: %w", err) + } + return outputFileName, nil +} + +// DefaultBlsKeyFile returns the default BLS key file path. +func DefaultBlsKeyFile(home string) string { + return filepath.Join(home, defaultBlsKeyFilePath) +} + +// DefaultBlsPasswordFile returns the default BLS password file path. +func DefaultBlsPasswordFile(home string) string { + return filepath.Join(home, defaultBlsPasswordPath) +} + +// SignMsgWithBls signs a message with BLS, implementing the BlsSigner interface +func (k *BlsKey) SignMsgWithBls(msg []byte) (bls12381.Signature, error) { + if k.PrivKey == nil { + return nil, fmt.Errorf("BLS private key does not exist: %w", checkpointingtypes.ErrBlsPrivKeyDoesNotExist) + } + return bls12381.Sign(k.PrivKey, msg), nil +} + +// BlsPubKey returns the public key of the BLS, implementing the BlsSigner interface +func (k *BlsKey) BlsPubKey() (bls12381.PublicKey, error) { + if k.PrivKey == nil { + return nil, checkpointingtypes.ErrBlsPrivKeyDoesNotExist + } + if k.PubKey == nil { + return nil, checkpointingtypes.ErrBlsKeyDoesNotExist + } + return k.PubKey, nil +} diff --git a/app/signer/bls_test.go b/app/signer/bls_test.go new file mode 100644 index 000000000..0b2ec91fd --- /dev/null +++ b/app/signer/bls_test.go @@ -0,0 +1,58 @@ +package signer + +import ( + "os" + "testing" + + "github.com/babylonlabs-io/babylon/crypto/bls12381" + "github.com/test-go/testify/assert" +) + +func TestNewBls(t *testing.T) { + tempDir := t.TempDir() + defer os.RemoveAll(tempDir) + + keyFilePath := DefaultBlsKeyFile(tempDir) + passwordFilePath := DefaultBlsPasswordFile(tempDir) + + err := EnsureDirs(keyFilePath, passwordFilePath) + assert.NoError(t, err) + + t.Run("failed when private key is nil", func(t *testing.T) { + assert.Panics(t, func() { + NewBls(nil, keyFilePath, passwordFilePath) + }) + }) + + t.Run("save bls key to file without delegator address", func(t *testing.T) { + pv := NewBls(bls12381.GenPrivKey(), keyFilePath, passwordFilePath) + assert.NotNil(t, pv) + + password := "password" + pv.Key.Save(password) + + t.Run("load bls key from file", func(t *testing.T) { + loadedPv := LoadBls(keyFilePath, passwordFilePath) + assert.NotNil(t, loadedPv) + + assert.Equal(t, pv.Key.PrivKey, loadedPv.Key.PrivKey) + assert.Equal(t, pv.Key.PubKey.Bytes(), loadedPv.Key.PubKey.Bytes()) + }) + }) + + t.Run("save bls key to file with delegator address", func(t *testing.T) { + pv := NewBls(bls12381.GenPrivKey(), keyFilePath, passwordFilePath) + assert.NotNil(t, pv) + + password := "password" + pv.Key.Save(password) + + t.Run("load bls key from file", func(t *testing.T) { + loadedPv := LoadBls(keyFilePath, passwordFilePath) + assert.NotNil(t, loadedPv) + + assert.Equal(t, pv.Key.PrivKey, loadedPv.Key.PrivKey) + assert.Equal(t, pv.Key.PubKey.Bytes(), loadedPv.Key.PubKey.Bytes()) + }) + }) +} diff --git a/app/signer/private.go b/app/signer/private.go index 8fcd1f217..6e3332e9a 100644 --- a/app/signer/private.go +++ b/app/signer/private.go @@ -1,33 +1,86 @@ package signer import ( - "path/filepath" + "fmt" cmtconfig "github.com/cometbft/cometbft/config" cmtos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/privval" - "github.com/babylonlabs-io/babylon/privval" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) -type PrivSigner struct { - WrappedPV *privval.WrappedFilePV +// ConsensusKey represents the consensus keys +type ConsensusKey struct { + Comet *privval.FilePVKey + Bls *BlsKey } -func InitPrivSigner(nodeDir string) (*PrivSigner, error) { - nodeCfg := cmtconfig.DefaultConfig() - pvKeyFile := filepath.Join(nodeDir, nodeCfg.PrivValidatorKeyFile()) - err := cmtos.EnsureDir(filepath.Dir(pvKeyFile), 0777) +// LoadConsensusKey loads the consensus keys from the node directory +// Since it loads both the FilePV and Bls from the local, +// User who runs the remote signer cannot operate this function +func LoadConsensusKey(nodeDir string) (*ConsensusKey, error) { + filePV, err := loadFilePV(nodeDir) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to load file pv key: %w", err) } - pvStateFile := filepath.Join(nodeDir, nodeCfg.PrivValidatorStateFile()) - err = cmtos.EnsureDir(filepath.Dir(pvStateFile), 0777) + bls, err := loadBls(nodeDir) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to load bls pv key: %w", err) } - wrappedPV := privval.LoadOrGenWrappedFilePV(pvKeyFile, pvStateFile) - return &PrivSigner{ - WrappedPV: wrappedPV, + return &ConsensusKey{ + Comet: &filePV.Key, + Bls: &bls.Key, }, nil } + +// InitBlsSigner initializes the bls signer +func InitBlsSigner(nodeDir string) (*checkpointingtypes.BlsSigner, error) { + bls, err := loadBls(nodeDir) + if err != nil { + return nil, fmt.Errorf("failed to load bls pv key: %w", err) + } + blsSigner := checkpointingtypes.BlsSigner(&bls.Key) + return &blsSigner, nil +} + +// loadFilePV loads the private key from the node directory in local +func loadFilePV(homeDir string) (*privval.FilePV, error) { + nodeCfg := cmtconfig.DefaultConfig() + nodeCfg.SetRoot(homeDir) + + pvKeyFile := nodeCfg.PrivValidatorKeyFile() + pvStateFile := nodeCfg.PrivValidatorStateFile() + + if err := EnsureDirs(pvKeyFile, pvStateFile); err != nil { + return nil, fmt.Errorf("failed to ensure dirs: %w", err) + } + + if !cmtos.FileExists(pvKeyFile) { + return nil, fmt.Errorf("validator key file does not exist. create file using `babylond init`: %s", pvKeyFile) + } + + filePV := privval.LoadFilePV(pvKeyFile, pvStateFile) + return filePV, nil +} + +// loadBls loads the private key from the node directory in local +func loadBls(homeDir string) (*Bls, error) { + nodeCfg := cmtconfig.DefaultConfig() + nodeCfg.SetRoot(homeDir) + + blsKeyFile := DefaultBlsKeyFile(homeDir) + blsPasswordFile := DefaultBlsPasswordFile(homeDir) + + if err := EnsureDirs(blsKeyFile, blsPasswordFile); err != nil { + return nil, fmt.Errorf("failed to ensure dirs: %w", err) + } + + if !cmtos.FileExists(blsKeyFile) || !cmtos.FileExists(blsPasswordFile) { + return nil, fmt.Errorf("BLS key file does not exist. create file using `babylond init` or `babylond create-bls-key`: %s", blsKeyFile) + } + + bls := LoadBls(blsKeyFile, blsPasswordFile) + return bls, nil +} diff --git a/privval/types.go b/app/signer/types.go similarity index 77% rename from privval/types.go rename to app/signer/types.go index be2a302f4..3765ca531 100644 --- a/privval/types.go +++ b/app/signer/types.go @@ -1,7 +1,7 @@ -package privval +package signer import ( - "errors" + "fmt" cmtcrypto "github.com/cometbft/cometbft/crypto" @@ -9,6 +9,7 @@ import ( "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) +// ValidatorKeys represents a validator keys. type ValidatorKeys struct { ValPubkey cmtcrypto.PubKey BlsPubkey bls12381.PublicKey @@ -18,10 +19,11 @@ type ValidatorKeys struct { blsPrivkey bls12381.PrivateKey } +// NewValidatorKeys creates a new instance including validator keys. func NewValidatorKeys(valPrivkey cmtcrypto.PrivKey, blsPrivKey bls12381.PrivateKey) (*ValidatorKeys, error) { pop, err := BuildPoP(valPrivkey, blsPrivKey) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to build PoP: %w", err) } return &ValidatorKeys{ ValPubkey: valPrivkey.PubKey(), @@ -36,14 +38,14 @@ func NewValidatorKeys(valPrivkey cmtcrypto.PrivKey, blsPrivKey bls12381.PrivateK // where valPrivKey is Ed25519_sk and blsPrivkey is BLS_sk func BuildPoP(valPrivKey cmtcrypto.PrivKey, blsPrivkey bls12381.PrivateKey) (*types.ProofOfPossession, error) { if valPrivKey == nil { - return nil, errors.New("validator private key is empty") + return nil, fmt.Errorf("validator private key is empty") } if blsPrivkey == nil { - return nil, errors.New("BLS private key is empty") + return nil, fmt.Errorf("BLS private key is empty") } data, err := valPrivKey.Sign(blsPrivkey.PubKey().Bytes()) if err != nil { - return nil, err + return nil, fmt.Errorf("Error while building PoP: %w", err) } pop := bls12381.Sign(blsPrivkey, data) return &types.ProofOfPossession{ diff --git a/app/signer/util.go b/app/signer/util.go new file mode 100644 index 000000000..43c7f9b57 --- /dev/null +++ b/app/signer/util.go @@ -0,0 +1,22 @@ +package signer + +import ( + "fmt" + "path/filepath" + + cmtos "github.com/cometbft/cometbft/libs/os" +) + +// EnsureDirs ensures the directories of the given paths exist. +func EnsureDirs(paths ...string) error { + // Check file path of bls key + for _, path := range paths { + if path == "" { + return fmt.Errorf("filePath for bls key not set") + } + if err := cmtos.EnsureDir(filepath.Dir(path), 0777); err != nil { + return fmt.Errorf("failed to ensure key path dir: %w", err) + } + } + return nil +} diff --git a/app/test_helpers.go b/app/test_helpers.go index 852bbe584..b38384a62 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -9,8 +9,6 @@ import ( "cosmossdk.io/log" "cosmossdk.io/math" pruningtypes "cosmossdk.io/store/pruning/types" - "github.com/babylonlabs-io/babylon/testutil/signer" - minttypes "github.com/babylonlabs-io/babylon/x/mint/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/crypto/ed25519" tmjson "github.com/cometbft/cometbft/libs/json" @@ -31,10 +29,12 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" - appparams "github.com/babylonlabs-io/babylon/app/params" appsigner "github.com/babylonlabs-io/babylon/app/signer" + "github.com/babylonlabs-io/babylon/testutil/signer" + minttypes "github.com/babylonlabs-io/babylon/x/mint/types" + + appparams "github.com/babylonlabs-io/babylon/app/params" "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" bbn "github.com/babylonlabs-io/babylon/types" btclighttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" @@ -49,7 +49,7 @@ type SetupOptions struct { AppOpts types.AppOptions } -func setup(t *testing.T, ps *appsigner.PrivSigner, withGenesis bool, invCheckPeriod uint, btcConf bbn.SupportedBtcNetwork) (*BabylonApp, GenesisState) { +func setup(t *testing.T, blsSigner checkpointingtypes.BlsSigner, withGenesis bool, invCheckPeriod uint, btcConf bbn.SupportedBtcNetwork) (*BabylonApp, GenesisState) { db := dbm.NewMemDB() nodeHome := t.TempDir() @@ -61,6 +61,7 @@ func setup(t *testing.T, ps *appsigner.PrivSigner, withGenesis bool, invCheckPer appOptions[server.FlagMempoolMaxTxs] = mempool.DefaultMaxTx appOptions[flags.FlagChainID] = "chain-test" baseAppOpts := server.DefaultBaseappOptions(appOptions) + app := NewBabylonApp( log.NewNopLogger(), db, @@ -68,7 +69,7 @@ func setup(t *testing.T, ps *appsigner.PrivSigner, withGenesis bool, invCheckPer true, map[int64]bool{}, invCheckPeriod, - ps, + &blsSigner, appOptions, EmptyWasmOpts, baseAppOpts..., @@ -83,10 +84,10 @@ func setup(t *testing.T, ps *appsigner.PrivSigner, withGenesis bool, invCheckPer // Created Babylon application will have one validator with hardcoed amount of tokens. // This is necessary as from cosmos-sdk 0.46 it is required that there is at least // one validator in validator set during InitGenesis abci call - https://github.com/cosmos/cosmos-sdk/pull/9697 -func NewBabylonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *appsigner.PrivSigner, options SetupOptions) *BabylonApp { +func NewBabylonAppWithCustomOptions(t *testing.T, isCheckTx bool, blsSigner checkpointingtypes.BlsSigner, options SetupOptions) *BabylonApp { t.Helper() // create validator set with single validator - valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) + valKeys, err := appsigner.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) require.NoError(t, err) valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) require.NoError(t, err) @@ -112,7 +113,7 @@ func NewBabylonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *ap true, options.SkipUpgradeHeights, options.InvCheckPeriod, - privSigner, + &blsSigner, options.AppOpts, EmptyWasmOpts, ) @@ -237,22 +238,29 @@ func Setup(t *testing.T, isCheckTx bool) *BabylonApp { func SetupWithBitcoinConf(t *testing.T, isCheckTx bool, btcConf bbn.SupportedBtcNetwork) *BabylonApp { t.Helper() - ps, err := signer.SetupTestPrivSigner() + tbs, err := signer.SetupTestBlsSigner() require.NoError(t, err) - valPubKey := ps.WrappedPV.Key.PubKey + blsSigner := checkpointingtypes.BlsSigner(tbs) + + cmtPrivKey := ed25519.GenPrivKey() + // generate genesis account - acc := authtypes.NewBaseAccount(valPubKey.Address().Bytes(), &cosmosed.PubKey{Key: valPubKey.Bytes()}, 0, 0) + acc := authtypes.NewBaseAccount( + cmtPrivKey.PubKey().Address().Bytes(), + &cosmosed.PubKey{Key: cmtPrivKey.PubKey().Bytes()}, + 0, + 0, + ) balance := banktypes.Balance{ Address: acc.GetAddress().String(), Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(100000000000000))), } - ps.WrappedPV.Key.DelegatorAddress = acc.GetAddress().String() // create validator set with single validator - genesisKey, err := signer.GenesisKeyFromPrivSigner(ps) + genesisKey, err := signer.GenesisKeyFromPrivSigner(cmtPrivKey, tbs.PrivKey, sdk.ValAddress(acc.GetAddress())) require.NoError(t, err) genesisValSet := []*checkpointingtypes.GenesisKey{genesisKey} - app := SetupWithGenesisValSet(t, btcConf, genesisValSet, ps, []authtypes.GenesisAccount{acc}, balance) + app := SetupWithGenesisValSet(t, btcConf, genesisValSet, blsSigner, []authtypes.GenesisAccount{acc}, balance) return app } @@ -262,9 +270,9 @@ func SetupWithBitcoinConf(t *testing.T, isCheckTx bool, btcConf bbn.SupportedBtc // of one consensus engine unit (10^6) in the default token of the babylon app from first genesis // account. A Nop logger is set in BabylonApp. // Note that the privSigner should be the 0th item of valSet -func SetupWithGenesisValSet(t *testing.T, btcConf bbn.SupportedBtcNetwork, valSet []*checkpointingtypes.GenesisKey, privSigner *appsigner.PrivSigner, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { +func SetupWithGenesisValSet(t *testing.T, btcConf bbn.SupportedBtcNetwork, valSet []*checkpointingtypes.GenesisKey, blsSigner checkpointingtypes.BlsSigner, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { t.Helper() - app, genesisState := setup(t, privSigner, true, 5, btcConf) + app, genesisState := setup(t, blsSigner, true, 5, btcConf) genesisState = genesisStateWithValSet(t, app, genesisState, valSet, genAccs, balances...) stateBytes, err := json.MarshalIndent(genesisState, "", " ") diff --git a/cmd/babylond/cmd/bls/create_bls_key.go b/cmd/babylond/cmd/bls/create_bls_key.go deleted file mode 100644 index 62139545c..000000000 --- a/cmd/babylond/cmd/bls/create_bls_key.go +++ /dev/null @@ -1,85 +0,0 @@ -package bls - -import ( - "errors" - "fmt" - "path/filepath" - "strings" - - cmtconfig "github.com/cometbft/cometbft/config" - cmtos "github.com/cometbft/cometbft/libs/os" - "github.com/cosmos/cosmos-sdk/client/flags" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/cobra" - - "github.com/babylonlabs-io/babylon/app" - appparams "github.com/babylonlabs-io/babylon/app/params" - "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" -) - -func CreateBlsKeyCmd() *cobra.Command { - bech32PrefixAccAddr := appparams.Bech32PrefixAccAddr - - cmd := &cobra.Command{ - Use: "create-bls-key [account-address]", - Args: cobra.ExactArgs(1), - Short: "Create a pair of BLS keys for a validator", - Long: strings.TrimSpace( - fmt.Sprintf(`create-bls will create a pair of BLS keys that are used to -send BLS signatures for checkpointing. - -BLS keys are stored along with other validator keys in priv_validator_key.json, -which should exist before running the command (via babylond init or babylond testnet). - -Example: -$ babylond create-bls-key %s1f5tnl46mk4dfp4nx3n2vnrvyw2h2ydz6ykhk3r --home ./ -`, - bech32PrefixAccAddr, - ), - ), - - RunE: func(cmd *cobra.Command, args []string) error { - homeDir, _ := cmd.Flags().GetString(flags.FlagHome) - - addr, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - _, err = CreateBlsKey(homeDir, addr) - return err - }, - } - - cmd.Flags().String(flags.FlagHome, app.DefaultNodeHome, "The node home directory") - - return cmd -} - -func CreateBlsKey(home string, addr sdk.AccAddress) (*privval.WrappedFilePV, error) { - nodeCfg := cmtconfig.DefaultConfig() - keyPath := filepath.Join(home, nodeCfg.PrivValidatorKeyFile()) - statePath := filepath.Join(home, nodeCfg.PrivValidatorStateFile()) - - pv, err := LoadWrappedFilePV(keyPath, statePath) - if err != nil { - return nil, err - } - - wrappedPV := privval.NewWrappedFilePV(pv.GetValPrivKey(), bls12381.GenPrivKey(), keyPath, statePath) - wrappedPV.SetAccAddress(addr) - - return wrappedPV, nil -} - -// LoadWrappedFilePV loads the wrapped file private key from the file path. -func LoadWrappedFilePV(keyPath, statePath string) (*privval.WrappedFilePV, error) { - if !cmtos.FileExists(keyPath) { - return nil, errors.New("validator key file does not exist") - } - if !cmtos.FileExists(statePath) { - return nil, errors.New("validator state file does not exist") - } - return privval.LoadWrappedFilePV(keyPath, statePath), nil -} diff --git a/cmd/babylond/cmd/cmd_test.go b/cmd/babylond/cmd/cmd_test.go index ddebd4ac7..3ff250df4 100644 --- a/cmd/babylond/cmd/cmd_test.go +++ b/cmd/babylond/cmd/cmd_test.go @@ -18,6 +18,7 @@ func TestInitCmd(t *testing.T) { "init", // Test the init cmd "app-test", // Moniker fmt.Sprintf("--%s=%s", cli.FlagOverwrite, "true"), // Overwrite genesis.json, in case it already exists + fmt.Sprintf("--%s=%s", "bls-password", "testpassword"), }) require.NoError(t, svrcmd.Execute(rootCmd, app.BabylonAppEnvPrefix, app.DefaultNodeHome)) diff --git a/cmd/babylond/cmd/create_bls_key.go b/cmd/babylond/cmd/create_bls_key.go new file mode 100644 index 000000000..21e417a12 --- /dev/null +++ b/cmd/babylond/cmd/create_bls_key.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "strings" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/babylonlabs-io/babylon/app" + appsigner "github.com/babylonlabs-io/babylon/app/signer" +) + +func CreateBlsKeyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-bls-key", + Short: "Create a pair of BLS keys for a validator", + Long: strings.TrimSpace(`create-bls will create a pair of BLS keys that are used to +send BLS signatures for checkpointing. + +BLS keys are stored along with other validator keys in priv_validator_key.json, +which should exist before running the command (via babylond init or babylond testnet). + +Example: +$ babylond create-bls-key --home ./ +`, + ), + + RunE: func(cmd *cobra.Command, args []string) error { + homeDir, _ := cmd.Flags().GetString(flags.FlagHome) + appsigner.GenBls(appsigner.DefaultBlsKeyFile(homeDir), appsigner.DefaultBlsPasswordFile(homeDir), blsPassword(cmd)) + return nil + }, + } + + cmd.Flags().String(flags.FlagHome, app.DefaultNodeHome, "The node home directory") + cmd.Flags().String(flagBlsPassword, "", "The password for the BLS key. If the flag is not set, the password will be read from the prompt.") + cmd.Flags().Bool(flagNoBlsPassword, false, "The BLS key will use an empty password if the flag is set.") + return cmd +} + +// blsPassword returns the password for the BLS key. +// If the noBlsPassword flag is set, the function returns an empty string. +// If the blsPassword flag is set but no argument, the function returns "flag needs an argument: --bls-password" error. +// If the blsPassword flag is set with non-empty string, the function returns the value of the flag. +// If the blsPassword flag is set with empty string, the function requires the user to enter a password. +// If the blsPassword flag is not set and the noBlsPassword flag is not set, the function requires the user to enter a password. +func blsPassword(cmd *cobra.Command) string { + noBlsPassword, _ := cmd.Flags().GetBool(flagNoBlsPassword) + if noBlsPassword { + return "" + } + password, _ := cmd.Flags().GetString(flagBlsPassword) + if password == "" { + return appsigner.NewBlsPassword() + } + return password +} diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 1d2d3ea75..5aaf7f452 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -53,6 +53,8 @@ const ( flagMinSignedPerWindow = "min-signed-per-window" flagFinalitySigTimeout = "finality-sig-timeout" flagJailDuration = "jail-duration" + flagBlsPassword = "bls-password" + flagNoBlsPassword = "no-bls-password" ) type GenesisCLIArgs struct { diff --git a/cmd/babylond/cmd/genhelpers/gentx.go b/cmd/babylond/cmd/genhelpers/gentx.go index 5fd726996..5c4ef9f94 100644 --- a/cmd/babylond/cmd/genhelpers/gentx.go +++ b/cmd/babylond/cmd/genhelpers/gentx.go @@ -12,14 +12,14 @@ import ( "os" "path/filepath" - "github.com/babylonlabs-io/babylon/cmd/babylond/cmd/bls" - checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" "github.com/spf13/cobra" - address "cosmossdk.io/core/address" + appsigner "github.com/babylonlabs-io/babylon/app/signer" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" + + "cosmossdk.io/core/address" sdkerrors "cosmossdk.io/errors" - "github.com/babylonlabs-io/babylon/privval" cmtconfig "github.com/cometbft/cometbft/config" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -176,28 +176,27 @@ $ %s gentx my-key-name 1000000stake --home=/path/to/home/dir --keyring-backend=o } // bls load/create - privValKeyFilePath, statePath := PathsNodeCfg(clientCtx.HomeDir) - wrappedPV := privval.LoadWrappedFilePV(privValKeyFilePath, statePath) - if wrappedPV.Key.DelegatorAddress == "" { - cmd.PrintErrf("failed to read bls keys in %q. You probably forgot to run `babylond create-bls-key`, creating...\n", privValKeyFilePath) - wrappedPV, err = bls.CreateBlsKey(clientCtx.HomeDir, addr) - if err != nil { - return sdkerrors.Wrap(err, "failed to set bls keys into priv_validator_key.json") - } - cmd.PrintErrf("successfully created bls key in %q.\n", privValKeyFilePath) + + ck, err := appsigner.LoadConsensusKey(clientCtx.HomeDir) + if err != nil { + return fmt.Errorf("failed to load key from %s: %w", clientCtx.HomeDir, err) } - filePathGenBlsOut := filepath.Join(privValKeyFilePath, fmt.Sprintf("gen-bls-%s.json", wrappedPV.GetAddress().String())) + outputFileName, err := appsigner.ExportGenBls( + sdk.ValAddress(addr), + ck.Comet.PrivKey, + ck.Bls.PrivKey, + filepath.Join(clientCtx.HomeDir, cmtconfig.DefaultConfigDir), + ) + if err != nil { + return err + } - genBls, err := checkpointingtypes.LoadGenesisKeyFromFile(filePathGenBlsOut) + genBls, err := checkpointingtypes.LoadGenesisKeyFromFile(outputFileName) if err != nil { - cmd.PrintErrf("Bls PoP not found in %q, creating...\n", filePathGenBlsOut) - genBls, _, err = wrappedPV.ExportGenBls(filepath.Dir(privValKeyFilePath)) - if err != nil { - return err - } - cmd.PrintErrf("successfully created bls PoP in %q.\n", filePathGenBlsOut) + return fmt.Errorf("failed to load BLS key from genesis file path %s: %w", outputFileName, err) } + cmd.PrintErrf("successfully created bls PoP in %q.\n", outputFileName) err = genBls.Validate() if err != nil { diff --git a/cmd/babylond/cmd/genhelpers/gentx_test.go b/cmd/babylond/cmd/genhelpers/gentx_test.go index 96e05785e..799a3b91f 100644 --- a/cmd/babylond/cmd/genhelpers/gentx_test.go +++ b/cmd/babylond/cmd/genhelpers/gentx_test.go @@ -7,10 +7,10 @@ import ( "path/filepath" "testing" - dbm "github.com/cosmos/cosmos-db" - "cosmossdk.io/log" - appparams "github.com/babylonlabs-io/babylon/app/params" + cmtconfig "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/privval" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/hd" @@ -26,9 +26,10 @@ import ( "github.com/babylonlabs-io/babylon/app" "github.com/babylonlabs-io/babylon/app/params" + appparams "github.com/babylonlabs-io/babylon/app/params" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/cmd/babylond/cmd" "github.com/babylonlabs-io/babylon/cmd/babylond/cmd/genhelpers" - "github.com/babylonlabs-io/babylon/privval" "github.com/babylonlabs-io/babylon/testutil/signer" "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) @@ -39,7 +40,7 @@ func Test_CmdGenTx(t *testing.T) { cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - signer, err := signer.SetupTestPrivSigner() + signer, err := signer.SetupTestBlsSigner() require.NoError(t, err) bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ Logger: logger, @@ -75,6 +76,24 @@ func Test_CmdGenTx(t *testing.T) { addr, _, err := testutil.GenerateSaveCoinKey(kb, keyName, "", true, algo) require.NoError(t, err) + // create BLS keys + nodeCfg := cmtconfig.DefaultConfig() + nodeCfg.SetRoot(home) + + keyPath := nodeCfg.PrivValidatorKeyFile() + statePath := nodeCfg.PrivValidatorStateFile() + blsKeyFile := appsigner.DefaultBlsKeyFile(home) + blsPasswordFile := appsigner.DefaultBlsPasswordFile(home) + + err = appsigner.EnsureDirs(keyPath, statePath, blsKeyFile, blsPasswordFile) + require.NoError(t, err) + + filePV := privval.GenFilePV(keyPath, statePath) + filePV.Key.Save() + + bls := appsigner.GenBls(blsKeyFile, blsPasswordFile, "password") + defer Clean(keyPath, statePath, blsKeyFile, blsPasswordFile) + baseFlags := []string{ fmt.Sprintf("--%s=%s", flags.FlagHome, home), fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendTest), @@ -98,15 +117,22 @@ func Test_CmdGenTx(t *testing.T) { require.NoError(t, err) // verifies if the BLS was successfully created with gentx - keyPath, statePath := genhelpers.PathsNodeCfg(home) - filePV := privval.LoadWrappedFilePV(keyPath, statePath) outputFilePath := filepath.Join(filepath.Dir(keyPath), fmt.Sprintf("gen-bls-%s.json", sdk.ValAddress(addr).String())) require.NoError(t, err) genKey, err := types.LoadGenesisKeyFromFile(outputFilePath) + require.NoError(t, err) require.Equal(t, sdk.ValAddress(addr).String(), genKey.ValidatorAddress) - require.True(t, filePV.Key.BlsPubKey.Equal(*genKey.BlsKey.Pubkey)) require.Equal(t, filePV.Key.PubKey.Bytes(), genKey.ValPubkey.Bytes()) + require.True(t, bls.Key.PubKey.Equal(*genKey.BlsKey.Pubkey)) + require.True(t, genKey.BlsKey.Pop.IsValid(*genKey.BlsKey.Pubkey, genKey.ValPubkey)) } + +// Clean removes PVKey file and PVState file +func Clean(paths ...string) { + for _, path := range paths { + _ = os.RemoveAll(filepath.Dir(path)) + } +} diff --git a/cmd/babylond/cmd/init.go b/cmd/babylond/cmd/init.go new file mode 100644 index 000000000..92e1d0a0b --- /dev/null +++ b/cmd/babylond/cmd/init.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + + appsigner "github.com/babylonlabs-io/babylon/app/signer" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/types/module" + genutil "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" +) + +// InitCmd returns the command to initialize the config. +// It runs InitCmd of cosmos-sdk first, then runs createBlsKeyAndSave. +func InitCmd(mbm module.BasicManager, defaultNodeHome string) *cobra.Command { + cosmosInitCmd := genutil.InitCmd(mbm, defaultNodeHome) + cmd := &cobra.Command{ + Use: cosmosInitCmd.Use, + Short: cosmosInitCmd.Short, + Long: `Initializes the configuration files for the validator and node. + This command also asks for a password to + generate the BLS key and encrypt it into an erc2335 structure.`, + Args: cosmosInitCmd.Args, + RunE: func(cmd *cobra.Command, args []string) error { + // run cosmos init first + if err := cosmosInitCmd.RunE(cmd, args); err != nil { + return fmt.Errorf("failed to run init command: %w", err) + } + + homeDir, _ := cmd.Flags().GetString(flags.FlagHome) + appsigner.GenBls(appsigner.DefaultBlsKeyFile(homeDir), appsigner.DefaultBlsPasswordFile(homeDir), blsPassword(cmd)) + return nil + }, + } + cmd.Flags().AddFlagSet(cosmosInitCmd.Flags()) + cmd.Flags().String(flagBlsPassword, "", "The password for the BLS key. If the flag is not set, the password will be read from the prompt.") + cmd.Flags().Bool(flagNoBlsPassword, false, "The BLS key will use an empty password if the flag is set.") + return cmd +} diff --git a/cmd/babylond/cmd/migrate_bls_key.go b/cmd/babylond/cmd/migrate_bls_key.go new file mode 100644 index 000000000..6b4d5ae9c --- /dev/null +++ b/cmd/babylond/cmd/migrate_bls_key.go @@ -0,0 +1,134 @@ +package cmd + +import ( + "bytes" + "fmt" + "os" + "strings" + + "github.com/babylonlabs-io/babylon/app" + + appsigner "github.com/babylonlabs-io/babylon/app/signer" + "github.com/babylonlabs-io/babylon/crypto/bls12381" + cmtcfg "github.com/cometbft/cometbft/config" + cmtcrypto "github.com/cometbft/cometbft/crypto" + cmtjson "github.com/cometbft/cometbft/libs/json" + cmtos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/privval" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" +) + +// PrevWrappedFilePV is a struct for prev version of priv_validator_key.json +type PrevWrappedFilePV struct { + PrivKey cmtcrypto.PrivKey `json:"priv_key"` + BlsPrivKey bls12381.PrivateKey `json:"bls_priv_key"` +} + +// MigrateBlsKeyCmd returns a command to migrate the bls keys +func MigrateBlsKeyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "migrate-bls-key", + Short: "Migrate the contents of the priv_validator_key.json file into separate files of bls and comet", + Long: strings.TrimSpace(`Migration splits the contents of the priv_validator_key.json file, + which contained both the bls and comet keys used in previous versions, into separate files. + +BLS keys are stored along with the Ed25519 validator key in priv_validator_key.json in the previous version, +which should exist before running the command (via babylond init or babylond testnet). + +NOTE: Before proceeding with the migration, ensure you back up the priv_validator_key.json file to a secure location. +This will help prevent potential loss of critical validator information in case of issues during the migration process. + +Example: +$ babylond migrate-bls-key --home ./ +`, + ), + + RunE: func(cmd *cobra.Command, args []string) error { + homeDir, _ := cmd.Flags().GetString(flags.FlagHome) + return migrate(homeDir, blsPassword(cmd)) + }, + } + + cmd.Flags().String(flags.FlagHome, app.DefaultNodeHome, "The node home directory") + cmd.Flags().String(flagBlsPassword, "", "The password for the BLS key. If the flag is not set, the password will be read from the prompt.") + cmd.Flags().Bool(flagNoBlsPassword, false, "The BLS key will use an empty password if the flag is set.") + return cmd +} + +// migrate splits the contents of the priv_validator_key.json file, +// which contained both the bls and comet keys used in previous versions, into separate files. +// After saving keys to separate files, it verifies if the migrated keys match +func migrate(homeDir, password string) error { + cmtcfg := cmtcfg.DefaultConfig() + cmtcfg.SetRoot(homeDir) + + filepath := cmtcfg.PrivValidatorKeyFile() + + if !cmtos.FileExists(filepath) { + return fmt.Errorf("priv_validator_key.json of previous version not found in %s", filepath) + } + + pv, err := loadPrevWrappedFilePV(filepath) + if err != nil { + return fmt.Errorf("failed to load previous version of priv_validator_key.json in %s", filepath) + } + + prevCmtPrivKey := pv.PrivKey + prevBlsPrivKey := pv.BlsPrivKey + + if prevCmtPrivKey == nil || prevBlsPrivKey == nil { + return fmt.Errorf("priv_validator_key.json of previous version does not contain both the comet and bls keys") + } + + cmtKeyFilePath := cmtcfg.PrivValidatorKeyFile() + cmtStateFilePath := cmtcfg.PrivValidatorStateFile() + blsKeyFilePath := appsigner.DefaultBlsKeyFile(homeDir) + blsPasswordFilePath := appsigner.DefaultBlsPasswordFile(homeDir) + + cmtPv := privval.NewFilePV(prevCmtPrivKey, cmtKeyFilePath, cmtStateFilePath) + bls := appsigner.NewBls(prevBlsPrivKey, blsKeyFilePath, blsPasswordFilePath) + + // save key to files after verification + cmtPv.Save() + bls.Key.Save(password) + + if err := verifySeparateFiles( + cmtKeyFilePath, cmtStateFilePath, blsKeyFilePath, blsPasswordFilePath, + prevCmtPrivKey, prevBlsPrivKey, + ); err != nil { + return fmt.Errorf("failed to verify separate files: %w", err) + } + + return nil +} + +// loadPrevWrappedFilePV loads a prev version of priv_validator_key.json +func loadPrevWrappedFilePV(filePath string) (*PrevWrappedFilePV, error) { + keyJSONBytes, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("Error reading PrivValidator key from %v: %v\n", filePath, err) + } + pvKey := PrevWrappedFilePV{} + err = cmtjson.Unmarshal(keyJSONBytes, &pvKey) + if err != nil { + return nil, fmt.Errorf("Error reading PrivValidator key from %v: %v\n", filePath, err) + } + return &pvKey, nil +} + +// verifySeparateFiles checks if the migrated keys match +// after saving keys to separate files +func verifySeparateFiles( + cmtKeyFilePath, cmtStateFilePath, blsKeyFilePath, blsPasswordFilePath string, + prevCmtPrivKey cmtcrypto.PrivKey, + prevBlsPrivKey bls12381.PrivateKey, +) error { + cmtPv := privval.LoadFilePV(cmtKeyFilePath, cmtStateFilePath) + bls := appsigner.LoadBls(blsKeyFilePath, blsPasswordFilePath) + + if bytes.Equal(prevCmtPrivKey.Bytes(), cmtPv.Key.PrivKey.Bytes()) && bytes.Equal(prevBlsPrivKey, bls.Key.PrivKey) { + return nil + } + return fmt.Errorf("migrated keys do not match") +} diff --git a/cmd/babylond/cmd/migrate_bls_key_test.go b/cmd/babylond/cmd/migrate_bls_key_test.go new file mode 100644 index 000000000..40c5116a2 --- /dev/null +++ b/cmd/babylond/cmd/migrate_bls_key_test.go @@ -0,0 +1,134 @@ +package cmd + +import ( + "os" + "path/filepath" + "testing" + + "github.com/babylonlabs-io/babylon/crypto/bls12381" + "github.com/cometbft/cometbft/crypto/ed25519" + cmtjson "github.com/cometbft/cometbft/libs/json" + "github.com/stretchr/testify/require" +) + +func TestMigrate(t *testing.T) { + tempDir := t.TempDir() + defer os.RemoveAll(tempDir) + + t.Run("file not found", func(t *testing.T) { + err := migrate(tempDir, "") + require.Error(t, err) + require.Contains(t, err.Error(), "priv_validator_key.json of previous version not found") + }) + + t.Run("invalid json format", func(t *testing.T) { + // Create invalid json file + configDir := filepath.Join(tempDir, "config") + err := os.MkdirAll(configDir, 0755) + require.NoError(t, err) + + pvKeyFile := filepath.Join(configDir, "priv_validator_key.json") + err = os.WriteFile(pvKeyFile, []byte("invalid json"), 0644) + require.NoError(t, err) + + err = migrate(tempDir, "") + require.Error(t, err) + }) + + t.Run("missing keys", func(t *testing.T) { + // Create json file with missing keys + configDir := filepath.Join(tempDir, "config") + err := os.MkdirAll(configDir, 0755) + require.NoError(t, err) + + pvKeyFile := filepath.Join(configDir, "priv_validator_key.json") + pvKey := PrevWrappedFilePV{} + jsonBytes, err := cmtjson.MarshalIndent(pvKey, "", " ") + require.NoError(t, err) + + err = os.WriteFile(pvKeyFile, jsonBytes, 0644) + require.NoError(t, err) + + err = migrate(tempDir, "") + require.Error(t, err) + require.Contains(t, err.Error(), "does not contain both the comet and bls keys") + }) + + t.Run("successful migration", func(t *testing.T) { + // Create valid priv_validator_key.json + configDir := filepath.Join(tempDir, "config") + err := os.MkdirAll(configDir, 0755) + require.NoError(t, err) + + dataDir := filepath.Join(tempDir, "data") + err = os.MkdirAll(dataDir, 0755) + require.NoError(t, err) + + pvKeyFile := filepath.Join(configDir, "priv_validator_key.json") + pvKey := PrevWrappedFilePV{ + PrivKey: ed25519.GenPrivKey(), + BlsPrivKey: bls12381.GenPrivKey(), + } + jsonBytes, err := cmtjson.MarshalIndent(pvKey, "", " ") + require.NoError(t, err) + + err = os.WriteFile(pvKeyFile, jsonBytes, 0644) + require.NoError(t, err) + + // Run migration + err = migrate(tempDir, "testpassword") + require.NoError(t, err) + + // Check if new files are created + newPvKeyFile := filepath.Join(configDir, "priv_validator_key.json") + newPvStateFile := filepath.Join(dataDir, "priv_validator_state.json") + newBlsKeyFile := filepath.Join(configDir, "bls_key.json") + newBlsPasswordFile := filepath.Join(configDir, "bls_password.txt") + require.FileExists(t, newPvKeyFile) + require.FileExists(t, newPvStateFile) + require.FileExists(t, newBlsKeyFile) + require.FileExists(t, newBlsPasswordFile) + + t.Run("verify separated files", func(t *testing.T) { + verifySeparateFiles( + newPvKeyFile, newPvStateFile, newBlsKeyFile, newBlsPasswordFile, + pvKey.PrivKey, pvKey.BlsPrivKey, + ) + }) + }) +} + +func TestLoadPrevWrappedFilePV(t *testing.T) { + tempDir := t.TempDir() + pvKeyFile := filepath.Join(tempDir, "priv_validator_key.json") + + t.Run("file not found", func(t *testing.T) { + _, err := loadPrevWrappedFilePV(pvKeyFile) + require.Error(t, err) + }) + + t.Run("invalid json", func(t *testing.T) { + err := os.WriteFile(pvKeyFile, []byte("invalid json"), 0644) + require.NoError(t, err) + + _, err = loadPrevWrappedFilePV(pvKeyFile) + require.Error(t, err) + }) + + t.Run("valid file", func(t *testing.T) { + pvKey := PrevWrappedFilePV{ + PrivKey: ed25519.GenPrivKey(), + BlsPrivKey: bls12381.GenPrivKey(), + } + jsonBytes, err := cmtjson.MarshalIndent(pvKey, "", " ") + require.NoError(t, err) + + err = os.WriteFile(pvKeyFile, jsonBytes, 0644) + require.NoError(t, err) + + loadedPvKey, err := loadPrevWrappedFilePV(pvKeyFile) + require.NoError(t, err) + require.NotNil(t, loadedPvKey.PrivKey) + require.NotNil(t, loadedPvKey.BlsPrivKey) + }) +} diff --git a/cmd/babylond/cmd/root.go b/cmd/babylond/cmd/root.go index 4ccf1e0a2..c7dca5f39 100644 --- a/cmd/babylond/cmd/root.go +++ b/cmd/babylond/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "errors" + "fmt" "io" "os" "strings" @@ -10,7 +11,6 @@ import ( confixcmd "cosmossdk.io/tools/confix/cmd" "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - "github.com/babylonlabs-io/babylon/app/signer" cmtcfg "github.com/cometbft/cometbft/config" cmtcli "github.com/cometbft/cometbft/libs/cli" dbm "github.com/cosmos/cosmos-db" @@ -23,6 +23,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + appsigner "github.com/babylonlabs-io/babylon/app/signer" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -43,8 +45,8 @@ import ( "github.com/babylonlabs-io/babylon/app" "github.com/babylonlabs-io/babylon/app/params" - "github.com/babylonlabs-io/babylon/cmd/babylond/cmd/bls" "github.com/babylonlabs-io/babylon/cmd/babylond/cmd/genhelpers" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) // NewRootCmd creates a new root command for babylond. It is called once in the @@ -75,12 +77,12 @@ func NewRootCmd() *cobra.Command { initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) if err != nil { - return err + return fmt.Errorf("failed to read command flags: %w", err) } initClientCtx, err = config.ReadFromClientConfig(initClientCtx) if err != nil { - return err + return fmt.Errorf("failed to read client config: %w", err) } if !initClientCtx.Offline { @@ -97,14 +99,14 @@ func NewRootCmd() *cobra.Command { txConfigOpts, ) if err != nil { - return err + return fmt.Errorf("failed to create tx config: %w", err) } initClientCtx = initClientCtx.WithTxConfig(txConfig) } if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { - return err + return fmt.Errorf("failed to set cmd client context handler: %w", err) } customAppTemplate, customAppConfig := initAppConfig() @@ -113,7 +115,7 @@ func NewRootCmd() *cobra.Command { err = server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCometConfig) if err != nil { - return err + return fmt.Errorf("failed to intercept configs: %w", err) } return nil @@ -140,7 +142,7 @@ func NewRootCmd() *cobra.Command { // EnhanceRootCommandWithoutTxStaking excludes staking tx commands func EnhanceRootCommandWithoutTxStaking(autoCliOpts autocli.AppOptions, rootCmd *cobra.Command) { if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { - panic(err) + panic(fmt.Errorf("failed to enhance root command: %w", err)) } txCmd := FindSubCommand(rootCmd, "tx") @@ -205,7 +207,7 @@ func initRootCmd(rootCmd *cobra.Command, txConfig client.TxEncodingConfig, basic gentxModule := basicManager[genutiltypes.ModuleName].(genutil.AppModuleBasic) rootCmd.AddCommand( - genutilcli.InitCmd(basicManager, app.DefaultNodeHome), + InitCmd(basicManager, app.DefaultNodeHome), genhelpers.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, app.DefaultNodeHome, gentxModule.GenTxValidator, authcodec.NewBech32Codec(params.Bech32PrefixValAddr)), genutilcli.MigrateGenesisCmd(genutilcli.MigrationMap), genhelpers.GenTxCmd(basicManager, txConfig, banktypes.GenesisBalancesIterator{}, app.DefaultNodeHome, authcodec.NewBech32Codec(params.Bech32PrefixValAddr)), @@ -215,7 +217,8 @@ func initRootCmd(rootCmd *cobra.Command, txConfig client.TxEncodingConfig, basic cmtcli.NewCompletionCmd(rootCmd, true), TestnetCmd(basicManager, banktypes.GenesisBalancesIterator{}), genhelpers.CmdGenHelpers(gentxModule.GenTxValidator), - bls.CreateBlsKeyCmd(), + MigrateBlsKeyCmd(), + CreateBlsKeyCmd(), ModuleSizeCmd(), DebugCmd(), confixcmd.ConfigCommand(), @@ -301,9 +304,13 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts serverty } homeDir := cast.ToString(appOpts.Get(flags.FlagHome)) - privSigner, err := signer.InitPrivSigner(homeDir) + + // auto migrate when build tag is set to "e2e_upgrade" + automigrate_e2e_upgrade(logger, homeDir) + + blsSigner, err := appsigner.InitBlsSigner(homeDir) if err != nil { - panic(err) + panic(fmt.Errorf("failed to initialize priv signer: %w", err)) } var wasmOpts []wasmkeeper.Option @@ -314,7 +321,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts serverty return app.NewBabylonApp( logger, db, traceStore, true, skipUpgradeHeights, cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), - privSigner, + blsSigner, appOpts, wasmOpts, baseappOptions..., @@ -339,19 +346,41 @@ func appExport( return servertypes.ExportedApp{}, errors.New("application home not set") } - privSigner, err := signer.InitPrivSigner(homePath) + ck, err := appsigner.LoadConsensusKey(homePath) if err != nil { - panic(err) + panic(fmt.Errorf("failed to initialize priv signer: %w", err)) } + + blsSigner := checkpointingtypes.BlsSigner(ck.Bls) + if height != -1 { - babylonApp = app.NewBabylonApp(logger, db, traceStore, false, map[int64]bool{}, uint(1), privSigner, appOpts, app.EmptyWasmOpts) + babylonApp = app.NewBabylonApp(logger, db, traceStore, false, map[int64]bool{}, uint(1), &blsSigner, appOpts, app.EmptyWasmOpts) if err = babylonApp.LoadHeight(height); err != nil { - return servertypes.ExportedApp{}, err + return servertypes.ExportedApp{}, fmt.Errorf("failed to load height: %w", err) } } else { - babylonApp = app.NewBabylonApp(logger, db, traceStore, true, map[int64]bool{}, uint(1), privSigner, appOpts, app.EmptyWasmOpts) + babylonApp = app.NewBabylonApp(logger, db, traceStore, true, map[int64]bool{}, uint(1), &blsSigner, appOpts, app.EmptyWasmOpts) } return babylonApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) } + +// automigrate_e2e_upgrade_test runs when the build tag is set to "e2e_upgrade". +// It always checks if the key structure is the previous version +// and migrates into a separate version of the divided key files +func automigrate_e2e_upgrade(logger log.Logger, homeDir string) { + if app.IsE2EUpgradeBuildFlag { + logger.Debug( + "***************************************************************************\n" + + "NOTE: In testnet mode, it will automatically migrate the key file\n" + + "if priv_validator_key.json contains both the comet and bls keys,\n" + + "used in previous version.\n" + + "Do not run it in a production environment, as it may cause problems.\n" + + "***************************************************************************\n", + ) + if err := migrate(homeDir, "password"); err != nil { + logger.Debug(err.Error()) + } + } +} diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 994d3bc1d..935d03579 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -38,7 +38,7 @@ import ( "github.com/babylonlabs-io/babylon/cmd/babylond/cmd/genhelpers" appparams "github.com/babylonlabs-io/babylon/app/params" - "github.com/babylonlabs-io/babylon/privval" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" @@ -180,7 +180,7 @@ func InitTestnet( genesisParams GenesisParams, ) error { nodeIDs := make([]string, numValidators) - valKeys := make([]*privval.ValidatorKeys, numValidators) + valKeys := make([]*appsigner.ValidatorKeys, numValidators) babylonConfig := DefaultBabylonAppConfig() babylonConfig.MinGasPrices = minGasPrices diff --git a/cmd/babylond/cmd/testnet_test.go b/cmd/babylond/cmd/testnet_test.go index be1002310..6dc404647 100644 --- a/cmd/babylond/cmd/testnet_test.go +++ b/cmd/babylond/cmd/testnet_test.go @@ -19,6 +19,7 @@ import ( "github.com/babylonlabs-io/babylon/app" "github.com/babylonlabs-io/babylon/testutil/signer" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) func Test_TestnetCmd(t *testing.T) { @@ -27,9 +28,11 @@ func Test_TestnetCmd(t *testing.T) { cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - signer, err := signer.SetupTestPrivSigner() + tbs, err := signer.SetupTestBlsSigner() require.NoError(t, err) - bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + blsSigner := checkpointingtypes.BlsSigner(tbs) + + bbn := app.NewBabylonAppWithCustomOptions(t, false, blsSigner, app.SetupOptions{ Logger: logger, DB: dbm.NewMemDB(), InvCheckPeriod: 0, diff --git a/cmd/babylond/cmd/validate_genesis_test.go b/cmd/babylond/cmd/validate_genesis_test.go index feb82fe99..23aad6ad5 100644 --- a/cmd/babylond/cmd/validate_genesis_test.go +++ b/cmd/babylond/cmd/validate_genesis_test.go @@ -90,9 +90,11 @@ func generateTestGenesisState(t *testing.T, home string, n int) (*app.BabylonApp logger := log.NewNopLogger() cfg, _ := genutiltest.CreateDefaultCometConfig(home) - signer, err := signer.SetupTestPrivSigner() + tbs, err := signer.SetupTestBlsSigner() require.NoError(t, err) - bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + blsSigner := checkpointingtypes.BlsSigner(tbs) + + bbn := app.NewBabylonAppWithCustomOptions(t, false, blsSigner, app.SetupOptions{ Logger: logger, DB: dbm.NewMemDB(), InvCheckPeriod: 0, diff --git a/contrib/images/Makefile b/contrib/images/Makefile index ef41b46f5..3f4173eb4 100644 --- a/contrib/images/Makefile +++ b/contrib/images/Makefile @@ -9,7 +9,7 @@ babylond: babylond-rmi babylond-e2e: docker build --tag babylonlabs-io/babylond -f babylond/Dockerfile ${BABYLON_FULL_PATH} \ - --build-arg BABYLON_BUILD_OPTIONS="testnet" + --build-arg BABYLON_BUILD_OPTIONS="testnet e2e_upgrade" babylond-rmi: docker rmi babylonlabs-io/babylond --force 2>/dev/null; true diff --git a/crypto/erc2335/erc2335.go b/crypto/erc2335/erc2335.go new file mode 100644 index 000000000..e018f7250 --- /dev/null +++ b/crypto/erc2335/erc2335.go @@ -0,0 +1,62 @@ +package erc2335 + +import ( + "encoding/json" + "fmt" + "os" + + keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" +) + +// Erc2335KeyStore represents an ERC-2335 compatible keystore used in keystorev4. +type Erc2335KeyStore struct { + Crypto map[string]interface{} `json:"crypto"` // Map containing the encryption details for the keystore such as checksum, cipher, and kdf. + Version uint `json:"version"` // Version of the keystore format (e.g., 4 for keystorev4). + UUID string `json:"uuid"` // Unique identifier for the keystore. + Path string `json:"path"` // File path where the keystore is stored. + Pubkey string `json:"pubkey"` // Public key associated with the keystore, stored as a hexadecimal string. + Description string `json:"description"` // Optional description of the keystore, currently used to store the delegator address. +} + +// Encrypt encrypts the private key using the keystorev4 encryptor. +func Encrypt(privKey, pubKey []byte, password string) ([]byte, error) { + if privKey == nil { + return nil, fmt.Errorf("private key cannot be nil") + } + + encryptor := keystorev4.New() + cryptoFields, err := encryptor.Encrypt(privKey, password) + if err != nil { + return nil, fmt.Errorf("failed to encrypt private key: %w", err) + } + + keystoreJSON := Erc2335KeyStore{ + Crypto: cryptoFields, + Version: 4, + Pubkey: fmt.Sprintf("%x", pubKey), + } + + return json.Marshal(keystoreJSON) +} + +// Decrypt decrypts the private key from the keystore using the given password. +func Decrypt(keystore Erc2335KeyStore, password string) ([]byte, error) { + encryptor := keystorev4.New() + return encryptor.Decrypt(keystore.Crypto, password) +} + +// LoadKeyStore loads a keystore from a file. +func LoadKeyStore(filePath string) (Erc2335KeyStore, error) { + var keystore Erc2335KeyStore + + keyJSONBytes, err := os.ReadFile(filePath) + if err != nil { + return Erc2335KeyStore{}, fmt.Errorf("failed to read keystore file: %w", err) + } + + if err := json.Unmarshal(keyJSONBytes, &keystore); err != nil { + return Erc2335KeyStore{}, fmt.Errorf("failed to unmarshal keystore: %w", err) + } + + return keystore, nil +} diff --git a/crypto/erc2335/erc2335_test.go b/crypto/erc2335/erc2335_test.go new file mode 100644 index 000000000..9706b599a --- /dev/null +++ b/crypto/erc2335/erc2335_test.go @@ -0,0 +1,94 @@ +package erc2335 + +import ( + "encoding/json" + "os" + "testing" + + "github.com/babylonlabs-io/babylon/crypto/bls12381" + "github.com/cometbft/cometbft/libs/tempfile" + "github.com/test-go/testify/require" +) + +const password string = "password" + +func TestEncryptBLS(t *testing.T) { + t.Run("create bls key", func(t *testing.T) { + blsPrivKey := bls12381.GenPrivKey() + blsPubKey := blsPrivKey.PubKey().Bytes() + + t.Run("encrypt bls key", func(t *testing.T) { + encryptedBlsKey, err := Encrypt(blsPrivKey, blsPubKey, password) + require.NoError(t, err) + t.Logf("encrypted bls key: %s", encryptedBlsKey) + + t.Run("decrypt bls key", func(t *testing.T) { + var keystore Erc2335KeyStore + err = json.Unmarshal(encryptedBlsKey, &keystore) + require.NoError(t, err) + + decryptedBlsKey, err := Decrypt(keystore, password) + require.NoError(t, err) + require.Equal(t, blsPrivKey, bls12381.PrivateKey(decryptedBlsKey)) + }) + + t.Run("decrypt bls key with wrong password", func(t *testing.T) { + var keystore Erc2335KeyStore + err = json.Unmarshal(encryptedBlsKey, &keystore) + require.NoError(t, err) + _, err := Decrypt(keystore, "wrong password") + require.Error(t, err) + }) + }) + + t.Run("save password and encrypt bls key", func(t *testing.T) { + encryptedBlsKey, err := Encrypt(blsPrivKey, blsPubKey, password) + require.NoError(t, err) + t.Logf("encrypted bls key: %s", encryptedBlsKey) + err = tempfile.WriteFileAtomic("password.txt", []byte(password), 0600) + require.NoError(t, err) + + t.Run("load password and decrypt bls key", func(t *testing.T) { + passwordBytes, err := os.ReadFile("password.txt") + require.NoError(t, err) + password := string(passwordBytes) + + var keystore Erc2335KeyStore + err = json.Unmarshal(encryptedBlsKey, &keystore) + require.NoError(t, err) + + decryptedBlsKey, err := Decrypt(keystore, password) + require.NoError(t, err) + require.Equal(t, blsPrivKey, bls12381.PrivateKey(decryptedBlsKey)) + }) + + t.Run("save new password into same file", func(t *testing.T) { + newPassword := "new password" + err = tempfile.WriteFileAtomic("password.txt", []byte(newPassword), 0600) + require.NoError(t, err) + }) + + t.Run("failed when load different password and decrypt bls key", func(t *testing.T) { + passwordBytes, err := os.ReadFile("password.txt") + require.NoError(t, err) + password := string(passwordBytes) + + var keystore Erc2335KeyStore + err = json.Unmarshal(encryptedBlsKey, &keystore) + require.NoError(t, err) + + _, err = Decrypt(keystore, password) + require.Error(t, err) + }) + + t.Run("failed when password file don't exist", func(t *testing.T) { + _, err := os.ReadFile("nopassword.txt") + require.Error(t, err) + }) + }) + + t.Run("clean test files", func(t *testing.T) { + _ = os.RemoveAll("password.txt") + }) + }) +} diff --git a/go.mod b/go.mod index f78133b4c..853b2f54e 100644 --- a/go.mod +++ b/go.mod @@ -228,8 +228,10 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.7.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect + github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect diff --git a/go.sum b/go.sum index c25346d86..a2d6a3346 100644 --- a/go.sum +++ b/go.sum @@ -476,6 +476,8 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/ferranbt/fastssz v0.0.0-20210120143747-11b9eff30ea9 h1:9VDpsWq096+oGMDTT/SgBD/VgZYf4pTF+KTPmZ+OaKM= +github.com/ferranbt/fastssz v0.0.0-20210120143747-11b9eff30ea9/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= @@ -648,6 +650,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -742,6 +745,8 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/herumi/bls-eth-go-binary v0.0.0-20210130185500-57372fb27371 h1:LEw2KkKciJEr3eKDLzdZ/rjzSR6Y+BS6xKxdA78Bq6s= +github.com/herumi/bls-eth-go-binary v0.0.0-20210130185500-57372fb27371/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= @@ -845,6 +850,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -856,6 +863,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= @@ -1072,6 +1081,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1088,6 +1099,12 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vulpine-io/io-test v1.0.0 h1:Ot8vMh+ssm1VWDAwJ3U4C5qG9aRnr5YfQFZPNZBAUGI= github.com/vulpine-io/io-test v1.0.0/go.mod h1:X1I+p5GCxVX9m4nFd1HBtr2bVX9v1ZE6x8w+Obt36AU= +github.com/wealdtech/go-eth2-types/v2 v2.5.2 h1:tiA6T88M6XQIbrV5Zz53l1G5HtRERcxQfmET225V4Ls= +github.com/wealdtech/go-eth2-types/v2 v2.5.2/go.mod h1:8lkNUbgklSQ4LZ2oMSuxSdR7WwJW3L9ge1dcoCVyzws= +github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 h1:SxrDVSr+oXuT1x8kZt4uWqNCvv5xXEGV9zd7cuSrZS8= +github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3/go.mod h1:qiIimacW5NhVRy8o+YxWo9YrecXqDAKKbL0+sOa0SJ4= +github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2 h1:264/meVYWt1wFw6Mtn+xwkZkXjID42gNra4rycoiDXI= +github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2/go.mod h1:k6kmiKWSWBTd4OxFifTEkPaBLhZspnO2KFD5XJY9nqg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1162,6 +1179,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= @@ -1409,6 +1427,7 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/privval/file.go b/privval/file.go deleted file mode 100644 index 0bdcfb245..000000000 --- a/privval/file.go +++ /dev/null @@ -1,296 +0,0 @@ -package privval - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - cmtcrypto "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - cmtjson "github.com/cometbft/cometbft/libs/json" - cmtos "github.com/cometbft/cometbft/libs/os" - "github.com/cometbft/cometbft/libs/tempfile" - "github.com/cometbft/cometbft/privval" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cometbft/cometbft/types" - "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/babylonlabs-io/babylon/crypto/bls12381" - checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" -) - -// copied from github.com/cometbft/cometbft/privval/file.go" -// -//nolint:unused -const ( - stepNone int8 = 0 // Used to distinguish the initial state - stepPropose int8 = 1 - stepPrevote int8 = 2 - stepPrecommit int8 = 3 -) - -// copied from github.com/cometbft/cometbft/privval/file.go" -// -//nolint:unused -func voteToStep(vote *cmtproto.Vote) int8 { - switch vote.Type { - case cmtproto.PrevoteType: - return stepPrevote - case cmtproto.PrecommitType: - return stepPrecommit - default: - panic(fmt.Sprintf("Unknown vote type: %v", vote.Type)) - } -} - -// WrappedFilePVKey wraps FilePVKey with BLS keys. -type WrappedFilePVKey struct { - DelegatorAddress string `json:"acc_address"` - Address types.Address `json:"address"` - PubKey cmtcrypto.PubKey `json:"pub_key"` - PrivKey cmtcrypto.PrivKey `json:"priv_key"` - BlsPubKey bls12381.PublicKey `json:"bls_pub_key"` - BlsPrivKey bls12381.PrivateKey `json:"bls_priv_key"` - - filePath string -} - -// Save persists the FilePVKey to its filePath. -func (pvKey WrappedFilePVKey) Save() { - outFile := pvKey.filePath - if outFile == "" { - panic("cannot save PrivValidator key: filePath not set") - } - - jsonBytes, err := cmtjson.MarshalIndent(pvKey, "", " ") - if err != nil { - panic(err) - } - - if err := tempfile.WriteFileAtomic(outFile, jsonBytes, 0600); err != nil { - panic(err) - } -} - -// ------------------------------------------------------------------------------- - -// WrappedFilePV wraps FilePV with WrappedFilePVKey. -type WrappedFilePV struct { - Key WrappedFilePVKey - LastSignState privval.FilePVLastSignState -} - -// NewWrappedFilePV wraps FilePV -func NewWrappedFilePV(privKey cmtcrypto.PrivKey, blsPrivKey bls12381.PrivateKey, keyFilePath, stateFilePath string) *WrappedFilePV { - filePV := privval.NewFilePV(privKey, keyFilePath, stateFilePath) - return &WrappedFilePV{ - Key: WrappedFilePVKey{ - Address: privKey.PubKey().Address(), - PubKey: privKey.PubKey(), - PrivKey: privKey, - BlsPubKey: blsPrivKey.PubKey(), - BlsPrivKey: blsPrivKey, - filePath: keyFilePath, - }, - LastSignState: filePV.LastSignState, - } -} - -// GenWrappedFilePV generates a new validator with randomly generated private key -// and sets the filePaths, but does not call Save(). -func GenWrappedFilePV(keyFilePath, stateFilePath string) *WrappedFilePV { - return NewWrappedFilePV(ed25519.GenPrivKey(), bls12381.GenPrivKey(), keyFilePath, stateFilePath) -} - -// LoadWrappedFilePV loads a FilePV from the filePaths. The FilePV handles double -// signing prevention by persisting data to the stateFilePath. If either file path -// does not exist, the program will exit. -func LoadWrappedFilePV(keyFilePath, stateFilePath string) *WrappedFilePV { - return loadWrappedFilePV(keyFilePath, stateFilePath, true) -} - -// LoadWrappedFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState. -// If the keyFilePath does not exist, the program will exit. -func LoadWrappedFilePVEmptyState(keyFilePath, stateFilePath string) *WrappedFilePV { - return loadWrappedFilePV(keyFilePath, stateFilePath, false) -} - -// If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState. -func loadWrappedFilePV(keyFilePath, stateFilePath string, loadState bool) *WrappedFilePV { - keyFilePath = filepath.Clean(keyFilePath) - keyJSONBytes, err := os.ReadFile(keyFilePath) - if err != nil { - cmtos.Exit(err.Error()) - } - pvKey := WrappedFilePVKey{} - err = cmtjson.Unmarshal(keyJSONBytes, &pvKey) - if err != nil { - cmtos.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err)) - } - - // overwrite pubkey and address for convenience - pvKey.PubKey = pvKey.PrivKey.PubKey() - pvKey.Address = pvKey.PubKey.Address() - pvKey.BlsPubKey = pvKey.BlsPrivKey.PubKey() - pvKey.filePath = keyFilePath - - pvState := privval.FilePVLastSignState{} - - if loadState { - stateFilePath := filepath.Clean(stateFilePath) - stateJSONBytes, err := os.ReadFile(stateFilePath) - if err != nil { - cmtos.Exit(err.Error()) - } - err = cmtjson.Unmarshal(stateJSONBytes, &pvState) - if err != nil { - cmtos.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err)) - } - } - - // adding path is not needed - // pvState.filePath = stateFilePath - - return &WrappedFilePV{ - Key: pvKey, - LastSignState: pvState, - } -} - -// LoadOrGenWrappedFilePV loads a FilePV from the given filePaths -// or else generates a new one and saves it to the filePaths. -func LoadOrGenWrappedFilePV(keyFilePath, stateFilePath string) *WrappedFilePV { - var pv *WrappedFilePV - if cmtos.FileExists(keyFilePath) { - pv = LoadWrappedFilePV(keyFilePath, stateFilePath) - } else { - pv = GenWrappedFilePV(keyFilePath, stateFilePath) - pv.Save() - } - return pv -} - -// ExportGenBls writes a {address, bls_pub_key, pop, and pub_key} into a json file -func (pv *WrappedFilePV) ExportGenBls(filePath string) (genbls *checkpointingtypes.GenesisKey, outputFileName string, err error) { - if !cmtos.FileExists(filePath) { - return nil, outputFileName, errors.New("export file path does not exist") - } - - valAddress := pv.GetAddress() - if valAddress.Empty() { - return nil, outputFileName, errors.New("validator address should not be empty") - } - - validatorKey, err := NewValidatorKeys(pv.GetValPrivKey(), pv.GetBlsPrivKey()) - if err != nil { - return nil, outputFileName, err - } - - pubkey, err := codec.FromCmtPubKeyInterface(validatorKey.ValPubkey) - if err != nil { - return nil, outputFileName, err - } - - genbls, err = checkpointingtypes.NewGenesisKey(valAddress, &validatorKey.BlsPubkey, validatorKey.PoP, pubkey) - if err != nil { - return nil, outputFileName, err - } - - jsonBytes, err := cmtjson.MarshalIndent(genbls, "", " ") - if err != nil { - return nil, outputFileName, err - } - - outputFileName = filepath.Join(filePath, fmt.Sprintf("gen-bls-%s.json", valAddress.String())) - err = tempfile.WriteFileAtomic(outputFileName, jsonBytes, 0600) - return genbls, outputFileName, err -} - -// GetAddress returns the delegator address of the validator. -// Implements PrivValidator. -func (pv *WrappedFilePV) GetAddress() sdk.ValAddress { - if pv.Key.DelegatorAddress == "" { - return sdk.ValAddress{} - } - addr, err := sdk.AccAddressFromBech32(pv.Key.DelegatorAddress) - if err != nil { - cmtos.Exit(err.Error()) - } - return sdk.ValAddress(addr) -} - -func (pv *WrappedFilePV) SetAccAddress(addr sdk.AccAddress) { - pv.Key.DelegatorAddress = addr.String() - pv.Key.Save() -} - -// GetPubKey returns the public key of the validator. -// Implements PrivValidator. -func (pv *WrappedFilePV) GetPubKey() (cmtcrypto.PubKey, error) { - return pv.Key.PubKey, nil -} - -func (pv *WrappedFilePV) GetValPrivKey() cmtcrypto.PrivKey { - return pv.Key.PrivKey -} - -func (pv *WrappedFilePV) GetBlsPrivKey() bls12381.PrivateKey { - return pv.Key.BlsPrivKey -} - -func (pv *WrappedFilePV) SignMsgWithBls(msg []byte) (bls12381.Signature, error) { - blsPrivKey := pv.GetBlsPrivKey() - if blsPrivKey == nil { - return nil, checkpointingtypes.ErrBlsPrivKeyDoesNotExist - } - return bls12381.Sign(blsPrivKey, msg), nil -} - -func (pv *WrappedFilePV) GetBlsPubkey() (bls12381.PublicKey, error) { - blsPrivKey := pv.GetBlsPrivKey() - if blsPrivKey == nil { - return nil, checkpointingtypes.ErrBlsPrivKeyDoesNotExist - } - return blsPrivKey.PubKey(), nil -} - -func (pv *WrappedFilePV) GetValidatorPubkey() (cmtcrypto.PubKey, error) { - return pv.GetPubKey() -} - -// Save persists the FilePV to disk. -func (pv *WrappedFilePV) Save() { - pv.Key.Save() - pv.LastSignState.Save() -} - -// Reset resets all fields in the FilePV. -// NOTE: Unsafe! -func (pv *WrappedFilePV) Reset() { - var sig []byte - pv.LastSignState.Height = 0 - pv.LastSignState.Round = 0 - pv.LastSignState.Step = 0 - pv.LastSignState.Signature = sig - pv.LastSignState.SignBytes = nil - pv.Save() -} - -// Clean removes PVKey file and PVState file -func (pv *WrappedFilePV) Clean(keyFilePath, stateFilePath string) { - _ = os.RemoveAll(filepath.Dir(keyFilePath)) - _ = os.RemoveAll(filepath.Dir(stateFilePath)) -} - -// String returns a string representation of the FilePV. -func (pv *WrappedFilePV) String() string { - return fmt.Sprintf( - "PrivValidator{%v LH:%v, LR:%v, LS:%v}", - pv.GetAddress(), - pv.LastSignState.Height, - pv.LastSignState.Round, - pv.LastSignState.Step, - ) -} diff --git a/test/e2e/initialization/config.go b/test/e2e/initialization/config.go index df6fc6d7a..476fb9097 100644 --- a/test/e2e/initialization/config.go +++ b/test/e2e/initialization/config.go @@ -7,8 +7,6 @@ import ( "time" sdkmath "cosmossdk.io/math" - - minttypes "github.com/babylonlabs-io/babylon/x/mint/types" "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -21,14 +19,13 @@ import ( staketypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/gogoproto/proto" + "github.com/babylonlabs-io/babylon/test/e2e/util" bbn "github.com/babylonlabs-io/babylon/types" btccheckpointtypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" blctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" btclighttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" - finalitytypes "github.com/babylonlabs-io/babylon/x/finality/types" - - "github.com/babylonlabs-io/babylon/test/e2e/util" + minttypes "github.com/babylonlabs-io/babylon/x/mint/types" ) // NodeConfig is a configuration for the node supplied from the test runner diff --git a/test/e2e/initialization/export.go b/test/e2e/initialization/export.go index 6c741a4c0..dbf3d51e0 100644 --- a/test/e2e/initialization/export.go +++ b/test/e2e/initialization/export.go @@ -19,6 +19,7 @@ type Node struct { PrivateKey []byte `json:"privateKey"` PeerId string `json:"peerId"` IsValidator bool `json:"isValidator"` + CometPrivKey []byte `json:"cometPrivKey"` } type Chain struct { diff --git a/test/e2e/initialization/node.go b/test/e2e/initialization/node.go index 5107b7041..07d82b662 100644 --- a/test/e2e/initialization/node.go +++ b/test/e2e/initialization/node.go @@ -8,11 +8,10 @@ import ( "path/filepath" "strings" + "github.com/cometbft/cometbft/crypto/ed25519" + "cosmossdk.io/math" - checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" cmtconfig "github.com/cometbft/cometbft/config" - cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" - cmtos "github.com/cometbft/cometbft/libs/os" "github.com/cometbft/cometbft/p2p" cmttypes "github.com/cometbft/cometbft/types" sdkcrypto "github.com/cosmos/cosmos-sdk/crypto" @@ -32,11 +31,14 @@ import ( "github.com/cosmos/go-bip39" "github.com/spf13/viper" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" + + "github.com/cometbft/cometbft/privval" + babylonApp "github.com/babylonlabs-io/babylon/app" appparams "github.com/babylonlabs-io/babylon/app/params" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/cmd/babylond/cmd" - "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" "github.com/babylonlabs-io/babylon/test/e2e/util" ) @@ -46,7 +48,7 @@ type internalNode struct { mnemonic string keyInfo *keyring.Record privateKey cryptotypes.PrivKey - consensusKey privval.WrappedFilePVKey + consensusKey appsigner.ConsensusKey nodeKey p2p.NodeKey peerId string isValidator bool @@ -80,7 +82,7 @@ func (n *internalNode) configDir() string { return fmt.Sprintf("%s/%s", n.chain.chainMeta.configDir(), n.moniker) } -func (n *internalNode) buildCreateValidatorMsg(amount sdk.Coin, consensusKey privval.WrappedFilePVKey) (sdk.Msg, error) { +func (n *internalNode) buildCreateValidatorMsg(amount sdk.Coin, consensusKey appsigner.ConsensusKey) (sdk.Msg, error) { description := stakingtypes.NewDescription(n.moniker, "", "", "", "") commissionRates := stakingtypes.CommissionRates{ Rate: math.LegacyMustNewDecFromStr("0.1"), @@ -91,7 +93,7 @@ func (n *internalNode) buildCreateValidatorMsg(amount sdk.Coin, consensusKey pri // get the initial validator min self delegation minSelfDelegation, _ := math.NewIntFromString("1") - valPubKey, err := cryptocodec.FromCmtPubKeyInterface(n.consensusKey.PubKey) + valPubKey, err := cryptocodec.FromCmtPubKeyInterface(n.consensusKey.Comet.PubKey) if err != nil { return nil, err } @@ -113,12 +115,12 @@ func (n *internalNode) buildCreateValidatorMsg(amount sdk.Coin, consensusKey pri return nil, err } - proofOfPossession, err := privval.BuildPoP(consensusKey.PrivKey, consensusKey.BlsPrivKey) + proofOfPossession, err := appsigner.BuildPoP(consensusKey.Comet.PrivKey, consensusKey.Bls.PrivKey) if err != nil { return nil, err } - return checkpointingtypes.NewMsgWrappedCreateValidator(stkMsgCreateVal, &consensusKey.BlsPubKey, proofOfPossession) + return checkpointingtypes.NewMsgWrappedCreateValidator(stkMsgCreateVal, &consensusKey.Bls.PubKey, proofOfPossession) } func (n *internalNode) createConfig() error { @@ -169,30 +171,36 @@ func (n *internalNode) createNodeKey() error { func (n *internalNode) createConsensusKey() error { serverCtx := server.NewDefaultContext() config := serverCtx.Config - config.SetRoot(n.configDir()) config.Moniker = n.moniker pvKeyFile := config.PrivValidatorKeyFile() - if err := cmtos.EnsureDir(filepath.Dir(pvKeyFile), 0o777); err != nil { - return err - } - pvStateFile := config.PrivValidatorStateFile() - if err := cmtos.EnsureDir(filepath.Dir(pvStateFile), 0o777); err != nil { - return err - } + blsKeyFile := appsigner.DefaultBlsKeyFile(n.configDir()) + blsPasswordFile := appsigner.DefaultBlsPasswordFile(n.configDir()) - privKey := cmted25519.GenPrivKeyFromSecret([]byte(n.mnemonic)) - blsPrivKey := bls12381.GenPrivKeyFromSecret([]byte(n.mnemonic)) - filePV := privval.NewWrappedFilePV(privKey, blsPrivKey, pvKeyFile, pvStateFile) + if err := appsigner.EnsureDirs(pvKeyFile, pvStateFile, blsKeyFile, blsPasswordFile); err != nil { + return fmt.Errorf("failed to ensure dirs: %w", err) + } - accAddress, _ := n.keyInfo.GetAddress() - filePV.Save() - filePV.SetAccAddress(accAddress) + // create file pv + var privKey ed25519.PrivKey + if n.mnemonic == "" { + privKey = ed25519.GenPrivKey() + } else { + privKey = ed25519.GenPrivKeyFromSecret([]byte(n.mnemonic)) + } + filePV := privval.NewFilePV(privKey, pvKeyFile, pvStateFile) + filePV.Key.Save() + filePV.LastSignState.Save() - n.consensusKey = filePV.Key + // create bls pv + bls := appsigner.GenBls(blsKeyFile, blsPasswordFile, "password") + n.consensusKey = appsigner.ConsensusKey{ + Comet: &filePV.Key, + Bls: &bls.Key, + } return nil } @@ -262,6 +270,7 @@ func (n *internalNode) export() *Node { PrivateKey: n.privateKey.Bytes(), PeerId: n.peerId, IsValidator: n.isValidator, + CometPrivKey: n.consensusKey.Comet.PrivKey.Bytes(), } } diff --git a/test/replay/driver.go b/test/replay/driver.go index 974db133d..c2a90fbad 100644 --- a/test/replay/driver.go +++ b/test/replay/driver.go @@ -21,13 +21,6 @@ import ( "cosmossdk.io/log" "cosmossdk.io/math" - "github.com/babylonlabs-io/babylon/app" - babylonApp "github.com/babylonlabs-io/babylon/app" - appsigner "github.com/babylonlabs-io/babylon/app/signer" - "github.com/babylonlabs-io/babylon/test/e2e/initialization" - "github.com/babylonlabs-io/babylon/testutil/datagen" - btclighttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" - bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -35,6 +28,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" cs "github.com/cometbft/cometbft/consensus" cmtcrypto "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/crypto/ed25519" cometlog "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/mempool" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -58,6 +52,15 @@ import ( gogoprotoio "github.com/cosmos/gogoproto/io" "github.com/otiai10/copy" "github.com/stretchr/testify/require" + + "github.com/babylonlabs-io/babylon/app" + babylonApp "github.com/babylonlabs-io/babylon/app" + appsigner "github.com/babylonlabs-io/babylon/app/signer" + "github.com/babylonlabs-io/babylon/test/e2e/initialization" + "github.com/babylonlabs-io/babylon/testutil/datagen" + btclighttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" + bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) var validatorConfig = &initialization.NodeConfig{ @@ -138,7 +141,7 @@ type FinalizedBlock struct { type BabylonAppDriver struct { App *app.BabylonApp - PrivSigner *appsigner.PrivSigner + BlsSigner checkpointingtypes.BlsSigner DriverAccountPrivKey cryptotypes.PrivKey DriverAccountSeqNr uint64 DriverAccountAccNr uint64 @@ -149,6 +152,8 @@ type BabylonAppDriver struct { ValidatorAddress []byte FinalizedBlocks []FinalizedBlock LastState sm.State + DelegatorAddress sdk.ValAddress + CometPrivKey cmtcrypto.PrivKey } // Inititializes Babylon driver for block creation @@ -194,14 +199,16 @@ func NewBabylonAppDriver( panic(err) } - signer, err := appsigner.InitPrivSigner(chain.Nodes[0].ConfigDir) + blsSigner, err := appsigner.InitBlsSigner(chain.Nodes[0].ConfigDir) + require.NoError(t, err) + require.NotNil(t, blsSigner) + signerValAddress := sdk.ValAddress(chain.Nodes[0].PublicAddress) require.NoError(t, err) - require.NotNil(t, signer) - signerValAddress := signer.WrappedPV.GetAddress() fmt.Printf("signer val address: %s\n", signerValAddress.String()) appOptions := NewAppOptionsWithFlagHome(chain.Nodes[0].ConfigDir) baseAppOptions := server.DefaultBaseappOptions(appOptions) + tmpApp := babylonApp.NewBabylonApp( log.NewNopLogger(), dbm.NewMemDB(), @@ -209,7 +216,7 @@ func NewBabylonAppDriver( true, map[int64]bool{}, 0, - signer, + blsSigner, appOptions, babylonApp.EmptyWasmOpts, baseAppOptions..., @@ -258,7 +265,7 @@ func NewBabylonAppDriver( return &BabylonAppDriver{ App: tmpApp, - PrivSigner: signer, + BlsSigner: *blsSigner, DriverAccountPrivKey: &validatorPrivKey, // Driver account always start from 1, as we executed tx for creating validator // in genesis block @@ -271,6 +278,8 @@ func NewBabylonAppDriver( ValidatorAddress: validatorAddress, FinalizedBlocks: []FinalizedBlock{}, LastState: state.Copy(), + DelegatorAddress: signerValAddress, + CometPrivKey: ed25519.PrivKey(chain.Nodes[0].CometPrivKey), } } @@ -455,7 +464,7 @@ func (d *BabylonAppDriver) GenerateNewBlock(t *testing.T) *abci.ResponseFinalize t, extension, lastFinalizedBlock.Height, - d.PrivSigner.WrappedPV.GetValPrivKey(), + d.CometPrivKey, ) // We are adding invalid signatures here as we are not validating them in @@ -768,11 +777,9 @@ func NewBlockReplayer(t *testing.T, nodeDir string) *BlockReplayer { panic(err) } - signer, err := appsigner.InitPrivSigner(nodeDir) + blsSigner, err := appsigner.InitBlsSigner(nodeDir) require.NoError(t, err) - require.NotNil(t, signer) - signerValAddress := signer.WrappedPV.GetAddress() - fmt.Printf("signer val address: %s\n", signerValAddress.String()) + require.NotNil(t, blsSigner) appOptions := NewAppOptionsWithFlagHome(nodeDir) baseAppOptions := server.DefaultBaseappOptions(appOptions) @@ -783,7 +790,7 @@ func NewBlockReplayer(t *testing.T, nodeDir string) *BlockReplayer { true, map[int64]bool{}, 0, - signer, + blsSigner, appOptions, babylonApp.EmptyWasmOpts, baseAppOptions..., diff --git a/testutil/datagen/btc_blockchain.go b/testutil/datagen/btc_blockchain.go index d963e50e1..da0a207da 100644 --- a/testutil/datagen/btc_blockchain.go +++ b/testutil/datagen/btc_blockchain.go @@ -104,7 +104,7 @@ func GenRandomBtcdBlockWithTransactions( var proofs []*btcctypes.BTCSpvProof - for i, _ := range msgTxs { + for i := range msgTxs { headerBytes := bbn.NewBTCHeaderBytesFromBlockHeader(header) proof, err := btcctypes.SpvProofFromHeaderAndTransactions(&headerBytes, txBytes, uint(i)) if err != nil { diff --git a/testutil/datagen/genesiskey.go b/testutil/datagen/genesiskey.go index dbc39dda2..acb577e27 100644 --- a/testutil/datagen/genesiskey.go +++ b/testutil/datagen/genesiskey.go @@ -1,9 +1,10 @@ package datagen import ( + "fmt" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" "github.com/babylonlabs-io/babylon/testutil/signer" checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" cmtcrypto "github.com/cometbft/cometbft/crypto" @@ -59,7 +60,7 @@ func GenesisValidatorSet(numVals int) (*GenesisValidators, error) { blsPrivKey := bls12381.GenPrivKey() // create validator set with single validator valPrivKey := cmted25519.GenPrivKey() - valKeys, err := privval.NewValidatorKeys(valPrivKey, blsPrivKey) + valKeys, err := appsigner.NewValidatorKeys(valPrivKey, blsPrivKey) if err != nil { return nil, err } @@ -88,27 +89,32 @@ func GenesisValidatorSet(numVals int) (*GenesisValidators, error) { // GenesisValidatorSetWithPrivSigner generates a set with `numVals` genesis validators // along with the privSigner, which will be in the 0th position of the return validator set -func GenesisValidatorSetWithPrivSigner(numVals int) (*GenesisValidators, *appsigner.PrivSigner, error) { - ps, err := signer.SetupTestPrivSigner() +func GenesisValidatorSetWithPrivSigner(numVals int) (*GenesisValidators, checkpointingtypes.BlsSigner, error) { + tbs, err := signer.SetupTestBlsSigner() if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to setup test bls signer: %w", err) } - signerGenesisKey, err := signer.GenesisKeyFromPrivSigner(ps) + blsSigner := checkpointingtypes.BlsSigner(tbs) + + cmtPrivKey := cmted25519.GenPrivKey() + validatorAddress := sdk.AccAddress(cmtPrivKey.PubKey().Address()) + + signerGenesisKey, err := signer.GenesisKeyFromPrivSigner(cmtPrivKey, tbs.PrivKey, sdk.ValAddress(validatorAddress)) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to get genesis key from priv signer: %w", err) } signerVal := &GenesisKeyWithBLS{ GenesisKey: *signerGenesisKey, - PrivateKey: ps.WrappedPV.Key.BlsPrivKey, - PrivKey: ps.WrappedPV.Key.PrivKey, + PrivateKey: tbs.PrivKey, + PrivKey: cmtPrivKey, } genesisVals, err := GenesisValidatorSet(numVals) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to get genesis validators: %w", err) } genesisVals.Keys[0] = signerVal - return genesisVals, ps, nil + return genesisVals, blsSigner, nil } func GenerateGenesisKey() *checkpointingtypes.GenesisKey { @@ -123,7 +129,7 @@ func GenerateGenesisKey() *checkpointingtypes.GenesisKey { blsPubKey := blsPrivKey.PubKey() address := sdk.ValAddress(accPrivKey.PubKey().Address()) - pop, err := privval.BuildPoP(tmValPrivKey, blsPrivKey) + pop, err := appsigner.BuildPoP(tmValPrivKey, blsPrivKey) if err != nil { panic(err) } diff --git a/testutil/datagen/init_val.go b/testutil/datagen/init_val.go index 46e883b46..11ea977b8 100644 --- a/testutil/datagen/init_val.go +++ b/testutil/datagen/init_val.go @@ -2,25 +2,24 @@ package datagen import ( "fmt" - "path/filepath" cfg "github.com/cometbft/cometbft/config" - cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" - cmtos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cometbft/cometbft/p2p" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/go-bip39" - "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" + appsigner "github.com/babylonlabs-io/babylon/app/signer" + cmtos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/privval" ) // InitializeNodeValidatorFiles creates private validator and p2p configuration files. -func InitializeNodeValidatorFiles(config *cfg.Config, addr sdk.AccAddress) (string, *privval.ValidatorKeys, error) { +func InitializeNodeValidatorFiles(config *cfg.Config, addr sdk.AccAddress) (string, *appsigner.ValidatorKeys, error) { return InitializeNodeValidatorFilesFromMnemonic(config, "", addr) } -func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic string, addr sdk.AccAddress) (nodeID string, valKeys *privval.ValidatorKeys, err error) { +func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic string, addr sdk.AccAddress) (nodeID string, valKeys *appsigner.ValidatorKeys, err error) { if len(mnemonic) > 0 && !bip39.IsMnemonicValid(mnemonic) { return "", nil, fmt.Errorf("invalid mnemonic") } @@ -32,29 +31,41 @@ func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic strin nodeID = string(nodeKey.ID()) - pvKeyFile := config.PrivValidatorKeyFile() - if err := cmtos.EnsureDir(filepath.Dir(pvKeyFile), 0777); err != nil { - return "", nil, err + cmtKeyFile := config.PrivValidatorKeyFile() + cmtStateFile := config.PrivValidatorStateFile() + blsKeyFile := appsigner.DefaultBlsKeyFile(config.RootDir) + blsPasswordFile := appsigner.DefaultBlsPasswordFile(config.RootDir) + if err := appsigner.EnsureDirs(cmtKeyFile, cmtStateFile, blsKeyFile, blsPasswordFile); err != nil { + return "", nil, fmt.Errorf("failed to ensure dirs: %w", err) } - pvStateFile := config.PrivValidatorStateFile() - if err := cmtos.EnsureDir(filepath.Dir(pvStateFile), 0777); err != nil { - return "", nil, err + var filePV *privval.FilePV + if cmtos.FileExists(cmtKeyFile) { + filePV = privval.LoadFilePV(cmtKeyFile, cmtStateFile) + } else { + var privKey ed25519.PrivKey + if len(mnemonic) == 0 { + privKey = ed25519.GenPrivKey() + } else { + privKey = ed25519.GenPrivKeyFromSecret([]byte(mnemonic)) + } + filePV = privval.NewFilePV(privKey, cmtKeyFile, cmtStateFile) + filePV.Key.Save() + filePV.LastSignState.Save() } - var filePV *privval.WrappedFilePV - if len(mnemonic) == 0 { - filePV = privval.LoadOrGenWrappedFilePV(pvKeyFile, pvStateFile) + var bls *appsigner.Bls + if cmtos.FileExists(blsKeyFile) { + // if key file exists but password file does not exist -> error + if !cmtos.FileExists(blsPasswordFile) { + cmtos.Exit(fmt.Sprintf("BLS password file does not exist: %v", blsPasswordFile)) + } + bls = appsigner.LoadBls(blsKeyFile, blsPasswordFile) } else { - privKey := cmted25519.GenPrivKeyFromSecret([]byte(mnemonic)) - blsPrivKey := bls12381.GenPrivKeyFromSecret([]byte(mnemonic)) - filePV = privval.NewWrappedFilePV(privKey, blsPrivKey, pvKeyFile, pvStateFile) + bls = appsigner.GenBls(blsKeyFile, blsPasswordFile, "password") } - filePV.SetAccAddress(addr) - valPrivkey := filePV.GetValPrivKey() - blsPrivkey := filePV.GetBlsPrivKey() - valKeys, err = privval.NewValidatorKeys(valPrivkey, blsPrivkey) + valKeys, err = appsigner.NewValidatorKeys(filePV.Key.PrivKey, bls.Key.PrivKey) if err != nil { return "", nil, err } diff --git a/testutil/helper/helper.go b/testutil/helper/helper.go index cdff0a65c..3a12e1105 100644 --- a/testutil/helper/helper.go +++ b/testutil/helper/helper.go @@ -7,29 +7,26 @@ import ( "testing" "cosmossdk.io/core/header" - "github.com/babylonlabs-io/babylon/app/signer" - "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/testutil/datagen" - checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" + "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - protoio "github.com/cosmos/gogoproto/io" - - "cosmossdk.io/math" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + protoio "github.com/cosmos/gogoproto/io" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" "github.com/babylonlabs-io/babylon/app" appparams "github.com/babylonlabs-io/babylon/app/params" + "github.com/babylonlabs-io/babylon/crypto/bls12381" + "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" "github.com/babylonlabs-io/babylon/x/epoching/keeper" "github.com/babylonlabs-io/babylon/x/epoching/types" ) @@ -49,21 +46,21 @@ type Helper struct { // NewHelper creates the helper for testing the epoching module func NewHelper(t *testing.T) *Helper { - valSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(1) + valSet, blsSigner, err := datagen.GenesisValidatorSetWithPrivSigner(1) require.NoError(t, err) - return NewHelperWithValSet(t, valSet, privSigner) + return NewHelperWithValSet(t, valSet, blsSigner) } // NewHelperWithValSet is same as NewHelper, except that it creates a set of validators // the privSigner is the 0th validator in valSet -func NewHelperWithValSet(t *testing.T, valSet *datagen.GenesisValidators, privSigner *signer.PrivSigner) *Helper { +func NewHelperWithValSet(t *testing.T, valSet *datagen.GenesisValidators, blsSigner checkpointingtypes.BlsSigner) *Helper { t.Helper() // generate the genesis account - signerPubKey := privSigner.WrappedPV.Key.PubKey + signerPubKey := valSet.Keys[0].PrivKey.PubKey() acc := authtypes.NewBaseAccount(signerPubKey.Address().Bytes(), &cosmosed.PubKey{Key: signerPubKey.Bytes()}, 0, 0) - privSigner.WrappedPV.Key.DelegatorAddress = acc.Address - valSet.Keys[0].ValidatorAddress = privSigner.WrappedPV.GetAddress().String() + + valSet.Keys[0].ValidatorAddress = sdk.ValAddress(acc.GetAddress()).String() // ensure the genesis account has a sufficient amount of tokens balance := banktypes.Balance{ Address: acc.GetAddress().String(), @@ -72,7 +69,7 @@ func NewHelperWithValSet(t *testing.T, valSet *datagen.GenesisValidators, privSi GenAccs := []authtypes.GenesisAccount{acc} // setup the app and ctx - app := app.SetupWithGenesisValSet(t, bbn.BtcSimnet, valSet.GetGenesisKeys(), privSigner, GenAccs, balance) + app := app.SetupWithGenesisValSet(t, bbn.BtcSimnet, valSet.GetGenesisKeys(), blsSigner, GenAccs, balance) ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 // get necessary subsets of the app/keeper @@ -96,11 +93,10 @@ func NewHelperWithValSet(t *testing.T, valSet *datagen.GenesisValidators, privSi // NewHelperWithValSetNoSigner is same as NewHelperWithValSet, except that the privSigner is not // included in the validator set -func NewHelperWithValSetNoSigner(t *testing.T, valSet *datagen.GenesisValidators, privSigner *signer.PrivSigner) *Helper { +func NewHelperWithValSetNoSigner(t *testing.T, valSet *datagen.GenesisValidators, blsSigner checkpointingtypes.BlsSigner) *Helper { // generate the genesis account - signerPubKey := privSigner.WrappedPV.Key.PubKey + signerPubKey := valSet.Keys[0].PrivKey.PubKey() acc := authtypes.NewBaseAccount(signerPubKey.Address().Bytes(), &cosmosed.PubKey{Key: signerPubKey.Bytes()}, 0, 0) - privSigner.WrappedPV.Key.DelegatorAddress = acc.Address // set a random validator address instead of the privSigner's valSet.Keys[0].ValidatorAddress = datagen.GenRandomValidatorAddress().String() // ensure the genesis account has a sufficient amount of tokens @@ -111,7 +107,7 @@ func NewHelperWithValSetNoSigner(t *testing.T, valSet *datagen.GenesisValidators GenAccs := []authtypes.GenesisAccount{acc} // setup the app and ctx - app := app.SetupWithGenesisValSet(t, bbn.BtcSimnet, valSet.GetGenesisKeys(), privSigner, GenAccs, balance) + app := app.SetupWithGenesisValSet(t, bbn.BtcSimnet, valSet.GetGenesisKeys(), blsSigner, GenAccs, balance) ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 // get necessary subsets of the app/keeper diff --git a/testutil/keeper/checkpointing.go b/testutil/keeper/checkpointing.go index f9ec59271..a8d90a825 100644 --- a/testutil/keeper/checkpointing.go +++ b/testutil/keeper/checkpointing.go @@ -20,7 +20,7 @@ import ( "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) -func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.BlsSigner) (*keeper.Keeper, sdk.Context, *codec.ProtoCodec) { +func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer types.BlsSigner) (*keeper.Keeper, sdk.Context, *codec.ProtoCodec) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) db := dbm.NewMemDB() diff --git a/testutil/mocks/bls_signer.go b/testutil/mocks/bls_signer.go index c59a2fcd7..6d8a965b8 100644 --- a/testutil/mocks/bls_signer.go +++ b/testutil/mocks/bls_signer.go @@ -3,94 +3,3 @@ // Package mocks is a generated GoMock package. package mocks - -import ( - reflect "reflect" - - bls12381 "github.com/babylonlabs-io/babylon/crypto/bls12381" - crypto "github.com/cometbft/cometbft/crypto" - types "github.com/cosmos/cosmos-sdk/types" - gomock "github.com/golang/mock/gomock" -) - -// MockBlsSigner is a mock of BlsSigner interface. -type MockBlsSigner struct { - ctrl *gomock.Controller - recorder *MockBlsSignerMockRecorder -} - -// MockBlsSignerMockRecorder is the mock recorder for MockBlsSigner. -type MockBlsSignerMockRecorder struct { - mock *MockBlsSigner -} - -// NewMockBlsSigner creates a new mock instance. -func NewMockBlsSigner(ctrl *gomock.Controller) *MockBlsSigner { - mock := &MockBlsSigner{ctrl: ctrl} - mock.recorder = &MockBlsSignerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBlsSigner) EXPECT() *MockBlsSignerMockRecorder { - return m.recorder -} - -// GetAddress mocks base method. -func (m *MockBlsSigner) GetAddress() types.ValAddress { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAddress") - ret0, _ := ret[0].(types.ValAddress) - return ret0 -} - -// GetAddress indicates an expected call of GetAddress. -func (mr *MockBlsSignerMockRecorder) GetAddress() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddress", reflect.TypeOf((*MockBlsSigner)(nil).GetAddress)) -} - -// GetBlsPubkey mocks base method. -func (m *MockBlsSigner) GetBlsPubkey() (bls12381.PublicKey, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBlsPubkey") - ret0, _ := ret[0].(bls12381.PublicKey) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBlsPubkey indicates an expected call of GetBlsPubkey. -func (mr *MockBlsSignerMockRecorder) GetBlsPubkey() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlsPubkey", reflect.TypeOf((*MockBlsSigner)(nil).GetBlsPubkey)) -} - -// GetValidatorPubkey mocks base method. -func (m *MockBlsSigner) GetValidatorPubkey() (crypto.PubKey, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValidatorPubkey") - ret0, _ := ret[0].(crypto.PubKey) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetValidatorPubkey indicates an expected call of GetValidatorPubkey. -func (mr *MockBlsSignerMockRecorder) GetValidatorPubkey() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorPubkey", reflect.TypeOf((*MockBlsSigner)(nil).GetValidatorPubkey)) -} - -// SignMsgWithBls mocks base method. -func (m *MockBlsSigner) SignMsgWithBls(msg []byte) (bls12381.Signature, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SignMsgWithBls", msg) - ret0, _ := ret[0].(bls12381.Signature) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SignMsgWithBls indicates an expected call of SignMsgWithBls. -func (mr *MockBlsSignerMockRecorder) SignMsgWithBls(msg interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignMsgWithBls", reflect.TypeOf((*MockBlsSigner)(nil).SignMsgWithBls), msg) -} diff --git a/testutil/mocks/checkpointing_expected_keepers.go b/testutil/mocks/checkpointing_expected_keepers.go index e69ac67ea..d4d0f0a4d 100644 --- a/testutil/mocks/checkpointing_expected_keepers.go +++ b/testutil/mocks/checkpointing_expected_keepers.go @@ -122,6 +122,21 @@ func (mr *MockEpochingKeeperMockRecorder) GetTotalVotingPower(ctx, epochNumber i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTotalVotingPower", reflect.TypeOf((*MockEpochingKeeper)(nil).GetTotalVotingPower), ctx, epochNumber) } +// GetValidator mocks base method. +func (m *MockEpochingKeeper) GetValidator(ctx context.Context, addr types1.ValAddress) (types2.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValidator", ctx, addr) + ret0, _ := ret[0].(types2.Validator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetValidator indicates an expected call of GetValidator. +func (mr *MockEpochingKeeperMockRecorder) GetValidator(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidator", reflect.TypeOf((*MockEpochingKeeper)(nil).GetValidator), ctx, addr) +} + // GetValidatorSet mocks base method. func (m *MockEpochingKeeper) GetValidatorSet(ctx context.Context, epochNumer uint64) types0.ValidatorSet { m.ctrl.T.Helper() diff --git a/testutil/signer/private.go b/testutil/signer/private.go index a4f39cb3a..ef6427d13 100644 --- a/testutil/signer/private.go +++ b/testutil/signer/private.go @@ -1,41 +1,63 @@ package signer import ( + "fmt" "os" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/babylonlabs-io/babylon/app/signer" - "github.com/babylonlabs-io/babylon/privval" + cmtconfig "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + + appsigner "github.com/babylonlabs-io/babylon/app/signer" + "github.com/babylonlabs-io/babylon/crypto/bls12381" checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) -// SetupTestPrivSigner sets up a PrivSigner for testing -func SetupTestPrivSigner() (*signer.PrivSigner, error) { +const TestPassword string = "password" + +// SetupTestBlsSigner sets up a BLS signer for testing +func SetupTestBlsSigner() (*appsigner.BlsKey, error) { // Create a temporary node directory nodeDir, err := os.MkdirTemp("", "tmp-signer") if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create temporary node directory: %w", err) } defer func() { _ = os.RemoveAll(nodeDir) }() - privSigner, _ := signer.InitPrivSigner(nodeDir) - return privSigner, nil + + nodeCfg := cmtconfig.DefaultConfig() + nodeCfg.SetRoot(nodeDir) + + blsKeyFile := appsigner.DefaultBlsKeyFile(nodeDir) + blsPasswordFile := appsigner.DefaultBlsPasswordFile(nodeDir) + + if err := appsigner.EnsureDirs(blsKeyFile, blsPasswordFile); err != nil { + return nil, fmt.Errorf("failed to ensure dirs: %w", err) + } + + bls := appsigner.GenBls(blsKeyFile, blsPasswordFile, TestPassword) + return &bls.Key, nil } -func GenesisKeyFromPrivSigner(ps *signer.PrivSigner) (*checkpointingtypes.GenesisKey, error) { - valKeys, err := privval.NewValidatorKeys(ps.WrappedPV.GetValPrivKey(), ps.WrappedPV.GetBlsPrivKey()) +// GenesisKeyFromPrivSigner generates a genesis key from a priv signer +func GenesisKeyFromPrivSigner(cmtPrivKey crypto.PrivKey, blsPrivKey bls12381.PrivateKey, delegatorAddress sdk.ValAddress) (*checkpointingtypes.GenesisKey, error) { + valKeys, err := appsigner.NewValidatorKeys( + cmtPrivKey, + blsPrivKey, + ) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to generate validator keys: %w", err) } valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to convert validator public key: %w", err) } return checkpointingtypes.NewGenesisKey( - ps.WrappedPV.GetAddress(), + delegatorAddress, &valKeys.BlsPubkey, valKeys.PoP, &cosmosed.PubKey{Key: valPubkey.Bytes()}, diff --git a/x/checkpointing/client/cli/tx_test.go b/x/checkpointing/client/cli/tx_test.go index bee75d842..c211a4bb7 100644 --- a/x/checkpointing/client/cli/tx_test.go +++ b/x/checkpointing/client/cli/tx_test.go @@ -4,14 +4,12 @@ import ( "context" "fmt" "io" - "path/filepath" "testing" sdkmath "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" cmtconfig "github.com/cometbft/cometbft/config" cmtbytes "github.com/cometbft/cometbft/libs/bytes" - cmtos "github.com/cometbft/cometbft/libs/os" rpcclient "github.com/cometbft/cometbft/rpc/client" rpcclientmock "github.com/cometbft/cometbft/rpc/client/mock" coretypes "github.com/cometbft/cometbft/rpc/core/types" @@ -28,9 +26,10 @@ import ( "github.com/babylonlabs-io/babylon/app" "github.com/babylonlabs-io/babylon/app/params" - "github.com/babylonlabs-io/babylon/privval" + appsigner "github.com/babylonlabs-io/babylon/app/signer" testutilcli "github.com/babylonlabs-io/babylon/testutil/cli" checkpointcli "github.com/babylonlabs-io/babylon/x/checkpointing/client/cli" + "github.com/cometbft/cometbft/privval" ) type mockCometRPC struct { @@ -103,17 +102,27 @@ func (s *CLITestSuite) SetupSuite() { func (s *CLITestSuite) TestCmdWrappedCreateValidator() { require := s.Require() homeDir := s.T().TempDir() + + // create BLS keys nodeCfg := cmtconfig.DefaultConfig() - pvKeyFile := filepath.Join(homeDir, nodeCfg.PrivValidatorKeyFile()) - err := cmtos.EnsureDir(filepath.Dir(pvKeyFile), 0777) - require.NoError(err) - pvStateFile := filepath.Join(homeDir, nodeCfg.PrivValidatorStateFile()) - err = cmtos.EnsureDir(filepath.Dir(pvStateFile), 0777) + nodeCfg.SetRoot(homeDir) + + cmtKeyPath := nodeCfg.PrivValidatorKeyFile() + cmtStatePath := nodeCfg.PrivValidatorStateFile() + blsKeyFile := appsigner.DefaultBlsKeyFile(homeDir) + blsPasswordFile := appsigner.DefaultBlsPasswordFile(homeDir) + + err := appsigner.EnsureDirs(cmtKeyPath, cmtStatePath, blsKeyFile, blsPasswordFile) require.NoError(err) - wrappedPV := privval.LoadOrGenWrappedFilePV(pvKeyFile, pvStateFile) + + filePV := privval.GenFilePV(cmtKeyPath, cmtStatePath) + filePV.Key.Save() + filePV.LastSignState.Save() + + appsigner.GenBls(blsKeyFile, blsPasswordFile, "password") cmd := checkpointcli.CmdWrappedCreateValidator(authcodec.NewBech32Codec("cosmosvaloper")) - consPrivKey := wrappedPV.GetValPrivKey() + consPrivKey := filePV.Key.PrivKey consPubKey, err := cryptocodec.FromCmtPubKeyInterface(consPrivKey.PubKey()) require.NoError(err) consPubKeyBz, err := s.clientCtx.Codec.MarshalInterfaceJSON(consPubKey) diff --git a/x/checkpointing/client/cli/utils.go b/x/checkpointing/client/cli/utils.go index c30ad96dd..cd9cf535e 100644 --- a/x/checkpointing/client/cli/utils.go +++ b/x/checkpointing/client/cli/utils.go @@ -10,8 +10,6 @@ import ( "cosmossdk.io/core/address" errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - cmtconfig "github.com/cometbft/cometbft/config" - cmtos "github.com/cometbft/cometbft/libs/os" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" @@ -23,7 +21,7 @@ import ( staketypes "github.com/cosmos/cosmos-sdk/x/staking/types" flag "github.com/spf13/pflag" - "github.com/babylonlabs-io/babylon/privval" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) @@ -202,14 +200,14 @@ func buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr string) (commiss return commission, nil } -func getValKeyFromFile(homeDir string) (*privval.ValidatorKeys, error) { - nodeCfg := cmtconfig.DefaultConfig() - keyPath := filepath.Join(homeDir, nodeCfg.PrivValidatorKeyFile()) - statePath := filepath.Join(homeDir, nodeCfg.PrivValidatorStateFile()) - if !cmtos.FileExists(keyPath) { - return nil, errors.New("validator key file does not exist") +// getValKeyFromFile loads the validator key from the node directory +// Both FilePV from priv_validator_key.json and Bls should be present in the node directory +// befor function is called. +func getValKeyFromFile(homeDir string) (*appsigner.ValidatorKeys, error) { + ck, err := appsigner.LoadConsensusKey(homeDir) + if err != nil { + return nil, err } - wrappedPV := privval.LoadWrappedFilePV(keyPath, statePath) - return privval.NewValidatorKeys(wrappedPV.GetValPrivKey(), wrappedPV.GetBlsPrivKey()) + return appsigner.NewValidatorKeys(ck.Comet.PrivKey, ck.Bls.PrivKey) } diff --git a/x/checkpointing/genesis_test.go b/x/checkpointing/genesis_test.go index e6e9c18cf..ffa9805b0 100644 --- a/x/checkpointing/genesis_test.go +++ b/x/checkpointing/genesis_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" "github.com/babylonlabs-io/babylon/x/checkpointing" "github.com/cometbft/cometbft/crypto/ed25519" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -13,6 +12,7 @@ import ( "github.com/stretchr/testify/require" simapp "github.com/babylonlabs-io/babylon/app" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) @@ -24,7 +24,7 @@ func TestInitGenesis(t *testing.T) { valNum := 10 genKeys := make([]*types.GenesisKey, valNum) for i := 0; i < valNum; i++ { - valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) + valKeys, err := appsigner.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) require.NoError(t, err) valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) require.NoError(t, err) diff --git a/x/checkpointing/keeper/bls_signer.go b/x/checkpointing/keeper/bls_signer.go index 8e7eb0ad8..29882d978 100644 --- a/x/checkpointing/keeper/bls_signer.go +++ b/x/checkpointing/keeper/bls_signer.go @@ -1,20 +1,15 @@ package keeper import ( - "github.com/cometbft/cometbft/crypto" + "context" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/babylonlabs-io/babylon/crypto/bls12381" "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) -type BlsSigner interface { - GetAddress() sdk.ValAddress - SignMsgWithBls(msg []byte) (bls12381.Signature, error) - GetBlsPubkey() (bls12381.PublicKey, error) - GetValidatorPubkey() (crypto.PubKey, error) -} - // SignBLS signs a BLS signature over the given information func (k Keeper) SignBLS(epochNum uint64, blockHash types.BlockHash) (bls12381.Signature, error) { // get BLS signature by signing @@ -22,14 +17,11 @@ func (k Keeper) SignBLS(epochNum uint64, blockHash types.BlockHash) (bls12381.Si return k.blsSigner.SignMsgWithBls(signBytes) } -func (k Keeper) GetBLSSignerAddress() sdk.ValAddress { - return k.blsSigner.GetAddress() -} - -func (k Keeper) GetValidatorAddress() sdk.ValAddress { - pk, err := k.blsSigner.GetValidatorPubkey() +// GetValidatorAddress returns the validator address of the signer +func (k Keeper) GetValidatorAddress(ctx context.Context) (sdk.ValAddress, error) { + blsPubKey, err := k.blsSigner.BlsPubKey() if err != nil { - panic(err) + return nil, fmt.Errorf("failed to get BLS public key: %w", err) } - return sdk.ValAddress(pk.Address()) + return k.GetValAddr(ctx, blsPubKey) } diff --git a/x/checkpointing/keeper/keeper.go b/x/checkpointing/keeper/keeper.go index 7ea7a61d3..65908ca0d 100644 --- a/x/checkpointing/keeper/keeper.go +++ b/x/checkpointing/keeper/keeper.go @@ -6,6 +6,7 @@ import ( "fmt" corestoretypes "cosmossdk.io/core/store" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" txformat "github.com/babylonlabs-io/babylon/btctxformatter" @@ -24,7 +25,7 @@ type ( Keeper struct { cdc codec.BinaryCodec storeService corestoretypes.KVStoreService - blsSigner BlsSigner + blsSigner types.BlsSigner epochingKeeper types.EpochingKeeper hooks types.CheckpointingHooks } @@ -33,7 +34,7 @@ type ( func NewKeeper( cdc codec.BinaryCodec, storeService corestoretypes.KVStoreService, - signer BlsSigner, + signer types.BlsSigner, ek types.EpochingKeeper, ) Keeper { return Keeper{ @@ -428,26 +429,41 @@ func (k Keeper) GetBLSPubKeySet(ctx context.Context, epochNumber uint64) ([]*typ return valWithblsKeys, nil } +// GetBlsPubKey returns the BLS public key of the validator func (k Keeper) GetBlsPubKey(ctx context.Context, address sdk.ValAddress) (bls12381.PublicKey, error) { return k.RegistrationState(ctx).GetBlsPubKey(address) } +// GetValAddr returns the validator address of the BLS public key +func (k Keeper) GetValAddr(ctx context.Context, key bls12381.PublicKey) (sdk.ValAddress, error) { + return k.RegistrationState(ctx).GetValAddr(key) +} + +// GetEpoch returns the current epoch func (k Keeper) GetEpoch(ctx context.Context) *epochingtypes.Epoch { return k.epochingKeeper.GetEpoch(ctx) } +// GetValidatorSet returns the validator set for a given epoch func (k Keeper) GetValidatorSet(ctx context.Context, epochNumber uint64) epochingtypes.ValidatorSet { return k.epochingKeeper.GetValidatorSet(ctx, epochNumber) } +// GetTotalVotingPower returns the total voting power for a given epoch func (k Keeper) GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 { return k.epochingKeeper.GetTotalVotingPower(ctx, epochNumber) } +// GetPubKeyByConsAddr returns the public key of a validator by consensus address func (k Keeper) GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) { return k.epochingKeeper.GetPubKeyByConsAddr(ctx, consAddr) } +// GetValidatorByConsAddr returns the validator by consensus address +func (k Keeper) GetValidator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.Validator, error) { + return k.epochingKeeper.GetValidator(ctx, addr) +} + // GetLastFinalizedEpoch gets the last finalised epoch func (k Keeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { store := k.storeService.OpenKVStore(ctx) @@ -460,6 +476,7 @@ func (k Keeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { return sdk.BigEndianToUint64(epochNumberBytes) } +// GetEpochByHeight returns the epoch number for a given height func (k Keeper) GetEpochByHeight(ctx context.Context, height uint64) uint64 { return k.epochingKeeper.GetEpochNumByHeight(ctx, height) } diff --git a/x/checkpointing/keeper/msg_server_test.go b/x/checkpointing/keeper/msg_server_test.go index fffba8d6e..2c57f7463 100644 --- a/x/checkpointing/keeper/msg_server_test.go +++ b/x/checkpointing/keeper/msg_server_test.go @@ -13,8 +13,8 @@ import ( "github.com/babylonlabs-io/babylon/app" appparams "github.com/babylonlabs-io/babylon/app/params" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" "github.com/babylonlabs-io/babylon/testutil/datagen" testhelper "github.com/babylonlabs-io/babylon/testutil/helper" checkpointingkeeper "github.com/babylonlabs-io/babylon/x/checkpointing/keeper" @@ -225,7 +225,7 @@ func buildMsgWrappedCreateValidatorWithAmount(addr sdk.AccAddress, bondTokens ma return nil, err } blsPrivKey := bls12381.GenPrivKey() - pop, err := privval.BuildPoP(cmtValPrivkey, blsPrivKey) + pop, err := appsigner.BuildPoP(cmtValPrivkey, blsPrivKey) if err != nil { return nil, err } diff --git a/x/checkpointing/keeper/registration_state.go b/x/checkpointing/keeper/registration_state.go index 968a4d185..2d8e349ba 100644 --- a/x/checkpointing/keeper/registration_state.go +++ b/x/checkpointing/keeper/registration_state.go @@ -5,11 +5,12 @@ import ( "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" - "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/x/checkpointing/types" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonlabs-io/babylon/crypto/bls12381" + "github.com/babylonlabs-io/babylon/x/checkpointing/types" ) type RegistrationState struct { @@ -72,6 +73,16 @@ func (rs RegistrationState) GetBlsPubKey(addr sdk.ValAddress) (bls12381.PublicKe return *pk, err } +// GetValAddr returns the validator address of the BLS public key +func (rs RegistrationState) GetValAddr(key bls12381.PublicKey) (sdk.ValAddress, error) { + pkKey := types.BlsKeyToAddrKey(key) + rawBytes := rs.blsKeysToAddr.Get(pkKey) + if rawBytes == nil { + return nil, types.ErrValAddrDoesNotExist.Wrapf("validator address does not exist with BLS public key %s", key) + } + return rawBytes, nil +} + // Exists checks whether a BLS key exists func (rs RegistrationState) Exists(addr sdk.ValAddress) bool { pkKey := types.AddrToBlsKeyKey(addr) diff --git a/x/checkpointing/types/errors.go b/x/checkpointing/types/errors.go index f212924c6..2971bd5cd 100644 --- a/x/checkpointing/types/errors.go +++ b/x/checkpointing/types/errors.go @@ -19,4 +19,5 @@ var ( ErrConflictingCheckpoint = errorsmod.Register(ModuleName, 1213, "Conflicting checkpoint is found") ErrInvalidAppHash = errorsmod.Register(ModuleName, 1214, "Provided app hash is Invalid") ErrInsufficientVotingPower = errorsmod.Register(ModuleName, 1215, "Accumulated voting power is not greater than 2/3 of total power") + ErrValAddrDoesNotExist = errorsmod.Register(ModuleName, 1216, "Validator address does not exist") ) diff --git a/x/checkpointing/types/expected_keepers.go b/x/checkpointing/types/expected_keepers.go index 3041e13e1..160118800 100644 --- a/x/checkpointing/types/expected_keepers.go +++ b/x/checkpointing/types/expected_keepers.go @@ -20,6 +20,7 @@ type EpochingKeeper interface { CheckMsgCreateValidator(ctx context.Context, msg *stakingtypes.MsgCreateValidator) error StkMsgCreateValidator(ctx context.Context, msg *stakingtypes.MsgCreateValidator) error GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) + GetValidator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.Validator, error) } // Event Hooks diff --git a/x/checkpointing/types/msgs_test.go b/x/checkpointing/types/msgs_test.go index 89301ebbf..114adcfa8 100644 --- a/x/checkpointing/types/msgs_test.go +++ b/x/checkpointing/types/msgs_test.go @@ -5,8 +5,8 @@ import ( sdkmath "cosmossdk.io/math" appparams "github.com/babylonlabs-io/babylon/app/params" + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" "github.com/babylonlabs-io/babylon/x/checkpointing/types" "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cosmos/cosmos-sdk/codec" @@ -71,7 +71,7 @@ func buildMsgWrappedCreateValidatorWithAmount(addr sdk.AccAddress, bondTokens sd return nil, err } blsPrivKey := bls12381.GenPrivKey() - pop, err := privval.BuildPoP(tmValPrivkey, blsPrivKey) + pop, err := appsigner.BuildPoP(tmValPrivkey, blsPrivKey) if err != nil { return nil, err } diff --git a/x/checkpointing/types/pop_test.go b/x/checkpointing/types/pop_test.go index 10e03df6a..b5a9c1630 100644 --- a/x/checkpointing/types/pop_test.go +++ b/x/checkpointing/types/pop_test.go @@ -1,18 +1,19 @@ package types_test import ( + "testing" + + appsigner "github.com/babylonlabs-io/babylon/app/signer" "github.com/babylonlabs-io/babylon/crypto/bls12381" - "github.com/babylonlabs-io/babylon/privval" "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/stretchr/testify/require" - "testing" ) func TestProofOfPossession_IsValid(t *testing.T) { valPrivKey := ed25519.GenPrivKey() blsPrivKey := bls12381.GenPrivKey() - pop, err := privval.BuildPoP(valPrivKey, blsPrivKey) + pop, err := appsigner.BuildPoP(valPrivKey, blsPrivKey) require.NoError(t, err) valpk, err := codec.FromCmtPubKeyInterface(valPrivKey.PubKey()) require.NoError(t, err) diff --git a/x/checkpointing/types/types.go b/x/checkpointing/types/types.go index 73893b2b3..903510c08 100644 --- a/x/checkpointing/types/types.go +++ b/x/checkpointing/types/types.go @@ -22,6 +22,12 @@ const ( BitmapBits = txformat.BitMapLength * 8 // 104 bits for 104 validators at top ) +// BlsSigner is an interface for signing BLS messages +type BlsSigner interface { + SignMsgWithBls(msg []byte) (bls12381.Signature, error) + BlsPubKey() (bls12381.PublicKey, error) +} + type BlockHash []byte type BlsSigHash []byte diff --git a/x/checkpointing/vote_ext.go b/x/checkpointing/vote_ext.go index 6bdd10e1d..c4921a049 100644 --- a/x/checkpointing/vote_ext.go +++ b/x/checkpointing/vote_ext.go @@ -51,17 +51,20 @@ func (h *VoteExtensionHandler) ExtendVote() sdk.ExtendVoteHandler { return emptyRes, nil } - // 1. check if itself is the validator as the BLS sig is only signed - // when the node itself is a validator - signer := k.GetBLSSignerAddress() - curValSet := k.GetValidatorSet(ctx, epoch.EpochNumber) - _, _, err := curValSet.FindValidatorWithIndex(signer) + // 1. get validator address for VoteExtension structure + valOperAddr, err := k.GetValidatorAddress(ctx) if err != nil { - // NOTE: this indicates programmatic error because ExtendVote - // should not be invoked if the validator is not in the - // active set according to: - // https://github.com/cometbft/cometbft/blob/a17290f6905ef714761f12c1f82409b0731e3838/consensus/state.go#L2434 - panic(fmt.Errorf("the BLS signer %s is not in the validator set", signer.String())) + panic(fmt.Errorf("failed to get validator address: %w", err)) + } + + val, err := k.GetValidator(ctx, valOperAddr) + if err != nil { + panic(fmt.Errorf("the BLS signer's address %s is not in the validator set", valOperAddr.String())) + } + + valConsPubkey, err := val.ConsPubKey() + if err != nil { + panic(fmt.Errorf("the BLS signer's consensus pubkey %s is invalid", val.OperatorAddress)) } // 2. sign BLS signature @@ -77,11 +80,10 @@ func (h *VoteExtensionHandler) ExtendVote() sdk.ExtendVoteHandler { // NOTE: this indicates programmatic error in CometBFT panic(fmt.Errorf("invalid CometBFT hash")) } - // 3. build vote extension ve := &ckpttypes.VoteExtension{ - Signer: signer.String(), - ValidatorAddress: k.GetValidatorAddress().String(), + Signer: valOperAddr.String(), + ValidatorAddress: sdk.ValAddress(valConsPubkey.Address()).String(), BlockHash: &bhash, EpochNum: epoch.EpochNumber, Height: uint64(req.Height), diff --git a/x/checkpointing/vote_ext_test.go b/x/checkpointing/vote_ext_test.go index 90c57342f..a0001b891 100644 --- a/x/checkpointing/vote_ext_test.go +++ b/x/checkpointing/vote_ext_test.go @@ -175,78 +175,3 @@ func FuzzExtendVote_InvalidBlockHash(f *testing.F) { require.NoError(t, err) }) } - -// FuzzExtendVote_EmptyBLSPrivKey tests the case where the -// BLS private key of the private signer is missing -func FuzzExtendVote_EmptyBLSPrivKey(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - // generate the validator set with 10 validators as genesis - genesisValSet, ps, err := datagen.GenesisValidatorSetWithPrivSigner(10) - require.NoError(t, err) - - // set the BLS private key to be nil to trigger panic - ps.WrappedPV.Key.BlsPrivKey = nil - helper := testhelper.NewHelperWithValSet(t, genesisValSet, ps) - ek := helper.App.EpochingKeeper - - epoch := ek.GetEpoch(helper.Ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - - // go to block 10, reaching epoch boundary - interval := ek.GetParams(helper.Ctx).EpochInterval - for i := uint64(0); i < interval-2; i++ { - _, err := helper.ApplyEmptyBlockWithVoteExtension(r) - require.NoError(t, err) - } - - req := &abci.RequestExtendVote{ - Hash: datagen.GenRandomByteArray(r, types.HashSize), - Height: 10, - } - - // error is expected due to nil BLS private key - _, err = helper.App.ExtendVote(helper.Ctx, req) - require.Error(t, err) - }) -} - -// FuzzExtendVote_NotInValidatorSet tests the case where the -// private signer is not in the validator set -func FuzzExtendVote_NotInValidatorSet(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - // generate the validator set with 10 validators as genesis - genesisValSet, ps, err := datagen.GenesisValidatorSetWithPrivSigner(10) - require.NoError(t, err) - - // the private signer is not included in the validator set - helper := testhelper.NewHelperWithValSetNoSigner(t, genesisValSet, ps) - - ek := helper.App.EpochingKeeper - - epoch := ek.GetEpoch(helper.Ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - - // go to block 10, reaching epoch boundary - interval := ek.GetParams(helper.Ctx).EpochInterval - for i := uint64(0); i < interval-2; i++ { - _, err := helper.ApplyEmptyBlockWithSomeInvalidVoteExtensions(r) - require.NoError(t, err) - } - - req := &abci.RequestExtendVote{ - Hash: datagen.GenRandomByteArray(r, types.HashSize), - Height: 10, - } - - // error is expected because the BLS signer in not - // in the validator set - _, err = helper.App.ExtendVote(helper.Ctx, req) - require.Error(t, err) - }) -} diff --git a/x/epoching/keeper/staking_functions.go b/x/epoching/keeper/staking_functions.go index cdc8ddf0d..a98a24e85 100644 --- a/x/epoching/keeper/staking_functions.go +++ b/x/epoching/keeper/staking_functions.go @@ -121,3 +121,7 @@ func (k Keeper) StkMsgCreateValidator(ctx context.Context, msg *stakingtypes.Msg func (k Keeper) GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) { return k.stk.GetPubKeyByConsAddr(ctx, consAddr) } + +func (k Keeper) GetValidator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.Validator, error) { + return k.stk.GetValidator(ctx, addr) +}