lndsigner

package module
v0.0.1-alpha Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 4, 2023 License: MIT Imports: 30 Imported by: 0

README

lndsigner

lndsigner is a remote signer for lnd. Currently, it can do the following:

  • store seeds for multiple nodes in Hashicorp Vault
  • securely generate new node seeds in vault
  • import seed/pass phrases
  • run unit tests
  • perform derivation and signing operations in a Vault plugin
  • export account list as JSON from vault
  • sign messages for network announcements
  • derive shared keys for peer connections
  • sign PSBTs for on-chain transactions, channel openings/closes, HTLC updates, etc.
  • run itests

There is a list of issues that tracks TODO items needed for a mainnet release.

Usage

Ensure you have bitcoind, lnd, and vault installed. Build signer using Go 1.18+ from this directory:

$ go install ./cmd/...

Create a directory ~/vault_plugins and then move the vault-plugin-lndsigner binary to it.

Start Vault from your home directory:

~$ vault server -dev -dev-root-token-id=root -dev-plugin-dir=./vault_plugins -log-level=trace

Enable the signer plugin:

$ VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root vault secrets enable --path=lndsigner vault-plugin-lndsigner

Create a new node:

$ VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root vault write lndsigner/lnd-nodes network=regtest

Note that this should return a pubkey for the new node:

Key     Value
---     -----
node    03dc60dce282bb96abb4328c3e19640aa4f87defc400458322b80f0b73c2b14263

You can also list the nodes as follows:

$ VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root vault read lndsigner/lnd-nodes
Key                                                                   Value
---                                                                   -----
03dc60dce282bb96abb4328c3e19640aa4f87defc400458322b80f0b73c2b14263    regtest

The value is the network specified above. Note that the Vault plugin is multi-tenant (supports multiple nodes), so you can add more nodes by writing as above.

Create a directory ~/.lndsigner (Linux) with a signer.conf similar to:

rpclisten=tcp://127.0.0.1:10021
network=regtest
nodepubkey=*pubkey*

Use the pubkey from the node you created above. Note that on other platforms, the lndsigner directory you need to create may be different, such as:

  • C:\Users\<username>\AppData\Local\Lndsigner on Windows
  • ~/Library/Application Support/Lndsigner on MacOS

The rest of this README assumes you're working on Linux. Additional documentation for other platforms welcome.

You'll need to provide a tls.key and tls.cert for the daemon. This allows it to accept TLS connections and lets lnd to authenticate that it's connecting to the correct signer, as configured below. For testing purposes, you can grab some that are auto-generated by a regtest instance of lnd. For deploy, you'll want your infrastructure to create these.

Run the signer binary as follows:

~/.lndsigner$ VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root lndsignerd

Ensure you have a bitcoind instance running locally on regtest. Then, create a directory ~/.lnd-watchonly with a lnd.conf similar to:

[bitcoin]
bitcoin.active=true
bitcoin.regtest=true
bitcoin.node=bitcoind

[remotesigner]
remotesigner.enable=true
remotesigner.rpchost=127.0.0.1:10021
remotesigner.tlscertpath=/home/*user*/.lndsigner/tls.cert
remotesigner.macaroonpath=any.macaroon

Note that lnd checks that the macaroon file deserializes correctly but lndsigner ignores the macaroon.

Next, get the account list for the node (this works on Linux with jq installed):

~/.lnd-watchonly$ VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root \
   vault read lndsigner/lnd-nodes/accounts node=*pubkey* | \
   tail -n 1 | sed s/acctList\\s*// | jq > accounts.json

You'll get an accounts.json file that starts like:

{
  "accounts": [
    {
      "name": "default",
      "address_type": "HYBRID_NESTED_WITNESS_PUBKEY_HASH",
      "extended_public_key": "upub...

Now, run lnd in watch-only mode:

~/.lnd-watchonly$ lnd --lnddir=.

Create the watch-only wallet using the accounts exported by the signer:

~$ lncli createwatchonly .lndsigner/accounts.json

Now you can use your node as usual. Note that MuSig2 isn't supported yet. If you created multiple nodes in the vault, you can create a separate directory for each signer instance (.lndsigner) and each watch-only node (.lnd) and start each as above.

You can also import a seedphrase, optionally protected by a passphrase, into the vault if you have a backup from an existing LND installation:

~$ vault write lndsigner/lnd-nodes/import \
   seedphrase="abstract inch live custom just tray hockey enroll upon friend mass author filter desert parrot network finger uniform alley artefact path palace chicken diet" \
   passphrase=weks1234 \
   network=regtest \
   node=03c7926302ac72f51ef009dc169561734414b3c6bfd9fb0dc42cac93101c3c25bf

Note that the node parameter is optional and used to check that the correct node pubkey is derived from the seed and network passed to the vault. You should get output like this if the command succeeds:

Key     Value
---     -----
node    03c7926302ac72f51ef009dc169561734414b3c6bfd9fb0dc42cac93101c3c25bf

Now you can use the imported key as before.

Testing

You can run unit tests and integration tests, together or separately, in Docker or on your host system. To run tests inside Docker, from the project directory, run one of:

  • $ make docker-test for unit tests
  • $ make docker-itest for integration tests
  • $ make docker-test-all for integration and unit tests

To run tests directly on your development machine, you can use:

  • $ make test for unit tests
  • $ make itest for integration tests
  • $ make test-all for integration and unit tests

Before running integration tests on your development machine, ensure you have all the required binaries (bitcoind, bitcoin-cli, lnd, lncli, vault).

To get a shell on a container that can run tests, you can use make docker-shell. Then, you can make test, make itest, or make test-all inside the container, just like you would directly on the host system.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// DefaultSignerDir is the default directory where lndsignerd tries to
	// find its configuration file and store its data. This is a directory
	// in the user's application data, for example:
	//   C:\Users\<username>\AppData\Local\Lndsigner on Windows
	//   ~/.lndsigner on Linux
	//   ~/Library/Application Support/Lndsigner on MacOS
	DefaultSignerDir = btcutil.AppDataDir("lndsigner", false)

	// DefaultConfigFile is the default full path of lndsignerd's
	// configuration file.
	DefaultConfigFile = filepath.Join(DefaultSignerDir, defaultConfigFilename)
)

Functions

func CleanAndExpandPath

func CleanAndExpandPath(path string) string

CleanAndExpandPath expands environment variables and leading ~ in the passed path, cleans the result, and returns it. This function is taken from https://github.com/btcsuite/btcd

func GetAccounts

func GetAccounts(acctList string) (map[[3]uint32]string, error)

GetAccounts is currently used in integration testing, but will soon also be used in policy enforcement. For current status, see the branch at https://github.com/aakselrod/lndsigner/tree/offchain-ratelimiting

func ListenOnAddress

func ListenOnAddress(addr net.Addr) (net.Listener, error)

ListenOnAddress creates a listener that listens on the given address.

func Main

func Main(cfg *Config, lisCfg ListenerCfg) error

Main is the true entry point for lnd. It accepts a fully populated and validated main configuration struct and an optional listener config struct. This function starts all main system components then blocks until a signal is received on the shutdownChan at which point everything is shut down again.

func NormalizeAddresses

func NormalizeAddresses(addrs []string, defaultPort string) ([]net.Addr,
	error)

NormalizeAddresses returns a new slice with all the passed addresses normalized with the given default port and all duplicates removed.

func ParseAddressString

func ParseAddressString(strAddress string, defaultPort string) (net.Addr,
	error)

ParseAddressString converts an address in string format to a net.Addr that is compatible with lndsignerd.

Types

type Config

type Config struct {
	SignerDir  string `long:"signerdir" description:"The base directory that contains signer's data, logs, configuration file, etc."`
	ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`

	TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lndsignerd's RPC services"`
	TLSKeyPath  string `long:"tlskeypath" description:"Path to write the TLS private key for lndsignerd's RPC services"`

	// We'll parse these 'raw' string arguments into real net.Addrs in the
	// loadConfig function. We need to expose the 'raw' strings so the
	// command line library can access them.
	// Only the parsed net.Addrs should be used!
	RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
	RPCListeners    []net.Addr

	Network string `` /* 134-byte string literal not displayed */

	// ActiveNetParams contains parameters of the target chain.
	ActiveNetParams chaincfg.Params

	// Node contains the node ID as a 66-character hex string.
	NodePubKey string `long:"nodepubkey" description:"Node pubkey hex"`
}

Config defines the configuration options for lndsignerd.

See LoadConfig for further details regarding the configuration loading+parsing process.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns all default values for the Config struct.

func LoadConfig

func LoadConfig() (*Config, error)

LoadConfig initializes and parses the config using a config file and command line options.

The configuration proceeds as follows:

  1. Start with a default config with sane settings
  2. Pre-parse the command line to check for an alternative config file
  3. Load configuration file overwriting defaults with any specified options
  4. Parse CLI options and overwrite/add any specified options

func ValidateConfig

func ValidateConfig(cfg Config, fileParser, flagParser *flags.Parser) (
	*Config, error)

ValidateConfig check the given configuration to be sane. This makes sure no illegal values or combination of values are set. All file system paths are normalized. The cleaned up config is returned on success.

type ListenerCfg

type ListenerCfg struct {
	// RPCListeners can be set to the listeners to use for the RPC server.
	// If empty a regular network listener will be created.
	RPCListeners []*ListenerWithSignal
}

ListenerCfg is a wrapper around custom listeners that can be passed to lnd when calling its main method.

type ListenerWithSignal

type ListenerWithSignal struct {
	net.Listener

	// Ready will be closed by the server listening on Listener.
	Ready chan struct{}
}

ListenerWithSignal is a net.Listener that has an additional Ready channel that will be closed when a server starts listening.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL