[Bitcoin] - ch4. 지갑

‍허진·2023년 3월 1일
0

Blockchain

목록 보기
4/19
post-thumbnail

본 글은 '비트코인, 공개 블록체인 프로그래밍(Andreas M. Antonopoulos 저, 최은실, 김도훈, 송주한 옮김, 2018)'을 바탕으로 작성되었습니다.

> '지갑' in 비트코인

'지갑'은 비트코인에서 여러가지 의미를 지닌다.
넓은 의미에서 지갑은 주요 사용자 인터페이스 역할을 하는 어플리케이션이다. 지갑은 사용자의 돈에 접근하는 것을 통제하고 키와 지갑을 관리하며 잔액을 추적하고 거래를 생성 및 서명한다.
좁은 의미에서 지갑은 프로그래머의 관점에서 사용자의 키를 저장하고 관리하는 데 사용되는 데이터 구조를 의미한다.

지갑은 크게 비결정적(무작위) 지갑과 결정적(종자) 지갑 두 가지로 나뉜다.

> 비결정적(무작위) 지갑

최초의 비트코인 지갑(현재 비트코인 코어)의 경우 처음 시작할 때 무작위로 선택한 개인키를 생성하고 필요할 때마다 추가로 생성하였다. 이러한 지갑들은 관리하거나 백업, 데이터를 불러오기 복잡하다. 만약 키를 백업하지 못해 잃어버리면 키가 관리하는 돈은 고스란히 잃어버리게 된다.
이러한 단점은 또 비트코인 주소를 단 하나의 거래에만 이용하여 주소의 재사용을 피하라는 원리와 상충된다. 주소를 재사용하면 여러 건의 거래와 주소가 연관되기 때문에 프라이버시가 노출되고, 그렇다고 주소를 매번 바꾸기가 비결정적 지갑에서는 힘들다. 따라서 비결정적 지갑은 사용을 권장하지 않는다.

[그림 - 무결정적 지갑 형태]

> 결정적(종자) 지갑

결정적 혹은 '종자' 지갑에는 일방 해시 함수를 이용해서 공통 종자(common seed)에서 얻은 개인키들이 담겨 있다. 결정적 지갑 내에서는 종자만 있으면 추출키 전부를 복원할 수 있기에 특정한 시기에 백업을 한 번만 해도 된다. 또한 지갑의 export와 import도 종자만 있으면 가능하기 때문에 다른 종류의 지갑 사이에서도 사용자들의 키 전부가 쉽게 이동할 수 있다.

[그림 - 결정적 지갑 형태]

> HD 지갑(BIP-32/BIP-44)

결정적 지갑 중 가장 발달된 유형은 HD 지갑으로, BIP-32 표준에서 규정하고 있다. HD 지갑에는 트리 구조에서 생성된 키를 담고 있다.

[그림 - HD 지갑 형태(트리 구조)]

HD 지갑은 두 가지 장점을 제공한다.

  1. 수입금이나 잔액을 받기 위한 서브키로 구성된 특정 브랜치(branch)가 언제 사용되는지 등 유기적인 의미를 표현하기 위해 트리구조가 사용될 수 있다. 키와 브랜치는 각기 다른 브랜치를 각 부서, 자회사, 특정 행사나 회계 범주에 배당함으로써 회사 내 설정에서도 사용될 수 있다.
  2. 사용자들이 공개키에 대응하는 개인키에 접근하지 않고도 공개키 열을 생성할 수 있다. 따라서 안전하지 않은 서버상에서나 수신 기능만을 가지고 있는 서버에서도 HD 지갑을 사용할 수 있고, 각각의 거래에 대해 다른 공개키를 발급하게 된다.

> 연상기호 코드 워드 -> Seed

연상기호 코드 워드는 결정적 지갑을 추출하기 위해 종자로 이용한 난수를 표현하는(인코딩하는) 영어 단어열이다. 단어열만 있으면 종자를 재현할 수 있고, 종자에서 지갑과 추출키 전부를 재현할 수 있다. 연상기호 코드를 이용해 결정적 지갑을 실행하는 지갑 어플리케이션은 처음 지갑을 생성할 때 12-24 단어로 구성된 단어열을 사용자에게 보여 주게 된다. 단어열은 지갑을 백업해 놓은 것으로, 동일한 지갑 어플리케이션이나 호환 가능한 지갑 어플리케이션에 들어 있는 키 전부를 복원하고 재현하는 데 사용할 수 있다.

연상기호 워드를 생성하는 과정은 다음과 같아.

  1. 128-256비트의 무작위열(엔트로피)을 생성한다.
  2. 생성된 열의 SHA256 해시 중 첫 몇 비트(엔트로피 길이/32)를 따서 무작위열의 검사합을 생성한다.
  3. 생성된 검사합을 무작위열의 끝부분에 붙인다.
  4. 무작위열을 11비트씩 나눈다.
  5. 미리 정해진 단어 2,048개로 구성된 사전에 있는 단어와 11 비트 크기의 값을 연결시킨다.
  6. 연상기호 코드가 단어열이 된다.

    [그림 - 연상기호 워드 생성 과정]

연상기호 워드는 128-256비트를 나타내며 키스트레칭(KEY-STRETCHING) 함수인 PBKDF2를 사용해서 좀 더 길이가 긴(512비트) 종자를 추출하는 데 사용된다. 결과값으로 산추로딘 종자는 결정적 지갑과 지갑 내에 들어가는 추출키 전부를 생성하는 데 사용된다.

키스트레칭 함수는 두 개의 변수를 취한다. 하나는 연상기호고, 나머지 하나는 솔트(salt)다. 솔트는 bruce-force attack을 어렵게 막는 역할을 한다.

연상기호에서 종자에 이르기까지의 과정은 다음과 같다.

  1. PBKDF2 키스트레칭에 대한 첫 번째 변수는 6단계에서 산출된 연상기호다.
  2. PBKDF2 키스트레칭에 대한 두 번째 변수는 솔트다. 솔트에서는 'mnemonic'이라고 하는 문자열 상수와 사용자가 제공하는 선택적 패스프레이즈열이 연결되어 있다.
  3. PBKDF2는 HMAC-SHA512 알고리즘으로 2,048회 해싱해서 연상기호와 솔트를 늘인다. 그 결과 512 비트의 값이 최종 출력값으로 생산된다. 이것이 바로 종자다.
엔트로피 입력값 (128 bits)0c1e24e5917779d297e14d45f14e1a1a
연상 기호 (12 words)army van defense carry jealous true garbage claim echo media make crunch
PassphraseSuperDuperSecret
Seed(종자) (512 bits)3b5df16df2157104cfdd22830162a5e170c0161653e3afe6c88defeefb0818c793dbb28ab3ab091897d0 715861dc8a18358f80b79d49acf64142ae57037d1d54

[표 - 128비트의 엔트로피 연상기호 코드와 종자 결과 값 예시 (패스프레이즈 O)]

> Seed로부터 HD 지갑 생성하기

HD 지갑은 128, 256, 512비트 크기의 무작위 숫자인 근원종자(root seed) 한 개로부터 생성된다. 이 종자는 아까 전까지 살펴본 연상기호로부터 생성되는 것이 일반적이다.

HD 지갑에서 마스터 키와 마스터 체인코드를 생성하는 과정은 다음과 같다.

[그림 - root seed로부터 마스터 키와 마스터 체인코드 생성하기]

근원종자는 HMAC-SHA512 알고리즘에 입력되고 그 결과로 나온 해시값이 마스터 개인키(m)과 마스터 체인코드로 나뉜다. 마스터 공개키(M)는 마스터 개인키에 의해 생성된다.

> 개인 자식키 유도하기

HD 지갑은 부모키로부터 자식키를 얻기 위해 자식키 유도(CKD) 함수를 사용한다.
자식키 유도 함수는 일방 해시 함수를 기반으로 하고 있으며, 다음 항목들을 포함하고 있다.

  • 부모 개인키 혹은 부모 공개키(ECDSA 비압축키)
  • 체인코드라는 이름의 종자(256비트)
  • 색인번호(Index Number, 32비트)

위의 그림과 같이, 부모 공개키, 체인코드, 색인번호를 이용해서 HMAC-SHA512 알고리즘을 통해 해싱 작업을 거치면 512 비트의 해시가 만들어진다. 해시의 왼쪽 절반은 자식 개인키로, 오른쪽 절반은 자식 체인코드가 된다. 자식 공개키는 자식 개인키로부터 만들어진다.

색인번호(Index Number)를 변경하면 부모키를 확장해서 동일한 열 내에 다른 자식키를 생성하는 것이 가능하다.
트리 구조에서 이 과정을 반복하면서 한 단계 내려가게 되면 이번에는 각 자식키들이 부모키가 되어 자식키를 다시 생성하는 과정이 무한대로 반복된다.

자식 개인키는 자식 공개키와 비트코인 주소를 만드는 데 사용된다. 그 후, 자식 개인키는 해당 주소로 지불된 비트코인을 소비하기 위한 거래를 서명하는 데 이용될 수 있다.

> 확장키

앞에서 보았듯이 키 유도 함수는 세 가지 입력값(키, 체인코드, 목표 자식의 색인번호)을 기반으로 트리 구조의 어떠한 레벨에서도 자식키를 생성하는 데 사용될 수 있다. 여기서 중요한 요소는 키와 체인코드이고, 이 둘을 결합한 것을 확장키(extended key)라고 부른다.

확장 개인키는 개인키와 체인코드의 결합으로, 자식 개인키를 얻는 데 쓰일 수 있다.
확장 공개키는 공개키와 체인코드의 결합으로, 자식 공개키를 얻는 데 쓰일 수 있다.

HD 지갑의 트리 구조에서 확장키를 브랜치의 근원으로 간주해보자. 브랜치의 근원을 가지고 우리는 해당 브랜치의 나머지를 구할 수 있다. 확장 개인키만 있으면 브랜치의 나머지 부분을 완성할 수 있는 반면 확장 공개키는 공개키로 이루어진 브랜치만을 생성할 수 있다.

> 공개 자식 키 유도하기

앞서 언급했듯이, HD 지갑의 매우 유용한 특성 중 하나는 개인키에 직접 접근하지 않고도 공개 부모키로부터 공개 자식키를 얻을 수 있다는 점이다. 이에 따라 우리는 자식 공개키를 얻는 방법 두 가지를 가지게 된다.
1. 자식 개인키로부터 생성해내기
2. 부모 공개키로부터 직접 얻기
따라서 HD 지갑 구조의 해당 브랜치 내에서 공개키 전부를 얻기 위해서 확장 공개키를 사용할 수 있다. 이 방법을 이용하면 개인키들을 외부에 노출시키지 않으면서 확장 공개키의 복사본만으로도 매우 안전한 서버나 어플리케이션을 만들 수 있다.

> 단절된 자식키 유도하기

확장 공개키에서 공개키 브랜치를 생성하는 기능은 매우 유용하긴 하지만 위험을 수반할 가능성이 있다. 확장 공개키에 접근할 수 있다고 해서 자식 개인키에 접근할 수 있는 것은 아니지만, 확장 공개키가 체인코드를 포함하고 있기 때문에 자식 개인키를 알게 되는 경우 입수한 자식 개인키와 체인코드를 함께 사용하면 다른 자식 개인키 전부를 얻을 수 있다. 부모 체인코드와 함께 자식 개인키가 단 하나만 유출되어도 자식 개인키 전부가 알려지게 된다. 또, 부모 개인키를 추론하는 데에도 사용될 수 있다.

이러한 위험성에 대응하기 위해 HD 지갑은 단절 유도법(hardened derivation)이라고 불리는 대안 유도 함수를 사용해서 부모 공개키와 자식 체인코드 간 관계를 '끊어' 버린다. 단절 유도 함수는 부모 공개키를 사용하지 않고 부모 개인키를 사용해서 자식 체인코드를 만든다. 이를 통해 부모/자식 열에 '방화벽'이 생성되며 체인코드는 부모 개인키나 다른 자식 개인키를 위험에 빠뜨리지 못한다.

단절 개인 유도 함수가 사용되는 경우, 자식 개인키 및 체인코드 결과값은 정규 유도 함수를 사용할 때와 비교해서 완전히 다르게 나온다. 결과값으로 나온 키들의 브랜치는 위험에 취약하지 않은 확장 공개키를 생성하는 데 사용될 수 있다 왜냐하면 확장 공개키에 담겨 있는 체인코드는 어떠한 개인키를 푸는 데도 사용될 수 없기 때문이다.

간단하게 설명하자면, 만약 체인코드 유출 위험성에 노출시키지 않고 공개키 브랜치를 얻기 위해서 확장 공개키의 편리성을 활용하고 싶다면 확장 공개키를 정규 부모보다는 단절 부모에게서 얻어야 한다. 모범 사례로, 마스터키의 위험을 방지하기 위해서 마스터키의 1세대 자식들은 항상 단절 유도법을 통해 생성된다.

[그림 - 자식 키에 대한 단절 유도법]

> 정규 유도와 단절 유도의 index number

유도 함수에서 사용하는 색인번호(index number)는 32비트의 정수다. 정규 유도 함수를 통해 얻은 키와 단절 유도법을 통해 얻은 키를 쉽게 구분하기 위해서 색인번호는 두 영역으로 나뉜다. 0에서 2^31-1까지의 색인번호(0x0 ~ 0x7FFFFFFF)는 정규 유도법에서만 사용된다. 2^31 ~ 2^32-1까지의 색인번호(0x80000000 ~ 0xFFFFFFFF)는 단절 유도법에서만 사용된다.
추가로, 단절 자식키는 0부터 시작하지만 구별하기 위해 프라임 부호(')를 붙인다.

> HD 지갑키 식별자(경로)

HD 지갑에 들어 있는 키는 명명 규칙인 '경로(path)'를 이용해서 식별되며, 트리 구조의 각 단계는 슬래시(/)로 구분된다. 마스터 개인키에서 나온 개인키들은 'm'으로 시작한다. 마스터 공개키에서 나온 공개키들은 'M'으로 시작한다. 따라서 마스터 개인키의 첫 번째 자식 개인키는 m/0이 된다. 첫 번째 자식 공개키는 M/0이 된다. 그리고 첫 번째 자식키의 두 번째 손자키는 m/0/1이 되고, 이 과정은 계속 반복된다.

HD 경로키 설명
m/0마스터 개인키(m)에서 만들어진 첫 번째(0) 자식 개인키
m/0/0첫 번째 자식키(m/0)의 첫 번째 손자 개인키
m/0'/0첫 번째 단절 자식키(m/0')의 첫 번째 정규 손자키
M/23/17/0/024번째 자식키의 18번째 손자키의 첫 번째 증손자 키의 첫 번째 고손자 공개키
profile
매일 공부하기 목표 👨‍💻 

0개의 댓글