메타마스크나 카이카스 등 블록체인 상 지갑을 만들면, 지갑의 주소와 키가 발급된다. 그 키를 가진 사용자는 해당 지갑에 대한 접근 권한을 얻는다.
여기서 주소는 공개키에 해당하며, 이는 은행의 계좌번호나 계정의 아이디와 같다. 키는 개인키에 해당하며, 계좌나 아이디의 비밀번호와 같다. 공개키와 개인키는 한 쌍을 이루며, 블록체인 지갑에는 이러한 쌍이 여러 개 존재할 수 있다.
개인키는 블록체인 지갑을 만들 때 생성된다. 생성된 개인키를 특정 함수에 적용하면 공개키를 얻을 수 있다. 이렇게 얻은 공개키를 해시 함수에 대입하면 지갑 주소를 얻을 수 있다. 이때 사용하는 함수들은 역산할 수 없다. 그래서 특정 지갑의 주소를 알고 있더라도 개인키를 도출해낼 수 없다.
개인키는 256비트의 난수로 구성된 값이다. 키 생성을 요청하면, 거대한 난수 표에서 무작위로 값이 선정된다.
이때, 난수를 선정하는 알고리즘이 매우 중요하다. 난수 생성기의 품질이 떨어질 경우 개인키 값을 예측할 가능성이 높아지기 때문이다. 따라서 단순한 난수 생성기가 아니라 검증된 난수 생성기를 사용해야 한다. 실제로 해커가 안드로이드의 난수 생성기를 사용하여 난수를 파악한 뒤 개인키를 예측했고, 이를 통해 안드로이드 지갑에 보관된 비트코인을 도난한 사례가 있다.
이더리움은 생성한 개인키의 64개 헥사 값을 그대로 사용한다. 반면 비트코인은 WIF 방식으로 키를 변환하여 사용한다. WIF(Wallet Import Format)이란 256비트의 개인키를 Base58Check 인코딩 체계를 통해 표현하는 방식이다. 이 방식으로 개인키를 변환하는 과정은 다음과 같다.
1. 개인키 값을 가져온다.
0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1
2. 키 값 앞에 버전 정보를 추가한다. 메인넷의 경우 0x80, 테스트넷의 경우 0xef를 추가한다.
(따라서 메인넷의 경우 모든 키 값이 5 또는 K로 시작하게 된다.)
800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
3. 키 값이 SEC 형식을 사용할 경우 키 값 마지막에 0x01을 추가한다.
800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D01
4. 오타나 변조를 방지하기 위해 키 값을 SHA256 해시 함수에 2번 대입한다. (더블 해싱)
- 8147786C4D15106333BF278D71DADAF1079EF2D2440A4DDE37D747DED5403592
- 507A5B8DFED0FC6FE8801743720CEDEC06AA5C6FCA72B07C49964492FB98A714
5. 더블 해싱 값의 첫 4바이트를 2번 값 뒤에 추가한다.
800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D + 507A5B8D
6. 이를 Base58로 인코딩하여 WIF 결과를 얻는다.
5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
비트코인과 이더리움에서는 ECDSA(Elliptic Curve Digital Signature Algorithm)의 타원 곡선 함수에 개인키 값을 대입하여 공개키를 얻는다. 타원 곡선 함수는 다음과 같다.
K = k*G (k:개인키, G:상수)
G는 기준점으로, 계산을 시작하는 위치다. G에서 접선을 그어 그래프 상에서 접점을 찾는다. 해당 접점을 x축에 대칭시킨다. 이 과정을 개인키 값(k)만큼 반복하면 공개키 값을 얻을 수 있다.
이렇게 얻어진 공개키는 타원 곡선 상 한 쌍의 좌표 (x, y) 이다. 각 좌표의 값은 각각 256비트의 숫자로 표현된다. 이 좌표의 x 값과 y 값을 이어 붙인 형식을 비압축형 공개키라고 한다. 이 경우 다른 형식의 공개키와 구분하기 위해 값 앞에 0x04를 추가한다.
비압축형 공개키의 경우 키를 표현하기 위해 총 520비트의 저장 공간이 필요하다. 이 공간을 줄이기 위해 압축형 공개키 방식을 사용한다. 압축형 공개키는 x 값으로만 공개키를 표현한다. y 값은 생략하는데, y 값은 다음 수식을 통해 도출할 수 있기 때문이다.
y^2 = x^3 + 7
단, 해당 함수에 값을 대입하면 2개의 y 값이 도출된다. 따라서 y가 양수일 경우 K 값 앞에 0x02를, 음수일 경우 0x03을 추가하여 이를 구분한다.
공개키 형식을 정리하자면 다음과 같다.
1. 비압축형 공개키 : K = prefix(0x04) + x + y
2. 압축형 공개키 : K = prefix(0x02 or 0x03) + x
주소는 공개키를 이용하여 얻을 수 있다. 비트코인의 경우 SHA(보안 해시 알고리즘)와 RIPEMD(RACE Integrity Primitives Evaluation Message Digest) 함수에 공개키 값을 대입하여 주소를 얻는다. 반면 이더리움은 Keccak 함수로 주소를 얻는다.
1. 공개키 값을 가져온다.
03564213318d739994e4d9785bf40eac4edbfa21f0546040ce7e6859778dfce5d4
2. 공개키를 SHA256 함수에 대입한다.
482c77b119e47024d00b38a256a3a83cbc716ebb4d684a0d30b8ea1af12d42d9
3. 결과 값을 RIPEMD160 함수에 대입한다.
0c2c910a661178ef63e276dd0e239883b862f58c
4. 결과 값 앞에 0x00을 추가한다.
00 + 0c2c910a661178ef63e276dd0e239883b862f58c
5. 이를 SHA256 함수에 2번 대입한다. (더블 해싱)
c3c0439f33dc4cf4d66d3dd37900fc12597938a64817306b542a75b9223213e0
6. 더블 해싱 값의 첫 4바이트를 4번 값 뒤에 추가한다.
000c2c910a661178ef63e276dd0e239883b862f58c + c3c0439f
7. 이를 Base58로 인코딩하여 공개키를 압축한다. 이것을 주소로 사용한다.
127NVqnjf8gB9BFAW2dnQeM6wqmy1gbGtv
1. 공개키 값을 가져온다.
6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
2. 공개키를 Keccak256 함수에 대입한다.
2a5bc342ed616b5ba5732269001d3f1ef827552ae1114027bd3ecf1f086ba0f9
3. 결과 값의 마지막 20바이트를 가져온 뒤, 값 앞에 0x를 추가한다. 이를 주소로 사용한다.
0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9
단, 이더리움 주소는 checksum이 없어 주소의 오류를 감지하기가 어렵다. 따라서 EIP-55에 따라 checksum을 추가한다.
1. 주소를 가져온 뒤, 주소 앞의 0x를 제외한다.
7f7625faa1ca985e9ad678656a9dcdf79620df6b
2. 이를 Keccak256 함수에 대입한다.
3015b5c87eeb15cce85e3e48eefb50b400dd497c7b0bd41f16937ead349b3784
3. 결과 값의 각 문자가 16진수에 따라 0x8보다 클 경우, 주소의 해당 자릿수를 대문자로 변경한다.
(예를 들어 결과 값의 10번째 문자인 e는 0x8보다 크다. 따라서 주소의 10번째 문자에 해당하는 a를 대문자로 변경한다.)
결과 값 : 3015b5c87eeb15cce85e3e48eefb50b400dd497c...
주소 : 7f7625faa1ca985e9ad678656a9dcdf79620df6b
4. 이러한 규칙에 따라 주소의 소문자를 대문자로 변환한다.
0x7f7625FAa1CA985E9Ad678656A9DcdF79620dF6B