Skip to content

Latest commit

 

History

History
219 lines (194 loc) · 9.97 KB

address.org

File metadata and controls

219 lines (194 loc) · 9.97 KB

ERC-55 Ethereum address encoding

Concepts and purpose

Ethereum address

Ethereum address
An Ethereum address is the last 20 bytes of the Keccak256 hash of the uncompressed public key without the 0x04 prefix

ERC-55 Ethereum address encoding

ERC-55 Mixed-case checksum address encoding

ERC-55 Ethereum address encoding
The ERC-55 Ethereum address encoding is a backward-compatible, mixed-case encoding of case-insensitive Ethereum addresses that incorporates an error detecting checksum into the now case-sensitive Ethereum address by capitalizing certain hex letters of the Ethereum address. The Ethereum address encoding conveys the error detecting information without changing the semantic value of the address. The Ethereum address encoding is used to detect errors and protect against mistakes with the 99.986% accuracy

Design and implementation

Address derive

Address derive
The AddressDerive function derives an Ethereum address from a compressed or an uncompressed public key by taking the last 20 bytes of the Keccak256 hash of the uncompressed public key without the 0x04 prefix. The address derive function converts a compressed public key into an uncompressed public key before applying the Keccak256 hash function
func AddressDerive(pub []byte) ([]byte, error) {
  switch {
  case len(pub) == 65 && pub[0] == 0x04: // Uncompressed public key
  case len(pub) == 33 && (pub[0] == 0x02 || pub[0] == 0x03): // Compressed public key
    pubx, puby := ecc.UnmarshalCompressed(ecc.P256k1(), pub)
    pub = NewPubKey(pubx, puby).Pub
  default:
    return nil, fmt.Errorf("address derive: invalid public key: %x", pub)
  }
  hash := crypto.Keccak256(pub[1:])
  addr := hash[12:]
  return addr, nil
}
    

Address encode

Address encode
The AddressEncode function encodes an Ethereum address and incorporates the error detecting checksum into the address without changing the semantic value of the address. The address encode function
  • Convert the address into a lower case hex string
  • Compute the keccak256 hash of the address hex string
  • Convert the keccak256 hash into a lower case hex string
  • For each character of the address hex string
    • Capitalize each character of the address hex string if the hex value of the corresponding character of the hash hex string is greater than or equal to 8, leaving other characters of the address hex string unchanged
  • Return the encoded mixed-case address with the incorporated checksum
func AddressEncode(addr []byte) string {
  hexAddr := fmt.Sprintf("%x", addr)
  hash := crypto.Keccak256([]byte(hexAddr))
  hexHash := fmt.Sprintf("%x", hash)
  chAddr := strings.Split(hexAddr, "")
  chHash := strings.Split(hexHash, "")
  var encAddr strings.Builder
  for i := range chAddr {
    h, _ := strconv.ParseInt(chHash[i], 16, 8)
    a := chAddr[i]
    if h >= 8 {
      a = strings.ToUpper(a)
    }
    encAddr.WriteString(a)
  }
  return encAddr.String()
}
    

Address verify

Address verify
The AddressVerify function verifies that the encoded mixed-case Ethereum address does not have errors. The address verify function
  • Convert the address into a lower case hex string
  • Compute the keccak256 hash of the address hex string
  • Convert the keccak256 hash into a lower case hex string
  • For each character of the address hex string
    • Check that each character of the encoded address hex string is in the upper case if the hex value of the corresponding character of the hash hex string is greater than or equal to 8, and is in the lower case otherwise
  • Return the invalid checksum error if at least one character of the encoded address does not meet the above conditions, otherwise the encoded address is valid
    var reUpper = regexp.MustCompile(`[A-F0-9]`)
    var reLower = regexp.MustCompile(`[a-f0-9]`)
    
    func AddressVerify(hexAddr string) error {
      hash := crypto.Keccak256([]byte(strings.ToLower(hexAddr)))
      hexHash := fmt.Sprintf("%x", hash)
      chAddr := strings.Split(hexAddr, "")
      chHash := strings.Split(hexHash, "")
      for i := range hexAddr {
        h, _ := strconv.ParseInt(chHash[i], 16, 8)
        a := chAddr[i]
        if h >= 8 && !reUpper.MatchString(a) || h < 8 && !reLower.MatchString(a) {
          return fmt.Errorf("address verify: invalid checksum")
        }
      }
      return nil
    }
            

Testing and usage

Testing address derive, address encode, and address verify CLI commands

go build -o wallet; ./hdwallet/cli-test.nu

Using address derive, address encode, and address verify CLI commands

Show the help and usage instructions of the wallet address command

./wallet address
# NAME:
#    wallet address - Derive, encode and verify an Ethereum address (ERC-55)

# USAGE:
#    wallet address [command [command options]]

# COMMANDS:
#    derive  Derive an Ethereum address from a secp256k1 public key
#              stdin: a compressed or uncompressed secp256k1 public key in hex
#              stdout: an Ethereum address in hex
#    encode  Encode an Ethereum address (ERC-55)
#              stdin: an Ethereum address in hex
#              stdout: an encoded case-sensitive Ethereum address string
#    verify  Verify an encoded case-sensitive Ethereum address (ERC-55)
#              stdin: an encoded case-sensitive Ethereum address string
#              stdout: true if the address is valid, false otherwise

# OPTIONS:
#    --help, -h  show help

Generate a random secp256k1 private key by keccak256 hashing a sequence of 32 bytes taken from the /dev/urandom CSPRNG. Derive a secp256k1 public key from the private key. Derive an Ethereum address from the derived public key. Note, the that same address is derived from both uncompressed and compressed forms of the public key

$env.PATH = $env.PATH | prepend ("." | path expand)
let prv = open /dev/urandom | first 32 | wallet keccak256
print $prv
# 838c2f329e8e98855bd648ca95e3939fc118a0f63b703fb443d0e1f0eaae33cb
let pub = $prv | wallet eckey derive | from yaml
print $pub
# ╭──────┬────────────────────────────────────────────────────────────────────────────────────╮
# │ prv  │ 838c2f329e8e98855bd648ca95e3939fc118a0f63b703fb443d0e1f0eaae33cb                   │
# │ pub  │ 04c694264d1933cb3d3b1a4073b3189452173d7f510312c5c86c9689574a6d25e81523533b578eb09d │
# │      │ 0b4e9414f53a3bd259843aeb22ea677025f51f8b90d8d05e                                   │
# │ pubc │ 02c694264d1933cb3d3b1a4073b3189452173d7f510312c5c86c9689574a6d25e8                 │
# ╰──────┴────────────────────────────────────────────────────────────────────────────────────╯
let addr = $pub.pub | wallet address derive
print $addr
# 445f86f47591cc2161e5efbb31b708e964cc8c6d
let addr2 = $pub.pubc | wallet address derive
print $addr2
# 445f86f47591cc2161e5efbb31b708e964cc8c6d

Generate a secp256k1 key pair. Derive an Ethereum address from the public key. Encode the address and incorporate the error detecting checksum. Note, the encoded address has some letters capitalized, while the initial address is all lower case. Verify the encoded address and confirm that the address is valid and does not have errors. Modify the encoded address in order to introduce errors. Verify the modified encoded address with errors and confirm that the validation returns the invalid checksum error

$env.PATH = $env.PATH | prepend ("." | path expand)
let key = wallet eckey generate | from yaml
print $key
# ╭──────┬────────────────────────────────────────────────────────────────────────────────────╮
# │ prv  │ 840257eb47ab36bbd952b18f856eb399c57534d8eacda2765e89b799f214bde5                   │
# │ pub  │ 04e9eb5e40adab72f15ffe5b650498bc320252b92284c2522e3a30f5ed0bbe7da993b442e1a48e5840 │
# │      │ f59d9b72cf6df6b9fa3d2b45099388b503fbcfc2d77019ed                                   │
# │ pubc │ 03e9eb5e40adab72f15ffe5b650498bc320252b92284c2522e3a30f5ed0bbe7da9                 │
# ╰──────┴────────────────────────────────────────────────────────────────────────────────────╯
let addr = $key.pub | wallet address derive
print $addr
# 883477898a318f37fd7e4f19f9d3e47400f5bd5f
let encAddr = $addr | wallet address encode
print $encAddr
# 883477898A318F37Fd7E4F19f9D3e47400f5BD5F
$encAddr | wallet address verify
# true
$encAddr | str replace --regex '[a-f]' "A" | wallet address verify
# address verify: invalid checksum
# false