-
Notifications
You must be signed in to change notification settings - Fork 55
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
NUT-20: signature on mint request #188
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
# NUT-20: Signature on Mint Quote | ||
|
||
`optional` | ||
|
||
`depends on: NUT-04` | ||
|
||
--- | ||
|
||
This NUT defines signature-based authentication for mint quote redemption. When requesting a mint quote, clients provide a public key. The mint will then require a valid signature from the corresponding secret key to process the mint operation. | ||
|
||
> [!CAUTION] > [NUT-04][04] mint quotes without a public key can be minted by anyone who knows the mint quote id 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. We present an example with the `method` being `bolt11` here. | ||
|
||
```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>, // Optional | ||
"pubkey": <str> // Optional <-- New | ||
} | ||
``` | ||
|
||
with the requested `amount`,`unit`, and `description` according to [NUT-04][04]. | ||
|
||
`pubkey` is the public key that will be required for signature verification during the minting operation. The mint will only mint ecash after receiving a valid signature from the corresponding private key in the subsequent `PostMintRequest`. | ||
|
||
> [!IMPORTANT] > **Privacy:** To prevent the mint from being able to link multiple mint quotes, 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> // Optional <-- New | ||
} | ||
``` | ||
|
||
The response is the same as in [NUT-04][04] except for `pubkey` which has been provided by the wallet in the previous request. | ||
|
||
## 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" | ||
} | ||
``` | ||
|
||
## Signing the mint request | ||
|
||
### Message aggregation | ||
|
||
To provide a signature for a mint request, the owner of the signing public keys must concatenate the quote ID `quote` in `PostMintQuoteBolt11Response` and the `B_` fields of all `BlindedMessages` in the `PostMintBolt11Request` (i.e., the outputs, see [NUT-00][00]) to a single message string in the order they appear in the `PostMintRequest`. This concatenated string is then hashed and signed (see [Signature scheme](#signature-scheme)). | ||
|
||
> [!NOTE] | ||
> Concatenating the quote ID and the outputs into a single message prevents maliciously replacing the outputs. | ||
|
||
If a request has `n` outputs, the message to sign becomes: | ||
|
||
``` | ||
msg_to_sign = quote || B_0 || ... || B_(n-1) | ||
``` | ||
|
||
Where `||` denotes concatenation, `quote` is the UTF-8 quote id in `PostMintQuoteBolt11Response`, and each `B_n` is a UTF-8 encoded hex string of the outputs in the `PostMintBolt11Request`. | ||
|
||
### Signature scheme | ||
|
||
To mint a quote where a public key was provided, the wallet includes a signature on `msg_to_sign` in the `PostMintBolt11Request`. We use a [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) Schnorr 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]>, | ||
"signature": <str|null> <-- New | ||
} | ||
``` | ||
|
||
with the `quote` being the quote ID from the previous step and `outputs` being `BlindedMessages` as in [NUT-04][04]. | ||
|
||
`signature` is the signature on the `msg_to_sign` which is the concatenated quote id and the outputs as defined above. | ||
|
||
The mint responds with a `PostMintBolt11Response` as in [NUT-04][04] if all validations are successful. | ||
|
||
## 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" | ||
} | ||
], | ||
"signature": "d9be080b33179387e504bb6991ea41ae0dd715e28b01ce9f63d57198a095bccc776874914288e6989e97ac9d255ac667c205fa8d90a211184b417b4ffdd24092" | ||
|
||
}' | ||
``` | ||
|
||
Response of `Bob`: | ||
|
||
```json | ||
{ | ||
"signatures": [ | ||
{ | ||
"id": "009a1f293253e41e", | ||
"amount": 2, | ||
"C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec" | ||
}, | ||
{ | ||
"id": "009a1f293253e41e", | ||
"amount": 8, | ||
"C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c" | ||
} | ||
] | ||
} | ||
``` | ||
|
||
## Errors | ||
|
||
If the wallet user `Alice` does not include a signature on the `PostMintBolt11Request` but did include a `pubkey` in the `PostMintBolt11QuoteRequest` then `Bob` **MUST** respond with an error. `Alice` **CAN** repeat the request with a valid signature. | ||
|
||
See [Error Codes][errors]: | ||
|
||
- `20008`: Mint quote with `pubkey` but no valid `signature` provided for mint request. | ||
- `20009`: Mint quote requires `pubkey` but none given or invalid `pubkey`. | ||
|
||
## 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 | ||
{ | ||
"20": { | ||
"supported": <bool>, | ||
} | ||
} | ||
``` | ||
|
||
[00]: 00.md | ||
[04]: 04.md | ||
[06]: 06.md | ||
[errors]: error_codes.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# NUT-20 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" | ||
} | ||
], | ||
"signature": "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" | ||
} | ||
], | ||
"signature": "cb2b8e7ea69362dfe2a07093f2bbc319226db33db2ef686c940b5ec976bcbfc78df0cd35b3e998adf437b09ee2c950bd66dfe9eb64abd706e43ebc7c669c36c3" | ||
} | ||
``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future reference: https://json-schema.org/understanding-json-schema/reference/object#required