feat(crypto): deterministic P2PK & NUT-20 quote-lock key derivation - #697
Merged
Conversation
robwoodgate
force-pushed
the
bip32-pubkey-gen
branch
2 times, most recently
from
June 17, 2026 12:26
ec4eb57 to
3ce032c
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #697 +/- ##
==========================================
+ Coverage 92.40% 95.21% +2.80%
==========================================
Files 54 54
Lines 4990 5078 +88
Branches 1224 1254 +30
==========================================
+ Hits 4611 4835 +224
+ Misses 160 103 -57
+ Partials 219 140 -79
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Add BIP-32 derivation of signing keys under m/129373'/{purpose}'/0'/0'/{counter}
for NUT-11 (P2PK, index 10) and NUT-20 (quote locking, index 20).
- deriveKeyPair(seed, purpose, counter) -> { pubkey, privkey } (both hex),
dropping straight into the existing lock/quote and signP2PKProofs APIs
- createSecretKeyDeriver(seed, purpose) caches the shared parent for restore
scans (single non-hardened child derivation per counter)
- Bip32KeyPurposes is a single-source name->index map; purpose passed by name
Counter allocation and quote/proof<->counter mapping stay with the consumer.
Verified against the NUT-11 / NUT-20 test vectors for both purposes, counters 0-4.
robwoodgate
force-pushed
the
bip32-pubkey-gen
branch
from
June 17, 2026 12:34
3ce032c to
630e0ce
Compare
New "Derive Keys" usage page covering deriveKeyPair / createSecretKeyDeriver for recoverable P2PK (NUT-11) and quote-locking (NUT-20) keys, plus index and README entries and a cross-link from the Create P2PK recipe.
This was referenced Jun 17, 2026
Rename createSecretKeyDeriver to createKeyPairDeriver and return the hex
{ pubkey, privkey } directly. The cached loop primitive now yields the same
ready-to-use format as deriveKeyPair, so restore scans drop the manual
getPubKeyFromPrivKey + bytesToHex wiring and are no longer nudged toward the
slower per-call deriveKeyPair.
The non-hardened counter child enables watch-only pubkey derivation from the parent xpub, but exporting that xpub alongside any counter's private key allows recovery of the parent key and all sibling keys.
Contributor
🧬 Mutation testing (incremental)✅ Mutation score: 89.4% (break threshold: 80%) — repo-wide 5221 killed / 535 survived / 84 no-coverage. Surviving mutants in files this PR changed (20):
Full report (all files): download the |
robwoodgate
added a commit
that referenced
this pull request
Jul 9, 2026
🤖 I have created a release *beep* *boop* --- ## [5.0.0-rc.3](v5.0.0-rc.2...v5.0.0-rc.3) (2026-07-08) ### ⚠ BREAKING CHANGES * **crypto:** align NUT-29 batch quote signatures with amended spec ([#675](#675)) * **nut04/05:** quote accounting and custom payment method base structs ([#698](#698)) ### Features * **crypto:** deterministic P2PK & NUT-20 quote-lock key derivation ([#697](#697)) ([042e56c](042e56c)) * **nut04/05:** quote accounting and custom payment method base structs ([#698](#698)) ([0e373f9](0e373f9)) * **nut29:** add batch quote checks ([#768](#768)) ([854d91f](854d91f)) ### Bug Fixes * **crypto:** align NUT-29 batch quote signatures with amended spec ([#675](#675)) ([f3a3841](f3a3841)) * **p2bk:** align HTLC key slots with the [data, ...pubkeys, ...refund] order (NUT-28) ([#763](#763)) ([bb0b766](bb0b766)) * **p2pk:** cap NUT-28 locking slots at 11 (P2PK 11 keys, HTLC 10) ([#753](#753)) ([80a55b9](80a55b9)) * **scripts:** probe docker tags with and without v prefix ([#726](#726)) ([8667aca](8667aca)) * **utils:** throw CTSError for malformed token templates ([#742](#742)) ([c60cd35](c60cd35)) ### Miscellaneous Chores * **deps:** bump actions/setup-node from 4 to 6 in /.github/actions/integration-against-mint in the github-actions group ([#729](#729)) ([b94af5f](b94af5f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
42 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NUTS:
Summary
Adds BIP-32 derivation of deterministic signing keys under
m/129373'/{purpose}'/0'/0'/{counter}(non-hardened counter), as defined for:10)20)API
purposeis passed by name ('P2PK'/'QuoteLock'). Both return hexpubkey/privkeythat drop straight into the existing lock/quote andsignP2PKProofsAPIs with zero conversion:createKeyPairDerivercaches the shared parent derivation, so restore scans cost a single non-hardened child derivation per counter (~5x faster than re-walking the full path) while still returning the ready-to-use hex keypair:Scope
Crypto primitive only. Counter allocation and the quote/proof-to-counter mapping stay with the consumer; they need that mapping regardless, so a wallet-owned counter would add coupling without removing consumer state. No NUT-20 module changes needed:
createLockedMintQuote/locked mint already accept the pubkey/privkey directly.Testing
Verified against the NUT-11 / NUT-20 test vectors for both purposes, counters 0 to 4.
npm run prtasksgreen (lint, format, api-extractor, full suite).