※ 예시를 위해 사용된 주소(address)와 Private Key는 테스트 용도입니다.
이더리움 주소(Address) 란?
ex: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
이더리움의 네트워크 EVM에는 address type의 데이터가 있습니다.
0x + 40글자(char)의 형태로 20 bytes의 용량을 갖고 있습니다.
- 0x는 단순히 16진수 데이터를 표기하는 의미를 갖고 있으며, 데이터의 용량에 포함되지 않습니다.
- 1 char = 4 bit , 8 bit = 1 bytes이며, address의 40글자(char)는 40 * 4 / 8 = 20 bytes가 됩니다.
위 데이터의 형태에서 벗어나게 되면 EVM은 이를 address type 데이터로 인식하지 않고, Solidity의 address 변수에 데이터가 저장되지 않습니다.
블록체인 지갑 주소 EOA 란?
메타마스크나 카이카스 등 블록체인 상에 지갑을 만들면, 지갑의 주소(address)와 키(private key)가 발급됩니다. 그 키를 가진 사람이 해당 지갑의 접근 권한을 얻을 수 있습니다.
지갑의 주소(EOA)는 계좌번호 또는 아이디와 같습니다. 그리고 키는 계좌 또는 아이디의 비밀번호를 의미합니다.
계좌의 비밀번호를 알고 있는 사람이 예금된 디지털 화폐를 전송할 수 있듯, 블록체인에서도 키를 가진 사람이 코인 및 토큰을 전송 할 수 있습니다.
이더리움의 주소(address)는 아래 두 가지로 나뉩니다.
Contract Address (CA) : 컨트렉트 주소
Externally Owned Account (EOA) : 개인 소유 주소
이번 게시글에서는 지갑 주소인 EOA가 생성되는 원리에 대해 알아보겠습니다.
개인 소유 주소(EOA : Externally Owned Account) : 이더리움에서 사용자 간의 상호 작용을 하는 역할
EOA는 공개 키와 개인키를 한 쌍으로 존재합니다. “쌍”이라고 표현하는 이유는 공개키는 개인키로부터 생성되기 때문입니다. 우리는 공개 키가 어떻게 생성되는지를 알기 위해, 개인키가 어떻게 만들어지는지를 먼저 알아야 합니다.
(1) 개인키 생성 원리
이더리움의 개인 키는 기본 운영체제의 난수 생성기(RNG : Random Number Generator)를 이용해 256bit의 엔트로피를 만들어 냅니다.
256bit의 엔트로피 란 :
1 ~ 2²⁵⁶ 사이의 숫자 범위(78자리 숫자로, 대략 1 ~ 1.1579209 E + 77)에서 예측 불가능한 알고리즘 하에 무작위로 선택된 숫자를 말합니다.
아직까지도 수많은 논문과 연구가 이루어질만큼 프로그램에서 무작위 한 난수를 생성하는건 수학적으로 상당히 어려운 일입니다.
수학이란 논리적으로 계산되는 이론을 말합니다. 무작위한 난수는 생성될 다음 숫자를 예측하지 못해야 하지만, 수학적 알고리즘으로 돌아가는 컴퓨터에게 예측 불가능한 일을 시키는 건 AI에게 감정을 가르치는 일과 같습니다.
그렇게 생성된 1~2²⁵⁶ 사이의 완전 랜덤 한 숫자를 16진수로 표현하면 개인 키가 만들어집니다.
어떤 사람은 완전 랜덤한 숫자가 겹치는 경우 어떻게 되는지에 대해 궁금해 할 수 있습니다.
이더리움의 난수 범위는 1 ~ 2²⁵⁶입니다. 이 범위를 비교해 보자면, 우리가 현재 볼 수 있는 우주에서 관측 가능한 원자는 10⁸⁰개라고 합니다. 2²⁵⁶은 약 10⁷⁷입니다.
우연히 생성한 지갑의 개인키가 다시 생성될 가능성은 존재하지만, 엔트로피 범위에서 발생할 확률은 수학적으로 불가능한 확률을 의미합니다.
(2) 공개 키 생성 원리
공개 키는 이더리움의 난수 생성기에서 얻은 난수를 ECDSA(Eliptic Curve Digital Signature Algorithm)의 타원 곡선 함수에 대입하여 얻을 수 있습니다.
ECDSA(Eliptic Curve Digital Signature Algorithm) 타원 곡선 함수는 암호학에서 사용되는 공식이며, 암호화된 데이터를 다시 복호화할 수 없는 해시 암호 함수중 하나입니다.
타원 곡선 방정식 :
K(공개키) = k(개인 키) * G(Generator Point:생성점)
G(Generator Point:생성점)은 상수이며, G를 기준점으로 접선을 그어 그래프 상에 접점을 찾습니다. 해당 접점을 x축에 대칭시킵니다. 이 과정을 개인키(K)만큼 반복하면 공개키를 얻을 수 있습니다.
수학적 이론이지만 아래 영상을 참고하면 이해가 빠를 겁니다.
> 홀인원 최희준 교수님의 타원곡선 암호 강의
공개키 형식 : 비압축형 / 압축형
- 비압축형
ex : 0x04b238b89abf707218c9184f3efd9568973d44f3ae5905e8f2bdcf9b2c90a409540eef946d7086b1acd034bc13ca00d6b670420a6bf7bf25a22a8ee952eb587c38
ECDSA를 통해 얻어진 공개키는 타원 곡선 상의 한 쌍의 좌표(x , y)입니다. 이때 각 좌표의 값을 각각 256비트의 숫자로 표현하게 되며,
x 값과 y 값을 이어 붙인 형식을 비압축형 공개키라고 합니다.
결과값은 0x04 + 128 글자(char)의 형태를 갖게 됩니다.
0x04는 비압축형을 의미합니다.
공개키 데이터 용량 계산 :
x(256 bit) + y(256 bit) = K (512 bit)
1 char = 4 bit , 8 bit = 8 bytes
K : 0x04 + 128 (char)
128 char * 4 bit = 512 bit / 8 bit = 64 bytes
- 압축형
ex : 0x02b238b89abf707218c9184f3efd9568973d44f3ae5905e8f2bdcf9b2c90a40954
비압축형 공개키의 경우, 64 bytes의 데이터 용량을 갖습니다. 이 공간을 줄이기 위해 압축형 공개키 방식을 사용합니다.
압축형 공개키는 x 값으로만 공개키를 표현합니다. y 값은 생략하지만, SECP256K1 타원 곡선 방정식으로 도출할 수 있습니다.
SECP256K1 방정식 :
압축형인 경우 y 값에 따라, y가 양수일 경우 "0x02"를, 음수일 경우 "0x03"을 앞에 추가하여 구분합니다.
결과값은 0x03 / 0x02 + 64 글자(char)의 형태를 갖게 됩니다.
(3) 주소(address) 생성 원리
ex : 0x2B814A290C1EEb7cb7e739726133798CffB32f00
ECDSA를 통해 얻어진 공개키는 총 128 문자열(0x04 제외)이며, 64 bytes의 저장 공간을 갖습니다. 너무 길고 큰 용량이기에 주소로 사용하기에 적합하지 않습니다. 때문에 비압축형 공개키를 Keccak256 해시 암호 알고리즘을 통해 64 길이의 문자열(HEX)로 만듭니다.
1 char = 4 bit, 64 char * 4 = 256 bit, 256 / 8 = 32 bytes
그리고 64개 문자열 중, 앞에 24개를 버리고 "0x"를 붙여 0x + 40 글자(char)의 형태를 갖추게 됩니다.
이렇게 최종적으로 우리가 메타마스크나 카이카스 지갑에서 사용하는 지갑의 주소 값이 만들어집니다.
📝 Code Example by JavaScript with ethers.js
이번에는 자바스크립트를 활용하여 지갑을 생성해보겠습니다.
이번 실습은 ethers 모듈을 사용하여 진행하겠습니다.
> npm i ethers
(1) Private key 생성
이더리움은 난수 생성기(RNG : Random Number Generator)를 통해 무작위 숫자를 만들어냅니다.
const {ethers} =require("ethers");
const random = ethers.Wallet.createRandom();
console.log(random.privateKey);
> 0x4bf90be9fcbdb9037d195acc9b4d9b387b7843917eb68381a0babd40380f02cd
console.log(random.address);
> 0x19222E33Afb6AE475A40c323915849B90867070D
ethers 모듈의 ethers.Wallet.createRandom() 를 통해 쉽게 난수를 생성하고, private key를 만들어 낼 수 있습니다.
(2) Public key 생성
const bytes32 = ethers.utils.arrayify(privateKey)
> Uint8Array(32) [
75, 249, 11, 233, 252, 189, 185, 3,
125, 25, 90, 204, 155, 77, 155, 56,
123, 120, 67, 145, 126, 182, 131, 129,
160, 186, 189, 64, 56, 15, 2, 205
]
createRandom()으로 생성된 PrivateKey는 16진수의 형태입니다. ECDSA(Eliptic Curve Digital Signature Algorithm)의 타원 곡선 함수는 수학공식이기에, 숫자로 변환해 줘야 합니다. 이때 자바스크립트가 표현할 수 있는 최대 숫자를 넘어가게 되면 애러가 발생하기에, Array의 형태로 숫자를 유지시켜주는 모듈을 이용해야 합니다. ethers.utils.arrayify() 함수가 이를 도와줍니다.
const { SigningKey } = require("ethers/lib/utils");
const signingKey = new SigningKey(bytes32)
console.log(signingKey);
> SigningKey {
curve: 'secp256k1',
privateKey: '0x4bf90be9fcbdb9037d195acc9b4d9b387b7843917eb68381a0babd40380f02cd',
publicKey: '0x047668e954e5548bbd68019992442bdb3846442774ade67847bb0f9aa0f2fd91d6e486f95b01bfcf3081be2cce8f201555fdf38110a001d72197d954d367eacd16',
compressedPublicKey: '0x027668e954e5548bbd68019992442bdb3846442774ade67847bb0f9aa0f2fd91d6',
_isSigningKey: true
}
ethers/lib/utils모듈 안에 SigningKey Class 함수가 있습니다. 이를 통해 타원곡선 함수를 계산할 수 있습니다.
결과 값을 보면 비압축형 publicKey와 압축형 compressedPublicKey를 둘 다 계산해줍니다.
(3) address 생성
const address = ethers.utils.keccak256(publicKey)
console.log(address);
>0x596a5438d1b8c0e5fecf00d819222e33afb6ae475a40c323915849b90867070d
// 2 + 24 = 맨 앞 "0x" + 24개 제거
console.log("0x" + address.slice(2 + 24));
>0x19222e33afb6ae475a40c323915849b90867070d
publicKey를 keccak256 해시 함수로 암호 시킨 후, 맨 앞 24 글자를 지워주면
address의 값이 최종적으로 나오게 됩니다.
지금까지 이더리움이 개인 소유 지갑 주소를 생성하는 원리에 대해 알아봤습니다.
감사합니다.
'Block Chain > Web3 Series' 카테고리의 다른 글
[Wallet Series #2] 메타마스크의 window.ethereum는 어디서 왔을까? (0) | 2023.04.13 |
---|---|
[Wallet Series #1]이더리움의 디지털 서명과 타원곡선 함수 (0) | 2023.04.11 |
[Rollup Series #2] 이더리움의 롤업을 위한 댕크샤딩 & PBS (0) | 2023.04.06 |
[RollUp Series #1] 레이어2와 사이드체인의 차이점 (0) | 2023.04.03 |
댓글