Skip to content

feat: p2pk keys deterministic derivation - #33

Open
d4rp4t wants to merge 6 commits into
Kukks:masterfrom
d4rp4t:feat/deterministic-p2pk
Open

feat: p2pk keys deterministic derivation#33
d4rp4t wants to merge 6 commits into
Kukks:masterfrom
d4rp4t:feat/deterministic-p2pk

Conversation

@d4rp4t

@d4rp4t d4rp4t commented Mar 19, 2026

Copy link
Copy Markdown
Collaborator

cashubtc/nuts#331

Summary by CodeRabbit

  • New Features

    • Added deterministic P2PK key derivation from wallet mnemonics or seeds.
    • Added support for purpose-specific derivation counters, including retrieval, incrementing, and updating.
    • Added deterministic P2PK locking when creating mint quotes.
    • Added validation for supported derivation counter ranges.
  • Tests

    • Added coverage for deterministic P2PK minting and swapping.
    • Added derivation vectors, boundary checks, counter behavior, and compatibility tests.

@coderabbitai

coderabbitai Bot commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@d4rp4t, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e7b55ff-9558-407a-81d9-20dc9c1b6232

📥 Commits

Reviewing files that changed from the base of the PR and between b3aaf70 and 5738a1a.

📒 Files selected for processing (8)
  • DotNut.Tests/Integration.cs
  • DotNut.Tests/Unit/Nut20DerivationTests.cs
  • DotNut.sln.DotSettings.user
  • DotNut/Abstractions/Handlers/MintHandlerBolt11.cs
  • DotNut/Abstractions/Handlers/MintHandlerBolt12.cs
  • DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs
  • DotNut/Abstractions/MintQuoteBuilder.cs
  • DotNut/NUT20/MintQuoteKeyDerivation.cs
📝 Walkthrough

Walkthrough

The PR adds NUT-13 deterministic P2PK derivation, purpose-specific counters, wallet support, and deterministic P2PK locking for mint quotes. Unit and integration tests cover derivation vectors, counter behavior, key recovery, and swaps.

Changes

Deterministic P2PK locking

Layer / File(s) Summary
NUT-13 P2PK derivation contract
DotNut/Abstractions/DerivationPurpose.cs, DotNut/NUT13/Nut13.cs, DotNut.Tests/Unit/Nut13Tests.cs
Adds derivation purposes and mnemonic- or seed-based P2PK private-key derivation. Counters at or above 2^31 are rejected.
Derivation counter storage and wallet exposure
DotNut/Abstractions/Interfaces/IDerivationCounter.cs, DotNut/Abstractions/InMemoryCounter.cs, DotNut/Abstractions/Interfaces/IWalletBuilder.cs, DotNut/Abstractions/Wallet.cs, DotNut.Tests/Unit/UnitTests2.cs
Adds independent asynchronous counters by derivation purpose. Wallets expose the counter only when the configured counter supports IDerivationCounter.
Deterministic mint-quote locking
DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs, DotNut/Abstractions/MintQuoteBuilder.cs
Adds WithDeterministicP2PkLock. Output creation derives and prepends a P2PK key while consuming the P2PK counter.
Deterministic swap integration coverage
DotNut.Tests/Integration.cs
Validates counter advancement, deterministic key recovery, failed swaps without the private key, and successful swaps with the derived key.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Wallet
  participant MintQuoteBuilder
  participant DerivationCounter
  participant Nut13
  participant Mint
  Wallet->>MintQuoteBuilder: configure deterministic P2PK locking
  MintQuoteBuilder->>DerivationCounter: fetch and increment P2Pk counter
  MintQuoteBuilder->>Nut13: derive P2PK key
  Nut13-->>MintQuoteBuilder: return derived key
  MintQuoteBuilder->>Mint: create locked outputs
  Mint-->>Wallet: return mint quote and proofs
Loading

Possibly related PRs

  • Kukks/DotNut#25: Both PRs modify NUT-13 derivation APIs and counter handling.

Suggested reviewers: kukks

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies deterministic P2PK key derivation, which is the main change in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

d4rp4t added 4 commits August 2, 2026 16:45
NUT-13 specifies m/129373'/10'/0'/0'/{counter}, but this derived from
129372' — the NUT-13 secret purpose. The test vectors were generated with
our own code, so they confirmed the wrong path; they are replaced with the
official ones from tests/13-tests.md.

Upstream fixed the purpose in cashubtc/nuts@04cce64 ("use correct purpose
and tests"), eight days after this derivation was written here.

The counter is also narrowed from ulong to uint, matching the rest of
NUT-13. The spec calls it a non-hardened child index, so anything from 2^31
up is invalid — KeyPath parsed those as a hardened index and silently
returned a colliding key instead of failing.
The P2PK derivation path has no keyset in it, so the existing per-keyset
counters do not fit. Adds a DerivationPurpose-keyed counter, scoped so the
NUT-20 quote locking counter can reuse it — the spec calls that one
independent from the NUT-13 counters, and it needs the same shape.

It lives in its own IDerivationCounter rather than on ICounter, so
implementations outside this repo keep compiling; InMemoryCounter
implements both. Reading a purpose counter is a single GetCounter call, so
there is no export counterpart to ICounter.Export.
Wires the NUT-13 P2PK derivation into the wallet. WithDeterministicP2PkLock
takes the next P2PK counter value, derives the key from the seed and makes
it the primary key on the builder, so the lock can be recovered on restore
instead of depending on a key the caller kept somewhere.

Wallet keeps the counter as IDerivationCounter when the injected ICounter
implements it, and GetDerivationCounter returns null otherwise — so an
older counter still works for everything else and only this feature is
unavailable.

Any pubkeys already on the builder are kept after the derived one, which
leaves multisig locks with one recoverable key available.
@d4rp4t
d4rp4t force-pushed the feat/deterministic-p2pk branch from ff380a6 to b3aaf70 Compare August 2, 2026 18:15
@d4rp4t
d4rp4t marked this pull request as ready for review August 2, 2026 18:16

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DotNut/Abstractions/Wallet.cs`:
- Around line 128-132: Update WalletBuilder.WithCounter and the counter
restoration flow to persist and restore both keyset counters and
derivation-purpose counters, rather than initializing _derivationCounter with
empty state. Add a dedicated persisted counter-state type or an overload
accepting both dictionaries, then add a restart test verifying the next P2PK
derivation counter continues from its previous value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cd125dd2-74a3-420f-a6e9-2b856920c679

📥 Commits

Reviewing files that changed from the base of the PR and between d6b4d36 and b3aaf70.

📒 Files selected for processing (11)
  • DotNut.Tests/Integration.cs
  • DotNut.Tests/Unit/Nut13Tests.cs
  • DotNut.Tests/Unit/UnitTests2.cs
  • DotNut/Abstractions/DerivationPurpose.cs
  • DotNut/Abstractions/InMemoryCounter.cs
  • DotNut/Abstractions/Interfaces/IDerivationCounter.cs
  • DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs
  • DotNut/Abstractions/Interfaces/IWalletBuilder.cs
  • DotNut/Abstractions/MintQuoteBuilder.cs
  • DotNut/Abstractions/Wallet.cs
  • DotNut/NUT13/Nut13.cs

Comment on lines 128 to +132
public IWalletBuilder WithCounter(IDictionary<KeysetId, uint> counter)
{
this._counter = new InMemoryCounter(counter);
var inMemory = new InMemoryCounter(counter);
this._counter = inMemory;
this._derivationCounter = inMemory;

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.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Persist derivation-purpose counters with dictionary-backed wallets.

This overload restores only keyset counters. It initializes _derivationCounter with empty purpose state. After a wallet restart, the P2PK counter starts again at zero and can reuse deterministic mint-lock keys for the same seed.

Persist and restore purpose counters with the keyset counters. Add a builder overload or a dedicated persisted counter-state type. Add a restart test that verifies the next P2PK counter does not reset.

Based on PR objectives: wallet quote locks use wallet-level derivation counters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/Abstractions/Wallet.cs` around lines 128 - 132, Update
WalletBuilder.WithCounter and the counter restoration flow to persist and
restore both keyset counters and derivation-purpose counters, rather than
initializing _derivationCounter with empty state. Add a dedicated persisted
counter-state type or an overload accepting both dictionaries, then add a
restart test verifying the next P2PK derivation counter continues from its
previous value.

Adds the m/129373'/20'/0'/0'/{counter} derivation from cashubtc/nuts#373,
covered by the vectors in tests/20-test.md. The counter is its own
DerivationPurpose, which the spec requires to be independent of the NUT-13
keyset counters.

WithDeterministicPubkey locks a quote to the derived key instead of one the
caller has to keep, and the handler then signs the mint request itself. The
point is recovery: a quote that was locked but not yet minted is otherwise
lost with the key.
NUT-20: deterministic quote locking key derivation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant