Skip to content

NUT-XX: Get quotes by pubkeys - #341

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

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

Conversation

@callebtc

@callebtc callebtc commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Implementations

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

@robwoodgate robwoodgate left a comment

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.

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

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

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

Comment thread xx.md
}
```

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

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.

Presumably, each quote will be the shape specified for the method it represents? So maybe a better wording is something like:

Suggested change
Where `MintQuoteResponse` is the quote response type defined in [NUT-04][04].
Where `MintQuoteResponse` is the quote response type defined in [NUT-04][04], or an extension of it based on the method the quote relates to.

Comment thread xx.md
@vnprc

vnprc commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Suggestion: drop {method} from the path. It's already been removed in cashubtc/cdk#1834. The endpoint answers "what does this key own," and a NUT-20 key locks quotes of any payment method — so the segment forces one request per method and buys nothing. The signature doesn't cover it, and every MintQuoteResponse already carries a method field (NUT-04), so a mixed-method response is unambiguous without it.

@vnprc

vnprc commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Suggestion: bound the pubkey count. The mint must verify every signature before it can tell whether the caller is entitled to anything, so request length decides how much work an anonymous caller can demand. Add a MUST to limit it, error 11017 on overflow, and an optional max_pubkeys in the NUT-06 settings — the same shape NUT-29 already uses for max_batch_size.

@vnprc

vnprc commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Suggestion: let the request bound the response. The query returns a pubkey's full quote history in every state, with no way to request a subset so the response grows unbounded with the number of quotes a key accumulates. Two optional request fields fix it: mintable quotes (amount_issued < amount_paid), and an updated_at filter. Omitting both preserves current behavior.

@robwoodgate

robwoodgate commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Suggestion: drop {method} from the path.

Previously discussed and mint side prefers to keep:
#341 (comment)

Suggestion: bound the pubkey count.

Good idea. I wonder if instead of yet-another-mintinfo-flag, we just cap it in the NUT spec to (say) 100?

Suggestion: let the request bound the response.

In an ideal world, pubkeys would not be constantly reused for privacy, but there is no bound in the real world. I wonder if a mint returning just NON-EXPIRED and mintable quotes (amount_issued < amount_paid) is enough to cap and avoid the complexity of filter flags, pagination etc?

@vnprc

vnprc commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

I wonder if instead of yet-another-mintinfo-flag, we just cap it in the NUT spec to (say) 100?

Fine with me. Before we pick a number perhaps we should do some load testing to help inform our choice.

avoid the complexity of filter flags, pagination etc

I agree in principle that we shouldn't build it before we need it. My use case does a lot more volume than the rest of the cashu ecosystem. This is why Hashpool has been useful in the past for load testing cdk. I plan to rotate locking keys on a timer at the miner wallet, not implemented yet.

I could lean on vardiff or progressive mint fees to limit the number of quotes the mint processes, the latter is probably the best solution (market solutions ftw). Lots of knobs I can turn to shift complexity outside of the cashu protocol definition.

@robwoodgate

Copy link
Copy Markdown
Collaborator

Suggestion: bound the pubkey count. The mint must verify every signature before it can tell whether the caller is entitled to anything, so request length decides how much work an anonymous caller can demand. Add a MUST to limit it, error 11017 on overflow, and an optional max_pubkeys in the NUT-06 settings — the same shape NUT-29 already uses for max_batch_size.

For cross-reference, this PR would solve the pubkey bound issue in practice (mints already cap array lengths using the value internally): #425

@thesimplekid

Copy link
Copy Markdown
Collaborator

Suggestion: bound the pubkey count. The mint must verify every signature before it can tell whether the caller is entitled to anything, so request length decides how much work an anonymous caller can demand. Add a MUST to limit it, error 11017 on overflow, and an optional max_pubkeys in the NUT-06 settings — the same shape NUT-29 already uses for max_batch_size.

For cross-reference, this PR would solve the pubkey bound issue in practice (mints already cap array lengths using the value internally): #425

There is another issue of not only max pubkey but max quotes per pubkey. If a wallet creates many quotes for a pubkey and then does a look up of just one pubkey but over time has generated many quotes on the mint this could be an expensive look up for the mint especially if we add logic like is mintable etc. We don't have anyway to limit this right now.

@callebtc

Copy link
Copy Markdown
Contributor Author

There is another issue of not only max pubkey but max quotes per pubkey. If a wallet creates many quotes for a pubkey and then does a look up of just one pubkey but over time has generated many quotes on the mint this could be an expensive look up for the mint especially if we add logic like is mintable etc. We don't have anyway to limit this right now.

Good point. We would probably have to limit the length of the returned arrays as well.

@callebtc

Copy link
Copy Markdown
Contributor Author

@thesimplekid @robwoodgate are both implementation PRs gtg?

@robwoodgate

robwoodgate commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

@thesimplekid @robwoodgate are both implementation PRs gtg?

CTS: Yes, just need to gate the mint info once nut number is finalized and add integration tests once a mint release supports it. So available pretty quickly once nut and cdk merge it

@TheMhv

TheMhv commented Aug 25, 2026

Copy link
Copy Markdown

We can assign a number for this thing?

@robwoodgate

robwoodgate commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Cross link:

@thesimplekid

Copy link
Copy Markdown
Collaborator

There is another issue of not only max pubkey but max quotes per pubkey. If a wallet creates many quotes for a pubkey and then does a look up of just one pubkey but over time has generated many quotes on the mint this could be an expensive look up for the mint especially if we add logic like is mintable etc. We don't have anyway to limit this right now.

Good point. We would probably have to limit the length of the returned arrays as well.

We can't just limit it though other wise quotes will be lost if a user only has the pubkey, but i don't see a great solution other then limiting the number of quotes that can be created for a pubkey or adding pagination to the look up by pubkey both not good.

@robwoodgate

Copy link
Copy Markdown
Collaborator

We can't just limit it though other wise quotes will be lost if a user only has the pubkey, but i don't see a great solution other then limiting the number of quotes that can be created for a pubkey or adding pagination to the look up by pubkey both not good.

Is there an issue if the mint simply rejects a create mint quote request if it uses a pubkey that has been used more than the max_array_inputs number of times?

Then it can always return a max number of quotes, no pagination

@robwoodgate robwoodgate left a comment

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.

This still needs replay protection to stop an onlooker following the lifecycle of a pubkey forever. Suggest we include timestamp in the request, signed over, and the mint with its NTP server clock can accept or reject it +/- a defined window.

This avoids needing an extra challenge request, and also avoids mint needing to store or track nonces

Request:

{
  "pubkeys": [
    "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
    "02a4c1d2f3e5b6a7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"
  ],
  "timestamp": 1788825600,
  "signatures": [
    "d9be080b33179387e504bb6991ea41ae0dd715e28b01ce9f63d57198a095bb2f...",
    "3f2a8c1e5b7d9f0a2c4e6b8d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c0e2b4d6f8a..."
  ]
}

Signature scheme:

For each pubkey P_i in the request, the wallet computes:

    msg_i = len32(P_i) || P_i || len32(t) || t

where:
    P_i    = the 33-byte compressed public key
    t      = the timestamp as an 8-byte big-endian unsigned integer
             (Unix seconds)
    len32  = the byte length of the following field as a 4-byte
             big-endian unsigned integer
    ||     = byte concatenation

The wallet signs:

    sig_i = BIP340_sign(k_i, TaggedHash("Cashu_QuoteLookup_v1", msg_i))

where k_i is the private key for P_i and TaggedHash is the BIP-340
tagged hash: SHA256(SHA256(tag) || SHA256(tag) || msg) with the tag
as UTF-8 bytes.

signatures[i] MUST be the signature for pubkeys[i] (same order,
same length). All signatures in one request commit to the same
timestamp.

The mint MUST reject the request if:
- |now - timestamp| > TIMESTAMP_WINDOW (suggested: 300 seconds),
  checked before any signature verification; or
- the pubkeys and signatures arrays differ in length; or
- any signature fails BIP-340 verification against the
  x-coordinate of its pubkey.

Rejection is all-or-nothing; a stale timestamp returns a distinct
error code so wallets can retry with a fresh timestamp rather than
treating it as an authorization failure.

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

Labels

needs implementation Needs a reference implementation ready Ready to merge

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

9 participants