code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/key_rotate.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package cmd
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  
    24  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/cli"
    25  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/flags"
    26  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/printer"
    27  	"code.vegaprotocol.io/vega/wallet/api"
    28  	"code.vegaprotocol.io/vega/wallet/wallets"
    29  
    30  	"github.com/spf13/cobra"
    31  )
    32  
    33  var (
    34  	rotateKeyLong = cli.LongDesc(`
    35  		Build a signed key rotation transaction as a Base64 encoded string.
    36  		Choose a public key to rotate to and target block height.
    37  
    38  		The generated transaction can be sent using the command: "tx send".
    39  	`)
    40  
    41  	rotateKeyExample = cli.Examples(`
    42  		# Build signed transaction for rotating to new key public key
    43  		{{.Software}} key rotate --wallet WALLET --tx-height TX_HEIGHT --chain-id CHAIN_ID --target-height TARGET_HEIGHT --pubkey PUBLIC_KEY --current-pubkey CURRENT_PUBLIC_KEY
    44  	`)
    45  )
    46  
    47  type RotateKeyHandler func(api.AdminRotateKeyParams, string) (api.AdminRotateKeyResult, error)
    48  
    49  func NewCmdRotateKey(w io.Writer, rf *RootFlags) *cobra.Command {
    50  	h := func(params api.AdminRotateKeyParams, passphrase string) (api.AdminRotateKeyResult, error) {
    51  		ctx := context.Background()
    52  
    53  		walletStore, err := wallets.InitialiseStore(rf.Home, false)
    54  		if err != nil {
    55  			return api.AdminRotateKeyResult{}, fmt.Errorf("could not initialise wallets store: %w", err)
    56  		}
    57  		defer walletStore.Close()
    58  
    59  		if _, errDetails := api.NewAdminUnlockWallet(walletStore).Handle(ctx, api.AdminUnlockWalletParams{
    60  			Wallet:     params.Wallet,
    61  			Passphrase: passphrase,
    62  		}); errDetails != nil {
    63  			return api.AdminRotateKeyResult{}, errors.New(errDetails.Data)
    64  		}
    65  
    66  		rawResult, errDetails := api.NewAdminRotateKey(walletStore).Handle(context.Background(), params)
    67  		if errDetails != nil {
    68  			return api.AdminRotateKeyResult{}, errors.New(errDetails.Data)
    69  		}
    70  		return rawResult.(api.AdminRotateKeyResult), nil
    71  	}
    72  
    73  	return BuildCmdRotateKey(w, h, rf)
    74  }
    75  
    76  func BuildCmdRotateKey(w io.Writer, handler RotateKeyHandler, rf *RootFlags) *cobra.Command {
    77  	f := RotateKeyFlags{}
    78  
    79  	cmd := &cobra.Command{
    80  		Use:     "rotate",
    81  		Short:   "Build a signed key rotation transaction",
    82  		Long:    rotateKeyLong,
    83  		Example: rotateKeyExample,
    84  		RunE: func(_ *cobra.Command, args []string) error {
    85  			req, pass, err := f.Validate()
    86  			if err != nil {
    87  				return err
    88  			}
    89  
    90  			resp, err := handler(req, pass)
    91  			if err != nil {
    92  				return err
    93  			}
    94  
    95  			switch rf.Output {
    96  			case flags.InteractiveOutput:
    97  				PrintRotateKeyResponse(w, resp)
    98  			case flags.JSONOutput:
    99  				return printer.FprintJSON(w, resp)
   100  			}
   101  
   102  			return nil
   103  		},
   104  	}
   105  
   106  	cmd.Flags().StringVarP(&f.Wallet,
   107  		"wallet", "w",
   108  		"",
   109  		"Wallet holding the master key and new public key",
   110  	)
   111  	cmd.Flags().StringVarP(&f.PassphraseFile,
   112  		"passphrase-file", "p",
   113  		"",
   114  		"Path to the file containing the wallet's passphrase",
   115  	)
   116  	cmd.Flags().StringVar(&f.ToPublicKey,
   117  		"new-pubkey",
   118  		"",
   119  		"A public key to rotate to. Should be generated by wallet's 'generate' command.",
   120  	)
   121  	cmd.Flags().StringVar(&f.ChainID,
   122  		"chain-id",
   123  		"",
   124  		"The identifier of the chain on which the rotation will be done.",
   125  	)
   126  	cmd.Flags().StringVar(&f.FromPublicKey,
   127  		"current-pubkey",
   128  		"",
   129  		"A public key to rotate from. Should be currently used public key.",
   130  	)
   131  	cmd.Flags().Uint64Var(&f.SubmissionBlockHeight,
   132  		"tx-height",
   133  		0,
   134  		"It should be close to the current block height when the transaction is applied, with a threshold of ~ - 150 blocks.",
   135  	)
   136  	cmd.Flags().Uint64Var(&f.EnactmentBlockHeight,
   137  		"target-height",
   138  		0,
   139  		"Height of block where the public key change will take effect",
   140  	)
   141  
   142  	autoCompleteWallet(cmd, rf.Home, "wallet")
   143  
   144  	return cmd
   145  }
   146  
   147  type RotateKeyFlags struct {
   148  	Wallet                string
   149  	PassphraseFile        string
   150  	FromPublicKey         string
   151  	ToPublicKey           string
   152  	ChainID               string
   153  	SubmissionBlockHeight uint64
   154  	EnactmentBlockHeight  uint64
   155  }
   156  
   157  func (f *RotateKeyFlags) Validate() (api.AdminRotateKeyParams, string, error) {
   158  	if f.ToPublicKey == "" {
   159  		return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("new-pubkey")
   160  	}
   161  
   162  	if f.FromPublicKey == "" {
   163  		return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("current-pubkey")
   164  	}
   165  
   166  	if f.ChainID == "" {
   167  		return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("chain-id")
   168  	}
   169  
   170  	if f.EnactmentBlockHeight == 0 {
   171  		return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("target-height")
   172  	}
   173  
   174  	if f.SubmissionBlockHeight == 0 {
   175  		return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("tx-height")
   176  	}
   177  
   178  	if f.EnactmentBlockHeight <= f.SubmissionBlockHeight {
   179  		return api.AdminRotateKeyParams{}, "", flags.RequireLessThanFlagError("tx-height", "target-height")
   180  	}
   181  
   182  	if len(f.Wallet) == 0 {
   183  		return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("wallet")
   184  	}
   185  
   186  	passphrase, err := flags.GetPassphrase(f.PassphraseFile)
   187  	if err != nil {
   188  		return api.AdminRotateKeyParams{}, "", err
   189  	}
   190  
   191  	return api.AdminRotateKeyParams{
   192  		Wallet:                f.Wallet,
   193  		FromPublicKey:         f.FromPublicKey,
   194  		ToPublicKey:           f.ToPublicKey,
   195  		ChainID:               f.ChainID,
   196  		SubmissionBlockHeight: f.SubmissionBlockHeight,
   197  		EnactmentBlockHeight:  f.EnactmentBlockHeight,
   198  	}, passphrase, nil
   199  }
   200  
   201  func PrintRotateKeyResponse(w io.Writer, req api.AdminRotateKeyResult) {
   202  	p := printer.NewInteractivePrinter(w)
   203  
   204  	str := p.String()
   205  	defer p.Print(str)
   206  
   207  	str.CheckMark().SuccessText("Key rotation succeeded").NextSection()
   208  	str.Text("Transaction (base64-encoded):").NextLine()
   209  	str.WarningText(req.EncodedTransaction).NextSection()
   210  	str.Text("Master public key used:").NextLine()
   211  	str.WarningText(req.MasterPublicKey).NextLine()
   212  }