지갑의 개요
•
지갑은 다음과 같은 역할을 하는 애플리케이션을 말한다.
◦
사용자의 자산에 접근하고 관리한다.
◦
개인키와 주소를 관리한다.
◦
잔액을 확인한다.
◦
트랜잭션(거래)을 생성하고 서명한다.
•
프로그래머 관점에서는 개인키를 저장하고 관리하기 위한 자료 구조로 해석되기도 한다.
•
비트코인의 지갑에는 비트코인이 들어있는 것이 아니고 개인키가 들어있다.
결정적 지갑과 비결정적 지갑
•
최초의 비트코인 코어에는 무작위로 생성된 100개의 지갑을 각각 한번씩만 사용할 수 있었다.
•
비결정적 지갑 (Type-0)
◦
100개의 지갑을 생성하게 한 이유는 한 지갑에서 여러 트랜잭션이 발생하면 해킹의 위험이 있을 뿐 아니라, 보유자의 프라이버시가 위배된다.
◦
다량의 키를 저장하여 불러오는 것이 매우 불편하다
무작위로 생성된 개인키 집합
•
결정적 지갑 (Type-1, Deterministic Wallets)
◦
하나의 시드값으로 부터 파생된 여러개의 개인키를 포함하는 지갑이다.
◦
시드는 보통 난수생성기에의해 무작위수를 추출하여 사용한다.
◦
공통시드만 잘 보관하면 여러개의 주소를 관리할 수 있다.
공통시드로부터 파생된 개인키들
HD 지갑 (Type-2, Hierarchy Deterministic Wallets)
•
BIP-32 표준에 정의된 가장 발전된 형태의 지갑이다.
•
하나의 공통 시드로 부터 트리구조의 무제한에 가까운 개인키를 만들 수 있다.
•
공통시드로부터 트리 구조로 파생된 개인키들
니모닉 코드
•
니모닉(Mnemonic)은 일련의 영단어로 공통 시드를 표현하는 방법이다.
•
•
개인키는 아무리 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 |