Skip to content

feat(auth): introduce signed-request authentication with replay protection#1612

Open
oche2920 wants to merge 1 commit into
Stellar-Mail:mainfrom
oche2920:feat/signed-request-auth-1459
Open

feat(auth): introduce signed-request authentication with replay protection#1612
oche2920 wants to merge 1 commit into
Stellar-Mail:mainfrom
oche2920:feat/signed-request-auth-1459

Conversation

@oche2920

Copy link
Copy Markdown

Replaces header-only identification in actor.ts with cryptographic proof
of Stellar key ownership via Ed25519 signed requests.

Canonical payload (domain-separated, no challenge round-trip):
stealth-auth:v1::::

New headers:
x-stealth-address verified Stellar G-address (claimed public key)
x-stealth-timestamp ISO 8601 UTC timestamp
x-stealth-nonce caller-chosen opaque string (8-128 chars)
x-stealth-signature 128-char lowercase hex Ed25519 signature

Security properties:

  • Expiration: timestamps outside +/-5 min window are rejected
  • Replay protection: (address, nonce) digest stored for the window;
    reused nonces rejected; only SHA-256 digest persisted, never raw values
  • Domain separation: stealth-auth:v1 prefix prevents cross-protocol reuse
  • Signature verification: Keypair.verify() from @stellar/stellar-sdk

Exports:
verifySignedRequest - core verifier, returns VerifiedActor
requireVerifiedActor - route helper, returns address string
requireVerifiedActorMatches - route helper with ownership assertion
buildCanonicalPayload - shared by client and server

26 unit tests covering happy path, expiration, invalid signatures,
tampered payloads, nonce bounds, replay rejection, and digest privacy.

Closes #1459

…ction

Replaces header-only identification in actor.ts with cryptographic proof
of Stellar key ownership via Ed25519 signed requests.

Canonical payload (domain-separated, no challenge round-trip):
  stealth-auth:v1:<METHOD>:<PATH>:<TIMESTAMP>:<NONCE>

New headers:
  x-stealth-address    verified Stellar G-address (claimed public key)
  x-stealth-timestamp  ISO 8601 UTC timestamp
  x-stealth-nonce      caller-chosen opaque string (8-128 chars)
  x-stealth-signature  128-char lowercase hex Ed25519 signature

Security properties:
  - Expiration: timestamps outside +/-5 min window are rejected
  - Replay protection: (address, nonce) digest stored for the window;
    reused nonces rejected; only SHA-256 digest persisted, never raw values
  - Domain separation: stealth-auth:v1 prefix prevents cross-protocol reuse
  - Signature verification: Keypair.verify() from @stellar/stellar-sdk

Exports:
  verifySignedRequest          - core verifier, returns VerifiedActor
  requireVerifiedActor         - route helper, returns address string
  requireVerifiedActorMatches  - route helper with ownership assertion
  buildCanonicalPayload        - shared by client and server

26 unit tests covering happy path, expiration, invalid signatures,
tampered payloads, nonce bounds, replay rejection, and digest privacy.

Closes Stellar-Mail#1459
@kryputh

kryputh commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

@oche2920 fix ci...

Just format the code wiry prettier all should be fine

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.

Replace trusted actor headers with cryptographically verified Stellar authentication

2 participants