lotus/cli/wallet.go

474 lines
9.4 KiB
Go
Raw Permalink Normal View History

2019-07-13 00:41:32 +00:00
package cli
import (
"bufio"
2019-10-08 09:17:03 +00:00
"encoding/hex"
2019-10-08 09:46:36 +00:00
"encoding/json"
2019-07-13 00:41:32 +00:00
"fmt"
2019-10-09 00:22:18 +00:00
"io/ioutil"
"os"
"strings"
2019-07-13 00:41:32 +00:00
2020-09-29 09:17:23 +00:00
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
2020-09-29 09:17:23 +00:00
"github.com/filecoin-project/go-state-types/big"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/crypto"
2020-09-29 09:17:23 +00:00
types "github.com/filecoin-project/lotus/chain/types"
2020-09-29 09:17:23 +00:00
"github.com/filecoin-project/lotus/lib/tablewriter"
2019-07-13 00:41:32 +00:00
)
var walletCmd = &cli.Command{
Name: "wallet",
Usage: "Manage wallet",
Subcommands: []*cli.Command{
walletNew,
walletList,
2019-07-18 20:26:04 +00:00
walletBalance,
2019-10-08 09:17:03 +00:00
walletExport,
walletImport,
walletGetDefault,
walletSetDefault,
walletSign,
walletVerify,
2020-06-05 23:04:23 +00:00
walletDelete,
2019-07-13 00:41:32 +00:00
},
}
var walletNew = &cli.Command{
Name: "new",
Usage: "Generate a new key of the given type",
ArgsUsage: "[bls|secp256k1 (default secp256k1)]",
2019-07-13 00:41:32 +00:00
Action: func(cctx *cli.Context) error {
2019-10-03 18:12:30 +00:00
api, closer, err := GetFullNodeAPI(cctx)
2019-07-13 00:41:32 +00:00
if err != nil {
return err
}
2019-10-03 18:12:30 +00:00
defer closer()
2019-07-18 23:16:23 +00:00
ctx := ReqContext(cctx)
2019-07-13 00:41:32 +00:00
t := cctx.Args().First()
if t == "" {
2020-01-08 19:16:09 +00:00
t = "secp256k1"
2019-07-13 00:41:32 +00:00
}
nk, err := api.WalletNew(ctx, types.KeyType(t))
2019-07-13 00:41:32 +00:00
if err != nil {
return err
}
fmt.Println(nk.String())
return nil
},
}
var walletList = &cli.Command{
Name: "list",
Usage: "List wallet address",
2020-09-29 09:17:23 +00:00
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "addr-only",
Usage: "Only print addresses",
Aliases: []string{"a"},
},
},
2019-07-13 00:41:32 +00:00
Action: func(cctx *cli.Context) error {
2019-10-03 18:12:30 +00:00
api, closer, err := GetFullNodeAPI(cctx)
2019-07-13 00:41:32 +00:00
if err != nil {
return err
}
2019-10-03 18:12:30 +00:00
defer closer()
2019-07-18 23:16:23 +00:00
ctx := ReqContext(cctx)
2019-07-13 00:41:32 +00:00
addrs, err := api.WalletList(ctx)
if err != nil {
return err
}
2020-09-30 05:10:03 +00:00
// Assume an error means no default key is set
def, _ := api.WalletDefaultAddress(ctx)
2020-09-29 09:17:23 +00:00
tw := tablewriter.New(
tablewriter.Col("Address"),
tablewriter.Col("Balance"),
tablewriter.Col("Nonce"),
tablewriter.Col("Default"),
tablewriter.NewLineCol("Error"))
2019-07-13 00:41:32 +00:00
for _, addr := range addrs {
2020-09-29 09:17:23 +00:00
if cctx.Bool("addr-only") {
fmt.Println(addr.String())
} else {
a, err := api.StateGetActor(ctx, addr, types.EmptyTSK)
if err != nil {
if !strings.Contains(err.Error(), "actor not found") {
tw.Write(map[string]interface{}{
"Address": addr,
"Error": err,
})
continue
}
a = &types.Actor{
Balance: big.Zero(),
}
}
row := map[string]interface{}{
"Address": addr,
"Balance": types.FIL(a.Balance),
"Nonce": a.Nonce,
}
if addr == def {
row["Default"] = "X"
}
tw.Write(row)
}
}
if !cctx.Bool("addr-only") {
return tw.Flush(os.Stdout)
2019-07-13 00:41:32 +00:00
}
2020-09-29 09:17:23 +00:00
2019-07-13 00:41:32 +00:00
return nil
},
}
2019-07-18 20:26:04 +00:00
var walletBalance = &cli.Command{
Name: "balance",
Usage: "Get account balance",
ArgsUsage: "[address]",
2019-07-18 20:26:04 +00:00
Action: func(cctx *cli.Context) error {
2019-10-03 18:12:30 +00:00
api, closer, err := GetFullNodeAPI(cctx)
2019-07-18 20:26:04 +00:00
if err != nil {
return err
}
2019-10-03 18:12:30 +00:00
defer closer()
2019-07-18 23:16:23 +00:00
ctx := ReqContext(cctx)
2019-07-18 20:26:04 +00:00
var addr address.Address
if cctx.Args().First() != "" {
addr, err = address.NewFromString(cctx.Args().First())
} else {
addr, err = api.WalletDefaultAddress(ctx)
}
2019-07-18 20:26:04 +00:00
if err != nil {
return err
}
balance, err := api.WalletBalance(ctx, addr)
if err != nil {
return err
}
if balance.Equals(types.NewInt(0)) {
fmt.Printf("%s (warning: may display 0 if chain sync in progress)\n", types.FIL(balance))
} else {
fmt.Printf("%s\n", types.FIL(balance))
}
2019-07-18 20:26:04 +00:00
return nil
},
}
2019-10-08 09:17:03 +00:00
var walletGetDefault = &cli.Command{
Name: "default",
Usage: "Get default wallet address",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
addr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
fmt.Printf("%s\n", addr.String())
return nil
},
}
var walletSetDefault = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "set-default",
Usage: "Set default wallet address",
ArgsUsage: "[address]",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() {
return fmt.Errorf("must pass address to set as default")
}
addr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
return api.WalletSetDefault(ctx, addr)
},
}
2019-10-08 09:17:03 +00:00
var walletExport = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "export",
Usage: "export keys",
ArgsUsage: "[address]",
2019-10-08 09:17:03 +00:00
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() {
return fmt.Errorf("must specify key to export")
}
addr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
2019-10-08 09:46:36 +00:00
ki, err := api.WalletExport(ctx, addr)
if err != nil {
return err
}
b, err := json.Marshal(ki)
2019-10-08 09:17:03 +00:00
if err != nil {
return err
}
fmt.Println(hex.EncodeToString(b))
return nil
},
}
var walletImport = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "import",
Usage: "import keys",
ArgsUsage: "[<path> (optional, will read from stdin if omitted)]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "format",
Usage: "specify input format for key",
Value: "hex-lotus",
},
&cli.BoolFlag{
Name: "as-default",
Usage: "import the given key as your new default key",
},
},
2019-10-08 09:17:03 +00:00
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
var inpdata []byte
2019-10-09 00:22:18 +00:00
if !cctx.Args().Present() || cctx.Args().First() == "-" {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter private key: ")
indata, err := reader.ReadBytes('\n')
2019-10-09 00:22:18 +00:00
if err != nil {
return err
}
inpdata = indata
2019-10-09 00:22:18 +00:00
} else {
fdata, err := ioutil.ReadFile(cctx.Args().First())
if err != nil {
return err
}
inpdata = fdata
2019-10-08 09:17:03 +00:00
}
2019-10-08 09:46:36 +00:00
var ki types.KeyInfo
switch cctx.String("format") {
case "hex-lotus":
data, err := hex.DecodeString(strings.TrimSpace(string(inpdata)))
if err != nil {
return err
}
if err := json.Unmarshal(data, &ki); err != nil {
return err
}
case "json-lotus":
if err := json.Unmarshal(inpdata, &ki); err != nil {
return err
}
case "gfc-json":
var f struct {
KeyInfo []struct {
PrivateKey []byte
SigType int
}
}
if err := json.Unmarshal(inpdata, &f); err != nil {
return xerrors.Errorf("failed to parse go-filecoin key: %s", err)
}
gk := f.KeyInfo[0]
ki.PrivateKey = gk.PrivateKey
switch gk.SigType {
case 1:
ki.Type = types.KTSecp256k1
case 2:
ki.Type = types.KTBLS
default:
return fmt.Errorf("unrecognized key type: %d", gk.SigType)
}
default:
return fmt.Errorf("unrecognized format: %s", cctx.String("format"))
2019-10-08 09:46:36 +00:00
}
addr, err := api.WalletImport(ctx, &ki)
2019-10-08 09:17:03 +00:00
if err != nil {
return err
}
if cctx.Bool("as-default") {
if err := api.WalletSetDefault(ctx, addr); err != nil {
return fmt.Errorf("failed to set default key: %w", err)
}
}
2019-10-29 12:54:26 +00:00
fmt.Printf("imported key %s successfully!\n", addr)
2019-10-08 09:17:03 +00:00
return nil
},
}
var walletSign = &cli.Command{
Name: "sign",
Usage: "sign a message",
ArgsUsage: "<signing address> <hexMessage>",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() || cctx.NArg() != 2 {
return fmt.Errorf("must specify signing address and message to sign")
}
addr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
msg, err := hex.DecodeString(cctx.Args().Get(1))
if err != nil {
return err
}
sig, err := api.WalletSign(ctx, addr, msg)
if err != nil {
return err
}
2020-03-08 00:46:12 +00:00
sigBytes := append([]byte{byte(sig.Type)}, sig.Data...)
fmt.Println(hex.EncodeToString(sigBytes))
return nil
},
}
var walletVerify = &cli.Command{
Name: "verify",
Usage: "verify the signature of a message",
ArgsUsage: "<signing address> <hexMessage> <signature>",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() || cctx.NArg() != 3 {
return fmt.Errorf("must specify signing address, message, and signature to verify")
}
addr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
msg, err := hex.DecodeString(cctx.Args().Get(1))
if err != nil {
return err
}
sigBytes, err := hex.DecodeString(cctx.Args().Get(2))
if err != nil {
return err
}
2020-03-08 00:46:12 +00:00
var sig crypto.Signature
if err := sig.UnmarshalBinary(sigBytes); err != nil {
return err
}
2020-09-17 10:22:56 +00:00
ok, err := api.WalletVerify(ctx, addr, msg, &sig)
if err != nil {
return err
}
if ok {
fmt.Println("valid")
return nil
}
fmt.Println("invalid")
return NewCliError("CLI Verify called with invalid signature")
},
}
2020-06-05 23:04:23 +00:00
var walletDelete = &cli.Command{
Name: "delete",
Usage: "Delete an account from the wallet",
ArgsUsage: "<address> ",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() || cctx.NArg() != 1 {
return fmt.Errorf("must specify address to delete")
}
addr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
return api.WalletDelete(ctx, addr)
},
}