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 (e.g. 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. The 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]

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 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   L3nonce
     \   /                             \   /                       \   /
      v v                               v v                         v v
L1A=H("LnBranch",L1||L1nonce) L2A=H("LnBranch",L2nonce||L2)  L3A=H("LnBranch",L3nonce||L3)

Assume L1A < L2A:

       L1A   L2A                                 L3A=H("LnBranch",L3nonce||L3)
         \   /                                    |
          v v                                     v
  L1A2A=H("LnBranch",L1A||L2A)                   L3A=H("LnBranch",L3nonce||L3)

Assume L1A2A > L3A:

  L1A2A=H("LnBranch",L1A||L2A)          L3A
                          \            /
                           v          v
                Root=H("LnBranch",L3A||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: 30 (node_id)
    22. data:
      • [point32:node_id]
    23. type: 54 (send_invoice)
    24. type: 34 (refund_for)
    25. data:
      • [sha256:refunded_payment_hash]
    26. type: 240 (signature)
    27. data:
      • [bip340sig:sig]
  3. subtype: blinded_path

  4. data:

Requirements For Offers

A writer of an offer:

A reader of an offer:

Rationale

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: 3 (chain)
    2. data:
      • [chain_hash:chain]
    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: 38 (payer_key)
    12. data:
      • [point32:key]
    13. type: 39 (payer_note)
    14. data:
      • [...*utf8:note]
    15. type: 50 (payer_info)
    16. data:
      • [...*byte:blob]
    17. type: 56 (replace_invoice)
    18. data:
      • [sha256:payment_hash]
    19. type: 240 (payer_signature)
    20. data:
      • [bip340sig:sig]

Requirements for Invoice Requests

The writer of an invoice_request:

The reader of an invoice_request:

Rationale

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 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: 3 (chain)
    2. data:
      • [chain_hash:chain]
    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: 38 (payer_key)
    26. data:
      • [point32:key]
    27. type: 39 (payer_note)
    28. data:
      • [...*utf8:note]
    29. type: 50 (payer_info)
    30. data:
      • [...*byte:blob]
    31. type: 40 (created_at)
    32. data:
      • [tu64:timestamp]
    33. type: 42 (payment_hash)
    34. data:
      • [sha256:payment_hash]
    35. type: 44 (relative_expiry)
    36. data:
      • [tu32:seconds_from_creation]
    37. type: 46 (cltv)
    38. data:
      • [tu32:min_final_cltv_expiry]
    39. type: 48 (fallbacks)
    40. data:
      • [byte:num]
      • [num*fallback_address:fallbacks]
    41. type: 52 (refund_signature)
    42. data:
      • [bip340sig:payer_signature]
    43. type: 56 (replace_invoice)
    44. data:
      • [sha256:payment_hash]
    45. type: 240 (signature)
    46. 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. Note that such caching is optional, and should be carefully limited when e.g. currency conversion is involved, or if the invoice has expired.

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 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 allowed 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. Streaming invoices?

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