Skip to content

Reject case-varied LSPS5 replay signatures#4701

Merged
tnull merged 1 commit into
lightningdevkit:mainfrom
joostjager:fix-lsps5-case-replay
Jun 16, 2026
Merged

Reject case-varied LSPS5 replay signatures#4701
tnull merged 1 commit into
lightningdevkit:mainfrom
joostjager:fix-lsps5-case-replay

Conversation

@joostjager

Copy link
Copy Markdown
Contributor

LSPS5 webhook signatures are zbase32 strings, and the verifier accepts case aliases when decoding them. The replay cache compared raw header strings, so a case-only change could bypass immediate replay detection even though it represented the same signature bytes.

Canonicalize the verified signature text before cache lookup and storage. Keying the replay cache on decoded signature bytes would be the semantic ideal, but doing that locally would decode once in the validator and again inside message_signing::verify. This keeps the fix local while matching the verifier's identity semantics. Add regression coverage for the case-varied replay.

LSPS5 webhook signatures are zbase32 strings, and the verifier
accepts case aliases when decoding them. The replay cache compared
raw header strings, so a case-only change could bypass immediate
replay detection even though it represented the same signature bytes.

Canonicalize the verified signature text before cache lookup and
storage. Keying the replay cache on decoded signature bytes would be
the semantic ideal, but doing that locally would decode once in the
validator and again inside message_signing::verify. This keeps the
fix local while matching the verifier's identity semantics. Add
regression coverage for the case-varied replay.
@ldk-reviews-bot

Copy link
Copy Markdown

👋 Hi! Please choose at least one reviewer by assigning them on the right bar.
If no reviewers are assigned within 10 minutes, I'll automatically assign one.
Once the first reviewer has submitted a review, a second will be assigned if required.

@ldk-claude-review-bot

Copy link
Copy Markdown
Collaborator

No issues found.

The fix correctly canonicalizes the signature to lowercase before the replay-cache lookup and storage, matching the verifier's case-insensitive zbase32 semantics:

  • base32.rs:140 decodes case-insensitively, and base32.rs:152-158 rejects non-zero trailing bits, so for a valid 65-byte sigrec (exactly 104 zbase32 chars, no padding slack), case is the only equivalence dimension. Lowercasing therefore fully captures the verifier's identity semantics without false collisions (the zbase32 alphabet has no lowercase collisions).
  • The regression test is valid: zbase32 signatures contain letters, so the uppercased variant differs from the original yet still verifies and is correctly detected as a replay.

@tnull tnull merged commit c7fcdf6 into lightningdevkit:main Jun 16, 2026
1 check passed
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported in #4706

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants