Skip to content

Commit

Permalink
feat: nut19 signature on mint request
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Nov 21, 2024
1 parent 96891ac commit a4349b5
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 0 deletions.
182 changes: 182 additions & 0 deletions 19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# 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": <int>,
"unit": <str_enum["sat"]>,
"description": <str|null>,
"pubkey": <str|null> <-- 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`.

> **Privacy:** To prevent linking multiple mint quotes together, wallets **SHOULD** generate a unique public key for each mint quote request
The mint `Bob` then responds with a `PostMintQuoteBolt11Response`:

```json
{
"quote": <str>,
"request": <str>,
"state": <str_enum[STATE]>,
"expiry": <int>,
"pubkey": <str|null> <-- New
}
```

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,
"pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"
}
```

#### Message aggregation

To provide a signature for a mint request, the owner of one of the signing public keys must concatenate the `PostMintQuoteBolt11Response.quote` and the `B_` fields of all `BlindedMessages` (outputs, see [NUT-00][00]) to a single message string in the order they appear in the `PostMintRequest`. This string concatenated is then hashed and signed (see [Signature scheme](#signature-scheme)).

If a request has `n` outputs the message to sign becomes:

```
msg = quote_id || B_0 || ... || B_n
```

Where || denotes concatenation, `quote_id` is a UTF-8 string, and each `B_` is a hex string, with all components encoded to UTF-8 bytes

#### Signature scheme

To mint a quote where a public key was provided, the minter needs to include signatures in the `PostMintBolt11Request`. We use a [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) signature on the SHA-256 hash of the message to sign as defined above.

# 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": <str>,
"outputs": <Array[BlindedMessage]>,
"witness": <str|null> <-- 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": <Array[BlindSignature]>
}
```

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` **MAY** 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` then `Bob` **MUST** respond with an error, `Alice` **SHOULD** 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": <bool>,
"required": <bool>
}
}
```

In addition to signaling optional support, the mint can require a public key for mint quotes and a corresponding witness during minting. If `required` is set to `true` in the NUT-19 settings:

1. The wallet **MUST** provide a public key when requesting a mint quote
2. The wallet **MUST** provide a valid witness signature when submitting the mint request

> **Note:** Enabling this setting makes the mint incompatible with wallets that do not support NUT-19
[00]: 00.md
[06]: 06.md
3 changes: 3 additions & 0 deletions error_codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
| 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 on mint request not provided or invalid | [NUT-19][19] |
| 20009 | Pubkey required on mint quote | [NUT-19][19] |

[00]: 00.md
[01]: 01.md
Expand All @@ -30,3 +32,4 @@
[10]: 10.md
[11]: 11.md
[12]: 12.md
[19]: 19.md
79 changes: 79 additions & 0 deletions tests/19-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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": [
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79"
}
],
"witness": "d4b386f21f7aa7172f0994ee6e4dd966539484247ea71c99b81b8e09b1bb2acbc0026a43c221fd773471dc30d6a32b04692e6837ddaccf0830a63128308e4ee0"
}
```

The following is the expected message to sign on the above `PostMintBolt11Request`.

```
[57, 100, 55, 52, 53, 50, 55, 48, 45, 49, 52, 48, 53, 45, 52, 54, 100, 101, 45, 98, 53, 99, 53, 45, 101, 50, 55, 54, 50, 98, 52, 102, 53, 101, 48, 48, 48, 51, 52, 50, 101, 53, 98, 99, 99, 55, 55, 102, 53, 98, 50, 97, 51, 99, 50, 97, 102, 98, 52, 48, 98, 98, 53, 57, 49, 97, 49, 101, 50, 55, 100, 97, 56, 51, 99, 100, 100, 99, 57, 54, 56, 97, 98, 100, 99, 48, 101, 99, 52, 57, 48, 52, 50, 48, 49, 97, 50, 48, 49, 56, 51, 52, 48, 51, 50, 102, 100, 51, 99, 52, 100, 99, 52, 57, 97, 50, 56, 52, 52, 97, 56, 57, 57, 57, 56, 100, 53, 101, 57, 100, 53, 98, 48, 102, 48, 98, 48, 48, 100, 100, 101, 57, 51, 49, 48, 48, 54, 51, 97, 99, 98, 56, 97, 57, 50, 101, 50, 102, 100, 97, 102, 97, 52, 49, 50, 54, 100, 52, 48, 51, 51, 98, 54, 102, 100, 101, 53, 48, 98, 54, 97, 48, 100, 102, 101, 54, 49, 97, 100, 49, 52, 56, 102, 102, 102, 49, 54, 55, 97, 100, 57, 99, 102, 56, 51, 48, 56, 100, 101, 100, 53, 102, 54, 102, 54, 98, 50, 102, 101, 48, 48, 48, 97, 48, 51, 54, 99, 52, 54, 52, 99, 51, 49, 49, 48, 50, 98, 101, 53, 97, 53, 53, 102, 48, 51, 101, 53, 99, 48, 97, 97, 101, 97, 55, 55, 53, 57, 53, 100, 53, 55, 52, 98, 99, 101, 57, 50, 99, 54, 100, 53, 55, 97, 50, 97, 48, 102, 98, 50, 98, 53, 57, 53, 53, 99, 48, 98, 56, 55, 101, 52, 53, 50, 48, 101, 48, 54, 98, 53, 51, 48, 50, 50, 48, 57, 102, 99, 50, 56, 55, 51, 102, 50, 56, 53, 50, 49, 99, 98, 100, 100, 101, 55, 102, 55, 98, 51, 98, 98, 49, 53, 50, 49, 48, 48, 50, 52, 54, 51, 102, 53, 57, 55, 57, 54, 56, 54, 102, 100, 49, 53, 54, 102, 50, 51, 102, 101, 54, 97, 56, 97, 97, 50, 98, 55, 57]
```

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": [
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53"
},
{
"amount": 1,
"id": "00456a94ab4e1c46",
"B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79"
}
],
"witness": "cb2b8e7ea69362dfe2a07093f2bbc319226db33db2ef686c940b5ec976bcbfc78df0cd35b3e998adf437b09ee2c950bd66dfe9eb64abd706e43ebc7c669c36c3"
}
```

0 comments on commit a4349b5

Please sign in to comment.