[Lightning-dev] [VERY ROUGH DRAFT] BOLT 12: Offers

Yaacov Akiba Slama ya at slamail.org
Tue Nov 5 08:49:54 UTC 2019


Hi Rusty,

It seems that there are two kind of TLV fields in your proposition:
1) LN specific fields like `num_paths` and `payment_preimage`.
2) "Business" fields like `address1` and `currency`.
I understand the need to define and include the first category, but I 
don't think that we need or can define the second category. These fields 
already exists in software like crm, erp, etc.. and are well defined by 
standard body.
My suggestion is to have a generic field containing well defined 
structured and standardized data. See for instance 
https://en.wikipedia.org/wiki/EDIFACT and/or 
https://en.wikipedia.org/wiki/Universal_Business_Language.
What do you think?
PS: Sorry for crossposting here and in 
https://github.com/lightningnetwork/lightning-rfc/pull/694
--yas

On 05/11/2019 06:23, Rusty Russell wrote:
> Hi all,
>
>          This took longer than I'd indicated; for that I'm sorry.
> However, this should give us all something to chew on.  I've started
> with a draft "BOLT 12" (it might be BOLT 13 by the time it's finalized
> though!).
>
> I've also appended indications where we touch other BOLTs:
> 1. BOLT 7 gains a message/reply system, encoded like htlc onions and
>     failure messages.
> 2. BOLT 11 gains a `q` field for quantity; this avoids changing the
>     description when the user requests an invoice for more than one of something
>     (since changing the description between offer and invoice requires user
>     interaction: it's the *invoice* which you are committing to).
>
> There's definite handwaving in here; let's see if you can find it!
>
> Cheers,
> Rusty.
>
> # BOLT #12: Offer Protocols for Lightning Payments
>
> An higher-level, QR-code-ready protocol for dealing with invoices over
> Lightning.  There are two simple flows supported: in one, a user gets
> an offer (`lno...`) and requests an invoice over the lightning
> network, obtaining one (or an error) in reply.  In the other, a user
> gets an invoice request (`lni...`), and sends the invoice over the
> lightning network, retreiving an empty reply.
>
> # Table of Contents
>
>    * [Offers](#offers)
>      * [Encoding](#encoding)
>      * [TLV Fields](#tlv-fields)
>    * [Invrequests](#invrequests)
>      * [Encoding](#encoding)
>      * [TLV Fields](#tlv-fields)
>
> # Offers
>
> Offers supply a reader with enough information to request one or more
> invoices via the lightning network itself.
>
> ## Encoding
>
> The human-readable part of a Lightning offer is `lno`.  The data part
> consists of three parts:
>
> 1. 0 or more [TLV](01-messaging.md#type-length-value-format) encoded fields.
> 2. A 32-byte nodeid[1]
> 3. 64-byte signature of SHA256(hrp-as-utf8 | tlv | nodeid).
>
> ## TLV Fields
>
> The TLV fields define how to get the invoice, and what it's for.
> Each offer has a unique `offer_idenfitier` so the offering node can
> distinguish different invoice requests.
>
> Offers can request recurring payments of various kinds, and specify
> what base currency they are calculated in (the actual amount will be
> in the invoice).
>
> `additional_data` is a bitfield which indicates what information the
> invoice requester should (odd) or must (even) supply:
> 1. Bits `0/1`: include `delivery_address`
> 2. Bits `2/3`: include `delivery_telephone_number`
> 3. Bits `4/5`: include `voucher_code`
> 4. Bits `6/7`: include `refund_proof`
>
> `refund_for` indicates an offer for a (whole or part) refund for a
> previous invoice, as indicated by the `payment_hash`.
>
> 1. tlvs: `offer`
> 2. types:
>      1. type: 1 (`paths`)
>      2. data:
>          * [`u16`:`num_paths`]
>          * [`num_paths*path`:`path`]
>      1. type: 2 (`description`)
>      2. data:
>          * [`...*byte`:`description`]
>      1. type: 3 (`expiry`)
>      2. data:
>          * [`tu64`:`seconds_from_epoch`]
>      1. type: 4 (`offer_identifier`)
>      2. data:
>          * [`...*byte`:`id`]
>      1. type: 5 (`amount`)
>      2. data:
>          * [`4*byte`:`currency`]
>          * [`tu64`:`amount`]
>      1. type: 6 (`additional_data`)
>      2. data:
> 	    * [`...*byte`:`rbits`]
>      1. type: 7 (`recurrance`)
>      2. data:
> 	    * [`byte`:`time_unit`]
> 		* [`u32`:`period`]
> 		* [`tu32`:`number`]
>      1. type: 8 (`recurrance_base`)
>      2. data:
> 		* [`u32`:`basetime`]
> 		* [`tu32`:`paywindow`]
>      1. type: 9 (`quantity`)
>      2. data:
> 		* [`tu64`:`max`]
>      1. type: 10 (`refund_for`)
>      2. data:
>          * [`32*byte`:`payment_hash`]
>
> 1. subtype: `path`
> 2. data:
>     * [`u16`:`num_hops`]
>     * [`num_hops*hop`:`hops`]
>
> 1. subtype: `hop`
> 2. data:
>     * [`pubkey`:`nodeid`]
>     * [`short_channel_id`:`short_channel_id`]
>     * [`u16`:`flen`]
>     * [`flen*byte`:`features`]
>
> ## Requirements For Offers And Invrequests
>
> A writer of an offer or an invrequest:
>    - if it is connected only by private channels:
>      - MUST include `paths` containing a path to the node.
>    - otherwise:
>      - MAY include `paths` containing a path to the node.
>    - MUST describe the item(s) being offered or purpose of invoice in `description`.
>    - MUST include `expiry` if the offer/invrequest will not be valid after some time.
>    - if it includes `expiry`:
>      - MUST set `seconds_from_epoch` to the expiry time in seconds since 1970 UTC.
>
> ## Requirements For Offers
>
> A writer of an offer:
>    - MUST use a unique `offer_idenfitier` for each offer.
>    - MAY include `recurrence` to indicate offer should trigger time-spaced
>      invoices.
>    - MUST include `amount` if it includes `recurrence`.
>    - if it includes `amount`:
>      - MUST specify `currency` as the ISO 4712 or BIP-0173, padded with zero bytes if required
> 	- MUST specify `amount` to the amount expected for the invoice, as the ISO 4712 currency unit multiplied by exponent, OR the BIP-0173 minimum unit (eg. `satoshis`).
>    - if it requires specific fields in the invoice:
>      - MUST set the corresponding even bits in the `additional_data` field
>
> A reader of an offer:
>    - SHOULD gain user consent for recurring payments.
>    - SHOULD allow user to view and cancel recurring payments.
>    - SHOULD gain user consent to send `delivery_` fields.
>    - if it uses `amount` to provide the user with a cost estimate:
> 	- MUST warn user if amount of actual invoice differs significantly
> 		from that expectation.
>    - FIXME: more!
>
> ## Recurrance
>
> Some offers are *periodic*, such as a subscription service or monthly
> dues, in that payment is expected to be repeated.  There are many
> different flavors of repetition, consider:
>
> * Payments due on the first of every month, for 6 months.
> * Payments due on every Monday, 1pm Pacific Standard Time.
> * Payments due once a year:
>     * which must be made on January 1st, or
>     * which are only valid if started January 1st 2020, or
>     * which if paid after January 1st you (over) pay the full rate first year, or
>     * which if paid after January 1st are paid pro-rata for the first year, or
>     * which repeat from whenever you made the first payment
>
> Thus, each payment has:
> 1. A `time_unit` defining 0 (seconds), 1 (days), 2 (months), 3 (years).
> 2. A `period`, defining how often (in `time_unit`) it has to be paid.
> 3. An optional `number` of total payments to be paid.
> 4. An optional `basetime`, defining when the first payment applies
>     in seconds since 1970-01-01 UTC.
> 5. An optional `paywindow`, defining how many seconds into the period
>     a payment will be accepted: 0xFFFFFFFF being a special value meaning
>     "any time during the period, but you will have to pay proportionally
>     to the remaining time in the period".
>
> Note that the `expiry` field covers the case where an offer is no longer
> valid after January 1st 2020.
>
> ## Default Offer
>
> The "default offer" of a node is a nominal offer used to send
> unsolicited payments.  It is generally not actually sent, but can be
> used by any other node as if it has been.  It has the following
> fields:
>
> * `offer_idenfitier`: zero-length
> * `d`: any
> * `n`: the node id of the recipient.
>
> ## Invoice Request Encoding
>
> Once it has an offer, the node can request an actual invoice using the
> `invoice_req` message inside `directed`'s `onion_routing_packet`.  It
> would expect an `invoice_or_error_tlv` inside the `directed_reply`
> message.
>
> This includes a `tag` it can use to identify replies, the
> `offer_idenfitier` from the offer, a `key` it can use to prove it was
> the requester of this invoice, a `recurrence` number if this
> is a payment in a recurring series, and other codes as required.
>
> The `refund_proof` refers to a previous invoice paid by the sender for
> the specific case of a `refund_for` offer.  It provides proof of
> payment (the `payment_preimage` and also a signature of the
> `payment_hash` from the `key` which requested the being-refunded
> invoice (which does *not* have to be the same as this `key`!).
>
> 1. tlvs: `invoice_request_tlv`
> 2. types:
>     1. type: 1 (`tag`)
>     2. data:
>        * [`...*byte`:`tag`]
>     1. type: 2 (`offer_identifier`)
>     2. data:
>        * [`...*byte`:`id`]
>     1. type: 3 (`key`)
>     2. data:
>       * [`32`:`key`]
>     1. type: 4 (`recurrence`)
>     2. data:
>       * [`tu64`:`number`]
>     1. type: 5 (`quantity`)
>     2. data:
>       * [`tu64`:`n`]
>     1. type: 6 (`delivery_address_name`)
>     2. data:
> 	 * [`...*byte`:`name`]
>     1. type: 7 (`delivery_address1`)
>     2. data:
> 	 * [`...*byte`:`address1`]
>     1. type: 8 (`delivery_address2`)
>     2. data:
> 	 * [`...*byte`:`address2`]
>     1. type: 9 (`delivery_city`)
>     2. data:
> 	 * [`...*byte`:`city`]
>     1. type: 10 (`delivery_state_province_or_region`)
>     2. data:
> 	 * [`...*byte`:`state_province_or_region`]
>     1. type: 11 (`delivery_zip_or_postal_code`)
>     2. data:
> 	 * [`...*byte`:`zip_or_postal_code`]
>     1. type: 12 (`delivery_country`)
>     2. data:
> 	 * [`2*byte`:`country_code`]
>     1. type: 13 (`delivery_telephone_number`)
>     2. data:
> 	 * [`...*byte`:`tel`]
>     1. type: 14 (`voucher_code`)
>     2. data:
> 	 * [`...*byte`:`code`]
>     1. type: 15 (`refund_proof`)
>     2. data:
>        * [`32*byte`:`payment_preimage`]
>        * [`signature`:`signature`]
>
> ## Requirements
>
> FIXME: many more
> Sender MUST use ISO 3166 alpha-2 code for `delivery_country`.
> Sender MUST set offer_identifier to match offer.
> Sender MUST include `key`
>     - SHOULD use a transient unpredictable key
>     - MUST reuse key for successive recurring invoices.
> Sender MUST set `recurrence` for recurring invoices.
>
> Receiver MUST check `offer_identifier`
> Receiver MUST check `delivery_` fields.
> Receiver MUST check `recurrence`.
> Receiver MUST check `amount`.
>
> 1. tlvs: `invoice_or_error_tlv`
> 2. types:
>     1. type: 1 (`tag`)
>     2. data:
>       * [`...*byte`:`tag`]
>     1. type: 3 (`omitted`)
>     2. data:
> 	 * [`...*u64`:`omitted_fields`]
>     1. type: 4 (`invoice`)
>     2. data:
>       * [`...*byte`:`invoice`]
>     1. type: 5 (`message`)
>       * [`...*byte`:`message`]
>     1. type: 6 (`replacement`)
>     2. data:
>       * [`signature`:`signature`]
>       * [`...*byte`:`offer`]
>
> Sender:
> - MUST copy `tag` from sender.
> - MUST omit fields it does not use, and place number in order in `omitted_fields`.
> - if it includes `invoice`:
>    - MUST not include `message`
>    - MUST not include `replacement`
>    - MUST [merkle fields it used](#merkle-calculation) and place that in invoice `s` field.
> - otherwise, if it includes `replacement`:
>    - MAY include `message`
> - otherwise:
>    - MUST include `message` describing the error.
>
> Receiver:
> - MUST check that `tag` matches req.
> - if `replacement`:
>    - MUST fail if `signature` does not sign `offer` with same key as original.
>    - MUST only fetch once (no double-redirects!)
> - if description or amount significantly changes, must re-ask user.
>    - SHOULD note if description simply has something appended (eg "+ postage").
> - within invoice:
>      - MUST check that `s` matches merkle of fields, minus `omitted`.
> 	- MUST check that no vital fields are in `omitted`.
> 	- MUST check that `d` matches `description`
>      - MUST check that `q` DNE if `quantity` DNE, otherwise is equal.
>      - Must check valid signature, etc.
>
> ## Merkle Calculation
>
> 1. For each `invoice_req_tlv` field in ascending `tlv` type order:
>     1. If the field was omitted, it is added to `omitted_fields`.
>     2. Otherwise, the immediate parent merkle is:
>     
>        SHA256(SHA256(`tag` | `be64-n`) | SHA256(`tlv-value`))
>
>        Where `be64-n` is a 64-bit big-endian counter starting at 0 and
>        incrementing for each leaf.
>
>     3. Order these nodes in increasing SHA256(`tag` | `be64-n`) order.
>
> 2. Create additional leaves until `be64-n` is the next power of 2:
>
>        SHA256(`tag` | `be64-n`)
>
> 3. Combine adjacent leaves using SHA256(leaf1 | leaf2) until none remain.
>
> By creating adjacent leaves using the `tag` field and a counter, and
> sorting the leaves, the only significant information revealed by a
> merkle proof on a node is the depth of tree (which implies the total
> number of TLV fields).
>
> # InvRequests
>
> There are times when it makes sense to request an invoice over another
> medium, such as HTTP or a QR code.
>
> ## Encoding
>
> The human-readable part of a Lightning invrequest is `lni`.  The data part
> consists of three parts:
>
> 1. 0 or more [TLV](01-messaging.md#type-length-value-format) encoded fields.
> 2. A 32-byte nodeid[1]
> 3. 64-byte signature of SHA256(hrp-as-utf8 | tlv | nodeid).
>
> ## TLV Fields
>
> 1. tlvs: `invreq`
> 2. types:
>      1. type: 1 (`paths`)
>      2. data:
>          * [`u16`:`num_paths`]
>          * [`num_paths*path`:`path`]
>      1. type: 2 (`description`)
>      2. data:
>          * [`...*byte`:`description`]
>      1. type: 3 (`expiry`)
>      2. data:
>          * [`tu64`:`seconds_from_epoch`]
>      1. type: 4 (`amount`)
>      2. data:
>          * [`tu64`:`millisatoshis`]
>
> The fields `paths`, `description`, and `expiry` fields are the
> same as those for offers; the optional `amount` field describes the
> amount an invoice will be accepted for.
>
> Upon parsing and accepting an `invreq`, the node sends an
> `invoice_or_error_tlv` within an onion.  The reply is empty.
>
> ## Requirements
>
> The requirements for `paths`, `description` and `expiry` are
> [described above](#requirements-for-offers-and-invrequests).
>
> FIXME: More.
>
> [1] Assuming we go for Schnorr sigs and 32-byte pubkeys.
> ----
> Addendum: BOLT #7: P2P Node and Channel Discovery and Directed Messages
> ...
> # Directed Messages
>
> Directed messages allow peers to use existing connections to query for
> invoices (see [BOLT 12](12-offer-encoding.md)).  Like gossip messages,
> they are not associated with a particular local channel.
>
> The `id` is a unique, transient identifier between the peers, used to
> identify match messages and replies.
>
> ## The `directed` and `directed_reply` Messages
>
> 1. type: 384 (`directed`) (`option_directed_messages`)
> 2. data:
>      * [`chain_hash`:`chain_hash`]
>      * [`u64`:`id`]
>      * [`1366*byte`:`onion_routing_packet`]
>
> 1. type: 384 (`directed_reply`) (`option_directed_messages`)
> 2. data:
>      * [`chain_hash`:`chain_hash`]
>      * [`u64`:`id`]
>      * [`u16`:`len`]
>      * [`len*byte`:`reply`]
>
> ## Requirements
>
> FIXME: similar to update_add_htlc and update_fail_htlc.
> FIXME: define reasonable timeout after which you can forget if not replied?
> _______________________________________________
> Lightning-dev mailing list
> Lightning-dev at lists.linuxfoundation.org
> https://lists.linuxfoundation.org/mailman/listinfo/lightning-dev




More information about the Lightning-dev mailing list