fix(lnurlcash): assert named claims land at the wallet's own secret

Review feedback: bind the claim answer to the secret that was polled
before building a note from it. The kit already guarantees this -
claimMintedNote returns the queried secret by construction and
fetchNoteInfo rejects a mismatched k1 echo - so the assert cannot trip
through the kit (and is therefore not separately testable); it exists so
a future kit regression fails loudly instead of tracking a wrong secret.
This commit is contained in:
2026-08-31 12:54:46 +02:00
parent 637376e291
commit 6bb07ff9ec
2 changed files with 19 additions and 4 deletions
+7
View File
@@ -245,6 +245,13 @@ export const claimFromSecret = async (
// is lying about the note it just credited // is lying about the note it just credited
throw new Error('The mint reports the freshly minted note as already spent.') throw new Error('The mint reports the freshly minted note as already spent.')
} }
if (claim.k1 !== target.noteSecret) {
// belt and suspenders: the kit's claimMintedNote returns the queried
// secret by construction and fetchNoteInfo rejects a mismatched echo,
// so this cannot trip through the kit - if it ever does, building a
// note from the answer would track the wrong secret
throw new Error('The mint answered the claim for a different note secret.')
}
if (claim.amountMsat === null || !claim.callback) { if (claim.amountMsat === null || !claim.callback) {
throw new Error('The mint did not return the minted note details.') throw new Error('The mint did not return the minted note details.')
} }
+12 -4
View File
@@ -215,10 +215,18 @@ export const transferBetweenMints = async (
// proof - the target only credits it once the melt's payment landed // proof - the target only credits it once the melt's payment landed
try { try {
const claim = await pollMintClaim(prepared.withdrawLink, noteSecret, poll, options) const claim = await pollMintClaim(prepared.withdrawLink, noteSecret, poll, options)
if (claim.state !== 'minted' || claim.amountMsat === null || !claim.callback) { if (
// 'spent' on a fresh wallet-chosen secret, or an incomplete claim.state !== 'minted' ||
// answer: the target misbehaved - the way back to the note is claim.k1 !== noteSecret ||
// already in the claim material claim.amountMsat === null ||
!claim.callback
) {
// 'spent' on a fresh wallet-chosen secret, a claim naming a
// secret other than the one polled (impossible through the kit,
// which returns the queried secret and rejects mismatched
// echoes - asserted anyway: building from it would track the
// wrong note), or an incomplete answer: the target misbehaved -
// the way back to the note is already in the claim material
return {...base, outcome: 'settled-claim-failed', claimMaterial} return {...base, outcome: 'settled-claim-failed', claimMaterial}
} }
const note: NewBearer = { const note: NewBearer = {