bLIP: 0017
Title: Hosted Channels
Status: Active
Author: Anton Kumaigorodski <[email protected]>
fiatjaf <[email protected]>
Created: 2022-07-07
License: CC0
Hosted Channels is a specification for auditable Lightning-like channels backed only by trust from a client in a host.
This bLIP serves to document what is already supported by some wallets and daemons (see Reference Implementations) and to encourage new implementations.
This bLIP is licensed under the CC0 license.
Hosted Channels are an improvement over the traditional custodial Lightning wallet model. They describe the roles of "host" and "client", the client being the peer that is trusting the host to allow them to spend from the channel according to their balance.
Like custodial wallets, hosted channel clients do not have to have any previous Bitcoin UTXO or touch the chain in any way; they gain the ability to receive payments immediately; and they do not have to keep track of a channel state history and all the pitfalls involved on that, or keep track of Bitcoin transactions or fees.
Unlike custodial wallets, hosted channel clients are not "accounts" in a central server, they communicate through signed messages over the Lightning protocol and are otherwise anonymous to the hosts; they perform client-side routing and their hosts only ever see onions to forward, never the final destination of a payment; they can open multiple channels to different hosts and send MPP payments over these channels, further improving their anonymity; and their channel state is signed by both parties, so they are immune to hosts that may tamper with balances from their actual state on purpose or by mistake. All this also means clients must be online to send and receive, as they must sign state updates.
The protocol consists of messages being passed through bolt01 connections and mimics bolt02, with different messages that serve analogous purposes.
Importantly, it defines two distinct roles in the channel: that of the HOST and that of the CLIENT. The CLIENT being the party who trusts the HOST with its funds when it requests a hosted channel and receives payments into it.
This is a diagram that illustrates the entire channel establishment process.
C is the CLIENT, H is the HOST.
+-------+ +-------+
| |--(1)--- invoke_hosted_channel -->| |
| |<-(2)--- init_hosted_channel -----| |
| C | | H |
| |--(3)------ state_update -------->| |
| |<-(4)------ state_update ---------| |
+-------+ +-------+
The following sections about the messages will detail this process further.
This message is sent by the CLIENT when it wants to request a new hosted channel from a HOST, but also every time it establishes a connection to the HOST, to activate that channel and check if the two parties are in agreement with regards to the channel state.
- type: 65535 (
invoke_hosted_channel
) - data:
- [
chain_hash
:chain_hash
] - [
u16
:len
] - [
len*byte
:refund_scriptpubkey
] - [
u16
:len
] - [
len*byte
:secret
]
The refund_scriptpubkey
is similar to scriptpubkey
in bolt02
and must be provided in case HOST wants to terminate the channel but cannot contact the
CLIENT.
The secret
is an optional blob that can be used by HOST to tweak channel parameters
in multiple ways: offer an initial balance, tweak the capacity, only allow cliets with
a given secret etc.
Upon receiving an invoke_hosted_channel
message, the HOST, if willing to establish the
channel, must reply with an init_hosted_channel
message. If the CLIENT already has a
channel established, the HOST must instead reply with a last_cross_signed_state
.
This is sent by the HOST after a new CLIENT requests a channel using the
invoke_hosted_channel
message.
- type: 65533 (
init_hosted_channel
) - data:
- [
u64
:max_htlc_value_in_flight_msat
] - [
u64
:htlc_minimum_msat
] - [
u16
:max_accepted_htlcs
] - [
u64
:channel_capacity_msat
] - [
u64
:initial_client_balance_msat
] - [
u16
:len
] - [
len*byte
:features
]
It contains the parameters of the hosted channel offered from HOST to CLIENT.
This message essentially represents the state of the channel and the only piece of data that must be stored by peers. It is different for each peer or rather each peer sees the reverse of what the other peer sees, but it includes a signature by each peer of the other peer's view, thus the name "cross-signed".
- type: 65531 (
last_cross_signed_state
) - data:
- [
bool
:is_host
] - [
u16
:len
] - [
len*byte
:last_refund_scriptpubkey
] - [
init_hosted_channel
:init_hosted_channel
] - [
u32
:block_day
] - [
u64
:local_balance_msat
] - [
u64
:remote_balance_msat
] - [
u32
:local_updates
] - [
u32
:remote_updates
] - [
u16
:num_incoming_htlcs
] - [
num_incoming_htlcs
*update_add_htlc
:incoming_htlcs
] - [
u16
:num_outgoing_htlcs
] - [
num_outgoing_htlcs
*update_add_htlc
:outgoing_htlcs
] - [
signature
:remote_sig_of_local
] - [
signature
:local_sig_of_remote
]
The is_host
property must be set to either true
or false
depending on the side
that is signing it.
The block_day
is the current Bitcoin block height divided by 144 and rounded down.
The last_refund_scriptpubkey
is the value of refund_scriptpubkey
received in the
last invoke_hosted_channel
sent by CLIENT.
local_updates
and remote_updates
are the number of updates each party has added to
the channel over the time, these numbers increase every time a peer sends an
update_add_htlc
, update_fail_htlc
, update_fail_malformed_htlc
or update_fulfill_htlc
.
Upon receiving this message the CLIENT must check the signatures and proceed as follows:
- if the number of
local_updates
+remote_updates
is smaller than what they have stored, the CLIENT must ignore the message and send their ownlast_cross_signed_state
. In this case the HOST must accept the state as sent by the peer, reverse it and override their own state with the state received from CLIENT. - if the number of updates is the same that means CLIENT and HOST are on agreement.
The CLIENT must still sent their
last_cross_signed_state
so the HOST will acknowledge that and the channel can become "active". - if the number of updates is greater than what the CLIENT has stored, they must
reverse it and replace their local state with that, then send their local updated
last_cross_signed_state
to HOST so they can acknowledge that. From the point of view of the HOST there is no difference between cases 2 and 3.
If the signatures are bad the peer must send an error
and put
the channel in an "errored" state until it can be fixed manually.
To reverse a last_cross_signed_state
means to see it as the channel peer would, i.e.
change is_host
from true
to false
and vice-versa; to set local_updates
to
remote_updates
and vice-versa; to to set local_balance_msat
to remote_balance_msat
and vice-versa, to set incoming_htlcs
to outgoing_htlcs
and vice-versa and finally
(although this part is not relevant when signing a state_update
) to set
local_sig_of_remote
to remote_sig_of_local
and vice-versa.
After receiving an init_hosted_channel
the CLIENT should check if it likes its params,
then send this message.
Upon receiving it, the HOST will then send its own state_update
and from the two
state_update
s
combined both parties should be able to construct their last_cross_signed_state
independently, which are the effective channel "state" that both parties must store and
keep track of.
- type: 65529 (
state_update
) - data:
- [
u32
:block_day
] - [
u32
:local_updates
] - [
u32
:remote_updates
] - [
signature
:local_sig_of_remote
]
The initial value for local_updates
and remote_updates
is 0
.
The local_sig_of_remote
is a signature over the reverse of the current value for
last_cross_signed_state
known to the signer, i.e. the peer's view of the channel
state. The calculation of the hash to be signed can be seen in this piece of source code.
The normal channel operation consists in peers offering the default bolt02
"update messages" to each other, update_add_htlc
, update_fail_htlc
,
update_fail_malformed_htlc
and update_fulfill_htlc
, then following them with a
state_update
representing what will be the next state of the channel after the updates
are accepted by the peer.
The peer must respond with their own state_update
after applying the received update
to their local state view.
There is no distinction between HOST and CLIENT here.
+-------+ +-------+
| |--(1)---- update_add_htlc ---->| |
| |--(2)---- update_add_htlc ---->| |
| |--(3)---- state_update ------->| |
| |<-(4)---- state_update --------| |
| | | |
| A |<-(5)-- update_fulfill_htlc ---| B |
| |<-(6)-- state_update ----------| |
| |--(7)-- state_update --------->| |
| | | |
| |<-(8)--- update_fail_htlc -----| |
| |<-(9)--- state_update ---------| |
| |-(10)--- state_update -------->| |
+-------+ +-------+
To construct the state_update
message, for each update sent or received, the peers must
increase the local_updates
or remote_updates
count of their next (still uncommitted,
unsigned) state (last_cross_signed_state
), change the balances accordingly, then sign the
reverse of that.
state_update
messages with local_updates
or remote_updates
counts smaller than the
current view of a peer's next state must be ignored, and only when their counts and signature
matches a the receiver's current view of the next state that state can be considered
committed, i.e. cross-signed.
This is the same update_add_htlc
message from bolt01,
but using the type 63505
.
This is the same update_fulfill_htlc
message from bolt01,
but using the type 63503
.
This is the same update_fail_htlc
message from bolt01,
but using the type 63501
.
This is the same update_fail_malformed_htlc
message from bolt01,
but using the type 63499
.
This is the same error
message from bolt02, but using the type 63497
.
If a peer receives a preimage from a payment sent upstream, but can't contact their hosted
peer to send the update_fulfill_htlc
and effectively update the state, they can opt to
publish the preimage to the Bitcoin chain in an OP_RETURN
output. The output scriptPubKey
must take the form of either OP_RETURN <32-byte-preimage>
or OP_RETURN <32-byte-preimage> <32-byte-preimage>
(for publishing two preimages in the same output). Peers should also
monitor the blockchain in search for preimages that may have been published for their
in-flight HTLCs, as the presence of these preimages onchain effectively proves the payment,
even if the peer hasn't sent an update_fulfill_htlc
update through the normal means.
If an HTLC that is incoming through a hosted channel times out and there is no sight of its
preimage anywhere, it is considered failed, and the peer who is holding that must send an
error
and put the channel in an "errored" state until it can be
fixed manually.
When the state is "errored" it must not accept new updates except update_fail_htlc
and
update_fulfill_htlc
for non-expired pending HTLCs, as good HTLCs may still be resolved
peacefully independent of the conflict caused by others.
After a channel reaches an "errored" state peers can agree through extra-protocol means to
reactivate it under a certain balance distribution. Once that happens the HOST must send
an state_override
message and the CLIENT must accept it.
- type: 65527 (
state_override
) - data:
- [
u32
:block_day
] - [
u32
:local_updates
] - [
u32
:remote_updates
] - [
signature
:local_sig_of_remote
]
After receiving a state_override
, at any point a CLIENT may send a state_update
that
matches the last_cross_signed_state
implied by the state_override
parameters. That will
put the channel back in an active state.
Channels can be "branded", i.e. they can have a URL for human contact, a color and an image logo.
Upon establishing a connection to a HOST, a CLIENT can at anytime send a ask_branding_info
message and a HOST may or may not reply with a hosted_channel_branding
message.
- type: 65511 (
ask_branding_info
) - data:
- [
chain_hash
:chain_hash
]
- type: 65525 (
hosted_channel_branding
) - data:
- [
3*byte
:rgb_color
] - [
bool
:has_png
] - [
if has_png: u16
:len
] - [
len*byte
:png_icon
] - [
u16
:len
] - [
len*byte
:contact_info
]
The png_icon
must have at most 65535 bytes, and contact_info
must be a valid URL.
The protocol for hosted channels was created based on real-world experience maintaining the first standalone Lightning mobile wallet, BLW, and improved upon the initial version of the protocol that was deployed on that in 2019.
It introduces some new messages with numbers picked from the end of the range of available numbers and a set of messages related to HTLC adding and removing that are exactly like their BOLT equivalents, but wrapped so they don't interfere with the normal protocol and can be implemented on plugins that allow custom messages to be sent and received from nodes like Eclair, CLN and LND.
Most nodes are probably not interested in using hosted channels, but that is fine since for the protocol to be useful we only ever need two parties that are interested in establishing such a channel to support it, and they connect to each other directly.
This does not have backwards compatibility concerns.
- Simple Bitcoin Wallet (client): https://github.com/btcontract/wallet
- Eclair Plugin (host): https://github.com/btcontract/plugin-hosted-channels
- IMMORTAN (client): https://github.com/fiatjaf/immortan
- Poncho (host): https://github.com/fiatjaf/poncho
- scoin (types and codecs): https://github.com/fiatjaf/scoin