Skip to content

NYDIG/lndsigner

Repository files navigation

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=https://127.0.0.1:8200 VAULT_TOKEN=root vault secrets enable --path=lndsigner vault-plugin-lndsigner

Create a new node:

$ VAULT_ADDR=https://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=https://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:https://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=https://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=https://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.