ERC-55 대소문자 체크섬 주소 표기 (Mixed-case checksum address encoding)

Final · 계정·지갑 · 2016-01-14 작성

이더리움 주소의 영문자 a~f를 규칙에 따라 대문자와 소문자로 섞어 적어, 지갑이나 앱이 잘못 입력된 주소를 걸러낼 수 있게 합니다. 주소 길이는 40자 그대로입니다.

왜 필요한가

이더리움 주소는 0x 뒤에 16진수 40자가 이어지는 긴 문자열입니다. 사람이 옮겨 적거나 입력하다 한 글자만 틀려도 전혀 다른 주소가 됩니다. 그런데 기존 16진수 표기에는 그 주소가 올바른지 확인할 장치가 없어, 오타가 난 주소도 형식만 맞으면 그대로 받아들여집니다. 원문은 비교 대상으로 4바이트 검사 코드를 덧붙이는 방식과 ICAP 방식을 들고 있습니다. 이 표준은 그와 달리 주소 글자의 대소문자만 바꿔 검사 정보를 숨겨 넣습니다. 길이는 40자 그대로이고 대소문자가 섞인 16진수를 이미 받아들이는 파서와도 호환되므로 기존 도구를 깨지 않고 서서히 도입할 수 있습니다. 무작위 주소를 잘못 입력했을 때 검사를 우연히 통과할 확률은 0.0247%로 낮습니다.

심층 설명

인코딩 규칙

입력은 20바이트 이진 주소입니다. 먼저 0x를 뺀 소문자 16진수 40자 문자열로 바꿉니다. 이 문자열을 ASCII(UTF-8) 텍스트로 취급해 keccak256 해시를 구하고 해시도 16진수 문자열로 둡니다. 그다음 주소 문자열을 글자 하나씩 돌면서 처리합니다. 글자가 0~9면 그대로 넣습니다. 글자가 a~f면 해시 문자열의 같은 위치(nibble_index) 글자를 16진수 숫자로 읽어, 그 값이 7보다 크면(8 이상) 대문자로, 아니면 소문자로 넣습니다. 끝나면 앞에 0x를 붙여 돌려줍니다. 원문은 이를 '소문자 16진수 주소의 해시에서 4*i번째 비트가 1이면 i번째 글자를 대문자로 적는다'고 요약합니다. 해시의 i번째 16진수 자리가 8 이상이라는 말과 그 자리의 최상위 비트가 1이라는 말은 같은 뜻입니다.

해시 입력은 바이트가 아니라 글자

구현에서 가장 자주 틀리는 부분입니다. keccak256에 넣는 것은 20바이트 주소 자체가 아니라, 그 주소를 소문자 16진수로 적은 40자 ASCII 문자열입니다. Python 참조 구현은 eth_utils.keccak(text=hex_addr)로 텍스트를 해시합니다. JavaScript 구현은 Buffer.from(address.toLowerCase(), 'ascii')를 해시 입력으로 씁니다. 0x 접두어는 해시 전에 제거합니다. 입력 주소에 대문자가 섞여 있어도 toLowerCase()로 먼저 소문자로 통일한 뒤 해시합니다. 이 순서를 지키지 않으면 테스트 벡터와 결과가 달라집니다.

검증 방법과 테스트 벡터

검증은 별도 알고리즘 없이 재인코딩해 비교하는 방식입니다. 참조 구현의 test 함수는 입력 문자열을 바이트로 바꾼 뒤 checksum_encode로 다시 인코딩하고 결과가 원래 입력 문자열과 완전히 같은지 assert로 확인합니다. 원문은 세 종류 테스트 벡터를 줍니다. 모두 대문자인 주소 2개(0x52908400098527886E0F7030069857D2E4169EE7, 0x8617E340B3D01FA5F11F306F4090FD50E238070D), 모두 소문자인 주소 2개(0xde709f2102306220921060314715629080e2fb77, 0x27b1fdb04752bbc536007a920d24acb045561c26), 그리고 대소문자가 섞인 보통 주소 4개입니다. 모두 대문자나 모두 소문자인 벡터도 같은 규칙으로 인코딩한 결과이며 구현이 이런 경우에도 같은 결과를 내는지 확인하는 용도입니다.

설계 근거와 검출력

원문 Rationale는 세 가지 이점을 듭니다. 첫째, 대소문자가 섞인 16진수를 받아들이는 기존 파서가 많아 호환성이 유지됩니다. 그래서 시간을 두고 서서히 도입할 수 있습니다. 둘째, 주소 길이가 40자로 그대로입니다. 셋째, 주소마다 평균 15비트의 검사 비트가 생기며 무작위 주소를 잘못 입력했을 때 검사를 우연히 통과할 확률은 0.0247%입니다. 원문은 이것이 ICAP보다 약 50배 좋은 수치이지만 4바이트 검사 코드만큼은 아니라고 밝힙니다. 검사 비트가 '평균' 15비트인 이유는 a~f 글자에만 대소문자 정보가 실리고 숫자 글자에는 실리지 않기 때문입니다.

잘못된 글자 처리와 참조 구현

Python 참조 구현은 입력이 20바이트 이진 주소이고 addr.hex()가 소문자 16진수를 돌려주므로, 정상 경로에서는 항상 0~9와 a~f만 나옵니다. 그 밖의 글자가 나오면 eth_utils.ValidationError를 던집니다. JavaScript 구현 toChecksumAddress는 입력에서 0x를 떼고 소문자로 바꾼 뒤 keccak 해시를 구하고 각 위치에서 parseInt(hash[i], 16) >= 8이면 대문자로 바꿉니다. 숫자 글자는 toUpperCase()를 해도 변하지 않으므로 별도 분기 없이 같은 코드로 처리합니다. 예시로 0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359를 넣으면 0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359가 나옵니다.

ERC 원문 · ← ERCs