챕터 5 : 지갑
넓은 의미에서 지갑은 이더리움의 주요 사용자 인터페이스를 제공하는 소프트웨어 어플리케이션이다. 지갑은 사용자 돈에 대한 접근을 통제하고, 키와 주소를 관리하며, 잔액을 추적하고, 트랜잭션 생성과 서명을 제어한다. 여기에 더해서 몇몇 이더리움 지갑은 ERC20 토큰처럼 컨트랙트와 상호작용할 수 있다.
사실 이더리움이나 비트코인 네트워크에서 지갑이라는 것에 어떤 형태는 존재하지 않는다.
그냥 개인키랑 공개키가 다다.
그걸 통해서 블록안에서 내 기록을 가져오는 형태이다.
우리가 흔히 알고 있는 메타마스크 같이 잔고를 표시해주고, 송금, 스왑과 같은 기능을 탑재한 지갑들이 사실은 당연한게 아니라,
그 지갑에서 우리의 편의를 위해 대신 처리해주는 것이다.
우리가 직접 그 기능들을 쓰려고 한다면, 터미널을 켜는 작업부터 시작해야한다.
소위 콜드월렛이라고 불리는 것들도, 본질적인 것만 놓고 보면 그냥 개인키가 든 usb이다.
다만 이제 보안적인 부분이나 편의 기능이 들어있겠지만(인터넷에 접속하지 않고 트랜잭션을 보내기 위한 기능들)
어쨋든 내 코인은 블록안의 기록에 있는 것이다.
그 월렛을 잃어버려도, 키만 알고 있으면 기록은 블록체인에 있는 것이기 때문에 코인을 되찾을 수 있다.
개발자의 시각으로 좀 더 좁혀보면, 지갑이란 단어는 사용자의 키를 보관하고 관리하기 위해 사용되는 시스템을 의미하며, 모든 지갑은 키 관리 구성요소를 갖고 있다.
...
이번 장에서는 개인키를 담는 공간이자 키를 관리하는 시스템으로서 지갑을 살펴볼 것이다.
지갑 기술의 개요
지갑을 설계할 때 중요한 고려사항 하나는 편의성과 프라이버시 사이에 균형을 맞추는 것이다.
가장 편리한 이더리움 지갑은 하나의 개인키와 주소를 가지고 이를 재사용해서 모든 것을 처리하는 지갑이다.
하지만 불행하게도 그러한 솔루션은 누구나 쉽게 여러분의 모든 트랜잭션을 추적하고 연결하여 볼 수 있으므로 프라이버시에 대한 악몽이 될 수 있다.
퍼블릭 블록체인은 모든 장부가 공개되어 있기 때문에, 하나의 주소가 특정되면 그 주소의 트랜잭션을 모두 확인 할 수 있기 때문에,
프라이버시를 위해서는 하나의 지갑을 이용해서 하나의 트랜잭션만을 사용하는 것이 가장 이상적이지만 관리하기가 어렵다.
이더리움에 관한 일반적인 오해 중 하나는 이더리움 지갑이 이더 혹은 토큰을 보유한다는 것이다.
사실 매우 엄격하게 말하자면 지갑은 단지 키만 보유한다. 이더 혹은 다른 토큰은 이더리움 블록체인에 기록된다.
사용자는 지갑에 있는 키로 트랜잭션을 서명함으로써 네트워크에서 토큰을 제어한다.
이러한 맥락에서 이더리움 지갑은 키체인(keychain)이다.
위에서 얘기했던 내용이지만, 중요한 부분이라 인용했다.
지갑 = 개인키 = 코인 제어권 이라는 사실을 아는 것은 매우 중요하다.
내가 개인키(지갑)을 소유하지 않으면 그 코인은 남의 통제에 있는 것이기 때문에 내것이라 말할 수 없다.
예를 들어 업비트, 빗썸, 코인원과 같은 거래소 지갑에 내 코인을 보관하고 있는 경우,
해당 거래소가 계정을 잠궈버리면 그 안에 있는 코인은 내가 컨트롤 할 수 없다.
하지만 법안이 바뀌어 거래소가 사라져도, 내 개인 지갑에 내 코인이 있다면,
네트워크가 살아 있는 한 그 코인은 내 코인이고, 내가 컨트롤 할 수 있다.
중요한 것은 기존 은행(여러분과 은행만이 여러분의 계좌에 있는 돈을 볼 수 있고 트랜잭션을 위해 자금을 옮기고 싶다고 은행만 납득시키면 된다)의 중앙화된 시스템을 다루는 것에서부터 블록체인 플랫폼(모든 사람이 계좌의 이더 잔액을 볼 수 있고 계좌 주인을 알지 못하지만 소유자느 ㄴ트랜잭션 진행을 위해 자금을 옮기고 싶어 한다는 것을 모든 사람에게 납득시켜야한다)의 탈중앙화된 시스템으로 사고방식을 바꾸는 것이다. 실제로 이것은 지갑 없이도 계좌의 잔액을 확인하는 독립적인 방법이 있음을 의미한다. 더구나, 사용하던 지갑 앱이 싫어지면 현재 지갑에서 다른 지갑으로 계정을 옮길 수 있다.
탈중앙이라는 것은 개인의 자유를 넓혀가는 과정이다.
다만 자유에 대한 책임도 본인이 져야한다.
지갑은 주요한 두가지 형태가 있는데, 지갑이 포함하는 키가 서로 관련이 있느냐 없는냐에 따라 구분된다.
처음의 프라이버시 얘기와 이어진다.
지갑의 주소를 여러개를 만들 때, 독립적으로 만드느냐, 아니면 상관관계를 가지고 만드느냐에 따라서 지갑의 형태를 구분할 수 있다.
비결정적 지갑(nondeterministic wallet) - 각각의 키를 독립적으로 파생시킨다. JBOK 지갑이라고도 부른다(Just a Bunch Of Keys)
결정적 지갑(deterministic wallet) - 시드라고 하는 단일 마스터 키로부터 파생된다. 모든 키는 서로 관련이 있고 원래의 시드를 갖고 있다면 다시 키를 파생시킬 수 있다. 여러가지 키 파생 방법이 있는데 'HD 지갑' 방식의 트리 구조를 많이 사용한다.
결정적 지갑의 경우 니모닉 코드 단어라는 것을 사용하여, 키 분실에 대비할 수 있다.
물론 이 니모닉 코드 또한 개인키와 마찬가지로 누군가에게 노출되면, 지갑의 통제권을 뺏길 수 있다..
비결정적(무작위) 지갑
'타입 0'으로 볼 수 있다.
여러가지로 불편하기 때문에 결정적 지갑으로 대체되고 있다.
아까의 프라이버시 얘기를 다시하자면,
각 트랜잭션마다 새로 지갑을 만들어서 처리하는 것을 추천한다.(물론 단일지갑보다 비용이 많이 든다)
이렇게 하려면 정기적으로 키 목록을 증가시키고, 백업해야하는데
백업을 하기 전에 데이터를 잃어버리면 자금에 아예 접근 할 수 없게 된다.
'타입 0'으로 볼 수 있는 비결정적 지갑은 '때마다(just in time)' 새로운 주소를 위한 새로운 지갑 파일을 만들기 때문에 다루기가 가장 어렵다.
그럼에도 불구하고 많은 이더리움 클라이언트(게스 포함)는 보안 강화를 위해 암호문으로 암호화된 단일(무작위로 생성된) 개인키가 들어 있는, JSON 인코딩 파일인 키 저장소 파일을 사용한다. JSON 파일의 내용은 다음과 같다. (keythereum 출처)
{
address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b",
Crypto: {
cipher: "aes-128-ctr",
ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
cipherparams: {
iv: "6087dab2f9fdbbfaddc31a909735c1e6"
},
mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2",
kdf: "pbkdf2",
kdfparams: {
c: 262144,
dklen: 32,
prf: "hmac-sha256",
salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
}
},
id: "e13b209c-3b2f-4327-bab0-3bef2e51630d",
version: 3
}
키 저장소 형식은 무차별, 사전 및 레인보우 테이블 공격을 대비해서 암호 확장 알고리즘으로 알려진 '키 파생함수(key derivation fuction, KDF)'를 사용한다. 간단히 말해서, 개인키는 암호문에 의해 직접적으로 암호화되지 않는다. 대신 암호문은 반복적으로 해싱됨으로써 강화된다. 해시 함수는 262144 라운드로 반복되며, 키 저장소 JSON에서 crypto.kdfparams.n으로 확인할 수 있다.
--> 공격자가 암호문을 무차별적으로 생성할 때마다 똑같이 262144 라운드를 적용해야 하므로 1회당 공격 속도가 느려져, 복잡하고 길이가 긴 암호문에 대한 공격을 불가능하게 한다.
자바스크립트 라이브러리 keythereum과 같이 키 저장소 형식을 읽고 쓸 수 있는 여러 소프트웨어 라이브러리가 있다.
https://github.com/ethereumjs/keythereum
결정적(시드)지갑 ||
결정적 혹은 '시드' 지갑은 단일 마스터 키 또는 '단일 시드'로부터 파생된 개인키를 포함하고 있다. 시드는 개인키를 만들기 위해 인덱스 번호나 '체인 코드(chain code)'와 같은 데이터와 결합된 무작위로 추출된 번호다.
결정적 지갑은 최초의 키가 있고, 그 키로부터 여러 키를 파생시키는 방법이다.
HD 지갑(BIP-32/BIP-44)
결정적 지갑은 단일 시드로부터 아주 많은 키를 쉽게 추출하기 위해 개발되었다. 현재 가장 개선된 결정적 지갑은 비트코인의 BIP-32표준으로 정의된 HD(hierarchical deterministic)지갑이다. HD 지갑은 트리 구조로 파생된 키들을 가지고 있다. 이러한 구조는 부모 키가 자식 키의 시퀀스를 파생할 수 있고, 각각의 자식은 다시 또 손자 키의 시퀀스를 파생할 수 있다.
HD 지갑의 장점은
1. 하위 키 분기의 용도를 정해 사용할 수 있다. 기업과 같은 구조적인 의미를 표현하는 데도 사용할 수 있다.
-> HD 지갑은 말 그대로 hierarchical이기 때문에 계층적 구조로 지갑을 사용할 때 편리하다.
2. 개인키 없이 공개키 시퀀스를 만들 수 있다.(마스터 키 없이 하위 계층의 키들을 생성 가능하다.)
-> 개인키의 유출을 줄일 수 있으므로, 보안상 이점이다.
시드와 니모닉 코드(BIP-39)
안전한 백업 및 검색을 위해 개인키를 인코딩하는 데는 다양한 방법이 있다. 현재 많이 사용하는 방법은 단어 시퀀스를 사용하는 것인데, 이는 올바른 순서로 단어 시퀀스가 입력되면 고유한 개인키를 다시 만들 수 있다. 이러한 방법을 니모닉(mnemonic)이라 하고, 이러한 접근은 BIP-39에 의해 표준화되었다. 요즘 많은 이더리움 지갑(기타 암호화폐 지갑을 포함하여)은 이 표준을 사용하여 백업 및 복구를 위해 호환이 가능한 니모닉으로 시드 가져오기(import)나 내보내기(export)를 할 수 있다.
16진수 개인키는 의미가 없기 때문에 백업시에 한 글자만 잃어버려도 추측하기 힘들어 찾기 힘들다.
하지만 이를 의미있는 단어 단위로 만들면, 기억하기도 쉽고 글자 단위의 실수에서는 올바른 단어를 추측할 수 있다.
지갑의 모범 사례 ||
일반적인 표준
- BIP-39 기반 니모닉 코드 단어
- BIP-32 기반 HD 지갑
- BIP-43 기반 다목적(multipurpose) HD 지갑 구조
- BIPp-44 기반 복수화폐(multicurrency) 및 복수계정(multiaccount)지갑
니모닉 코드 단어(BIP-39)
니모닉 코드 단어는 결정적 지갑을 파생하기 위해 시드로 사용되는 난수를 인코딩하는 단어 시퀀스다.
니모닉 코드 단어는 시드 키 그니까 마스터키를 저장하는 방법 중 하나이다.
이는 계층 지갑을 위한 마스터 키를 생성할 때 니모닉 코드를 만들기 때문에 그런 것이다.
니모닉 코드 단어가 생김새만 다른 것이고 사실상 마스터키와 같기 때문에, 니모닉 코드는 개인키와 같이 다뤄야한다.
BIP-39는 니모닉 코드 표준의 하나일 뿐이지만, 사실상 업계 표준으로 고려되어야 한다.
BIP-39는 니모닉 코드와 시드 생성을 9단계로 정의한다.
크게 '니모닉 단어 생성'의 1~6단계, '시드 생성'의 7~9단계로 나뉜다.
니모닉 단어 생성(1~6단계)
- 128 ~ 256 비트의 무작위 암호화 시퀀스 S를 생성한다.
- S를 SHA-256으로 해싱한 값을 32비트로 나눈 처음 길이를 체크섬으로 생성한다.(S의 길이를 32로 나눈 만큼을 해싱한 값의 앞부분에서 잘라서 체크섬으로 생성함)
- 무작위 시퀀스 S의 끝에 체크섬을 추가한다.
- 시퀀스와 체크섬을 연결한 것을 11비트 단위로 나눈다.
- 각각의 11비트 값을 사전에 정의된 2048단어 사전과 매핑한다.
- 단어의 시퀀스로부터 순서를 유지하면서 니모닉 코드를 생성한다.
엔트로피 (비트) | 체크섬 (비트) | 엔트로피 + 체크섬 (비트) | 니모닉 길이 (단어 갯수) |
128 | 4 | 123 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
니모닉에서 시드까지
니모닉 단어는 128~256비트 길이의 엔트로피를 표현한다. 엔트로피는 키 스트레칭(key-stretching) 함수 PBKDF2를 사용하여 더 긴(512비트) 시드를 파생하는 데 사용되며, 생성된 시드는 결정론적 지갑을 구축하고 키를 파생하는 데 사용된다.
키 스트레칭 함수에는 파라미터가 니모닉, 솔트(salt) 두가지인데,
솔트는 조회 테이블 공격을 방어하기 위한 목적으로, 사용자가 지정한 암호문을 적을 수 있다.
옵션이라서 넣지 않아도 되지만 넣어두는 것이 좋다.
<니모닉 단어 생성(7~9단계)>
- PBKDF2 키 스트레칭 함수의 첫 번째 파라미터는 6단계에서 생성된 니모닉이다.
- PBKDF2 키 스트레칭 함수의 두 번째 파라미터는 솔트다. 솔트는 문자열 상수 "mnemonic"과 선택적으로 사용자가 지정한 암호문을 연결하여 구성한다.
- PBKDF2는 최종 출력으로 512비트 값을 만드는 HMAC-SHA512 알고리즘으로, 2048 해시 라운드를 사용하여 니모닉과 솔트 파라미터를 확장하며, 이 결과로 나온 512비트 값이 시드다.
2048 라운드의 해싱을 하는 이유는 무차별 대입 공격에 드는 비용을 늘려 방어하기 위함이다.
BIP-39 선택적 암호문
이 부분이 솔트에 대한 부분이다.
사용자가 솔트를 지정하지 않는다면, 니모닉은 상수문자열 "mnemonic"과 함께 솔트를 구성하여 연장된다.
선택적 암호문은 두가지 중요한 특징을 지닌다.
- 니모닉 자체만으로는 의미가 없도록 만들어서, 니모닉 백업이 도난으로부터 보호될 수 있도록 하는 2차 팩터(추가적으로 기억해야 하는)로 기능한다.
- 공격자의 협박 때문에 암호문을 가르쳐 줘야 할 경우는 진짜 암호문 대신 그럴 듯한 가짜 암호문을 제공한다. 그러면 대부분의 자금을 담고 있는 '진짜(real)' 지갑 대신 적은 양의 자금이 있는 지갑으로 공격자의 주의를 돌릴 수 있다.
두번째 특징은 이해가 되지 않는다. 자금이 적은 지갑을 하나 더 파두라는 말인걸까.
그럴듯한 암호문을 제공했을 때, 돈이 없는 것을 확인하면 공격자가 공격을 멈출지가 의문이다.
그러나 암호문의 사용은 손실의 위홈 또한 가져온다는 점을 주목해야 한다.
- 만약 지갑의 주인이 의식을 잃었거나 사망했고 암호문을 알고 있는 사람이 없다면, 시드는 쓸모없어지고 지갑에 저장된 모든 자금을 영원히 잃게 된다.
- 반대로, 소유자가 암호문을 시드와 동일한 위치에 백업하는 것은 2차 팩터를 사용하는 목적에 어긋난다.
여기서 알 수 있는 당연한 듯 하지만 생각 못했던 부분
-> 2차 팩터는 시드와 따로 보관해야한다.
니모닉 코드로 작업하기
BIP-39는 여러 가지 프로그래밍 언어의 라이브러리로 구현되어 있다. 예를 들면 다음과 같다.
<파이썬 니모닉(python-mnemonic)>
파이썬으로 BIP-39를 제안한 사토시랩(SatoshiLabs) 팀의 표준참조 구현
https://github.com/trezor/python-mnemonic
<컨센시스/이더-라이트월렛(ConsenSys/eth-lightwallet)>
노드와 브라우저용 경량 JS 이더리움 지갑(BIP-39 포함)
https://github.com/ConsenSys/eth-lightwallet
<npm/bip39>
비트코인 BIP-39의 자바스크립트 구현: 결정적 키 생성용 니모닉 코드
https://www.npmjs.com/package/bip39
테스트와 실습에 아주 유용한 독립형(standalone) 웹 페이지로 구현된 BIP-39 생성기가 있다.
니모닉 코드 변환기는 니모닉(Mnemonic Code Converter, https://iancoleman.io/bip39/), 시드 그리고 확장된 개인키를 생성한다. 또한 브라우저에서 오프라인으로 사용하거나 온라인으로 접속할 수 있다.
<니모닉 코드 변환기>
시드로 HD 지갑 생성하기
그러니까 결정적 지갑의 루트 시드를 만드는 과정을 쉽게 정리하면
-> 임의의 128 ~ 256비트의 S 만들기
-> S와 S 앞의 특정 수 비트 합치기
-> 니모닉으로 치환하기
-> 니모닉과 솔트 합친 뒤 해싱
-> 512비트 시드 생성!!
결론적으로 임의의 수를 니모닉과 매칭하여 시드를 생성하는 셈이다.
HD 지갑의 모든 키는 루트 시드에서 결정적으로 파생되었으며, 모든 호환 HD 지갑에서 그 시드로부터 전체 HD 지갑을 재생성할 수 있다. 이것은 루트 시드를 파생시킨 니모닉을 전송하는 것만으로도 수천 혹은 수백만 개의 키가 포함된 HD 지갑의 내보내기, 백업, 복원, 가져오기를 쉽게 만든다.
HD 지갑(BIP-32)과 경로(BIP-43/44)
대부분의 HD 지갑은 BIP-32를 표준으로 사용하고 있다.
개념적인 부분에서 제일 중요한 것은 키들간의 트리 같은 계층적인 관계 그리고,
다음절에서 설명 할 확장 키(extended key)와 강화 키(hardened key)의 아이디어를 이해하는 것이다.
BIP-32를 구현한 많은 라이브러리가 있지만 대부분 비트코인 지갑용으로 설계되었다.
서로 다른 방법으로 주소를 구현하지만 동일한 키 파생(key-derivatioon)구현을 공유한다.
1. 이더리움을 위해 설계된 지갑을 사용하거나(https://github.com/ConsenSys/eth-lightwallet)
2. 이더리움 주소 인코딩 라이브러리를 추가하여 비트코인에서 채택한 것을 사용하는 것이 좋다.
BIP-32로 테스트하고 실습하기에 아주 유용한 독립형 웹 페이지(http://bip32.org/)로 구현된 BIP-32 생성기가 있다.
HTTPS 사이트가 아니므로 이 도구를 사용하는 것은 안전하지 않다. 오직 테스트용이므로, 이 사이트에서 생성된 키를 실제 자금에 사용해선 안된다.
확장된 공개키와 개인키
자식 공개키를 파생하기 위한 방법은 두가지가 있다.
자식 개인키로부터 직접 파생하는 방법과
부모 공개키로부터 직접 파생하는 방법이다.
강화된 자식 키의 파생
유출된 하나의 자식 개인키와 부모 체인 코드는 모든 자식의 개인키를 노출시킬 수 있다. 더욱이, 부모 체인 코드와 함께 자식 개인키를 사용하여 부모 개인키를 추론할 수 있다.
이러한 위험에 대응하기 위해 HD 지갑은 강화 파생(hardened derivation)이라고 하는 대체 가능 파생 함수를 사용한다.
이 파생 함수는 부모 공개키와 자식 체인 코드 간의 관계를 '끊는다(break)'.
일반 및 강화 파생을 위하 인덱스 번호
BIP-32 부모-자식 파생함수에 사용하는 인덱스 번호는 32비트 정수다.
강화 파생을 통해 파생된 키와 일반(비강화된) 파생 함수를 통해 파생된 키를 쉽게 구별하기 위해 인덱스 번호는 두 범위로 나뉜다.
0과 2^31 -1 사이의 인덱스 번호는 오직 일반 파생을 위해서만 사용된다.
2^31과 2^32 사이의 인덱스 번호는 오직 강화 파생에만 사용된다.
HD 지갑 키 식별자(경로)
HD 지갑의 키는 '경로(path)' 이름 규칙을 사용하여 식별하며, 트리의 각 레벨은 슬래시(/) 문자로 구분한다. 마스터 개인키에서 파생된 개인키는 m으로 시작하며, 마스터 공개키에서 파생된 공개키는 M으로 시작한다. 따라서 마스터 개인키의 첫 번째 자식 개인키는 m/0 이며, 첫 번째 자식 공개키는 M/0이다. 첫 번째 자식의 두 번째 자식은 m/0/1이고, 나머지도 마찬가지다.
결론
지갑은 사용자를 상대하는 모든 블록체인 애플리케이션의 기본이다. 지갑을 이용하여 사용자는 키와 주소들을 관리한다. 또한 6장에서 살펴보겠지만, 지갑을 사용하면 사용자가 이더의 소유권을 입증하고 디지털 서명을 적용하여 트랜잭션을 승인할 수 있다.
<참고한 내용>
1. 해당 내용 강의
https://www.youtube.com/watch?v=YqenuY23duc
2. 해당 부분 블로그
https://potensj.tistory.com/57
https://velog.io/@syapeach4/2%EC%A3%BC%EC%B0%A8-%EA%B3%B5%EB%B6%80
3. 세미나 글
'블록체인 > 책' 카테고리의 다른 글
스마트 컨트랙트와 솔리디티 | 마스터링 이더리움 (0) | 2021.08.29 |
---|---|
트랜잭션 | 마스터링 이더리움 (0) | 2021.08.13 |
암호학 | 마스터링 이더리움 (0) | 2021.05.10 |
이더리움 클라이언트 | 마스터링 이더리움 (0) | 2021.04.24 |
이더리움 기초 | 마스터링 이더리움 (0) | 2021.04.16 |