Authors:
- Conner Fromknecht conner@lightning.engineering
- Olaoluwa Osuntokun roasbeef@lightning.engineering
Created: 2021-03-04
This document proposes a standard for multi-path Lightning payments where HTLC preimages are generated sender-side.
While simple, the construction directly lends itself to spontaneous payments, removing the need for a setup phase to exchange an invoice. It also enables other of useful applications where a one-time setup can be be amortized over an an unbounded number of payments, e.g e.g. static/reusable invoices, subscriptions, and exchange accounts.
Unlike traditional Lightning invoices, the ones generated by this standard can be published without comporomising the security of the payment scheme, enabling long-sought features like public donation invoices.
The proposal also offers improved privacy over existing techniques by completely avoiding payment hash reuse.
s_1 s_2 s_3 SHARES OF ROOT SEED
| | |
└──┐ | ┌──┘ S = s_1 ^ s_2 ^ s_3
V V V
S ROOT SEED
| | |
┌──┘ | └──┐ r_i = DeriveChild(S, s_i, child_index_i)
V V V
r_1 r_2 r_3 CHILD PREIMAGES
| | |
| | | p_i = SHA256(r_i)
V V V
p_1 p_2 p_3 CHILD PAYMENT HASHES
^
denotes bit-wise XOR.||
denotes byte array concatenation.be(x)
returns a byte array containing the the big-endian serialization of the integerx
.
Bits | Name | Description | Context | Dependencies |
---|---|---|---|---|
30/31 | option_amp |
Atomic Multi-Path payments | IN9 | option_mpp |
The ability to send AMP payments is enabled primarily by the addition of a new, required TLV record embedded in the final hop's onion payload:
- tlvs:
tlv_payload
- types:
- type: 10 (
amp_record
) - data:
- [
32*byte
:root_share
] - [
32*byte
:set_id
] - [
tu32
:child_index
]
- [
- type: 10 (
Due to this design, only the sender and receiver need to be running AMP-compatible software. Unlike other proposals, this does not require a network-wide upgrade of the interior routing nodes before it can be deployed in end-user applications.
An AMP HTLC set is defined as the set of all HTLCs carrying the same
(payment_data.payment_secret, amp_record.set_id)
pair.
This definition is intentionally designed as a super set of the defintion of
HTLC sets for basic_mpp
. Conceptually, an HTLC without an amp_record
can be
described using (payment_data.payment_secret, nil)
.
HTLC sets satisfying the form (_, nil)
are classified as MPP HTLC sets and are
subject to the basic_mpp
requirements specified in BOLT 4. Otherwise, the HTLC
should be treated as part of an AMP HTLC set and is subject to the requirements
in this document.
Throughout this document, the term "HTLC set" is assumed to refer to an AMP HTLC set unless otherwise specified.
Input:
- The root seed
S
: a 32-byte array, freshly generated uniformly at random - A root share
s
: a 32-byte array, an N-of-N additive sharing ofS
- A child index
c
: 32-bit unsigned integer
Output:
- A child preimage
r
: a 32-byte array - A child hash
p
: a 32-byte array, the SHA256 image ofr
The algorithm DeriveChild(S, s, c)
is defined as:
- Compute
r = SHA256(S || s || be(c))
. - Compute
p = SHA256(r)
. - Return
(r, p)
.
The inputs to the SHA256 invocation creating the child preimage are justified as follows:
- Committing to the root seed
S
enforces that the recipient must learn the reconstructed seed in order to derive the preimage for any of the HTLCs in a given HTLC set. - For a given root seed, commiting to the root share
s
ensures that each child preimage in the set is unique with high probabilty. By extension, the child hash of each HTLC in the set will also be unique, which prevents intermediaries from trivially correlating HTLCs in the same HTLC due to payment hash reuse. - If the child preimage depends only on
(S, s)
, then payment hash reuse is still posssible across retries, i.e. when an HTLC for same amount gets sent along a different path. The inclusion of a child indexc
allows the sender can rerandomize an HTLC's payment hash without needing to further split the root shares
and payment amount into additional sub-payments.
Should the derivation commit to the set_id
? This would effectively remove any
potential for payment hash reuse, even if the same S
value is somehow shared
between HTLC sets.
A sender of amp_record
:
- MUST NOT include
amp_record
for any non-final node. - if the final node does not signal support for
option_amp
in theI
orN
feature bit contexts:- MUST NOT include
amp_record
for the final node.
- MUST NOT include
- otherwise:
- MAY include
amp_record
for the final node. - if it does include an
amp_record
:- MUST also include a
payment_data
record. - if paying to an invoice:
- MUST use the
payment_secret
provided in the invoice.
- MUST use the
- otherwise:
- MUST generate a uniformly random
payment_secret
to be used for the HTLC set.
- MUST generate a uniformly random
- if paying to an invoice with non-zero
amount
:- MUST set
payment_data.total_msat
equal toamount
.
- MUST set
- otherwise:
- MUST set
payment_data.total_msat
equal to the amount it wishes to pay.
- MUST set
- MUST generate a unique
set_id
to identify the HTLC set. - Each HTLC in the HTLC set:
- MUST use the same
payment_data.payment_secret
. - MUST use the same
payment_data.total_msat
. - MUST use the same
amp_record.set_id
.
- MUST use the same
- MUST generate a uniformly random root seed
S
to be used for the HTLC set. - MUST generate a random
amp_record.child_index
. - For all unfailed HTLCs in the HTLC set:
- Let
s_i
be theamp_record.root_share
of the i-th HTLC. - Let
c_i
be theamp_record.child_index
of the i-th HTLC. - Let
(_, p_i) = DeriveChild(S, s_i, c_i)
- MUST set the payment hash of i-th HTLC to
p_i
. - MUST ensure that the
s_1 ^ ... ^ s_n = S
. - MUST ensure the sum of the HTLC amounts equals
payment_data.total_msat
.
- Let
- MUST also include a
- MAY include
If the sender detects that an HTLC in an AMP HTLC set fails:
- MAY abort the payment.
- otherwise, if the sender wants to retry the HTLC along a different route:
- MUST re-randomize the
amp_record.child_index
.
- MUST re-randomize the
- otherwise, when sender wants to split the failed HTLC into two or more sub-HTLCs.
- MUST generate the new HTLCs such that they satisfy the sending requiements above.
The most difficult task in splitting a failed HTLC is with regards to ensuring
the root share S
is not ultimately invalidated. This can be done using the
algorithm presented in Recursive Splitting of Root
Shares.
There are many possible strategies in which the sender may choose to satisfy the
requirement that, for a given S
, the amp_record.root_share
values in the
HTLC set always XOR to S
. Implementers are of course free to choose shares as
they see fit, so long as S
reconstructs properly and, most importantly, S
cannot be recovered knowing only a subset of the HTLC set.
Several alternative algorithms were considered, however, the one presented as chosen as the recommended strategy due to the following useful properties:
- The final number of HTLCs does not need to be known a priori, nor does it ever need to be explicitly committed to by the sender. This permits the sender to adaptively discover the number of required HTLCs for the payment.
- There is no "distinguished" HTLC, i.e. an HTLC that cannot be further split or must be sent in a paritcular order.
- Splitting an HTLC does not require shared state amongst the other HTLCs, which greatly simplifies the resulting implementation.
Implementers should be mindful of these contraints when selecting alternative strategies, and analyze the tradeoffs in terms complexity as well as potential restrictions placed on the sender's ability to adapatively send AMP HTLCs.
Input:
- A root share
s
: a 32-byte array
Output:
- A root share
l
: a 32-byte array, satisfyingl ^ r = s
- A root share
r
: a 32-byte array, satisfyingl ^ r = s
The algorithm SplitShare(s)
is defined as:
- Generate
l
uniformly at random. - Compute
r = l ^ s
. - Return
(l, r)
.
Assume that n
active HTLCs exist in a given AMP HTLC set, and that each HTLC
carries an amp_data.root_share
value of s_i
.
The root seed of the HTLC set is computed as:
s_1 ^ ... ^ s_n = S (1)
WLOG, assume that HTLC carrying s_n
fails. The sender splits the share as:
(l, r) = SplitShare(s_n)
and replaces the HTLC containing s_n
with two new HTLCs containing l
and r
as their amp_record.root_share
.
The root seed for the new HTLC set (now of cardinatlity n+1
) is computed as:
s_1 ^ ... ^ s_n-1 ^ l ^ r = s_1 ^ ... ^ s_n-1 ^ (l ^ r)
= s_1 ^ ... ^ s_n-1 ^ (l ^ (l ^ s_n))
= s_1 ^ ... ^ s_n-1 ^ s_n
= S by (1)
It follows that SplitShare
can also be applied recursively to its own outputs
without invalidating S
. This allows the sender to split any of the HTLCs in
the HTLC set as necessary in order to satisfy any liquidity constraints in the
network.
For the base case we have simply that s_1 = S
, meaning the root seed is sent
directly when the HTLC set consists of only one HTLC.
A receiver of amp_record
:
- MUST fail the HTLC with
invalid_onion_payload
if it is not the final node. - MUST fail the HTLC with
invalid_onion_payload
if thepayment_data
record is not also present. - if an invoice is known containining
payment_secret
equal topayment_data.payment_secret
:- if the invoice does not advertise
option_amp
:- MUST fail the HTLC with
incorrect_payment_details
.
- MUST fail the HTLC with
- if the invoice does not advertise
- otherwise:
- MAY create and store an invoice that:
- has
payment_secret
equal topayment_data.payment_secret
, and - has
amount
equal topayment_data.total_msat
, and - advertises support for
option_amp
and all transitive feature dependencies.
- has
- MAY create and store an invoice that:
- Let "the HTLC set" be defined as all known, unfailed HTLCs containing the
same
payment_data.payment_secret
andamp_record.set_id
values. - MUST fail the HTLC if
payment_data.total_msat
is not the same for all HTLCs in the HTLC set. - MUST add the HTLC to the HTLC set.
- Let
total
be the sum of the HTLCs in the HTLC set. - if
total > payment_data.total_msat
:- MUST fail the HTLC with
incorrect_payment_detials
.
- MUST fail the HTLC with
- otherwise, if
total = payment_data.total_msat
:- For each HTLC in the HTLC set:
- Let
s_i
be the i-th HTLC'samp_record.root_share
.
- Let
- Compute the root seed
S = s_1 ^ ... ^ s_n
. - For each HTLC in the HTLC set:
- Let
c_i
be the i-th HTLC'samp_record.child_index
. - Compute
(r_i, p_i) = DeriveChild(S, s_i, c_i)
.
- Let
- if any
p_i
does not match the i-th HTLC's payment hash:- MUST fail the HTLC set with
incorrect_payment_details
.
- MUST fail the HTLC set with
- otherwise:
- MAY fulfill the i-th HTLC in the HTLC set using
r_i
. - MUST fulfill the entire HTLC set if it fulfills any HTLC in the HTLC set.
- MAY fulfill the i-th HTLC in the HTLC set using
- For each HTLC in the HTLC set:
- otherwise, when
total < payment_data.total_msat
:- SHOULD wait for more HTLCs in the set to arrive.
- MUST fail the HTLC set with
mpp_timeout
after some reasonable timeout.- SHOULD wait for at least 60 seconds after the initial HTLC.