ERC-2098 Compact signature representation format (Compact Signature Representation)

Final · Signatures & auth · created 2019-03-14

Defines a way to carry an Ethereum signature in 64 bytes instead of 65. Of the signature's three values, yParity is packed into the top bit of the s value, so the signature is expressed with only two values, r and yParityAndS.

Why it is needed

An Ethereum signature consists of three values, r, s and yParity, totaling 65 bytes. On chain, however, data is handled in 256-bit (32-byte) units, so fitting a 65-byte signature to that unit inserts 31 zero-padded bytes and makes it 96 bytes. When a transaction is encoded with RLP, the yParity value also takes up 1.5 bytes on average by itself. This waste repeats when verifying the origin of a transaction sent by an externally owned account (EOA), and also when verifying signatures on chain, as in meta-transactions or multisig contracts. As a result, transactions become larger than necessary, gas costs increase, and client code has to handle the three values separately.

What to implement

In depth

The three values that make up a signature: r, s and yParity

A signature on the secp256k1 curve consists of three values, r, s and yParity. r is the x coordinate of a point on the curve, and y can be computed from it. s is the challenge solution obtained when signing with the private key. Because an elliptic curve is symmetric, a single x has two possible y values, and yParity is the value that tells which of the two is meant, by its parity. Given the signature and the signed digest, the signer's public key can be computed. This property is used to verify the origin of a transaction sent by an externally owned account, and to perform on-chain verification in meta-transactions and multisig contracts.

Two observations that make compression possible

First, yParity is always either 0 or 1. By convention 27 and 28 have been used, values chosen so as not to overlap with other binary prefixes used in Bitcoin. Second, the top bit of s is always 0. Under the constraint introduced in Homestead (EIP-2), only canonical signatures are allowed, and a canonical signature flips the parity of the solution to avoid negative values. Combining these two facts, the top bit of s is always empty and yParity needs only 1 bit, so the top bit of s can be borrowed to store yParity.

Bit layout and conversion formulas

The compact signature is laid out in the order [256-bit r value][1-bit yParity value][255-bit s value]. The Python example in the original text assumes yParity is normalized to 0 or 1 rather than 27 or 28. To compress, to_compact(r, s, yParity) leaves r unchanged and builds yParityAndS = (yParity << 255) | s. To convert back, to_canonical(r, yParityAndS) extracts the lower 255 bits with s = yParityAndS & ((1 << 255) - 1) and extracts the top bit with yParity = yParityAndS >> 255. The original text explains that this approach is simple to produce and unpack on both the client and Solidity sides, so it can be supported easily and intuitively.

Size reduction

A regular signature is 65 bytes, and fitting it to a 256-bit word adds 31 zero-padded bytes, making it 96 bytes. In an RLP-encoded transaction, yParity takes up 1.5 more bytes on average. A compact signature is 64 bytes, stays at 64 bytes even when fitted to a word, and saves the 1.5 bytes that yParity takes in an RLP transaction. Through this, the original text aims to simplify transaction handling in client code and reduce gas cost and transaction size.

How it differs from existing signatures

The compact representation does not conflict with existing canonical signatures. The compact representation consists of two values, r and yParityAndS, and is 64 bytes long, whereas a canonical signature consists of three values, r, s and yParity, and is 65 bytes long. Because the number of values and the length differ, the two formats cannot be confused, and the original text relies on this difference to conclude that there is no backward compatibility problem. Code that keeps using existing 65-byte signatures and code that uses 64-byte compact signatures can sit side by side and still be told apart.

Conversion seen through test cases

The original text gives an example of signing two messages with the same private key. The signature for the message "Hello World" has v of 27, so yParity is 0, and the compact signature's yParityAndS is identical to s, a value starting with 0x7e865ad0. The signature for the message "It's a small(er) world" has v of 28, so yParity is 1. The top bit of s flips to 1, so an s that started with 0x139c6d6b becomes a yParityAndS that starts with 0x939c6d6b in the compact signature. In both cases r is unchanged.

Reference implementation and status

The ethers.js library supports this representation in v5 through the unofficial property sig._vs of the split signature. However, the original text states that this should be treated as an internal property and may change depending on community judgment or changes to this EIP. Because it is not an official API, code that relies on this property must assume that. The Security Considerations section states that this EIP introduces no additional security concerns. The status is Final, a settled standard.

Original ERC · ← ERCs