Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bolt12 #170

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
191 changes: 191 additions & 0 deletions 20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# NUT-19: Mint tokens via BOLT12

`optional`

`depends on: NUT-19`

---

Similar to [NUT-04][04], which covers minting via BOLT11 invoices, minting via [BOLT12](https://github.com/lightning/bolts/blob/master/12-offer-encoding.md) is also a two-step process: requesting a mint quote and minting tokens. This document describes both steps, focusing on BOLT12-specific considerations.

# Mint quote

To request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/bolt12`.

```http
POST https://mint.host:3338/v1/mint/quote/bolt12
```

The wallet of `Alice` includes the following `PostMintQuoteBolt12Request` data in its request:

```json
{
"amount": <int|null>,
"unit": <str_enum["sat"]>,
"description": <str|null>,
"expiry": <int|null>,
"single_use": <bool>,
"pubkey": <str>
}
```

The `amount` field is optional and specifies the amount to mint. The `unit` field is mandatory. An optional `description` can be passed if the mint signals support for it in `MintMethodSetting`. If `single_use` is false, the offer can be paid multiple times. The optional `expiry` field specifies the unix timestamp when the offer expires it **MUST** be before the `max_expiry` in the `MintMethodSettings` if one is given. `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`.

> **Note:** While a pubkey is optinal as per [NUT-19][19] for [NUT-04][04] it is required in this NUT and the mint **MUST NOT** issue a mint quote if one is not included.

> **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 `PostMintQuoteBolt12Response`:

```json
{
"quote": <str>,
"request": <str>,
"expiry": <int>,
"amount_paid": <int>,
"amount_issued": <int>,
"pubkey": <str>
}
```

Where `quote` is the quote ID and `request` is the bolt12 offer. `expiry` is the Unix timestamp until which the mint quote is valid. `amount_paid` is the amount that has been paid to the mint via the bolt12 offer. `amount_issued` is the amount of ecash that has been issued for the given mint quote. `amount_paid` - `amount_issued` represents the amount of ecash a wallet can still mint.

Note: `quote` is a **unique and random** id generated by the mint to internally look up the payment state. `quote` **MUST** remain a secret between user and mint and **MUST NOT** be derivable from the payment request. A third party who knows the `quote` ID can front-run and steal the ecash that this operation mints.
thesimplekid marked this conversation as resolved.
Show resolved Hide resolved

## Example

Request of `Alice` with curl:

```bash
curl -X POST http://localhost:3338/v1/mint/quote/bolt12 -d '{"amount": 10, "unit": "sat", "single_use": true}' -H "Content-Type: application/json"
```

Response of `Bob`:

```json
{
"quote": "DSGLX9kevM...",
"request": "lno1qcp...",
"expiry": 1701704757,
"amount_paid": 0,
"amount_issued": 0,
"pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"
}
```

After payment, the wallet continues with the next section.

## Check mint quote

To check whether a mint quote has been paid and has ecash that can be issued, `Alice` makes a `GET /v1/mint/quote/bolt12/{quote_id}`.

```http
GET https://mint.host:3338/v1/mint/quote/bolt12/{quote_id}
```

The mint `Bob` responds with a `PostMintQuoteBolt12Response`.

Example request of `Alice` with curl:

```bash
curl -X GET http://localhost:3338/v1/mint/quote/bolt12/DSGLX9kevM...
```

#### Witness

In order to mint ecash the wallet **MUST** include a signature as defined in [NUT-19][19].

# Minting tokens

After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling `POST /v1/mint/bolt12`.

```http
POST https://mint.host:3338/v1/mint/bolt12
```

The wallet `Alice` includes the following `PostMintBolt12Request` data in its request:

```json
{
"quote": <str>,
"outputs": <Array[BlindedMessage]>,
"witness": <str>
}
```

The `quote` is the quote ID from the previous step and `outputs` are `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on. The sum of the outputs must equal the amount that can be minted (`amount_paid` - `amount_issued`). `witness` is the signature on the mint quote id as defined above.

The mint `Bob` then responds with a `PostMintBolt12Response`:

```json
{
"signatures": <Array[BlindSignature]>
}
```

where `signatures` is an array of blind signatures on the outputs.

## Multiple issuances

Unlike BOLT11 invoices, BOLT12 offers can be paid multiple times as long as the `single_use` flag is false. This allows the wallet to mint multiple times for one quote. The wallet can call the check bolt12 endpoint, where the mint will return the `PostMintQuoteBolt12Response` including `amount_paid` and `amount_issued`. The difference between these values represents how much the wallet can mint by calling the mint endpoint as defined above.

## Settings

The settings for this nut indicate the supported method-unit pairs for minting and whether minting is disabled or not. They are part of the info response of the mint ([NUT-06][06]) which in this case reads:

```json
{
"19": {
"methods": [
<MintMethodSetting>,
...
],
"disabled": <bool>
}
}
```

`MintMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether this minting is disabled.

`MintMethodSetting` is of the form:

```json
{
"method": <str>,
"unit": <str>,
"min_amount": <int|null>,
"max_amount": <int|null>,
"description": <bool|null>,
"max_expiry": <int|null>
}
```

`min_amount` and `max_amount` define the allowed range for transactions using this method-unit pair. `max_expiry` specifies the latest timestamp (in Unix time) until which payments for this offer will be accepted.

Example `MintMethodSetting`:

```json
{
"method": "bolt12",
"unit": "sat",
"min_amount": 0,
"max_amount": 10000,
"description": true,
"max_expiry": 1825076378
}
```

[00]: 00.md
[01]: 01.md
[02]: 02.md
[03]: 03.md
[04]: 04.md
[05]: 05.md
[06]: 06.md
[07]: 07.md
[08]: 08.md
[09]: 09.md
[10]: 10.md
[11]: 11.md
[12]: 12.md
[19]: 19.md
Loading