package cli import ( "bufio" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "os" "strings" "github.com/filecoin-project/go-address" types "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/specs-actors/actors/crypto" "golang.org/x/xerrors" "github.com/urfave/cli/v2" ) var walletCmd = &cli.Command{ Name: "wallet", Usage: "Manage wallet", Subcommands: []*cli.Command{ walletNew, walletList, walletBalance, walletExport, walletImport, walletGetDefault, walletSetDefault, walletSign, walletVerify, walletDelete, }, } var walletNew = &cli.Command{ Name: "new", Usage: "Generate a new key of the given type", ArgsUsage: "[bls|secp256k1 (default secp256k1)]", Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) t := cctx.Args().First() if t == "" { t = "secp256k1" } nk, err := api.WalletNew(ctx, wallet.ActSigType(t)) if err != nil { return err } fmt.Println(nk.String()) return nil }, } var walletList = &cli.Command{ Name: "list", Usage: "List wallet address", Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) addrs, err := api.WalletList(ctx) if err != nil { return err } for _, addr := range addrs { fmt.Println(addr.String()) } return nil }, } var walletBalance = &cli.Command{ Name: "balance", Usage: "Get account balance", ArgsUsage: "[address]", Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) var addr address.Address if cctx.Args().First() != "" { addr, err = address.NewFromString(cctx.Args().First()) } else { addr, err = api.WalletDefaultAddress(ctx) } 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)) } return nil }, } 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{ 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) }, } var walletExport = &cli.Command{ Name: "export", Usage: "export keys", 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 specify key to export") } addr, err := address.NewFromString(cctx.Args().First()) if err != nil { return err } ki, err := api.WalletExport(ctx, addr) if err != nil { return err } b, err := json.Marshal(ki) if err != nil { return err } fmt.Println(hex.EncodeToString(b)) return nil }, } var walletImport = &cli.Command{ 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", }, }, Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) var inpdata []byte if !cctx.Args().Present() || cctx.Args().First() == "-" { reader := bufio.NewReader(os.Stdin) fmt.Print("Enter private key: ") indata, err := reader.ReadBytes('\n') if err != nil { return err } inpdata = indata } else { fdata, err := ioutil.ReadFile(cctx.Args().First()) if err != nil { return err } inpdata = fdata } 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 = wallet.KTSecp256k1 case 2: ki.Type = wallet.KTBLS default: return fmt.Errorf("unrecognized key type: %d", gk.SigType) } default: return fmt.Errorf("unrecognized format: %s", cctx.String("format")) } addr, err := api.WalletImport(ctx, &ki) if err != nil { return err } fmt.Printf("imported key %s successfully!\n", addr) 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 } 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 } var sig crypto.Signature if err := sig.UnmarshalBinary(sigBytes); err != nil { return err } if api.WalletVerify(ctx, addr, msg, &sig) { fmt.Println("valid") return nil } else { fmt.Println("invalid") return NewCliError("CLI Verify called with invalid signature") } }, } 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) }, }