Let's consider the following setup:
- E creates a blinded path C->D->E
- E computes a
cltv_expiry_deltafor the blinded path:- E starts by adding the
cltv_expiry_deltaof every hop - E sets
cltv_expiry_delta = 10in theencrypted_recipient_datafor C - E sets
cltv_expiry_delta = 15in theencrypted_recipient_datafor 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_dataprovided 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_dataprovided 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_valueis 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_dataspecifiescltv_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_dataspecifiescltv_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_heightmay 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_deltacovers 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_dataspecifiescltv_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_dataspecifiescltv_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_heightmay 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_dataspecifiescltv_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_dataspecifiescltv_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 = 40instead of30for 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_dataspecifiescltv_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_dataspecifiescltv_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 = 25instead of30for 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_dataspecifiescltv_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_dataspecifiescltv_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_deltaused by E, but this isn't a private information anyway
- which is equivalent to
- the onion contains