From f0e4fc731f7e466d62348e6652b1bee9fcec0506 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:00:37 +0930 Subject: [PATCH 01/13] 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 bcd11d999..bf8bd21d8 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -243,6 +243,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 21cc0ba065488f4d3a161e7662a858a43d8ca386 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:01:37 +0930 Subject: [PATCH 02/13] 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 bf8bd21d8..b3ee908c6 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -247,6 +247,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 5bd55f2b9404a7ed200b5daffbd7febfd1f40380 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:02:37 +0930 Subject: [PATCH 03/13] 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 fb309315f..ac12eb8e9 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 fdc8f23d196c9fc333cbe81c1510016312f527c7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:03:37 +0930 Subject: [PATCH 04/13] 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. I need to thank everyone who gave detailed feedback, particularly: 1. Thomas H of ACINQ (https://github.com/thomash-acinq) 2. Joost Jager (https://github.com/joostjager) 3. Aditya Sharma (https://github.com/adi2011) 4. Rene Pickhardt (https://github.com/renepickhardt) 5. Bastien Teinturier (https://github.com/t-bast) 6. Valentine Wallace of LDK (https://github.com/valentinewallace) 7. Matt Corallo of LDK (https://github.com/BlueMatt) 8. Jeffrey Czyz of Square Crypto (https://github.com/jkczyz) Also @bjarnemagnussen, @ellemouton, @animatedbarber, @617a7a, @instagibbs, and @eupn. Signed-off-by: Rusty Russell --- .aspell.en.pws | 8 + 12-offer-encoding.md | 840 +++++++++++++++++++++++++++++++++ bolt12/format-string-test.json | 47 ++ bolt12/signature-test.json | 136 ++++++ 4 files changed, 1031 insertions(+) create mode 100644 12-offer-encoding.md create mode 100644 bolt12/format-string-test.json create mode 100644 bolt12/signature-test.json diff --git a/.aspell.en.pws b/.aspell.en.pws index 8265be213..4f6d9f3d6 100644 --- a/.aspell.en.pws +++ b/.aspell.en.pws @@ -388,6 +388,14 @@ IFDUP sats anysegwit 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..638f48bf7 --- /dev/null +++ b/12-offer-encoding.md @@ -0,0 +1,840 @@ +# 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 contains offer fields + which refer to its attempt to send money, including an amount. +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 + +Readers of a bolt12 string: +- 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 TLV1, TLV2, and TLV3 (of types 1, 2 and 3 respectively): + +``` +L1=H("LnLeaf",TLV1) +L1nonce=H("LnNonce"||TLV1,1) +L2=H("LnLeaf",TLV2) +L2nonce=H("LnNonce"||TLV1,2) +L3=H("LnLeaf",TLV3) +L3nonce=H("LnNonce"||TLV1,3) + +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_node_id`) + 2. data: + * [`point`:`node_id`] + +## Requirements For Offers + +A writer of an offer: + - MUST NOT set any tlv fields greater or equal to 80, or tlv field 0. + - MUST set `offer_node_id` to the node's public key to request the invoice from. + - MUST set `offer_description` to a complete description of the purpose + of the payment. + - if the chain for the invoice is not solely bitcoin: + - MUST specify `offer_chains` the offer is valid for. + - otherwise: + - MAY 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 `amount` in multiples of the minimum lightning-payable unit + (e.g. milli-satoshis for bitcoin). + - otherwise: + - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 + exponent (e.g. USD cents). + - otherwise: + - MUST NOT set `offer_amount` + - MUST NOT set `offer_currency` + - 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`: + - SHOULD ignore any invoice_request which does not use the path. + - 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 greater or equal to 80: + - 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_description` is not set: + - MUST NOT respond to the offer. + - if `offer_node_id` is not set: + - 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 user if amount of actual invoice differs significantly + from that estimate. + - SHOULD not respond to an offer if the current time is after + `offer_absolute_expiry`. + +## 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. + +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. + +`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. + +# 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_node_id` 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 no `offer_node_id` (using the +`invreq_payer_id` instead, as it in the one paying), and 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) as this is the first +TLV element, which ensures payer-provided entropy is used in hashing +for [Signature Calculation](#signature-calculation). + + +## 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_node_id`) + 2. data: + * [`point`:`node_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: 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 MAY 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 (or not set) `offer_description`, `offer_absolute_expiry`, `offer_paths` and `offer_issuer` as it would for an offer. + - MUST set `invreq_payer_id` as it would set `offer_node_id` for an offer. + - MUST NOT include `signature`, `offer_metadata`, `offer_chains`, `offer_amount`, `offer_currency`, `offer_features`, `offer_quantity_max` or `offer_node_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 greater or equal to 160. + - 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 fail the request if `invreq_payer_id` or `invreq_metadata` are not present. + - MUST fail the request if any non-signature TLV fields greater or equal to 160. + - 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 fail the request. + - MUST fail the request if `signature` is not correct as detailed in [Signature Calculation](#signature-calculation) using the `invreq_payer_id`. + - if `offer_node_id` is present (response to an offer): + - MUST fail the request if the offer fields do not exactly match a valid, unexpired offer. + - if `offer_quantity_max` is present: + - MUST fail the request if there is no `invreq_quantity` field. + - if `offer_quantity_max` is non-zero: + - MUST fail the request if `invreq_quantity` is zero, OR greater than `offer_quantity_max`. + - otherwise: + - MUST fail the 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 fail the request if `invreq_amount`.`msat` is less than the *expected amount*. + - MAY fail the request if `invreq_amount`.`msat` greatly exceeds the *expected amount*. + - otherwise (no `offer_amount`): + - MUST fail the request if it does not contain `invreq_amount`. + - SHOULD send an invoice in response using the `onionmsg_tlv` `reply_path`. + - otherwise (no `offer_node_id`, not a response to our offer): + - MUST fail the request if any of the following are present: + - `offer_chains`, `offer_features` or `offer_quantity_max`. + - MUST fail the 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 `offer_paths` if present, otherwise MUST use `invreq_payer_id` as the node id to send to. + - if `invreq_chain` is not present: + - MUST fail the request if bitcoin is not a supported chain. + - otherwise: + - MUST fail the 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_request`s 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). + +# Invoices + +Invoices are a payment request, and when the payment is made, +it 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_node_id`) + 2. data: + * [`point`:`node_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: 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*. + - otherwise (invoice not requested, e.g. for user to scan directly): + - MUST set `invreq_chain` as it would for an invoice_request. + - MUST set `offer_description` as it would for an offer. + - MUST NOT set `invreq_payer_id` or `offer_node_id`. + - MUST set `invoice_payment_hash` to the SHA256 hash of the + `payment_preimage` that will be given in return for payment. + - if `offer_node_id` is present: + - MUST set `invoice_node_id` to `offer_node_id`. + - otherwise: + - MUST set `invoice_node_id` to a valid public key. + - 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` + - MUST 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. + - if `offer_node_id` is present, and `invreq_payer_id` is identical to a previous `invoice_request`: + - MAY simply reuse the previous invoice. + - otherwise: + - MUST NOT reuse a previous invoice. + +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 `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 `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 less than type 160 do not exactly match the `invoice_request`. + - if `offer_node_id` is present (invoice_request for an offer): + - MUST reject the invoice if `invoice_node_id` is not equal to `offer_node_id`. + - otherwise (invoice_request without an offer): + - MAY reject the invoice if it cannot confirm that `invoice_node_id` is correct, out-of-band. + - otherwise: (a invoice presented without being requested, eg. scanned by user): + - MAY choose to accept or reject the invoice. + - 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. + - 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` + +## Rationale + +Because the messaging layer is unreliable, it's quite possible to +receive multiple requests for the same offer. As it's the caller's +responsibility not to reuse `invreq_payer_id` +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. + +# 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_node_id` +3. Any empty TLV fields can mean the value is supposed to be known by + other means (i.e. transport-specific), but is still hashed for sig. +4. We could upgrade to allow multiple offers in one 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 diff --git a/bolt12/format-string-test.json b/bolt12/format-string-test.json new file mode 100644 index 000000000..f150d5017 --- /dev/null +++ b/bolt12/format-string-test.json @@ -0,0 +1,47 @@ +[ + { + "comment": "A complete string is valid", + "valid": true, + "string": "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + }, + { + "comment": "+ can join anywhere", + "valid": true, + "string": "l+no1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + }, + { + "comment": "Multiple + can join", + "valid": true, + "string": "lno1qcp4256ypqpq+86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn0+0fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0+sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qs+y" + }, + { + "comment": "+ can be followed by whitespace", + "valid": true, + "string": "lno1qcp4256ypqpq+ 86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn0+ 0fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0+\nsqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43l+\r\nastpwuh73k29qs+\r y" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy+" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy+ " + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "+lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "+ lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + }, + { + "comment": "+ must be surrounded by bech32 characters", + "valid": false, + "string": "ln++o1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + } +] diff --git a/bolt12/signature-test.json b/bolt12/signature-test.json new file mode 100644 index 000000000..3956089ff --- /dev/null +++ b/bolt12/signature-test.json @@ -0,0 +1,136 @@ +[ + { + "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_node_id = Alice (privkey 0x414141...), offer_description = 'A Mathematical Treatise', offer_amount = 100, offer_currency = 'USD', invreq_payer_id = Bob (privkey 0x424242...), invreq_metadata = 0x0000000000000000", + "signature_tag": "lightninginvoicerequestsignature", + "H(signature_tag,msg)": "aefe3aa88a69772c246dcaef75ed3e7566c08ecc4e9f995233526a5651fc34cd", + "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" + } +] From 92deb1218c3c32691c1186a7e4da69899e7af7b8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:04:37 +0930 Subject: [PATCH 05/13] 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 212306330..6c4d08fa1 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -1472,6 +1472,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`] 1. subtype: `blinded_path` 2. data: From e2c3a85c4f63b117daf48c4b05be0919f245b127 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:05:37 +0930 Subject: [PATCH 06/13] fixup! BOLT 12: offers, sixth draft Regenerated bolt12/signature-test.json; for some reason jq reordered a few fields, but it now shows the complete signature. --- bolt12/signature-test.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bolt12/signature-test.json b/bolt12/signature-test.json index 3956089ff..613ed57ff 100644 --- a/bolt12/signature-test.json +++ b/bolt12/signature-test.json @@ -72,8 +72,6 @@ }, { "comment": "invoice_request test: offer_node_id = Alice (privkey 0x414141...), offer_description = 'A Mathematical Treatise', offer_amount = 100, offer_currency = 'USD', invreq_payer_id = Bob (privkey 0x424242...), invreq_metadata = 0x0000000000000000", - "signature_tag": "lightninginvoicerequestsignature", - "H(signature_tag,msg)": "aefe3aa88a69772c246dcaef75ed3e7566c08ecc4e9f995233526a5651fc34cd", "bolt12": "lnr1qqyqqqqqqqqqqqqqqcp4256ypqqkgzshgysy6ct5dpjk6ct5d93kzmpq23ex2ct5d9ek293pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpjkppqvjx204vgdzgsqpvcp4mldl3plscny0rt707gvpdh6ndydfacz43euzqhrurageg3n7kafgsek6gz3e9w52parv8gs2hlxzk95tzeswywffxlkeyhml0hh46kndmwf4m6xma3tkq2lu04qz3slje2rfthc89vss", "tlv": "invoice_request", "first-tlv": "00080000000000000000", @@ -131,6 +129,9 @@ "H(`LnBranch`,2ac9b0261d644027939d9a7bd055cb2468b79d92c6811d56a300c6b8ff97c14d432097bd1a848ab41eee3695a2c5932c4aea987b27b1a61e58ac950ecce1214a)": "608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624" } ], - "merkle": "608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624" + "merkle": "608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624", + "signature_tag": "lightninginvoice_requestsignature", + "H(signature_tag,merkle)": "aefe3aa88a69772c246dcaef75ed3e7566c08ecc4e9f995233526a5651fc34cd", + "signature": "b8f83ea3288cfd6ea510cdb481472575141e8d8744157f98562d162cc1c472526fdb24befefbdebab4dbb726bbd1b7d8aec057f8fa805187e5950d2bbe0e5642" } ] From 0be484da833a9be4f80c5a6e8c931298a4232cf9 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 12 Aug 2023 01:06:37 +0930 Subject: [PATCH 07/13] fixup! BOLT 12: offers, sixth draft bolt12/format-string-test.json is now a valid offer. --- bolt12/format-string-test.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bolt12/format-string-test.json b/bolt12/format-string-test.json index f150d5017..e97cb5b19 100644 --- a/bolt12/format-string-test.json +++ b/bolt12/format-string-test.json @@ -2,46 +2,46 @@ { "comment": "A complete string is valid", "valid": true, - "string": "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + "string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" }, { "comment": "+ can join anywhere", "valid": true, - "string": "l+no1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + "string": "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" }, { "comment": "Multiple + can join", "valid": true, - "string": "lno1qcp4256ypqpq+86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn0+0fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0+sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qs+y" + "string": "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg" }, { "comment": "+ can be followed by whitespace", "valid": true, - "string": "lno1qcp4256ypqpq+ 86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn0+ 0fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0+\nsqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43l+\r\nastpwuh73k29qs+\r y" + "string": "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg" }, { "comment": "+ must be surrounded by bech32 characters", "valid": false, - "string": "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy+" + "string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+" }, { "comment": "+ must be surrounded by bech32 characters", "valid": false, - "string": "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy+ " + "string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ " }, { "comment": "+ must be surrounded by bech32 characters", "valid": false, - "string": "+lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + "string": "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" }, { "comment": "+ must be surrounded by bech32 characters", "valid": false, - "string": "+ lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + "string": "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" }, { "comment": "+ must be surrounded by bech32 characters", "valid": false, - "string": "ln++o1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" + "string": "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" } ] From fe6c2c6f210f8a35031578873b63fa22c3dd60ee Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 14 Aug 2023 14:32:38 +0930 Subject: [PATCH 08/13] bolt12: add test vectors for offers. Signed-off-by: Rusty Russell --- bolt12/offers-test.json | 494 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 bolt12/offers-test.json diff --git a/bolt12/offers-test.json b/bolt12/offers-test.json new file mode 100644 index 000000000..9628e0e75 --- /dev/null +++ b/bolt12/offers-test.json @@ -0,0 +1,494 @@ +[ + { + "description": "Minimal bolt12 offer", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg", + "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": "... and with second blinded path via Carol (0x434343...), blinding 020202...", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese", + "field info": "path is [id=02020202..., enc=0x00*16], [id=02020202..., enc=0x22*8]", + "fields": [ + { + "type": 10, + "length": 12, + "hex": "5465737420766563746f7273" + }, + { + "type": 16, + "length": 322, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c0202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200100000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020200081111111111111111027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa20070202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200100000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020200082222222222222222" + }, + { + "type": 22, + "length": 33, + "hex": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + } + ] + }, + { + "description": "unknown odd field", + "valid": true, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg", + "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": "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_node_id", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps" + }, + { + "description": "Contains type >= 80", + "valid": false, + "bolt12": "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" + }, + { + "description": "Contains unknown feature 22", + "valid": false, + "bolt12": "lno1pgx9getnwss8vetrw3hhyucvqdqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" + }, + { + "description": "Missing offer_description", + "valid": false, + "bolt12": "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese" + }, + { + "description": "Missing offer_node_id", + "valid": false, + "bolt12": "lno1pgx9getnwss8vetrw3hhyuc" + } +] From ba2b95a98a415a24c27685a34648c1f64468a4bf Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 11 Sep 2023 12:56:09 +0930 Subject: [PATCH 09/13] 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. --- 04-onion-routing.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/04-onion-routing.md b/04-onion-routing.md index 6c4d08fa1..f68a0da0a 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -243,7 +243,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 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: - `max_cltv_expiry` to the largest block height at which the route is allowed to be used, starting from the final node and adding `encrypted_data_tlv.payment_relay.cltv_expiry_delta` at each hop. @@ -1541,7 +1542,8 @@ 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`. + - SHOULD forward the message using `onion_message` to the next peer indicated by either `next_node_id` + or the channel counterparty with `short_channel_id`. - if it forwards the message: - MUST set `blinding` in the forwarded `onion_message` to the next blinding as calculated in [Route Blinding](#route-blinding). - otherwise (it is the final node): From 3db064e4cb943885ff5557181ae655d03ac8ce79 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 11 Sep 2023 12:57:05 +0930 Subject: [PATCH 10/13] 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 b3ee908c6..fbf1a60c5 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -246,6 +246,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 f68a0da0a..c5826e19b 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -1485,7 +1485,7 @@ even, of course!). 1. subtype: `blinded_path` 2. data: - * [`point`:`first_node_id`] + * [`sciddir_or_pubkey`:`first_node_id`] * [`point`:`blinding`] * [`byte`:`num_hops`] * [`num_hops*onionmsg_hop`:`path`] From 9118a8f6be5bc08b7bc28fc2b4ebfed433900a9c Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 21 Sep 2023 09:46:43 +0930 Subject: [PATCH 11/13] fixup! bolt12: add test vectors for offers. --- bolt12/offers-test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bolt12/offers-test.json b/bolt12/offers-test.json index 9628e0e75..15d5edfd5 100644 --- a/bolt12/offers-test.json +++ b/bolt12/offers-test.json @@ -346,7 +346,7 @@ { "description": "unknown odd field", "valid": true, - "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg", + "bolt12": "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs", "field info": "type 33 is 'helloworld'", "fields": [ { From a3f3815575ec38c1706ba3085fa6406a3cb4c601 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 10 Feb 2024 23:01:31 +0000 Subject: [PATCH 12/13] Define a URI encoding for BOLT12 offers --- 12-offer-encoding.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/12-offer-encoding.md b/12-offer-encoding.md index 638f48bf7..8f788d5b1 100644 --- a/12-offer-encoding.md +++ b/12-offer-encoding.md @@ -186,6 +186,12 @@ distinct TLV ranges. The human-readable prefix for offers is `lno`. +If a URI scheme is desired, offers should be encoded in standard `bitcoin:` +URIs as defined in BIP 21, as query parameter with a key of `b12` and a value +of the ASCII encoding defined above. Both the key and value are +case-independent and, for compactness, should be uppercase for URIs which are +encoded in QR codes. + ## TLV Fields for Offers 1. `tlv_stream`: `offer` From 5809dfd1fce7fe1233812acb25bd37b908318968 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 10 Feb 2024 23:13:59 +0000 Subject: [PATCH 13/13] Define a mechanism for looking up an offer from a user/domain pair --- 04-onion-routing.md | 13 +++++++++++++ 12-offer-encoding.md | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/04-onion-routing.md b/04-onion-routing.md index c5826e19b..d45a00477 100644 --- a/04-onion-routing.md +++ b/04-onion-routing.md @@ -1482,6 +1482,19 @@ even, of course!). 1. type: 68 (`invoice_error`) 2. data: * [`tlv_invoice_error`:`inverr`] + 1. type: 70 (`offer_request`) + 2. data: + * [`u8`:`name_len`] + * [`name_len*byte`:`name`] + * [`u8`:`domain_len`] + * [`name_len*byte`:`domain`] + 1. type: 72 (`offer_response`) + 2. data: + * [`u8`:`name_len`] + * [`name_len*byte`:`name`] + * [`u8`:`domain_len`] + * [`name_len*byte`:`domain`] + * [`tlv_offer`:`offer`] 1. subtype: `blinded_path` 2. data: diff --git a/12-offer-encoding.md b/12-offer-encoding.md index 8f788d5b1..cd525f74e 100644 --- a/12-offer-encoding.md +++ b/12-offer-encoding.md @@ -10,6 +10,7 @@ * [Invoice Requests](#invoice-requests) * [Invoices](#invoices) * [Invoice Errors](#invoice-errors) + * [Offer Requests](#offer-requests) # Limitations of BOLT 11 @@ -824,6 +825,26 @@ 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. +# Offer Requests + +`bitcoin:` URIs with a query paraketer key (case-insensitive) of `omlookup` +indicate that an offer should be requested over onion messages. The value in +the query parameter should be a hex-encoded `blinded_path` which represents a +(FIXME: Use a specific name here for blinded paths for onion messages) +recipient to send an `offer_request`-containing onion message to. These URIs +only make sense in the context of a BIP XXXX resolution, where a sender is +resolving a `name`/`domain` pair to payment instructions. + +## Requirements +The sender of an `offer_request`-containing onion message: + * MUST set `name` and `domain` to printable ASCII charachters only, + * MUST set `domain` to a canonically-encoded domain name, ending in a "." + +The recipient of an `offer_request`-containing onion message: + * if the `name` and `domain` describe a recipient it is responsible for, SHOULD + reply with an `offer_response`-containing onion message with the same `name` + and `domain`. + # FIXME: Possible future extensions: 1. The offer can require delivery info in the `invoice_request`.