NUT-XX: Deterministic Keypairs #384

Open
robwoodgate wants to merge 1 commit from robwoodgate/nut-xx-deterministic-keypairs into main
robwoodgate commented 2026-05-29 16:02:46 +00:00 (Migrated from github.com)

Supersedes

  • #331 (P2PK derivation via BIP-32)
  • #373 (NUT-20 quote-key derivation via BIP-32)

Summary

Adds a new NUT specifying HMAC-SHA256 derivation of secp256k1 keypairs from the wallet seed, for use as locking keys in spending conditions (NUT-11 P2PK) and as quote locking keys (NUT-20). Each application gets a distinct DST and an independent counter:

  • Cashu_KDF_HMAC_SHA256_NUT11 for NUT-11 P2PK keys
  • Cashu_KDF_HMAC_SHA256_NUT20 for NUT-20 quote locking keys

Test vectors use the canonical NUT-13 mnemonic and publish the first five compressed pubkeys per domain.

This PR supersedes #331 and #373. Both PRs bolt BIP-32 derivation guidance into existing NUTs; this consolidates them into one optional NUT using the HMAC-SHA256 KDF style that NUT-13 v2 already established for the secrets / blinding-factor case.

Why HMAC-SHA256 over BIP-32

  1. Performance. HMAC is faster in all scenarios, and BIP-32 needs careful handling to avoid performance tanking.
Pattern                                          Cost/key   vs HMAC
HMAC NUT-XX                                       ~165 µs    1.0×
BIP-32 parent-cached, HDKey.publicKey             ~172 µs    1.0×    natural restore-loop code
BIP-32 master-cached, full path per derive        ~862 µs    5.2×    natural single-derive code
BIP-32 cold, no caching                          ~1216 µs    7.4×    worst case
  1. Alignment with NUT-13 v2. The spec already chose HMAC-SHA256 with modular reduction for keyset-bound derivation. Using the same primitive for non-keyset-bound P2PK and quote keys keeps one KDF across the spec rather than mixing two (BIP-32 here, HMAC there) and lines up with the direction NUT-13 v2 was already moving.

  2. No BIP-32 features actually apply. P2PK and quote keys are derived wallet-side from a hot seed. BIP-32's design targets hardware-wallet path traversal, chain-code propagation, and non-hardened xpub export. None of these apply: there is no hardware wallet in the loop, no derivation tree to walk, no xpub-export workflow shipping anywhere in the ecosystem. The HD-tree structure pays complexity for benefits Cashu never collects.

  3. The BIP-32 xpub watch-only argument is inconsistent with Cashu's privacy model. BIP-32's non-hardened terminal step allows xpub-based enumeration in theory, but using it for P2PK / NUT-20 keys requires (a) a NUT-shaped query-by-pubkey API that does not exist, (b) wallet xpub-export workflows that no one ships, and (c) a lookup API that does not gain mint-side correlation info by linking a user's many candidate pubkeys into one query. Bitcoin tolerates the equivalent because the chain is already public; Cashu mints actively gain correlation info from each lookup, which the protocol's privacy guarantees specifically try to prevent.

  4. Smaller dependency surface. Wallets need BIP-39 (mnemonic → seed bytes) regardless of derivation choice. BIP-32 derivation adds a second library (@scure/bip32, python-bip32, similar) carrying path parsing, hardened vs non-hardened logic, and chain-code handling that the wallet never uses. HMAC-SHA256 derivation drops the need for that second dep once legacy keysets are removed.

Why separate Domains?

Simply because quotes can be churned and disposed of more often, so a restore based on P2PK locking keys would potentially face larger gaps in a mixed domain scenario. It also helps prevent key collisions (privacy leak) between quotes and locked proofs in cases where counter management goes askew (eg multiple wallets using same seed).

Implementations

  • cdk
  • nutmix
  • coco
  • cashu-ts
  • nutshell
  • macadamia
# Supersedes - #331 (P2PK derivation via BIP-32) - #373 (NUT-20 quote-key derivation via BIP-32) ## Summary Adds a new NUT specifying HMAC-SHA256 derivation of secp256k1 keypairs from the wallet seed, for use as locking keys in spending conditions (NUT-11 P2PK) and as quote locking keys (NUT-20). Each application gets a distinct DST and an independent counter: - `Cashu_KDF_HMAC_SHA256_NUT11` for NUT-11 P2PK keys - `Cashu_KDF_HMAC_SHA256_NUT20` for NUT-20 quote locking keys Test vectors use the canonical NUT-13 mnemonic and publish the first five compressed pubkeys per domain. This PR supersedes #331 and #373. Both PRs bolt BIP-32 derivation guidance into existing NUTs; this consolidates them into one optional NUT using the HMAC-SHA256 KDF style that NUT-13 v2 already established for the secrets / blinding-factor case. ## Why HMAC-SHA256 over BIP-32 1. **Performance.** HMAC is faster in all scenarios, and BIP-32 needs careful handling to avoid performance tanking. ``` Pattern Cost/key vs HMAC HMAC NUT-XX ~165 µs 1.0× BIP-32 parent-cached, HDKey.publicKey ~172 µs 1.0× natural restore-loop code BIP-32 master-cached, full path per derive ~862 µs 5.2× natural single-derive code BIP-32 cold, no caching ~1216 µs 7.4× worst case ``` 2. **Alignment with NUT-13 v2.** The spec already chose HMAC-SHA256 with modular reduction for keyset-bound derivation. Using the same primitive for non-keyset-bound P2PK and quote keys keeps one KDF across the spec rather than mixing two (BIP-32 here, HMAC there) and lines up with the direction NUT-13 v2 was already moving. 3. **No BIP-32 features actually apply.** P2PK and quote keys are derived wallet-side from a hot seed. BIP-32's design targets hardware-wallet path traversal, chain-code propagation, and non-hardened xpub export. None of these apply: there is no hardware wallet in the loop, no derivation tree to walk, no xpub-export workflow shipping anywhere in the ecosystem. The HD-tree structure pays complexity for benefits Cashu never collects. 4. **The BIP-32 xpub watch-only argument is inconsistent with Cashu's privacy model.** BIP-32's non-hardened terminal step allows xpub-based enumeration in theory, but using it for P2PK / NUT-20 keys requires (a) a NUT-shaped query-by-pubkey API that does not exist, (b) wallet xpub-export workflows that no one ships, and (c) a lookup API that does not gain mint-side correlation info by linking a user's many candidate pubkeys into one query. Bitcoin tolerates the equivalent because the chain is already public; Cashu mints actively gain correlation info from each lookup, which the protocol's privacy guarantees specifically try to prevent. 5. **Smaller dependency surface.** Wallets need BIP-39 (mnemonic → seed bytes) regardless of derivation choice. BIP-32 derivation adds a second library (`@scure/bip32`, `python-bip32`, similar) carrying path parsing, hardened vs non-hardened logic, and chain-code handling that the wallet never uses. HMAC-SHA256 derivation drops the need for that second dep once legacy keysets are removed. ## Why separate Domains? Simply because quotes can be churned and disposed of more often, so a restore based on P2PK locking keys would potentially face larger gaps in a mixed domain scenario. It also helps prevent key collisions (privacy leak) between quotes and locked proofs in cases where counter management goes askew (eg multiple wallets using same seed). ## Implementations - [ ] cdk - [ ] nutmix - [ ] coco - [ ] cashu-ts - [ ] nutshell - [ ] macadamia
a1denvalu3 (Migrated from github.com) approved these changes 2026-05-31 13:46:02 +00:00
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin robwoodgate/nut-xx-deterministic-keypairs:robwoodgate/nut-xx-deterministic-keypairs
git switch robwoodgate/nut-xx-deterministic-keypairs

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff robwoodgate/nut-xx-deterministic-keypairs
git switch robwoodgate/nut-xx-deterministic-keypairs
git rebase main
git switch main
git merge --ff-only robwoodgate/nut-xx-deterministic-keypairs
git switch robwoodgate/nut-xx-deterministic-keypairs
git rebase main
git switch main
git merge --no-ff robwoodgate/nut-xx-deterministic-keypairs
git switch main
git merge --squash robwoodgate/nut-xx-deterministic-keypairs
git switch main
git merge --ff-only robwoodgate/nut-xx-deterministic-keypairs
git switch main
git merge robwoodgate/nut-xx-deterministic-keypairs
git push origin main
Sign in to join this conversation.
No description provided.