Skip to content

Extend NUT-13 to derive NUT-20 quote signing keys from seed #356

@Forte11Cuba

Description

@Forte11Cuba

Problem

NUT-13 enables deterministic derivation of proof secrets (0x00) and blinding factors (0x01) from a wallet seed, allowing full ecash recovery via NUT-09.

NUT-20 quote signing keys are not covered. All known implementations ( cdk (Rust) and Nutshell (Python) ) generate them randomly:

These keys exist only in the local database. After device loss, the user cannot produce the NUT-20 signature required to claim proofs from a PAID quote even with the correct mnemonic and the quote ID.

What this solves

A user who has:

  • Their mnemonic (seed)
  • A quote ID (from their records or provided by the mint operator)

Can currently recover on mints without NUT-20: just call mint(quote_id). Cannot currently recover on mints with NUT-20: the signing key is lost, the mint rejects with error 20008. With deterministic derivation, the wallet regenerates the same signing key from the seed and produces a valid signature. Recovery works on all mints.

Proposed derivation
Use the same HMAC-SHA256 pattern as NUT-13 with a separate domain string, since quote keys are not tied to a keyset:
message = b"Cashu_KDF_HMAC_SHA256_QUOTE" || quote_counter_bytes
quote_signing_key = HMAC_SHA256(seed, message)

Where:

  • quote_counter_bytes is an unsigned 64-bit integer in big-endian format
  • The 32-byte HMAC digest is used as a secp256k1 secret key
  • The corresponding public key is sent to the mint per NUT-20

The wallet persists a quote_counter (incremented per mint quote) alongside keyset counters.
NUT-20 recommends a unique public key per quote. Deterministic derivation preserves this — each counter value produces a unique, unlinkable key.

This proposal covers key derivation only. With the quote ID, recovery works immediately.
Full automatic recovery (without knowing the quote ID) would require a mint endpoint to look up quotes by public key. That could be defined in a separate NUT.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions