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

Rusty Russell rusty at rustcorp.com.au
Fri Nov 8 03:09:15 UTC 2019


Yaacov Akiba Slama <ya at slamail.org> writes:
> 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.

Hi Yaacov,

        I've been pondering this since reading your comment on the PR!

        As a fan of standards, I am attracted to UBL (I've chaired an
OASIS TC in the past and have great respect for them); as a fan of
simplicity I am not.  Forcing UBL implementation on wallet providers is
simply not going to happen, whatever I were to propose.

	We also don't want duplication; what if the "UBL field" were to
say I were selling you a bridge for $1 and the description and amount
fields actually said I was selling you a coffee for $3?

	However, since invoices/offers and UBL are both structures, we
should have an explicit mapping between the two.  What fields should
have their own existence in the invoice/offer and what should be in a
general UBL field is a question we have to think on further.

        Anyway, you'll have to bear with me as I read this 172 page
standard...

Cheers,
Rusty.

> 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