관리 메뉴

개발이야기

[Mastering Bitcoin] 마스터링 비트코인 내 맘대로 정리 - Ch 05 본문

블록체인 /마스터링 비트코인

[Mastering Bitcoin] 마스터링 비트코인 내 맘대로 정리 - Ch 05

안성주지몬 2019. 1. 18. 00:00

마스터링 비트코인 Ch 05 - Wallet



Wallets

비트코인에서의 '지갑'은 몇 가지 다른 의미로 쓰인다.

높은 수준에서 ‘지갑’은 기본 사용자 인터페이스로 사용되는 응용 프로그램이다. 사용자의 돈에 접근하고, 키와 주소 관리, 잔액 추적, 트랜잭션 생성 및 서명을 제어한다.

조금 더 좁은 의미의 프로그래머의 관점에서 보면 ‘지갑’이라는 단어는 사용자의 키를 저장하고 관리하는 데 사용되는 데이터 구조를 의미한다.

이 장에서는 두 번째 의미를 살펴볼 것이다.

Wallet Technology Overview

비트코인 지갑에 비트코인이 들어 있다는 것은 오해다. ‘코인’은 비트코인 네트워크의 블록체인에 기록되고 ‘지갑’에는 ‘키’만 들어있다. 지갑은 개인키, 공개키 쌍을 포함하는 키 체인이라고 할 수 있다. 사용자는 지갑의 키를 사용하여 트랜잭션에 서명함으로써 코인에 대한 소유권을 증명한다.

지갑에 포함된 키가 서로 연관이 있는지 여부에 따라 비결정적 지갑과 결정적 지갑 두 가지 유형으로 나눌 수 있다. 자세한 것은 뒤에서 설명한다.

Nondeterministic(Random) Wallets

첫 번째 비트코인 지갑(지금은 Bitcoin Core라고 함)은 무작위로 생성된 개인키의 모음이다. 예를 들어, 원래 Bitcoin Core 클라이언트는 처음에 100개의 임의의 개인키를 미리 생성하고 각 키를 한 번만 사용하여 필요에 따라 더 많은 키를 생성한다. 이 지갑은 키들을 관리하고 백업하고 가져오기가 번거롭기 때문에 ‘결정론적 지갑’으로 대체되고 있다.

이 지갑의 단점은 많은 키를 생성 할 경우 모든 키의 복사본을 보관해야하므로 지갑을 자주 백업해야 한다는 것이다. 각 키들을 백업하지 않으면 지갑이 손실되었을 때 복구할 수 없게 된다. 이는 하나의 트랜잭션에만 각 비트코인 주소를 사용하여 주소 재사용을 피하는 원칙과 직접적으로 충돌한다. 주소 재사용은 여러 트랜잭션 및 주소를 서로 연관시킴으로써 프라이버시를 감소시킨다. 따라서 주소 재사용을 피하고 싶다면 이 지갑을 사용하지 않는 것이 좋다.

[그림1] 비결정적(임의) 지갑 : 무작위로 생성 된 키의 모음

Deterministic(Seeded) Wallets

결정적 지갑은 단방향 공통 시드(Common seed)에 단방향 해시 함수를 통해서 개인키를 연속적으로 생성한다. 여기서 ‘시드(seed)'는 인덱스 키 또는 '체인 코드와 같은 다른 데이터와 결합되어 임의로 생성된 숫자이다.

[그림2] 공통 시드에서 개인키가 파생되는 과정



결정적 지갑에서 시드는 모든 파생된 키를 복구하기에 적절하므로 생성 시에 한 번 백업하는 것으로 충분하다. 또한 지갑으로 보내고 가져오기에도 적절하므로 다른 지갑으로 이동할 때에 사용자의 모든 키를 쉽게 이동시킬 수 있다.



[그림3]  결정적 지갑




HD Wallets(Hierarchical Deterministic wallet) -> 다중주소를 가지고 있는 지갑(하나의 지갑에 여러 주소)

‘계층 결정적 지갑’은 하나의 시드(seed)에서 많은 키를 쉽게 파생시킬 수 있다. 이 지갑의 가장 진보된 형태는 BIP-32 표준에 의해 정의된 형태이다. 부모키가 자식키들을 만들어낼 수 있고, 각각의 자식키가 손자키들을 무한정 만들어낼 수 있는 형태이다.

[그림4] HD wallet : 단일 시드에서 생성 된 키 트리

HD지갑은 128,256,512비트 크기의 Root seed로부터 만들어지며, 해시 알고리즘을 통해 마스터 개인키와 마스터 체인코드를 생성한다. 이와 같이 HD 지갑은 계층적으로 키를 생성하여 매 거래마다 새로운 주소를 생성하고 거래에 사용할 수 있도록 하여 개인정보 보호를 보장한다. (왜냐하면 매번 주소가 생성되므로 해킹하려는 사람에게는 힘든 작업이 된다.)

HD 지갑의 장점은 사용자들이 공개키에 대응하는 개인키에 접근하지 않고도 공개키를 생성할 수 있다는 것이다. 따라서 HD지갑은 안전하지 않은 서버에서 사용되거나 수신전용 지갑으로 사용할 수 있으며 각 트랜잭션마다 다른 공개키를 발급 할 수 있다.

Seeds and Mnemonic Codes

HD지갑은 많은 키와 주소를 관리하는 매우 강력한 메커니즘이다. ‘시드’를 생성하는 표준화된 방법과 결합하면 훨씬 유용하다(시드의 생성 - 많은 키와 주소들을 지갑에서 쉽게 생성하고 내보내고 가져올 수 있도록 함). 이것은 연상기호(mnemonic)로 알려져 있으며 표준은 BIP-39에 의해 정의된다. 이 연상기호(mnemonic)을 사용하여 백업 및 복구를 한다.


다음 중 어떤 것이 더 기록하기 쉽고, 오류 없이 읽으며, 내보내고 가져오기 쉬운가?

결정적 지갑의 시드(16진수)

결정적 지갑의 시드(12단어로 된 연상기호)

비트 코인 지갑을 구현하는 경우 BIP-32, BIP-39, BIP-43 및 BIP-44 표준에 따라 백업을 위한 연상기호(mnemonic)로 인코딩 된 시드를 사용하여 HD 지갑을 구축해야 한다. 다음 절에서 자세히 설명.


Using a Bitcoin Wallet

[그림5] Trezor : 키를 저장하고 거래를 표시하는 두 개의 버튼이 있는 간단한 USB 장치

Trezor가 사용되었을 때, 내장된 난수 생성기로 니모닉과 시드를 생성했다. 이 초기 단계에서 지갑은 번호가 매겨진 단어들을 하나씩 화면에 표시했다.

[그림6] 니모닉 단어 중 하나를 표시하는 trezor

이 적혀진 니모닉을 이용해서 Trezor가 손실되거나 손상된 경우 복구에 사용할 수 있는 백업 테이블을 만들었다. 적혀진 단어순서가 중요하므로 니모닉 백업 테이블에는 각 단어에 대해 번호가 매겨진 공간이 있다.

[그림7] 니모닉 : 가브리엘 (Gabriel)의 종이 백업

Wallet Technology Details

Mnemonic Code Words (BIP-39)

니모닉 코드 워드는 결정론적 지갑을 파생시키기 위해 필요한 시드로 사용되는 임의의 수를 나타내는 단어 시퀀스이다. 니모닉 단어가 있는 결정론적 지갑을 구현하는 지갑 응용 프로그램은 처음 지갑을 만들 때 사용자에게 12-24 단어의 시퀀스를 표시한다. 이 12-24 단어 시퀀스는 지갑 백업이며 동일하거나 호환되는 지갑 응용 프로그램의 모든 키를 복구하고 재생성하는 데 사용할 수 있다. 니모닉 단어를 사용하면 임의의 숫자 시퀀스와 비교했을 때 읽기 쉽고 정확하게 바꿔 쓸 수 있기 때문에 사용자가 지갑을 쉽게 백업 할 수 있다.

니모닉 코드는 BIP-39에 정의되어 있다. BIP-39는 니모닉 코드 표준을 구현한 것 중 하나이다. BIP-39는 니모닉 코드와 시드의 생성을 정의한다. 여기서는 9단계로 설명하는데 명확하게 하기 위해 프로세스는 두 부분으로 나뉜다. 1단계~6단계는 니모닉 단어 생성에 표시 되고 7단계~9단계는 니모닉에서 시드로 표시 된다.

Generating mnemonic words

니모닉 단어는 BIP-39에서 정의된 표준화된 프로세스를 사용하여 지갑에서 자동으로 생성된다. 지갑은 엔트로피 소스에서 시작하여 체크섬을 추가한 다음 엔트로피를 단어 목록에 매핑한다.

니모닉 단어를 생성하는 과정은 다음과 같다.

① 128~256 비트의 random 값 A 생성

② SHA256(A) 값의 첫 몇 비트(엔트로피/32)를 checksum으로 생성

③ checksum을 A 값의 끝부분에 추가

④ 변경된 A를 11비트로 나누어 12개의 세그먼트(인덱스?)를 생성

⑤ 각 11비트 값을 미리 정해진 2048개의 단어로 구성된 배열의 인덱스로 사용하여 단어열 생성

⑥ 니모닉 코드는 일련의 단어이다.

[그림8] 엔트로피 생성 및 연상기호 단어를 생성하는 과정1

[그림9] 엔트로피 생성 및 연상기호 단어를 생성하는 과정2

연상기호 코드의 엔트로피와 단어 길이는 다음과 같이 정의되어 있다. 예를 들어 Random 값이 128비트인 경우 체크섬은 해시값의 상위 4비트를 가져오며, random값에 4비트를 추가한 132비트를 11로 나누게 되어 단어길이는 12가 된다.


[그림10] 니모닉 코드 - 엔트로피 및 단어 길이

From mnemonic to seed

연상 기호는 길이가 128 ~ 256 비트인 엔트로피를 나타낸다. 그런 다음 엔트로피는 키 스트레칭(연장) 기능 함수 PBKDF2를 사용하여 더 긴(512 비트) 시드를 만드는데 사용된다. 생성 된 시드를 사용하여 결정론적 지갑을 만들고 키를 파생시킨다.

⑦ PBKDF2 함수의 첫 번째 매개 변수는 6단계에서 생성된 니모닉이다.

⑧ PBKDF2 함수의 두 번째 매개 변수는 ‘salt' 이다. salt는 “mnemonic”에 사용자에게 제공된(optional) 암호(passphrase)가 연결된 문자열 상수이다.

-> Salt 매개변수의 목적은 무차별 공격(brute-force attack)을 못하게 보호하는 것이다.(2^512)

⑨ PBKDF2는 HMAC-SHA512 해시 알고리즘으로 2048회 해싱을 사용하여 니모닉과 salt 매개 변수를 확장하여 최종 출력으로 512비트 값을 생성한다. 이 512비트 값이 시드이다.

[그림11] 니모닉을 사용하여 시드를 생성하는 방법

[그림12] 니모닉 및 시드 생성과정 예시

Optional passphrase in BIP-39

같은 니모닉으로부터 시드를 생성할 때, 패스 프레이즈가 사용되지 않는 경우, "mnemonic + salt"로 확장된 니모닉은 어떤 특정한 512비트 시드를 생성한다. 패스 프레이즈가 사용되는 경우, 앞의 시드와 다른 시드를 생성한다.

[그림13] 128비트 엔트로피 니모닉 코드, 패스프레이즈 사용, 결과 시드

[그림14] 128비트 엔트로피 니모닉 코드, 패스프레이즈 안사용, 결과 시드

사실, 한 니모닉이 주어졌을 때, 가능한 모든 암호(passphrase)는 다른 시드로 이어진다. 이때 "잘못된" 암호는 없고 모든 암호는 유효하며 모두 다른 시드로 이어져 광대한 지갑을 만든다. 가능한 지갑 세트는 너무 커서(2^512) 실제로 무차별 공격(brute force)의 가능성이 없다.

선택적 암호의 장점

- 니모닉은 지갑에 기억된 것이기 때문에 선택적 암호를 결합하여 사용하면 도둑에 의한 손실로부터 보호할 수 있다.

- 선택적 암호(passphrase)는 자금의 대부분을 담고 있는 "진짜 지갑"으로부터 공격자의 주의를 돌리는데 사용하기 위한 적은 양의 자금을 가진 “강압적인 지갑”을 형성할 수 있다.

선택적 암호의 단점

그러나 암호 구문을 사용하면 손실 위험이 있다.

- 지갑 주인이 죽은 경우 암호문을 아는 사람이 아무도 없으면 시드는 쓸모없으며 지갑에 저장된 모든 자금은 영원히 잃어버리게 된다.

- 소유자가 시드와 동일한 위치에서 암호문을 백업하면 지갑은 누설될 수 있다....(뭔소린지 모르겠다....)

선택적 암호는 매우 유용하지만 소유자에 의존하고 그의 가족이 암호화된 화폐를 복구할 수 있도록 허용하는 것을 고려하여 신중하게 계획된 백업 및 복구 프로세스와 함께 사용해야한다.

Working with mnemonic codes

BIP-39는 다양한 프로그래밍 언어로 된 라이브러리로 구현된다.

파이썬 - 니모닉

Python으로 BIP-39를 제안한 SatoshiLabs 팀이 표준을 구현했다.

bitcoinjs / bip-39

인기 있는 bitcoinJS 프레임 워크의 일부인 BIP-39를 자바 스크립트로 구현

libbitcoin / 니모닉

인기 있는 Libbitcoin 프레임 워크의 일부인 BIP-39를 C++로 구현

Creating an HD Wallet from the Seed

루트 시드는 HMAC-SHA512 알고리즘에 입력되고 결과 해시는 마스터 개인키(m) 및 마스터 체인코드(c) 를 생성하는 데 사용된다.

그런 다음 마스터 개인 키(m)는 타원 곡선 함수를 이용해 m * G를 사용하여 해당 마스터 공개키(M)를 생성한다.

체인 코드(c)는 부모 키로부터 자식 키를 생성하는 함수에서 엔트로피를 도입하는데 사용된다. 다음 절에 나옴.

[그림15] 루트 시드에서 마스터키와 체인 코드 만들기


Private child key derivation

HD지갑은 자식 키 유도(CKD : Child Key Derivation) 기능을 사용하여 부모 키에서 자식 키를 생성한다. 자식 키 파생 함수는 다음을 결합하는 단방향 해시 함수를 기반으로 한다.

1. 부모의 개인키 또는 공개키 (ECDSA uncompressed key)

2. 체인 코드 (부모로부터 생성됨)

3. 인덱스 번호 (32bits)

위의 3가지를 기반으로 자식 키들이 생성된다.

체인 코드는 임의의 데이터를 프로세스에 도입하는데 사용되므로, 인덱스와 자식 키를 아는 것만으로는 다른 자식 키를 파생시키는데 충분하지 않다. 또한 체인 코드를 가지고 있지 않으면, 자식 키를 안다고 그 형제 키를 찾을 수 있는 것은 아니다. 트리의 루트에 있는 초기 체인 코드 시드는 시드에서 만들어지며, 이후 자식 체인 코드는 각 부모 체인 코드에서 파생된다.

부모 공개키, 체인 코드 및 인덱스 번호가 결합되어 HMAC-SHA512 알고리즘으로 해시되어 512비트 해시를 생성한다. 이 512비트 해시는 두 개의 256비트로 나뉜다. 왼쪽 256비트는 추후 타원 곡선 기반의 공개키를 생성하기 위한 개인키로, 오른쪽 256비트는 자식키들을 생성하기 위한 엔트로피로 적용된다.

[그림16]  부모 개인키를 확장하여 자식 개인키 만들기

Extended keys

앞서 살펴본 것처럼 키 파생 함수는 키, 체인 코드 및 원하는 자식 인덱스와 같은 세 가지 입력을 기반으로 트리의 모든 수준에서 하위 항목을 만드는 데 사용할 수 있다. 두 가지 필수적인 요소는 키와 체인 코드이며 이를 조합하여 ‘확장된 키’라고 한다. "확장된 키"라는 용어는 이러한 키가 자식을 파생시키는데 사용될 수 있으므로 "확장 가능한 키"로 생각할 수 있다.

확장된 키는 단순히 256비트 키와 256비트 체인 코드를 512비트로 연결한 것으로 저장되고 표현된다. 확장된 키에는 두 가지 유형이 있다. 확장된 개인키는 개인키와 체인 코드의 조합이며 자식 개인키를 파생시키는데 사용될 수 있다. 확장된 공개키는 자식 공개키를 만드는데 사용할 수 있는 공개키 및 체인 코드이다.

확장된 키는 Base58Check로 인코딩되어 다른 BIP-32 호환 가능한 지갑 간에 쉽게 내보내고 가져올 수 있다. 확장된 키에 대한 Base58Check 코딩은 쉽게 식별 할 수 있도록 Base58 문자로 인코딩 할 때 접두사 "xprv"및 "xpub"가되는 특수 버전 번호를 사용한다. 확장된 키는 512 또는 513 비트이기 때문에 이전에 보았던 다른 Base58Check 인코딩된 문자열보다 훨씬 길다.

[그림17]  Base58Check로 인코딩된 확장된 개인키와 공개키의 예

Public child key derivation

앞에서 언급했듯이 HD지갑의 장점은 사용자들이 공개키에 대응하는 개인키에 접근하지 않고도 공개키를 생성할 수 있다는 것이다. 이렇게 하면 자식 공개키를 파생시키는 두 가지 방법, 즉 자식 개인키 또는 부모 공개키에서 직접 가져올 수 있다.

따라서 확장된 공개키를 사용하여 HD지갑 구조의 해당 분기에서 모든 공개키(공개키만)를 파생시킬 수 있다.

[그림18] 부모 공개키를 확장하여 자식 공개 키 만들기

Hardened child key derivation

xpub에서 공개키를 파생시키는 기능은 매우 유용하지만 잠재적인 위험이 따른다. xpub에 대한 접근은 자식 개인키에 대한 접근을 제공하지 않는다. 그러나 xpub에 체인 코드가 포함되어 있으므로 자식 개인키가 알려져 있거나 누출된 경우 체인 코드와 함께 사용하여 다른 모든 개인키를 파생시킬 수 있다. 더욱이 부모 자식 키와 부모 체인 코드를 함께 사용하여 부모 개인키를 추론 할 수 있다.(왜냐면 확장된 키에는 체인코드가 포함되어 있으므로 체인코드를 알고 하나의 개인키만 안다면 그 이후의 개인키는 재생성이 가능하다.)

이를 방지하기 위해 부모의 공개키와 자식의 체인 코드간의 관계를 없애는 ‘강화된 파생(hardened derivation)’ 방법이 고안되었다. 이 방식은 자식의 체인 코드를 생성하는데 있어서 기존 부모의 공개키가 아닌 개인키를 기반으로 한다는 것이다.


[그림19] 하위 키의 강화 된 파생. 부모 공개 키를 생략한다.


알아보기 쉽게 하기 위해 normal은 인덱스 시작을 0, hardened는 0′ 로 표현한다.

Index numbers for normal and hardened derivation

파생 함수에 사용된 인덱스 번호는 32비트 정수다. 일반 유도 함수를 통해 파생된 키 vs 강화 유도를 통해 파생된 키를 쉽게 구분하기 위해 인덱스 번호는 두 범위로 나뉜다. 0과 2^31 - 1 사이의 인덱스 번호(0x0 ~ 0x7FFFFFFF)는 정상 파생에만 사용 된다. 2^31 ~ 2^32 - 1 사이의 인덱스 번호(0x80000000 ~ 0xFFFFFFFF)는 강화된 파생에만 사용 된다. 따라서 색인 번호가 2^31보다 작으면 자식은 정상파생이며 색인 번호가 2^31 보다 크거나 같으면 자식이 강화파생이다.



Comments