Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/lnd/code_contribution_guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
1. [Development Guidelines](#development-guidelines)
7. [Code Approval Process](#code-approval-process)
1. [Code Review](#code-review)
1. [Continuous Integration](#continuous-integration)
1. [Rework Code (if needed)](#rework-code-if-needed)
1. [Acceptance](#acceptance)
1. [Backporting Changes](#backporting-changes)
Expand Down Expand Up @@ -210,6 +211,22 @@ other checks which are generally performed as follows:
- The change is not something which is deemed inappropriate by community
consensus

## Continuous Integration

CI does not run automatically on a new pull request. When a pull request is
opened it gets the `ci-hold` label, and while the label is present the CI
workflow skips all of its jobs and the `CI result` check fails with "CI is
held". This keeps the CI runners free for pull requests that are ready to be
tested. Once a maintainer has taken a first look at the pull request they
remove the label, which starts CI for the latest commit of the pull request.
New commits pushed while the label is present are held as well. Maintainers can
remove the label from their own pull requests right away.

Once CI runs, pull requests only run the fast checks (linting, static checks,
the commit checks, the cross compilation and part of the unit tests). The full
unit test matrix and the integration tests run in the merge queue. A maintainer
can add the `full-ci` label to run the merge queue checks on a pull request.

## Rework Code (if needed)

After the code review, the change will be accepted immediately if no issues are
Expand Down
7 changes: 7 additions & 0 deletions docs/lnd/release-notes/release-notes-0.22.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@
migration for now, so a normal build does not change the database
schema.

* [BOLT 12 invoice request
handler](https://github.com/lightningnetwork/lnd/pull/11340): add the
experimental `protocol.bolt12-offers` flag, which needs a build with the `dev`
and `test_native_sql` tags and native SQL. Add a handler that answers an
invoice request for a stored offer with a signed invoice and notifies invoice
subscribers. The node does not route invoice requests to the handler yet.

## Testing

* [BOLT 12 spec test vectors](https://github.com/lightningnetwork/lnd/pull/11001):
Expand Down
15 changes: 15 additions & 0 deletions docs/lnd/release-notes/unreleased/11304.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Bug Fixes

* lnd [now exits with a non-zero
code](https://github.com/lightningnetwork/lnd/pull/11304) when it shuts
itself down because of a
[failure](https://github.com/lightningnetwork/lnd/issues/5625), so process
managers such as systemd restart it instead of treating the stop as
deliberate. A critical error exits with 1, and a failed health check exits
with a code that identifies it: 10 chain backend, 11 disk space, 12 TLS
certificate, 13 tor connection, 14 remote signer, 15 leader status. A
health check code always names the real cause, but a failure can surface
elsewhere before the check gives up, and then lnd exits with 1 instead. The
code is logged when the shutdown starts. An OS signal or `lncli stop` exits
with 0, including a signal received while lnd is still waiting for the
wallet to be unlocked, which previously exited with 1.
6 changes: 6 additions & 0 deletions docs/lnd/release-notes/unreleased/11478.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Bug Fixes

* lnd now [rejects peer channel
aliases](https://github.com/lightningnetwork/lnd/pull/11478) that are
already assigned to another channel, and revalidates aliases before signing
channel updates.
11 changes: 11 additions & 0 deletions docs/lnd/release-notes/unreleased/11479.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Bug Fixes

* [Preserve funding batch state](https://github.com/lightningnetwork/lnd/pull/11479)
when publishing a batch transaction returns an error. Since an error can be
returned after the transaction has already reached the network, pending
channels are no longer abandoned and their input leases are no longer
explicitly released during cleanup.
This allows lnd to continue watching and recovering channels whose funding
transaction may still confirm. If the transaction is permanently
unpublishable, the retained pending channels currently require manual cleanup
after verifying that the funding transaction cannot confirm.
3 changes: 3 additions & 0 deletions docs/lnd/release-notes/unreleased/11480.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Bug Fixes

* [Reject force-close requests for restored channels](https://github.com/lightningnetwork/lnd/pull/11480) when no usable commitment transaction is available.
8 changes: 8 additions & 0 deletions docs/lnd/release-notes/unreleased/11481.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Bug Fixes

* Confirmation height hints are now [scoped by output
script](https://github.com/lightningnetwork/lnd/pull/11481) in addition to
the transaction id. A confirmation request for a transaction that never
confirms can no longer advance the cached hint of another request for
the same transaction id, which could delay the detection of an already
confirmed transaction on nodes without a transaction index.
6 changes: 6 additions & 0 deletions docs/lnd/release-notes/unreleased/11482.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Bug Fixes

* The channel database now [rejects oversized unsigned acked update
logs](https://github.com/lightningnetwork/lnd/pull/11482) instead of
truncating the entry count on write, and validates the record on load so
a restart cannot silently drop previously acknowledged channel updates.
5 changes: 5 additions & 0 deletions docs/lnd/release-notes/unreleased/11483.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Bug Fixes

* [Fixed a peer deadlock](https://github.com/lightningnetwork/lnd/pull/11483)
that could occur when a link failure overlaps cooperative-close hook
registration.
5 changes: 5 additions & 0 deletions docs/lnd/release-notes/unreleased/11484.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Bug Fixes

* [Handle replacement fee rejections](https://github.com/lightningnetwork/lnd/pull/11484)
when publishing force-close commitments and legacy HTLC timeout
transactions.
11 changes: 11 additions & 0 deletions docs/lnd/release-notes/unreleased/11489.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Technical and Architectural Updates

## Tooling and Documentation

* CI is [split into three
tiers](https://github.com/lightningnetwork/lnd/pull/11489): pull requests
only run the fast checks, the merge queue runs the full unit test matrix,
the basic itests and the key database itests, and a new nightly workflow
runs the complete set on master, including the Windows and macOS itests and
the coverage report. The `full-ci` label runs the merge queue tier on a pull
request.
8 changes: 8 additions & 0 deletions docs/lnd/release-notes/unreleased/11498.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Technical and Architectural Updates

## Tooling and Documentation

* CI [no longer runs right away on new pull
requests](https://github.com/lightningnetwork/lnd/pull/11498): they get the
`ci-hold` label, and CI only runs once a maintainer removes it. Adding the
`full-ci` label now re-runs CI on its own.
9 changes: 9 additions & 0 deletions docs/lnd/release-notes/unreleased/11499.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Technical and Architectural Updates

## Tooling and Documentation

* CI now [points Go's temporary build
directory](https://github.com/lightningnetwork/lnd/pull/11499) at the
runner's disk instead of `/tmp`, which is a small RAM backed tmpfs on the
`ubuntu-26.04` runner image. Large builds such as the per-commit unit test
check no longer fail with "disk quota exceeded" there.
10 changes: 10 additions & 0 deletions docs/lnd/release-notes/unreleased/11501.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Technical and Architectural Updates

## Tooling and Documentation

* CI [no longer occupies runners for jobs that don't run on a pull
request](https://github.com/lightningnetwork/lnd/pull/11501): the unit test,
basic itest, cross compilation and pinned dependency jobs are now skipped
entirely when they aren't part of the pull request tier, when CI is held and
for documentation-only changes. `CI result` is the only required status
check.
9 changes: 9 additions & 0 deletions docs/lnd/release-notes/unreleased/11504.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Technical and Architectural Updates

## Tooling and Documentation

* CI now [caches the custom `golangci-lint`
build](https://github.com/lightningnetwork/lnd/pull/11504) in the GitHub
Actions cache store instead of recompiling it from source on every lint
run, cutting several minutes off the lint job whenever the `tools/`
dependencies are unchanged.
8 changes: 8 additions & 0 deletions docs/lnd/release-notes/unreleased/11505.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Technical and Architectural Updates

## Tooling and Documentation

* CI now [persists `golangci-lint`'s per-file analysis
cache](https://github.com/lightningnetwork/lnd/pull/11505) between lint
runs, so the lint job only re-analyzes packages whose sources changed
instead of re-typechecking the whole repository from scratch every time.
123 changes: 117 additions & 6 deletions docs/lnd/remote-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ 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 or
outbound gRPC connection.
Note that this protects the key material itself, but does not prevent a
compromised watch-only node from obtaining valid signatures. See
[Security model](#security-model) for what the split does and does not
guarantee.
The signer instance can run on a different machine with more tightly locked down
network security, optimally only allowing the single gRPC connection to or from
the outside.
Expand Down Expand Up @@ -40,12 +44,119 @@ xxx xx
```

When using a remote signer, the "signer" node can be configured to operate in
one of two modes.
It can either be configured as an "inbound" remote signer (the default setting)
or as an "outbound" remote signer. As an "inbound" remote signer, the signer
node permits a single inbound gRPC connection **from** the watch-only lnd node.
Conversely, when configured as an "outbound" remote signer, it allows a single
outbound gRPC connection **to** the watch-only lnd node.
one of two modes. The only thing that differs between them is **which node
dials the other**; the signing itself is identical either way.

| Mode | Who dials | Who listens |
| --- | --- | --- |
| "inbound" remote signer (default) | The watch-only node dials the signer | The signer |
| "outbound" remote signer | The signer dials the watch-only node | The watch-only node |

The names describe the connection from the **signer's** point of view: an
"inbound" remote signer is one that receives an inbound connection, and an
"outbound" remote signer is one that makes an outbound connection.

Be aware that the same connection has the opposite name on the other side, so
the labels appear inverted depending on which node you are looking at. A setup
using an "outbound" remote signer is one where the watch-only node accepts an
*inbound* connection, which is why the option that selects it is called
`remotesigner.experimentalallowinboundconnection` and why the watch-only node's
internals refer to it as an inbound connection. Where it matters, this document
says which node dials rather than relying on the labels.

## Security model

Remote signing splits key material away from the node that is exposed to the
internet. It does **not** make the watch-only node harmless if it is
compromised. Read this section before deploying a remote signer, as the
distinction decides what the setup actually buys you.

> **Warning:** "watch-only" describes which keys the node holds, not how much
> damage it can do. The signer does not inspect or validate what it is asked to
> sign. An attacker who controls the watch-only node can ask the signer to
> produce valid signatures, including signatures over transactions that send
> your funds to an address of their choosing. The signer will produce them. In
> its current form this represents blind signing rather than a spending
> policy or a hardware-wallet style confirmation step.

### What the signer protects against

* **Key exfiltration from the exposed node.** The watch-only node only ever
holds public keys (`xpub`s). An attacker who reads its disk, memory or
backups does not obtain the seed or any private key, and cannot sign offline
or after losing access.
* **Blast radius of a host compromise over time.** Access to the watch-only
node is only useful for as long as the attacker keeps it and the signer stays
reachable. Revoking the connection or stopping the signer immediately removes
the ability to produce new signatures.
* **Network exposure of the key material.** The signer needs no chain backend,
no p2p connectivity and no inbound internet access. It can be confined to a
separate network zone that permits exactly one gRPC connection, as shown in
the example configurations below.

### What the signer does not protect against

The signer forwards every request it receives to its local `signrpc` and
`walletrpc` sub-servers and returns the result. It does not examine the
contents of a request, and there is no allow-list of destinations, amounts,
transaction shapes or key paths.

The following operations are delegated over the connection, and a compromised
watch-only node can invoke any of them:

| Request | What the signer does with it |
| --- | --- |
| `SignPsbt` | Signs the PSBT given, whatever it spends and wherever it pays |
| `SignMessage` | Signs an arbitrary digest under a requested key locator |
| `DeriveSharedKey` | Performs ECDH against a supplied public key |
| `MuSig2*` | Runs the full MuSig2 session lifecycle, incl. partial signing |

Concretely, this means a compromised watch-only node can construct a
transaction that sweeps the wallet to an attacker-controlled address and obtain
a valid signature for it. The private keys stay on the signer throughout, and
the funds still leave.

### Assumptions this setup makes

* The watch-only node is honest, and only asks the signer to sign what the
operator intended. The signer has no way to check this.
* The operator secures the watch-only node's host, credentials and RPC surface
to a standard appropriate for a machine that can move funds. Its security is
part of the fund-safety perimeter, not outside it.
* The macaroon used for the connection is a bearer credential and must be
protected like a private key, as must the listening node's TLS private key.
Note that the macaroon belongs to the node being **dialled**, not the one
dialling: when the signer dials the watch-only node it presents the
*watch-only node's* macaroon, and when the watch-only node dials the signer
it presents the *signer's* macaroon.

If it is compromised, rotate it on the node that issued it, i.e. on the node
being dialled. `lncli changepassword --new_mac_root_key` invalidates every
macaroon that node has issued. Deleting its `macaroons.db` has the same effect
but also discards any custom root key.

What a stolen macaroon enables depends on which node issued it:

* **The signer's macaroon**, used when the watch-only node dials the signer.
Whoever holds it can send signing requests to the signer directly, so it is
theft-capable. YOU MUST rotate it, and stop the signer until the rotation is
done.
* **The watch-only node's macaroon**, used when the signer dials the
watch-only node. As the registration challenge is not verified yet (see
below), whoever holds it can connect to the watch-only node as if it were
the signer. The watch-only node accepts only one signer connection at a
time, so if the holder connects while the real signer is not connected, the
real signer is refused and the holder receives every signing request the
watch-only node sends. It cannot produce valid signatures, so the node can
no longer sign, and it learns everything the node asks to have signed.
Rotate it as soon as possible.

### What this is not (yet)

A validating remote signer, which would enforce a policy on what it is willing
to sign, is a separate and larger piece of work. Until it exists, the trust
model above is the one in effect: the signer protects the keys, and the
watch-only node decides what gets signed.

## Example setups

Expand Down