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.
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)
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 BlindSignature
s to the wallet together with the successful payment status.
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.
Here we describe how the mint generates BlindSignature
s 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 BlindSignature
s. 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.
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 BlindSignature
s due to overpaid fees.
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 BlindedMessage
s 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.