From 69198f143721f56bb93aa2a4699812fb371111c8 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 25 Jun 2019 21:22:28 -0400 Subject: [PATCH 01/12] enforce cobra.NoArgs for algokey commands --- cmd/algokey/export.go | 3 ++- cmd/algokey/generate.go | 3 ++- cmd/algokey/import.go | 3 ++- cmd/algokey/main.go | 1 + cmd/algokey/multisig.go | 3 ++- cmd/algokey/sign.go | 3 ++- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/algokey/export.go b/cmd/algokey/export.go index 66312d56ba..507e5e0b7d 100644 --- a/cmd/algokey/export.go +++ b/cmd/algokey/export.go @@ -37,7 +37,8 @@ func init() { var exportCmd = &cobra.Command{ Use: "export", Short: "Export key file to mnemonic and public key", - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { seed := loadKeyfile(exportKeyfile) mnemonic := computeMnemonic(seed) diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index 2b993a4b54..3cd353fbc6 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -36,7 +36,8 @@ func init() { var generateCmd = &cobra.Command{ Use: "generate", Short: "Generate key", - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { var seed crypto.Seed crypto.RandBytes(seed[:]) diff --git a/cmd/algokey/import.go b/cmd/algokey/import.go index ceb6524eff..ac157dbcc1 100644 --- a/cmd/algokey/import.go +++ b/cmd/algokey/import.go @@ -37,7 +37,8 @@ func init() { var importCmd = &cobra.Command{ Use: "import", Short: "Import key file from mnemonic", - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { seed := loadMnemonic(mnemonic) key := crypto.GenerateSignatureSecrets(seed) diff --git a/cmd/algokey/main.go b/cmd/algokey/main.go index c83221170f..df7794ef95 100644 --- a/cmd/algokey/main.go +++ b/cmd/algokey/main.go @@ -26,6 +26,7 @@ import ( var rootCmd = &cobra.Command{ Use: "algokey", Short: "CLI for managing Algorand keys", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { // If no arguments passed, we should fallback to help cmd.HelpFunc()(cmd, args) diff --git a/cmd/algokey/multisig.go b/cmd/algokey/multisig.go index e91710fd0d..1075eea537 100644 --- a/cmd/algokey/multisig.go +++ b/cmd/algokey/multisig.go @@ -46,7 +46,8 @@ func init() { var multisigCmd = &cobra.Command{ Use: "multisig", Short: "Add a multisig signature to transactions from a file using a private key", - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { seed := loadKeyfileOrMnemonic(multisigKeyfile, multisigMnemonic) key := crypto.GenerateSignatureSecrets(seed) diff --git a/cmd/algokey/sign.go b/cmd/algokey/sign.go index e4981ab4a4..3d8d423302 100644 --- a/cmd/algokey/sign.go +++ b/cmd/algokey/sign.go @@ -46,7 +46,8 @@ func init() { var signCmd = &cobra.Command{ Use: "sign", Short: "Sign transactions from a file using a private key", - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { seed := loadKeyfileOrMnemonic(signKeyfile, signMnemonic) key := crypto.GenerateSignatureSecrets(seed) From 8f81942baa46b4e71bf103458f8ec5f8ac72e538 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 25 Jun 2019 22:04:33 -0400 Subject: [PATCH 02/12] add algokey support for managing partkeys --- cmd/algokey/main.go | 1 + cmd/algokey/part.go | 175 ++++++++++++++++++++++++++++++++++ data/account/participation.go | 8 ++ 3 files changed, 184 insertions(+) create mode 100644 cmd/algokey/part.go diff --git a/cmd/algokey/main.go b/cmd/algokey/main.go index df7794ef95..6f1c40b03d 100644 --- a/cmd/algokey/main.go +++ b/cmd/algokey/main.go @@ -39,6 +39,7 @@ func init() { rootCmd.AddCommand(exportCmd) rootCmd.AddCommand(signCmd) rootCmd.AddCommand(multisigCmd) + rootCmd.AddCommand(partCmd) } func main() { diff --git a/cmd/algokey/part.go b/cmd/algokey/part.go new file mode 100644 index 0000000000..0ef6846ffd --- /dev/null +++ b/cmd/algokey/part.go @@ -0,0 +1,175 @@ +// Copyright (C) 2019 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "encoding/base64" + "fmt" + "math" + "os" + + "github.com/spf13/cobra" + + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/util/db" +) + +var partKeyfile string +var partFirstRound uint64 +var partLastRound uint64 +var partKeyDilution uint64 +var partParent string + +var partCmd = &cobra.Command{ + Use: "part", + Short: "Manage participation keys", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + // If no arguments passed, we should fallback to help + cmd.HelpFunc()(cmd, args) + }, +} + +var partGenerateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate participation key", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { + if partLastRound < partFirstRound { + fmt.Fprintf(os.Stderr, "Last round %d < first round %d\n", partLastRound, partFirstRound) + os.Exit(1) + } + + if partKeyDilution == 0 { + partKeyDilution = 1 + uint64(math.Sqrt(float64(partLastRound-partFirstRound))) + } + + var err error + var parent basics.Address + if partParent != "" { + parent, err = basics.UnmarshalChecksumAddress(partParent) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot parse parent address %s: %v\n", partParent, err) + os.Exit(1) + } + } + + partdb, err := db.MakeErasableAccessor(partKeyfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot open partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + partkey, err := account.FillDBWithParticipationKeys(partdb, parent, basics.Round(partFirstRound), basics.Round(partLastRound), partKeyDilution) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot generate partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + printPartkey(partkey) + }, +} + +var partInfoCmd = &cobra.Command{ + Use: "info", + Short: "Print participation key information", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { + partdb, err := db.MakeErasableAccessor(partKeyfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot open partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + partkey, err := account.RestoreParticipation(partdb) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot load partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + printPartkey(partkey) + }, +} + +var partReparentCmd = &cobra.Command{ + Use: "reparent", + Short: "Change parent address of participation key", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { + parent, err := basics.UnmarshalChecksumAddress(partParent) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot parse parent address %s: %v\n", partParent, err) + os.Exit(1) + } + + partdb, err := db.MakeErasableAccessor(partKeyfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot open partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + partkey, err := account.RestoreParticipation(partdb) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot load partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + partkey.Parent = parent + err = partkey.PersistNewParent() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot persist partkey database %s: %v\n", partKeyfile, err) + os.Exit(1) + } + + printPartkey(partkey) + }, +} + +func printPartkey(partkey account.Participation) { + fmt.Printf("Parent address: %s\n", partkey.Parent.GetChecksumAddress().String()) + fmt.Printf("VRF public key: %s\n", base64.StdEncoding.EncodeToString(partkey.VRF.PK[:])) + fmt.Printf("Voting public key: %s\n", base64.StdEncoding.EncodeToString(partkey.Voting.OneTimeSignatureVerifier[:])) + fmt.Printf("First round: %d\n", partkey.FirstValid) + fmt.Printf("Last round: %d\n", partkey.LastValid) + fmt.Printf("Key dilution: %d\n", partkey.KeyDilution) + fmt.Printf("First batch: %d\n", partkey.Voting.FirstBatch) + fmt.Printf("First offset: %d\n", partkey.Voting.FirstOffset) +} + +func init() { + partCmd.AddCommand(partGenerateCmd) + partCmd.AddCommand(partInfoCmd) + partCmd.AddCommand(partReparentCmd) + + partGenerateCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") + partGenerateCmd.Flags().Uint64VarP(&partFirstRound, "first", "", 0, "First round for participation key") + partGenerateCmd.Flags().Uint64VarP(&partLastRound, "last", "", 0, "Last round for participation key") + partGenerateCmd.Flags().Uint64VarP(&partKeyDilution, "dilution", "", 0, "Key dilution (default to sqrt of validity window)") + partGenerateCmd.Flags().StringVarP(&partParent, "parent", "", "", "Address of parent account") + partGenerateCmd.MarkFlagRequired("first") + partGenerateCmd.MarkFlagRequired("last") + partGenerateCmd.MarkFlagRequired("keyfile") + + partInfoCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") + partInfoCmd.MarkFlagRequired("keyfile") + + partReparentCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") + partReparentCmd.Flags().StringVarP(&partParent, "parent", "", "", "Address of parent account") + partReparentCmd.MarkFlagRequired("keyfile") + partReparentCmd.MarkFlagRequired("parent") +} diff --git a/data/account/participation.go b/data/account/participation.go index 5796ea2960..c36f24fb73 100644 --- a/data/account/participation.go +++ b/data/account/participation.go @@ -103,6 +103,14 @@ func (part Participation) DeleteOldKeys(current basics.Round, proto config.Conse }) } +// PersistNewParent writes a new parent address to the partkey database. +func (part Participation) PersistNewParent() error { + return part.Store.Atomic(func(tx *sql.Tx) error { + _, err := tx.Exec("UPDATE ParticipationAccount SET parent=?", part.Parent[:]) + return err + }) +} + // VRFSecrets returns the VRF secrets associated with this Participation account. func (part Participation) VRFSecrets() *crypto.VRFSecrets { return part.VRF From ca98cef7ab89bb969a3e093ce83ad86b0fb76b9d Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 25 Jun 2019 22:35:38 -0400 Subject: [PATCH 03/12] add a "goal account installpartkey" command Installs a partkey that was generated by algokey --- cmd/goal/account.go | 23 +++++++++++++++++++++++ libgoal/participation.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 23aa655771..7586f3c204 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -56,6 +56,7 @@ var ( keyDilution uint64 threshold uint8 partKeyOutDir string + inputPartkey string importDefault bool mnemonic string ) @@ -69,6 +70,7 @@ func init() { accountCmd.AddCommand(rewardsCmd) accountCmd.AddCommand(changeOnlineCmd) accountCmd.AddCommand(addParticipationKeyCmd) + accountCmd.AddCommand(installParticipationKeyCmd) accountCmd.AddCommand(listParticipationKeysCmd) accountCmd.AddCommand(importCmd) accountCmd.AddCommand(exportCmd) @@ -138,6 +140,10 @@ func init() { addParticipationKeyCmd.Flags().StringVarP(&partKeyOutDir, "outdir", "o", "", "Save participation key file to specified output directory to (for offline creation)") addParticipationKeyCmd.Flags().Uint64VarP(&keyDilution, "keyDilution", "", 0, "Key dilution for two-level participation keys") + // installParticipationKey flags + installParticipationKeyCmd.Flags().StringVarP(&inputPartkey, "partkey", "", "", "Participation key file to install") + installParticipationKeyCmd.MarkFlagRequired("partkey") + // import flags importCmd.Flags().BoolVarP(&importDefault, "default", "f", false, "Set this account as the default one") importCmd.Flags().StringVarP(&mnemonic, "mnemonic", "m", "", "Mnemonic to import (will prompt otherwise)") @@ -579,6 +585,23 @@ var addParticipationKeyCmd = &cobra.Command{ }, } +var installParticipationKeyCmd = &cobra.Command{ + Use: "installpartkey", + Short: "Install a participation key", + Long: `Install a participation key`, + Args: validateNoPosArgsFn, + Run: func(cmd *cobra.Command, args []string) { + dataDir := ensureSingleDataDir() + + client := ensureFullClient(dataDir) + _, _, err := client.InstallParticipationKeys(inputPartkey) + if err != nil { + reportErrorf(errorRequestFail, err) + } + fmt.Println("Participation key installed successfully") + }, +} + var renewParticipationKeyCmd = &cobra.Command{ Use: "renewpartkey", Short: "Renew an account's participation key", diff --git a/libgoal/participation.go b/libgoal/participation.go index 993a173be5..4d240c7e5d 100644 --- a/libgoal/participation.go +++ b/libgoal/participation.go @@ -167,6 +167,44 @@ func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, k return newPart, partKeyPath, err } +// InstallParticipationKeys creates a .partkey database for a given address, +// based on an existing database from inputfile +func (c *Client) InstallParticipationKeys(inputfile string) (part account.Participation, filePath string, err error) { + // Get the GenesisID for use in the participation key path + var genID string + genID, err = c.GenesisID() + if err != nil { + return + } + + outDir := filepath.Join(c.DataDir(), genID) + + inputdb, err := db.MakeErasableAccessor(inputfile) + if err != nil { + return + } + defer inputdb.Close() + + partkey, err := account.RestoreParticipation(inputdb) + if err != nil { + return + } + + newdbpath, err := participationKeysPath(outDir, partkey.Parent, partkey.FirstValid, partkey.LastValid) + if err != nil { + return + } + + newdb, err := db.MakeErasableAccessor(newdbpath) + if err != nil { + return + } + + partkey.Store = newdb + err = partkey.Persist() + return partkey, newdbpath, err +} + // ListParticipationKeys returns the available participation keys, // as a map from database filename to Participation key object. func (c *Client) ListParticipationKeys() (partKeyFiles map[string]account.Participation, err error) { From 95833a04f5e119f1d7828f019265b91f4048dfbd Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 25 Jun 2019 22:57:23 -0400 Subject: [PATCH 04/12] allow specifying a partkey file for "goal account changeonlinestatus" --- cmd/goal/account.go | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 7586f3c204..be4d120a99 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -56,7 +56,7 @@ var ( keyDilution uint64 threshold uint8 partKeyOutDir string - inputPartkey string + partKeyFile string importDefault bool mnemonic string ) @@ -120,8 +120,8 @@ func init() { rewardsCmd.MarkFlagRequired("address") // changeOnlineStatus flags - changeOnlineCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to change (required)") - changeOnlineCmd.MarkFlagRequired("address") + changeOnlineCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to change (required if no -partkeyfile)") + changeOnlineCmd.Flags().StringVarP(&partKeyFile, "partkeyfile", "", "", "Participation key file (required if no -account)") changeOnlineCmd.Flags().BoolVarP(&online, "online", "o", true, "Set this account to online or offline") changeOnlineCmd.MarkFlagRequired("online") changeOnlineCmd.Flags().Uint64VarP(&transactionFee, "fee", "f", 0, "The Fee to set on the status change transaction (defaults to suggested fee)") @@ -141,7 +141,7 @@ func init() { addParticipationKeyCmd.Flags().Uint64VarP(&keyDilution, "keyDilution", "", 0, "Key dilution for two-level participation keys") // installParticipationKey flags - installParticipationKeyCmd.Flags().StringVarP(&inputPartkey, "partkey", "", "", "Participation key file to install") + installParticipationKeyCmd.Flags().StringVarP(&partKeyFile, "partkey", "", "", "Participation key file to install") installParticipationKeyCmd.MarkFlagRequired("partkey") // import flags @@ -477,11 +477,36 @@ var changeOnlineCmd = &cobra.Command{ Long: `Change online status for the specified account. Set online should be 1 to set online, 0 to set offline. The broadcast transaction will be valid for a limited number of rounds. goal will provide the TXID of the transaction if successful. Going online requires that the given account have a valid participation key.`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { + if accountAddress == "" && partKeyFile == "" { + fmt.Printf("Must specify one of --address or --partkeyfile\n") + os.Exit(1) + } + // Pull the current round for use in our new transactions dataDir := ensureSingleDataDir() client := ensureFullClient(dataDir) - err := changeAccountOnlineStatus(accountAddress, nil, online, onlineTxFile, walletName, onlineFirstRound, onlineValidRounds, transactionFee, dataDir, client) + var part *algodAcct.Participation + if partKeyFile != "" { + partdb, err := db.MakeErasableAccessor(partKeyFile) + if err != nil { + fmt.Printf("Cannot open partkey %s: %v\n", partKeyFile, err) + os.Exit(1) + } + + partkey, err := algodAcct.RestoreParticipation(partdb) + if err != nil { + fmt.Printf("Cannot load partkey %s: %v\n", partKeyFile, err) + os.Exit(1) + } + + part = &partkey + if accountAddress == "" { + accountAddress = part.Parent.GetChecksumAddress().String() + } + } + + err := changeAccountOnlineStatus(accountAddress, part, online, onlineTxFile, walletName, onlineFirstRound, onlineValidRounds, transactionFee, dataDir, client) if err != nil { reportErrorf(err.Error()) } @@ -594,7 +619,7 @@ var installParticipationKeyCmd = &cobra.Command{ dataDir := ensureSingleDataDir() client := ensureFullClient(dataDir) - _, _, err := client.InstallParticipationKeys(inputPartkey) + _, _, err := client.InstallParticipationKeys(partKeyFile) if err != nil { reportErrorf(errorRequestFail, err) } From e3c64c4abdf8ab431d3fb4e5daae0bcdff822ea3 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 07:32:41 -0400 Subject: [PATCH 05/12] use StringVar instead of StringVarP when appropriate --- cmd/algokey/part.go | 16 ++++++++-------- cmd/goal/account.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/algokey/part.go b/cmd/algokey/part.go index 0ef6846ffd..46d1f55447 100644 --- a/cmd/algokey/part.go +++ b/cmd/algokey/part.go @@ -156,20 +156,20 @@ func init() { partCmd.AddCommand(partInfoCmd) partCmd.AddCommand(partReparentCmd) - partGenerateCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") - partGenerateCmd.Flags().Uint64VarP(&partFirstRound, "first", "", 0, "First round for participation key") - partGenerateCmd.Flags().Uint64VarP(&partLastRound, "last", "", 0, "Last round for participation key") - partGenerateCmd.Flags().Uint64VarP(&partKeyDilution, "dilution", "", 0, "Key dilution (default to sqrt of validity window)") - partGenerateCmd.Flags().StringVarP(&partParent, "parent", "", "", "Address of parent account") + partGenerateCmd.Flags().StringVar(&partKeyfile, "keyfile", "", "Participation key filename") + partGenerateCmd.Flags().Uint64Var(&partFirstRound, "first", 0, "First round for participation key") + partGenerateCmd.Flags().Uint64Var(&partLastRound, "last", 0, "Last round for participation key") + partGenerateCmd.Flags().Uint64Var(&partKeyDilution, "dilution", 0, "Key dilution (default to sqrt of validity window)") + partGenerateCmd.Flags().StringVar(&partParent, "parent", "", "Address of parent account") partGenerateCmd.MarkFlagRequired("first") partGenerateCmd.MarkFlagRequired("last") partGenerateCmd.MarkFlagRequired("keyfile") - partInfoCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") + partInfoCmd.Flags().StringVar(&partKeyfile, "keyfile", "", "Participation key filename") partInfoCmd.MarkFlagRequired("keyfile") - partReparentCmd.Flags().StringVarP(&partKeyfile, "keyfile", "", "", "Participation key filename") - partReparentCmd.Flags().StringVarP(&partParent, "parent", "", "", "Address of parent account") + partReparentCmd.Flags().StringVar(&partKeyfile, "keyfile", "", "Participation key filename") + partReparentCmd.Flags().StringVar(&partParent, "parent", "", "Address of parent account") partReparentCmd.MarkFlagRequired("keyfile") partReparentCmd.MarkFlagRequired("parent") } diff --git a/cmd/goal/account.go b/cmd/goal/account.go index be4d120a99..5eb81938ae 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -141,7 +141,7 @@ func init() { addParticipationKeyCmd.Flags().Uint64VarP(&keyDilution, "keyDilution", "", 0, "Key dilution for two-level participation keys") // installParticipationKey flags - installParticipationKeyCmd.Flags().StringVarP(&partKeyFile, "partkey", "", "", "Participation key file to install") + installParticipationKeyCmd.Flags().StringVar(&partKeyFile, "partkey", "", "Participation key file to install") installParticipationKeyCmd.MarkFlagRequired("partkey") // import flags From 36f90ccb2a48e66e7c21db0a0fd8cf752fb86db1 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 07:33:23 -0400 Subject: [PATCH 06/12] fix typo --- cmd/goal/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 5eb81938ae..deed2e5417 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -474,7 +474,7 @@ var rewardsCmd = &cobra.Command{ var changeOnlineCmd = &cobra.Command{ Use: "changeonlinestatus", Short: "Change online status for the specified account", - Long: `Change online status for the specified account. Set online should be 1 to set online, 0 to set offline. The broadcast transaction will be valid for a limited number of rounds. goal will provide the TXID of the transaction if successful. Going online requires that the given account have a valid participation key.`, + Long: `Change online status for the specified account. Set online should be 1 to set online, 0 to set offline. The broadcast transaction will be valid for a limited number of rounds. goal will provide the TXID of the transaction if successful. Going online requires that the given account has a valid participation key.`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { if accountAddress == "" && partKeyFile == "" { From f919210abcbe8175fb118bfa9d217dfd1265ebde Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 07:37:30 -0400 Subject: [PATCH 07/12] clarify changeonlinestatus --partkeyfile requires installpartkey --- cmd/goal/account.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index deed2e5417..e09db0705f 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -474,7 +474,7 @@ var rewardsCmd = &cobra.Command{ var changeOnlineCmd = &cobra.Command{ Use: "changeonlinestatus", Short: "Change online status for the specified account", - Long: `Change online status for the specified account. Set online should be 1 to set online, 0 to set offline. The broadcast transaction will be valid for a limited number of rounds. goal will provide the TXID of the transaction if successful. Going online requires that the given account has a valid participation key.`, + Long: `Change online status for the specified account. Set online should be 1 to set online, 0 to set offline. The broadcast transaction will be valid for a limited number of rounds. goal will provide the TXID of the transaction if successful. Going online requires that the given account has a valid participation key. If the participation key is specified using --partkeyfile, you must separately install the participation key from that file using "goal account installpartkey".`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { if accountAddress == "" && partKeyFile == "" { @@ -482,6 +482,11 @@ var changeOnlineCmd = &cobra.Command{ os.Exit(1) } + if partKeyFile != "" && !online { + fmt.Printf("Going offline does not support --partkeyfile\n") + os.Exit(1) + } + // Pull the current round for use in our new transactions dataDir := ensureSingleDataDir() client := ensureFullClient(dataDir) From 957abb2db7fab4d5e5739b2cdc5a165542ca86c8 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 07:39:54 -0400 Subject: [PATCH 08/12] clarify that installpartkey does not keyreg --- cmd/goal/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index e09db0705f..fa20fed355 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -618,7 +618,7 @@ var addParticipationKeyCmd = &cobra.Command{ var installParticipationKeyCmd = &cobra.Command{ Use: "installpartkey", Short: "Install a participation key", - Long: `Install a participation key`, + Long: `Install a participation key from a partkey file. Intended for use with participation key files generated by "algokey part generate". Does not change the online status of an account or register the participation key; use "goal account changeonlinestatus" for doing so.`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { dataDir := ensureSingleDataDir() From 8eaf91d61fa175bc390390244a3be2f29616d808 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 07:40:42 -0400 Subject: [PATCH 09/12] just algod for goal account installpartkey --- cmd/goal/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index fa20fed355..ffccb24cbf 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -623,7 +623,7 @@ var installParticipationKeyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { dataDir := ensureSingleDataDir() - client := ensureFullClient(dataDir) + client := ensureAlgodClient(dataDir) _, _, err := client.InstallParticipationKeys(partKeyFile) if err != nil { reportErrorf(errorRequestFail, err) From 6c38c4de92742fe398d83cc077e3115ed3f069c8 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 09:55:15 -0400 Subject: [PATCH 10/12] goal account installpartkey: delete input partkey on success --- cmd/goal/account.go | 2 +- libgoal/participation.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index ffccb24cbf..1ee863a4e0 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -618,7 +618,7 @@ var addParticipationKeyCmd = &cobra.Command{ var installParticipationKeyCmd = &cobra.Command{ Use: "installpartkey", Short: "Install a participation key", - Long: `Install a participation key from a partkey file. Intended for use with participation key files generated by "algokey part generate". Does not change the online status of an account or register the participation key; use "goal account changeonlinestatus" for doing so.`, + Long: `Install a participation key from a partkey file. Intended for use with participation key files generated by "algokey part generate". Does not change the online status of an account or register the participation key; use "goal account changeonlinestatus" for doing so. Deletes input key file on successful install to ensure forward security.`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { dataDir := ensureSingleDataDir() diff --git a/libgoal/participation.go b/libgoal/participation.go index 4d240c7e5d..167d9f21ab 100644 --- a/libgoal/participation.go +++ b/libgoal/participation.go @@ -19,6 +19,7 @@ package libgoal import ( "fmt" "io/ioutil" + "math" "os" "path/filepath" @@ -168,7 +169,8 @@ func (c *Client) GenParticipationKeysTo(address string, firstValid, lastValid, k } // InstallParticipationKeys creates a .partkey database for a given address, -// based on an existing database from inputfile +// based on an existing database from inputfile. On successful install, it +// deletes the input file. func (c *Client) InstallParticipationKeys(inputfile string) (part account.Participation, filePath string, err error) { // Get the GenesisID for use in the participation key path var genID string @@ -190,6 +192,11 @@ func (c *Client) InstallParticipationKeys(inputfile string) (part account.Partic return } + if partkey.Parent == (basics.Address{}) { + err = fmt.Errorf("Cannot install partkey with missing (zero) parent address") + return + } + newdbpath, err := participationKeysPath(outDir, partkey.Parent, partkey.FirstValid, partkey.LastValid) if err != nil { return @@ -200,9 +207,24 @@ func (c *Client) InstallParticipationKeys(inputfile string) (part account.Partic return } - partkey.Store = newdb - err = partkey.Persist() - return partkey, newdbpath, err + newpartkey := partkey + newpartkey.Store = newdb + err = newpartkey.Persist() + if err != nil { + return + } + + // After successful install, remove the input copy of the + // partkey so that old keys cannot be recovered after they + // are used by algod. We try to delete the data inside + // sqlite first, so the key material is zeroed out from + // disk blocks, but regardless of whether that works, we + // delete the input file. The consensus protocol version + // is irrelevant for the maxuint64 round number we pass in. + partkey.DeleteOldKeys(basics.Round(math.MaxUint64), config.Consensus[protocol.ConsensusCurrentVersion]) + os.Remove(inputfile) + + return partkey, newdbpath, nil } // ListParticipationKeys returns the available participation keys, From c72ec5fee2335d563f311a2dd2e2010375d1fd6f Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 10:09:55 -0400 Subject: [PATCH 11/12] force user to acknowledge that installpartkey deletes input --- cmd/goal/account.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 1ee863a4e0..a6ff52a780 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -57,6 +57,7 @@ var ( threshold uint8 partKeyOutDir string partKeyFile string + partKeyDeleteInput bool importDefault bool mnemonic string ) @@ -143,6 +144,7 @@ func init() { // installParticipationKey flags installParticipationKeyCmd.Flags().StringVar(&partKeyFile, "partkey", "", "Participation key file to install") installParticipationKeyCmd.MarkFlagRequired("partkey") + installParticipationKeyCmd.Flags().BoolVar(&partKeyDeleteInput, "delete-input", false, "Acknowledge that installpartkey will delete the input key file") // import flags importCmd.Flags().BoolVarP(&importDefault, "default", "f", false, "Set this account as the default one") @@ -621,6 +623,20 @@ var installParticipationKeyCmd = &cobra.Command{ Long: `Install a participation key from a partkey file. Intended for use with participation key files generated by "algokey part generate". Does not change the online status of an account or register the participation key; use "goal account changeonlinestatus" for doing so. Deletes input key file on successful install to ensure forward security.`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { + if !partKeyDeleteInput { + fmt.Println( +`The installpartkey command deletes the input participation file on +successful installation. Please acknowledge this by passing the +"--delete-input" flag to the installpartkey command. You can make +a copy of the input file if needed, but please keep in mind that +participation keys must be securely deleted for each round, to ensure +forward security. Storing old participation keys compromises overall +system security. + +No --delete-input flag specified, exiting without installing key.`) + os.Exit(1) + } + dataDir := ensureSingleDataDir() client := ensureAlgodClient(dataDir) From 8cf132dcfc1367c452dcd2c8fc138d1d6c4511a7 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Jun 2019 12:12:52 -0400 Subject: [PATCH 12/12] oops --- libgoal/participation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libgoal/participation.go b/libgoal/participation.go index 167d9f21ab..fb03e82c28 100644 --- a/libgoal/participation.go +++ b/libgoal/participation.go @@ -224,7 +224,7 @@ func (c *Client) InstallParticipationKeys(inputfile string) (part account.Partic partkey.DeleteOldKeys(basics.Round(math.MaxUint64), config.Consensus[protocol.ConsensusCurrentVersion]) os.Remove(inputfile) - return partkey, newdbpath, nil + return newpartkey, newdbpath, nil } // ListParticipationKeys returns the available participation keys,