Merge pull request #2177 from filecoin-project/feat/lotus-shed-keyinfo
lotus-shed: organize peer key generate and other key commands under keyinfo
This commit is contained in:
commit
726c09618a
@ -1,8 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -11,77 +13,356 @@ import (
|
|||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
"github.com/libp2p/go-libp2p-core/crypto"
|
||||||
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/chain/wallet"
|
"github.com/filecoin-project/lotus/chain/wallet"
|
||||||
|
"github.com/filecoin-project/lotus/node/modules/lp2p"
|
||||||
|
"github.com/filecoin-project/lotus/node/repo"
|
||||||
|
|
||||||
|
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
||||||
|
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type walletInfo struct {
|
var validTypes = []string{wallet.KTBLS, wallet.KTSecp256k1, lp2p.KTLibp2pHost}
|
||||||
|
|
||||||
|
type keyInfoOutput struct {
|
||||||
Type string
|
Type string
|
||||||
Address string
|
Address string
|
||||||
PublicKey string
|
PublicKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wi walletInfo) String() string {
|
|
||||||
bs, _ := json.Marshal(wi)
|
|
||||||
return string(bs)
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyinfoCmd = &cli.Command{
|
var keyinfoCmd = &cli.Command{
|
||||||
Name: "keyinfo",
|
Name: "keyinfo",
|
||||||
Description: "decode a keyinfo",
|
Usage: "work with lotus keyinfo files (wallets and libp2p host keys)",
|
||||||
|
Description: `The subcommands of keyinfo provide helpful tools for working with keyinfo files without
|
||||||
|
having to run the lotus daemon.`,
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
keyinfoNewCmd,
|
||||||
|
keyinfoInfoCmd,
|
||||||
|
keyinfoImportCmd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyinfoImportCmd = &cli.Command{
|
||||||
|
Name: "import",
|
||||||
|
Usage: "import a keyinfo file into a lotus repository",
|
||||||
|
Description: `The import command provides a way to import keyfiles into a lotus repository
|
||||||
|
without running the daemon.
|
||||||
|
|
||||||
|
Note: The LOTUS_PATH directory must be created. This command will not create this directory for you.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
env LOTUS_PATH=/var/lib/lotus lotus-shed keyinfo import libp2p-host.keyinfo`,
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
flagRepo := cctx.String("repo")
|
||||||
|
|
||||||
|
var input io.Reader
|
||||||
|
if cctx.Args().Len() == 0 {
|
||||||
|
input = os.Stdin
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
input, err = os.Open(cctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := ioutil.ReadAll(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := hex.DecodeString(strings.TrimSpace(string(encoded)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyInfo types.KeyInfo
|
||||||
|
if err := json.Unmarshal(decoded, &keyInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fsrepo, err := repo.NewFS(flagRepo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lkrepo, err := fsrepo.Lock(repo.FullNode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer lkrepo.Close()
|
||||||
|
|
||||||
|
keystore, err := lkrepo.KeyStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch keyInfo.Type {
|
||||||
|
case lp2p.KTLibp2pHost:
|
||||||
|
if err := keystore.Put(lp2p.KLibp2pHost, keyInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sk, err := crypto.UnmarshalPrivateKey(keyInfo.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerid, err := peer.IDFromPrivateKey(sk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", peerid.String())
|
||||||
|
|
||||||
|
break
|
||||||
|
case wallet.KTSecp256k1:
|
||||||
|
case wallet.KTBLS:
|
||||||
|
w, err := wallet.NewWallet(keystore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := w.Import(&keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyinfoInfoCmd = &cli.Command{
|
||||||
|
Name: "info",
|
||||||
|
Usage: "print information about a keyinfo file",
|
||||||
|
Description: `The info command prints additional information about a key which can't easily
|
||||||
|
be retrieved by inspecting the file itself.
|
||||||
|
|
||||||
|
The 'format' flag takes a golang text/template template as its value.
|
||||||
|
|
||||||
|
The following fields can be retrived through this command
|
||||||
|
Type
|
||||||
|
Address
|
||||||
|
PublicKey
|
||||||
|
|
||||||
|
The PublicKey value will be printed base64 encoded using golangs StdEncoding
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
Retreive the address of a lotus wallet
|
||||||
|
lotus-shed keyinfo info --format '{{ .Address }}' wallet.keyinfo
|
||||||
|
`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
Value: "{{.Address}}",
|
Value: "{{ .Type }} {{ .Address }}",
|
||||||
Usage: "Format to output",
|
Usage: "specify which output columns to print",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
format := cctx.String("format")
|
format := cctx.String("format")
|
||||||
|
|
||||||
var input io.Reader
|
var input io.Reader
|
||||||
|
|
||||||
if cctx.Args().Len() == 0 {
|
if cctx.Args().Len() == 0 {
|
||||||
input = os.Stdin
|
input = os.Stdin
|
||||||
} else {
|
} else {
|
||||||
input = strings.NewReader(cctx.Args().First())
|
var err error
|
||||||
|
input, err = os.Open(cctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := ioutil.ReadAll(input)
|
encoded, err := ioutil.ReadAll(input)
|
||||||
|
|
||||||
data, err := hex.DecodeString(strings.TrimSpace(string(bytes)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ki types.KeyInfo
|
decoded, err := hex.DecodeString(strings.TrimSpace(string(encoded)))
|
||||||
if err := json.Unmarshal(data, &ki); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := wallet.NewKey(ki)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bs, err := json.Marshal(key)
|
var keyInfo types.KeyInfo
|
||||||
|
if err := json.Unmarshal(decoded, &keyInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kio keyInfoOutput
|
||||||
|
|
||||||
|
switch keyInfo.Type {
|
||||||
|
case lp2p.KTLibp2pHost:
|
||||||
|
kio.Type = keyInfo.Type
|
||||||
|
|
||||||
|
sk, err := crypto.UnmarshalPrivateKey(keyInfo.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var wi walletInfo
|
pk := sk.GetPublic()
|
||||||
if err := json.Unmarshal(bs, &wi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("").Parse(format)
|
peerid, err := peer.IDFromPrivateKey(sk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmpl.Execute(os.Stdout, wi)
|
pkBytes, err := pk.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
kio.Address = peerid.String()
|
||||||
|
kio.PublicKey = base64.StdEncoding.EncodeToString(pkBytes)
|
||||||
|
|
||||||
|
break
|
||||||
|
case wallet.KTSecp256k1:
|
||||||
|
case wallet.KTBLS:
|
||||||
|
kio.Type = keyInfo.Type
|
||||||
|
|
||||||
|
key, err := wallet.NewKey(keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
kio.Address = key.Address.String()
|
||||||
|
kio.PublicKey = base64.StdEncoding.EncodeToString(key.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("output").Parse(format)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpl.Execute(os.Stdout, kio)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var keyinfoNewCmd = &cli.Command{
|
||||||
|
Name: "new",
|
||||||
|
Usage: "create a new keyinfo file of the provided type",
|
||||||
|
ArgsUsage: "[bls|secp256k1|libp2p-host]",
|
||||||
|
Description: `Keyinfo files are base16 encoded json structures containing a type
|
||||||
|
string value, and a base64 encoded private key.
|
||||||
|
|
||||||
|
Both the bls and secp256k1 keyfiles can be imported into a running lotus daemon using
|
||||||
|
the 'lotus wallet import' command. Or imported to a non-running / unitialized repo using
|
||||||
|
the 'lotus-shed keyinfo import' command. Libp2p host keys can only be imported using lotus-shed
|
||||||
|
as lotus itself does not provide this functionality at the moment.`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Value: "<type>-<addr>.keyinfo",
|
||||||
|
Usage: "output file formt",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "silent",
|
||||||
|
Value: false,
|
||||||
|
Usage: "do not print the address to stdout",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
if !cctx.Args().Present() {
|
||||||
|
return fmt.Errorf("please specify a type to generate")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyType := cctx.Args().First()
|
||||||
|
flagOutput := cctx.String("output")
|
||||||
|
|
||||||
|
if i := SliceIndex(len(validTypes), func(i int) bool {
|
||||||
|
if keyType == validTypes[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}); i == -1 {
|
||||||
|
return fmt.Errorf("invalid key type argument provided '%s'", keyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
keystore := wallet.NewMemKeyStore()
|
||||||
|
|
||||||
|
var keyAddr string
|
||||||
|
var keyInfo types.KeyInfo
|
||||||
|
|
||||||
|
switch keyType {
|
||||||
|
case lp2p.KTLibp2pHost:
|
||||||
|
sk, err := lp2p.PrivKey(keystore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ki, err := keystore.Get(lp2p.KLibp2pHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerid, err := peer.IDFromPrivateKey(sk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyAddr = peerid.String()
|
||||||
|
keyInfo = ki
|
||||||
|
|
||||||
|
break
|
||||||
|
case wallet.KTSecp256k1:
|
||||||
|
case wallet.KTBLS:
|
||||||
|
key, err := wallet.GenerateKey(wallet.ActSigType(keyType))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyAddr = key.Address.String()
|
||||||
|
keyInfo = key.KeyInfo
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := flagOutput
|
||||||
|
filename = strings.ReplaceAll(filename, "<addr>", keyAddr)
|
||||||
|
filename = strings.ReplaceAll(filename, "<type>", keyType)
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
log.Warnf("failed to close output file: %w", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := hex.EncodeToString(bytes)
|
||||||
|
if _, err := file.Write([]byte(encoded)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cctx.Bool("silent") {
|
||||||
|
fmt.Println(keyAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func SliceIndex(length int, fn func(i int) bool) int {
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
if fn(i) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
@ -19,7 +19,6 @@ func main() {
|
|||||||
base16Cmd,
|
base16Cmd,
|
||||||
bitFieldCmd,
|
bitFieldCmd,
|
||||||
keyinfoCmd,
|
keyinfoCmd,
|
||||||
peerkeyCmd,
|
|
||||||
noncefix,
|
noncefix,
|
||||||
bigIntParseCmd,
|
bigIntParseCmd,
|
||||||
staterootStatsCmd,
|
staterootStatsCmd,
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
|
||||||
"github.com/filecoin-project/lotus/node/modules/lp2p"
|
|
||||||
"github.com/libp2p/go-libp2p-core/peer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keystore struct {
|
|
||||||
set bool
|
|
||||||
info types.KeyInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ks *keystore) Put(name string, info types.KeyInfo) error {
|
|
||||||
ks.info = info
|
|
||||||
ks.set = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ks *keystore) Get(name string) (types.KeyInfo, error) {
|
|
||||||
if !ks.set {
|
|
||||||
return types.KeyInfo{}, types.ErrKeyInfoNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ks.info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ks *keystore) Delete(name string) error {
|
|
||||||
panic("Implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ks *keystore) List() ([]string, error) {
|
|
||||||
panic("Implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
var peerkeyCmd = &cli.Command{
|
|
||||||
Name: "peerkey",
|
|
||||||
Description: "create libp2p host key",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "output",
|
|
||||||
Value: "<peerid>.peerkey",
|
|
||||||
Usage: "Output file format",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "silent",
|
|
||||||
Value: false,
|
|
||||||
Usage: "Do not print peerid at end",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(cctx *cli.Context) error {
|
|
||||||
output := cctx.String("output")
|
|
||||||
ks := keystore{}
|
|
||||||
|
|
||||||
sk, err := lp2p.PrivKey(&ks)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bs, err := json.Marshal(ks.info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
peerid, err := peer.IDFromPrivateKey(sk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
output = strings.ReplaceAll(output, "<peerid>", peerid.String())
|
|
||||||
|
|
||||||
f, err := os.Create(output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Warnf("failed to close output file: %w", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := f.Write(bs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cctx.Bool("silent") {
|
|
||||||
fmt.Println(peerid.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -19,7 +19,10 @@ import (
|
|||||||
|
|
||||||
var log = logging.Logger("p2pnode")
|
var log = logging.Logger("p2pnode")
|
||||||
|
|
||||||
const kstorePrivkey = "libp2p-host"
|
const (
|
||||||
|
KLibp2pHost = "libp2p-host"
|
||||||
|
KTLibp2pHost = KLibp2pHost
|
||||||
|
)
|
||||||
|
|
||||||
type Libp2pOpts struct {
|
type Libp2pOpts struct {
|
||||||
fx.Out
|
fx.Out
|
||||||
@ -28,7 +31,7 @@ type Libp2pOpts struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PrivKey(ks types.KeyStore) (crypto.PrivKey, error) {
|
func PrivKey(ks types.KeyStore) (crypto.PrivKey, error) {
|
||||||
k, err := ks.Get(kstorePrivkey)
|
k, err := ks.Get(KLibp2pHost)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return crypto.UnmarshalPrivateKey(k.PrivateKey)
|
return crypto.UnmarshalPrivateKey(k.PrivateKey)
|
||||||
}
|
}
|
||||||
@ -44,8 +47,8 @@ func PrivKey(ks types.KeyStore) (crypto.PrivKey, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ks.Put(kstorePrivkey, types.KeyInfo{
|
if err := ks.Put(KLibp2pHost, types.KeyInfo{
|
||||||
Type: kstorePrivkey,
|
Type: KTLibp2pHost,
|
||||||
PrivateKey: kbytes,
|
PrivateKey: kbytes,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
Reference in New Issue
Block a user