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 BlindedMessages 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 from 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/bolt11 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+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 inputs provided were
greater than the total_amount_paid - fees.
If the
overpaid_fees = input_amount - fees - total_paid is
positive, Bob decomposes it to amounts supported by
the keyset, typically 2^n, and 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 all blind 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 outputs field carries the
BlindedMessages.
The mint Bob then responds with a
PostMeltQuoteBolt11Response:
{
"quote": <str>,
"request": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"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": "019e6d5a-2347-7000-813e-a11ea6e419e7",
"inputs": [
{
"amount": 4,
"id": "009a1f293253e41e",
"secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5",
"C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011"
},
{
"amount": 8,
"id": "009a1f293253e41e",
"secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad",
"C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9"
}
],
"outputs": [
{
"amount": 0,
"id": "009a1f293253e41e",
"B_": "03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4"
}
]
}'
Everything here is the same as in NUT-05 except for outputs.
Alice MUST set the
amount of each blank output to 0, and
Bob MUST ignore it, imprinting the
amount himself.
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.
Mint info setting
The NUT-06 nuts object
indicates support for this feature:
{
"8": {
"supported": true
}
}