From d43854aa34ca0c2d0dfa12b06f299def39b512fb Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:43:02 +0200 Subject: [PATCH] docs: add remote signing documentation and release notes --- docs/release-notes/release-notes-0.14.0.md | 7 + docs/remote-signing.md | 281 +++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 docs/remote-signing.md diff --git a/docs/release-notes/release-notes-0.14.0.md b/docs/release-notes/release-notes-0.14.0.md index 3f0d06fd..6bb9c59b 100644 --- a/docs/release-notes/release-notes-0.14.0.md +++ b/docs/release-notes/release-notes-0.14.0.md @@ -164,6 +164,13 @@ documentation](../psbt.md#use-the-batchopenchannel-rpc-for-safe-batch-channel-fu take longer to start a `lnd` node when running in `simnet` or `regtest`, something developers need to watch out from this release. +### Remote signing + +It is now possible to delegate any operation that needs access to private keys +to a [remote signer that serves signing requests over +RPC](https://github.com/lightningnetwork/lnd/pull/5689). More information can be +found [in the new remote signing document](../remote-signing.md). + ## Security * The release signature verification script [was overhauled to fix some possible diff --git a/docs/remote-signing.md b/docs/remote-signing.md new file mode 100644 index 00000000..9e456152 --- /dev/null +++ b/docs/remote-signing.md @@ -0,0 +1,281 @@ +# Remote signing + +Remote signing refers to an operating mode of `lnd` in which the wallet is +segregated into two parts, each running within its own instance of `lnd`. One +instance is running in watch-only mode which means it only has **public** +keys in its wallet. The second instance (in this document referred to as +"signer" or "remote signer" instance) has the same keys in its wallet, including +the **private** keys. + +The advantage of such a setup is that the `lnd` instance containing the private +keys (the "signer") can be completely offline except for a single inbound gRPC +connection and the outbound connection to `bitcoind` (or `btcd` or `neutrino`). +The signer instance can run on a different machine with more tightly locked down +network security, optimally only allowing the single gRPC connection from the +outside. + +An example setup could look like: + +```text + xxxxxxxxx + xxxxxxxxx xxxx +xxx xx +x LN p2p network xx +x x +xxx xx + xxxxx xxxxxxxx + xxx + ^ +----------------------------------+ + | p2p traffic | firewalled/offline network zone | + | | | + v | | + +----------------+ gRPC | +----------------+ | + | watch-only lnd +--------------+-->| full seed lnd | | + +-------+--------+ | +------+---------+ | + | | | | + +-------v--------+ | +------v---------+ | + | bitcoind/btcd | | | bitcoind/btcd | | + +----------------+ | +----------------+ | + | | + +----------------------------------+ +``` + +NOTE: Offline in this context means that `lnd` itself does not need to be +reachable from the internet and itself doesn't connect out. If the `bitcoind` +or other chain backend is indeed running within this same firewall/offline zone +then that component will need at least _outbound_ internet access. But it is +also possible to use a chain backend that is running outside this zone. + +## Restrictions / limitations + +The current implementation of the remote signing mode comes with a few +restrictions and limitations: + +- Both `lnd` instances need a connection to a chain backend: + - The type of chain backend (`bitcoind`, `btcd` or `neutrino`) **must** + match. + - Both instances can point to the same chain backend node, though limits + apply with the number of `lnd` instances that can use the same `bitcoind` + node over ZMQ. + See ["running multiple lnd nodes" in the safety guide](safety.md#running-multiple-lnd-nodes) + . + - Using a pruned chain backend is not recommended as that increases the + chance of the two wallets falling out of sync with each other. +- The wallet of the "signer" instance **must not be used** for anything. + Especially generating new addresses manually on the "signer" will lead to the + two wallets falling out of sync! + +## Example setup + +In this example we are going to set up two nodes, the "signer" that has the full +seed and private keys and the "watch-only" node that only has public keys. + +### The "signer" node + +The node "signer" is the hardened node that contains the private key material +and is not connected to the internet or LN P2P network at all. Ideally only a +single RPC based connection (that can be firewalled off specifically) can be +opened to this node from the host on which the node "watch-only" is running. + +Recommended entries in `lnd.conf`: + +```text +# No special configuration required other than basic "hardening" parameters to +# make sure no connections to the internet are opened. + +[Application Options] +# Don't listen on the p2p port. +nolisten=true + +# Don't reach out to the bootstrap nodes, we don't need a synced graph. +nobootstrap=true + +# Just an example, this is the port that needs to be opened in the firewall and +# reachable from the node "watch-only". +rpclisten=10019 +``` + +After successfully starting up "signer", the following command can be run to +export the `xpub`s of the wallet: + +```shell +signer> ⛰ lncli wallet accounts list > accounts-signer.json +``` + +That `accounts-signer.json` file has to be copied to the machine on which +"watch-only" will be running. It contains the extended public keys for all of +`lnd`'s accounts. + +A custom macaroon can be baked for the watch-only node so it only gets the +minimum required permissions on the signer instance: + +```shell +signer> ⛰ lncli bakemacaroon --save_to signer.custom.macaroon \ + message:write signer:generate address:read onchain:write +``` + +Copy this file (`signer.custom.macaroon`) along with the `tls.cert` of the +signer node to the machine where the watch-only node will be running. + +### The "watch-only" node + +The node "watch-only" is the public, internet facing node that does not contain +any private keys in its wallet but delegates all signing operations to the node +"signer" over a single RPC connection. + +Required entries in `lnd.conf`: + +```text +[remotesigner] +remotesigner.enable=true +remotesigner.rpchost=zane.example.internal:10019 +remotesigner.tlscertpath=/home/watch-only/example/signer.tls.cert +remotesigner.macaroonpath=/home/watch-only/example/signer.custom.macaroon +``` + +After starting "watch-only", the wallet can be created in watch-only mode by +running: + +```shell +watch-only> ⛰ lncli createwatchonly accounts-signer.json + +Input wallet password: +Confirm password: + +Input an optional wallet birthday unix timestamp of first block to start scanning from (default 0): + + +Input an optional address look-ahead used to scan for used keys (default 2500): +``` + +Alternatively a script can be used for initializing the watch-only wallet +through the RPC interface as is described in the next section. + +## Example initialization script + +This section shows an example script that initializes the watch-only wallet of +the public node using NodeJS. + +To use this example, first initialize the "signer" wallet with the root key +`tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg` +using the command line. This can be done by using the new `x` option during the +interactive `lncli create` command: + +```bash +signer> ⛰ lncli create +Input wallet password: +Confirm password: + +Do you have an existing cipher seed mnemonic or extended master root key you want to use? +Enter 'y' to use an existing cipher seed mnemonic, 'x' to use an extended master root key +or 'n' to create a new seed (Enter y/x/n): +``` + +Then run this script against the "watch-only" node (after editing the +constants): + +```javascript + +// EDIT ME: +const WATCH_ONLY_LND_DIR = '/home/watch-only/.lnd'; +const WATCH_ONLY_RPC_HOSTPORT = 'localhost:10018'; +const WATCH_ONLY_WALLET_PASSWORD = 'testnet3'; +const LND_SOURCE_DIR = '.'; + +const fs = require('fs'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const loaderOptions = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}; +const packageDefinition = protoLoader.loadSync([ + LND_SOURCE_DIR + '/lnrpc/walletunlocker.proto', +], loaderOptions); + +process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' + +// build ssl credentials using the cert the same as before +let lndCert = fs.readFileSync(WATCH_ONLY_LND_DIR + '/tls.cert'); +let sslCreds = grpc.credentials.createSsl(lndCert); + +let lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition); +let lnrpc = lnrpcDescriptor.lnrpc; +var client = new lnrpc.WalletUnlocker(WATCH_ONLY_RPC_HOSTPORT, sslCreds); + +client.initWallet({ + wallet_password: Buffer.from(WATCH_ONLY_WALLET_PASSWORD, 'utf-8'), + recovery_window: 2500, + watch_only: { + accounts: [{ + purpose: 49, + coin_type: 0, + account: 0, + xpub: 'tpubDDXEYWvGCTytEF6hBog9p4qr2QBUvJhh4P2wM4qHHv9N489khkQoGkBXDVoquuiyBf8SKBwrYseYdtq9j2v2nttPpE8qbuW3sE2MCkFPhTq', + }, { + purpose: 84, + coin_type: 0, + account: 0, + xpub: 'tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3DkhvuW2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQiKTPPV', + }, { + purpose: 1017, + coin_type: 1, + account: 0, + xpub: 'tpubDDXFHr67Ro2tHKVWG2gNjjijKUH1Lyv5NKFYdJnuaLGVNBVwyV5AbykhR43iy8wYozEMbw2QfmAqZhb8gnuL5mm9sZh8YsR6FjGAbew1xoT', + }, { + purpose: 1017, + coin_type: 1, + account: 1, + xpub: 'tpubDDXFHr67Ro2tKkccDqNfDqZpd5wCs2n6XRV2Uh185DzCTbkDaEd9v7P837zZTYBNVfaRriuxgGVgxbGjDui4CKxyzBzwz4aAZxjn2PhNcQy', + }, { + purpose: 1017, + coin_type: 1, + account: 2, + xpub: 'tpubDDXFHr67Ro2tNH4KH41i4oTsWfRjFigoH1Ee7urvHow51opH9xJ7mu1qSPMPVtkVqQZ5tE4NTuFJPrbDqno7TQietyUDmPTwyVviJbGCwXk', + }, { + purpose: 1017, + coin_type: 1, + account: 3, + xpub: 'tpubDDXFHr67Ro2tQj5Zvav2ALhkU6dRQAhEtNPnYJVBC8hs2U1A9ecqxRY3XTiJKBDD7e8tudhmTRs8aGWJAiAXJN5kXy3Hi6cmiwGWjXK5Cv5', + }, { + purpose: 1017, + coin_type: 1, + account: 4, + xpub: 'tpubDDXFHr67Ro2tSSR2LLBJtotxx2U45cuESLWKA72YT9td3SzVKHAptzDEx5chsUNZ4WRMY5h6HJxRSebjRatxQKX1uUsux1LvKS1wsfNJ2PH', + }, { + purpose: 1017, + coin_type: 1, + account: 5, + xpub: 'tpubDDXFHr67Ro2tTwzfWvNoMoPpZbxdMEfe1WhbXJxvXikGixPa4ggSRZeGx6T5yxVHTVT3rjVh35Veqsowj7emX8SZfXKDKDKcLduXCeWPUU3', + }, { + purpose: 1017, + coin_type: 1, + account: 6, + xpub: 'tpubDDXFHr67Ro2tYEDS2EByRedfsUoEwBtrzVbS1qdPrX6sAkUYGLrZWvMmQv8KZDZ4zd9r8WzM9bJ2nGp7XuNVC4w2EBtWg7i76gbrmuEWjQh', + }, { + purpose: 1017, + coin_type: 1, + account: 7, + xpub: 'tpubDDXFHr67Ro2tYpwnFJEQaM8eAPM2UV5uY6gFgXeSzS5aC5T9TfzXuawYKBbQMZJn8qHXLafY4tAutoda1aKP5h6Nbgy3swPbnhWbFjS5wnX', + }, { + purpose: 1017, + coin_type: 1, + account: 8, + xpub: 'tpubDDXFHr67Ro2tddKpAjUegXqt7EGxRXnHkeLbUkfuFMGbLJYgRpG4ew5pMmGg2nmcGmHFQ29w3juNhd8N5ZZ8HwJdymC4f5ukQLJ4yg9rEr3', + }, { + purpose: 1017, + coin_type: 1, + account: 9, + xpub: 'tpubDDXFHr67Ro2tgE89V8ZdgMytC2Jq1iT9ttGhdzR1X7haQJNBmXt8kau6taC6DGASYzbrjmo9z9w6JQFcaLNqbhS2h2PVSzKf79j265Zi8hF', + }] + } +}, (err, res) => { + if (err != null) { + console.log(err); + } + console.log(res); +}); +```