비트코인/비트코인 구조

[비트코인 구조] BIP32: HD지갑(Hierarchical Deterministic Wallet)

라이튼 2023. 1. 10. 17:21

미리 알아야 할 내용들


 

[비트코인 구조] BIP39: 니모닉(Mnemonic)과 시드(Seed)

미리 알아야 할 내용들 [블록체인 용어] 니모닉(Mnemonic) 코인을 보관하기 위해 개인 지갑을 이용하는 경우 사용자는 블록체인 계좌의 통장 비밀번호라고 할 수 있는 개인키를 직접 보관해야 합니

kwjdnjs.tistory.com

 

[비트코인 구조] 개인키(Private key), 공개키(Public key), 주소(Address) 생성

미리 알아야 할 내용들 '비트코인/암호학' 카테고리의 글 목록 평범한 대학생의 블록체인 기술 관련 블로그 입니다. kwjdnjs.tistory.com [비트코인 암호학] 3.1 공개키 암호화 타원곡선을 이용해 디지

kwjdnjs.tistory.com

 

[비트코인 암호학] 2.2 유한체에서의 타원곡선

[비트코인 암호학] 2.1 유한체 [비트코인 암호학] 1.3 타원곡선의 점 덧셈2 [비트코인 암호학] 1.2 타원곡선의 점 덧셈1 [비트코인 암호학] 1.1 타원곡선 [비트코인 암호학] 0. 비트코인의 거래와 서명

kwjdnjs.tistory.com


BIP32: HD지갑(Hierarchical Deterministic Wallet)

 

 이번 글에서는 니모닉 시드를 이용하여 개인키를 파생하는 방법에 대해 알아보겠습니다.

 

 개인키를 파생하는 가장 일반적인 표준은 HD지갑입니다. HD는 Hierarchical Deterministic 약자로, 부모키와 인덱스를 이용하여 항상 같은 자식키를 구할 수 있으며(결정적), 계층적 구조를 이용하여 개인키를 파생함을 뜻합니다. 지금부터 HD지갑에 대해 자세히 알아보겠습니다.

 

 기본적인 키 파생방법은 다음과 같습니다.

 

 

 먼저 시드를 이용하여 마스터키를 구합니다. 이후 마스터키의 자식키를 구합니다. 자식키를 이용하면 또 다른 자식키를 구할 수 있습니다. 이러한 방식을 계속하여 계층구조를 만듭니다.

 

 추가적으로 HD지갑에서는 개인키 없이 공개키만을 이용하여 새로운 공개키를 파생시킬 수 있습니다.

 

 

마스터키

 이제부터 본격적으로 개인키 파생을 시작해 보겠습니다. 가장 먼저 해야 할 일은 마스터키를 구하는 것입니다. 마스터키를 구하기 위해 먼저 이전 글에서 생성했던 니모닉 시드를 불러오겠습니다.

 

 시드: 5654edaa75b4e9278d90d496df8776b3c902eb7f9c963054a6b527770da15b834e6ad5c42e8332d875ba2dcb382068e6d47307203be8e8d3ad71b0dab0499e77

 

 불러온 시드를 hamc-sha512에 대입하여 값을 구합니다. 여기에서 추가로 사용된 'Bitcoin seed'는 마스터키 생성에 사용되는 고정된 값입니다. 다음은 해당 작업을 하는 파이썬 코드입니다.

import hmac
from hashlib import sha512

seed = '5654edaa75b4e9278d90d496df8776b3c902eb7f9c963054a6b527770da15b834e6ad5c42e8332d875ba2dcb382068e6d47307203be8e8d3ad71b0dab0499e77'

h = hmac.new(b'Bitcoin seed', bytes.fromhex(seed), sha512)
print(h.hexdigest())

 결괏값: ef7ac307b3f5437768282886cbc23e6c8fda8855f05a8a2fee6318b7810714d1a3ac24407a320f9fd80e2f896748a8c309a2e840b2137075cf82be33a30fbead

 

 위 결괏값의 첫 32바이트는 개인키이며, 이어지는 32바이트는 체인 코드입니다. HD지갑에서는 키와 체인 코드 전체를 합쳐서 확장키라고 부릅니다. 해당키는 마스터키이기 때문에 위 결괏값은 마스터 확장 개인키가 됩니다.

 

 확장 개인키: ef7ac307b3f5437768282886cbc23e6c8fda8855f05a8a2fee6318b7810714d1a3ac24407a320f9fd80e2f896748a8c309a2e840b2137075cf82be33a30fbead

 개인키: ef7ac307b3f5437768282886cbc23e6c8fda8855f05a8a2fee6318b7810714d1

 체인 코드: a3ac24407a320f9fd80e2f896748a8c309a2e840b2137075cf82be33a30fbead

 

 

 지금까지의 결과를 표현해 보면 다음과 같습니다.

 

 

개인키 파생

 지금부터는 구한 마스터키를 이용해서 새로운 개인키를 파생시켜 보겠습니다. 개인키를 파생시키기 위해서는 개인키와 체인 코드 즉, 확장 개인키가 필요합니다. 이 값들은 이미 구했으므로 넘어가겠습니다.

 

 확장 개인키를 이용하여 개인키를 파생시키기 위해 가장 먼저 해야 할 것은 자식 개인키의 종류를 결정하는 것입니다. 개인키의 자식키에는 '강화(hardened)'와 '일반(normal)' 두 종류가 있습니다. 강화 자식 개인키는 개인키만이 공개키를 파생시킬 수 있는 형태이며, 일반 자식 개인키는 확장 공개키로도 공개키를 파생시킬 수 있는 형태입니다.

 

강화(hardened) 자식 개인키

 만약 강화 자식 개인키를 생성한다면 다음과 같은 과정을 거칩니다. 먼저 부모 개인키 뒤에 4바이트 길이의 인덱스를 붙입니다.  강화 자식 개인키는 정수로 인덱스 2147483648부터 4294967295까지 사용합니다. 따라서 강화 개인키의 가장 작은 인덱스는 16진수 0x80000000입니다. 인덱스를 붙인 뒤 개인키의 맨 앞에 0x00을 붙입니다. 그리고 이 값과 체인 코드를 사용해 자식키를 구합니다.

 

 다음은 인덱스 0x80000000번의 강화 자식 개인키를 구하는 파이썬 코드입니다. 마스터키를 구할 때 사용했던 hmac-sha512를 사용하여 64바이트의 값을 구합니다. 마스터키를 구하는 과정에서 ''Bitcoin seed'이 있었던 자리에는 체인 코드를 넣습니다.

 

 이전처럼 얻은 64바이트 결괏값의 첫 32바이트를 바로 개인키로 사용하는 것이 아니라, (첫 32바이트 값 + 부모 개인키) mod n (n은 secp256k1에 정의된 값)으로 개인키를 구합니다. 이어지는 32바이트는 체인코드입니다.

 

import hmac
from hashlib import sha512

n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 #secp256k1

p = 'ef7ac307b3f5437768282886cbc23e6c8fda8855f05a8a2fee6318b7810714d1'
chain = 'a3ac24407a320f9fd80e2f896748a8c309a2e840b2137075cf82be33a30fbead'
d = '00' + p + '80000000'

h = hmac.new(bytes.fromhex(chain), bytes.fromhex(d), sha512)
il = h.hexdigest()[:64]
cc = h.hexdigest()[64:]

ck = (int(il,16) + int(p,16)) % n

print('child chain code:', cc)
print('child private key:', hex(ck))

 

 구한 결괏값은 다음과 같습니다.

 

 개인키: c7c19961e4202650ef6071dc8fce24a45b4c3809cb2b21e9062cb3c01284f1c0

 체인 코드: abadaea3a8c415d831844dfefef46d1facde81323a47f7d041345ac100d2876d

 

 

  지금까지의 결과를 표현해 보면 다음과 같습니다.

 

 

일반(normal) 자식 개인키

 이어서 일반 자식 개인키를 구해보겠습니다. 일반 자식 개인키는 인덱스 0에서 2147483647까지 사용하며, hmac-sha512에 개인키가 아닌 개인키의 공개키와 체인코드를 넣습니다. 공개키에 인덱스를 붙인 후 체인코드와 함께 hmac-sha512 해시값을 구합니다. 공개키를 구하는 과정은 이전 글에서 다뤘으므로 생략하겠습니다. 참고로 압축방식의 공개키를 사용해야 합니다.

 

 해시값을 구한 이후의 과정은 강화 자식 개인키 생성 방식과 같습니다.

 

 공개키: 020ee8f0ad5fd4f36d88b059be208d08ce39264c4b0df774567c1a63a99020e22d

 체인 코드: a3ac24407a320f9fd80e2f896748a8c309a2e840b2137075cf82be33a30fbead

 

 이제 인덱스 0번의 일반 자식 개인키를 생성해 보겠습니다. 다음은 자식키를 생성하는 파이썬 코드입니다.

import hmac
from hashlib import sha512

n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 #secp256k1

p_pub = '020ee8f0ad5fd4f36d88b059be208d08ce39264c4b0df774567c1a63a99020e22d'
p_priv = 'ef7ac307b3f5437768282886cbc23e6c8fda8855f05a8a2fee6318b7810714d1'
chain = 'a3ac24407a320f9fd80e2f896748a8c309a2e840b2137075cf82be33a30fbead'
d = p_pub + '00000000'

h = hmac.new(bytes.fromhex(chain), bytes.fromhex(d), sha512)
il = h.hexdigest()[:64]
cc = h.hexdigest()[64:]

ck = (int(il,16) + int(p_priv,16)) % n

print('child chain code:', cc)
print('child private key:', hex(ck))

 

구한 결괏값은 다음과 같습니다.

 

 개인키: cf3873d75a00e4cc2059933a3cf1234d06e8cb14b100ec8bf65d385b0c5dd5d9

 체인 코드: 6416b9ef43e984329200db663edcefdbb662f95b24074bce44d501e569840f7b

 

 

 개인키 종류와 상관없이 최종적으로 생성된 결괏값은 개인키와 체인코드를 가집니다. 개인키와 체인코드는 확장 개인키이고, 확장 개인키를 이용하면 새로운 개인키를 파생시킬 수 있습니다. 즉, 위 방식을 계속 반복한다면 자식키의 자식키를 계속해서 구해나갈 수 있습니다.

 

 지금까지의 결과를 표현해 보면 다음과 같습니다.

 

 

확장 공개키를 이용한 공개키 파생

 일반 개인키에 대응하는 공개키를 이용하면 자식 공개키를 파생시킬 수 있습니다. 이때의 공개키를 확장 공개키라고 합니다. 지금부터는 확장 공개키를 이용해 공개키를 파생시키는 방법에 대해 알아보겠습니다.

 

 먼저 이전에 구했던 일반 개인키의 공개키를 구해보겠습니다. 이전과 마찬가지로 공개키를 구하는 과정은 생략하겠습니다.

 

 일반 개인키: cf3873d75a00e4cc2059933a3cf1234d06e8cb14b100ec8bf65d385b0c5dd5d9

 체인 코드: 6416b9ef43e984329200db663edcefdbb662f95b24074bce44d501e569840f7b

 확장 공개키: 021b9bbf49dc45f4a0ba9dc4912f373885374f5c72c4837c9494672276ec61ce69

 

 위 공개키는 일반 개인키의 공개키이기 때문에 확장 공개키로 사용할 수 있습니다. 해당 공개키와 체인 코드를 이용해 자식 공개키를 구해보겠습니다.

 

 자식 공개키를 구하는 과정은 일반 자식 개인키를 구하는 과정과 유사합니다. 먼저 공개키와 인덱스 그리고 체인코드를 hmac-sha512 함수에 넣습니다. 인덱스는 0번을 사용하겠습니다. 다음은 해당 값을 구하는 코드입니다.

 

import hmac
from hashlib import sha512

n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 #secp256k1

p_pub = '021b9bbf49dc45f4a0ba9dc4912f373885374f5c72c4837c9494672276ec61ce69'
chain = '6416b9ef43e984329200db663edcefdbb662f95b24074bce44d501e569840f7b'
d = p_pub + '00000000'

h = hmac.new(bytes.fromhex(chain), bytes.fromhex(d), sha512)
il = h.hexdigest()[:64]
cc = h.hexdigest()[64:]

print('left:', il)
print('child chain code:', cc)

 

 첫 32바이트 값: 943a42fb258ba2d012a680f6707c3dbd62225663eb7f6061d7a8b6b317f24349

 체인 코드: 0d1be3743af509d77acf50724c1aa3b6412a881bffaac6018e45c14fe7e4dd26

 

 자식 개인키를 구하는 과정과 비슷하게 구해진 첫 32바이트 값은 자식 공개키 생성에 사용하고, 나머지 32바이트 값은 체인 코드로 사용합니다. 첫 32 바이트로 공개키를 구하기 위해서는 첫 32 바이트 값을 개인키처럼 사용하여 공개키를 구한 후, 이 값과 부모 공개키(확장 공개키)를 타원곡선 점 덧셈해야 합니다. 만약 첫 32바이트 값이 secp256k1에 정의된 n값보다 크거나, 구해진 공개키가 무한원점인 경우 해당 자식키를 공개키를 사용할 수 없습니다. 이 경우 새로운 인덱스를 이용해야 합니다.

 

 먼저 첫 32바이트 값을 이용한 공개키를 구해보겠습니다.

 

 첫 32바이트 값의 공개키: 029861c3bf84e07596a372d043807038b34fc6c3bf20ccfe71a01754222042a791

 

 이제 위 값에 부모 공개키(확장 공개키)를 점 덧셈 해보겠습니다. 이 과정을 거치면 다음과 같은 자식 공개키가 생성됩니다.

 

 자식 공개키: 0325f168723ee7b5ad76a3524fb3f527c468d03d9d7e2f734ab31f7385ee53f577

 

 

 위 공개키에는 한 가지 놀라운 점이 있습니다. 만약 위 자식 공개키를 생성하는 데 사용된 부모 공개키와 해당 공개키에 해당하는 일반 개인키를 이용하여 인덱스 0번의 일반 개인키를 파생시키면, 해당 개인키는 위 공개키에 해당하는 개인키가 됩니다.

 

 지금까지의 결과를 표현해 보면 다음과 같습니다.

 

 

 마지막으로 지금까지 사용했던 주요 개념들에 대해 다시 한번 정리해 보겠습니다.

 

1. 개인키와 공개키 파생을 위해서는 가장 먼저 마스터키를 구해야 합니다. 해당 값은 시드를 이용해 구할 수 있습니다.

 

2. 개인키 또는 공개키와 체인 코드가 합쳐진 64바이트 값을 확장키라고 합니다.

 

3. 확장키를 이용하면 새로운 자식키를 파생시킬 수 있습니다. 이 과정에서 인덱스를 사용해 여러 개의 자식키를 파생시킬 수 있습니다. 확장키를 이용해 자식키를 구하는 과정에는 hmac-sha512 함수가 사용됩니다.

 

4. 개인키에는 강화 개인키일반 개인키가 있습니다. 강화 개인키의 공개키는 확장 공개키로 사용할 수 없지만, 일반 개인키의 공개키는 확장 공개키로 사용할 수 있습니다.

 

5. 확장 공개키로 생성된 자식 공개키는 같은 인덱스를 가진 확장 개인키의 자식 개인키 값과 쌍을 이룹니다.

 

 지금까지 HD지갑에 대해 알아봤습니다. 감사합니다.

 

 

이어지는 글들


 

[비트코인 구조] HD 지갑 파생 경로(Derivation Paths)

미리 알아야 할 내용들 [비트코인 구조] BIP32: HD지갑(Hierarchical Deterministic Wallet) 미리 알아야 할 내용들 [비트코인 구조] BIP39: 니모닉(Mnemonic)과 시드(Seed) 미리 알아야 할 내용들 [블록체인 용어] 니

kwjdnjs.tistory.com