From dcc8235ea2cb7147421abf4cf64fd86fe54b7585 Mon Sep 17 00:00:00 2001 From: Hyoung-yoon Kim Date: Thu, 14 Dec 2023 13:32:37 +0900 Subject: [PATCH] feat: add key dedicated to vrf --- .gitignore | 1 + DEVELOPING.md | 7 ++ app/app.go | 11 ++- cmd/seda-chaind/utils/keys.go | 172 ++++++++++++++++++++++++++++++++++ x/randomness/keeper/abci.go | 116 ++++++----------------- 5 files changed, 216 insertions(+), 91 deletions(-) create mode 100644 cmd/seda-chaind/utils/keys.go diff --git a/.gitignore b/.gitignore index 56dfc93a..b79e98a1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ scripts/testnet/artifacts # Ignore the env file one may actually fill out seda.env .env +.netrc # coverage files coverage.txt \ No newline at end of file diff --git a/DEVELOPING.md b/DEVELOPING.md index a7d191db..d407e7f9 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -167,6 +167,13 @@ machine github.com password ``` +The e2e testing can now be run with the following command: +```bash +machine github.com + login + password +``` + Change the permissions of `.netrc` and run e2e with the following commands: ```bash chmod 600 .netrc diff --git a/app/app.go b/app/app.go index 7bd10845..9c697290 100644 --- a/app/app.go +++ b/app/app.go @@ -123,6 +123,7 @@ import ( ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" appparams "github.com/sedaprotocol/seda-chain/app/params" + "github.com/sedaprotocol/seda-chain/cmd/seda-chaind/utils" "github.com/sedaprotocol/seda-chain/docs" randomness "github.com/sedaprotocol/seda-chain/x/randomness" "github.com/sedaprotocol/seda-chain/x/randomness/keeper" @@ -866,11 +867,17 @@ func NewApp( app.SetEndBlocker(app.EndBlocker) // Pseudorandomness beacon + // homePath, + // cast.ToString(appOpts.Get("priv_validator_key_file")), + vrfKey, err := utils.LoadOrGenVRFKey("~/.seda-chain/config/vrf_key.json") + if err != nil { + panic(fmt.Errorf("failed to load of generate VRF key: %w", err)) + } + app.SetPrepareProposal( randomnesskeeper.PrepareProposalHandler( txConfig, - homePath, - cast.ToString(appOpts.Get("priv_validator_key_file")), + vrfKey, app.RandomnessKeeper, app.AccountKeeper, app.StakingKeeper, diff --git a/cmd/seda-chaind/utils/keys.go b/cmd/seda-chaind/utils/keys.go new file mode 100644 index 00000000..e93703cf --- /dev/null +++ b/cmd/seda-chaind/utils/keys.go @@ -0,0 +1,172 @@ +package utils + +import ( + "context" + "fmt" + "os" + + "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/crypto/secp256k1" + cmtjson "github.com/cometbft/cometbft/libs/json" + cmtos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/types" + + "github.com/cosmos/cosmos-sdk/client" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdkcrypto "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + + vrf "github.com/sedaprotocol/vrf-go" +) + +var _ VRFSigner = &VRFKey{} + +type VRFSigner interface { + VRFProve(alpha []byte) (pi, beta []byte, err error) + SignTransaction(ctx sdk.Context, + signMode signing.SignMode, + txBuilder client.TxBuilder, txConfig client.TxConfig, account sdk.AccountI) (signing.SignatureV2, error) +} + +type VRFKey struct { + Address types.Address `json:"address"` + PubKey sdkcrypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` // TO-DO can we not export it? + + filePath string + vrf *vrf.VRFStruct +} + +// Save persists the VRFKey to its filePath. +func (key VRFKey) Save() error { + outFile := key.filePath + if outFile == "" { + return fmt.Errorf("key's file path is empty") + } + + jsonBytes, err := cmtjson.MarshalIndent(key, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal key: %v", err) + } + + err = os.WriteFile(outFile, jsonBytes, 0o600) + if err != nil { + return fmt.Errorf("failed to write key file: %v", err) + } + return nil +} + +func (v *VRFKey) VRFProve(alpha []byte) (pi, beta []byte, err error) { + pi, err = v.vrf.Prove(v.PrivKey.Bytes(), alpha) + if err != nil { + return nil, nil, err + } + beta, err = v.vrf.ProofToHash(pi) + if err != nil { + return nil, nil, err + } + return pi, beta, nil +} + +func (v *VRFKey) SignTransaction( + ctx sdk.Context, + signMode signing.SignMode, + txBuilder client.TxBuilder, txConfig client.TxConfig, account sdk.AccountI, +) (signing.SignatureV2, error) { + var sigV2 signing.SignatureV2 + + signerData := authsigning.SignerData{ + ChainID: ctx.ChainID(), + AccountNumber: account.GetAccountNumber(), + Sequence: account.GetSequence(), + PubKey: v.PubKey, + Address: account.GetAddress().String(), + } + + bytesToSign, err := authsigning.GetSignBytesAdapter( + context.Background(), + txConfig.SignModeHandler(), + signMode, + signerData, + txBuilder.GetTx(), + ) + if err != nil { + return sigV2, err + } + + sigBytes, err := v.PrivKey.Sign(bytesToSign) + if err != nil { + return sigV2, err + } + + sigV2 = signing.SignatureV2{ + PubKey: v.PubKey, + Data: &txsigning.SingleSignatureData{ + SignMode: signMode, + Signature: sigBytes, + }, + Sequence: account.GetSequence(), + } + + return sigV2, nil +} + +// NewVRFKey generates a new VRFKey from the given key and key file path. +func NewVRFKey(privKey crypto.PrivKey, keyFilePath string) (*VRFKey, error) { + vrfStruct := vrf.NewK256VRF(0xFE) + pubKey, err := cryptocodec.FromCmtPubKeyInterface(privKey.PubKey()) + if err != nil { + return nil, err + } + return &VRFKey{ + Address: privKey.PubKey().Address(), + PubKey: pubKey, + PrivKey: privKey, + filePath: keyFilePath, + vrf: &vrfStruct, + }, nil +} + +// GenVRFKey generates a new VRFKey with a randomly generated private key. +func GenVRFKey(keyFilePath string) (*VRFKey, error) { + return NewVRFKey(secp256k1.GenPrivKey(), keyFilePath) +} + +func LoadVRFKey(keyFilePath string) (*VRFKey, error) { + keyJSONBytes, err := os.ReadFile(keyFilePath) + if err != nil { + return nil, fmt.Errorf("error reading VRF key from %v: %v", keyFilePath, err) + } + vrfKey := new(VRFKey) + err = cmtjson.Unmarshal(keyJSONBytes, vrfKey) + if err != nil { + return nil, fmt.Errorf("error unmarshalling VRF key from %v: %v", keyFilePath, err) + } + return vrfKey, nil +} + +// LoadOrGenVRFKey loads a VRFKey from the given file path +// or else generates a new one and saves it to the file path. +func LoadOrGenVRFKey(keyFilePath string) (*VRFKey, error) { + var vrfKey *VRFKey + var err error + if cmtos.FileExists(keyFilePath) { + vrfKey, err = LoadVRFKey(keyFilePath) + if err != nil { + return nil, err + } + } else { + vrfKey, err = GenVRFKey(keyFilePath) + if err != nil { + return nil, err + } + err = vrfKey.Save() + if err != nil { + return nil, err + } + } + return vrfKey, nil +} diff --git a/x/randomness/keeper/abci.go b/x/randomness/keeper/abci.go index 6ea6f11c..c67817bd 100644 --- a/x/randomness/keeper/abci.go +++ b/x/randomness/keeper/abci.go @@ -2,33 +2,26 @@ package keeper import ( "bytes" - "context" "encoding/hex" "fmt" - "os" - "path/filepath" vrf "github.com/sedaprotocol/vrf-go" abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/crypto" - cmtjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/privval" "github.com/cosmos/cosmos-sdk/client" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/sedaprotocol/seda-chain/cmd/seda-chaind/utils" "github.com/sedaprotocol/seda-chain/x/randomness/types" ) func PrepareProposalHandler( txConfig client.TxConfig, - homePath string, - pvFile string, + vrfSigner utils.VRFSigner, keeper Keeper, authKeeper types.AccountKeeper, stakingKeeper types.StakingKeeper, @@ -48,20 +41,8 @@ func PrepareProposalHandler( } alpha := append([]byte(prevSeed), timestamp...) - // prepare secret key - secretKey, err := readPrivKey(filepath.Join(homePath, pvFile)) - if err != nil { - return nil, err - } - // produce VRF proof - k256vrf := vrf.NewK256VRF() - pi, err := k256vrf.Prove(secretKey.Bytes(), alpha) - if err != nil { - return nil, err - } - - beta, err := k256vrf.ProofToHash(pi) + pi, beta, err := vrfSigner.VRFProve(alpha) if err != nil { return nil, err } @@ -76,7 +57,7 @@ func PrepareProposalHandler( } account := authKeeper.GetAccount(ctx, sdk.AccAddress(publicKey.Address().Bytes())) - newSeedTx, _, err := encodeNewSeedTx(ctx, txConfig, secretKey, publicKey, account, &types.MsgNewSeed{ + newSeedTx, err := generateAndSignNewSeedTx(ctx, txConfig, vrfSigner, publicKey, account, &types.MsgNewSeed{ Proposer: sdk.AccAddress(req.ProposerAddress).String(), Pi: hex.EncodeToString(pi), Beta: hex.EncodeToString(beta), @@ -85,7 +66,7 @@ func PrepareProposalHandler( return nil, err } - // TO-DO mempool + // TO-DO mempool? // err = mempool.Insert(ctx, tx) // if err != nil { // return nil, err @@ -162,35 +143,24 @@ func ProcessProposalHandler( } } -func encodeNewSeedTx(ctx sdk.Context, txConfig client.TxConfig, privKey crypto.PrivKey, pubKey cryptotypes.PubKey, account sdk.AccountI, msg *types.MsgNewSeed) ([]byte, sdk.Tx, error) { +// generateAndSignNewSeedTx generates and signs a transaction containing +// a given NewSeed message. It returns a transaction encoded into bytes. +func generateAndSignNewSeedTx(ctx sdk.Context, txConfig client.TxConfig, vrfSigner utils.VRFSigner, pubKey cryptotypes.PubKey, account sdk.AccountI, msg *types.MsgNewSeed) ([]byte, error) { + // build a transaction containing the given message txBuilder := txConfig.NewTxBuilder() err := txBuilder.SetMsgs(msg) if err != nil { - return nil, nil, err + return nil, err } - txBuilder.SetFeePayer(account.GetAddress()) - txBuilder.SetFeeAmount(sdk.NewCoins()) txBuilder.SetGasLimit(200000) // TO-DO what number to put here? + txBuilder.SetFeeAmount(sdk.NewCoins()) + txBuilder.SetFeePayer(account.GetAddress()) - signerData := authsigning.SignerData{ - ChainID: ctx.ChainID(), - AccountNumber: account.GetAccountNumber(), - Sequence: account.GetSequence(), - PubKey: pubKey, - Address: account.GetAddress().String(), - } - - // TO-DO re-examine signing logic + // sign the transaction - // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on - // TxBuilder under the hood, and SignerInfos is needed to generate the sign - // bytes. This is the reason for setting SetSignatures here, with a nil - // signature. - // - // Note: This line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it - // also doesn't affect its generated sign bytes, so for code's simplicity - // sake, we put it here. + // gather signing info without actually signing + // TO-DO check if this step can be skipped sig := txsigning.SignatureV2{ PubKey: pubKey, Data: &txsigning.SingleSignatureData{ @@ -201,48 +171,29 @@ func encodeNewSeedTx(ctx sdk.Context, txConfig client.TxConfig, privKey crypto.P } if err := txBuilder.SetSignatures(sig); err != nil { - return nil, nil, err + return nil, err } - bytesToSign, err := authsigning.GetSignBytesAdapter( - context.Background(), - txConfig.SignModeHandler(), + // sign + sig, err = vrfSigner.SignTransaction( + ctx, txsigning.SignMode_SIGN_MODE_DIRECT, - signerData, - txBuilder.GetTx(), + txBuilder, + txConfig, + account, ) - if err != nil { - return nil, nil, err - } - - sigBytes, err := privKey.Sign(bytesToSign) - if err != nil { - return nil, nil, err - } - sig = txsigning.SignatureV2{ - PubKey: pubKey, - Data: &txsigning.SingleSignatureData{ - SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, - Signature: sigBytes, - }, - Sequence: account.GetSequence(), - } if err := txBuilder.SetSignatures(sig); err != nil { - return nil, nil, err + return nil, err } - signedTx := txBuilder.GetTx() - txBytes, err := txConfig.TxEncoder()(signedTx) + tx := txBuilder.GetTx() + txBytes, err := txConfig.TxEncoder()(tx) if err != nil { - return nil, nil, err + return nil, err } - tx, err := txConfig.TxDecoder()(txBytes) - if err != nil { - return nil, nil, err - } - return txBytes, tx, nil + return txBytes, nil } func decodeNewSeedTx(txConfig client.TxConfig, txBytes []byte) (*types.MsgNewSeed, error) { @@ -260,16 +211,3 @@ func decodeNewSeedTx(txConfig client.TxConfig, txBytes []byte) (*types.MsgNewSee } return msgNewSeed, nil } - -func readPrivKey(keyFilePath string) (crypto.PrivKey, error) { - keyJSONBytes, err := os.ReadFile(keyFilePath) - if err != nil { - return nil, err - } - pvKey := privval.FilePVKey{} - err = cmtjson.Unmarshal(keyJSONBytes, &pvKey) - if err != nil { - return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err) - } - return pvKey.PrivKey, nil -}