← Protocols
EIP-2098 — Compact Signature Representation
Standard / EIP·EVM

EIP-2098 — Compact Signature Representation

01Description

Final ERC defining a 64-byte compact signature `(r, vs)` where `vs` packs `yParity << 255 | s`. Saves 1 byte (and one calldata word in struct alignment) versus the canonical 65-byte `(r, s, v)` form, and is the wire format used by EIP-712 signers, ERC-1271 verifiers, and Permit2.

02Best for
  • 01calldata-size optimization
  • 02EIP-712 signature transport
  • 03Permit2 / ERC-2612 permits
  • 04ERC-1271 smart-contract signature verification
  • 05L2 calldata compression
03Install
  • pnpm add viem
  • pnpm add ethers
  • forge install vectorized/solady
05Prompt snippet
Encode compact signatures as `bytes32 r` followed by `bytes32 vs` where `vs = (uint256(yParity) << 255) | uint256(s)`. To decode in Solidity: `bytes32 s = vs & 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; uint8 v = uint8((uint256(vs) >> 255) + 27);`. The top bit of `s` is always zero in canonical (low-s) signatures (enforced post-Homestead), so it's free to reuse for parity. Compute `yParity` as `v - 27` (or `v % 2` for chain-id-mixed `v`). Use `ECDSA.recover(hash, r, vs)` from OpenZeppelin (≥4.7) or Solady's `SignatureCheckerLib` — both accept the 64-byte form. viem's `serializeSignature({ r, s, yParity, compact: true })` and ethers' `Signature.from({ r, yParityAndS })` produce 2098-format output.
06Gotchas
  • yParity calc: `yParity = v - 27` works for legacy signatures, but EIP-155 `v` is `chainId * 2 + 35 + parity` — extract parity as `(v - 35) % 2` or use the library helper; getting this wrong silently flips ecrecover to an attacker-controlled address.
  • Non-canonical (high-s) signatures break 2098 because the top bit of `s` is no longer free — always normalize to low-s (`s = N - s; v ^= 1` if `s > N/2`); OpenZeppelin's ECDSA reverts on high-s, but raw `ecrecover` does not.
  • Mixed-format APIs: a function accepting `bytes calldata sig` that branches on `sig.length == 64 || 65` is fine, but one that hard-codes `abi.decode(sig, (bytes32, bytes32, uint8))` reverts on compact sigs — pick a single format per surface and document it.
  • EIP-2098 ≠ ERC-1271 — 1271 takes `bytes signature` of any length and dispatches internally; do not assume 1271 verifiers accept 64-byte input unless they advertise 2098 support.
  • Front-end signers (MetaMask `personal_sign`, `eth_signTypedData_v4`) return the 65-byte form by default; convert to compact only on submit, never on display, since the legacy form is what users see in tooling.
  • Chains with non-secp256k1 signatures (zkSync native AA, StarkNet) cannot use 2098 — feature-detect before applying compression to avoid silent verification failures.
07Alternatives