Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Security Model – ERC-8128
Skip to content

Security Model

Understanding the security guarantees and limitations of ERC-8128.

Threat Model

ERC-8128 protects against:

ThreatProtection
ImpersonationSignature proves key ownership
Request tamperingSigned components detect modifications
Replay attacksNonces and time bounds
Man-in-the-middleRequest binding to specific authority

ERC-8128 does not protect against:

ThreatWhyMitigation
Private key theftOut of scopeSecure key storage
TLS strippingTransport securityUse HTTPS
Response tamperingOnly requests are signedSign responses too
Timing attacksImplementation detailConstant-time comparison

Security Properties

Authentication

The signature proves the signer controls the private key for address:

  • EOA: ECDSA signature verification via ecrecover
  • SCA: Contract-defined via ERC-1271 isValidSignature

Integrity

Tampering with signed components causes verification failure:

Original:  POST /transfer {"amount": "100"}
Tampered:  POST /transfer {"amount": "1000000"}
                          ^^^^^^^^^^^^^^^^^^^
                          Different body → different content-digest → signature invalid

Non-Repudiation

The signer cannot deny having signed a request (assuming key wasn't compromised).

Freshness

Time bounds ensure signatures aren't valid indefinitely:

created=1618884473 → signature starts being valid
expires=1618884533 → signature stops being valid
                     (60 second window)

Attack Scenarios

Replay Attack

Attack: Resend a captured signed request.

Defense:
  1. Nonce uniqueness — server rejects seen nonces
  2. Time bounds — expired signatures rejected
  3. Short TTL — narrow window for attack
Configuration:
{
  replayable: false,
  maxValiditySec: 60,
}

Parameter Tampering

Attack: Modify query parameters or body.

Defense:
  • Query signed via @query component
  • Body signed via content-digest component
If not using request-bound mode:
// No request integrity
opts: { binding: 'class-bound', components: ['@authority'] }
 
// Request integrity
opts: { binding: 'request-bound' }  // Default

Path Confusion

Attack: Use signature for different endpoint.

Defense: @path component in signature.

Example:
Signed for:     POST /api/v1/orders
Attacker uses:  POST /api/v1/admin/delete
                     ^^^^^^^^^^^^^^^^^^
                     Different path → signature invalid

Domain Confusion

Attack: Use signature on malicious server.

Defense: @authority component in signature.

Example:
Signed for:     POST https://api.example.com/transfer
Attacker uses:  POST https://evil.com/transfer
                          ^^^^^^^^
                          Different authority → signature invalid

Clock Drift Exploitation

Attack: Exploit clock differences between client and server.

Defense: clockSkewSec policy.

Configuration:
{
  clockSkewSec: 30,  // Allow 30 second drift
}

Nonce Prediction

Attack: Guess nonces to pre-generate valid signatures.

Defense: Use cryptographically random nonces.

Good:
{ nonce: crypto.randomUUID() }
Bad:
{ nonce: `request-${counter++}` }  // Predictable!

Verification Checklist

A secure verifier should:

  1. ✅ Require signatures on sensitive endpoints
  2. ✅ Enforce request-bound components
  3. ✅ Use a distributed nonce store
  4. ✅ Set reasonable maxValiditySec
  5. ✅ Allow minimal clockSkewSec
  6. ✅ Verify content-digest for bodies
  7. ✅ Support ERC-1271 for SCAs
  8. ✅ Log verification failures

Recommended Policies

Choose a policy tier based on the sensitivity of the endpoint — tighter constraints for mutations, relaxed for idempotent reads.

High Security
// For payments and mutations
{
  replayable: false,
  maxValiditySec: 60,
  maxNonceWindowSec: 60,
  clockSkewSec: 5,
}

Implementation Considerations

Signature Verification

  • Use constant-time comparison for signatures
  • Don't leak timing information on failures
  • Verify signature before processing request

Nonce Storage

  • Use atomic operations (SET NX)
  • Include TTL for automatic cleanup
  • Scope nonces per keyid to prevent collision

Error Messages

  • Don't reveal which check failed in production
  • Log detailed failures server-side
  • Return generic "authentication failed" to client

Rate Limiting

Even with valid signatures, apply rate limits:

const result = await verifyRequest({
  request,
  verifyMessage,
  nonceStore,
  policy
})
if (result.ok) {
  // Check rate limit for this address
  const allowed = await rateLimit(result.address)
  if (!allowed) {
    return { status: 429, error: 'Rate limited' }
  }
}