BOLT #12: Flexible Protocol for Lightning Payments

Table of Contents

Limitations of BOLT 11

The BOLT 11 invoice format has proven popular, but has several limitations:

  1. The entangling of bech32 encoding makes it awkward to send in other forms (i.e. inside the lightning network itself).
  2. The signature applying to the entire invoice makes it impossible to prove an invoice without revealing its entirety.
  3. Fields cannot generally be extracted for external use: the h field was a boutique extraction of the d field, only.
  4. The lack of 'it's OK to be odd' rule makes backwards compatibility harder.
  5. The 'human-readable' idea of separating amounts proved fraught: p was often mishandled, and amounts in pico-bitcoin are harder than the modern satoshi-based counting.
  6. The bech32 encoding was found to have an issue with extensions, which means we want to replace or discard it anyway.
  7. The payment_secret designed to prevent probing by other nodes in the path was only useful if the invoice remained private between the payer and payee.
  8. Invoices must be given per-user, and are actively dangerous if two payment attempts are made for the same user.

Payment Flow Scenarios

Here we use "user" as shorthand for the individual user's lightning node, and "merchant" as the shorthand for the node of someone who is selling or has sold something.

There are two basic payment flows supported by BOLT 12:

The general user-pays-merchant flow is:

  1. A merchant publishes an offer ("send me money"), such as on a web page or a QR code.
  2. Every user requests a unique invoice over the lightning network using an invoice_request message.
  3. The merchant replies with the invoice.
  4. The user makes a payment to the merchant indicated by the invoice.

The merchant-pays-user flow (e.g. ATM or refund):

  1. The merchant provides a user-specific offer ("take my money") in a web page or QR code, with an amount (for a refund, also a reference to the to-be-refunded invoice).
  2. A user sends an invoice for the amount in the offer (for a refund, a proof that they requested the original)
  3. The merchant makes a payment to the user indicated by the invoice.

Payment Proofs and Payer Proofs

Note that the normal lightning "proof of payment" can only demonstrate that an invoice was paid (by showing the preimage of the payment_hash), not who paid it. The merchant can claim an invoice was paid, and once revealed, anyone can claim they paid the invoice, too.[1]

  1. Sharing the minimum information required to prove sections of the invoice in dispute (e.g. the description of the item, the payment hash, and the merchant's signature).
  2. Contain a transferable proof that the user is the one who was responsible for paying the invoice in the first place.

Providing a key in invoice_request allows a user to prove that they were the one to request the invoice. In addition, the Merkle construction of the BOLT 12 invoice signature allows the user to selectively reveal fields of the invoice in case of dispute.

Encoding

Each of the forms documented here are in TLV format.

The supported ASCII encoding is the human-readable prefix, followed by a 1, followed by a bech32-style data string of the TLVs in order, optionally interspersed with + (for indicating additional data is to come).

Requirements

Readers of a bolt12 string:

Rationale

The use of bech32 is arbitrary, but already exists in the bitcoin world. We currently omit the six-character trailing checksum: QR codes have their own checksums, bech32 doesn't protect against many length differences, and bech32m is not yet widely supported.

The use of + (which is ignored) allows use over limited text fields like Twitter:

lno1xxxxxxxx+

yyyyyyyyyyyy+

zzzzz

See format-string-test.json.

Signature Calculation

All signatures are created as per BIP-340, and tagged as recommended there. Thus we define H(tag,msg) as SHA256(SHA256(tag) || SHA256(tag) || msg), and SIG(tag,msg,key) as the signature of H(tag,msg) using key.

Each form is signed using one or more TLV signature elements; TLV types 240 through 1000 are considered signature elements. For these the tag is lightning | messagename | fieldname, and msg is the Merkle-root; lightning is the literal 9-byte ASCII string, messagename is the name of the TLV stream being signed (i.e. offer, invoice_request or invoice) and the fieldname is the TLV field containing the signature (e.g. signature or payer_signature).

The formulation of the Merkle tree is similar to that proposed in [BIP-taproot], with the each TLV leaf paired with a nonce leaf to avoid revealing adjacent nodes in proofs (assuming there is a non-revealed TLV which has enough entropy).

The Merkle tree's leaves are, in TLV-ascending order for each tlv:

  1. The H(LnLeaf,tlv).
  2. The H(LnAll|all-tlvs,tlv) where "all-tlvs" consists of all non-signature TLV entries appended in ascending order.

The Merkle tree inner nodes are H(LnBranch, lesser-SHA256|greater-SHA256); this ordering means that proofs are more compact since left/right is inherently determined.

If there are not exactly a power of 2 leaves, then the tree depth will be uneven, with the deepest tree on the lowest-order leaves.

e.g. consider the encoding of an offer signature with TLVs TLV1, TLV2 and TLV3:

L1=H(`LnLeaf`,TLV1)
L1nonce=H(`LnAll`|TLV1|TLV2|TLV3,TLV1) 
L2=H(`LnLeaf`,TLV2)
L2nonce=H(`LnAll`|TLV1|TLV2|TLV3,TLV2) 
L3=H(`LnLeaf`,TLV3)
L3nonce=H(`LnAll`|TLV1|TLV2|TLV3,TLV3) 

Assume L1 < L1nonce, and L2 > L2nonce.

   L1    L1nonce                      L2   L2nonce               L3
     \   /                             \   /                     |
      v v                               v v                      v
L1A=H('LnBranch',L1|L1nonce) L2A=H('LnBranch',L2nonce|L2)       L3

Assume L1A < L2A:

       L1A   L2A                                 L3
         \   /                                    |
          v v                                     v
  L1A2A=H('LnBranch',L1A|L2A)                    L3

Assume L1A2A > L3:

  L1A2A=H('LnBranch',L1A|L2A)          L3
                          \            /
                           v          v
                Root=H('LnBranch',L3|L1A2A)

Signature = SIG('lightningoffersignature', Root, nodekey)

Offers

Offers are a precursor to an invoice: readers will either request an invoice (or multiple) or send an invoice based on the offer. An offer can be much longer-lived than a particular invoice, so has some different characteristics; in particular it can be recurring, and the amount can be in a non-lightning currency. It's also designed for compactness, to easily fit inside a QR code.

The human-readable prefix for offers is lno.

TLV Fields for Offers

  1. tlv_stream: offer
  2. types:

    1. type: 2 (chains)
    2. data:
      • [...*chain_hash:chains]
    3. type: 6 (currency)
    4. data:
      • [...*utf8:iso4217]
    5. type: 8 (amount)
    6. data:
      • [tu64:amount]
    7. type: 10 (description)
    8. data:
      • [...*utf8:description]
    9. type: 12 (features)
    10. data:
      • [...*byte:features]
    11. type: 14 (absolute_expiry)
    12. data:
      • [tu64:seconds_from_epoch]
    13. type: 16 (paths)
    14. data:
      • [...*blinded_path:paths]
    15. type: 20 (issuer)
    16. data:
      • [...*utf8:issuer]
    17. type: 22 (quantity_min)
    18. data:
      • [tu64:min]
    19. type: 24 (quantity_max)
    20. data:
      • [tu64:max]
    21. type: 26 (recurrence)
    22. data:
      • [byte:time_unit]
      • [tu32:period]
    23. type: 64 (recurrence_paywindow)
    24. data:
      • [u32:seconds_before]
      • [byte:proportional_amount]
      • [tu32:seconds_after]
    25. type: 66 (recurrence_limit)
    26. data:
      • [tu32:max_period]
    27. type: 28 (recurrence_base)
    28. data:
      • [byte:start_any_period]
      • [tu64:basetime]
    29. type: 30 (node_id)
    30. data:
      • [point32:node_id]
    31. type: 54 (send_invoice)
    32. type: 34 (refund_for)
    33. data:
      • [sha256:refunded_payment_hash]
    34. type: 240 (signature)
    35. data:
      • [bip340sig:sig]
  3. subtype: blinded_path

  4. data:

Recurrence

Some offers are periodic, such as a subscription service or monthly dues, in that payment is expected to be repeated. There are many different flavors of repetition, consider:

Thus, each payment has:

  1. A time_unit defining 0 (seconds), 1 (days), 2 (months), 3 (years).
  2. A period, defining how often (in time_unit) it has to be paid.
  3. An optional recurrence_limit of total payments to be paid.
  4. An optional recurrence_base:
  5. An optional recurrence_paywindow:

Note that the absolute_expiry field already covers the case where an offer is no longer valid after January 1st 2021.

Offer Period Calculation

Each period has a zero-based index, and a start time and an end time. Because the periods can be in non-seconds units, the duration of a period can depend on when it starts. The period with index N+1 begins immediately following the end of period with index N.

To calculate the start of period #N for N > 0:

Note that offset seconds can overflow only if the period start is in a leap second; we ignore this!

See offer-period-test.json.

Authorization

Authorization is generally required for payments: without some indication what someone intended to pay for and how much they intended to pay, proof of payment is pointless.

Normally this is simple: get the user to authorize the exact amount and description before paying an invoice. With recurrence this becomes more complex, as an implementation probably does not want to prompt the user on every payment, but receive some initial authorization to spend within a range (e.g. "Pay $5 AUD once a week?"). In particular, the authorization may be in the user's native currency, not the issuer's currency nor in Bitcoin.

For example, consider an offer with weekly recurrence (time_unit=1, period=7), amount 500, currency AUD ($5 Australian dollars). An implementation may present this to the user as USD $3.53 (max $3.71), to allow up to 5% exchange slippage, and receive their authorization. As it received each invoice, it would convert the msat into USD to check that it was below the maximum authorization of USD$3.71. If it was, it would simply pay the invoice without user interaction.

On the other hand, if an invoice did exceed the authorization, it would request re-authorization. It could also indicate whether it was due to AUD/USD changes (since the offer indicated that was the currency it was using) or a disagreement on the bitcoin exchange rate.

Note that the problem is simpler for non-recurring offers, where authorization may simply be delayed until the invoice is received and the exact amount is known.

Also, the implementation of a trusted exchange rate service is left to the reader.

Requirements For Offers

A writer of an offer:

A reader of an offer:

Rationale

It's quite reasonable to set a recurrence_paywindow with seconds_after equal to 0, but obviously this should not apply to the initial period if there is no recurrence_base.

A signature is optional, because it makes for a longer string (potentially limiting QR code use on low-end cameras); if the offer has an error, no invoice will be given (or, for send_invoice offers, accepted), since the offer_id already covers all the non-signature fields.

Invoice Requests

Invoice Requests are a request for an invoice; the human-readable prefix for invoices is lnr.

TLV Fields for invoice_request

  1. tlv_stream: invoice_request
  2. types:
    1. type: 2 (chains)
    2. data:
      • [...*chain_hash:chains]
    3. type: 4 (offer_id)
    4. data:
      • [sha256:offer_id]
    5. type: 8 (amount)
    6. data:
      • [tu64:msat]
    7. type: 12 (features)
    8. data:
      • [...*byte:features]
    9. type: 32 (quantity)
    10. data:
      • [tu64:quantity]
    11. type: 36 (recurrence_counter)
    12. data:
      • [tu32:counter]
    13. type: 68 (recurrence_start)
    14. data:
      • [tu32:period_offset]
    15. type: 38 (payer_key)
    16. data:
      • [point32:key]
    17. type: 39 (payer_note)
    18. data:
      • [...*utf8:note]
    19. type: 50 (payer_info)
    20. data:
      • [...*byte:blob]
    21. type: 56 (replace_invoice)
    22. data:
      • [sha256:payment_hash]
    23. type: 240 (payer_signature)
    24. data:
      • [bip340sig:sig]

Requirements for Invoice Requests

The writer of an invoice_request:

The reader of an invoice_request:

Rationale

We insist that recurring requests be in order (thus, if you pay an invoice for #34 of a recurring offer, it implicitly commits to the successful payment of #0 through #33).

The recurrence_paywindow constrains how far you can pay in advance precisely, and if it isn't in the offer the defaults provide some slack, without allowing commitments into the far future.

To avoid probing (should a payer_key become public in some way), we require a signature; this ensures that no third party can determine how many invoices have been paid already in the case of recurring requests, and disallows replacement of old invoices by third parties.

payer_info might typically contain information about the derivation of the payer_key. This should not leak any information (such as using a simple BIP-32 derivation path); a valid system might be for a node to maintain a base payer key, and encode a 128-bit tweak here. The payer_key would be derived by tweaking the base key with SHA256(payer_base_pubkey || tweak).

payer_note allows you to compliment, taunt, or otherwise engrave graffiti into the invoice for all to see.

Users can give a tip (or obscure the amount sent) by specifying an amount in their invoice request, even though the offer specifies an amount. Obviously this will only be accepted by the recipient if the invoice request amount exceeds the amount it's expecting (i.e. its amount after any currency conversion, multiplied by quantity if any). Note that for recurring invoices with proportional_amount set, the amount in the invoice request will be scaled by the time in the period; the sender should not attempt to scale it.

replace_invoice allows the mutually-agreed removal of and old unpaid invoice; this can be used in the case of stuck payments. If successful in replacing the stuck invoice, the sender may make a second payment such that it can prove double-payment should the receiver still accept the first, delayed payment.

Invoices

Invoices are a request for payment, and when the payment is made they it can be combined with the invoice to form a cryptographic receipt.

The human-readable prefix for invoices is lni. It can be sent in response to an invoice_request or an offer with send_invoice using onion_message invoice field.

  1. tlv_stream: invoice
  2. types:

    1. type: 2 (chains)
    2. data:
      • [...*chain_hash:chains]
    3. type: 4 (offer_id)
    4. data:
      • [sha256:offer_id]
    5. type: 8 (amount)
    6. data:
      • [tu64:msat]
    7. type: 10 (description)
    8. data:
      • [...*utf8:description]
    9. type: 12 (features)
    10. data:
      • [...*byte:features]
    11. type: 16 (paths)
    12. data:
      • [...*blinded_path:paths]
    13. type: 18 (blindedpay)
    14. data:
      • [...*blinded_payinfo:payinfo]
    15. type: 19 (blinded_capacities)
    16. data:
      • [...*u64:incoming_msat]
    17. type: 20 (issuer)
    18. data:
      • [...*utf8:issuer]
    19. type: 30 (node_id)
    20. data:
      • [point32:node_id]
    21. type: 32 (quantity)
    22. data:
      • [tu64:quantity]
    23. type: 34 (refund_for)
    24. data:
      • [sha256:refunded_payment_hash]
    25. type: 36 (recurrence_counter)
    26. data:
      • [tu32:counter]
    27. type: 54 (send_invoice)
    28. type: 68 (recurrence_start)
    29. data:
      • [tu32:period_offset]
    30. type: 64 (recurrence_basetime)
    31. data:
      • [tu64:basetime]
    32. type: 38 (payer_key)
    33. data:
      • [point32:key]
    34. type: 39 (payer_note)
    35. data:
      • [...*utf8:note]
    36. type: 50 (payer_info)
    37. data:
      • [...*byte:blob]
    38. type: 40 (created_at)
    39. data:
      • [tu64:timestamp]
    40. type: 42 (payment_hash)
    41. data:
      • [sha256:payment_hash]
    42. type: 44 (relative_expiry)
    43. data:
      • [tu32:seconds_from_creation]
    44. type: 46 (cltv)
    45. data:
      • [tu32:min_final_cltv_expiry]
    46. type: 48 (fallbacks)
    47. data:
      • [byte:num]
      • [num*fallback_address:fallbacks]
    48. type: 52 (refund_signature)
    49. data:
      • [bip340sig:payer_signature]
    50. type: 56 (replace_invoice)
    51. data:
      • [sha256:payment_hash]
    52. type: 240 (signature)
    53. data:
      • [bip340sig:sig]
  3. subtype: blinded_payinfo

  4. data:

  5. subtype: fallback_address

  6. data:

Requirements

A writer of an invoice:

A reader of an invoice:

Rationale

Because the messaging layer is unreliable, it's quite possible to receive multiple requests for the same offer. As it's the caller's responsibility not to reuse payer_key except for recurring invoices, the writer doesn't have to check all the fields are duplicates before simply returning a previous invoice.

The invoice duplicates fields rather than committing to the previous offer or invoice_request. This flattened format simplifies storage at some space cost, as the payer need only remember the invoice for any refunds or proof.

The recurrence_basetime similarly enables calculation of the next period without having to refer to the initial invoice (in the case where the offer does not contain recurrence_base.

The reader of the invoice cannot trust the invoice correctly reflects the offer and invoice_request fields, hence the requirements to check that they are correct.

Note that the recipient of the invoice can determine the expected amount from either the offer it received, or the invoice_request it sent, so often already has authorization for the expected amount.

It's natural to set the relative_expiry of an invoice for a recurring offer to the end of the payment window for the period, but if that is a long time and the offer was in another currency, it's common to cap this at some maximum duration. For example, omitting it implies the default of 7200 seconds, which is generally a sufficient time for payment.

The invoice issuer is allows to ignore payer_note (it has an odd number, so is optional), but if it does not, it must copy it exactly as the invoice_request specified.

It's often useful to provide capacity hints, particularly where more than one blinded path is included, for payers to use multi-part payments.

Invoice Errors

Informative errors can be returned in an onion message invoice_error field (via the onion reply_path) for either invoice_request or invoice.

TLV Fields for invoice_error

  1. tlv_stream: invoice_error
  2. types:
    1. type: 1 (erroneous_field)
    2. data:
      • [tu64:tlv_fieldnum]
    3. type: 3 (suggested_value)
    4. data:
      • [...*byte:value]
    5. type: 5 (error)
    6. data:
      • [...*utf8:msg]

Requirements

A writer of an invoice_error:

A reader of an invoice_error: FIXME!

Rationale

Usually an error message is sufficient for diagnostics, however there is at least one case where it should be programatically parsable. A recurring offer which sets send_invoice can also specify a currency, which opens the possibility for a disagreement on exchange rate. In this case, the suggested_value reflects its expected value, and the sender can send a new invoice.

FIXME: Possible future extensions:

  1. The offer can require delivery info in the invoice_request.
  2. An offer can be updated: the response to an invoice_request is another offer, perhaps with a signature from the original node_id
  3. Any empty TLV fields can mean the value is supposed to be known by other means (i.e. transport-specific), but is still hashed for sig.
  4. We could upgrade to allow multiple offers in one invoice_request and invoice, to make a shopping list.
  5. All-zero offer_id == gratuitous payment.
  6. Recurrent invoice requests?

[1] https://www.youtube.com/watch?v=4SYc_flMnMQ