From 158171371d2e03f236de91e764833b22ceac8340 Mon Sep 17 00:00:00 2001 From: will-bitlightlabs Date: Mon, 29 Jun 2026 03:07:18 +0800 Subject: [PATCH 1/3] docs: add RGBR disaster recovery bLIP, reserve 829 and message types in bLIP-2 Signed-off-by: will-bitlightlabs --- blip-0002.md | 4 + blip-rgb-recovery.md | 246 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 blip-rgb-recovery.md diff --git a/blip-0002.md b/blip-0002.md index 72da790..ab80ee1 100644 --- a/blip-0002.md +++ b/blip-0002.md @@ -54,6 +54,7 @@ bLIPs may reserve feature bits by adding them to the following table: | 260/261 | `htlc_endorsement` | This node forwards experimental htlc endorsement signals | N | | [bLIP 4](./blip-004.md) | | 262/263 | `bolt11_blinded_path` | This invoice may contain a new blinded path tagged field | I | `option_route_blinding` | [bLIP 39](./blip-0039.md) | | 729 | `option_supports_lsps` | This node supports LSPS protocol(s) | IN | | [bLIP 50](./blip-0050.md) | +| 828/829 | `rgb_recovery` | This node implements the RGBR disaster-recovery protocol | IN | `rgb_channel` | [bLIP XX](./blip-rgb-recovery.md) | ### Messages @@ -78,6 +79,9 @@ bLIPs may create new messages and reserve their type in the following table: | 63501 | `hc_updated_fail_htlc` | [bLIP 17](./blip-0017.md) | | 63499 | `hc_update_fail_malformed_htlc` | [bLIP 17](./blip-0017.md) | | 63497 | `hc_error` | [bLIP 17](./blip-0017.md) | +| 60001 | `rgbr_query_channel_close` | [bLIP XX](./blip-rgb-recovery.md) | +| 60003 | `rgbr_channel_close_info` | [bLIP XX](./blip-rgb-recovery.md) | +| 60005 | `rgbr_error` | [bLIP XX](./blip-rgb-recovery.md) | ### TLV fields in BOLT messages diff --git a/blip-rgb-recovery.md b/blip-rgb-recovery.md new file mode 100644 index 0000000..142fb18 --- /dev/null +++ b/blip-rgb-recovery.md @@ -0,0 +1,246 @@ +``` +bLIP: ? +Title: RGB-LN Disaster Recovery Protocol (RGBR) +Status: Draft +Author: will-bitlightlabs +Created: 2026-06-29 +License: CC0 +Requires: 70 +``` + +## Abstract + +This bLIP defines the **RGBR protocol**: an `rgb_recovery` feature bit (829, odd) and three custom messages (types 60001 / 60003 / 60005, odd) that let a party which has lost the local state of an RGB channel query its counterparty for that channel's close status and obtain the download address of the consignment needed to rebuild its own RGB allocation. + +It is a sibling to [bLIP-0070](https://github.com/lightning/blips/pull/70) (RGB colored channel support): bLIP-0070 defines how RGB assets travel over Lightning (feature bit 827), while this bLIP defines how to recover the assets after an RGB channel is closed (feature bit 829). + +All extensions are odd-typed; standard Lightning nodes ignore them under the "it's ok to be odd" rule and are unaffected. + +## Copyright + +CC0. + +--- + +## Motivation + +After an ordinary Lightning channel closes, a party that has lost its channel state can still sweep its BTC from the `to_remote` output using only its seed, with no communication with the counterparty — this is achievable purely on-chain through key derivation. + +RGB colored channels cannot. RGB is a client-side validated protocol: the asset allocation is maintained off-chain as **CSV state** (persisted in the node's Stock), and the chain carries only an OP_RETURN commitment. After a party loses its local state, it **cannot rebuild its own RGB allocation from on-chain data alone** — it must retrieve it from the counterparty that still holds the corresponding CSV state. + +RGBR fills this single missing link: it lets the recovering party query the counterparty for the channel's close status, locate the consignment the counterparty exports, and rebuild its RGB allocation from it. + +--- + +## Rationale + +### Reusing the RGB feature family (827 → 829) + +The number for `rgb_channel` (827) derives from RGB's SLIP-0044 registered coin type 827166/827167. RGBR is the companion recovery capability for RGB channels, so it reuses the adjacent odd bit **829** in the same family, forming an "open channel / recover channel" correspondence with 827. + +### 829 is advertised only in Init/Node + +RGBR is a **node capability** (whether the node can answer recovery queries). Recovery happens **after** a channel has closed, where there is no channel-type negotiation context. Therefore 829 appears only in `init` / `node_announcement` and not in `channel_type`. + +### consignment travels by URL, not inside the LN message + +A consignment grows with the channel's transfer history (the operation DAG) and is not compressed; it can reach MB or even GB scale, far too large to carry inside an LN message. `RGBR_CHANNEL_CLOSE_INFO` returns only a `consignment_url`, by which the recovering party downloads it out-of-band. The URL's concrete scheme is left to the implementation and is not constrained by this protocol. + +--- + +## Specification + +### Feature Bit + +| Bits | Name | Description | Context | Dependencies | +|---|---|---|---|---| +| 828/829 | `rgb_recovery` | The node implements RGBR and can answer `RGBR_QUERY_CHANNEL_CLOSE`. | `init`, `node_announcement` | `rgb_channel` (827) | + +- **829** (odd, optional): used in `init` and `node_announcement`. A counterparty lacking this bit does not affect the handshake; it merely signals that the counterparty does not answer recovery queries. +- Not used in `channel_type`. +- Depends on `rgb_channel` (827). + +### Custom Message Types + +RGBR uses odd types (BOLT-1: odd = optional; an unsupporting counterparty ignores rather than disconnects). + +| Message | Type | Direction | Description | +|---|---|---|---| +| `RGBR_QUERY_CHANNEL_CLOSE` | **60001** (0xEA61) | recovering party → counterparty | Query a channel's close status + consignment | +| `RGBR_CHANNEL_CLOSE_INFO` | **60003** (0xEA63) | counterparty → recovering party | Response | +| `RGBR_ERROR` | **60005** (0xEA65) | counterparty → recovering party | Error response | + +The range `60001-60099` (odd) is reserved for future extensions of the RGBR family. + +### Message TLV Fields + +Field types follow the [BOLT-1 fundamental types](https://github.com/lightning/bolts/blob/master/01-messaging.md#fundamental-types) (`byte` / `u16` / `u64` / `sha256` / `channel_id` / `utf8`). `utf8` and `byte` are encoded bare in the TLV value: the length is given by the TLV record's `length`, and the value contains no extra length prefix or trailing bytes. + +Each message's payload is a single TLV stream (the message's `data` carries one `tlvs` field, defined below). Whether a given field is required is specified in [Requirements](#requirements). + +#### `RGBR_QUERY_CHANNEL_CLOSE` (60001) + +1. type: 60001 (`rgbr_query_channel_close`) +2. data: + * [`rgbr_query_channel_close_tlvs`:`tlvs`] + +1. `tlv_stream`: `rgbr_query_channel_close_tlvs` +2. types: + 1. type: 0 (`protocol_version`) + 2. data: + * [`u16`:`protocol_version`] + 1. type: 2 (`query_id`) + 2. data: + * [`u64`:`query_id`] + 1. type: 4 (`channel_id`) + 2. data: + * [`channel_id`:`channel_id`] + 1. type: 6 (`funding_txid`) + 2. data: + * [`sha256`:`funding_txid`] + +#### `RGBR_CHANNEL_CLOSE_INFO` (60003) + +1. type: 60003 (`rgbr_channel_close_info`) +2. data: + * [`rgbr_channel_close_info_tlvs`:`tlvs`] + +1. `tlv_stream`: `rgbr_channel_close_info_tlvs` +2. types: + 1. type: 0 (`protocol_version`) + 2. data: + * [`u16`:`protocol_version`] + 1. type: 2 (`query_id`) + 2. data: + * [`u64`:`query_id`] + 1. type: 4 (`channel_id`) + 2. data: + * [`channel_id`:`channel_id`] + 1. type: 6 (`status`) + 2. data: + * [`byte`:`status`] + 1. type: 8 (`close_txid`) + 2. data: + * [`sha256`:`close_txid`] + 1. type: 10 (`consignment_url`) + 2. data: + * [`...*byte`:`consignment_url`] + 1. type: 12 (`rgb_amount`) + 2. data: + * [`u64`:`rgb_amount`] + 1. type: 14 (`peer_health`) + 2. data: + * [`byte`:`peer_health`] + +#### `RGBR_ERROR` (60005) + +1. type: 60005 (`rgbr_error`) +2. data: + * [`rgbr_error_tlvs`:`tlvs`] + +1. `tlv_stream`: `rgbr_error_tlvs` +2. types: + 1. type: 0 (`protocol_version`) + 2. data: + * [`u16`:`protocol_version`] + 1. type: 2 (`query_id`) + 2. data: + * [`u64`:`query_id`] + 1. type: 4 (`error_code`) + 2. data: + * [`byte`:`error_code`] + 1. type: 6 (`error_message`) + 2. data: + * [`...*byte`:`error_message`] + +### Enumerated Values + +The following `status` values are defined: + +* `0`: `Closed` — the counterparty has force-closed; `close_txid` + `consignment_url` are required +* `1`: `Open` — the counterparty considers the channel still open (state divergence) +* `2`: `Unknown` — the counterparty has forgotten the channel (already cleaned up) + +The following `peer_health` values are defined: + +* `0`: `Healthy` — the counterparty is operating normally +* `1`: `Recovering` — the counterparty is itself in recovery; its answer may be unreliable +* `2`: `Unknown` — the counterparty cannot determine its own state + +The following `error_code` values are defined: + +* `0`: `unsupported_version` — unrecognized `protocol_version` +* `1`: `unknown_channel` — no matching channel found +* `2`: `channel_not_closed` — the channel exists but is not closed +* `3`: `consignment_unavailable` — the channel is closed but the consignment cannot be provided +* `4`: `peer_not_authorized` — the counterparty refuses to provide the information (policy) +* `5`: `internal_error` — counterparty internal error + +### State Machine + +``` +recovering party counterparty + │ │ + │ ── RGBR_QUERY_CHANNEL_CLOSE ─────► │ (query_id=N) + │ │ + │ ◄──── RGBR_CHANNEL_CLOSE_INFO ──── │ (query_id=N, status, [close_txid, + │ │ consignment_url], peer_health) + │ or │ + │ ◄──────── RGBR_ERROR ───────────── │ (query_id=N, error_code) +``` + +- Single request / single response, with no subsequent state. +- The response MUST echo the request's `query_id` (by which the recovering party correlates concurrent queries). +- Retry on failure is decided by the caller; the protocol has no built-in retry. + +### Version Negotiation + +- Every message carries `protocol_version: u16` as TLV type 0; currently = **1**. +- A receiver that receives an unrecognized version replies with `RGBR_ERROR(unsupported_version)`. +- Backward-compatible extensions **add odd TLV fields** (BOLT-1: an unknown odd TLV is ignored, an unknown even TLV is treated as an error); breaking changes increment `protocol_version`. New fields therefore always use odd types, so older nodes can safely ignore them without erroring. + +### Requirements + +**`funding_txid` / `close_txid` byte order**: BOLT internal/wire byte order (i.e. consensus encoding, as in BOLT-2 `funding_txid`), **not** the block-explorer/RPC display order. + +**Responder (the counterparty constructing `RGBR_CHANNEL_CLOSE_INFO`)**: +- When `status = Closed`, MUST set `close_txid` and `consignment_url`. +- When `status = Open` or `Unknown`, MUST NOT set `close_txid` / `consignment_url` / `rgb_amount`. +- MUST always set `peer_health`. +- MUST echo the request's `query_id`. +- `consignment_url` MUST be ≤ 1024 bytes; `error_message` MUST be ≤ 1024 bytes. + +**Receiver**: +- If any required TLV field of that message is missing, MUST treat the message as invalid and discard it. +- If `status = Closed` but `close_txid` or `consignment_url` is missing, MUST treat the message as invalid. +- An unknown odd TLV type MUST be ignored; an unknown even TLV type MUST be treated as an error (BOLT-1). + +**Recovering party (initiating `RGBR_QUERY_CHANNEL_CLOSE`)**: +- SHOULD keep `query_id` unique among in-flight requests, to correlate the responses of concurrent queries. + +--- + +## Universality + +RGBR is entirely optional. `rgb_recovery` (829) is an odd bit; a standard node ignores it in `init` and the handshake is unaffected. All three custom messages are odd-typed; an unsupporting node ignores them under BOLT-1 and does not disconnect. + +## Backwards Compatibility + +| Extension | Parity | Standard node behavior | +|---|---|---| +| feature bit 829 | odd (optional) | Ignored in `init`; normal handshake | +| custom message 60001/60003/60005 | odd | Ignored on receipt; no disconnect | + +Depends on bLIP-0070 (827). + +## Reference Implementation + +A reference implementation based on LDK (rust-lightning) is under development by Bitlight Labs and will be made available upon public release. + + +--- + +## Limitations + +- **Double-blind disaster**: when both parties to a channel have lost their state, neither retains the latest CSV state (Stock), so the RGB portion cannot be recovered (`peer_health=Recovering` serves to warn of this). +- **in-flight HTLC**: the RGB balance already settled at channel close can be recovered normally; but the BTC and RGB carried by an HTLC still in flight (not yet settled) at the moment of close are out of scope for this version — they remain on the commitment's HTLC outputs and require a preimage or timeout to be claimed via the HTLC-claim path, which this version does not implement. From 3b0165fa27330a9b967e3a6adbf03a751679aefd Mon Sep 17 00:00:00 2001 From: will-bitlightlabs Date: Mon, 29 Jun 2026 13:58:03 +0800 Subject: [PATCH 2/3] bLIP-72: assign number, rename blip-rgb-recovery.md -> blip-0072.md Signed-off-by: will-bitlightlabs --- blip-0002.md | 8 ++++---- blip-rgb-recovery.md => blip-0072.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename blip-rgb-recovery.md => blip-0072.md (99%) diff --git a/blip-0002.md b/blip-0002.md index ab80ee1..0071c32 100644 --- a/blip-0002.md +++ b/blip-0002.md @@ -54,7 +54,7 @@ bLIPs may reserve feature bits by adding them to the following table: | 260/261 | `htlc_endorsement` | This node forwards experimental htlc endorsement signals | N | | [bLIP 4](./blip-004.md) | | 262/263 | `bolt11_blinded_path` | This invoice may contain a new blinded path tagged field | I | `option_route_blinding` | [bLIP 39](./blip-0039.md) | | 729 | `option_supports_lsps` | This node supports LSPS protocol(s) | IN | | [bLIP 50](./blip-0050.md) | -| 828/829 | `rgb_recovery` | This node implements the RGBR disaster-recovery protocol | IN | `rgb_channel` | [bLIP XX](./blip-rgb-recovery.md) | +| 828/829 | `rgb_recovery` | This node implements the RGBR disaster-recovery protocol | IN | `rgb_channel` | [bLIP 72](./blip-0072.md) | ### Messages @@ -79,9 +79,9 @@ bLIPs may create new messages and reserve their type in the following table: | 63501 | `hc_updated_fail_htlc` | [bLIP 17](./blip-0017.md) | | 63499 | `hc_update_fail_malformed_htlc` | [bLIP 17](./blip-0017.md) | | 63497 | `hc_error` | [bLIP 17](./blip-0017.md) | -| 60001 | `rgbr_query_channel_close` | [bLIP XX](./blip-rgb-recovery.md) | -| 60003 | `rgbr_channel_close_info` | [bLIP XX](./blip-rgb-recovery.md) | -| 60005 | `rgbr_error` | [bLIP XX](./blip-rgb-recovery.md) | +| 60001 | `rgbr_query_channel_close` | [bLIP 72](./blip-0072.md) | +| 60003 | `rgbr_channel_close_info` | [bLIP 72](./blip-0072.md) | +| 60005 | `rgbr_error` | [bLIP 72](./blip-0072.md) | ### TLV fields in BOLT messages diff --git a/blip-rgb-recovery.md b/blip-0072.md similarity index 99% rename from blip-rgb-recovery.md rename to blip-0072.md index 142fb18..3432994 100644 --- a/blip-rgb-recovery.md +++ b/blip-0072.md @@ -1,5 +1,5 @@ ``` -bLIP: ? +bLIP: 72 Title: RGB-LN Disaster Recovery Protocol (RGBR) Status: Draft Author: will-bitlightlabs From 6fb11c552073bd677297d3292fa03970c37eb8de Mon Sep 17 00:00:00 2001 From: will-bitlightlabs Date: Tue, 30 Jun 2026 16:00:05 +0800 Subject: [PATCH 3/3] Update bLIP-72 to rename RGBR to RGB Recovery and clarify protocol details in documentation Signed-off-by: will-bitlightlabs --- blip-0002.md | 2 +- blip-0072.md | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/blip-0002.md b/blip-0002.md index 0071c32..ddce823 100644 --- a/blip-0002.md +++ b/blip-0002.md @@ -54,7 +54,7 @@ bLIPs may reserve feature bits by adding them to the following table: | 260/261 | `htlc_endorsement` | This node forwards experimental htlc endorsement signals | N | | [bLIP 4](./blip-004.md) | | 262/263 | `bolt11_blinded_path` | This invoice may contain a new blinded path tagged field | I | `option_route_blinding` | [bLIP 39](./blip-0039.md) | | 729 | `option_supports_lsps` | This node supports LSPS protocol(s) | IN | | [bLIP 50](./blip-0050.md) | -| 828/829 | `rgb_recovery` | This node implements the RGBR disaster-recovery protocol | IN | `rgb_channel` | [bLIP 72](./blip-0072.md) | +| 828/829 | `rgb_recovery` | This node implements the RGB Recovery disaster-recovery protocol | IN | `rgb_channel` | [bLIP 72](./blip-0072.md) | ### Messages diff --git a/blip-0072.md b/blip-0072.md index 3432994..3b9984d 100644 --- a/blip-0072.md +++ b/blip-0072.md @@ -1,6 +1,6 @@ ``` bLIP: 72 -Title: RGB-LN Disaster Recovery Protocol (RGBR) +Title: RGB-LN Disaster Recovery Protocol Status: Draft Author: will-bitlightlabs Created: 2026-06-29 @@ -10,7 +10,7 @@ Requires: 70 ## Abstract -This bLIP defines the **RGBR protocol**: an `rgb_recovery` feature bit (829, odd) and three custom messages (types 60001 / 60003 / 60005, odd) that let a party which has lost the local state of an RGB channel query its counterparty for that channel's close status and obtain the download address of the consignment needed to rebuild its own RGB allocation. +This bLIP defines the **RGB Recovery** protocol: an `rgb_recovery` feature bit (829, odd) and three custom messages (types 60001 / 60003 / 60005, odd) that let a party which has lost the local state of an RGB channel query its counterparty for that channel's close status and obtain the download address of the consignment needed to rebuild its own RGB allocation. It is a sibling to [bLIP-0070](https://github.com/lightning/blips/pull/70) (RGB colored channel support): bLIP-0070 defines how RGB assets travel over Lightning (feature bit 827), while this bLIP defines how to recover the assets after an RGB channel is closed (feature bit 829). @@ -28,7 +28,7 @@ After an ordinary Lightning channel closes, a party that has lost its channel st RGB colored channels cannot. RGB is a client-side validated protocol: the asset allocation is maintained off-chain as **CSV state** (persisted in the node's Stock), and the chain carries only an OP_RETURN commitment. After a party loses its local state, it **cannot rebuild its own RGB allocation from on-chain data alone** — it must retrieve it from the counterparty that still holds the corresponding CSV state. -RGBR fills this single missing link: it lets the recovering party query the counterparty for the channel's close status, locate the consignment the counterparty exports, and rebuild its RGB allocation from it. +RGB Recovery fills this single missing link: it lets the recovering party query the counterparty for the channel's close status, locate the consignment the counterparty exports, and rebuild its RGB allocation from it. --- @@ -36,11 +36,11 @@ RGBR fills this single missing link: it lets the recovering party query the coun ### Reusing the RGB feature family (827 → 829) -The number for `rgb_channel` (827) derives from RGB's SLIP-0044 registered coin type 827166/827167. RGBR is the companion recovery capability for RGB channels, so it reuses the adjacent odd bit **829** in the same family, forming an "open channel / recover channel" correspondence with 827. +The number for `rgb_channel` (827) derives from RGB's SLIP-0044 registered coin type 827166/827167. RGB Recovery is the companion recovery capability for RGB channels, so it reuses the adjacent odd bit **829** in the same family, forming an "open channel / recover channel" correspondence with 827. ### 829 is advertised only in Init/Node -RGBR is a **node capability** (whether the node can answer recovery queries). Recovery happens **after** a channel has closed, where there is no channel-type negotiation context. Therefore 829 appears only in `init` / `node_announcement` and not in `channel_type`. +RGB Recovery is a **node capability** (whether the node can answer recovery queries). Recovery happens **after** a channel has closed, where there is no channel-type negotiation context. Therefore 829 appears only in `init` / `node_announcement` and not in `channel_type`. ### consignment travels by URL, not inside the LN message @@ -54,7 +54,7 @@ A consignment grows with the channel's transfer history (the operation DAG) and | Bits | Name | Description | Context | Dependencies | |---|---|---|---|---| -| 828/829 | `rgb_recovery` | The node implements RGBR and can answer `RGBR_QUERY_CHANNEL_CLOSE`. | `init`, `node_announcement` | `rgb_channel` (827) | +| 828/829 | `rgb_recovery` | The node implements RGB Recovery and can answer `RGBR_QUERY_CHANNEL_CLOSE`. | `init`, `node_announcement` | `rgb_channel` (827) | - **829** (odd, optional): used in `init` and `node_announcement`. A counterparty lacking this bit does not affect the handshake; it merely signals that the counterparty does not answer recovery queries. - Not used in `channel_type`. @@ -62,7 +62,7 @@ A consignment grows with the channel's transfer history (the operation DAG) and ### Custom Message Types -RGBR uses odd types (BOLT-1: odd = optional; an unsupporting counterparty ignores rather than disconnects). +RGB Recovery uses odd types (BOLT-1: odd = optional; an unsupporting counterparty ignores rather than disconnects). The three messages are named with the `RGBR_` prefix (short for RGB Recovery). | Message | Type | Direction | Description | |---|---|---|---| @@ -70,7 +70,7 @@ RGBR uses odd types (BOLT-1: odd = optional; an unsupporting counterparty ignore | `RGBR_CHANNEL_CLOSE_INFO` | **60003** (0xEA63) | counterparty → recovering party | Response | | `RGBR_ERROR` | **60005** (0xEA65) | counterparty → recovering party | Error response | -The range `60001-60099` (odd) is reserved for future extensions of the RGBR family. +The range `60001-60099` (odd) is reserved for future extensions of the RGB Recovery family. ### Message TLV Fields @@ -222,7 +222,7 @@ recovering party counterparty ## Universality -RGBR is entirely optional. `rgb_recovery` (829) is an odd bit; a standard node ignores it in `init` and the handshake is unaffected. All three custom messages are odd-typed; an unsupporting node ignores them under BOLT-1 and does not disconnect. +RGB Recovery is entirely optional. `rgb_recovery` (829) is an odd bit; a standard node ignores it in `init` and the handshake is unaffected. All three custom messages are odd-typed; an unsupporting node ignores them under BOLT-1 and does not disconnect. ## Backwards Compatibility