Skip to content

Recovered proofs copy blinded signature instead of unblinding output data #157

@Egge21M

Description

@Egge21M

Problem

ProofService.recoverProofsFromOutputData() reconstructs proofs from the mint restore response by copying the blinded signature point into the proof:

const proof: Proof = {
  id: signature.id,
  amount: signature.amount,
  secret: new TextDecoder().decode(output.secret),
  C: signature.C_,
};

signature.C_ is the blinded signature returned for the blinded output. A valid Proof.C must be the unblinded signature point for the output secret.

The sibling change-proof path in the same service already does this correctly via OutputData.toProof():

output.toProof(sig, { id: keyset.id, keys: keyset.keypairs as Keys })

Verification

This is not related to the cashu-ts v4 migration. The same recovery code path exists on origin/master.

I also verified the installed @cashu/cashu-ts@4.1.0 behavior directly: for the same output data and blinded signature, signature.C_ !== output.toProof(signature, keyset).C, while the recovered secret matches. So copying C_ into Proof.C persists a structurally plausible but cryptographically wrong proof.

Impact

Any path that uses recoverProofsFromOutputData() after mint-side signing but before local proof persistence can save bad proofs. Proof-state checks may still appear to pass because they are secret/Y based, but the proof can fail later when spent at the mint.

Known affected recovery surfaces include:

  • mint operation recovery
  • receive operation recovery
  • send rollback/recovery
  • P2PK send resurfacing
  • melt swap recovery

Current tests do not catch this; at least one recovery test asserts that recovered proof C equals the mock C_, which encodes the broken behavior.

Suggested fix

Use OutputData.toProof() inside recoverProofsFromOutputData() when matching restored signatures back to outputs.

The method will need access to the relevant keyset for each restored signature/output, likely by signature.id or output.blindedMessage.id, similar to unblindAndSaveChangeProofs().

Add a focused regression test that fails if recovered proof C is copied directly from signature.C_ instead of produced through OutputData.toProof().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions