ERC-55 Mixed-case checksum address encoding
Final · Accounts & wallets · created 2016-01-14
It writes the letters a to f of an Ethereum address in a mix of uppercase and lowercase according to a rule, so that wallets and apps can filter out mistyped addresses. The address length stays at 40 characters.
Why it is needed
An Ethereum address is a long string of 0x followed by 40 hexadecimal characters. If a person copies or types it and gets even one character wrong, it becomes a completely different address. The existing hexadecimal notation has no way to check whether an address is valid, so a mistyped address is accepted as long as its format is correct. The original text cites appending a 4-byte check code and the ICAP scheme as points of comparison. Unlike those, this standard hides the check information in the case of the address letters alone. The length stays at 40 characters, and it is compatible with parsers that already accept mixed-case hexadecimal, so it can be adopted gradually without breaking existing tools. The probability that a mistyped random address passes the check by chance is low, at 0.0247%.
In depth
Encoding rule
The input is a 20-byte binary address. First convert it to a 40-character lowercase hexadecimal string without 0x. Treat this string as ASCII (UTF-8) text, compute its keccak256 hash, and keep the hash as a hexadecimal string too. Then loop over the address string one character at a time. If the character is 0 to 9, append it as is. If the character is a to f, read the character at the same position (nibble_index) in the hash string as a hexadecimal digit, and append the character in uppercase if that value is greater than 7 (8 or higher), otherwise in lowercase. When finished, prepend 0x and return the result. The original text summarizes this as 'capitalize the i-th character if the 4*i-th bit of the hash of the lowercase hexadecimal address is 1'. Saying that the i-th hexadecimal digit of the hash is 8 or higher means the same thing as saying that the most significant bit of that digit is 1.
The hash input is characters, not bytes
This is the part implementations get wrong most often. What goes into keccak256 is not the 20-byte address itself but the 40-character ASCII string of that address written in lowercase hexadecimal. The Python reference implementation hashes the text with eth_utils.keccak(text=hex_addr). The JavaScript implementation uses Buffer.from(address.toLowerCase(), 'ascii') as the hash input. The 0x prefix is removed before hashing. Even if the input address contains uppercase letters, it is first normalized to lowercase with toLowerCase() and then hashed. If you do not follow this order, the result will differ from the test vectors.
Verification method and test vectors
Verification is done by re-encoding and comparing, with no separate algorithm. The reference implementation's test function converts the input string to bytes, re-encodes it with checksum_encode, and uses assert to confirm that the result is exactly identical to the original input string. The original provides three kinds of test vectors: 2 all-caps addresses (0x52908400098527886E0F7030069857D2E4169EE7, 0x8617E340B3D01FA5F11F306F4090FD50E238070D), 2 all-lowercase addresses (0xde709f2102306220921060314715629080e2fb77, 0x27b1fdb04752bbc536007a920d24acb045561c26), and 4 ordinary mixed-case addresses. The all-caps and all-lowercase vectors are also the result of encoding with the same rule, and they serve to check that an implementation produces the same result in these cases too.
Design rationale and detection power
The original Rationale lists three benefits. First, compatibility is preserved because many existing parsers accept mixed-case hexadecimal. This allows the scheme to be introduced gradually over time. Second, the address length stays at 40 characters. Third, each address gets an average of 15 check bits, and the probability that a mistyped random address passes the check by chance is 0.0247%. The original states that this is about 50 times better than ICAP but not as good as a 4-byte check code. The check bits are 'on average' 15 because case information is carried only by the letters a to f and not by digit characters.
Handling of invalid characters and the reference implementation
The Python reference implementation takes a 20-byte binary address as input, and addr.hex() returns lowercase hexadecimal, so the normal path only ever produces 0 to 9 and a to f. If any other character appears, it raises eth_utils.ValidationError. The JavaScript implementation toChecksumAddress strips 0x from the input, converts it to lowercase, computes the keccak hash, and uppercases each position where parseInt(hash[i], 16) >= 8. Digit characters are unchanged by toUpperCase(), so they are handled by the same code with no separate branch. For example, given 0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359, the result is 0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359.