/
key_rotate.go
197 lines (164 loc) · 5.56 KB
/
key_rotate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package cmd
import (
"context"
"errors"
"fmt"
"io"
"github.com/elysiumstation/fury/cmd/furywallet/commands/cli"
"github.com/elysiumstation/fury/cmd/furywallet/commands/flags"
"github.com/elysiumstation/fury/cmd/furywallet/commands/printer"
"github.com/elysiumstation/fury/wallet/api"
"github.com/elysiumstation/fury/wallet/wallets"
"github.com/spf13/cobra"
)
var (
rotateKeyLong = cli.LongDesc(`
Build a signed key rotation transaction as a Base64 encoded string.
Choose a public key to rotate to and target block height.
The generated transaction can be sent using the command: "tx send".
`)
rotateKeyExample = cli.Examples(`
# Build signed transaction for rotating to new key public key
{{.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
`)
)
type RotateKeyHandler func(api.AdminRotateKeyParams, string) (api.AdminRotateKeyResult, error)
func NewCmdRotateKey(w io.Writer, rf *RootFlags) *cobra.Command {
h := func(params api.AdminRotateKeyParams, passphrase string) (api.AdminRotateKeyResult, error) {
ctx := context.Background()
walletStore, err := wallets.InitialiseStore(rf.Home, false)
if err != nil {
return api.AdminRotateKeyResult{}, fmt.Errorf("could not initialise wallets store: %w", err)
}
defer walletStore.Close()
if _, errDetails := api.NewAdminUnlockWallet(walletStore).Handle(ctx, api.AdminUnlockWalletParams{
Wallet: params.Wallet,
Passphrase: passphrase,
}); errDetails != nil {
return api.AdminRotateKeyResult{}, errors.New(errDetails.Data)
}
rawResult, errDetails := api.NewAdminRotateKey(walletStore).Handle(context.Background(), params)
if errDetails != nil {
return api.AdminRotateKeyResult{}, errors.New(errDetails.Data)
}
return rawResult.(api.AdminRotateKeyResult), nil
}
return BuildCmdRotateKey(w, h, rf)
}
func BuildCmdRotateKey(w io.Writer, handler RotateKeyHandler, rf *RootFlags) *cobra.Command {
f := RotateKeyFlags{}
cmd := &cobra.Command{
Use: "rotate",
Short: "Build a signed key rotation transaction",
Long: rotateKeyLong,
Example: rotateKeyExample,
RunE: func(_ *cobra.Command, args []string) error {
req, pass, err := f.Validate()
if err != nil {
return err
}
resp, err := handler(req, pass)
if err != nil {
return err
}
switch rf.Output {
case flags.InteractiveOutput:
PrintRotateKeyResponse(w, resp)
case flags.JSONOutput:
return printer.FprintJSON(w, resp)
}
return nil
},
}
cmd.Flags().StringVarP(&f.Wallet,
"wallet", "w",
"",
"Wallet holding the master key and new public key",
)
cmd.Flags().StringVarP(&f.PassphraseFile,
"passphrase-file", "p",
"",
"Path to the file containing the wallet's passphrase",
)
cmd.Flags().StringVar(&f.ToPublicKey,
"new-pubkey",
"",
"A public key to rotate to. Should be generated by wallet's 'generate' command.",
)
cmd.Flags().StringVar(&f.ChainID,
"chain-id",
"",
"The identifier of the chain on which the rotation will be done.",
)
cmd.Flags().StringVar(&f.FromPublicKey,
"current-pubkey",
"",
"A public key to rotate from. Should be currently used public key.",
)
cmd.Flags().Uint64Var(&f.SubmissionBlockHeight,
"tx-height",
0,
"It should be close to the current block height when the transaction is applied, with a threshold of ~ - 150 blocks.",
)
cmd.Flags().Uint64Var(&f.EnactmentBlockHeight,
"target-height",
0,
"Height of block where the public key change will take effect",
)
autoCompleteWallet(cmd, rf.Home, "wallet")
return cmd
}
type RotateKeyFlags struct {
Wallet string
PassphraseFile string
FromPublicKey string
ToPublicKey string
ChainID string
SubmissionBlockHeight uint64
EnactmentBlockHeight uint64
}
func (f *RotateKeyFlags) Validate() (api.AdminRotateKeyParams, string, error) {
if f.ToPublicKey == "" {
return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("new-pubkey")
}
if f.FromPublicKey == "" {
return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("current-pubkey")
}
if f.ChainID == "" {
return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("chain-id")
}
if f.EnactmentBlockHeight == 0 {
return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("target-height")
}
if f.SubmissionBlockHeight == 0 {
return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("tx-height")
}
if f.EnactmentBlockHeight <= f.SubmissionBlockHeight {
return api.AdminRotateKeyParams{}, "", flags.RequireLessThanFlagError("tx-height", "target-height")
}
if len(f.Wallet) == 0 {
return api.AdminRotateKeyParams{}, "", flags.MustBeSpecifiedError("wallet")
}
passphrase, err := flags.GetPassphrase(f.PassphraseFile)
if err != nil {
return api.AdminRotateKeyParams{}, "", err
}
return api.AdminRotateKeyParams{
Wallet: f.Wallet,
FromPublicKey: f.FromPublicKey,
ToPublicKey: f.ToPublicKey,
ChainID: f.ChainID,
SubmissionBlockHeight: f.SubmissionBlockHeight,
EnactmentBlockHeight: f.EnactmentBlockHeight,
}, passphrase, nil
}
func PrintRotateKeyResponse(w io.Writer, req api.AdminRotateKeyResult) {
p := printer.NewInteractivePrinter(w)
str := p.String()
defer p.Print(str)
str.CheckMark().SuccessText("Key rotation succeeded").NextSection()
str.Text("Transaction (base64-encoded):").NextLine()
str.WarningText(req.EncodedTransaction).NextSection()
str.Text("Master public key used:").NextLine()
str.WarningText(req.MasterPublicKey).NextLine()
}