안녕하세요. 개발자 모도리입니다.
요즘 참여하고 있는 스터디에서 Mastering Ethereum 를 교재로 하여 매주 스터디를 진행하고 있습니다. 이번 주에 제가 맡은 부분을 정리해서 올립니다.
이더리움 키와 주소
이더리움의 기반 기술 중 하나는 암호학이다.
암호학(cryptography)는 그리스어로 비밀 글쓰기(secret writing)를 의미하지만, 암호학은 단순한 암호화 이 외의 더 많은 것들을 포함한다. - 디지털 서명(digital signature), 디지털 지문(digital fingerprints)
이더리움 네트워크에 정보가 공개 될 때 암호화는 사용되지 않는다. 모든 정보를 누구나 볼 수 있다. 그래서 상태 업데이트가 올바르게 되었는지는와 합의가 이루어졌는지를 검증할 수 있다.
이더리움에서 자산의 소유권을 제어하기 위한 개인 키와 주소에 사용되는 공개 키 암호화(PKC : public key cryptography)을 알아보자.
소개
이더리움에는 EOA(Externally Owned Accounts), CA(Contract Accounts) 두 가지 종류의 계정이 존재한다. CA는 개인 키를 가지고 있지 않기 때문에, EOA에 대해서만 다루도록 한다.
개인 키는 이더리움에서 사용자 간의 상호 작용에 있어 핵심적인 역할을 한다.
계정 주소는 개인 키로 부터 얻어낼 수 있으며, 1개의 개인 키는 오직 1개의 계정 주소와 매핑된다.
개인 키는 이더리움 시스템 내에서 직접적으로 사용되지 않는다. 즉, 이더리움 시스템에 직접적으로 전송되거나 저장되어 있지 않다.
계정 주소와 디지털 서명 만이 전송되고 저장된다.
자산의 접근과 제어는 개인 키를 이용해 만들어진 디지털 서명으로 이뤄진다.
만약 누군가가 개인 키 복사본을 가지고 있다면, 동일하게 자산의 접근과 제어가 가능하다. 그러므로 안전한 개인 키 관리가 필요하다.
이더리움과 같이 공개 키 암호화를 사용하는 시스템은 공개 키와 개인 키로 이루어진 키 쌍을 사용한다. 일반적으로 개인 키는 특별한 파일에 담겨 지갑 소프트웨어가 관리하고 있으므로, 사용자가 직접 보는 경우는 드물다.
공개 키 : 은행의 계좌번호와 같아서, 다른 계정과의 식별을 위해서 사용된다.
개인 키 : 은행의 비밀번호와 같아서, 계정을 제어할 수 있다.
이더리움 전송 시 수신자는 이더리움 주소를 나타낸다. 일반적으로 공개 키를 통해 생성 된 주소이지만, 그렇지 않은 경우도 있다. (CA주소를 나타내기도 하며, CA는 개인 키를 가지고 있지 않다.)
공개 키 암호화와 암호화폐
공개 키 암호화의 발견
공개 키 암호화(비대칭 키 암호화)은 현대의 정보 보안에 핵심이다.
1970년대 전까지는 정부가 비밀로 지키고 있던 강력한 암호학 지식이 Martin Hellman, Whitfield Diffie, Ralph Merkle 세 사람에 의해 공개적으로 알려지게 되었다.
공개 키 암호화
공개 키 암호화은 정보 보안을 위해 유일한 키를 사용한다.
이 키는 특별한 속성을 갖는 수학 함수를 기반으로 한다.
그냥 계산하시는 쉽지만, 역산은 힘들다.
이 함수를 이용하면 수학의 법칙으로 안전이 보장되는 디지털 비밀과 위조할 수 없는 디지털 서명을 만들 수 있다.
예를 들어 두 소수를 곱하는 것은 매우 쉬운 일이지만, 어떤 큰 수가 주어지고 곱해서 이 수를 만들 수 있는 두 소수를 구하는 것은 매우 어려운 일이다.
8018009가 주어지고 곱해서 이 수를 만들 수 있는 두 소수를 찾는 것은 어렵다.
이런 함수 중 몇 몇은 비밀 정보 일부가 주어지면 쉽게 풀 수 있다.
앞의 수에서 한 소수가 2003이라는 것을 알려준다면, 8018009 ÷ 2003 = 4003 를 계산하여 다른 한 소수도 쉽게 구할 수 있다.
이렇게 역산을 할 수 있는 함수의 비밀 정보가 없는 한 역산이 매우 힘든 함수를 trapdoor function이라고 부른다.
타원 곡선
암호학에서 유용하게 사용되는 고급 수학 함수는 타원 곡선이다.
타원 곡선의 산술, 곱셈 연산은 매우 단순하지만, 나눗셈은 거의 불가능하다.
이것을 이산 로그 문제라고 부르며, 현재는 알려진 trapdoor function이 없다.
타원 곡선 암호화는 현재 컴퓨터 시스템에 널리 사용되며 이더리움의 개인 키와 디지털 서명의 기초가 된다.
이더리움에서 공개키 암호화의 사용
이더리움에서 개인 키 - 공개 키 쌍을 만들기 위해서 공개 키 암호화를 사용한다.
"쌍" 이라고 불리우는 이유는 공개 키가 개인 키로 부터 생성되기 때문이다.
이것은 이더리움 계정과 계정의 자산을 제어할 수 있으며, 스마트 컨트랙트 이용 시 인증이 가능하다.
디지털 서명은 어떤 메세지로도 만들 수 있는데, 이더리움에서는 메세지라는 정보와 개인 키를 이용해서 디지털 서명을 만든다.
자산을 옮기거나 스마트 컨트랙트 실행을 위해 이더리움 네트워크에 트랜잭션을 보내기 위해서는 개인 키와 대응되는 디지털 서명이 있어야 한다.
타원 곡선 알고리즘을 통하면 디지털 서명과 계정 주소, 트랜잭션 상세 내역을 맞춰 봄으로써 누구나 트랜잭션 검증이 가능하다.
검증 과정에는 개인 키가 포함되지 않는다. 하지만 계정 주소를 생성한 공개 키와 쌍을 이루는 개인 키를 가진 사람만이 해당 메세지를 만들 수 있다는 것을 확실할 수 있습니다.
이것이 공개 키 암호화의 "magic" 입니다.
TIP
대부분의 지갑 구현체들은 개인 키, 공개 키 쌍을 모두 키 페어 형태로 저장한다. 하지만 개인 키를 통해서 공개 키를 생성할 수 있으므로, 개인 키가 저장되어 있어도 상관 없다.
이더리움 프로토콜에 암호화 부분은 없다. 즉, 이더리움의 모든 메세지는 누구나 읽을 수 있다. 개인 키는 오직 트랜잭션 인증을 위한 디지털 서명을 만드는 데에 만 사용 된다.
개인 키(Private Keys)
개인 키는 간단히 생각하면 랜덤하게 선택 된 숫자이다.
개인 키를 소유하고 제어하는 것이 해당 키와 연결 된 이더리움 주소의 자산을 제어하는 데 핵심이다.
그리고 또한 해당 이더리움 주소의 접근을 승인하는 스마트 컨트랙트에 접근할 수 있다.
개인 키는 자산의 사용하는데 필요한 디지털 서명을 만드는데 사용된다.
개인 키는 반드시 비밀로 잘 지켜야 한다. 개인 키가 제 3자에게 노출된다면, 그 사람 역시 개인 키 소유자와 동일하게 이더리움 계정의 자산과 스마트 컨트랙트들을 제어할 수 있게 된다.
개인 키는 반드시 백업해 두어야 하고, 갑작스런 손실(분실)로 부터 보호되어야 한다. 개인 키를 잃게 된다면, 복구 할 수 없으며 자산 역시 영원히 잃게 된다.
랜덤 한 수로 부터 개인 키 생성하기
랜덤 소스
개인 키를 만들기 위한 첫 단계는 안전한 엔트로피 소스 또는 랜덤성을 찾는 것이다.
개인 키를 만들 때에는 반드시 1 ~ 2^{256} 사이의 숫자를 선택해야 한다.
그 숫자를 고르는 방법이 예측 가능하거나 결정적(deterministic)이지 않다면 어떤 방법이든 상관없다.
이더리움은 랜덤 한 256 bits를 만들기 위해 기본 운영체제의 난수 생성기(RNG : Random Number Generator)를 사용한다.
보통 OS의 난수 생성기는 사람의 랜덤 소스에 의해 초기화 됩니다. 몇 초간 마우스를 마구잡이로 움직이거나 키보드를 마구잡이로 누르라고 요구하는게 이러한 이유입니다. 비트코인 주소 생성기
다른 방법은 컴퓨터의 마이크 채널을 통해 들어 오는 우주 방사선 잡음을 이용하는 방법이다.
개인 키 범위
조금 더 정밀하게 보자면, 개인 키는 0이 아닌 2^{256} (78자리 숫자로, 대략 1.158 * 10^{77} 정도) 이하의 숫자이다.
정확한 숫자는 2^{256}와 앞 38 자리를 공유하며, 이더리움에서 사용하는 타원 곡선의 규칙에 의해 정의된다.
개인 키 생성을 위해서는 랜덤 한 256 bits를 선택하고 유효한 범위인지 확인한다.
프로그래밍 측면에서는 보통 매우 긴 랜덤 한 문자열(암호학적으로 안전한 랜덤성 소스로 부터 수집 된)을 Keccak-256 또는 SHA256(두 방법 모두 간편히 256-bits의 숫자를 만들어 냄 )과 같은 256-bits 해시 알고리즘에 넣어서 랜덤한 256-bits를 만들어 냅니다.
만약 결과가 유효한 범위 안에 있다면 적합한 개인 키를 만들어 낸 것이고, 그렇지 않으면 다시 시도하면 된다.
개인 키 생성은 오프라인 작업
개인 키 생성 과정은 이더리움 네트워크 또는 다른 어떤 외부와의 커뮤니케이션이 필요 하지 않아 오프라인으로 진행할 수 있다. (오프라인으로 하는 것을 추천)
다른 누군가가 선택할 수 없는 숫자를 선택하려면 진~~짜 랜덤해야 한다.
만약에 자신이 직접 숫자를 선택한다면, 다른 누군가도 시도해 볼 확률이 굉장히 높다. (그리고 이더가 털린다.)
좋지 않은 난수 생성기(대부분의 프로그래밍 언어가 제공하는 pseudo-random rand() 함수와 같은 것)을 사용하는 것은 좋지 않은데, 그 이유는 제대로 된 난수 생성기에 비해 훨씬 더 분명하고 훨씬 더 복제하기 쉽다.
온라인 계정의 비밀번호 처럼, 추측 불가능 해야 한다.
다행스럽게도 개인 키를 기억할 필요가 없으므로 개인 키를 선택하는 최선의 방법(진정한 무작위성)을 취할 수 있습니다.
TIP
이더리움 개인 키 영역의 크기는 셀 수 없게 큰 숫자이다. 대략 10진수로 10^{77}, 77자리의 숫자이다. 비교를 해보자면, 볼 수 있는 우주가 10^{80}개의 원자를 포함하고 있다고 한다. 즉, 우주의 거의 모든 원자가 이더리움 계정을 가질 수 있을 만큼의 개인 키가 있는 것이다. 만약 랜덤 한 개인 키를 뽑았다면, 누구도 그 수를 추측할 수 있는 방법은 없다.
Warning
난수 생성을 위해 직접 코드를 작성하거나 프로그래밍 언어에서 제공하는 단순한 난수 생성기를 사용하지 마라. 필수적으로 충분한 엔트로피를 시드로 하는 암호학적으로 안전한 pseudo-random 숫자 생성기(CSPRNG - Cryptographically Secure Pseudo-Random Number Generator)를 사용하라. 난수 생성기의 문서를 공부하고 암호학적으로 안전한지 확실히 확인하라. CSPRNG 라이브러리의 올바른 구현은 키 보안에 굉장히 중요하다.
아래는 랜덤하게 생성된 개인 키를 16진수 형태로 나타낸 것이다. (256 bits 가 64자리의 16진수로 표현되어 있다.): f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
공개 키(Public Keys)
이더리움의 공개 키는 타원 곡선 상의 한 점이다. 즉, 타원 곡선 방정식을 만족하는 x, y 좌표 쌍이다.
단순히 말해서 이더리움 공개 키는 두 숫자이며 그것을 붙여서 사용합니다.
이 두 숫자는 개인 키를 가지고 단 방향 계산을 해서 얻어진다.
개인 키를 가지고 있으며 공개 키를 계산하는 것은 매우 쉽지만, 공개 키를 가지고 개인 키를 구하는 것은 불가능하다.
Warning
앞으로 수식들이 나올텐데, 당황하지 마라. 따라 오기 힘들다면, 다음 몇 섹션은 건너 뛸 수 있다. 당신 대신 수학을 해 줄 수 있는 툴과 라이브러리는 많다.
공개 키는 개인 키를 이용한 타원 곡선 곱셈 계산을 통해 얻어지지만, 실질적으로 역산은 불가능하다.
K = k * G
k : 개인 키
G : 상수 점 (x, y). Generator Point
K : 공개 키
* : 일반 곱셈이 아닌 타원 곡선 상에서 일어나는 특별한 곱셈 연산자
K를 안다고 했을 때 가능한 모든 k를 brute-force로 시도한다고 했을 때 걸리는 시간은 우주가 허용하는 시간 보다 오래 걸릴 것이다.
단순히 말하면 타원 곡선 상의 산술 연산은 일반적인 정수 산술 연산과 다르다.
한 점(G)에 정수(k)배를 해서 다른 한 점(K)를 얻을 수 있다. 하지만 나눗셈과 같은 연산은 없다. 그래서 단순히 공개 키 K를 점 G로 나눈다고 해서 개인 키 k를 얻을 수 없다.
이것이 공개 키 암호화와 암호화폐가 설명하고 있는 단방향 수학 함수이다.
TIP
타원 곡선 곱셈은 암호학자들이 "단 방향" 함수라고 말하는 것 중 하나이다. 정방향(곱셈)을 쉽게 할 수 있지만, 역방향(나눗셈)은 불가능하다. 개인 키 소유자는 쉽게 공개 키를 만들고 누구에게나 공유할 수 있지만, 누구도 공개 키를 가지고 개인 키를 계산할 수는 없다. 이 수학적 트릭이 위조할 수 없고 안전한 디지털 서명 기반이 되고, 디지털 서명은 이더리움 자산의 소유권을 증명하고 컨트랙트를 제어할 수 있다.
개인 키로 부터 어떻게 공개 키를 만들어 내는 지 시연해 보기 전에 타원 곡선 암호화를 조금 더 자세히 살펴 보자.
타원 곡선 암호화 설명
타원 곡선 암호화는 비대칭 (또는 공개 키 ) 암호화 종류로, 타원 곡선의 점에 덧셈과 곱셈으로 표현되는 이산 로그 문제를 기반으로 한다.
아래 그림은 타원 곡선의 한 예로 이더리움에서 사용하는 타원 곡선과 비슷하다.
TIP
이더리움은 비트코인과 동일하게 secp256k1이라는 타원 곡선을 사용한다. 이 덕분에 많은 비트코인 라이브러리를 재사용 할 수 있다.
이더리움은 NIST(US National Institute of Standards and Technology)에 의해 확립 된 secp256k1이라는 표준으로 정의 된 타원 곡선과 수학적 상수의 집합을 사용한다.
secp256k1 곡선은 타원 곡선을 만드는 아래 함수에 의해 정의된다.
y^2=(x^3+7) over (𝔽_p)
or
y^2 mod p = (x^3+7) mod p
mod p 는 이 곡선이 소수 차수 p의 유한 체(finite field) 위에 있다는 것을 나타내며 𝔽_p 라고 쓴다.
p는 매우 큰 소수이다. p = 2^{256} – 2^{32} – 2^{9} – 2^{8} – 2^{7} – 2^{6} – 2^{4} – 1
이 곡선은 실수가 아니라 소수 차수의 유한 체 상에 정의되기 때문에 시각화 하기 어려운 2차원의 흩어진 점들의 형태로 보여진다.
많은 타원 곡선 수학은 우리가 학교에서 배웠던 정수 산술 연산과 상당히 비슷하게 보이고 동작한다.
덧셈 연산자는 수직선 상에서 움직이는 대신에 곡선의 다른 점으로 움직인다.
덧셈 연산자가 정의 되었다면, 곱셈 연산자도 정의할 수 있다. (곱셈은 덧셈을 반복하면 된다.)
공개 키 생성하기
랜덤하게 생성 된 수 k (개인 키)를 미리 정의 된 곡선 상의 점 G(Generator Point)에 곱하면 곡선 상의 또 다른 점 K(공개 키)를 얻을 수 있습니다.
secp256k1 표준의 G가 정의되어 있으며, 항상 같은 G를 사용한다.
K = k * G
k : 개인 키
G : Generator Point (생성점?)
K : 공개 키
모든 이더리움 사용자들이 항상 같은 G 를 사용하기 때문에, G에 개인 키 k를 곱하면 항상 같은 공개 키 K가 나온다.
k(개인 키)와 K(공개 키)는 고정 된 관계를 가지고 있지만, k에서 K를 구하는 단 방향으로의 계산만 가능하다.
그래서 이더리움 주소(공개 키로 부터 생성 된)를 개인 키 노출 없이 누구나와 공유할 수 있다.
예제 용 개인 키
f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
위의 개인 키를 이용해서 공개 키를 만들어 보자.
K = f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315 * G
K 는 2차원 상의 한 점이다. K = (x, y)
개인 키로 얻은 2차원 점 K
x = 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b
y = 83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
이더리움의 공개 키는 130자의 16진수(65 bytes) 표현 된다.
Standards for Efficient Cryptography (SEC1)에서는 타원 곡선 상의 점을 식별하는데 사용할 수 있는 4가지 prefix를 정의했다.
| Prefix | Meaning | Length (bytes counting prefix)
| 0x00 | Point at Infinity | 1
| 0x04 | Uncompressed Point | 65
| 0x02 | Compressed Point with even Y | 33
| 0x03 | Compressed Point with odd Y | 33
이더리움은 압축 안된 공개 키만 사용하여, 04 prefix만 사용한다. 04 + X-coordinate (32 bytes/64 hex) + Y-coordinate (32 bytes/64 hex)
예제 용 개인 키로 만든 공개 키는 다음과 같다. 046e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
타원 곡선 라이브러리
암호화폐 관련 된 프로젝트에서 사용되는 secp256k1 타원 곡선의 구현체가 몇 가지 있다.
OpenSSL
OpenSSL 라이브러리는 secp256k1의 모든 구현을 포함하여, 포괄적인 암호학적 기본 요소(primitives)를 제공한다.
이더리움 주소는 공개 키 또는 컨트랙트에 Keccak-256 해시 함수를 적용하여 만들어 낸 유일한 식별자이다.
이전 예제에서 사용했던 개인 키, 공개 키를 다시 가져와보자.
개인 키 k : k = f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
공개 키 K : K = 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
Warning
주소를 계산할 때 사용하는 공개 키 형식은 prefix 04를 포함하지 않는다.
Keccak-256을 이용한 공개 키 K의 해시 : Keccak256(K) = 2a5bc342ed616b5ba5732269001d3f1ef827552ae1114027bd3ecf1f086ba0f9
Question
실제로 공개 키 K를 Keccak-256 해시 함수에 넣어서 돌려보면 다른 값이 나오는데 왜 그런가??? 더 찾아보기 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0 이 형태 그대로 Keccak256에 입력으로 집어 넣는게 아닌가??? 53a07bd0be78dd08f3505b0f4daafc5d8dc848ca07a8b87f24129615789912ff
아래 소개 되는 helpeth를 이용하면 위와 같은 결과가 나오는데 그냥 직접 Keccak256을 돌리면 아래 값이 나온다. ./helpeth keyDetails -p 0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
그런데 여기서 제공하는 keccak256을 돌리면 또 정상적으로 나오는데, 16진수 인코딩의 문제가 아닌가 생각된다.
입력 데이터에 prefix "0x"를 안 붙였을 때 ./helpeth keccak256 6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0 -> 53a07bd0be78dd08f3505b0f4daafc5d8dc848ca07a8b87f24129615789912ff
입력 데이터에 prefix "0x"를 붙였을 때 ./helpeth keccak256 0x6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0 -> 2a5bc342ed616b5ba5732269001d3f1ef827552ae1114027bd3ecf1f086ba0f9
해시 결과 중에서 마지막 20 bytes (least significant bytes)가 바로 이더리움 주소이다. 001d3f1ef827552ae1114027bd3ecf1f086ba0f9
대부분의 이더리움 주소는 16진수로 인코딩 되었다고 표시하는 prefix "0x"를 붙이고 있다. 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9
이더리움 주소 형식
이더리움 주소는 공개 키의 Keccak-256 해시 값 중 마지막 20 bytes를 16진수로 표현한 것이다.
주소 자체에 체크섬을 포함하고 있는 비트코인 주소와 다르게 이더리움 주소는 별도의 체크섬이 없는 raw 16진수이다.
상위 레이어에서 체크섬을 포함하려고 하는데, ENS 같은 상위 레이어의 개발이 초기에 기대했던 것 보다 매~~우 느려서 대안을 ICAP 같은 인코딩이 지갑 개발자들이 사용하게 되었다.
Inter Exchange Client Address Protocol (ICAP)
International Bank Account Number (IBAN) 와 부분적으로 호환되는 형식
실행 결과
Address: 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9
Address (checksum): 0x001d3F1ef827552Ae1114027BD3ECF1f086bA0F9
ICAP: XE60 HAMI CDXS V5QX VJA7 TJW4 7Q9C HWKJ D
Public key: 0x6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0
결론
이더리움의 공개 키, 개인 키 사용을 중심으로 공개 키 암호화에 대해서 알아보고, 이더리움 주소 생성과 검증에 사용되는 해시 함수와 같은 암호학적 도구들을 사용해 봤다.
그리고 디지털 서명과 어떻게 개인 키를 드러내지 않고 개인 키의 소유권을 주장할 수 있는지도 살펴봤다.