Let's consider the following setup:
- E creates a blinded path C->D->E
- E computes a
cltv_expiry_delta
for the blinded path:- E starts by adding the
cltv_expiry_delta
of every hop - E sets
cltv_expiry_delta = 10
in theencrypted_recipient_data
for C - E sets
cltv_expiry_delta = 15
in theencrypted_recipient_data
for D - E adds its
min_final_expiry_delta
(let's use5
) cltv_expiry_delta = 10 + 15 + 5 = 30
- E starts by adding the
blinded_path: expiry_delta = 30
+---------------------------------------------------------------+
+---+ +---+ expiry_delta = 20 | +---+ expiry_delta = 10 +---+ expiry_delta = 15 +---+ |
| A |---------| B |-----------------------| C |-----------------------| D |-----------------------| E | |
+---+ +---+ | +---+ +---+ +---+ |
+---------------------------------------------------------------+
A wants to pay E using that blinded path, and finds a path to C through B.
A computes the cltv_expiry
for each HTLC:
- She chooses a random number of blocks to add for privacy:
sender_delta = 15
- In the onion for E:
- sets
outgoing_cltv_value = block_height_when_sending_payment + sender_delta
- sets
- In the onion for D (blinded):
- does not set
outgoing_cltv_value
- sets
encrypted_recipient_data
provided by the recipient for D (e.g. in a Bolt 12 invoice)
- does not set
- In the onion for C (introduction node of the blinded path):
- does not set
outgoing_cltv_value
- sets
encrypted_recipient_data
provided by the recipient for C (e.g. in a Bolt 12 invoice)
- does not set
- In the onion for B (not blinded):
- sets
outgoing_cltv_value = block_height_when_sending_payment + 15 + blinded_path_expiry_delta = block_height_when_sending_payment + 45
- sets
- In the HTLC for B:
- sets
cltv_expiry = block_height_when_sending_payment + 45 + bc_expiry_delta = block_height_when_sending_payment + 65
- sets
A sends the HTLC to B (honest scenario):
- B receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 65
:- B verifies that the onion's
outgoing_cltv_value
is valid for its configuredcltv_expiry_delta
- B forwards the HTLC to C with
cltv_expiry = block_height_when_sending_payment + 45
- B verifies that the onion's
- C receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 45
:- the onion does not contain
outgoing_cltv_value
- but the
encrypted_recipient_data
specifiescltv_expiry_delta = 10
- C forwards the HTLC to D with
cltv_expiry = block_height_when_sending_payment + 35
- the onion does not contain
- D receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 35
- the onion does not contain
outgoing_cltv_value
- but the
encrypted_recipient_data
specifiescltv_expiry_delta = 15
- D forwards the HTLC to E with
cltv_expiry = block_height_when_sending_payment + 20
- the onion does not contain
- E receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 20
- the onion contains
outgoing_cltv_value = block_height_when_sending_payment + 20
- E verifies that
cltv_expiry >= outgoing_cltv_value
-> OK - E verifies that
cltv_expiry >= current_block_height + min_final_cltv_expiry_delta
:- note that the
current_block_height
may be different fromblock_height_when_sending_payment
- the inequality is then
block_height_when_sending_payment + sender_delta >= current_block_height
- which is fine as long as A's
sender_delta
covers the block height difference
- note that the
- the onion contains
If C uses a lower cltv_expiry_delta
than requested by E, this should be allowed:
- C receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 45
:- the
encrypted_recipient_data
specifiescltv_expiry_delta = 10
- C should forward the HTLC to D with
cltv_expiry = block_height_when_sending_payment + 35
- C instead forwards the HTLC to D with
cltv_expiry = block_height_when_sending_payment + 40
- the
- D receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 40
- the
encrypted_recipient_data
specifiescltv_expiry_delta = 15
- D forwards the HTLC to E with
cltv_expiry = block_height_when_sending_payment + 25
- the
- E receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 25
- the onion contains
outgoing_cltv_value = block_height_when_sending_payment + 20
- E verifies that
cltv_expiry >= outgoing_cltv_value
-> OK - E verifies that
cltv_expiry >= current_block_height + min_final_cltv_expiry_delta
:- note that the
current_block_height
may be different fromblock_height_when_sending_payment
- the inequality is then
block_height_when_sending_payment + 20 >= current_block_height
-> OK
- note that the
- the onion contains
If C uses a higher cltv_expiry_delta
than requested by E, this is a cheating attempt:
- C receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 45
:- the
encrypted_recipient_data
specifiescltv_expiry_delta = 10
- C should forward the HTLC to D with
cltv_expiry = block_height_when_sending_payment + 35
- C instead forwards the HTLC to D with
cltv_expiry = block_height_when_sending_payment + 30
- the
- D receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 30
- the
encrypted_recipient_data
specifiescltv_expiry_delta = 15
- D forwards the HTLC to E with
cltv_expiry = block_height_when_sending_payment + 15
- the
- E receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 15
- the onion contains
outgoing_cltv_value = block_height_when_sending_payment + 20
- E verifies that
cltv_expiry >= outgoing_cltv_value
-> NOT OK - E verifies that
cltv_expiry >= current_block_height + min_final_cltv_expiry_delta
-> OK - E rejects the payment
- the onion contains
We don't need to detail the cases where other intermediate nodes try to cheat: the effect is exactly the same.
If A user a higher cltv_expiry_delta
for the blinded path, this should be allowed:
- A creates the HTLC to B:
- In the onion for E, A sets
outgoing_cltv_value = block_height_when_sending_payment
- A uses
cltv_expiry_delta = 40
instead of30
for the blinded path - In the onion for B, A sets
outgoing_cltv_value = block_height_when_sending_payment + 40
- In the HTLC for B, A sets
cltv_expiry = block_height_when_sending_payment + 40 + bc_expiry_delta = block_height_when_sending_payment + 60
- In the onion for E, A sets
- B receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 60
:- B forwards the HTLC to C with
cltv_expiry = block_height_when_sending_payment + 40
- B forwards the HTLC to C with
- C receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 40
:- the onion does not contain
outgoing_cltv_value
- but the
encrypted_recipient_data
specifiescltv_expiry_delta = 10
- C forwards the HTLC to D with
cltv_expiry = block_height_when_sending_payment + 30
- the onion does not contain
- D receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 30
- the onion does not contain
outgoing_cltv_value
- but the
encrypted_recipient_data
specifiescltv_expiry_delta = 15
- D forwards the HTLC to E with
cltv_expiry = block_height_when_sending_payment + 15
- the onion does not contain
- E receives the HTLC with
cltv_expiry = block_height_when_sending_payment + 15
- the onion contains
outgoing_cltv_value = block_height_when_sending_payment
- E verifies that
cltv_expiry >= outgoing_cltv_value
-> OK - E verifies that
cltv_expiry >= current_block_height + min_final_cltv_expiry_delta
-> OK
- the onion contains
If A uses a lower cltv_expiry_delta
for the blinded path, this is a cheating attempt:
- A creates the HTLC to B:
- In the onion for E, A sets
outgoing_cltv_value = block_height_when_sending_payment + sender_delta
- A uses
cltv_expiry_delta = 25
instead of30
for the blinded path - In the onion for B, A sets
outgoing_cltv_value = block_height_when_sending_payment + sender_delta + 25
- In the HTLC for B, A sets
cltv_expiry = block_height_when_sending_payment + sender_delta + 25 + bc_expiry_delta = block_height_when_sending_payment + sender_delta + 45
- In the onion for E, A sets
- B receives the HTLC with
cltv_expiry = block_height_when_sending_payment + sender_delta + 45
:- B forwards the HTLC to C with
cltv_expiry = block_height_when_sending_payment + sender_delta + 25
- B forwards the HTLC to C with
- C receives the HTLC with
cltv_expiry = block_height_when_sending_payment + sender_delta + 25
:- the onion does not contain
outgoing_cltv_value
- but the
encrypted_recipient_data
specifiescltv_expiry_delta = 10
- C forwards the HTLC to D with
cltv_expiry = block_height_when_sending_payment + sender_delta + 15
- the onion does not contain
- D receives the HTLC with
cltv_expiry = block_height_when_sending_payment + sender_delta + 15
- the onion does not contain
outgoing_cltv_value
- but the
encrypted_recipient_data
specifiescltv_expiry_delta = 15
- D forwards the HTLC to E with
cltv_expiry = block_height_when_sending_payment + sender_delta
- the onion does not contain
- E receives the HTLC with
cltv_expiry = block_height_when_sending_payment + sender_delta
- the onion contains
outgoing_cltv_value = block_height_when_sending_payment + sender_delta
- E verifies that
cltv_expiry >= outgoing_cltv_value
-> OK - E verifies that
cltv_expiry >= current_block_height + min_final_cltv_expiry_delta
:- which is equivalent to
sender_delta >= current_block_height - block_height_when_sending_payment + min_final_cltv_expiry_delta
- this lets A discover the
min_final_cltv_expiry_delta
used by E, but this isn't a private information anyway
- which is equivalent to
- the onion contains