Skip to content

NUT-XX: Get quotes by pubkeys#341

Open
callebtc wants to merge 12 commits into
mainfrom
get-quotes-by-pubkeys
Open

NUT-XX: Get quotes by pubkeys#341
callebtc wants to merge 12 commits into
mainfrom
get-quotes-by-pubkeys

Conversation

@callebtc
Copy link
Copy Markdown
Contributor

@callebtc callebtc commented Feb 16, 2026

supersedes #329
Get NUT-20 quotes by pubkeys. Requires signatures to prove possession of the corresponding private keys.

@callebtc callebtc changed the title Get quotes by pubkeys NUT-XX: Get quotes by pubkeys Feb 16, 2026
Copy link
Copy Markdown
Contributor

@robwoodgate robwoodgate left a comment

Choose a reason for hiding this comment

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

The signature scheme is a bit worrying, but otherwise just some nit/questions for clarity.

Comment thread xx.md
- `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!

Comment thread xx.md Outdated
Comment thread xx.md
"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.

Comment thread xx.md Outdated
Comment thread xx.md
Comment thread xx.md
- `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

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.

callebtc and others added 2 commits February 26, 2026 16:22
Co-authored-by: Rob Woodgate <robwoodgate@users.noreply.github.com>
Co-authored-by: Rob Woodgate <robwoodgate@users.noreply.github.com>
Comment thread xx.md
@@ -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

Comment thread xx.md

```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

Comment thread xx.md
- `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

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.

TheMhv

This comment was marked as resolved.

@a1denvalu3
Copy link
Copy Markdown
Contributor

I've opened a PR with the updated signature logic and test vectors here: #363

@robwoodgate
Copy link
Copy Markdown
Contributor

robwoodgate commented Apr 17, 2026

I've opened a PR with the updated signature logic and test vectors here: #363

I support @a1denvalu3 's approach in #363 - using a timestamp also allows a mint to prevent reuse of the signature after xx minutes too.

It would be even better with a domain prefix, in case a similar scheme is used later on elsewhere:

SHA-256( b"Cashu_NUTXX_QUOTE_v1" || pubkey || timestamp || mint_pubkey )

But with a timestamp max age specified, the reuse window would be minimal in any case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

7 participants