Skip to content

Add Sign-In with Ethereum (EIP-4361) support#267

Merged
alisinabh merged 3 commits into
mainfrom
feat/siwe
Jul 19, 2026
Merged

Add Sign-In with Ethereum (EIP-4361) support#267
alisinabh merged 3 commits into
mainfrom
feat/siwe

Conversation

@alisinabh

Copy link
Copy Markdown
Member

Third PR of the v0.7 "Auth" tier. Stacked on #266 (base is feat/universal-sig-verification; retarget after #266 merges).

What

  • New Ethers.Siwe + Ethers.Siwe.Message implementing EIP-4361:
    • new/1 / new!/1 (field validation, EIP-55 address normalization, nonce rules), generate_nonce/0 (crypto-random, ≥8 alphanumeric)
    • to_message/1 (exact EIP-4361 rendering + String.Chars), strict parse/1 (byte-for-byte round-trip)
    • validate/2 — stateless checks with injectable time:: domain/scheme/nonce/address binding, expiration (exclusive) / not-before (inclusive), version
    • verify/3 — the one-call backend flow: parse → validate → signature check via Ethers.Signature.verify_message/4, so smart-wallet (ERC-1271/6492) sign-ins work out of the box; EOA sign-ins verify with zero RPC calls. {:ok, false} maps to {:error, :invalid_signature}.
  • New guide guides/siwe.md with the Phoenix controller recipe (nonce in session → verify endpoint).

Design notes

  • Timestamps are stored as RFC 3339 strings (not DateTime) so non-UTC offsets and sub-second precision round-trip exactly, matching spruceid's JS/Rust libraries; new/1 accepts DateTime and validate/2 parses on demand.
  • Includes the optional scheme field from the current EIP-4361 ABNF.

Testing

  • TDD; 100% coverage on both new modules.
  • Parsed against the official spruceid reference vectors: all positive vectors parse, field-match, and round-trip byte-for-byte; all negative message/object vectors rejected.
  • verify/3 covered end-to-end: EOA happy path under a raising RPC stub (proving no network use), wrong signer/message, every validation error short-circuiting before the signature check, and a deployed ERC-1271 wallet on anvil accepting its owner's signature.
  • Full suite green (474 tests); mix format, mix credo --strict, mix dialyzer clean.

🤖 Generated with Claude Code

@alisinabh
alisinabh force-pushed the feat/universal-sig-verification branch from ebe1cac to 6997141 Compare July 19, 2026 04:29
@alisinabh
alisinabh force-pushed the feat/universal-sig-verification branch from 6997141 to 63c965c Compare July 19, 2026 04:37
Base automatically changed from feat/universal-sig-verification to main July 19, 2026 05:24
alisinabh and others added 2 commits July 19, 2026 01:25
Implement Ethers.Siwe and Ethers.Siwe.Message covering the SIWE message
lifecycle: new/new! construction with field validation, crypto-random
nonce generation, EIP-4361 rendering (with String.Chars), strict parsing
per the spec grammar (verified against the spruceid/siwe reference test
vectors with byte-for-byte round-tripping), and stateless validation of
the validity window and domain/scheme/nonce/address binding.

Timestamps are stored as RFC 3339 strings exactly as parsed so that a
parsed message always re-renders identical to its source, which is
required for signature verification.

Signature verification (Ethers.Siwe.verify/3) lands separately on top of
the ERC-6492 universal signature verification work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The one-call backend flow: parse the raw EIP-4361 message (or accept an
already-parsed struct), run stateless field validation, then check the
signature via Ethers.Signature.verify_message/4 — EOA signatures verify
locally with zero RPC round-trips while ERC-1271 and ERC-6492
smart-contract wallets are verified with a single eth_call. When given a
string, the signature is verified over the exact original input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class Sign-In with Ethereum (SIWE, EIP-4361) support to the Ethers library, including message construction/parsing/validation and an end-to-end verification helper that leverages the universal signature verification stack (EOA + ERC-1271/6492). It also introduces a dedicated guide and comprehensive fixtures/tests for reference vectors and verification flows.

Changes:

  • Add Ethers.Siwe and Ethers.Siwe.Message implementing EIP-4361 creation, rendering, strict parsing, stateless validation, and signature verification.
  • Add SIWE documentation guide and wire it into ExDoc.
  • Add reference fixtures and ExUnit coverage for parsing/validation and EOA + ERC-1271 verification paths.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/ethers/siwe.ex Core SIWE implementation: build/parse/render/validate/verify.
lib/ethers/siwe/message.ex Defines the SIWE message struct, types, and String.Chars impl.
guides/siwe.md Phoenix integration guide and security notes for SIWE usage.
mix.exs Adds the SIWE guide/modules to generated docs navigation/groups.
CHANGELOG.md Documents new SIWE feature in the changelog.
test/ethers/siwe_test.exs Unit tests for message lifecycle: new/render/parse/validate and vectors.
test/ethers/siwe_verify_test.exs End-to-end verify/3 tests for EOA and ERC-1271 paths.
test/support/fixtures/siwe/parsing_positive.json Official/reference positive parsing vectors for round-trip tests.
test/support/fixtures/siwe/parsing_negative.json Reference negative raw-message vectors for strict parsing tests.
test/support/fixtures/siwe/parsing_negative_objects.json Reference negative object vectors for new/1 validation tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/ethers/siwe.ex Outdated
Comment thread test/support/fixtures/siwe/parsing_positive.json Outdated
Comment thread test/support/fixtures/siwe/parsing_negative.json Outdated
Comment thread test/support/fixtures/siwe/parsing_negative_objects.json Outdated
An authority with a port delimiter but no digits (example.com:) is now
rejected as a SIWE domain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alisinabh
alisinabh merged commit fd269f6 into main Jul 19, 2026
5 checks passed
@alisinabh
alisinabh deleted the feat/siwe branch July 19, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants