diff --git a/19.md b/19.md new file mode 100644 index 0000000..1da9290 --- /dev/null +++ b/19.md @@ -0,0 +1,144 @@ +# NUT-19: Signature on Mint Quote +`optional` + +This NUT defines a protocol extension that enables signature-based authentication for mint quote redemption. When requesting a mint quote, clients can provide a public key. The mint will then require a valid signature from the corresponding secret key before processing the mint. +Caution: If the mint does not support this NUT, anyone with the mint quote id will be able to mint even without providing a signature. + +# Mint quote + +To request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/{method}` request where `method` is the payment method requested (here `bolt11`). +```http +POST https://mint.host:3338/v1/mint/quote/bolt11 +``` + +The wallet of `Alice` includes the following `PostMintQuoteBolt11Request` data in its request: + +```json +{ + "amount": , + "unit": , + "description": , + "pubkey": <-- New +} +``` +with the requested `amount` and the `unit`. An optional `description` can be passed if the mint signals support for it in `MintMethodSetting`. `pubkey` is the public key that will be required for signature verification during the minting process. The mint will only mint ecash after receiving a valid signature from the corresponding private key in the `PostMintRequest`. +The mint `Bob` then responds with a `PostMintQuoteBolt11Response`: +```json +{ + "quote": , + "request": , + "state": , + "expiry": +} +``` + +Where `quote` is the quote ID and `request` is the payment request to fulfill. `expiry` is the Unix timestamp until which the mint quote is valid. +`state` is an enum string field with possible values `"UNPAID"`, `"PAID"`, `"ISSUED"`: +- `"UNPAID"` means that the quote's request has not been paid yet. +- `"PAID"` means that the request has been paid. +- `"ISSUED"` means that the quote has already been issued. + + +## Example + +Request of `Alice` with curl: + +```bash +curl -X POST http://localhost:3338/v1/mint/quote/bolt11 -d '{"amount": 10, "unit": "sat", "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"}' -H "Content-Type: application/json" +``` + +Response of `Bob`: + +```json +{ + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "request": "lnbc100n1pj4apw9...", + "state": "UNPAID", + "expiry": 1701704757 +} +``` + +#### Signature scheme +To mint a quote where a public key was provided, the minter needs to include signatures in the `PostMintBolt11Request`. We use `libsecp256k1`'s serialized 64-byte Schnorr signatures on the SHA256 hash of the message to sign. The message to sign is the field `PostMintQuoteBolt11Response.quote`. + +# Minting tokens +After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the `POST /v1/mint/{method}` endpoint where `method` is the payment method requested (here `bolt11`). +```http +POST https://mint.host:3338/v1/mint/bolt11 +``` +The wallet `Alice` includes the following `PostMintBolt11Request` data in its request +```json +{ + "quote": , + "outputs": , + "witness": <-- New +} +``` +with the `quote` being the quote ID from the previous step and `outputs` being `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on whose sum is `amount` as requested in the quote. `witness` is the signature on the mint quote id as defined above. +The mint `Bob` then responds with a `PostMintBolt11Response`: +```json +{ + "signatures": +} +``` +where `signatures` is an array of blind signatures on the outputs. + + +## Example + +Request of `Alice` with curl: + +```bash +curl -X POST https://mint.host:3338/v1/mint/bolt11 -H "Content-Type: application/json" -d \ +'{ + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "outputs": [ + { + "amount": 8, + "id": "009a1f293253e41e", + "B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d" + }, + { + "amount": 2, + "id": "009a1f293253e41e", + "B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6" + } + ], + "witness": "d9be080b33179387e504bb6991ea41ae0dd715e28b01ce9f63d57198a095bccc776874914288e6989e97ac9d255ac667c205fa8d90a211184b417b4ffdd24092" + +}' +``` + + +Response of `Bob`: + +```json +{ + "signatures": [ + { + "id": "009a1f293253e41e", + "amount": 2, + "C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec" + }, + { + "id": "009a1f293253e41e", + "amount": 8, + "C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c" + } + ] +} +``` + +If the invoice was not paid yet, `Bob` responds with an error. In that case, `Alice` **CAN** repeat the same request until the Lightning invoice is settled, as in NUT04. If `Alice` does not include a witness on the `PostMintBolt11Request` but did include a `pubkey` in the `PostMintBolt11QuoteRequest` the `Bob` **MUST** respond with an error, `Alice` **CAN** repeat the request with a witness in order to mint the ecash. + + +## Settings +The settings for this NUT indicate the support for requiring a signature before minting. They are part of the info response of the mint ([NUT-06][06]) which in this case reads +```json +{ + "19": { + "supported": + } +} +``` + diff --git a/error_codes.md b/error_codes.md index f340330..7472568 100644 --- a/error_codes.md +++ b/error_codes.md @@ -16,6 +16,7 @@ | 20005 | Quote is pending | [NUT-04][04], [NUT-05][05] | | 20006 | Invoice already paid | [NUT-05][05] | | 20007 | Quote is expired | [NUT-04][04], [NUT-05][05] | +| 20008 | Witness not provided for mint quote | [NUT-19][19] | [00]: 00.md [01]: 01.md @@ -30,3 +31,4 @@ [10]: 10.md [11]: 11.md [12]: 12.md +[19]: 19.md diff --git a/tests/19-tests.md b/tests/19-tests.md new file mode 100644 index 0000000..190299e --- /dev/null +++ b/tests/19-tests.md @@ -0,0 +1,21 @@ +# NUT-19 Test Vectors + +The following is a `PostMintBolt11Request` with a valid signature. Where the `pubkey` in the `PostMintQuoteBolt11Request` is `03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac`. + +```json +{ + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "outputs": [], + "witness": "d9be080b33179387e504bb6991ea41ae0dd715e28b01ce9f63d57198a095bccc776874914288e6989e97ac9d255ac667c205fa8d90a211184b417b4ffdd24092" +} +``` + +The following is a `PostMintBolt11Request` with an invalid signature. Where the `pubkey` in the `PostMintQuoteBolt11Request` is `03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac`. + +```json +{ + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "outputs": [], + "witness": "cb2b8e7ea69362dfe2a07093f2bbc319226db33db2ef686c940b5ec976bcbfc78df0cd35b3e998adf437b09ee2c950bd66dfe9eb64abd706e43ebc7c669c36c3" +} +```