fix(ecash): repair short v2 keyset ids on every swap, not just receive

Paid cloud downloads paid with Minibits ecash were always rejected. The
buyer sends a cashuB token, which carries NUT-02 v2 keyset ids in their
8-byte short form. Minibits rotated its active keyset to a v2 id, and the
seller's verify_and_receive_payment called MintClient::swap directly,
skipping the short->full id repair that only receive_token applied. The
mint answered 422 ("ID length invalid"). The buyer then showed the
misleading "seller doesn't accept your Cashu mint" hint.

Move the repair into swap() so every caller is covered: payment verify,
streaming gate, send change, and cross-mint swaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 15:47:23 +00:00
co-authored by Claude Opus 5.5
parent 540639d2c1
commit 8b74803290
+8 -2
View File
@@ -514,6 +514,13 @@ impl MintClient {
pub async fn swap(&self, inputs: &[Proof], target_amounts: &[u64]) -> Result<SwapResult> {
let keyset = self.get_active_sat_keyset().await?;
// cashuB tokens carry NUT-02 v2 keyset ids in their 8-byte short form,
// which the mint rejects (bare 422). Repair here, not at each caller:
// the paid-download seller path swapped directly and every Minibits
// payment failed once the mint rotated to a v2 keyset.
let repaired = self.resolve_truncated_keyset_ids(inputs).await;
let inputs: &[Proof] = &repaired;
// NUT-02: a mint may charge a per-input fee, and it rejects the swap
// outright unless outputs == inputs - fee (`11005 Transaction inputs
// should equal outputs less fee`). Applied here rather than at each
@@ -813,8 +820,7 @@ impl MintClient {
let total: u64 = entry.proofs.iter().map(|p| p.amount).sum();
let target_amounts = amount_to_denominations(total);
let proofs = self.resolve_truncated_keyset_ids(&entry.proofs).await;
let result = self.swap(&proofs, &target_amounts).await?;
let result = self.swap(&entry.proofs, &target_amounts).await?;
all_new_proofs.extend(result.new_proofs);
}