import eth_utilsdef checksum_encode(addr): # Takes a 20-byte binary address as input hex_addr = addr.hex() checksummed_buffer = "" # Treat the hex address as ascii/utf-8 for keccak256 hashing hashed_address = eth_utils.keccak(text=hex_addr).hex() # Iterate over each character in the hex address for nibble_index, character in enumerate(hex_addr): if character in "0123456789": # We can't upper-case the decimal digits checksummed_buffer += character elif character in "abcdef": # Check if the corresponding hex digit (nibble) in the hash is 8 or higher hashed_address_nibble = int(hashed_address[nibble_index], 16) if hashed_address_nibble > 7: checksummed_buffer += character.upper() else: checksummed_buffer += character else: raise eth_utils.ValidationError( f"Unrecognized hex character {character!r} at position {nibble_index}" ) return "0x" + checksummed_bufferdef test(addr_str): addr_bytes = eth_utils.to_bytes(hexstr=addr_str) checksum_encoded = checksum_encode(addr_bytes) assert checksum_encoded == addr_str, f"{checksum_encoded} != expected {addr_str}"test("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")test("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")test("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")test("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")
In English, convert the address to hex, but if the ith digit is a letter (ie. it's one of abcdef) print it in uppercase if the 4*ith bit of the hash of the lowercase hexadecimal address is 1 otherwise print it in lowercase.
Rationale
Benefits:
Backwards compatible with many hex parsers that accept mixed case, allowing it to be easily introduced over time
Keeps the length at 40 characters
On average there will be 15 check bits per address, and the net probability that a randomly generated address if mistyped will accidentally pass a check is 0.0247%. This is a ~50x improvement over ICAP, but not as good as a 4-byte check code.
Implementation
In javascript:
const createKeccakHash = require('keccak')function toChecksumAddress (address) { address = address.toLowerCase().replace('0x', '') var hash = createKeccakHash('keccak256').update(address).digest('hex') var ret = '0x' for (var i = 0; i < address.length; i++) { if (parseInt(hash[i], 16) >= 8) { ret += address[i].toUpperCase() } else { ret += address[i] } } return ret}
Note that the input to the Keccak256 hash is the lowercase hexadecimal string (i.e. the hex address encoded as ASCII):
var hash = createKeccakHash('keccak256').update(Buffer.from(address.toLowerCase(), 'ascii')).digest()
Test Cases
# All caps0x52908400098527886E0F7030069857D2E4169EE70x8617E340B3D01FA5F11F306F4090FD50E238070D# All Lower0xde709f2102306220921060314715629080e2fb770x27b1fdb04752bbc536007a920d24acb045561c26# Normal0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d3590xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb
Citation
Please cite this document as:
Vitalik Buterin, Alex Van de Sande, "Mixed-case checksum address encoding,"
Ethereum Improvement Proposals, no. 55, January 2016. [Online serial].
Available: https://eips-wg.github.io/EIPs/55/.