6️⃣

지갑

지갑의 개요

지갑은 다음과 같은 역할을 하는 애플리케이션을 말한다.
사용자의 자산에 접근하고 관리한다.
개인키와 주소를 관리한다.
잔액을 확인한다.
트랜잭션(거래)을 생성하고 서명한다.
프로그래머 관점에서는 개인키를 저장하고 관리하기 위한 자료 구조로 해석되기도 한다.
비트코인의 지갑에는 비트코인이 들어있는 것이 아니고 개인키가 들어있다.

결정적 지갑과 비결정적 지갑

최초의 비트코인 코어에는 무작위로 생성된 100개의 지갑을 각각 한번씩만 사용할 수 있었다.
비결정적 지갑 (Type-0)
100개의 지갑을 생성하게 한 이유는 한 지갑에서 여러 트랜잭션이 발생하면 해킹의 위험이 있을 뿐 아니라, 보유자의 프라이버시가 위배된다.
다량의 키를 저장하여 불러오는 것이 매우 불편하다
무작위로 생성된 개인키 집합
결정적 지갑 (Type-1, Deterministic Wallets)
하나의 시드값으로 부터 파생된 여러개의 개인키를 포함하는 지갑이다.
시드는 보통 난수생성기에의해 무작위수를 추출하여 사용한다.
공통시드만 잘 보관하면 여러개의 주소를 관리할 수 있다.
공통시드로부터 파생된 개인키들

HD 지갑 (Type-2, Hierarchy Deterministic Wallets)

BIP-32 표준에 정의된 가장 발전된 형태의 지갑이다.
하나의 공통 시드로 부터 트리구조의 무제한에 가까운 개인키를 만들 수 있다.
현재 가장 널리 사용되는 방식은 BIP-44에 5단계 경로를 갖는 HD 지갑이다.
공통시드로부터 트리 구조로 파생된 개인키들

니모닉 코드

니모닉(Mnemonic)은 일련의 영단어로 공통 시드를 표현하는 방법이다.
니모닉 코드 표준은 BIP-39에 정의되어 있다.
개인키는 아무리 16진수로 바꾸어도 사람이 기억하기 어렵거나, 옮겨 적는 과정에서 오타가 발생할 확률이 높다.
이러한 단점을 극복하기 위해 16진수의 난수를 영단어로 치환하여 보관할 수 있다.
16진수 엔트로피 0C1E24E5917779D297E14D45F14E1A1A
12 단어 니모닉 army van defense carry jealous true garbage claim echo media make crunch
니모닉 코드 생성 절차
아래 그림은 128비트 엔트로피로 12개의 니모닉 단어를 추출하는 과정을 보여준다.
① 128비트 길이의 엔트로피(랜덤 숫자)
② 엔트로피를 SHA256 해시하여 첫 4비트를 체크섬으로 사용
③ 엔트로피와 체크섬을 붙여 132비트 생성
④ 132비트를 11비트씩 쪼개 12개로 분할
⑤ 미리 정의된 2048개의 단어 사전을 이용하여 각각을 단어로 치환
⑥ 단어 배열의 니모닉 코드 완성
엔트로피의 길이에 따라 니모닉단어의 수도 달라진다.
엔트로피(bits)
체크섬(bits)
엔트로피+체크섬(bits)
니모닉 단어 길이
128
4
132
12
160
5
165
15
192
6
198
18
224
7
231
21
256
8
264
24
니모닉 코드로 공통 시드를 생성하는 절차
니모닉 코드는 512비트의 시드로 변환된다.
이 과정에서 PBKDF2(Password-Based Key Derivation Function 2)를 사용한다.
PBKDF2를 사용하기 위해서 두 개의 인자를 필요로 한다.
하나는 우리가 추출한 니모닉 코드 단어들이다.
나머지 하나는 패스프레이즈를 넣는다.
아래 그림은 공통 시드 생성 절차를 보여준다.
⑦ PBKDF2의 첫번째 파라미터는 앞 단계의 ⑥에서 얻은 니모닉을 가져온다.
⑧ 두번째 파라미터, 솔트는 'mnemonic'과 사용자가 정의한 패스프레이즈를 붙인다.
⑨ PBKDF2를 수행한다.
PBKDF2
Password-Based Key Derivation Function 2의 약자로 주로 낮은 엔트로피로 안전한 키를 생성할 때 쓰인다.
PBKDF2는 5가지의 입력값이 필요하며, 비트코인에서 쓰는 입력값은 다음과 같다.
해시알고리즘의 종류 : HMAC-SHA-512
엔트로피 : 니모닉 단어 리스트
솔트 : ‘mnemonic ’+패스프레이즈
반복 횟수 : 2048
결과값의 길이 : 512bit
결국 입력값을 넣고, HMAC-SHA-512을 2048회 돌리는 것이다.
아래 샘플코드를 통해 확인해보자.
from mnemonic import Mnemonic import hashlib import hmac # 니모닉 생성 mnemo = Mnemonic("english") mnemonic_words = "your mnemonic words here" # PBKDF2 사용하여 마스터 시드 생성 password = mnemonic_words.encode('utf-8') salt = b'mnemonic_salt' iterations = 2048 key_length = 64 # 64 bytes for SHA-512 master_seed = hashlib.pbkdf2_hmac('sha512', password, salt, iterations, key_length) print("Master Seed:", master_seed.hex())
Python
복사
HMAC-SHA-512(Hierarchical Message Authentication Code)
SHA 계열은 입력값을 통해 누구든 같은 값을 추출해낼 수 있다.
HMAC는 여기에 secret_key를 사용하여 은밀한 해시값을 추출할 수 있는 방법을 제공한다.
따라서, 입력값과 결과값을 검증할 때, secret_key를 알지 못하면 검증할 수 없다.
import hashlib import hmac # 비밀 키 생성 (임의의 값으로 대체해야 함) secret_key = b'my_secret_key' # 메시지 생성 message = b'Hello, HMAC-SHA-512!' # HMAC-SHA-512 생성 hmac_sha512 = hmac.new(secret_key, message, hashlib.sha512).digest() # 생성된 HMAC 출력 print("Generated HMAC-SHA-512:", hmac_sha512.hex()) # 이제 hmac_sha512를 전송하거나 저장한 후, 다시 검증할 때 사용할 수 있습니다. # 검증 단계: # 1. 동일한 비밀 키로 다시 HMAC 생성 # 2. 생성한 HMAC과 전송된 HMAC이 동일한지 비교 received_hmac = hmac.new(secret_key, message, hashlib.sha512).digest() if hmac.compare_digest(hmac_sha512, received_hmac): print("HMAC verification successful. The message is authentic.") else: print("HMAC verification failed. The message may have been tampered with.")
Python
복사
패스프레이즈의 역할
니모닉 코드가 유출되어도 패스프레이즈로 지갑을 안전하게 보호할 수 있다.
패스프레이즈가 있는 지갑과 없는 지갑을 구분해서 탈취 협박 시 공격자를 속이는데 사용할 수 있다.
하지만, 이를 기억하지 못할 경우 자산은 영구손실 된다.
그렇다고, 니모닉 코드와 함께 보관하면 패스프레이즈의 목적이 무산된다.
니모닉 코드와 시드의 비교
12자 니모닉 코드: 패스프레이즈 지정하지 않는 경우
엔트로피 (128비트)
0c1e24e5917779d297e14d45f14e1a1a
니모닉 (12 단어)
army van defense carry jealous true garbage claim echo media make crunch
패스프레이즈
(지정하지 않음)
시드 (512비트)
5b56c417303faa3fcba7e57400e120a0ca83ec5a4fc9ffba757fbe63fbd77a89a1a3be4c67196f57c39 a88b76373733891bfaba16ed27a813ceed498804c0570
12자 니모닉 코드: 패스프레이즈 지정한 경우
엔트로피 (128비트)
0c1e24e5917779d297e14d45f14e1a1a
니모닉 (12 단어)
army van defense carry jealous true garbage claim echo media make crunch
패스프레이즈
SuperDuperSecret
시드 (512비트)
3b5df16df2157104cfdd22830162a5e170c0161653e3afe6c88defeefb0818c793dbb28ab3ab091897d0 715861dc8a18358f80b79d49acf64142ae57037d1d54
24자 니모닉 코드: 패스프레이즈 지정하지 않은 경우
엔트로피 (256비트)
2041546864449caff939d32d574753fe684d3c947c3346713dd8423e74abcf8c
니모닉 (24 단어)
cake apple borrow silk endorse fitness top denial coil riot stay wolf luggage oxygen faint major edit measure invite love trap field dilemma oblige
패스프레이즈
(지정하지 않음)
시드 (512비트)
3269bce2674acbd188d4f120072b13b088a0ecf87c6e4cae41657a0bb78f5315b33b3a04356e53d062e5 5f1e0deaa082df8d487381379df848a6ad7e98798404

HD 지갑의 키 생성

추출된 512비트의 공통시드는 HMAC-SHA512함수를 통해 다시 512비트로 변환되고 이는 마스터 개인키를 만드는데 사용된다.
512비트를 둘로 쪼개어 왼쪽 256비트는 마스터 개인키로 사용하고, 오른쪽은 마스터 체인코드로 사용한다.
체인코드는 자식 개인키를 만들기 위한 엔트로피로 사용된다.
마스터키로부터 자식키를 만들어내는 방법은 다양하게 사용된다.
정규 개인키 유도법 (Private Child Key Derivation)
부모의 개인키를 통해 자식의 개인키를 유도하는 방법이다.
자식키를 만들기 위해서는 세 개의 입력 값이 필요하다.
부모의 공개키
부모의 체인코드 : 자식으로부터 부모를 숨기는 역할을 한다.
인덱스 : 자식의 수에 따라 순서대로 부여된다. ‘0’과 31비트를 붙여 32비트를 사용한다.
세 개의 값을 HMAC-SHA512함수를 통해 결과를 구한다.
왼쪽 256비트는 부모의 개인키와 더한 후 %(모듈러)연산을 하여 자식의 개인키로 쓴다.
여기서 %연산으로 나누는 값 n은 secp256k1의 위수이다.
n=115792089237316195423570985008687907852837564279074904382605163141518161494337
오른쪽 256비트는 체인코드가 되어 손자를 만드는데 사용한다.
정규 공개키 유도법(Public Child Key Derivation)
자식의 공개키를 추출하는 방법은 두 가지이다.
자식의 개인키를 통해 연산한다.
공개키 유도법을 활용한다.
부모의 공개키를 통해 자식의 공개키를 유도하는 방법이다.
자식의 공개키를 만들기 위해 개인키를 노출하고 싶지 않은 경우 사용하는 방법이다.
예를들어 쇼핑몰 판매자의 주소를 관리자가 변경하고자 하는 경우가 관리자는 판매자의 개인키를 몰라도 새로운 공개키를 만들 수 있다.
세 개의 입력값이 필요하다.
부모의 공개키
부모의 체인코드
인덱스
HMAC-SHA512의 결과를 다음과 같이 활용한다.
왼쪽 256비트는 자식의 공개키를 유도하는데 활용한다.
부모의 공개키에서 해당 숫자만큼의 타원곡선 덧셈을 수행하여 자식의 공개키를 추출한다.
오른쪽 256비트는 손자를 위한 체인코드로 사용된다.
단절 개인키 유도법(Hardened Private Key Derivation)
부모의 공개키를 사용하지 않고 자식의 개인키를 유도하는 방법이다.
정규 유도법의 경우 부모의 공개키를 알면 누구든 자식의 공개키를 알 수 있다.
하나의 공개키를 통해 사용자의 모든 자식 공개키를 알 수 있기 때문에 같은 소유자의 주소라는 것을 추론할 수 있다. 이는 프라이버시 위배라고 판단할 수 있다.
이를 해결하기 위해 공개키를 사용하지 않고 자식키를 유도하는 방법이 단절 개인키 유도법이다.
HMAC-SHA512 함수에 공개키를 입력으로 사용하지 않는 것을 알 수 있다.
인덱스는 ‘1’과 31비트를 붙여 32비트를 사용한다.
현재 단절 개인키 유도법은 HD 지갑의 표준으로 사용되고 있다.
정리하면 다음과 같다..

HD 지갑의 키 식별(경로, path)

HD 지갑의 트리 구조에서는 여러 개의 주소를 부모-자식 관계로 표현할 수 있다.
특정 주소를 추적할 때에는 트리의 경로(path)로 표기된다.
루트에는 마스터키가 표현된다.
마스터 개인키를 m으로 표현하고, 마스터 공개키는 M으로 표현한다.
그 뒤 인덱스에 따라 자식키가 표현되며 이러한 방식으로 최대 40억 개의 자식 표현이 가능하다.
HD 경로
설명
m/0
마스터 개인키의 첫번째 자식 개인키
m/0/0
m/0의 첫번째 자식 개인키
m/0'/0
첫번째 단절 자식 개인키(m/0')의 첫번째 정규 자식 개인키
m/1/0
두번째 자식 개인키(m/1)의 첫번째 자식 개인키
M/23/17/0/0
마스터 공개키(M)의 24번째 자식(M/23)의 18번째 자식(M/23/17)으로부터 나온 첫번째 자식(M/23/17/0)의 첫번째 자식 공개키
HD 지갑과 BIP-44
HD 지갑은 총 40억 개의 자식키를 보유할 수 있기 때문에 정해진 규칙이 없이 남용되면 자산을 탐색하는 데에 많은 시간이 소요될 것이다.
따라서, BIP-44 표준에서는 트리 내부에 있는 경로 규칙을 정규화하고 이를 따르도록 권장한다.
BIP-43에서는 최상위 경로를 제외한 첫번째 레벨을 '목적(purpose)'으로 나타내자고 제안하였다.
BIP-44 표준은 다양한 체인의 다양한 자산을 관리할 수 있는 HD 지갑의 다목적 구조를 제안하였는데, BIP-43를 확장하여 목적 정보를 44'로 고정하고, 5 레벨로 구조화하였다.
BIP-44가 제안한 HD 지갑의 경로: m / purpose' / coin_type' / account' / change / address_index
purpose'44'
coin_type': 화폐의 유형.
예) 0' 비트코인 메인넷, 1' 비트코인 테스트넷, 2' 라이트코인
account': 용도에 따라 하위 계좌 지정 가능
change: 잔돈 여부. 0(false)이나 1(true) 값을 가진다.
address_index: 주소의 일련 번호
각 레벨에서 BIP-32에서 나타난 단절 유도 방법(')이 사용되는 것을 확인할 수 있다.
BIP-44 적용 예시
coin
account
chain
address
경로
Bitcoin
첫번째
external
첫번째
m/44'/0'/0'/0/0
Bitcoin
첫번째
external
두번째
m/44'/0'/0'/0/1
Bitcoin
첫번째
change
첫번째
m/44'/0'/0'/1/0
Bitcoin
첫번째
change
두번째
m/44'/0'/0'/1/1
Bitcoin
두번째
external
첫번째
m/44'/0'/1'/0/0
Bitcoin
두번째
external
두번째
m/44'/0'/1'/0/1
Bitcoin
두번째
change
첫번째
m/44'/0'/1'/1/0
Bitcoin
두번째
change
두번째
m/44'/0'/1'/1/1

참고자료