Skip to content

Commit

Permalink
BOLT 12: allow replacement of previous invoices, make signature on in…
Browse files Browse the repository at this point in the history
…voice_request compulsory

We can ask the vendor to discard the previous invoice in the case of
stuck payments: this discarded invoice gets mirrored into the new
invoice, so if they actually don't do it and take both payments we can
prove it.

We need a signature using payer_key here, otherwise someone else could
cancel our invoice.  We previously only needed that signature for
recurrence; now we rename it and make it always present for
simplicity.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  • Loading branch information
rustyrussell committed Apr 16, 2021
1 parent 41b5680 commit fc8aab7
Showing 1 changed file with 28 additions and 9 deletions.
37 changes: 28 additions & 9 deletions 12-offer-encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,10 @@ invoices is `lnr`.
1. type: 50 (`payer_info`)
2. data:
* [`...*byte`:`blob`]
1. type: 242 (`recurrence_signature`)
1. type: 56 (`replace_invoice`)
2. data:
* [`sha256`:`payment_hash`]
1. type: 242 (`payer_signature`)
2. data:
* [`bip340sig`:`sig`]

Expand All @@ -541,6 +544,7 @@ The writer of an invoice_request:
- MUST remember the secret key corresponding to `payer_key`.
- MUST set `offer_id` to the Merkle root of the offer as described in [Signature Calculation](#signature-calculation).
- MUST NOT set or imply any `chain_hash` not set or implied by the offer.
- MUST set `payer_signature` `sig` as detailed in [Signature Calculation](#signature-calculation) using the `payer_key`.
- if the offer had a `quantity_min` or `quantity_max` field:
- MUST set `quantity`
- MUST set it within that (inclusive) range.
Expand All @@ -554,6 +558,9 @@ The writer of an invoice_request:
- if it sets `amount`:
- MUST specify `amount`.`msat` as greater or equal to amount expected by the offer
(before any proportional period amount).
- if the sender has a previous unpaid invoice (for the same offer) which it wants to cancel:
- MUST set `payer_key` to the same as the previous invoice.
- MUST set `replace_invoice` to the `payment_hash` or the previous invoice.
- if the offer contained `recurrence`:
- for the initial request:
- MUST use a unique `payer_key`.
Expand All @@ -568,8 +575,6 @@ The writer of an invoice_request:
- otherwise:
- MUST NOT include `recurrence_start`
- MAY set `payer_info` to arbitrary data to be reflected into the invoice.
- MUST set `recurrence_signature` `sig` as detailed in
[Signature Calculation](#signature-calculation) using the `payer_key`.
- if the offer contained `recurrence_limit`:
- MUST NOT send an `invoice_request` for a period greater than `max_period`
- SHOULD NOT send an `invoice_request` for a period which has
Expand All @@ -584,7 +589,6 @@ The writer of an invoice_request:
yet begun.
- otherwise:
- MUST NOT set `recurrence_counter`.
- MUST NOT set `recurrence_signature`.
- MUST NOT set `recurrence_start`

The reader of an invoice_request:
Expand All @@ -593,6 +597,8 @@ The reader of an invoice_request:
- MUST fail the request if `features` contains unknown even bits.
- MUST fail the request if `offer_id` is not present.
- MUST fail the request if the `offer_id` does not refer to an unexpired offer.
- MUST fail the request if there is no `payer_signature` field.
- MUST fail the request if `payer_signature` is not correct.
- if the offer had a `quantity_min` or `quantity_max` field:
- MUST fail the request if there is no `quantity` field.
- MUST fail the request if there is `quantity` is not within that (inclusive) range.
Expand All @@ -610,10 +616,13 @@ The reader of an invoice_request:
- otherwise:
- MUST fail the request if it does not contain `amount`.
- MUST use the request `amount` as the *base invoice amount*. (Note: invoice amount can be further modified by recurrence below)
- if the offer has a `replace_invoice`:
- if the `payment_hash` refers to an unpaid invoice for the same `offer_id` and `payer_key`:
- MUST immediately expire/remove that unpaid invoice such that it cannot be paid in future.
- otherwise:
- MUST fail the request.
- if the offer had a `recurrence`:
- MUST fail the request if there is no `recurrence_counter` field.
- MUST fail the request if there is no `recurrence_signature` field.
- MUST fail the request if `recurrence_signature` is not correct.
- if the offer had `recurrence_base` and `start_any_period` was 1:
- MUST fail the request if there is no `recurrence_start` field.
- MUST consider the period index for this request to be the
Expand Down Expand Up @@ -643,7 +652,6 @@ The reader of an invoice_request:
of the previous period.
- otherwise (the offer had no `recurrence`):
- MUST fail the request if there is a `recurrence_counter` field.
- MUST fail the request if there is a `recurrence_signature` field.

## Rationale

Expand All @@ -656,8 +664,9 @@ precisely, and if it isn't in the offer the defaults provide some
slack, without allowing commitments into the far future.

To avoid probing (should a payer_key become public in some way), we
require a signature for recurring invoice requests; this ensures that
no third party can determine how many invoices have been paid already.
require a signature; this ensures that no third party can determine
how many invoices have been paid already in the case of recurring
requests, and disallows replacement of old invoices by third parties.

`payer_info` might typically contain information about the derivation of the
`payer_key`. This should not leak any information (such as using a simple
Expand All @@ -674,6 +683,12 @@ any). Note that for recurring invoices with `proportional_amount`
set, the `amount` in the invoice request will be scaled by the time in
the period; the sender should not attempt to scale it.

`replace_invoice` allows the mutually-agreed removal of and old unpaid
invoice; this can be used in the case of stuck payments. If
successful in replacing the stuck invoice, the sender may make a
second payment such that it can prove double-payment should the
receiver still accept the first, delayed payment.

# Invoices

Invoices are a request for payment, and when the payment is made they
Expand Down Expand Up @@ -753,6 +768,9 @@ using `onion_message` `invoice` field.
1. type: 52 (`refund_signature`)
2. data:
* [`bip340sig`:`payer_signature`]
1. type: 56 (`replace_invoice`)
2. data:
* [`sha256`:`payment_hash`]
1. type: 240 (`signature`)
2. data:
* [`bip340sig`:`sig`]
Expand Down Expand Up @@ -833,6 +851,7 @@ A writer of an invoice:
- MUST set (or not set) `recurrence_start` exactly as the invoice_request did.
- MUST set `payer_key` exactly as the invoice_request did.
- MUST set (or not set) `payer_info` exactly as the invoice_request did.
- MUST set (or not set) `replace_invoice` exactly as the invoice_request did.
- MUST begin `description` with the `description` from the offer.
- MAY append additional information to `description` (e.g. " +shipping").
- if it does not set `amount` to the *base invoice amount* calculated from the invoice_request:
Expand Down

0 comments on commit fc8aab7

Please sign in to comment.