Fiat channels is a derived concept from original Hosted channels, or HC, proposal by Anton Kumaigoroski. Channels of this type were initially implemented in Bitcoin Lightning Wallet and since then continued in new Simple Bitcoin Wallet and IMMORTAN library.
Notable differences to the original Hosted channels RFC:
-
It adds special field into channel state for tracking last exchange rate. Thus, amount of satoshis in the channel and the last rate corresponding to last cross-signed state represent constant fiat-denominated value.
-
Fiat channels leverage resizing feature of the HC which helps to tackle sharp downward volatility of Bitcoin exchange rate and preserve purchasing power of the channel user.
For the sake of convenience original RFC is mostly preserved.
-
Hosted channel is always private and can never be closed in normal channel sense, its data may only be removed from Client wallet if there is no desire to use it anymore. Once removed, it can always be fully restored later provided that Client still has a wallet seed.
-
Since hosted channels have no on-chain basis their
shortChannelIds can not be derived from blockchain, as such they must be random and follow procedure defined in #681. -
Hosted channel ID is deterministic and is always known in advance for every Client/Host pair, it is derived as
sha256(bip69_lexicographical_order(Client node ID, Host node ID)). This implies that each Client/Host pair may only have one hosted channel between them. -
A notion of blockday is introduced as
current blockchain height / 144to be used in messages specific to hosted channels, the intention is to ground them in real blockchain timeline.
-
The Host determines the fiat rate. The current rate of the hosted channel can be changed only when user sends or receivies sats.
-
At the creation of the channel with
init_hosted_channelthe Host sets initial rate of the channel. -
The Host adjusts fiat rate of the channel with
ratefield in thestate_updatemessage. The Host should ignore the rate ofstate_updatethat arrived from the Client.
There are three scenarios when the Host adjust the fiat rate of the channel.
The scenario occurs when update_add_htlc message is sent from the Host to the Client. The new rate is calculated as average weighted summ of the old rate and new one:
newRate = round((old_balance * old_rate + payment_size * new_rate) / (old_balance + payment_size))
Note that old_rate and new_rate are reverse rates USD/BTC from the state_update and current state rates (they are msat/USD). Host places the new rate value inside
the state_update message, signs it and sends it to the Client. The Client restores remote state with the new channel rate, signs it and proceed with their own state_update
message as usual.
TODO
TODO
-
The peer feature flag for the Fiat Hosted Channel is
52972, we also imply that the channel is resizeable (feature flag52974). -
Hosted channel must be explicitly invoked by Client on reconnection which is different from normal channel reestablish procedure, the rationale is that Client may have normal channels with Host and may not wish to use a hosted one. Once invoked, Host should reply with either
init_hosted_channelmessage if no channel exists yet or withlast_cross_signed_stateif it does.+-------+ +-------+ | |--(1)------ invoke_hosted_channel -------->| | | |<-(2)------ init_hosted_channel -----------| | | |--(3)------ state_update ----------------->| | | |<-(4)------ state_update ------------------| | // new channel established | | or | | | A |<-(2)------ last_cross_signed_state -------| B | | |--(3)------ last_cross_signed_state ------>| | // existing channel invoked +-------+ +-------+ - where node A is Client and node B is Host
- type: 75535 (
invoke_hosted_channel) - data:
- [
chain_hash:chain_hash] - [
u16:len] - [
len*byte:refund_scriptpubkey] - [
u16:len] - [
len*byte:secret]
-
By sending this message Client prompts Host to reveal last cross signed channel state or offer a new hosted channel if none exists for a given Client yet.
-
refund_scriptpubkeyis similar toscriptpubkeyin 02-peer-protocol#closing-initiation-shutdown and must be provided by Client for a case when Host would want to stop a hosted channel and refund the rest of the balance while Client is offline. -
secretis an optional data which can be used by Host to tweak channel parameters (non-zero initial Client balance, larger capacity, only allow Clients with secrets etc).
- type: 75533 (
init_hosted_channel) - data:
- [
u64:max_htlc_value_in_flight_msat] - [
u64:htlc_minimum_msat] - [
u16:max_accepted_htlcs] - [
u64:channel_capacity_msat] - [
u16:liability_deadline_blockdays] - [
u64:minimal_onchain_refund_amount_satoshis] - [
u64:initial_client_balance_msat] - [
u64:initial_rate] - [
u16:len] - [
len*byte:features]
-
This message is sent by Host in reply to
invoke_hosted_channelif no hosted channel exists for a given Client yet. -
liability_deadline_blockdaysspecifies a period in blockdays after laststate_updateexchange during which the Host is definitely going to maintain a channel. That is, if there are no payments duringliability_deadline_blockdaysperiod then Host owes nothing to Client anymore. For example: if last exchangedstate_updatehadblockdayset to 1000 andliability_deadline_blockdaysis set to 2000, then Host may not maintain a channel after blockday 3000 if it was not used all this time. -
minimal_onchain_refund_amount_satoshisspecifies a minimal balance that Client must have in a hosted channel for a Host to consider refunding it on-chain using Client'srefund_scriptpubkey. -
featureshere work similarly to nodefeaturesfield inInitmessage. While establishing a new hosted channel a node must drop it if unknown even features are present.
- type: 75531 (
last_cross_signed_state) - data:
- [
u16:len] - [
len*byte:last_refund_scriptpubkey] - [
init_hosted_channel:init_hosted_channel] - [
u32:block_day] - [
u64:local_balance_msat] - [
u64:remote_balance_msat] - [
u64:rate] - [
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]
-
This message is sent by Host in reply to
invoke_hosted_channelif it already exists for a given Client, once sent Host expects a similarlast_cross_signed_statemessage from Client. -
Both parties must make sure that
remote_sig_of_localandlocal_sig_of_remotesignatures are valid, and that invertedlocal_updates/remote_updatesnumbers from remotelast_cross_signed_stateare the same aslocal_updates/remote_updatesfrom locallast_cross_signed_state. -
If local signature of remote
last_cross_signed_stateis valid but invertedlocal_updates/remote_updatesnumbers from remotelast_cross_signed_stateare higher thanlocal_updates/remote_updatesfrom locallast_cross_signed_statethen this means that local peer has fallen behind (or completely lost a state). Once this happens local peer must act as follows:- First, it must check if remote
last_cross_signed_statepoints to one of future channel states which can be re-created locally. To enable this both peers store each local and remoteupdate_add_htlc,update_fulfill_htlc,update_fail_htlc,update_fail_malformed_htlcmessage they send or receive in historic order until newlast_cross_signed_stateis reached. This vector of updates must be traversed with respected locallast_cross_signed_statemessage created on each step and then compared against remotelast_cross_signed_stateupdate numbers. Once a match is found local peer applies it as current state, resolves all updates preceding this state and re-transmits all local updates following this state. - Second, if no matching future local
last_cross_signed_statecould be found then this means that local peer has completely lost it, in this case local peer should invert a remotelast_cross_signed_stateand apply it as its current state, then resolve all incoming HTLCs contained in remotelast_cross_signed_state.
- First, it must check if remote
-
Local state signature is obtained by creating a sigHash from localy generated
last_cross_signed_state:sha256(refund_scriptpubkey + liability_deadline_blockdays + minimal_onchain_refund_amount_satoshis + channel_capacity_msat + initial_client_balance_msat + block_day + local_balance_msat + remote_balance_msat + local_updates + remote_updates + incoming_htlcs + outgoing_htlcs)and then signing it withnodeIdprivate key.
-
First a peer sends
update_add_htlc/update_fulfill_htlc/update_fail_htlc/update_fail_malformed_htlcmessages which are then followed by localstate_updatemessage, which is then followed by remotestate_update. Once both validstate_updates are collected a new cross-signed state is reached which in turn allows peers to resolve previous updates.+-------+ +-------+ | |----------- update_add_htlc #1 ------------>| | | |----------- state_update (is_terminal=0) -->| | // A: here's my state update, now you have a new cross-signed state | |<---------- state_update (is_terminal=1) ---| | // B: got your update, anything else incoming? | |----------- state_update (is_terminal=1) -->| | // A: no other updates for now | |<---------- state_update (is_terminal=1) ---| | // B: OK, resolving | A |<---------- update_fulfill_htlc #1 ---------| B | | |<---------- state_update (is_terminal=0) ---| | | |----------- state_update (is_terminal=1) -->| | | |<---------- state_update (is_terminal=1) ---| | | |----------- state_update (is_terminal=1) -->| | +-------+ +-------+ Where B is Host and A is Client A more involved case with entwined updates, a diagram takes into account that it takes time for messages to get through, but message order is always preserved +-------+ +-------+ | 1, 0 |----------> update_add_htlc #A1 | 0, 0 | | 1, 0 |----------> state_update #A1 (is_terminal=0) | 0, 0 | // A inverts local state and signs B's `0, 1`, by not making it terminal A instructs B to not resolve an HTLC even if signature is correct but send an acking `state_update` in return | 1, 0 | update_add_htlc #B1 <------------------| 1, 0 | | 1, 0 | state_update #B1 (is_terminal=0) <-----| 1, 0 | // B inverts local state and signs A's `0, 1`, by not making it terminal B instructs A to not resolve an HTLC even if signature is correct but send an acking `state_update` in return | 1, 0 | | 1, 0 | | 1, 0 | update_add_htlc #A1 ------------------>| 1, 1 | | 1, 0 | state_update #A1 (is_terminal=0) ----->| 1, 1 | // A's `state_update` signs B's `1, 0` state, but B has `1, 1` by the time it arrives | 1, 1 |<---------- update_add_htlc #B1 | 1, 1 | | 1, 1 |<---------- state_update #B1 (is_terminal=0) | 1, 1 | // B's `state_update` signs A's `1, 0` state, but A has `1, 1` by the time it arrives | 1, 1 | state_update #B2 (is_terminal=1) <-----| 1, 1 | // B replies with a new `state_update` which signs `1, 1` this time | 1, 1 |----------> state_update #A2 (is_terminal=1) | 1, 1 | // A replies with a new `state_update` which signs `1, 1` this time | 1, 1 |<---------- state_update #B2 (is_terminal=1) | 1, 1 | // A gets a new `state_update` which correctly signs `1, 1` and is terminal, B starts resolving A's HTLC | 1, 1 | state_update #A2 (is_terminal=1) ----->| 1, 1 | // B gets a new `state_update` which correctly signs `1, 1` and is terminal, A starts resolving B's HTLC | | | | | A | | B | | | | | +-------+ +-------+ Where B is Host and A is Client
- type: 75529 (
state_update) - data:
- [
u32:block_day] - [
u32:local_updates] - [
u32:remote_updates] - [
u64:rate] - [
signature:local_sig_of_remote] - [
boolean:is_terminal]
-
Local
state_updatecontains a local signature of remote view of nextlast_cross_signed_state. Signature is constructed as follows: generate a next locallast_cross_signed_statewith empty signature fields, then invert it (i.e. make a copy withlocal_balance_msat=remote_balance_msat,local_updates=remote_updatesand so on), then sign its signature hash. -
In normal channels each peer keeps track of
local_next_htlc_id/remote_next_htlc_idcounters which are increased by incoming and outgoingupdate_add_htlconly, in hosted channels each peer keeps track oflocal_updates/remote_updatescounters which are updated by every incoming and outgoing update message (update_add_htlc,update_fulfill_htlc,update_fail_htlc,update_fail_malformed_htlc). -
rateis Millisats/USD value. The field is used by client to reconstruct next crossigned state when the message recieved from the host. Host should ignore the field from the client. See theFiat ratesection for more details. -
While verifying a signature a drift of 1 blockday is permitted i.e.
abs(local block_day - remote block_day) <= 1. -
is_terminalis a flag which must always be set to0after sending localupdate_add_htlc/update_fulfill_htlc/update_fail_htlc/update_fail_malformed_htlcmessages and always be set to1when replying to remotestate_update. By using this convention peers can determine if remote peer is sending more updates or this is it for now and resolving should be started.
Normal channel operation may be interrupted in a number of ways (incorrect state update numbers, signature, timed out outgoing HTLC etc). Once this happens a peer must put a channel in SUSPENDED state and send out an Error message. Hosted channel use tagged errors with first two bytes of Error message reserved for the following cases:
0001: Wrong blockday in a remote message.
0002: Wrong local signature from remote message.
0003: Wrong remote signature from remote message.
0005: Too many state_update messages without reaching of new local last_cross_signed_state (more than 16 in a row).
0006: Timed out outgoing HTLC.
0007: Remote peer has lost all upstream channels and can't resolve in-flight routed HTLCs.
0008: Hosted channel denied by Host when Client was trying to invoke it.
Normal operation may be resumed after channel gets SUSPENDED by Host sending a state_override message to Client which would erase all previous problematic state and set a new agreed upon balance breakdown. Client must manually accept this message which would send a state_update in return. Client's wallet UI/UX must be especially explicit about what is going on in this situation.
+-------+ +-------+
| |<---------- state_override ----------------| | // A has new local `last_cross_signed_state`
| A |----------- state_update ----------------->| B | // B has new local `last_cross_signed_state`
+-------+ +-------+
Where B is Host and A is Client
- type: 75527 (
state_override) - data:
- [
u32:block_day] - [
u64:local_balance_msat] - [
u32:local_updates] - [
u32:remote_updates] - [
u64:rate] - [
signature:local_sig_of_remote]
When resolving disputes after a channel got SUSPENDED it may be necessary for a Client to manually provide a channel state snapshot which contains last cross signed state along with subsequent updates from both peers, those may include update_fulfill_htlc messages with preimages which can be used by Host to manually fulfill outstanding HTLCs. Otherwise channel snapshot must not contain any other types of secrets.
- data:
- [
channel_id:channel_id] - [
u16:num_next_local_updates] - [
num_next_local_updates*[update_add_htlc/update_fulfill_htlc/update_fail_htlc/update_fail_malformed_htlc]:next_local_updates] - [
u16:num_next_remote_updates] - [
num_next_remote_updates*[update_add_htlc/update_fulfill_htlc/update_fail_htlc/update_fail_malformed_htlc]:next_remote_updates] - [
last_cross_signed_state:last_cross_signed_state]
This situation is to be resolved in a standard way described in last_cross_signed_state Rationale.
Issue: malicious or offline peer may accept update_add_htlc and a following state_update without ever replying. In this situation sender has a limited time to either fulfill a payment downstream (which would require obtaining a preimage from non-responding peer) or fail it, otherwise sender risks downstream channel getting remotely force-closed.
Solution: similar to how this is handled in normal channels, right before CLTV timelock is about to expire a sender must put a hosted channel into SUSPENDED mode and fail a payment upstream. Hosted channel can be put back to operational mode at a later time by exchaning state_override messages where receiver balance is adjusted such that failed in-flight HTLC is removed.
Issue: on receiving update_fulfill_htlc sender may use a preimage to fulfill a payment upstream and stop responding to receiver. Sender would thus get the funds into its upstream channel while receiver won't have an updated last_cross_signed_state with payment resolved.
Solution: the goal here is not to enforce payment receiving since this is impossible for hosted channels but to cryptographically prove that malicious behaviour has taken place on sender side. This is achieved in a following way:
-
When sender transmits
update_add_htlcto receiver and expectsupdate_fulfill_htlcin return this can only mean that sender already has a committed in-flight payment upstream which can be unconditionally resolved once sender knows a preimage. -
After receiving
update_add_htlcfollowed bystate_updatereceiver has a newlast_cross_signed_statewhere sender has signed an obligation to provideXfunds to receiver if receiver reveals a preimage within nextYblocks (a CLTV expiry delta). -
When having this data along with preimage revealed (
update_fulfill_htlcsent) a sender software must notify an owner if payment is not getting resolved within a reasonable time frame, but well before CLTV deadline (otherwise sender could claim receiver just was not cooperating so sender had to fail a payment on CLTV expiry). Sender is then expected to take action which can range from contacing receiver support to revealing alast_cross_signed_statealong with preimage publically, thus giving sender no chance of denying that active in-flight HTLC exists and respected preimage is revealed. Note that for this to work a final CLTV window set by receiver software should be large enough, at least 285 blocks is recommended.
Issue: After doing that Client may show up with last last_cross_signed_state claiming that funds are still in a hosted channel.
Solution: Host must wait at least 1 blockday until broadcasting an on-chain refunding transaction, in that case it will be included in a block whose blockday is higher than the one contained in Client's last_cross_signed_state. This will prove that refund has happened after any last known channel activity. After refunding this way hosted channel data may be safelly removed from Host database.