SNIP 23: Mixed-case checksum address encoding

I’m proposing to enforce the address representation to be 32 bytes long, checksumed as in the Ethereum ecosystem.

The checksum mechanism can be the same.

See also ERCs/ERCS/erc-55.md at master · ethereum/ERCs · GitHub

Proposed implementation

from starkware.starknet.public.abi import starknet_keccak

def checksum_encode(addr: int): # Takes a 32-byte binary address as input
    addr_bytes = int.to_bytes(addr, 32, "big")
    hex_addr = addr_bytes.hex()
    checksummed_buffer = ""

    # Treat the hex address as ascii/utf-8 for hashing
    hashed_address = f"{starknet_keccak(addr_bytes):064x}"

    # 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_buffer

checksum_encode(0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7)
# 0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7

checksum_encode(0xbd3fcc84644ddd6b96f7c741b1562b82f9e004dc7)
# 0x00000000000000000000000Bd3fcc84644dDd6B96F7C741b1562B82f9e004dc7

Awesome the checksum mechanism will be amazing since its same Ethereum

100% in favor of padding addresses.

It’s a huge PITA because every tool treats it differently (SDKs, Apibara, block explorers, etc.) and while 0x0f00 and 0xf00 are numerically the same, since they pretty much have to be stored as strings for interop, this introduces hard to spot bugs.

See the full proposal to be merged™ soon™

I’m also in favour of padding addresses for the sake of consistency as it’s always different with every dev tool.

padding is a neccessary tool for dev, especially when it comes to issues of slot

from starkware.starknet.public.abi import starknet_keccak
from typing import Union

class AddressValidationError(Exception):
“”“Custom exception for address validation errors.”“”
pass

def checksum_encode(addr: Union[int, str]) → str:
“”"
Generate a checksummed address following ERC-55 style convention for Starknet addresses.

Args:
    addr (Union[int, str]): Address as integer or hex string
    
Returns:
    str: Checksummed address string
    
Raises:
    AddressValidationError: If address is invalid
"""
try:
    # Convert string input to int if needed
    if isinstance(addr, str):
        addr = int(addr.replace("0x", ""), 16)
        
    # Validate address size
    if addr < 0:
        raise AddressValidationError("Address cannot be negative")
    if addr.bit_length() > 256:
        raise AddressValidationError("Address exceeds 32 bytes")
        
    # Convert to 32-byte representation
    addr_bytes = int.to_bytes(addr, 32, "big")
    hex_addr = addr_bytes.hex()
    
    # Calculate hash using starknet_keccak
    hashed_address = f"{starknet_keccak(addr_bytes):064x}"
    
    checksummed_buffer = []
    
    # Process each character
    for nibble_index, character in enumerate(hex_addr):
        if character in "0123456789":
            checksummed_buffer.append(character)
        elif character in "abcdef":
            # Check corresponding hash nibble
            hash_nibble = int(hashed_address[nibble_index], 16)
            checksummed_buffer.append(
                character.upper() if hash_nibble > 7 else character
            )
        else:
            raise AddressValidationError(
                f"Invalid hex character '{character}' at position {nibble_index}"
            )
            
    return "0x" + "".join(checksummed_buffer)
    
except ValueError as e:
    raise AddressValidationError(f"Invalid address format: {str(e)}")

def validate_checksum(address: str) → bool:
“”"
Validate if an address string has the correct checksum.

Args:
    address (str): Address string to validate
    
Returns:
    bool: True if checksum is valid, False otherwise
"""
try:
    if not address.startswith("0x"):
        return False
        
    addr_int = int(address.replace("0x", ""), 16)
    return checksum_encode(addr_int) == address
except (AddressValidationError, ValueError):
    return False

Could we get buy-in from SDKs, as well as Wallets?
We’d like an answer and plan to include (and possible alerts and warnings on non-backward compatibility) from:

  • Braavos
  • Argent
  • Keplr
  • starknet-react
  • starknetJS
  • get-starknet
  • ledger
  • CEXes
  • starknet-rs
  • starkscan
  • voyager

etc.
whoever makes sense

What’s wrong with the current checksum approach?

most probably very similar as in the SNIP I’ve taken the EIP implementation and the snippet from starknet js quotes ethers.js

This is not a standard in Starknet though, which is the main purpose of this SNIP

Okay, if it’s just about formalizing, can we make sure the SNIP matches the implementation?
Migrating to a new implementation would be painful for everyone