WizardConnect/docs
Håvard Kittelsen b9a0e16893 feat(core): login challenge helpers, so replay protection is not optional
The sign_message extension leaves replay to the dapp, because a signature proves
key control over an exact string and carries no freshness or audience. The
previous commit said so in three places in the docs. That is exactly the failure
this repository should not ship: a login built on a bare signMessage call works in
every manual test and is a password that never expires, so documenting the
requirement mostly relocates the blame.

So the two failures that matter are structural here rather than advisory:

  verifyLoginChallenge cannot be called without `domain` and `consumeNonce`. There
  is no overload that omits them. Verifying a login without single-use enforcement
  and audience binding is not something this API can express — if you want plain
  signature verification, verifyMessageSignatureForAddress is right there and is
  honestly named.

  createLoginChallenge refuses a nonce under MIN_NONCE_LENGTH and refuses a line
  break in any field, so neither a guessable nonce nor an injected `Nonce:` line
  can reach a signed message.

Check order is deliberate: parse, domain, expiry, signature, THEN consume the
nonce. Consuming earlier would let anyone who sniffs a nonce burn it with a
garbage signature before the real user finishes signing; there is a test asserting
the nonce survives a bad signature and the genuine login still completes.

consumeNonce is a caller-supplied callback rather than a store this module owns,
because single-use enforcement is a property of the caller's database — two
replays arriving together both reach that point and only one may be told true. The
docstring says it must be atomic. createInMemoryNonceStore exists for development
and says plainly that it is per-process, so two servers behind a load balancer
would each honour the same signature once.

parseLoginChallenge is strict: unknown fields, duplicate fields, out-of-order
fields and stray lines are rejected rather than skipped, so exactly one byte
sequence parses to a given challenge. A lenient parser is where field injection
lives.

Address is optional in the message because under wallet_choice the dapp does not
yet know which key will answer. When present the proof is self-describing — a
third party reading the message alone sees which address was claimed — and
verification then requires the recovered address to match it.

NOT SIWE. The layout is deliberately similar to Sign-In With Ethereum so it reads
familiarly, but it does not claim EIP-4361 or CAIP-122 compatibility: there is no
agreed SIWX profile for Bitcoin Cash to conform to. If one lands it belongs beside
this as a second format, not as a silent change to this one.

Also adds addressesEqual() to message-signing, which compares decoded public key
hashes so prefixed CashAddr, bare CashAddr, the token-aware form and legacy base58
all compare equal for the same key.

27 tests, mostly about what must be refused: the replay, the wrong site, the stale
and future-dated challenge, four field-injection attempts, the nonce-burning
attack, the wrong key, and a cross-encoding address match.

test-cli now builds its challenge with these helpers rather than hand-rolled text,
since that is what integrators copy, and verifies the response twice — once as a
third party would and once as the server would, printing proof that replaying the
identical signature is rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 14:33:11 +02:00
..
connection-uri.md Add additional default relay 2026-04-14 08:42:34 +02:00
dapp.md feat(core): login challenge helpers, so replay protection is not optional 2026-08-06 14:33:11 +02:00
extensions.md feat(core): login challenge helpers, so replay protection is not optional 2026-08-06 14:33:11 +02:00
index.md Add 'extensions' to hdwalletv1 protocol 2026-03-26 10:50:40 +01:00
protocol.md feat(core): login challenge helpers, so replay protection is not optional 2026-08-06 14:33:11 +02:00
pubkey-derivation.md First commit for WizardConnect 2026-03-06 11:38:09 +01:00
react.md Add session persistence and refactor dapp manager 2026-04-03 17:11:18 +02:00
serialization.md Add serialization helpers to @wizardconnect/core 2026-03-23 08:59:02 +01:00
transport.md Add chunk transport extension for oversized messages 2026-04-21 14:58:12 +02:00
wallet.md feat: add the sign_message hdwalletv1 extension 2026-08-06 14:32:02 +02:00
xpub-sharing.md doc: An article on xpub sharing 2026-03-17 14:47:04 +01:00