package main import ( "encoding/json" "fmt" "strconv" "strings" "github.com/filecoin-project/go-address" "github.com/urfave/cli/v2" ledgerfil "github.com/whyrusleeping/ledger-filecoin-go" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" ledgerwallet "github.com/filecoin-project/lotus/chain/wallet/ledger" lcli "github.com/filecoin-project/lotus/cli" ) var ledgerCmd = &cli.Command{ Name: "ledger", Usage: "Ledger interactions", Flags: []cli.Flag{}, Subcommands: []*cli.Command{ ledgerListAddressesCmd, ledgerKeyInfoCmd, ledgerSignTestCmd, }, } const hdHard = 0x80000000 var ledgerListAddressesCmd = &cli.Command{ Name: "list", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "print-balances", Usage: "print balances", Aliases: []string{"b"}, }, }, Action: func(cctx *cli.Context) error { var api api.FullNode if cctx.Bool("print-balances") { a, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err } api = a defer closer() } ctx := lcli.ReqContext(cctx) fl, err := ledgerfil.FindLedgerFilecoinApp() if err != nil { return err } end := 20 for i := 0; i < end; i++ { if err := ctx.Err(); err != nil { return err } p := []uint32{hdHard | 44, hdHard | 461, hdHard, 0, uint32(i)} pubk, err := fl.GetPublicKeySECP256K1(p) if err != nil { return err } addr, err := address.NewSecp256k1Address(pubk) if err != nil { return err } if cctx.Bool("print-balances") && api != nil { // api check makes linter happier b, err := api.WalletBalance(ctx, addr) if err != nil { return xerrors.Errorf("getting balance: %w", err) } if !b.IsZero() { end = i + 21 // BIP32 spec, stop after 20 empty addresses } fmt.Printf("%s %s %s\n", addr, printHDPath(p), types.FIL(b)) } else { fmt.Printf("%s %s\n", addr, printHDPath(p)) } } return nil }, } func parseHDPath(s string) ([]uint32, error) { parts := strings.Split(s, "/") if parts[0] != "m" { return nil, fmt.Errorf("expected HD path to start with 'm'") } var out []uint32 for _, p := range parts[1:] { var hard bool if strings.HasSuffix(p, "'") { p = p[:len(p)-1] hard = true } v, err := strconv.ParseUint(p, 10, 32) if err != nil { return nil, err } if v >= hdHard { return nil, fmt.Errorf("path element %s too large", p) } if hard { v += hdHard } out = append(out, uint32(v)) } return out, nil } func printHDPath(pth []uint32) string { s := "m" for _, p := range pth { s += "/" hard := p&hdHard != 0 p &^= hdHard // remove hdHard bit s += fmt.Sprint(p) if hard { s += "'" } } return s } var ledgerKeyInfoCmd = &cli.Command{ Name: "key-info", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "verbose", Aliases: []string{"v"}, }, }, Action: func(cctx *cli.Context) error { if !cctx.Args().Present() { return cli.ShowCommandHelp(cctx, cctx.Command.Name) } fl, err := ledgerfil.FindLedgerFilecoinApp() if err != nil { return err } p, err := parseHDPath(cctx.Args().First()) if err != nil { return err } pubk, _, addr, err := fl.GetAddressPubKeySECP256K1(p) if err != nil { return err } if cctx.Bool("verbose") { fmt.Println(addr) fmt.Println(pubk) } a, err := address.NewFromString(addr) if err != nil { return err } var pd ledgerwallet.LedgerKeyInfo pd.Address = a pd.Path = p b, err := json.Marshal(pd) if err != nil { return err } var ki types.KeyInfo ki.Type = types.KTSecp256k1Ledger ki.PrivateKey = b out, err := json.Marshal(ki) if err != nil { return err } fmt.Println(string(out)) return nil }, } var ledgerSignTestCmd = &cli.Command{ Name: "sign", Action: func(cctx *cli.Context) error { if !cctx.Args().Present() { return cli.ShowCommandHelp(cctx, cctx.Command.Name) } fl, err := ledgerfil.FindLedgerFilecoinApp() if err != nil { return err } p, err := parseHDPath(cctx.Args().First()) if err != nil { return err } addr, err := address.NewFromString("f1xc3hws5n6y5m3m44gzb3gyjzhups6wzmhe663ji") if err != nil { return err } m := &types.Message{ To: addr, From: addr, } b, err := m.ToStorageBlock() if err != nil { return err } sig, err := fl.SignSECP256K1(p, b.RawData()) if err != nil { return err } fmt.Println(sig.SignatureBytes()) return nil }, }