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 }