Skip to content

NUT-08: Lightning fee return

optional

depends on: NUT-05


This document describes how the overpaid Lightning fees are handled and extends NUT-05 which describes melting tokens (i.e. paying a Lightning invoice). In short, a wallet includes blank outputs when paying a Lightning invoice which can be assigned a value by the mint if the user has overpaid Lightning fees. This can be the case due to the unpredictability of Lightning network fees. To solve this issue, we introduce so-called blank outputs which are blinded messages with an undetermined value.

The problem is also described in this gist.

Description

Before requesting a Lightning payment as described in NUT-05, Alice produces a number of BlindedMessage which are similar to ordinary blinded messages but their value is yet to be determined by the mint Bob and are thus called blank outputs. The number of necessary blank outputs is max(ceil(log2(fee_reserve)), 1) which ensures that there is at least one output if there is any fee. If the fee_reserve is 0, then the number of blank outputs is 0 as well. The blank outputs will contain the overpaid fees that will be returned by the mint to the wallet.

This code calculates the number of necessary blank outputs in Python:

def calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int:
    assert fee_reserve_sat >= 0, "Fee reserve can't be negative."
    if fee_reserve_sat == 0:
        return 0
    return max(math.ceil(math.log2(fee_reserve_sat)), 1)

Example

The wallet wants to pay an invoice with amount := 100 000 sat and determines by asking the mint that fee_reserve is 1000 sats. The wallet then provides 101 000 sat worth of proofs and 10 blank outputs to make the payment (since ceil(log2(1000))=ceil(9.96..)=10). The mint pays the invoice and determines that the actual fee was 100 sat, i.e, the overpaid fee to return is fee_return = 900 sat. The mint splits the amount 900 into summands of 2^n which is 4, 128, 256, 512. The mint inserts these amounts into the blank outputs it received form the wallet and generates 4 new promises. The mint then returns these BlindSignatures to the wallet together with the successful payment status.

Wallet flow

The wallet asks the mint for the fee_reserve for paying a specific bolt11 invoice of value amount by calling POST /v1/melt/quote as described in NUT-05. The wallet then provides a PostMeltBolt11Request to POST /v1/melt/bolt11 that has (1) proofs of the value amount+fee_reserve, (2) the bolt11 invoice to be paid, and finally, as a new entry, (3) a field outputs that has n_blank_outputs blinded messages that are generated before the payment attempt to receive potential overpaid fees back to her.

Mint flow

Here we describe how the mint generates BlindSignatures for the overpaid fees. The mint Bob returns in PostMeltQuoteBolt11Response the field change ONLY IF Alice has previously provided outputs for the change AND if the Lightning actual_fees were smaller than the fee_reserve.

If the overpaid_fees = fee_reserve - actual_fees is positive, Bob decomposes it to values of 2^n (as in NUT-00) and then imprints them into the blank_outputs provided by Alice.

Bob then signs these blank outputs (now with the imprinted amounts) and thus generates BlindSignatures. Bob then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees.

Importantly, while Bob does not necessarily return the same number of blind signatures as it received blank outputs from Alice (since some of them may be of value 0), Bob MUST return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if Bob receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, Bob will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, Alice can map the blind signatures returned from Bob to the blank outputs it provided so that she can further apply the correct unblinding operations on them.

Example

Request of Alice:

POST https://mint.host:3338/v1/melt/bolt11

With the data being of the form PostMeltBolt11Request:

{
  "quote": <str>,
  "inputs": <Array[Proof]>,
  "outputs": <Array[BlindedMessage]> <-- New
}

where the new output field carries the BlindMessages.

The mint Bob then responds with a PostMeltQuoteBolt11Response:

{
  "quote": <str>,
  "amount": <int>,
  "fee_reserve": <int>,
  "state": <str_enum[STATE]>,
  "expiry": <int>,
  "payment_preimage": <str|null>,
  "change": <Array[BlindSignature]> <-- New
}

where the new change field carries the returned BlindSignatures due to overpaid fees.

Example

Request of Alice with curl:

curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \
'{
  "quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP",
  "inputs": [
    {
      "amount": 4,
      "id": "009a1f293253e41e",
      "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5",
      "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011",
    },
    {
      "amount": 8,
      "id": "009a1f293253e41e",
      "secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad",
      "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9",
    }
  ],
  "outputs": [
    {
      "amount": 1,
      "id": "009a1f293253e41e",
      "B_": "03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4"
    }
  ]
}'

Everything here is the same as in NUT-05 except for outputs. The amount field in the BlindedMessages here are ignored by Bob so they can be set to any arbitrary value by Alice (they should be set to a value, like 1 so potential JSON validations do not error).

If the mint has made a successful payment, it will respond the following.

Response PostMeltQuoteBolt11Response from Bob:

{
  "state": "PAID",
  "payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b",
  "change": [
    {
      "id": "009a1f293253e41e",
      "amount": 2,
      "C_": "03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0"
    }
  ]
}

The field change is an array of BlindSignatures that account for the overpaid fees. Notice that the amount has been changed by the mint. Alice must take these and generate Proofs by unblinding them as described in NUT-00 and as she does in NUT-04 when minting new tokens. After generating the Proofs, Alice stores them in her database.