From 379349570e498dc9f4c7b26be90da6607bb9644e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 27 Aug 2024 17:00:19 +0930 Subject: [PATCH 1/8] BOLT 1: add bip340sig type. Signed-off-by: Rusty Russell --- 01-messaging.md | 1 + 1 file changed, 1 insertion(+) diff --git a/01-messaging.md b/01-messaging.md index 066d7b4da..0732ac466 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -251,6 +251,7 @@ The following convenience types are also defined: * `channel_id`: a 32-byte channel_id (see [BOLT #2](02-peer-protocol.md#definition-of-channel-id)) * `sha256`: a 32-byte SHA2-256 hash * `signature`: a 64-byte bitcoin Elliptic Curve signature +* `bip340sig`: a 64-byte bitcoin Elliptic Curve Schnorr signature as per [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) * `point`: a 33-byte Elliptic Curve point (compressed encoding as per [SEC 1 standard](http://www.secg.org/sec1-v2.pdf#subsubsection.2.3.3)) * `short_channel_id`: an 8 byte value identifying a channel (see [BOLT #7](07-routing-gossip.md#definition-of-short-channel-id)) * `bigsize`: a variable-length, unsigned integer similar to Bitcoin's CompactSize encoding, but big-endian. Described in [BigSize](#appendix-a-bigsize-test-vectors). From a8540254335d5dfdd12d5f1266df770e25120f35 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 27 Aug 2024 17:01:19 +0930 Subject: [PATCH 2/8] BOLT 1: Add utf8 type. It's far easier to validate these on parsing than to hand-validate them elsewhere. I didn't turn `alias` or `error` into this, though they're similar (`alias` can have a nul terminator). Signed-off-by: Rusty Russell --- 01-messaging.md | 1 + 1 file changed, 1 insertion(+) diff --git a/01-messaging.md b/01-messaging.md index 0732ac466..e9a18aa4f 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -255,6 +255,7 @@ The following convenience types are also defined: * `point`: a 33-byte Elliptic Curve point (compressed encoding as per [SEC 1 standard](http://www.secg.org/sec1-v2.pdf#subsubsection.2.3.3)) * `short_channel_id`: an 8 byte value identifying a channel (see [BOLT #7](07-routing-gossip.md#definition-of-short-channel-id)) * `bigsize`: a variable-length, unsigned integer similar to Bitcoin's CompactSize encoding, but big-endian. Described in [BigSize](#appendix-a-bigsize-test-vectors). +* `utf8`: a byte as part of a UTF-8 string. A writer MUST ensure an array of these is a valid UTF-8 string, a reader MAY reject any messages containing an array of these which is not a valid UTF-8 string. ## Setup Messages From 8611f6bfc6e776c061999ef4adf9adfa39cdce94 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 27 Aug 2024 17:02:19 +0930 Subject: [PATCH 3/8] tools/spellcheck.sh: more generally ignore things inside ``. Signed-off-by: Rusty Russell --- tools/spellcheck.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/spellcheck.sh b/tools/spellcheck.sh index 709913db3..8b98b67b8 100755 --- a/tools/spellcheck.sh +++ b/tools/spellcheck.sh @@ -63,8 +63,7 @@ do WORDS=$(sed -e 's/ [lL][nN]\([bB][cC]\|[tT][bB]\)[0-9munpxMUNP]*1[qpzry9x8gf2tvdw0s3jn54khce6mua7lQPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]\+//g' \ -e 's/\]([-#a-zA-Z0-9_.]*)//g' \ -e '/^```/,/^```/d' \ - -e 's/`[a-zA-Z0-9_]*`//g' \ - -e 's/\* \[`[_a-z0-9*]\+`://g' \ + -e 's/`[a-zA-Z0-9_*.(),]*`//g' \ -e 's/0x[a-fA-F0-9 ]\+//g' \ -e 's/[a-fA-F0-9]\{20,\}//g' \ -e 's/^ .*_htlcs//g' \ From c406f1ab039830210e475b15a2af8d970806c896 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 24 Sep 2024 18:00:51 +0930 Subject: [PATCH 4/8] Allow using `short_channel_id` in onion messages Offers may contain blinded paths to allow for greater recipient privacy. However, they come at a cost of increased QR code size as each hop requires a 33-byte `point` for the `next_node_id`. Allow using `short_channel_id` instead, which only requires 8 bytes. Still allow for use of `next_node_id` for cases where the blinded path may not involve channel counterparties or for long-lived offers, which may outlive the given channels. Signed-off-by: Rusty Russell --- 04-onion-routing.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/04-onion-routing.md b/04-onion-routing.md index 87eeebe32..da7bb0cd6 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -245,8 +245,8 @@ leaking its position in the route. The creator of `encrypted_recipient_data` (usually, the recipient of payment): - MUST create `encrypted_data_tlv` for each node in the blinded route (including itself). - - MUST include `encrypted_data_tlv.short_channel_id` and `encrypted_data_tlv.payment_relay` for each non-final node. - - MUST NOT include `encrypted_data_tlv.next_node_id`. + - MUST include `encrypted_data_tlv.payment_relay` for each non-final node. + - MUST include exactly one of `encrypted_data_tlv.short_channel_id` or `encrypted_data_tlv.next_node_id` for each non-final node. - MUST set `encrypted_data_tlv.payment_constraints` for each non-final node and MAY set it for the final node: - `max_cltv_expiry` to the largest block height at which the route is allowed to be used, starting from the final node's chosen `max_cltv_expiry` height at which the route should expire, adding @@ -1516,8 +1516,8 @@ even, of course!). The creator of `encrypted_recipient_data` (usually, the recipient of the onion): - MUST create the `encrypted_recipient_data` from the `encrypted_data_tlv` as required in [Route Blinding](#route-blinding). - - MUST NOT include `short_channel_id`, `payment_relay` or `payment_constraints` in any `encrypted_data_tlv` - - MUST include `encrypted_data_tlv.next_node_id` for each non-final node. + - MUST NOT include `payment_relay` or `payment_constraints` in any `encrypted_data_tlv` + - MUST include either `next_node_id` or `short_channel_id` in the `encrypted_data_tlv` for each non-final node. - MUST create the `encrypted_recipient_data` from the `encrypted_data_tlv` as required in [Route Blinding](#route-blinding). The writer: @@ -1558,7 +1558,13 @@ The reader: - if the `encrypted_data_tlv` contains `path_id`: - MUST ignore the message. - otherwise: - - SHOULD forward the message using `onion_message` to the next peer indicated by `next_node_id`. + - if `next_node_id` is present: + - the *next peer* is the peer with that node id. + - otherwise, if `short_channel_id` is present and corresponds to an announced short_channel_id or a local alias for a channel: + - the *next peer* is the peer at the other end of that channel. + - otherwise: + - MUST ignore the message. + - SHOULD forward the message using `onion_message` to the *next peer*. - if it forwards the message: - MUST set `path_key` in the forwarded `onion_message` to the next `path_key` as calculated in [Route Blinding](#route-blinding). - otherwise (it is the final node): From 8f1a5642a89dec5acb8d945c412beee166d0d98c Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 24 Sep 2024 18:01:04 +0930 Subject: [PATCH 5/8] Add a `sciddir_or_pubkey` fundamental type Offers may contain blinded paths to allow for greater recipient privacy. However, they come at a cost of increased QR code size as the introduction node requires a 33-byte `point`. Define a new `sciddir_or_pubkey` fundamental type such that either a point or a reference to one in a `channel_announcement` can be used. This is backwards compatible with `point`. Use this new type for the `blinded_path` subtype's `first_node_id`. --- 01-messaging.md | 6 ++++++ 04-onion-routing.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/01-messaging.md b/01-messaging.md index e9a18aa4f..0dd5cda91 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -254,6 +254,12 @@ The following convenience types are also defined: * `bip340sig`: a 64-byte bitcoin Elliptic Curve Schnorr signature as per [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) * `point`: a 33-byte Elliptic Curve point (compressed encoding as per [SEC 1 standard](http://www.secg.org/sec1-v2.pdf#subsubsection.2.3.3)) * `short_channel_id`: an 8 byte value identifying a channel (see [BOLT #7](07-routing-gossip.md#definition-of-short-channel-id)) +* `sciddir_or_pubkey`: either 9 or 33 bytes referencing or identifying a node, respectively + * if the first byte is 0 or 1, then an 8-byte `short_channel_id` follows for a total of 9 bytes + * 0 for the first byte indicates this refers to `node_id_1` in the `channel_announcement` for `short_channel_id` + * 1 for the first byte indicates this refers to `node_id_2` in the `channel_announcement` for `short_channel_id` + (see [BOLT #7](07-routing-gossip.md#the-channel_announcement-message) + * if the first byte is 2 or 3, then the value is a 33-byte `point` * `bigsize`: a variable-length, unsigned integer similar to Bitcoin's CompactSize encoding, but big-endian. Described in [BigSize](#appendix-a-bigsize-test-vectors). * `utf8`: a byte as part of a UTF-8 string. A writer MUST ensure an array of these is a valid UTF-8 string, a reader MAY reject any messages containing an array of these which is not a valid UTF-8 string. diff --git a/04-onion-routing.md b/04-onion-routing.md index da7bb0cd6..9be1332e7 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -440,7 +440,7 @@ intermediary nodes could simply claim the remaining ones. 1. subtype: `blinded_path` 2. data: - * [`point`:`first_node_id`] + * [`sciddir_or_pubkey`:`first_node_id`] * [`point`:`first_path_key`] * [`byte`:`num_hops`] * [`num_hops*blinded_path_hop`:`path`] From 47adf181db91349bc640eab4719ed2ae867d95d1 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 24 Sep 2024 18:01:04 +0930 Subject: [PATCH 6/8] BOLT 12: offers, sixth draft A BOLT11 "invoice" has proven too low-level for human use in many scenarios. Efforts like lnurl have covered the gap, but integrating some of such higher layers into the lightning protocol itself has many advantages. This draft defines three new things: 1. A new invoice format. I know, this is painful, but it maps almost 1:1 to the current format (though signatures are very different), is easier to implement, and easier to send via the lightning network itself. 2. Formats for an "offer", which for all intents and purposes serves as the new, persistent invoice for users. 3. Format for an "invoice_request": this is a message sent via the lightning network itself to receive the real invoice, or can be used directly in a send-money scenario (e.g. ATM). The offer (for accepting payments) or invoice_request (for sending payments) are usually presented via a QR code or similar, the replies are sent using onion messages. Each copies fields from the prior so it stands alone, to allow statelessness. Features which have been deliberately omitted for the initial version: - Recurrence. - Invoice replacement ("don't accept that old payment!") - Payer proof for refunds. This effort has been EPIC, and there is absolutely no way I could have done this without the often thankless task of implementing, re-implementing, revising and re-reading this text. In particular I have been delighted to receive the mental boost from the following people: 1. Thomas H of ACINQ (https://github.com/thomash-acinq) 2. Jeffrey Czyz of Square Crypto (https://github.com/jkczyz) 3. Joost Jager (https://github.com/joostjager) 4. Aditya Sharma (https://github.com/adi2011) 5. Rene Pickhardt (https://github.com/renepickhardt) 6. Bastien Teinturier of ACINQ (https://github.com/t-bast) 7. Valentine Wallace of LDK (https://github.com/valentinewallace) 8. Matt Corallo of LDK (https://github.com/BlueMatt) Also @bjarnemagnussen, @ellemouton, @animatedbarber, @617a7a, @instagibbs, @evansmj, @eupn and @yyforyongyu. (And no doubt others over the years, who I've accidentally omitted!) Yes, of course, thanks to my family for their patience with me. Signed-off-by: Rusty Russell --- .aspell.en.pws | 8 + 12-offer-encoding.md | 900 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 908 insertions(+) create mode 100644 12-offer-encoding.md diff --git a/.aspell.en.pws b/.aspell.en.pws index a63e1aeeb..7f95ea69c 100644 --- a/.aspell.en.pws +++ b/.aspell.en.pws @@ -410,6 +410,14 @@ quiesce quiescing SomeThing onionmsg +unrequested +Merkle +whitespace +TLVs +LnLeaf +LnNonce +LnBranch +payinfo griefing unspendable pkh diff --git a/12-offer-encoding.md b/12-offer-encoding.md new file mode 100644 index 000000000..930d6442d --- /dev/null +++ b/12-offer-encoding.md @@ -0,0 +1,900 @@ +# BOLT #12: Flexible 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) + +# 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](01-messaging.md#type-length-value-format) 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](bolt12/format-string-test.json). + +# Signature Calculation + +All signatures are created as per +[BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) +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](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki), +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`] + 1. type: 4 (`offer_metadata`) + 2. data: + * [`...*byte`:`data`] + 1. type: 6 (`offer_currency`) + 2. data: + * [`...*utf8`:`iso4217`] + 1. type: 8 (`offer_amount`) + 2. data: + * [`tu64`:`amount`] + 1. type: 10 (`offer_description`) + 2. data: + * [`...*utf8`:`description`] + 1. type: 12 (`offer_features`) + 2. data: + * [`...*byte`:`features`] + 1. type: 14 (`offer_absolute_expiry`) + 2. data: + * [`tu64`:`seconds_from_epoch`] + 1. type: 16 (`offer_paths`) + 2. data: + * [`...*blinded_path`:`paths`] + 1. type: 18 (`offer_issuer`) + 2. data: + * [`...*utf8`:`issuer`] + 1. type: 20 (`offer_quantity_max`) + 2. data: + * [`tu64`:`max`] + 1. type: 22 (`offer_issuer_id`) + 2. 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). + - 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 any 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_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 charging for something specific, the description is vital for the user to know what it was they paid for. + +# Invoice Requests + +Invoice Requests are a request for an invoice; the human-readable prefix for +invoice requests is `lnr`. + +There are two similar-looking uses for invoice requests, which are +almost identical from a workflow perspective, but are quite different +from a user's point of view. + +One is a response to an offer; this contains the `offer_issuer_id` or `offer_paths` and +all other offer details, and is generally received over an onion +message: if it's valid and refers to a known offer, the response is +generally to reply with an `invoice` using the `reply_path` field of +the onion message. + +The second case is publishing an invoice request without an offer, +such as via QR code. It contains neither `offer_issuer_id` nor `offer_paths`, setting the +`invreq_payer_id` (and possibly `invreq_paths`) instead, as it in the one paying: the +other offer fields are filled by the creator of the `invoice_request`, +forming a kind of offer-to-send-money. + +Note: the `invreq_metadata` is numbered 0 (not in the +80-159 range for other invreq fields) making it the "numerically-first TLV entry" +for [Signature Calculation](#signature-calculation). This ensures that merkle +leaves are unguessable, allowing a future compact representation to hide fields +while still allowing signature validation. + + +## TLV Fields for `invoice_request` + +1. `tlv_stream`: `invoice_request` +2. types: + 1. type: 0 (`invreq_metadata`) + 2. data: + * [`...*byte`:`blob`] + 1. type: 2 (`offer_chains`) + 2. data: + * [`...*chain_hash`:`chains`] + 1. type: 4 (`offer_metadata`) + 2. data: + * [`...*byte`:`data`] + 1. type: 6 (`offer_currency`) + 2. data: + * [`...*utf8`:`iso4217`] + 1. type: 8 (`offer_amount`) + 2. data: + * [`tu64`:`amount`] + 1. type: 10 (`offer_description`) + 2. data: + * [`...*utf8`:`description`] + 1. type: 12 (`offer_features`) + 2. data: + * [`...*byte`:`features`] + 1. type: 14 (`offer_absolute_expiry`) + 2. data: + * [`tu64`:`seconds_from_epoch`] + 1. type: 16 (`offer_paths`) + 2. data: + * [`...*blinded_path`:`paths`] + 1. type: 18 (`offer_issuer`) + 2. data: + * [`...*utf8`:`issuer`] + 1. type: 20 (`offer_quantity_max`) + 2. data: + * [`tu64`:`max`] + 1. type: 22 (`offer_issuer_id`) + 2. data: + * [`point`:`id`] + 1. type: 80 (`invreq_chain`) + 2. data: + * [`chain_hash`:`chain`] + 1. type: 82 (`invreq_amount`) + 2. data: + * [`tu64`:`msat`] + 1. type: 84 (`invreq_features`) + 2. data: + * [`...*byte`:`features`] + 1. type: 86 (`invreq_quantity`) + 2. data: + * [`tu64`:`quantity`] + 1. type: 88 (`invreq_payer_id`) + 2. data: + * [`point`:`key`] + 1. type: 89 (`invreq_payer_note`) + 2. data: + * [`...*utf8`:`note`] + 1. type: 90 (`invreq_paths`) + 2. data: + * [`...*blinded_path`:`paths`] + 1. type: 240 (`signature`) + 2. data: + * [`bip340sig`:`sig`] + +## Requirements for Invoice Requests + +The writer: + - if it is responding to an offer: + - MUST copy all fields from the offer (including unknown fields). + - if `offer_chains` is set: + - MUST set `invreq_chain` to one of `offer_chains` unless that chain is bitcoin, in which case it SHOULD omit `invreq_chain`. + - otherwise: + - if it sets `invreq_chain` it MUST set it to bitcoin. + - MUST set `signature`.`sig` as detailed in [Signature Calculation](#signature-calculation) using the `invreq_payer_id`. + - if `offer_amount` is not present: + - MUST specify `invreq_amount`. + - otherwise: + - MAY omit `invreq_amount`. + - if it sets `invreq_amount`: + - MUST specify `invreq_amount`.`msat` as greater or equal to amount expected by `offer_amount` (and, if present, `offer_currency` and `invreq_quantity`). + - MUST set `invreq_payer_id` to a transient public key. + - MUST remember the secret key corresponding to `invreq_payer_id`. + - if `offer_quantity_max` is present: + - MUST set `invreq_quantity` to greater than zero. + - if `offer_quantity_max` is non-zero: + - MUST set `invreq_quantity` less than or equal to `offer_quantity_max`. + - otherwise: + - MUST NOT set `invreq_quantity` + - otherwise (not responding to an offer): + - MUST set `offer_description` to a complete description of the purpose of the payment. + - MUST set (or not set) `offer_absolute_expiry` and `offer_issuer` as it would for an offer. + - MUST set `invreq_payer_id` (as it would set `offer_issuer_id` for an offer). + - MUST set `invreq_paths` as it would set (or not set) `offer_paths` for an offer. + - MUST NOT include `signature`, `offer_metadata`, `offer_chains`, `offer_amount`, `offer_currency`, `offer_features`, `offer_quantity_max`, `offer_paths` or `offer_issuer_id` + - if the chain for the invoice is not solely bitcoin: + - MUST specify `invreq_chain` the offer is valid for. + - MUST set `invreq_amount`. + - MUST NOT set any non-signature TLV fields outside the inclusive ranges: 0 to 159 and 1000000000 to 2999999999 + - MUST set `invreq_metadata` to an unpredictable series of bytes. + - if it sets `invreq_amount`: + - MUST set `msat` in multiples of the minimum lightning-payable unit + (e.g. milli-satoshis for bitcoin) for `invreq_chain` (or for bitcoin, if there is no `invreq_chain`). + - if it supports bolt12 invoice request features: + - MUST set `invreq_features`.`features` to the bitmap of features. + +The reader: + - MUST reject the invoice request if `invreq_payer_id` or `invreq_metadata` are not present. + - MUST reject the invoice request if any non-signature TLV fields are outside the inclusive ranges: 0 to 159 and 1000000000 to 2999999999 + - if `invreq_features` contains unknown _odd_ bits that are non-zero: + - MUST ignore the bit. + - if `invreq_features` contains unknown _even_ bits that are non-zero: + - MUST reject the invoice request. + - MUST reject the invoice request if `signature` is not correct as detailed in [Signature Calculation](#signature-calculation) using the `invreq_payer_id`. + - if `num_hops` is 0 in any `blinded_path` in `invreq_paths`: + - MUST reject the invoice request. + - if `offer_issuer_id` is present, and `invreq_metadata` is identical to a previous `invoice_request`: + - MAY simply reply with the previous invoice. + - otherwise: + - MUST NOT reply with a previous invoice. + - if `offer_issuer_id` or `offer_paths` are present (response to an offer): + - MUST reject the invoice request if the offer fields do not exactly match a valid, unexpired offer. + - if `offer_paths` is present: + - MUST ignore the invoice_request if it did not arrive via one of those paths. + - otherwise: + - MUST ignore any invoice_request if it arrived via a blinded path. + - if `offer_quantity_max` is present: + - MUST reject the invoice request if there is no `invreq_quantity` field. + - if `offer_quantity_max` is non-zero: + - MUST reject the invoice request if `invreq_quantity` is zero, OR greater than `offer_quantity_max`. + - otherwise: + - MUST reject the invoice request if there is an `invreq_quantity` field. + - if `offer_amount` is present: + - MUST calculate the *expected amount* using the `offer_amount`: + - if `offer_currency` is not the `invreq_chain` currency, convert to the + `invreq_chain` currency. + - if `invreq_quantity` is present, multiply by `invreq_quantity`.`quantity`. + - if `invreq_amount` is present: + - MUST reject the invoice request if `invreq_amount`.`msat` is less than the *expected amount*. + - MAY reject the invoice request if `invreq_amount`.`msat` greatly exceeds the *expected amount*. + - otherwise (no `offer_amount`): + - MUST reject the invoice request if it does not contain `invreq_amount`. + - SHOULD send an invoice in response using the `onionmsg_tlv` `reply_path`. + - otherwise (no `offer_issuer_id` or `offer_paths`, not a response to our offer): + - MUST reject the invoice request if any of the following are present: + - `offer_chains`, `offer_features` or `offer_quantity_max`. + - MUST reject the invoice request if `invreq_amount` is not present. + - MAY use `offer_amount` (or `offer_currency`) for informational display to user. + - if it sends an invoice in response: + - MUST use `invreq_paths` if present, otherwise MUST use `invreq_payer_id` as the node id to send to. + - if `invreq_chain` is not present: + - MUST reject the invoice request if bitcoin is not a supported chain. + - otherwise: + - MUST reject the invoice request if `invreq_chain`.`chain` is not a supported chain. + + +## Rationale + +`invreq_metadata` might typically contain information about the derivation of the +`invreq_payer_id`. 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_id would be derived by +tweaking the base key with SHA256(payer_base_pubkey || tweak). It's also +the first entry (if present), ensuring an unpredictable nonce for hashing. + +`invreq_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 +`invreq_amount` in their invoice request, even though the offer specifies an +`offer_amount`. The recipient will only accept this if +the invoice request amount exceeds the amount it's expecting (i.e. its +`offer_amount` after any currency conversion, multiplied by `invreq_quantity`, if +any). + +Non-offer-response invoice requests are currently required to +explicitly state the `invreq_amount` in the chain currency, +so `offer_amount` and `offer_currency` are redundant (but may be +informative for the payer to know how the sender claims +`invreq_amount` was derived). + +The requirement to use `offer_paths` if present, ensures a node does not reveal it is the source of an offer if it is asked directly. Similarly, the requirement that the correct path is used for the offer ensures that cannot be made to reveal that it is the same node that created some other offer. + +# Invoices + +Invoices are a payment request, and when the payment is made, +the payment preimage can be combined with the invoice to form a cryptographic receipt. + +The recipient sends an `invoice` in response to an `invoice_request` using +the `onion_message` `invoice` field. + +1. `tlv_stream`: `invoice` +2. types: + 1. type: 0 (`invreq_metadata`) + 2. data: + * [`...*byte`:`blob`] + 1. type: 2 (`offer_chains`) + 2. data: + * [`...*chain_hash`:`chains`] + 1. type: 4 (`offer_metadata`) + 2. data: + * [`...*byte`:`data`] + 1. type: 6 (`offer_currency`) + 2. data: + * [`...*utf8`:`iso4217`] + 1. type: 8 (`offer_amount`) + 2. data: + * [`tu64`:`amount`] + 1. type: 10 (`offer_description`) + 2. data: + * [`...*utf8`:`description`] + 1. type: 12 (`offer_features`) + 2. data: + * [`...*byte`:`features`] + 1. type: 14 (`offer_absolute_expiry`) + 2. data: + * [`tu64`:`seconds_from_epoch`] + 1. type: 16 (`offer_paths`) + 2. data: + * [`...*blinded_path`:`paths`] + 1. type: 18 (`offer_issuer`) + 2. data: + * [`...*utf8`:`issuer`] + 1. type: 20 (`offer_quantity_max`) + 2. data: + * [`tu64`:`max`] + 1. type: 22 (`offer_issuer_id`) + 2. data: + * [`point`:`id`] + 1. type: 80 (`invreq_chain`) + 2. data: + * [`chain_hash`:`chain`] + 1. type: 82 (`invreq_amount`) + 2. data: + * [`tu64`:`msat`] + 1. type: 84 (`invreq_features`) + 2. data: + * [`...*byte`:`features`] + 1. type: 86 (`invreq_quantity`) + 2. data: + * [`tu64`:`quantity`] + 1. type: 88 (`invreq_payer_id`) + 2. data: + * [`point`:`key`] + 1. type: 89 (`invreq_payer_note`) + 2. data: + * [`...*utf8`:`note`] + 1. type: 90 (`invreq_paths`) + 2. data: + * [`...*blinded_path`:`paths`] + 1. type: 160 (`invoice_paths`) + 2. data: + * [`...*blinded_path`:`paths`] + 1. type: 162 (`invoice_blindedpay`) + 2. data: + * [`...*blinded_payinfo`:`payinfo`] + 1. type: 164 (`invoice_created_at`) + 2. data: + * [`tu64`:`timestamp`] + 1. type: 166 (`invoice_relative_expiry`) + 2. data: + * [`tu32`:`seconds_from_creation`] + 1. type: 168 (`invoice_payment_hash`) + 2. data: + * [`sha256`:`payment_hash`] + 1. type: 170 (`invoice_amount`) + 2. data: + * [`tu64`:`msat`] + 1. type: 172 (`invoice_fallbacks`) + 2. data: + * [`...*fallback_address`:`fallbacks`] + 1. type: 174 (`invoice_features`) + 2. data: + * [`...*byte`:`features`] + 1. type: 176 (`invoice_node_id`) + 2. data: + * [`point`:`node_id`] + 1. type: 240 (`signature`) + 2. data: + * [`bip340sig`:`sig`] + +1. subtype: `blinded_payinfo` +2. data: + * [`u32`:`fee_base_msat`] + * [`u32`:`fee_proportional_millionths`] + * [`u16`:`cltv_expiry_delta`] + * [`u64`:`htlc_minimum_msat`] + * [`u64`:`htlc_maximum_msat`] + * [`u16`:`flen`] + * [`flen*byte`:`features`] + +1. subtype: `fallback_address` +2. data: + * [`byte`:`version`] + * [`u16`:`len`] + * [`len*byte`:`address`] + +## Invoice Features + +| Bits | Description | Name | +|------|----------------------------------|----------------| +| 16 | Multi-part-payment support | MPP/compulsory | +| 17 | Multi-part-payment support | MPP/optional | + +The 'MPP support' invoice feature indicates that the payer MUST (16) or +MAY (17) use multiple part payments to pay the invoice. + +Some implementations may not support MPP (e.g. for small payments), or +may (due to capacity limits on a single channel) require it. + +## Requirements + +A writer of an invoice: + - MUST set `invoice_created_at` to the number of seconds since Midnight 1 + January 1970, UTC when the invoice was created. + - MUST set `invoice_amount` to the minimum amount it will accept, in units of + the minimal lightning-payable unit (e.g. milli-satoshis for bitcoin) for + `invreq_chain`. + - if the invoice is in response to an `invoice_request`: + - MUST copy all non-signature fields from the invoice request (including unknown fields). + - if `invreq_amount` is present: + - MUST set `invoice_amount` to `invreq_amount` + - otherwise: + - MUST set `invoice_amount` to the *expected amount*. + - MUST set `invoice_payment_hash` to the SHA256 hash of the + `payment_preimage` that will be given in return for payment. + - if `offer_issuer_id` is present: + - MUST set `invoice_node_id` to the `offer_issuer_id` + - otherwise, if `offer_paths` is present: + - MUST set `invoice_node_id` to the final `blinded_node_id` on the path it received the invoice request + - MUST specify exactly one signature TLV element: `signature`. + - MUST set `sig` to the signature using `invoice_node_id` as described in [Signature Calculation](#signature-calculation). + - if it requires multiple parts to pay the invoice: + - MUST set `invoice_features`.`features` bit `MPP/compulsory` + - or if it allows multiple parts to pay the invoice: + - MUST set `invoice_features`.`features` bit `MPP/optional` + - if the expiry for accepting payment is not 7200 seconds after `invoice_created_at`: + - MUST set `invoice_relative_expiry`.`seconds_from_creation` to the number of + seconds after `invoice_created_at` that payment of this invoice should not be attempted. + - if it accepts onchain payments: + - MAY specify `invoice_fallbacks` + - SHOULD specify `invoice_fallbacks` in order of most-preferred to least-preferred + if it has a preference. + - for the bitcoin chain, it MUST set each `fallback_address` with + `version` as a valid witness version and `address` as a valid witness + program + - MUST include `invoice_paths` containing one or more paths to the node. + - MUST specify `invoice_paths` in order of most-preferred to least-preferred if it has a preference. + - MUST include `invoice_blindedpay` with exactly one `blinded_payinfo` for each `blinded_path` in `paths`, in order. + - MUST set `features` in each `blinded_payinfo` to match `encrypted_data_tlv`.`allowed_features` (or empty, if no `allowed_features`). + - SHOULD ignore any payment which does not use one of the paths. + +A reader of an invoice: + - MUST reject the invoice if `invoice_amount` is not present. + - MUST reject the invoice if `invoice_created_at` is not present. + - MUST reject the invoice if `invoice_payment_hash` is not present. + - MUST reject the invoice if `invoice_node_id` is not present. + - if `invreq_chain` is not present: + - MUST reject the invoice if bitcoin is not a supported chain. + - otherwise: + - MUST reject the invoice if `invreq_chain`.`chain` is not a supported chain. + - if `invoice_features` contains unknown _odd_ bits that are non-zero: + - MUST ignore the bit. + - if `invoice_features` contains unknown _even_ bits that are non-zero: + - MUST reject the invoice. + - if `invoice_relative_expiry` is present: + - MUST reject the invoice if the current time since 1970-01-01 UTC is greater than `invoice_created_at` plus `seconds_from_creation`. + - otherwise: + - MUST reject the invoice if the current time since 1970-01-01 UTC is greater than `invoice_created_at` plus 7200. + - MUST reject the invoice if `invoice_paths` is not present or is empty. + - MUST reject the invoice if `num_hops` is 0 in any `blinded_path` in `invoice_paths`. + - MUST reject the invoice if `invoice_blindedpay` is not present. + - MUST reject the invoice if `invoice_blindedpay` does not contain exactly one `blinded_payinfo` per `invoice_paths`.`blinded_path`. + - For each `invoice_blindedpay`.`payinfo`: + - MUST NOT use the corresponding `invoice_paths`.`path` if `payinfo`.`features` has any unknown even bits set. + - MUST reject the invoice if this leaves no usable paths. + - if the invoice is a response to an `invoice_request`: + - MUST reject the invoice if all fields in ranges 0 to 159 and 1000000000 to 2999999999 (inclusive) do not exactly match the invoice request. + - if `offer_issuer_id` is present (invoice_request for an offer): + - MUST reject the invoice if `invoice_node_id` is not equal to `offer_issuer_id` + - otherwise, if `offer_paths` is present (invoice_request for an offer without id): + - MUST reject the invoice if `invoice_node_id` is not equal to the final `blinded_node_id` it sent the invoice request to. + - otherwise (invoice_request without an offer): + - MAY reject the invoice if it cannot confirm that `invoice_node_id` is correct, out-of-band. + - MUST reject the invoice if `signature` is not a valid signature using `invoice_node_id` as described in [Signature Calculation](#signature-calculation). + - SHOULD prefer to use earlier `invoice_paths` over later ones if it has no other reason for preference. + - if `invoice_features` contains the MPP/compulsory bit: + - MUST pay the invoice via multiple separate blinded paths. + - otherwise, if `invoice_features` contains the MPP/optional bit: + - MAY pay the invoice via multiple separate payments. + - otherwise: + - MUST NOT use multiple parts to pay the invoice. + - if `invreq_amount` is present: + - MUST reject the invoice if `invoice_amount` is not equal to `invreq_amount` + - otherwise: + - SHOULD confirm authorization if `invoice_amount`.`msat` is not within the amount range authorized. + - for the bitcoin chain, if the invoice specifies `invoice_fallbacks`: + - MUST ignore any `fallback_address` for which `version` is greater than 16. + - MUST ignore any `fallback_address` for which `address` is less than 2 or greater than 40 bytes. + - MUST ignore any `fallback_address` for which `address` does not meet known requirements for the given `version` + - if `invreq_paths` is present: + - MUST reject the invoice if it did not arrive via one of those paths. + - otherwise, neither `offer_issuer_id` nor `offer_paths` are present (not derived from an offer): + - MUST reject the invoice if it arrived via a blinded path. + - otherwise (derived from an offer): + - MUST reject the invoice if it did not arrive via invoice request `onionmsg_tlv` `reply_path`. + +## 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 to make `invreq_metadata` both unpredictable and unique, +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 +invreq. 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 invreq fields, hence the requirements to check that they +are correct, although allowance is made for simply sending an unrequested +invoice directly. + +Note that the recipient of the invoice can determine the expected +amount from either the offer it received, or the invreq it +sent, so often already has authorization for the expected amount. + +The default `invoice_relative_expiry` of 7200 seconds, which is generally a +sufficient time for payment, even if new channels need to be opened. + +Blinded paths provide an equivalent to `payment_secret` and `payment_metadata` used in BOLT 11. +Even if `invoice_node_id` or `invreq_payer_id` is public, we force the use of blinding paths to keep these features. +If the recipient does not care about the added privacy offered by blinded paths, they can create a path of length 1 with only themselves. + +Rather than provide detailed per-hop-payinfo for each hop in a blinded path, we aggregate the fees and CLTV deltas. +This avoids trivially revealing any distinguishing non-uniformity which may distinguish the path. + +In the case of an invoice where there was no offer (just an invoice +request), the payer needs to ensure that the invoice is from the +intended payment recipient. This is the basis for the suggestion to +confirm the invoice_node_id for this case. + +Raw invoices (not based on an invoice_request) are generally not +supported, though an implementation is allowed to support them, and we +may define the behavior in future. The redundant requirement to check +`invreq_chain` explicitly is a nod to this: if the invoice is +a response to an invoice request, that field must have existed due +to the invoice request requirements, and we also require it to be mirrored +here. + + +# 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`] + 1. type: 3 (`suggested_value`) + 2. data: + * [`...*byte`:`value`] + 1. type: 5 (`error`) + 2. data: + * [`...*utf8`:`msg`] + +## Requirements + +A writer of an invoice_error: + - MUST set `error` to an explanatory string. + - MAY set `erroneous_field` to a specific field number in the + `invoice` or `invoice_request` which had a problem. + - if it sets `erroneous_field`: + - MAY set `suggested_value`. + - if it sets `suggested_value`: + - MUST set `suggested_value` to a valid field for that `tlv_fieldnum`. + - otherwise: + - MUST NOT set `suggested_value`. + +A reader of an invoice_error: + FIXME! + +## Rationale + +Usually an error message is sufficient for diagnostics, however future +enhancements may make automated handling useful. + +In particular, we could allow non-offer-response `invoice_request`s to +omit `invreq_amount` in future and use offer fields to +indicate alternate currencies. ("I will send you 10c!"). Then the +sender of the invoice would have to guess how many msat that was, +and could use the `invoice_error` to indicate if the recipient disagreed +with the conversion so 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 `offer_issuer_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 invreq and + invoice, to make a shopping list. +7. All-zero offer_id == gratuitous payment. +8. Streaming invoices? +9. Re-add recurrence. +10. Re-add `invreq_refund_for` to support proofs. +11. Re-add `invoice_replace` for requesting replacement of a (stuck-payment) + invoice with a new one. +12. Allow non-offer `invoice_request` with alternate currencies? +13. Add `offer_quantity_unit` to indicate stepping for quantity + (e.g. 100 grams). + +[1] https://www.youtube.com/watch?v=4SYc_flMnMQ From eaaa69bdddabf51badbd3c6ea8de1dbe8a47c5c0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 24 Sep 2024 18:01:05 +0930 Subject: [PATCH 7/8] BOLT 4: add bolt12 payloads to onion message payloads. Signed-off-by: Rusty Russell --- 04-onion-routing.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/04-onion-routing.md b/04-onion-routing.md index 9be1332e7..7df5eb8aa 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -1510,6 +1510,15 @@ even, of course!). 1. type: 4 (`encrypted_recipient_data`) 2. data: * [`...*byte`:`encrypted_recipient_data`] + 1. type: 64 (`invoice_request`) + 2. data: + * [`tlv_invoice_request`:`invreq`] + 1. type: 66 (`invoice`) + 2. data: + * [`tlv_invoice`:`inv`] + 1. type: 68 (`invoice_error`) + 2. data: + * [`tlv_invoice_error`:`inverr`] #### Requirements From f4d74a981efed97066d2055ed4cbf704614a3d6b Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 24 Sep 2024 18:01:07 +0930 Subject: [PATCH 8/8] BOLT 12: the test vectors. Three vectors: 1. Raw string decoding tests. (Start here!) 2. Offer decoding tests. 3. TLV signature tests. Signed-off-by: Rusty Russell --- bolt12/format-string-test.json | 57 ++++ bolt12/offers-test.json | 586 +++++++++++++++++++++++++++++++++ bolt12/signature-test.json | 137 ++++++++ 3 files changed, 780 insertions(+) create mode 100644 bolt12/format-string-test.json create mode 100644 bolt12/offers-test.json create mode 100644 bolt12/signature-test.json diff --git a/bolt12/format-string-test.json b/bolt12/format-string-test.json new file mode 100644 index 000000000..2bbd7d214 --- /dev/null +++ b/bolt12/format-string-test.json @@ -0,0 +1,57 @@ +[ + { + "comment": "A complete string is valid", + "valid": true, + "string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + }, + { + "comment": "Uppercase is valid", + "valid": true, + "string": "LNO1PQPS7SJQPGTYZM3QV4UXZMTSD3JJQER9WD3HY6TSW35K7MSJZFPY7NZ5YQCNYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD5XVXG" + }, + { + "comment": "+ can join anywhere", + "valid": true, + "string": "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + }, + { + "comment": "Multiple + can join", + "valid": true, + "string": "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg" + }, + { + "comment": "+ can be followed by whitespace", + "valid": true, + "string": "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg" + }, + { + "comment": "+ can be followed by whitespace, UPPERCASE", + "valid": true, + "string": "LNO1PQPS7SJQPGT+ YZM3QV4UXZMTSD3JJQER9WD3HY6TSW3+ 5K7MSJZFPY7NZ5YQCN+\nYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD+\r\n 5XVXG" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ " + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + } +] diff --git a/bolt12/offers-test.json b/bolt12/offers-test.json new file mode 100644 index 000000000..891ed5673 --- /dev/null +++ b/bolt12/offers-test.json @@ -0,0 +1,586 @@ +[ + { + "description": "Minimal bolt12 offer", + "valid": true, + "bolt12": "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese", + "fields": [ + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with description (but no amount)", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg", + "field info": "description is 'Test vectors'", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "for testnet", + "valid": true, + "bolt12": "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + "field info": "chains[0] is testnet", + "fields": [ + { + "type": 2, + "length": 32, + "hex": "43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000" + }, + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "for bitcoin (redundant)", + "valid": true, + "bolt12": "lno1qgsxlc5vp2m0rvmjcxn2y34wv0m5lyc7sdj7zksgn35dvxgqqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + "field info": "chains[0] is bitcoin", + "fields": [ + { + "type": 2, + "length": 32, + "hex": "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + }, + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "for bitcoin or liquidv1", + "valid": true, + "bolt12": "lno1qfqpge38tqmzyrdjj3x2qkdr5y80dlfw56ztq6yd9sme995g3gsxqqm0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq9qc4r9wd6zqan9vd6x7unnzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese", + "field info": "chains[0] is liquidv1, chains[1] is bitcoin", + "fields": [ + { + "type": 2, + "length": 64, + "hex": "1466275836220db2944ca059a3a10ef6fd2ea684b0688d2c379296888a2060036fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + }, + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with metadata", + "valid": true, + "bolt12": "lno1qsgqqqqqqqqqqqqqqqqqqqqqqqqqqzsv23jhxapqwejkxar0wfe3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + "field info": "metadata is 16 zero bytes", + "fields": [ + { + "type": 4, + "length": 16, + "hex": "00000000000000000000000000000000" + }, + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with amount", + "valid": true, + "bolt12": "lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + "field info": "amount is 10000msat", + "fields": [ + { + "type": 8, + "length": 2, + "hex": "2710" + }, + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with currency", + "valid": true, + "bolt12": "lno1qcp4256ypqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + "field info": "amount is USD $100.00", + "fields": [ + { + "type": 6, + "length": 3, + "hex": "555344" + }, + { + "type": 8, + "length": 2, + "hex": "2710" + }, + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with expiry", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucwq3ay997czcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese", + "field info": "expiry is 2035-01-01", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 14, + "length": 4, + "hex": "7a4297d8" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with issuer", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucjy358garswvaz7tmzdak8gvfj9ehhyeeqgf85c4p3xgsxjmnyw4ehgunfv4e3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + "field info": "issuer is 'https://bolt12.org BOLT12 industries'", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 18, + "length": 36, + "hex": "68747470733a2f2f626f6c7431322e6f726720424f4c54313220696e6475737472696573" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with quantity", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuc5qyz3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + "field info": "quantity_max is 5", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 20, + "length": 1, + "hex": "05" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with unlimited (or unknown) quantity", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuc5qqtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry", + "field info": "quantity_max is unknown/unlimited", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 20, + "length": 0, + "hex": "" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with single quantity (weird but valid)", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuc5qyq3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + "field info": "quantity_max is 1", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 20, + "length": 1, + "hex": "01" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with feature", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucvp5yqqqqqqqqqqqqqqqqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg", + "field info": "feature bit 99 set", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 12, + "length": 13, + "hex": "08000000000000000000000000" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with blinded path via Bob (0x424242...), blinding 020202...", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + "field info": "path is [id=02020202..., enc=0x00*16], [id=02020202..., enc=0x11*8]", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 16, + "length": 161, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c0202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200100000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020200081111111111111111" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "same, with blinded path first_node_id using sciddir", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucs3yqqqqqqqqqqqqp2qgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqyqqqqqqqqqqqqqqqqqqqqqqqqqqqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqgzyg3zyg3zyg3z93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + "field info": "short_channel_id is 0x0x42, direction is 0", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 16, + "length": 137, + "hex": "00000000000000002a0202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200100000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020200081111111111111111" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "with no issuer_id and blinded path via Bob (0x424242...), blinding 020202...", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zygs", + "field info": "path is [id=02020202..., enc=0x00*16], [id=02020202..., enc=0x11*8]", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 16, + "length": 161, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c0202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200100000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020200081111111111111111" + } + ] + }, + { + "description": "... and with second blinded path via 1x2x3 (direction 1), blinding 020202...", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucsl5qj5qeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygpqqqqzqqqqgqqxqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqgqqqqqqqqqqqqqqqqqqqqqqqqqqqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqqsg3zyg3zyg3zygtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry", + "field info": "path is [id=02020202..., enc=0x00*16], [id=02020202..., enc=0x22*8]", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 16, + "length": 298, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c02020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202001000000000000000000000000000000000020202020202020202020202020202020202020202020202020202020202020202000811111111111111110100000100000200030202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200100000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020200082222222222222222" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "unknown odd field", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs", + "field info": "type 33 is 'helloworld'", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + }, + { + "type": 33, + "length": 10, + "hex": "68656c6c6f776f726c64" + } + ] + }, + { + "description": "unknown odd experimental field", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvx078wdv5gg2dpjkcmr0wahhymry", + "field info": "type 1000000033 is 'helloworld'", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + }, + { + "type": 1000000033, + "length": 10, + "hex": "68656c6c6f776f726c64" + } + ] + }, + { + "description": "Malformed: fields out of order", + "valid": false, + "bolt12": "lno1zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszpgz5znzfgdzs" + }, + { + "description": "Malformed: unknown even TLV type 78", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpysgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" + }, + { + "description": "Malformed: empty", + "valid": false, + "bolt12": "lno1" + }, + { + "description": "Malformed: truncated at type", + "valid": false, + "bolt12": "lno1pg" + }, + { + "description": "Malformed: truncated in length", + "valid": false, + "bolt12": "lno1pt7s" + }, + { + "description": "Malformed: truncated after length", + "valid": false, + "bolt12": "lno1pgpq" + }, + { + "description": "Malformed: truncated in description", + "valid": false, + "bolt12": "lno1pgpyz" + }, + { + "description": "Malformed: invalid offer_chains length", + "valid": false, + "bolt12": "lno1qgqszzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: truncated currency UTF-8", + "valid": false, + "bolt12": "lno1qcqcqzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: invalid currency UTF-8", + "valid": false, + "bolt12": "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg" + }, + { + "description": "Malformed: truncated description UTF-8", + "valid": false, + "bolt12": "lno1pgqcq93pqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqy" + }, + { + "description": "Malformed: invalid description UTF-8", + "valid": false, + "bolt12": "lno1pgpgqsgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs" + }, + { + "description": "Malformed: truncated offer_paths", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3qqgpzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: zero num_hops in blinded_path", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: truncated onionmsg_hop in blinded_path", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs" + }, + { + "description": "Malformed: bad first_node_id in blinded_path", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3qqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: bad blinding in blinded_path", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcpqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: bad blinded_node_id in onionmsg_hop", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: truncated issuer UTF-8", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3yqvqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" + }, + { + "description": "Malformed: invalid issuer UTF-8", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3yq5qgytzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg" + }, + { + "description": "Malformed: invalid offer_issuer_id", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps" + }, + { + "description": "Contains type >= 80", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" + }, + { + "description": "Contains type > 1999999999", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06ae4jsq9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" + }, + { + "description": "Contains unknown even type (1000000002)", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06wu6egp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" + }, + { + "description": "Contains unknown feature 122", + "valid": false, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucvzqzqqqqqqqqqqqqqqqqqqqqqqqqpvggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs" + }, + { + "description": "Missing offer_description, but has offer_amount", + "valid": false, + "bolt12": "lno1pqpzwyqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + }, + { + "description": "Missing offer_issuer_id and no offer_path", + "valid": false, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuc" + }, + { + "description": "Second offer_path is empty", + "valid": false, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucsespjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zygszqqqqyqqqqsqqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsq" + } +] diff --git a/bolt12/signature-test.json b/bolt12/signature-test.json new file mode 100644 index 000000000..00d327071 --- /dev/null +++ b/bolt12/signature-test.json @@ -0,0 +1,137 @@ +[ + { + "comment": "Simple n1 test, tlv1 = 1000", + "tlv": "n1", + "first-tlv": "010203e8", + "leaves": [ + { + "H(`LnLeaf`,010203e8)": "67a2a995433890d8fe0c18a1765ad19e98f1fcfeff14c13a45bbc80964a78cf7", + "H(`LnNonce`|first-tlv,tlv1-type)": "255a95f5b6b3c6997e2838dc4d9348807fb6da8eb7bbc02d30662d144718b6aa", + "H(`LnBranch`,leaf+nonce)": "b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93" + } + ], + "branches": [], + "merkle": "b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93" + }, + { + "comment": "n1 test, tlv1 = 1000, tlv2 = 1x2x3", + "tlv": "n1", + "first-tlv": "010203e8", + "leaves": [ + { + "H(`LnLeaf`,010203e8)": "67a2a995433890d8fe0c18a1765ad19e98f1fcfeff14c13a45bbc80964a78cf7", + "H(`LnNonce`|first-tlv,tlv1-type)": "255a95f5b6b3c6997e2838dc4d9348807fb6da8eb7bbc02d30662d144718b6aa", + "H(`LnBranch`,leaf+nonce)": "b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93" + }, + { + "H(`LnLeaf`,02080000010000020003)": "cc04567fcbff60d4de87afe5142de16b7401531300554838b2d1117341a4ea8d", + "H(`LnNonce`|first-tlv,tlv2-type)": "12bc15565410d8e3251a6fb1c53a2d360f39a9f65afb8403ef875016e34ff678", + "H(`LnBranch`,leaf+nonce)": "19d6ecfa3be88d29c30e56167f58526d7695dfac9cb95e1256deb222c92db4d0" + } + ], + "branches": [ + { + "desc": "1: tlv1+nonce and tlv2+nonce", + "H(`LnBranch`,19d6ecfa3be88d29c30e56167f58526d7695dfac9cb95e1256deb222c92db4d0b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93)": "c3774abbf4815aa54ccaa026bff6581f01f3be5fe814c620a252534f434bc0d1" + } + ], + "merkle": "c3774abbf4815aa54ccaa026bff6581f01f3be5fe814c620a252534f434bc0d1" + }, + { + "comment": "n1 test, tlv1 = 1000, tlv2 = 1x2x3, tlv3 = 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518, 1, 2", + "tlv": "n1", + "first-tlv": "010203e8", + "leaves": [ + { + "H(`LnLeaf`,010203e8)": "67a2a995433890d8fe0c18a1765ad19e98f1fcfeff14c13a45bbc80964a78cf7", + "H(`LnNonce`|first-tlv,1)": "255a95f5b6b3c6997e2838dc4d9348807fb6da8eb7bbc02d30662d144718b6aa", + "H(`LnBranch`,leaf+nonce)": "b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93" + }, + { + "H(`LnLeaf`,02080000010000020003)": "cc04567fcbff60d4de87afe5142de16b7401531300554838b2d1117341a4ea8d", + "H(`LnNonce`|first-tlv,2)": "12bc15565410d8e3251a6fb1c53a2d360f39a9f65afb8403ef875016e34ff678", + "H(`LnBranch`,leaf+nonce)": "19d6ecfa3be88d29c30e56167f58526d7695dfac9cb95e1256deb222c92db4d0" + }, + { + "H(`LnLeaf`,03310266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c0351800000000000000010000000000000002)": "47da319b36d61a006e0dbcf6642fe4c822c33a6131af67dfa9293b089c5cbd27", + "H(`LnNonce`|first-tlv,3)": "068cf6e9d2db9258a6c1d3304a8f2e9d4d046ea711664c9a96960234f707a084", + "H(`LnBranch`,leaf+nonce)": "7c879819c09f1525e7bc69b84f7928180de584f92c846e01fa2daf5b17e32967" + } + ], + "branches": [ + { + "desc": "1: tlv1+nonce and tlv2+nonce", + "H(`LnBranch`,19d6ecfa3be88d29c30e56167f58526d7695dfac9cb95e1256deb222c92db4d0b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93)": "c3774abbf4815aa54ccaa026bff6581f01f3be5fe814c620a252534f434bc0d1" + }, + { + "desc": "1 and tlv3+nonce", + "H(`LnBranch`,7c879819c09f1525e7bc69b84f7928180de584f92c846e01fa2daf5b17e32967c3774abbf4815aa54ccaa026bff6581f01f3be5fe814c620a252534f434bc0d1)": "ab2e79b1283b0b31e0b035258de23782df6b89a38cfa7237bde69aed1a658c5d" + } + ], + "merkle": "ab2e79b1283b0b31e0b035258de23782df6b89a38cfa7237bde69aed1a658c5d" + }, + { + "comment": "invoice_request test: offer_issuer_id = Alice (privkey 0x414141...), offer_description = 'A Mathematical Treatise', offer_amount = 100, offer_currency = 'USD', invreq_payer_id = Bob (privkey 0x424242...), invreq_metadata = 0x0000000000000000", + "bolt12": "lnr1qqyqqqqqqqqqqqqqqcp4256ypqqkgzshgysy6ct5dpjk6ct5d93kzmpq23ex2ct5d9ek293pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpjkppqvjx204vgdzgsqpvcp4mldl3plscny0rt707gvpdh6ndydfacz43euzqhrurageg3n7kafgsek6gz3e9w52parv8gs2hlxzk95tzeswywffxlkeyhml0hh46kndmwf4m6xma3tkq2lu04qz3slje2rfthc89vss", + "tlv": "invoice_request", + "first-tlv": "00080000000000000000", + "leaves": [ + { + "H(`LnLeaf`,00080000000000000000)": "cd45d50b8dbb73ba995f92aa48be7c2909331998cb070572f5499bae338a03c6", + "H(`LnNonce`|first-tlv,0)": "edc13c82e89b213a5641b27f0c06c5f31ea948a0cc2fd6495120cc8590cac3f5", + "H(`LnBranch`,leaf+nonce)": "5ced451fad76ab7edc8084b84c8b5086df195b2a503c25b371e6850a280c94ab" + }, + { + "H(`LnLeaf`,0603555344)": "ae61bfe63f8fc81b7a02a962182a5b5e01501365806481d52fbdfbca915266fa", + "H(`LnNonce`|first-tlv,6)": "cc9fc57ce5e82252b6cc8908a93f012b13294a82132768e36dd767b3c3c289e8", + "H(`LnBranch`,leaf+nonce)": "a2ea87a666c1524d25132ff59883c96a118728ff76595d239f5806143e3e9c9e" + }, + { + "H(`LnLeaf`,080164)": "b4f3adb8ca4f4a4c0e7cd9e0b1cafe8634cf8a864e1a730868bdda39fbd3e336", + "H(`LnNonce`|first-tlv,8)": "376180f1ef3b7973ba4989f9391502bd78a1a8a54929fe9adcaec1dd2bfec648", + "H(`LnBranch`,leaf+nonce)": "fa0bb4f0fa2f2625c63eec9bf3a29c9aa304e64d5aa44d38e050a6bd7d6fc5c0" + }, + { + "H(`LnLeaf`,0a1741204d617468656d61746963616c205472656174697365)": "7007775409456c33c47bddd7ce946ecd5a82035f1d5a529cc90e84d146f75a6e", + "H(`LnNonce`|first-tlv,10)": "01926a0c38b4ec71d76b116eeb81ea7999706fdce24a7f5b9d67bf867fd0c4d8", + "H(`LnBranch`,leaf+nonce)": "349379beebd68fd72296e76cb2ae28554b35fa9234853956b81b24c008783230" + }, + { + "H(`LnLeaf`,162102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619)": "bdde38b7b58fa74acee1e943bbc32c04306368cb2aa513856f53f45be461051b", + "H(`LnNonce`|first-tlv,22)": "2e571571c7dd0739dbc4180bb96b7652b055f9e97f80d37337c96689990fdbaa", + "H(`LnBranch`,leaf+nonce)": "384853c9811863028876088ce34e75d784ac027fd564f103ea972cdf96236e47" + }, + { + "H(`LnLeaf`,58210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c)": "f3b92382531e261e16a0f35d65f314ae622306bbb1b206fee00d80153b76eea3", + "H(`LnNonce`|first-tlv,88)": "c31a695332d176217470b705cde5c8cd71cdb611e1f26c5a98f14c0d935c97bd", + "H(`LnBranch`,leaf+nonce)": "73e067757513706491e0da4e8077112e606da55c04239ad13ab609bc82907600" + } + ], + "branches": [ + { + "desc": "1: metadata+nonce and currency+nonce", + "H(`LnBranch`,5ced451fad76ab7edc8084b84c8b5086df195b2a503c25b371e6850a280c94aba2ea87a666c1524d25132ff59883c96a118728ff76595d239f5806143e3e9c9e)": "f0aa4611039a3a8a90dc8331fa75c9acf433be7285cac0983902aaaa8f66aaa9" + }, + { + "desc": "2: amount+nonce and descripton+nonce", + "H(`LnBranch`,349379beebd68fd72296e76cb2ae28554b35fa9234853956b81b24c008783230fa0bb4f0fa2f2625c63eec9bf3a29c9aa304e64d5aa44d38e050a6bd7d6fc5c0)": "92e6478159d6763b19c5d03a8a834e179116f89e0cec700049e5ce921f8c400e" + }, + { + "desc": "3: 1 and 2", + "H(`LnBranch`,92e6478159d6763b19c5d03a8a834e179116f89e0cec700049e5ce921f8c400ef0aa4611039a3a8a90dc8331fa75c9acf433be7285cac0983902aaaa8f66aaa9)": "432097bd1a848ab41eee3695a2c5932c4aea987b27b1a61e58ac950ecce1214a" + }, + { + "desc": "4: node_id+nonce and payer_id+nonce", + "H(`LnBranch`,384853c9811863028876088ce34e75d784ac027fd564f103ea972cdf96236e4773e067757513706491e0da4e8077112e606da55c04239ad13ab609bc82907600)": "2ac9b0261d644027939d9a7bd055cb2468b79d92c6811d56a300c6b8ff97c14d" + }, + { + "desc": "5: 3 and 4", + "H(`LnBranch`,2ac9b0261d644027939d9a7bd055cb2468b79d92c6811d56a300c6b8ff97c14d432097bd1a848ab41eee3695a2c5932c4aea987b27b1a61e58ac950ecce1214a)": "608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624" + } + ], + "merkle": "608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624", + "signature_tag": "lightninginvoice_requestsignature", + "H(signature_tag,merkle)": "aefe3aa88a69772c246dcaef75ed3e7566c08ecc4e9f995233526a5651fc34cd", + "signature": "b8f83ea3288cfd6ea510cdb481472575141e8d8744157f98562d162cc1c472526fdb24befefbdebab4dbb726bbd1b7d8aec057f8fa805187e5950d2bbe0e5642" + } +]