github.com/decred/dcrlnd@v0.7.6/lnrpc/walletrpc/walletkit_util.go (about)

     1  package walletrpc
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/decred/dcrlnd/lnrpc"
     9  )
    10  
    11  // AccountsToWatchOnly converts the accounts returned by the walletkit's
    12  // ListAccounts RPC into a struct that can be used to create a watch-only
    13  // wallet.
    14  func AccountsToWatchOnly(exported []*Account) ([]*lnrpc.WatchOnlyAccount,
    15  	error) {
    16  
    17  	result := make([]*lnrpc.WatchOnlyAccount, len(exported))
    18  	for idx, acct := range exported {
    19  		parsedPath, err := parseDerivationPath(acct.DerivationPath)
    20  		if err != nil {
    21  			return nil, fmt.Errorf("error parsing derivation path "+
    22  				"of account %d: %v", idx, err)
    23  		}
    24  		if len(parsedPath) < 3 {
    25  			return nil, fmt.Errorf("derivation path of account %d "+
    26  				"has invalid derivation path, need at least "+
    27  				"path of depth 3, instead has depth %d", idx,
    28  				len(parsedPath))
    29  		}
    30  
    31  		result[idx] = &lnrpc.WatchOnlyAccount{
    32  			Purpose:  parsedPath[0],
    33  			CoinType: parsedPath[1],
    34  			Account:  parsedPath[2],
    35  			Xpub:     acct.ExtendedPublicKey,
    36  		}
    37  	}
    38  
    39  	return result, nil
    40  }
    41  
    42  // parseDerivationPath parses a path in the form of m/x'/y'/z'/a/b into a slice
    43  // of [x, y, z, a, b], meaning that the apostrophe is ignored and 2^31 is _not_
    44  // added to the numbers.
    45  func parseDerivationPath(path string) ([]uint32, error) {
    46  	path = strings.TrimSpace(path)
    47  	if len(path) == 0 {
    48  		return nil, fmt.Errorf("path cannot be empty")
    49  	}
    50  	if !strings.HasPrefix(path, "m/") {
    51  		return nil, fmt.Errorf("path must start with m/")
    52  	}
    53  
    54  	// Just the root key, no path was provided. This is valid but not useful
    55  	// in most cases.
    56  	rest := strings.ReplaceAll(path, "m/", "")
    57  	if rest == "" {
    58  		return []uint32{}, nil
    59  	}
    60  
    61  	parts := strings.Split(rest, "/")
    62  	indices := make([]uint32, len(parts))
    63  	for i := 0; i < len(parts); i++ {
    64  		part := parts[i]
    65  		if strings.Contains(parts[i], "'") {
    66  			part = strings.TrimRight(parts[i], "'")
    67  		}
    68  		parsed, err := strconv.ParseInt(part, 10, 32)
    69  		if err != nil {
    70  			return nil, fmt.Errorf("could not parse part \"%s\": "+
    71  				"%v", part, err)
    72  		}
    73  		indices[i] = uint32(parsed)
    74  	}
    75  	return indices, nil
    76  }