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/01-messaging.md b/01-messaging.md index bcd11d999..fbf1a60c5 100644 --- a/01-messaging.md +++ b/01-messaging.md @@ -243,9 +243,17 @@ 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)) +* `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. ## Setup Messages diff --git a/04-onion-routing.md b/04-onion-routing.md index 212306330..d45a00477 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. @@ -1472,10 +1473,32 @@ 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. 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: - * [`point`:`first_node_id`] + * [`sciddir_or_pubkey`:`first_node_id`] * [`point`:`blinding`] * [`byte`:`num_hops`] * [`num_hops*onionmsg_hop`:`path`] @@ -1532,7 +1555,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): diff --git a/12-offer-encoding.md b/12-offer-encoding.md new file mode 100644 index 000000000..cd525f74e --- /dev/null +++ b/12-offer-encoding.md @@ -0,0 +1,867 @@ +# 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) + * [Offer Requests](#offer-requests) + +# 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`. + +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` +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. + +# 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`. +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..e97cb5b19 --- /dev/null +++ b/bolt12/format-string-test.json @@ -0,0 +1,47 @@ +[ + { + "comment": "A complete string 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": "+ 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..15d5edfd5 --- /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": "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": "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" + } +] diff --git a/bolt12/signature-test.json b/bolt12/signature-test.json new file mode 100644 index 000000000..613ed57ff --- /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_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", + "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" + } +] 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' \