Specification
ERC-8128 defines how Ethereum accounts sign HTTP requests using RFC 9421 (HTTP Message Signatures).
Full Specification: ERC-8128 on GitHub
Overview
ERC-8128 is a profile of RFC 9421 that specifies:
- Signature algorithm: Ethereum message signing (EIP-191)
- Key identification:
erc8128:<chainId>:<address>format - Default components: Request-bound signing
- Replay protection: Nonce-based with time bounds
Headers
Signature-Input
Structured Dictionary (RFC 8941) listing signed components and parameters:
Signature-Input: eth=("@authority" "@method" "@path" "@query" "content-digest");created=1618884473;expires=1618884533;nonce="abc123";keyid="erc8128:1:0xd8da6bf26964af9d7eed9e03e53415d37aa96045"created— Unix timestamp when signature was createdexpires— Unix timestamp when signature expireskeyid— Signer identifier inerc8128:<chainId>:<address>format
nonce— Unique value for replay protectiontag— Application-specific tag
Signature
Structured Dictionary with base64-encoded signature:
Signature: eth=:MEUCIQDXtPCJ5f7oKr...base64...:Content-Digest
SHA-256 hash of the request body (RFC 9530):
Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:Keyid Format
The keyid parameter identifies the signer using an ERC-8128-specific URI format:
erc8128:<chainId>:<address>chainId: Decimal integer (e.g.,1for Ethereum mainnet)address: Checksummed or lowercase Ethereum address with0xprefix
erc8128:1:0xd8da6bf26964af9d7eed9e03e53415d37aa96045
erc8128:8453:0x1234567890abcdef1234567890abcdef12345678
erc8128:42161:0xABCDEF1234567890ABCDEF1234567890ABCDEF12Signature Base
The message signed is an RFC 9421 signature base — a canonical representation of the HTTP request:
"@authority": api.example.com
"@method": POST
"@path": /orders
"@query": ?market=ETH-USD
"content-digest": sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
"@signature-params": ("@authority" "@method" "@path" "@query" "content-digest");created=1618884473;expires=1618884533;nonce="abc123";keyid="erc8128:1:0xd8da6bf26964af9d7eed9e03e53415d37aa96045"This is then signed as an Ethereum message:
keccak256("\x19Ethereum Signed Message:\n" + len(signatureBase) + signatureBase)Binding Modes
Request-Bound
The signature MUST cover at minimum:
@authority@method@path@query(if query string present)content-digest(if body present)
Class-Bound
The signer explicitly specifies which components to sign. The verifier MUST enforce its own required components.
Replay Protection
Non-Replayable
nonceparameter MUST be present- Verifier MUST track consumed nonces
- Verifier SHOULD reject if
expires - createdexceeds policy limit
Replayable
nonceparameter MAY be absent- Verifier MAY accept based on time bounds alone
- SHOULD only be used for safe, idempotent operations
Signature Verification
EOA (Externally Owned Account)
- Reconstruct the signature base from the request
- Apply EIP-191 prefix:
"\x19Ethereum Signed Message:\n" + length + message - Recover the public key using
ecrecover - Derive address and compare to
keyidaddress
SCA (Smart Contract Account)
- Reconstruct the signature base from the request
- Apply EIP-191 prefix
- Call
isValidSignature(hash, signature)on the contract atkeyidaddress - Verify return value is
0x1626ba7e(ERC-1271 magic value)
Time Constraints
createdMUST be a positive integer (Unix timestamp)expiresMUST be greater thancreated- Verifier SHOULD reject if
now < created - clockSkew - Verifier MUST reject if
now > expires + clockSkew - Verifier SHOULD enforce
expires - created <= maxValiditySec
Error Codes
| Code | Meaning |
|---|---|
missing_headers | No Signature-Input or Signature header |
label_not_found | Requested label not in Signature-Input |
bad_signature_input | Malformed Signature-Input header |
bad_signature | Signature verification failed |
bad_keyid | Invalid keyid format |
bad_time | Invalid time parameters |
not_yet_valid | Signature not yet valid |
expired | Signature expired |
validity_too_long | Validity window exceeds policy |
nonce_required | Nonce required but missing |
replayable_not_allowed | Replayable signature not accepted by policy |
class_bound_not_allowed | Class-bound signature not accepted by policy |
nonce_window_too_long | Nonce validity window exceeds policy limit |
replay | Nonce already consumed |
not_request_bound | Required components not signed |
digest_required | Content-Digest header missing |
digest_mismatch | Content-Digest doesn't match body |
alg_not_allowed | Signature algorithm not permitted |
bad_signature_bytes | Invalid signature byte encoding |
bad_signature_check | Signature verification function failed |