brunch

매거진 개발실습

You can make anything
by writing

C.S.Lewis

by SKKRYPTO Oct 30. 2020

파이썬으로 배우는
블록체인 구조와 이론

지갑(Wallet)

    안녕하세요 스크립토 6기 박세연입니다. 블록체인에 관심을 가지고 공부를 시작했을때, 가장 흥미로웠던 부분들은 지갑과 거래 등에 대한 새로운 내용이었습니다. 현재 실제로 사용하는 지갑과 거래와는 다른 개념이었기때문입니다. 앞으로 약 세 편의 글을 통해 비트코인에서의 지갑, 거래에 대해서 더 공부하고 그리고 블록체인의 블록 채굴에 대해서 공부해보겠습니다. 이번 첫 번째 글에서는 '파이썬으로 배우는 블록체인 구조와 이론' 책을 통해 지갑에 대해 공부한 내용을 적어보겠습니다.

    출처: 조성현, 이광성, 박혜리. 파이썬으로 배우는 블록체인 구조와 이론. 위키북스, 2019.


비트코인 지갑(Wallet)

    비트코인에서 지갑이란 사용자의 개인키와 공개키, 지갑 주소를 관리하고 트랜잭션을 생성하고 확인하는 기능을 수행한다. 비트코인을 공부하면서 흥미로웠던 부분이 바로 이 지갑에 대한 것이었다. 우리가 평범하게 이야기하는 지갑은 단순히 내가 가진 돈 혹은 카드 보관하는 것에 불과하다. 하지만 이 비트코인이라는 암호화폐에서의 지갑은 '키'를 관리하고, '거래(트랜잭션)'을 관리하는 기능을 한다. 비트코인 네트워크에서의 개인키는 코인의 주인을 판단하기 위한 중요한 요인으로 이러한 키를 관리하는 것은 지갑의 중요한 기능이다. 또한 트랜잭션을 생성하고 승인 여부를 확인하는 기능도 수행한다. 간단하게 비트코인의 지갑이란 실제 비트코인을 저장하는 것이 아니라 비트코인의 소유자를 증명하기 위한 개인키와 공개키를 보관하는 곳이다.



1. 비트코인 지갑 주소

지갑: 잔액을 사영할 수 있는 권한이 부여된 키와 주소를 관리 (비트코인의 잔액 자체를 보관하는 것이 아님)

비트코인 지갑 주소: 비트코인 지갑은 고유한 주소를 갖고, 이 주소를 이용해 노드들이 코인을 주고 받는다.

※ 비트코인의 지갑 주소는 해당 지갑 사용자의 개인키와 공개키로부터 만들어진다.

    개인키 ⇒ 공개키 ⇒ 공개키 해시 ⇒ 지갑 주소 이러한 과정으로 개인키로부터 지갑 주소를 생성한다. 따라서 개인키만 있으면 공개키, 지갑 주소는 언제든지 다시 생성할 수 있다. 하지만 반대 방향으로의 변환은 불가능하다. 지갑 주소는 공개키 해시를 Base58Check 인코딩한 값으로 역변환이 가능하지만 공개키 해시를 이용해서 공개키를 만들 수 없고, 공개키를 개인키로 역변환시키는 것은 불가능하다. 이 내용은 뒤에 더 자세히 설명하겠다.



2. 개인키(Private key)

    개인키는 특정 범위의 수에서 랜덤하게 선택한 하나의 수로, 256비트의 매우 큰 수이다. 개인키는 주소 생성의 시작점으로 비트코인의 소유권을 보증하는 가장 중요한 역할을 한다. 개인키는 타원곡선암호의 표준 문서에 정의된 'secp256k1' 규격에 따라 만들어진다.


1) 타원곡선암호의 표준문서

출처: http://www.secg.org/sec2-v2.pdf

The curve E : 사용해야 할 타원곡선의 함수식

n: 함수식 위에 존재하는 순환군의 점의 개수

    타원곡선의 함수식은

    에서

    개인키는 'secp256k1'에 정의된 n보다 작은 수로 만들어진다. 이 수는 256비트 크기의 매우 큰 숫자로, 개인키를 무작위로 만들어서 남의 비트코인을 훔치는 것은 불가능하다.


2) 랜덤 넘버 생성기(Random Number Generator: RNG)

난수를 생성하는 방법

TRNG(True Random Number Gernerators) : 실제 물리적인 행위를 기반으로 난수 생성

PRNG(Pseudo-random Number Generators) : 시드(seed) 값을 이용해 시계열로 난수 생성

CSPRNG(Cryptopraphically secure Pseudo-random Number Generators) : 하드웨어 장치에서 발생하는 노이즈들을 모아서 랜덤 시드로 사용해 난수 생성 (암호적으로 가장 안전한 방식이다.)

<개인키 생성 코드>

CSPRNG 방식, os.urandom()와 random()을 적당히 섞어서 SHA-256으로 난수 생성
위의 방식으로 개인키를 생성할 때, 그 값이 'secp256k1'에 정의된 n값 보다 작으면 된다.
위의 개인키 생성 코드로 3개의 개인키를 생성한 결과


3) Base58Check

    Base58Check 인코딩은 숫자나 문자열을 읽기 쉬운 형태의 문자열로 변환하는 알고리즘이다. 개인키, 공개키, 지갑 주소와 같은 매우 긴 숫자를 문자열로 바꾸면 가독성도 좋아지고, 외우기가 쉽다.

Base58Check 인코딩은 숫자나 문자열을 58개 문자로 표현한다. (0, 1, O, I 와 같이 혼동하기 쉬운 문자는 사용하지 않음)



3. 공개키(Public key)

1) 공개키 생성

    공개키는 개인키와 타원곡선암호로 생성된다.

secp256k1 표준에 정의된 타원곡선 함수식과 곡선상의 한 점인 G(베이스 포인트, 공개키를 만드는 시작점)를 이용한다. 타원 곡선의 덧셈 연산자를 사용하면 베이스 포인트에 G를 계속 더한 지점은 2G, 3G, 4G, …가 된다.

    공개키(K)는 G를 개인키만큼 더해서 만들어진다. 즉, 개인키의 값이 4라면 K=G+G+G+G=4G가 공개키의 위치가 된다. 이 위치도 타원곡선상의 한 점이므로 공개키도 좌표(x, y)로 표현되고 각각의 좌표는 256비트로 공개키는 x, y를 이어붙인 512비트이다.



    공개키를 계산할 때 개인키는 256비트의 매우 큰 수이므로 Double-and-Add알고리즘을 사용한다. Double-and-Add알고리즘은 덧셈을 수행할 때 2의 거듭제곱을 이용해 큰 수를 더 빠르게 더하는 알고리즘이다. 따라서 점 G를 매우 큰 수만큼 더해도 빠른 속도로 값을 계산할 수 있다.

<공개키 생성 코드>

앞의 개인키 코드 생성에서와 같은 난수 생성 방법으로 개인키를 먼저 생성한다.
생성한 개인키 d를 이용해서 공개키를 생성한다.

    여기서 double-and-Add 알고리즘을 사용해서 G를 개인키(아주 큰 수)만큼 더해준다. 만약 이 알고리즘을 사용하지 않고 개인키만큼 반복문을 실행해서 더한다면 계산이 매우 오래걸릴 것이다. 개인키를 알고 있으면 이 계산을 빠르고 쉽게 할 수 있지만 공개키로부터 개인키를 계산하는 것은 현실적으로 불가능하다. 따라서 공개키를 개인키로 역변환시키는 것이 불가능한 것이다.


2) 공개키 유형

비압축 포맷(uncompressed Format)

(x, y) 좌표를 모두 사용하고, 비압축 포맷임을 표시하기 위해 앞에 0x04를 붙인다.

비압축 포맷의 공개키 길이 : 0x04(8비트) + x좌표(256비트) + y좌표(256비트) = 520비트

압축 포맷(compressed Format)

x-좌표만 사용하고, 압축 포맷임을 표시하기 위해 앞에 0x02(y좌표 짝수) 혹은 0x03(y좌표 홀수)을 붙인다.

압축 포맷의 공개키 길이 : 0x02/0x03(8비트) + x좌표(256비트) = 264비트

    비압축 포맷을 기록하면 트랜잭션의 크기가 커지므로 더 많은 수수료를 지급해야 하므로 압축 포맷의 공개키를 쓰는 것이 좋다.


3) 공개키 포맷 변환

    압축 포맷의 공개키를 비압축 포맷으로 변환하려면 주어진 타원곡선의 함수식에 압축 포맷의 x 좌표 값을 대입해서 y 값을 계산한 후, x, y값을 이용해 비압축 포맷으로 나타내면 된다.

    타원곡선은 x축에 대칭이므로 한 x 좌표값에 y값이 2개 존재한다. 이것이 위의 압축 포맷에서 y좌표가 짝수인 경우와 홀수인 경우를 구분한 이유이다.








    주어진 타원곡선 함수와 x-좌표를 이용해서 y-좌표를 구하려면 아래의 정리가 필요하다.

    이 정리는 정수론의 '법(modular) p에 대한 제곱수 찾기 문제'로 P가 소수이고, a가 0이 아니며 p를 4로 나눴을 때의 나머지가 3인 경우 옆의 식으로 y-좌표를 구할 수 있다.

여기서 사용하는 secp256k1에 정의된 p는 4로 나누었을 때 나머지가 3이므로 위의 정리를 쓸 수 있다. 

<공개키 포맷 변환 코드>

타원곡선암호의 표준문서에 정의된 secp256k1의 p값
앞의 개인키, 공개키 생성 코드에서 키를 생성한 결과를 이용한다.
비압축 포맷(uncompressed format)
압축 포맷(compressed format) y좌표의 값에 따라 02, 03
위의 공식을 적용하는 코드. 이를 통해 x 값으로 y의 값을 구할 수 있다.



4. 지갑 주소(Address)

    지갑 주소는 비트코인을 주고받을  사용된다또한 지갑 애플리케이션에서 잔액과 거래 내역을 관리하는 경우에도 사용된다.


1) 지갑 주소 생성과정

개인키로 비압축 포맷의 공개키를 만든다.

double-SHA-256과 RIPEMD160 알고리즘을 이용해 공개키 해시 값을 계산한다.

    공개키 해시와 지갑 주소는 Base58Check 인코딩을 수행했는지에만 차이가 있으므로 상호 변환할 수 있는 같은 정보이다. 비트코인 트랜잭션의 출력부에 수신자의 지갑 주소 대신 공개키 해시를 기록하고, 블록체인 데이터에서 잔액을 조회하는 경우에도 지갑 주소를 직접 사용하지 않고 공개키 해시 값을 사용한다.

    공개키 해시로 지갑 주로를 만들 때, 공개키 해시 값 앞부분에 버전 프리픽스(prefix)를 추가한 다음 Base58Check인코딩을 수행한다. 버전 프리픽스는 지갑이 어떤 용도로 사용되는지 구분하기 위한 부분이다.

0x00: 메인넷에서 사용되는 일반 지갑 주소의 버전 프리픽스

0x6f: 테스트넷에서 사용되는 시험용 지갑 주소의 버전 프리픽스 (테스트넷은 시험용 네트워크이다.)

0x05: P2SH용 지갑 주소의 버전 프리픽스

<지갑 주소 생성 코드>

이 코드는 pybitcointools 라이브러리를 수정하여 사용한다.

개인키를 생성하고, 해당 개인키를 이용해서 공개키를 생성한다.
공개키로 공개키 해시를 생성하고 해당 공개키 해시에 Base58Check 인코딩을 수행해 지갑 주소를 생성한다. 지갑 주소와 공개키 해시는 서로 변환이 가능하다.
생성된 지갑주소는 메인넷의 지갑주소이므로 '1'로 시작하고, 해당 지갑주소는 다시 공개키 해시로 변환이 가능하다.

공개키 해시에 버전 프리픽스를 붙이고 인코딩을 수행하면 메인넷의 지갑 주소는 '1', 테스트넷의 주소는 'm'이나 'n', P2SH용 주소는 '3'으로 시작한다.



5. 지갑의 백업 관리

    지갑을 백업한다는 것은 개인키를 백업한다는 의미이다개인키만 있으면 공개키와 지갑 주소는 언제든지 생성할  있으므로 개인키만 안전하게 보관하면 된다.


1) 백업 방식

웜 스토리지(Warm Storage): 개인키를 CD, USB, 외장하드디스크 등의 전자장치에 백업하는 방식

콜드 스토리지(Cold Storage): 개인키를 물리적으로 기록해 두는 방식

페이퍼 월렛(paper wallet): 개인키를 종이에 기록해두는 것

    개인키를 분실하면 블록체인에 기록된 해당 지갑의 UTXO는 영원히 아무도 사용할 수 없다. 혹은 비트코인을 보유한 사람이 불의의 사고로 더 이상 개인키를 사용할 수 없는 경우에도 해당 비트코인은 영원히 사장된다.


2) 브레인 월렛(Brain Wallet)

    브레인 월렛은 개인키를 랜덤하게 생성하지 않고 단어 목록이나 특정 문장(passphrase)을 사용해서 개인키를 만드는 방식. 따라서 개인키를 만들 때 사용한 단어 목록이나 문장만 기억하고 있으면 언제든지 다시 개인키를 만들 수 있어서 개인키 자체를 따로 보관할 필요가 없다.

<브레인 월렛 코드>

passphrase에 저장된 문자열로 개인키를 생성할 수 있다.
위의 문자열로 생성한 개인키와 공개키, 지갑주소이다.
passphrase에 저장된 문자열을 변경하면 또 다른 개인키를 생성할 수 있다.


3) 베니티 월렛(Vanity Wallet)

    베니티 월렛은 지갑 주소의 특정 위치에 사용자가 원하는 문자열이 나타나게 한 것이다. 따라서 사용자나 다른 사람들이 지갑 주소를 알아보기 쉽다. 하지만 원하는 문자열이 나타날 때 까지 개인키를 반복해서 생성해야하므로 문자열이 길면 시간이 매우 오래걸린다. 실제로 예제 코드를 실행했을 때 주소를 생성하기까지 몇십초가 걸렸다.



6. 지갑의 유형과 키 관리

    지갑은 키를 관리하는 방식에 따라 크게 비결정적 방식, 결정적 방식으로 나눌 수 있다.


1) 비결정적 방식 지갑(Non-deterministic Wallet) Type-0 개인키를 랜덤하게 생성

    비트코인에서 가장 중요시되는 '거래의 익명성'을 위한 초기 지갑으로, 독립적으로 랜덤하게 생성된 개인키들을 모아놓는 장소라고 볼 수 있다. 키들은 서로 아무런 연관성이 없어서 보안 측면에서는 안전한 방식일 수 있지만 백업이 너무 복잡하다.


2) 결정적 방식 지갑(Deterministic Wallet) Type-1 개인키를 공통시드로부터 생성

    백업의 문제를 해결하기 위해 공통 종자(common seed)에서 개인키를 얻는 방식이다. 시드 값과 해시 함수를 이용해서 연쇄적으로 키를 만들기 때문에 키들은 서로 독립적이지 않고 어떤 관계를 갖는다. 결정적 방식은 초기 시드 값만 보관하면 나머지 키들은 언제든지 다시 만들 수 있다는 장점이 있다. 따라서 모든 키들을 일일이 백업할 필요가 없어진다. 하지만 이러한 키들의 모임인 키 체인(Key chain)에서 개인키 하나라도 해킹될 경우 여러개의 비밀키가 해킹되는 문제가 발생할 수 있다.



7. 계층 구조의 결정적 방식(Hierarchical Deterministic Wallets: HD Wallets, BIP-32) Type-2

1) HD 지갑의 구조

    HD 지갑은 계층적 구조를 이룬다. 마스터 시즈로부터 HMAC-SHA512 알고리즘의 결과 값으로 마스터 개인키를 만들고, 마스터 개인키를 활용해 마스터 공개키를 생성한다.

출처: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki

    여기서, 하위 계층의 공개키를 만들 때, 해당 계층의 개인키를 사용해서 공개키를  만드는 것이 아니라 상위 계층의 공개키를 사용해서 만든다. 지갑 주소를 만들 때도 마찬가지로 상위 계층의 공개키를 사용한다.

상위 계층의 공개키 ⇒ 하위 계층의 공개키 = 하위 계층의 개인키로 만든 공개키 (동일하다)

하위 계층의 키는 상위 계층의 키를 검색하는 데에 사용할 수 없고, 하부 트리를 검색하는 데에만 사용할 수 있따. 이를 통해 보안이 뛰어나다.


    이번 글에서는 개인키, 공개키, 지갑주소가 생성되는 과정과 지갑의 유형에 대해서 공부했습니다. 실제로 코드를 실행해보면서 내용을 이해하려고 하니 더 이해가 잘 되는 것 같습니다. 다음 글에서는 트랜잭션(Transaction)에 대해 공부한 내용을 가지고 오겠습니다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari