Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio
| [26][26] | Payment Request Bech32m Encoding | [cdk] | - |
| [27][27] | Nostr Mint Backup | [Cashu.me][cashume], [cdk] | - |
| [28][28] | Pay to Blinded Key (P2BK) | - | - |
| [XX][XX] | Get Locked Mint Quotes By Pubkey | - | - |

#### Wallets:

Expand Down Expand Up @@ -104,3 +105,4 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio
[26]: 26.md
[27]: 27.md
[28]: 28.md
[XX]: xx.md
60 changes: 60 additions & 0 deletions xx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# NUT-29: Mint Quote Lookup by Public Key
Copy link
Copy Markdown

@TheMhv TheMhv Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a new NUT number, NUT-29 is already defined in NUT-29: Batched Minting

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we us XX and just define before merge


`optional`

`depends on: NUT-04, NUT-20`

---

This NUT adds an endpoint for wallets to get all NUT-20 locked mint quotes associated with a set of public keys. Queries require a valid signature from the owner of the corresponding private keys.

## Request

To query quotes assigned to a public key, the wallet makes a `POST /v1/mint/quote/{method}/pubkey` request.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
To query quotes assigned to a public key, the wallet makes a `POST /v1/mint/quote/{method}/pubkey` request.
To query quotes assigned to a public key, the wallet needs to
1. Get a nonce from `GET /v1/mint/quote/{method}/pubkey` request.
2. Makes a `POST /v1/mint/quote/{method}/pubkey` request.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nonce doesn't need to be mint-supplied. It can just be a random string.


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

The wallet includes the following `PostMintQuotesByPubkeyRequest` data:

```json
{
"pubkeys": <Array[str]>,
"pubkey_signatures": <Array[str]>
Comment thread
callebtc marked this conversation as resolved.
}
```

- `pubkeys` is an array of hex-encoded compressed secp256k1 NUT-20 public keys (33 bytes each)
- `pubkey_signatures` is an array of hex-encoded Schnorr signatures on `pubkeys` in the same order (64 bytes each)

The wallet **MUST** provide a valid signature in `pubkey_signatures` for each public key in `pubkeys` with the corresponding private key in the same order as the `pubkeys` array. The message to sign is the byte representation of the public key.
Copy link
Copy Markdown
Contributor

@robwoodgate robwoodgate Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think signature on the plain public key is pretty risky, as it allows signature replay in any "proof of key ownership" context, or even for future uses on other endpoints and other mints.

It would be better defined as a signature on a domain separated message like we do in P2BK / keyset v2

eg: SHA-256( b"Cashu_NUTXX_QUOTE_v1" || MINT_URL || method || pubkey )

or

eg: SHA-256( b"Cashu_NUTXX_QUOTE_v1" || NONCE || pubkey )

where NONCE is a random shared nonce, supplied in a nonce param:

{
  "pubkeys": <Array[str]>,
  "pubkey_signatures": <Array[str]>,
  "nonce": str
}

)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The wallet **MUST** provide a valid signature in `pubkey_signatures` for each public key in `pubkeys` with the corresponding private key in the same order as the `pubkeys` array. The message to sign is the byte representation of the public key.
The wallet **MUST** provide a valid signature in `pubkey_signatures` for each public key in `pubkeys` with the corresponding private key in the same order as the `pubkeys` array. The message to sign is the byte representation of the public key.
### Signature Validation Failure
If there is a mismatch between signature and pubkey counts, or **any signature is invalid**, the mint MUST reject the request and return an error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A static schnorr signature allows replay attacks: if I observe a signature i can use it to query the status of a quote again and again.


Comment on lines +21 to +32
Copy link
Copy Markdown

@TheMhv TheMhv Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```json
{
"pubkeys": <Array[str]>,
"pubkey_signatures": <Array[str]>
}
```
- `pubkeys` is an array of hex-encoded compressed secp256k1 NUT-20 public keys (33 bytes each)
- `pubkey_signatures` is an array of hex-encoded Schnorr signatures on `pubkeys` in the same order (64 bytes each)
The wallet **MUST** provide a valid signature in `pubkey_signatures` for each public key in `pubkeys` with the corresponding private key in the same order as the `pubkeys` array. The message to sign is the byte representation of the public key.
```json
{
"pubkeys": <Array[str]>,
"pubkey_signatures": <Array[str]>,
"nonce": <hex_str>
}
```
- `pubkeys` is an array of hex-encoded compressed secp256k1 NUT-20 public keys (33 bytes each)
- `pubkey_signatures` is an array of hex-encoded Schnorr signatures on `pubkeys` in the same order (64 bytes each)
- `nonce` is an hex-encoded nonce fetched from previous request.
The wallet **MUST** provide a valid signature in `pubkey_signatures` for each public key in `pubkeys` with the corresponding private key in the same order as the `pubkeys` array. The message to sign is the byte representation of the following hash:
```
SHA-256( DOMAIN_SEPARATOR || nonce || pubkey)
```
Where:
- `DOMAIN_SEPARATOR` constant byte string `b"Cashu_MINTQUOTES_v1"`
- `nonce` is the previous nonce fetched by `GET` request
- `pubkey` is the byte representation of the public key
- `||` denotes concatenation

## Response

The mint responds with a `PostMintQuotesByPubkeyResponse`:
Copy link
Copy Markdown

@TheMhv TheMhv Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The mint responds with a `PostMintQuotesByPubkeyResponse`:
On GET request, the mint responds with a `GetMintQuotesByPubkeyResponse`:
```json
{
"nonce": <hex_str>
}
```
On POST request, the mint responds with a `PostMintQuotesByPubkeyResponse`:


```json
{
"quotes": <Array[MintQuoteResponse]>
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably specify what happens if any signature is invalid. Does entire op fail, or does mint return only the valid ones? What happens if more/fewer sigs are provided than pubkeys?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should match the plan for batched minting where if any are invalid the whole op fails.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. It lowers the abuse surface too, because a mint can reject at first invalid signature (or on mismatch key/sig count), rather than continue processing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a suggestion to the request section above, but it could be moved to response section if preferred.

Where `MintQuoteResponse` is the quote response type defined in [NUT-04][04].

## Settings

The settings for this NUT are part of the mint info response ([NUT-06][06]):

```json
{
"29": {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a new NUT number, NUT-29 is already defined in NUT-29: Batched Minting

Same thing here

"supported": <bool>
}
}
```

[04]: 04.md
[06]: 06.md
[20]: 20.md
[errors]: error_codes.md