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 reviewsSpecification
BOLT #12: Negotiation Protocol for Lightning Payments
Table of Contents
- Limitations of BOLT 11
- Payment Flow Scenarios
- Encoding
- Signature calculation
- Offers
- Invoice Requests
- Invoices
- Invoice Errors
Limitations of BOLT 11
The BOLT 11 invoice format has proven popular but has several limitations:
- The entangling of bech32 encoding makes it awkward to send in other forms (e.g. inside the lightning network itself).
- The signature applying to the entire invoice makes it impossible to prove an invoice without revealing its entirety.
- Fields cannot generally be extracted for external use: the
hfield was a boutique extraction of thedfield only. - The lack of the 'it's OK to be odd' rule makes backward compatibility harder.
- The 'human-readable' idea of separating amounts proved fraught:
pwas often mishandled, and amounts in pico-bitcoin are harder than the modern satoshi-based counting. - Developers found the bech32 encoding to have an issue with extensions, which means we want to replace or discard it anyway.
- The
payment_secretdesigned to prevent probing by other nodes in the path was only useful if the invoice remained private between the payer and payee. - 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:
- A merchant publishes an offer, such as on a web page or a QR code.
- Every user requests a unique invoice over the lightning network using an invoice_request message, which contains the offer fields.
- The merchant replies with the invoice.
- The user makes a payment to the merchant as indicated by the invoice.
The merchant-pays-user flow (e.g. ATM or refund):
- The merchant publishes an invoice_request which includes an amount it wishes to send to the user.
- The user sends an invoice over the lightning network for the amount in the invoice_request, using a (possibly temporary) invoice_node_id.
- 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.
- MUST remove the
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
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:
- The H("LnLeaf",tlv).
- 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
tlv_stream:offer- types:
- type: 2 (
offer_chains) - data:
- [
...*chain_hash:chains]
- [
- type: 4 (
offer_metadata) - data:
- [
...*byte:data]
- [
- type: 6 (
offer_currency) - data:
- [
...*utf8:iso4217]
- [
- type: 8 (
offer_amount) - data:
- [
tu64:amount]
- [
- type: 10 (
offer_description) - data:
- [
...*utf8:description]
- [
- type: 12 (
offer_features) - data:
- [
...*byte:features]
- [
- type: 14 (
offer_absolute_expiry) - data:
- [
tu64:seconds_from_epoch]
- [
- type: 16 (
offer_paths) - data:
- [
...*blinded_path:paths]
- [
- type: 18 (
offer_issuer) - data:
- [
...*utf8:issuer]
- [
- type: 20 (
offer_quantity_max) - data:
- [
tu64:max]
- [
- type: 22 (
offer_issuer_id) - data:
- [
point:id]
- [
- type: 2 (
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_chainsthe offer is valid for.
- MUST specify
- otherwise:
- SHOULD omit
offer_chains, implying that bitcoin is only chain.
- SHOULD omit
- if a specific minimum
offer_amountis required for successful payment:- MUST set
offer_amountto the amount expected (per item). - MUST set
offer_amountgreater than zero. - if the currency for
offer_amountis that of all entries inchains:- MUST specify
offer_amountin multiples of the minimum lightning-payable unit (e.g. milli-satoshis for bitcoin).
- MUST specify
- otherwise:
- MUST specify
offer_currencyiso4217as an ISO 4217 three-letter code. - MUST specify
offer_amountin the currency unit adjusted by the ISO 4217 exponent (e.g. USD cents).
- MUST specify
- MUST set
offer_descriptionto a complete description of the purpose of the payment.
- MUST set
- otherwise:
- MUST NOT set
offer_amount - MUST NOT set
offer_currency - MAY set
offer_description
- MUST NOT set
- MAY set
offer_metadatafor its own use. - if it supports bolt12 offer features:
- MUST set
offer_features.featuresto the bitmap of bolt12 features.
- MUST set
- if the offer expires:
- MUST set
offer_absolute_expiryseconds_from_epochto the number of seconds after midnight 1 January 1970, UTC that invoice_request should not be attempted.
- MUST set
- if it is connected only by private channels:
- MUST include
offer_pathscontaining one or more paths to the node from publicly reachable nodes.
- MUST include
- otherwise:
- MAY include
offer_paths.
- MAY include
- if it includes
offer_paths:- MAY set
offer_issuer_id.
- MAY set
- otherwise:
- MUST set
offer_issuer_idto the node's public key to request the invoice from.
- MUST set
- 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_maxto 0.
- MUST set that maximum in
- otherwise:
- MUST set
offer_quantity_maxto 0.
- MUST set
- if the maximum quantity is known:
- otherwise:
- MUST NOT set
offer_quantity_max.
- MUST NOT set
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_featurescontains unknown odd bits that are non-zero:- MUST ignore the bit.
- if
offer_featurescontains unknown even bits that are non-zero:- MUST NOT respond to the offer.
- SHOULD indicate the unknown bit to the user.
- if
offer_chainsis not set:- if the node does not accept bitcoin invoices:
- MUST NOT respond to the offer
- if the node does not accept bitcoin invoices:
- otherwise: (
offer_chainsis set):- if the node does not accept invoices for at least one of the
chains:- MUST NOT respond to the offer
- if the node does not accept invoices for at least one of the
- if
offer_amountis set andoffer_descriptionis not set:- MUST NOT respond to the offer.
- if
offer_amountis set and is not greater than zero:- MUST NOT respond to the offer.
- if
offer_currencyis set andoffer_amountis not set:- MUST NOT respond to the offer.
- if neither
offer_issuer_idnoroffer_pathsare set:- MUST NOT respond to the offer.
- if
num_hopsis 0 in anyblinded_pathinoffer_paths:- MUST NOT respond to the offer.
- if it uses
offer_amountto provide the user with a cost estimate:- MUST take into account the currency units for
offer_amount:offer_currencyfield if set- otherwise, the minimum lightning-payable unit (e.g. milli-satoshis for bitcoin).
- MUST warn the user if the received
invoice_amountdiffers significantly from that estimate.
- MUST take into account the currency units for
- 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_pathsis set:- MUST send the onion message via any path in
offer_pathsto the finalonion_msg_hop.blinded_node_idin that path
- MUST send the onion message via any path in
- otherwise:
- MUST send the onion message to
offer_issuer_id
- MUST send the onion message to
- MAY send more than one invoice request onion message at once.
- if
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 truncated — view full spec at source]
Discussion (0 threads)
No discussion yet. Be the first to comment.