Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/algokey/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion cmd/algokey/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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[:])

Expand Down
3 changes: 2 additions & 1 deletion cmd/algokey/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions cmd/algokey/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -38,6 +39,7 @@ func init() {
rootCmd.AddCommand(exportCmd)
rootCmd.AddCommand(signCmd)
rootCmd.AddCommand(multisigCmd)
rootCmd.AddCommand(partCmd)
}

func main() {
Expand Down
3 changes: 2 additions & 1 deletion cmd/algokey/multisig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
175 changes: 175 additions & 0 deletions cmd/algokey/part.go
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than breaking the assignment and persistence, can we change PersistNewParent() to ReplaceParent(parent)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going for that originally, but for historical reasons, all current methods on account.Participation are by value rather than by pointer. So, ReplaceParent() would not have any apparent effect on the caller's partkey object (in particular, the subsequent printPartkey() would print the old parent), even though it could write the new parent to the partkey db.. Modifying the parent in-place seemed cleaner than having a mix of by-value and by-pointer methods on account.Participation, and changing all of them to by-pointer seemed unnecessarily involved.

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")
}
3 changes: 2 additions & 1 deletion cmd/algokey/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
54 changes: 51 additions & 3 deletions cmd/goal/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var (
keyDilution uint64
threshold uint8
partKeyOutDir string
partKeyFile string
importDefault bool
mnemonic string
)
Expand All @@ -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)
Expand Down Expand Up @@ -118,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)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not important, but the StringVar() command exists (likewise for other data types) for commands that don't have the short-form parameter, so you don't have to specify "".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good to know -- thanks!

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)")
Expand All @@ -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(&partKeyFile, "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)")
Expand Down Expand Up @@ -471,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.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While here, can we fix the help-text ('Going online requires that the given account have...' : have -> has)?

Also it might be confusing to people that this command does not 'install' the part file too - we should try to make sure there is no confusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, clarified both.

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())
}
Expand Down Expand Up @@ -579,6 +610,23 @@ var addParticipationKeyCmd = &cobra.Command{
},
}

var installParticipationKeyCmd = &cobra.Command{
Use: "installpartkey",
Short: "Install a participation key",
Long: `Install a participation key`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should indicate that this does not change online status.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, thanks.

Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, args []string) {
dataDir := ensureSingleDataDir()

client := ensureFullClient(dataDir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need a full client for this do we (just algod)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, sloppy copy-pasting of libgoal boilerplate on my part.

_, _, err := client.InstallParticipationKeys(partKeyFile)
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",
Expand Down
8 changes: 8 additions & 0 deletions data/account/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading