← Back to Basis of Lightning Technology
BOLT 12specificationlightningpaymentsevents

BOLT 12: Negotiation Protocol for Lightning Payments

# Table of Contents * [Limitations of BOLT 11](#limitations-of-bolt-11) * [Payment Flow Scenarios](#payment-flow-scenarios) * [Encoding](#encoding) * [Signature calculation](#signature-calculation) * [Offers](#offers) * [Invoice Requests](#invoice-requests) * [Invoices](#invoices) * [Invoice Errors](#invoice-errors)

No reviews
Unknown·Updated Mar 28, 2026·0 reviews·0 attestations·View source
Collections:BOLTs — Merged

Specification

BOLT #12: Negotiation 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 the 'it's OK to be odd' rule makes backward 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. Developers found the bech32 encoding 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, 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, which contains the offer fields.
  3. The merchant replies with the invoice.
  4. The user makes a payment to the merchant as indicated by the invoice.

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

  1. The merchant publishes an invoice_request which includes an amount it wishes to send to the user.
  2. The user sends an invoice over the lightning network for the amount in the invoice_request, using a (possibly temporary) invoice_node_id.
  3. The merchant confirms the invoice_node_id to ensure it's about to pay the correct person, and makes a payment to 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 the payer 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 reveal invoice fields in case of a dispute selectively.

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). There is no checksum, unlike bech32m.

Requirements

Writers of a bolt12 string:

  • MUST either use all lowercase or all UPPERCASE.
  • SHOULD use uppercase for QR codes.
  • SHOULD use lower case otherwise.
  • MAY use +, optionally followed by whitespace, to separate large bolt12 strings.

Readers of a bolt12 string:

  • MUST handle strings which are all lowercase, or all uppercase.
  • if it encounters a + followed by zero or more whitespace characters between two bech32 characters:
    • MUST remove the + and whitespace.

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 anyway, and errors don't result in loss of funds, simply an invalid offer (or inability to parse).

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 signature TLV elements: TLV types 240 through 1000 (inclusive). 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. "invoice_request" or "invoice") and the fieldname is the TLV field containing the signature (e.g. "signature").

The formulation of the Merkle tree is similar to that proposed in BIP-341, with each TLV leaf paired with a nonce leaf to avoid revealing adjacent nodes in proofs.

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

  1. The H("LnLeaf",tlv).
  2. The H("LnNonce"||first-tlv,tlv-type) where first-tlv is the numerically-first TLV entry in the stream, and tlv-type is the "type" field (1-9 bytes) of the current tlv.

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

If there is 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 invoice signature with TLVs TLV0, TLV1, and TLV2 (of types 0, 1, and 2 respectively):

L1=H("LnLeaf",TLV0)
L1nonce=H("LnNonce"||TLV0,0)
L2=H("LnLeaf",TLV1)
L2nonce=H("LnNonce"||TLV0,1)
L3=H("LnLeaf",TLV2)
L3nonce=H("LnNonce"||TLV0,2)

Assume L1 < L1nonce, L2 > L2nonce and L3 > L3nonce.

   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("lightninginvoicesignature", Root, nodekey)

Offers

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

Note that the non-signature TLV elements get mirrored into invoice_request and invoice messages, so they each have specific and distinct TLV ranges.

The human-readable prefix for offers is lno.

TLV Fields for Offers

  1. tlv_stream: offer
  2. types:
    1. type: 2 (offer_chains)
    2. data:
      • [...*chain_hash:chains]
    3. type: 4 (offer_metadata)
    4. data:
      • [...*byte:data]
    5. type: 6 (offer_currency)
    6. data:
      • [...*utf8:iso4217]
    7. type: 8 (offer_amount)
    8. data:
      • [tu64:amount]
    9. type: 10 (offer_description)
    10. data:
      • [...*utf8:description]
    11. type: 12 (offer_features)
    12. data:
      • [...*byte:features]
    13. type: 14 (offer_absolute_expiry)
    14. data:
      • [tu64:seconds_from_epoch]
    15. type: 16 (offer_paths)
    16. data:
      • [...*blinded_path:paths]
    17. type: 18 (offer_issuer)
    18. data:
      • [...*utf8:issuer]
    19. type: 20 (offer_quantity_max)
    20. data:
      • [tu64:max]
    21. type: 22 (offer_issuer_id)
    22. data:
      • [point:id]

Requirements For Offers

A writer of an offer:

  • MUST NOT set any TLV fields outside the inclusive ranges: 1 to 79 and 1000000000 to 1999999999.
  • if the chain for the invoice is not solely bitcoin:
    • MUST specify offer_chains the offer is valid for.
  • otherwise:
    • SHOULD omit offer_chains, implying that bitcoin is only chain.
  • if a specific minimum offer_amount is required for successful payment:
    • MUST set offer_amount to the amount expected (per item).
    • MUST set offer_amount greater than zero.
    • if the currency for offer_amount is that of all entries in chains:
      • MUST specify offer_amount in multiples of the minimum lightning-payable unit (e.g. milli-satoshis for bitcoin).
    • otherwise:
      • MUST specify offer_currency iso4217 as an ISO 4217 three-letter code.
      • MUST specify offer_amount in the currency unit adjusted by the ISO 4217 exponent (e.g. USD cents).
    • MUST set offer_description to a complete description of the purpose of the payment.
  • otherwise:
    • MUST NOT set offer_amount
    • MUST NOT set offer_currency
    • MAY set offer_description
  • MAY set offer_metadata for its own use.
  • if it supports bolt12 offer features:
    • MUST set offer_features.features to the bitmap of bolt12 features.
  • if the offer expires:
    • MUST set offer_absolute_expiry seconds_from_epoch to the number of seconds after midnight 1 January 1970, UTC that invoice_request should not be attempted.
  • if it is connected only by private channels:
    • MUST include offer_paths containing one or more paths to the node from publicly reachable nodes.
  • otherwise:
    • MAY include offer_paths.
  • if it includes offer_paths:
    • MAY set offer_issuer_id.
  • otherwise:
    • MUST set offer_issuer_id to the node's public key to request the invoice from.
  • if it sets offer_issuer:
    • SHOULD set it to identify the issuer of the invoice clearly.
    • if it includes a domain name:
      • SHOULD begin it with either user@domain or domain
      • MAY follow with a space and more text
  • if it can supply more than one item for a single invoice:
    • if the maximum quantity is known:
      • MUST set that maximum in offer_quantity_max.
      • MUST NOT set offer_quantity_max to 0.
    • otherwise:
      • MUST set offer_quantity_max to 0.
  • otherwise:
    • MUST NOT set offer_quantity_max.

A reader of an offer:

  • if the offer contains any TLV fields outside the inclusive ranges: 1 to 79 and 1000000000 to 1999999999:
    • MUST NOT respond to the offer.
  • if offer_features contains unknown odd bits that are non-zero:
    • MUST ignore the bit.
  • if offer_features contains unknown even bits that are non-zero:
    • MUST NOT respond to the offer.
    • SHOULD indicate the unknown bit to the user.
  • if offer_chains is not set:
    • if the node does not accept bitcoin invoices:
      • MUST NOT respond to the offer
  • otherwise: (offer_chains is set):
    • if the node does not accept invoices for at least one of the chains:
      • MUST NOT respond to the offer
  • if offer_amount is set and offer_description is not set:
    • MUST NOT respond to the offer.
  • if offer_amount is set and is not greater than zero:
    • MUST NOT respond to the offer.
  • if offer_currency is set and offer_amount is not set:
    • MUST NOT respond to the offer.
  • if neither offer_issuer_id nor offer_paths are set:
    • MUST NOT respond to the offer.
  • if num_hops is 0 in any blinded_path in offer_paths:
    • MUST NOT respond to the offer.
  • if it uses offer_amount to provide the user with a cost estimate:
    • MUST take into account the currency units for offer_amount:
      • offer_currency field if set
      • otherwise, the minimum lightning-payable unit (e.g. milli-satoshis for bitcoin).
    • MUST warn the user if the received invoice_amount differs significantly from that estimate.
  • if the current time is after offer_absolute_expiry:
    • MUST NOT respond to the offer.
  • if it chooses to send an invoice request, it sends an onion message:
    • if offer_paths is set:
      • MUST send the onion message via any path in offer_paths to the final onion_msg_hop.blinded_node_id in that path
    • otherwise:
      • MUST send the onion message to offer_issuer_id
    • MAY send more than one invoice request onion message at once.

Rationale

The entire offer is reflected in the invoice_request, both for completeness (so all information will be returned in the invoice), and so that the offer node can be stateless. This makes offer_metadata particularly useful, since it can contain an authentication cookie to validate the other fields.

Because offer fields are copied into the invoice request (and then the invoice), they require distinct ranges. The range 1-79 is the normal range, with another billion for self-assigning experimental ranges.

A signature is unnecessary, and 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 since the request includes all the non-signature fields.

The offer_issuer_id can be omitted for brevity, if offer_paths is set, as each of the final blinded_node_id in the paths can serve as a valid public key for the destination.

Because offer_amount can be in a different currency (using the offer_currency field) it is merely a guide: the issuer will convert it into a number of millisatoshis for invoice_amount at the time they generate an invoice, or the invoice request can specify the exact amount in invreq_amount, but the issuer may then reject it if it disagrees.

offer_quantity_max is allowed to be 1, which seems useless, but useful in a system which bases it on available stock. It would be painful to have to special-case the "only one left" offer generation.

Offers can be used to simply send money without expecting anything in return (tips, kudos, donations, etc), which means the description field is optional (the offer_issuer field is very useful for this case!); if you are

[Content truncatedview full spec at source]

Discussion (0 threads)

No discussion yet. Be the first to comment.