Skip to content

feat: CashuClient mint library (cdk 0.17.2) — Cashu foundation CF-2#798

Open
grunch wants to merge 1 commit into
mainfrom
feat/cashu-cf2-client
Open

feat: CashuClient mint library (cdk 0.17.2) — Cashu foundation CF-2#798
grunch wants to merge 1 commit into
mainfrom
feat/cashu-cf2-client

Conversation

@grunch

@grunch grunch commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

CF-2 of the Cashu foundation (docs/cashu/01-fundamentals.md §6): a self-contained cdk 0.17.2 wrapper in src/cashu/mod.rs. Pure library — registered as a module (pub mod cashu;) but called by nothing; CF-5 does the boot wiring and Track A adds the first caller. Adapted from the reviewed first-attempt module (PR #765), updated to the re-planned spec.

Surface (frozen contract, spec §10)

  • connect(mint_url) — mint reachable, supports NUTs 07 (checkstate), 11 (P2PK), 12 (DLEQ), and exposes ≥ 1 active sat keyset (M-3). Fails fast instead of stranding orders later.
  • verify_2of3_condition(token, p_b, p_s, p_m) — per proof: P2PK only (HTLC rejected), n_sigs = 2 over exactly {P_B, P_S, P_M} (set-based; duplicates faking a 3-key set rejected), and the Track A §4B seller-recovery pathway: locktime tag present, refund = [P_S] exactly, n_sigs_refund = 1 (absent = 1 per NUT-11). This is inverted vs. the first attempt, which rejected locktime/refund — the spec now mandates them (a locktime'd token with no refund is anyone-can-spend after expiry).
  • verify_escrow_conditions(…, min_locktime) — offline half (condition + locktime floor), split out so it's unit-testable without a mint.
  • verify_escrow_token(…, expected_amount, min_locktime) — composes: condition + floor → mint binding → sat unit + exact amount → DLEQ → unspent (fail-closed if checkstate returns fewer states than proofs).
  • verify_token_dleq — NUT-12 mint authentication + per-proof sat keyset check (M-3: tokens may mix keysets, so the connect check alone doesn't cover it).
  • check_state (NUT-07), cashu_pubkey_from_xonly_hex (x-only → compressed even-Y).

Notes for reviewers

  • cdk = { version = "0.17.2", default-features = false, features = ["wallet"] } — wallet-side client only, no mint-server code.
  • Connect-time unit check is "≥ 1 active sat keyset", not "all keysets sat": multi-unit mints are legitimate, and the per-proof keyset check is the security floor keeping foreign-unit tokens out. The spec's wording was ambiguous here — flagging for confirmation (docs can be amended either way).
  • The n_sigs_refund tests forge raw NUT-10 secrets (0 and 2): cdk's own constructor refuses both shapes, but an attacker hands us raw secrets, not constructor output — the daemon-side check is what stands between a forged n_sigs_refund=0 (cdk has a known refund-path zero-sigs bypass hazard) and the escrow.
  • Kept the first attempt's hard-won regression guards: DLEQ-before-unspent rationale (fabricated-token hole), MissingDleqProof pin, odd-Y parity sign/verify roundtrip.
  • TODO(track-b) documented: active-keyset-only DLEQ lookup needs a /v1/keys/{id} fallback before Track B verifies aged tokens.

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test — 515 passed (503 pre-existing unmodified + 12 new, all offline)
  • Mint-backed paths (connect, check_state, verify_token_dleq, full verify_escrow_token) → env-gated CF-3 integration suite (next PR)

Wave-1 PR of the CF-0…CF-5 series (CF-0 #795, CF-1 #796, CF-4 #797). Remaining: CF-3 (mint harness), CF-5 (integration, last).

Cashu foundation CF-2 (docs/cashu/01-fundamentals.md, section 6): a
self-contained cdk 0.17.2 wrapper, registered as a module but not wired
into the daemon (CF-5 does the boot wiring). Adapted from the reviewed
first-attempt module (PR #765) with the re-planned spec's changes:

- connect(): mint reachable + NUTs 07/11/12 + at least one active sat
  keyset (M-3) — refuses to boot against an unusable mint.
- verify_2of3_condition(): P2PK-only (HTLC rejected), exactly 2 sigs
  over exactly {P_B, P_S, P_M} (set-based, duplicates rejected), and
  NOW REQUIRES the seller-recovery pathway (Track A 4B): locktime tag
  present, refund = [P_S] exactly, n_sigs_refund = 1.
- verify_escrow_conditions(): offline half (condition + locktime floor
  >= min_locktime), unit-testable without a mint.
- verify_escrow_token(): composes condition + floor + mint binding +
  sat unit + exact amount + DLEQ + unspent (fail-closed on checkstate
  count mismatch).
- verify_token_dleq(): NUT-12 mint authentication + per-proof sat
  keyset check (M-3: tokens may mix keysets).
- cashu_pubkey_from_xonly_hex(): x-only -> compressed (even-Y) with a
  sign/verify roundtrip test for odd-Y source keys.

Tests (12, all offline): every spec-required verify_2of3_condition
case (valid shapes, wrong sig count, missing locktime, missing/wrong/
extra refund key, forged n_sigs_refund 0/2 via raw NUT-10 secrets,
missing/wrong/extra/duplicated pubkey), locktime floor, x-only
conversion x3, odd-Y roundtrip, and the no-DLEQ regression guard.
Mint-backed paths (connect/check_state/verify_token_dleq) are covered
by the env-gated CF-3 integration suite.

515 tests pass (503 pre-existing unmodified + 12 new).
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 38 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa32253e-b5fc-47c3-8880-18170d46e271

📥 Commits

Reviewing files that changed from the base of the PR and between 53e0086 and fc8ccbe.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml
  • src/cashu/mod.rs
  • src/main.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cashu-cf2-client

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc8ccbe93a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cashu/mod.rs
Comment on lines +183 to +184
let conditions = conditions
.ok_or_else(|| Error::Condition("P2PK condition carries no NUT-11 tags".into()))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-SIG_INPUTS escrow tokens

When the seller includes sigflag = SIG_ALL, this parser still accepts the token because the parsed conditions.sig_flag is never checked. The Cashu architecture docs explicitly base the happy path and fee policy on SIG_INPUTS so the seller can sign once and the buyer can later choose their own swap outputs; accepting a SIG_ALL escrow can lock an order that the documented release flow cannot redeem without a different pre-constructed-output signing round. Please reject any parsed condition whose sig flag is not SigInputs during lock validation.

Useful? React with 👍 / 👎.

Comment thread src/cashu/mod.rs
Comment on lines +169 to +170
let spending_conditions = SpendingConditions::try_from(secret)
.map_err(|e| Error::Condition(e.to_string()))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate NUT-11 tags before parsing

SpendingConditions::try_from in the pinned CDK parser collapses duplicate standard tags to the first value, so checks below can pass for a raw secret that still contains a second refund, locktime, n_sigs, or sigflag tag. NUT-11 marks duplicate tags malformed, and with a non-CDK configured mint that rejects or interprets duplicates differently, Mostro can accept and persist an escrow token that later cannot be settled or has different spend conditions than the daemon verified. Please inspect the raw NUT-10 tag list and reject duplicates before converting it to Conditions.

Useful? React with 👍 / 👎.

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