Mastering Ethereum 5

개발새발·2021년 11월 21일
0

Mastering Ethereum

목록 보기
5/7
post-thumbnail

Chapter 5. 지갑

본 글은 『Mastering Ethereum』을 읽고 정리한 내용입니다.

이더리움에서는 '지갑'이라는 단어를 몇 가지 다른 것을 설명하는 데 사용한다.

넓은 의미에서 지갑은 이더리움의 주요 사용자 인터페이스를 제공하는 소프트웨어 애플리케이션이다. 지갑은 사용자 돈에 대한 접근을 통제하고, 키와 주소를 관리하며, 잔액을 추적하고, 트랜잭션 생성과 서명을 제어한다. 여기에 더해서 몇몇 이더리움 지갑은 ERC20 토큰처럼 컨트랙트와 상호작용할 수 있다.

개발자의 시각으로 좀 더 좁혀보면, 지갑(wallet)이란 단어는 사용자의 키를 보관하고 관리하기 위해 사용되는 시스템을 의미하며, 모든 지갑은 키 관리 구성요소를 갖고 있다. 그 자체로 전부인 지갑도 있고, 좀 더 넓은 범주에서 이더리움 기반 탈중앙화 애플리케이션의 인터페이스인 브라우저(browser)의 일부이거나, DApp의 한 부분인 지갑도 있다. 지갑이라는 단어에 함축된 다양한 범주를 구분할 수 있는 명확한 기준은 없다.

이번 장에서는 개인키를 담는 공간이자 키를 관리하는 시스템으로서 지갑을 살펴볼 것이다.

1. 지갑 기술의 개요

지갑을 설계할 때 중요한 고려사항 하나는 편의성과 프라이버시 사이에 균형을 맞추는 것이다. 가장 편리한 이더리움 지갑은 하나의 개인키 주소를 가지고 이를 재사용해서 모든 것을 처리하는 지갑이다. 하지만 불행하게도 그러한 솔루션(solution)은 누구나 쉽게 여러분의 모든 트랜잭션을 추적하고 연결하여 볼 수 있으므로 프라이버시에 대한 악몽이 될 수 있다. 모든 트랜잭션에 새로운 키를 사용하는 것이 프라이버시를 위해 가장 좋지만 관리하기가 몹시 어렵다. 그 사이에서 적절한 균형을 달성하기가 쉽지 않는데, 이것이 바로 좋은 지갑을 설계하는 것이 다른 무엇보다 중요한 이유다.

이더리움에 관한 일반적인 오해 중 하나는 이더리움 지갑이 이더 혹은 토큰을 보유한다는 것이다. 사실 매우 엄격하게 말하자면 지갑은 단지 키만 보유한다. 이더 혹은 다른 토큰은 이더리움 블록체인에 기록된다. 사용자는 지갑에 있는 키로 트랜잭션을 서명함으로써 네트워크에서 토큰을 제어한다. 이러한 맥락에서 이더리움 지갑은 키체인(keychain)이다. 지갑에 있는 키가 이더나 토큰을 전송하는 데 필요한 유일한 것이라고 생각하면, 실제로 이렇게 구분하는 것이 큰 의미는 없다. 중요한 것은 기존 은행(여러분과 은행만이 여러분의 계좌에 있는 돈을 볼 수 있고 트랜잭션을 위해 자금을 옮기고 싶다고 은행만 납득시키면 된다)의 중앙화된 시스템을 다루는 것에서부터 블록체인 플랫폼(모든 사람이 계좌의 이더 잔액을 볼 수 있고 계좌 주인을 알지 못하지만 소유자는 트랜잭션 진행을 위해 자금을 옮기고 싶어 한다는 것을 모든 사람에게 납득시켜야 한다)의 탈중앙화된 시스템으로 사고방식을 바꾸는 것이다. 실제로 이것은 지갑이 없이도 계좌의 잔액을 확인하는 독립적인 방법이 있음을 의미한다. 더구나, 사용하던 지갑 앱(app)이 싫어지면 현재 지갑에서 다른 지갑으로 계정을 옮길 수 있다.

이더리움 지갑은 키는 포함하고 이더나 토큰은 포함하지 않는다. 지갑은 개인키와 공개키 쌍을 포함하는 키체인과 같은 것이다. 사용자는 개인키로 트랜잭션에 서명함으로써 이더가 자신의 소유임을 증명한다. 이더는 블록체인에 저장된다.

지갑은 주요한 두 가지 형태가 있는데, 지갑이 포함하는 키가 서로 관련이 있느냐 없느냐에 따라 구분된다.

첫 번째 유형은 각기 다른 무작위 수로부터 각각의 키를 무작위적으로 추출하는 비결정적 지갑(nondeterministic wallet)이다. 이러한 형태의 지갑을 '그냥 열쇠뭉치(Just a Bunch Of Keys)'라는 뜻의 JBOK 지갑이라 부른다.

두 번째 유형은 모든 키가 시드(seed)라고 하는 단일 마스터 키로부터 파생된 결정적 지갑(deterministic wallet)이다. 이러한 지갑 형태의 모든 키는 서로 관련이 있고 원래의 시드를 갖고 있다면 다시 키를 파생시킬 수 있다. 결정적 지갑에는 여러 가지 키 파생(key derivation) 방식이 있는데, 가장 많이 사용하는 파생 방식은 이 후에 나오는 'HD 지갑(BIP-32/BIT-44)'절에서 설명하는 트리 같은 구조를 사용한다.

휴대전화를 도난당하거나 화장실에 떨어뜨림으로써 데이터를 분실할 수 있다. 따라서 좀 더 안전한 결정적 지갑을 만들기 위해서 시드는 단어 목록(영어 또는 기타 언어)으로 인코딩되어 불의의 사고에 대비할 수 있도록 적어두고 사용한다. 이를 지갑의 니모닉 코드 단어(mnemonic code words)라고 한다. 물론, 누군가가 여러분의 니모닉 코드 단어를 손에 넣으면 지갑을 재생성하여 여러분의 이더와 스마트 컨트랙트에 접근할 수 있다. 그러므로 복구 단어 목록은 아주 조심스럽게 다루고 절대로 컴퓨터나 휴대전화의 전자파일로 저장하지 말고 종이에 적어서 안전한 곳에 보관해야 한다.

1-1. 비결정적(무작위) 지갑

첫 번째 이더리움 지갑(이더리움 사전 판매용으로 생성된)은 각 지갑 파일에 무작위로 추출된 단일 개인키를 저장했다. 이런 '이전 스타일' 지갑은 여러 측면에서 불편하기 때문에 결정적 지갑으로 대체되고 있다. 예를 들어, 이더리움을 사용하는 동안 프라이버시를 극대화한다는 차원에서는 이더리움 주소의 재사용을 피하는 것이 좋은 지침으로 간주된다. 즉, 자금을 받을 때마다 새로운 주소(새로운 개인키가 필요한)를 사용한다. 이렇게 하려면 비결정적 지갑은 정기적으로 키 목록을 증가시켜야 하는데, 이는 정기적인 백업이 필요하다는 뜻이다. 만약 지갑을 백업하기 전에 데이터(디스크 고장, 음료 사고, 휴대전화 도난)를 잃어버리면 자금과 스마트 컨트랙트에 접근할 수 없게 된다. '타입 0'으로 볼 수 있는 비결정적 지갑을 '때마다(just in time)' 모두 새로운 주소를 위한 지갑 파일을 만들기 때문에 다루기가 가장 어렵다.

그러므로 불구하고 많은 이더리움 클라이언트(게스 포함)는 보안 강화를 위해 암호문으로 암호화된 단일(무작위로 생성된) 개인키가 들어 있는, JSON 인코딩 파일인 키저장소(keystore) 파일을 사용한다. JSON 파일의 내용은 다음과 같다.

{
    "address": "001d3f1ef827552ae1114027bd3ecf1f086ba0f9",
    "crypto": {
        "cipher": "aes-128-ctr",
        "ciphertext":
            "233a9f4d236ed0c13394b504b6da5df02587c8bf1ad8946f6f2b58f055507ece",
        "cipherparams": {
            "iv": "d10c6ec5bae81b6cb9144de81037fa15"
        },
        "kdf": "scrypt",
        "kdfparams": {
            "dklen": 32,
            "n": 262144,
            "p": 1,
            "r": 8,
            "salt":
                "99d37a47c7c9429c66976f643f386a61b78b97f3246adca89abe4245d2788407"
        },
        "mac": "594c8df1c8ee0ded8255a50caf07e8c12061fd859f4b7c76ab704b17c957e842"
    },
    "id": "4fcb2ba4-ccdb-424f-89d5-26cce304bf9c",
    "version": 3
}

키저장소 형식은 무차별(brute-force), 사전(dicionary) 및 레인보우 테이블(rainbow table) 공격을 대비해 암호 확장 알고리즘으로 알려진 키 파생 함수(key derivation function, KDF)를 사용한다. 간단히 말해서, 개인키는 암호문에 의해 직접적으로 암호화되지 않는다. 대신, 암호문은 반복적으로 해싱됨으로써 강화된다. 해시 함수는 262,144 라운드로 반복되며, 키저장소 JSON에서 파리미터 crypto.kdfparams.n으로 확인할 수 있다. 공격자가 암호문을 무차별적으로 생성하려면 암호화를 시도할 때마다 262,144 해시 라운드를 적용해야 할 것이다. 이것은 공격 속도를 늦추어 복잡하고 길이가 긴 암호문에 대한 공격을 불가능하게 한다.

자바스크립트 라이브러리 keythereum과 같이 키저장소 형식을 읽고 쓸 수 있는 여러 소프트웨어 라이브러리가 있다.

비결정적 지갑은 간단하 테스트 외에는 사용을 권장하지 않는다. 왜냐하면 가장 단순한 상황 이외에는 백업하고 사용하기가 매우 불편하기 때문이다. 대신, 백업을 위해 니모딕 시드가 있든 산업계 표준인 HD(hierarchical deterministic) 지갑을 사용하라.

1-2. 결정적(시드) 지갑

결정적 혹은 '시드' 지갑은 단일 마스터 키 또는 '단일 시드'로부터 파생된 개인키를 포함하고 있다. 시드는 개인키를 만들기 위해 인덱스 번호나 '체인 코드(chain code)'같은 데이터와 결합된 무작위로 추출된 번호다. 결정적 지갑에서 시드는 모든 파생된 키를 복구할 수 있다. 그러므로 생성 시점에 단일 백업으로 지갑에 있는 모든 자금과 컨트랙트를 안전하게 보호할 수 있다. 시드는 또한 지갑을 내보내거나(export) 가져오기(import)에 활용되고 다른 지갑 간에 모든 키를 쉽게 이관할 수 있다.

이러한 구조로 인해 시드만 있으면 전체 지갑에 접근이 가능하기 때문에 시드의 보안이 최우선적인 과제가 된다. 한편, 보안 노력을 단일 데이터에 집중할 수 있다는 것은 장점으로 볼 수 있다.

1-3. HD지갑(BIP-32/BIP-44)

결정적 지갑은 단일 시드로부터 아주 많은 키를 쉽게 추출하기 위해 개발되었다. 현재 가장 개선된 결정적 지갑은 비트코인의 BIP-32 표준으로 정의된 HD(hierarchical deterministic)지갑이다. HD 지갑은 트리 구조로 파생된 키들을 가지고 있다. 이러한 구조는 부모 키가 자식 키의 시퀀스(sequence)를 파생할 수 있고, 각각의 자식은 다시 또 손자 키의 시퀀스를 파생할 수 있다. 이 트리의 구조는 [그림 5-1]에 나와 있다.

[그림 5-1] HD 지갑 : 단일 시드로부터 생성된 키 트리

HD 지갑은 결정적 지갑에 비해 몇 가지 장점을 지닌다. 먼저, 트리 구조는 예를 들어 하위 키의 특정 분기(branch)는 입금을 받는 데 사용하고 다른 분기는 송금 후 잔액을 받는 데 사용할 수 있으며, 또한 부서, 자회사, 특정 기능 혹은 회계 범주로 다른 분기를 할당하여 기업 환경 설정과 같은 구조적인 의미를 표현하는 데도 사용할 수 있다.

HD 지갑의 두 번째 장점은 개인키에 접속하지 않고 사용자가 공개키 시퀀스를 만들 수 있다는 것이다. HD 지갑은 보안상 안전하지 않는 서버, 보기 전용, 수신 전용의 용도로 사용할 수 있는데, 이때 지갑에는 자금을 움직이는 개인키가 들어 있지 않게 만들 수 있다.

1-4. 시드와 니모닉 코드(BIP-39)

안전한 백업 및 검색을 위해 개인키를 인코딩하는 데는 다양한 방법이 있다. 현재 많이 사용하는 방법은 단어 시퀀스를 사용하는 것인데, 이는 올바른 순서로 단어 시퀀스가 입력되면 고유한 개인키를 다시 만들 수 있다. 이러한 방법을 니모닉(mnomonic)이라 하고, 이러한 접근은 BIP-39에 의해 표준화되었다. 요즘 많은 이더리움 지갑(기타 암호화폐 지갑을 포함하여)은 이 표준을 사용하여 백업 및 복구를 위해 호환이 가능한 니모닉으로 시드 가져오기(import)나 내보내기(export)를 할 수 있다.

왜 이러한 접근을 선호하는지 다음 예제를 확인해 보자.

FCCF1AB3329FD5DA3DA9577511F8F137

wolf juice proud gown wool unfair
wall cliff insect more detail hub

실용적인 측면에서, 16진수 시퀀스를 기록할 때는 오류가 발생할 확률이 매우 높다. 반대로 알려진 단어 목록은 단어(특히 영어 단어)들을 사용할 때 중복성이 커서 다루기가 매우 쉽다. 만약 'inzect'라고 우연히 기록된 게 있다면, 지갑을 복구해야 할 때 'inzect'는 유효한 영어 단어가 아니므로 'insect'를 사용해야 한다고 빠르게 결정할 수 있다. 이것은 HD 지갑을 관리할 때 시드를 어떻게 보관해야 하는가와 연결된 문제다. 데이터 손실(사고 혹은 도난)이 나서 지갑을 복구하려면 시드가 필요하므로 백업은 매우 중요하다. 그래서 시드는 디지털 백업보다는 종이에 써서 보관할 것을 추천한다.

요약하면, HD 지갑의 인코딩을 위한 복구 단어 목록을 사용하는 것이 오류 없이 고쳐 쓰고, 종이에 기록하고, 읽고, 안전하게 내보내고, 개인키들을 다른 지갑으로 가져오는 가장 쉬운 방법이다.

2. 지갑의 모범 사례

암호화폐 지갑 기술이 성숙해짐에 따라 광범위하게 상호운용할 수 있고, 사용하기 쉽고, 안전하고, 유연한 지갑을 만들기 위한 일반적인 산업 표준이 등장했다. 또한 이러한 표준을 통해 지갑은 단일 니모닉에서 여러 개의 서로 다른 암호화폐에 대한 키를 파생시킬 수 있다. 이러한 일반적인 표준은 다음과 같다.

  • BIP-39 기반 니모닉 코드 단어
  • BIP-32 기반 HD 지갑
  • BIP-43 기반 다목적(multipurpose) HD 지갑 구조
  • BIP-44 기반 복수화폐(multicurrency) 및 복수계정(multiaccount) 지갑

이러한 표준은 향후 개발로 변경되거나 폐기될 수도 있지만, 현재 대부분의 블록체인 플랫폼과 암호화폐를 서로 연결해 주는 사실상의 기술적 지갑 표준으로 사용되고 있다.

이 표준은 소프트웨어 및 하드웨어 지갑에 광범위하게 채택되어 모든 지갑의 상호운용이 가능하게 되었다. 사용자는 이러한 지갑 중 하나에서 생성된 니모닉을 내보내고 다른 지갑으로 가져와서 모든 키와 주소를 복구할 수 있다. 이러한 표준을 지원하는 소프트웨어 지갑의 몇 가지 예로는 잭스(Jaxx), 메타마스크(MetaMask), 마이크립토(MyCrypto), 마이이더월렛(MyEtherWallet)이 있다. 이러한 표준을 지원하는 하드웨어 지갑의 예로는 킵키(Keepkey), 레저(Ledger) 및 트레저(Trezor)가 있다.

만약 이더리움 지갑을 구현하려면 BIP-32, BIP-39, BIP-43, BIP-44 표준을 따라 백업을 위해 니모닉 코드로 인코딩된 시드를 사용하여 HD 지갑을 구축해야 한다.

2-1. 니모닉 코드 단어(BIP-39)

니모닉 코드 단어는 결정적 지갑을 파생하기 위해 시드로 사용되는 난수를 인코딩하는 단어 시퀀스다. 단어 시퀀스는 시드를 다시 만들어내고, 이 시드로부터 지갑과 모든 파생된 키들을 재생성할 수 있다. 니모닉 단어와 함께 결정적 지갑을 구현한 지갑 애플리케이션은 지갑을 처음 만들 때 12~24개의 단어 시퀀스를 보여줄 것이다. 단어 시퀀스는 지갑의 백업으로, 동일하거나 호환 가능한 지갑 애플리케이션에서 모든 키를 복구하고 다시 생성하는 데 사용할 수 있다. 앞서 설명했듯이, 니모닉 단어 목록을 사용하면 사용자가 쉽게 읽을 수 있고 정확하게 바꿔쓸 수 있으므로 지갑을 백업하기가 더 쉽다.

니모닉 단어를 '브레인월렛(brainwallet)'과 혼동하는데, 이들은 서로 같지 않다. 브레인월렛은 사용자가 고른 단어로 구성되는 반면, 니모닉 단어는 지갑이 무작위로 생성해서 사용자에게 보여준다는 것이 가장 큰 차이점이다. 이런 중요한 차이점은 니모닉 단어를 좀 더 안전하게 만든다. 왜냐하면 인간은 무작위성에 아주 약하기 때문이다. 아마도 더 중요하게는 '브레인월렛'이란 용어를 사용한다는 건 암기해야 한다는 것으로, 이는 아주 좋지 않은 아이디어일 뿐만 아니라 필요한 백업을 하지 않게 만드는 나쁜 방법이다.

니모닉 코드는 BIP-39에 정의되어 있다. BIP-39가 니모닉 코드 표준의 한 가지뿐임을 기억할 필요가 있다. 일렉트럼 비트코인 지갑에서 BIP-39 이전에 사용한 다른 단어 세트인 다른 표준도 있다. BIP-39는 트래저(Trezor) 하드웨어 지갑을 지원하는 회사가 제안했으며, 일렉트럼 구현과 호환되지 않는다. 그러나 BIP-39는 현재 수십 개의 상호운용이 가능한 구현으로 광범위하게 산업계 전반에 걸쳐 지원받고 있으며, 사실상 업계 표준으로 고려되어야 한다. 더욱이 BIP-39는 일렉트럼 시드와 달리 이더리움을 지원하는 복수화폐 지갑을 생산하는 데 사용할 수 있다.

BIP-39는 니모닉 코드와 시드의 생성을 정의하는데, 여기서는 아홉 단계로 설명한다. 명확하게 하기 위해 1~6단계, 7~9단계로 프로세스를 두 부분으로 나누어 볼 수 있다.

니모닉 단어 생성

니모닉 단어는 BIP-39에서 정의한 표준화된 절차에 따라 지갑에서 자동으로 생성된다. 지갑은 엔트로피의 원천에서 시작해서 체크섬을 추가하고 단어 목록에 엔트로피를 매핑한다.

  1. 128~256비트의 무작위 암호화 시퀀스 S를 생성한다.
  2. S를 SHA-256으로 해싱한 값을 32비트로 나눈 처음 길이를 체크섬으로 생성한다.
  3. 무작위 시퀀스 S의 끝에 체크섬을 추가한다.
  4. 시퀀스와 체크섬을 연결한 것을 11비트 단위로 나눈다.
  5. 각각의 11비트 값을 사전에 정의된 2,048단어 사전과 매핑한다.
  6. 단어의 시퀀스로부터 순서를 유지하면서 니모닉 코드를 생성한다.

[그림 5-2]는 엔트로피를 사용해서 어떻게 니모닉 단어를 생성하는지 보여준다.

[그림 5-2] 엔트로피 생성과 니모닉 단어로 인코딩

아래 표는 엔트로피 데이터 크기와 니모닉 코드 길이 간의 관계를 보여준다.

엔트로피(비트)체크섬(비트)엔트로피 + 체크섬(비트)니모닉 길이(단어)
128413212
160516515
192619818
224723121
256826424

니모닉에서 시드까지

니모닉 단어는 128~256비트 길이의 엔트로피를 표현한다. 엔트로피는 키 스트레칭(key-stretching) 함수 PBKDF2를 사용하여 더 킨(512비트) 시드를 파생하는 데 사용되며, 생성된 시드는 결정론적 지갑을 구축하고 키를 파생하는 데 사용된다.

키 스트레칭 함수에는 니모닉과 솔트(salt)라는 두 가지 파라미터가 있다. 키 스트레칭 함수에서 솔트의 목적은 무차별 대입 공격을 가능하게 하는 조회 테이블(lookup table) 생성을 어렵게 하는 것이다. BIP-39 표준에서 솔트는 또 다른 목적을 갖는다. 솔트는 추가적인 보안 요소 역할을 하는 암호문 추가를 사용할 수 있게 해준다.

7단계부터 9단계까지 설명하는 절차는 앞 절에서 설명한 절차에서 이어진다.

  1. PBKDF2 키 스트레칭 함수의 첫 번째 파라미터는 6단계에서 생성된 니모닉이다.
  2. PBKDF2 키 스트레칭 함수의 두 번째 파라미터는 솔트이다. 솔트는 문자열 상수 "mnemonic"과 선택적으로 사용자가 지정한 암호문을 연결하여 구성한다.
  3. PBKDF2는 최종 출력으로 512비트 값을 만드는 HMAC-SHA512 알고리즘으로, 2048 해시 라운드를 사용하여 니모닉과 솔트 파라미터를 확장하며, 이 결과로 나온 512비트 값이 시드다.

[그림 5-3]은 니모닉이 어떻게 시드를 생성하는지를 보여준다.

[그림 5-3] 니모닉에서 시드까지

키 스트레칭 함수는 2048 해시 라운드로 니모닉 또는 암호문에 대한 무차별 대입 공격을 다소 효과적으로 막아준다. 수천 개 이상의 암호와 니모닉 조합을 시도하는 데는 비용(계산)이 많이 든다. 가능한 파생 시드의 수(2^512, 즉 약 10^154)는 방대하고 가시적인 우주의 원자 수(약 10^80)보다 훨씬 크다.

다음은 생성한 니모닉 코드와 시드의 3가지 예를 보여준다.

1. 128비트 엔트로피 니모닉 코드, 암호문 없음, 결과 시드

엔트로피 입력(128비트) :
0c1e24e5917779d297e14d45f14e1a1a

니모닉(12단어) :
army van defense carry jealous true garbage claim echo media make crunch

암호문 :
(없음)

시드(512비트) : 5b56c417303faa3fcba7e57400e120a0ca83ec5a4fc9ffba757fbe63fbd77a89a1a3be4c67196f57c39a88b76373733891bfaba16ed27a813ceed498804c0570

2. 128비트 엔트로피 니모닉 코드, 암호문 있음, 결과 시드

엔트로피 입력(128비트) :
0c1e24e5917779d297e14d45f14e1a1a

니모닉(12단어) :
army van defense carry jealous true garbage claim echo media make crunch

암호문 :
SuperDuperSecret

시드(512비트) : 3b5df16df2157104cfdd22830162a5e170c0161653e3afe6c88defeefb0818c793dbb28ab3ab091897d0715861dc8a18358f80b79d49acf64142ae57037d1d54

3. 256비트 엔트로피 니모닉 코드, 암호문 없음, 결과 시드

엔트로피 입력(128비트) :
2041546864449caff939d32d574753fe684d3c947c3346713dd8423e74abcf8c

니모닉(12단어) :
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비트) : 3269bce2674acbd188d4f120072b13b088a0ecf87c6e4cae41657a0bb78f5315b33b3a04356e53d062e55f1e0deaa082df8d487381379df848a6ad7e98798404

BIT-39 선택적 암호문

BIT-39 표준은 시드의 파생에 선택적 암호문을 사용할 수 있다. 암호문을 사용하지 않으면 니모닉은 상수 문자열 "mnemonic"과 함께 솔트를 구성하여 연장되고, 주어진 니모닉으로부터 특정한 512비트 시드를 생성한다. 만약 암호문을 사용한다면 스트래칭 함수는 동일한 니모닉 으로부터 '다른' 시드를 생성한다. 실제로, 단일 니모닉이 주어졌을 때 암호문이 다르면 다른 시드를 만들어낸다. 본질적으로 '잘못된' 암호문은 없다. 모든 암호문은 유효하며, 각각 다른 시드를 만들어내고, 가능한 한 초기화되지 않은 많은 지갑을 형성한다. 암호문이 충분히 복잡하고 길어서(2^512) 실제로 무차별 대입 혹은 우연히 사용 중인 것을 추측해 낼 가능성은 없다.

BIP-39에는 '잘못된' 암호문이 있을 수 없다. 모든 암호문은 별개의 지갑을 생성하게 되는데, 이전에 사용되지 않았던 것이라면 빈 지갑이 상태가 된다.

선택적 암호문은 두 가지 중요한 특성을 지닌다.

  • 니모닉 자체만으로는 의미가 없도록 만들어서, 니모닉 백업이 도난으로부터 보호될 수 있도록 하는 2차 팩터(추가적으로 기억해야 하는)로 기능한다.
  • 공격자의 협박 때문에 암호문을 가르쳐 줘야 할 경우는 진짜 암호문 대신 그럴 듯한 가짜 암호문을 제공한다. 그러면 대부분의 자금을 담고 있는 '진짜' 지갑 대신 적은 양의 자금이 있는 지갑으로 공격자의 주의를 돌릴 수 있다.

그러나 암호문의 사용은 손실의 위험 또한 가져온다는 점을 주목해야 한다.

  • 만약 지갑의 주인이 의식을 잃었거나 사망했고 암호문을 알고 있는 사람이 없다면, 시드는 쓸모없어지고 지갑에 저장된 모든 자금을 영원히 잃게 된다.
  • 반대로, 소유자가 암호문을 시드와 동일한 위치에 백업하는 것은 2차 팩터를 사용하는 목적에 어긋난다.

암호는 매우 유용하지만 상속인이 암호화폐를 복구할 수 있는 가능성을 고려해야 하므로, 신중하게 계획된 백업 및 복구 프로세스와 함께 사용해야 한다.

니모닉 코드로 작업하기

BIP-39는 여러 가지 프로그래밍 언어의 라이브러리로 구현되어 있다. 예를 들면 다음과 같다.

파이썬 니모닉(python-mnemonic)

  • 파이썬으로 BIP-39를 제안한 사토시랩(SatoshiLabs) 팀의 표준참조 구현

컨센시스/이더-라이트월렛(ConsenSys/eth-lightwallet)

  • 노드와 브러우저용 경량 JS 이더리움 지갑(BIP-39 포함)

npm/bit39

  • 비트코인 BIP-39의 자바스크립트 구현 : 결정적 키 생성용 니모닉 코드

테스트와 실습에 아주 유용한 독립형(standalone) 웹 페이지로 구현된 BIP-39 생성기가 있다. 니모닉 코드 변환기는 니모닉(Mnemonicd Code Converter), 시드, 그리고 확장된 개인키를 생성한다. 또한 브라우저에서 오프라인으로 사용하거나 온라인으로 접속할 수 있다.

[그림 5-4] 독립형 웹 페이지 BIP-39 생성기

2-2. 시드로 HD 지갑 생성하기

HD 지갑은 128, 256 또는 512비트의 임의의 숫자인 단일 루트 시드(root seed)로 만든다. 일반적으로 앞 절에서 상세하게 설명했던 니모닉 시드를 생성한다.

HD 지갑의 모든 키는 루트 시드에서 결정적으로 파생되었으며, 모든 호환 HD 지갑에서 그 시드로부퍼 전체 HD 지갑을 재생성할 수 있다. 이것은 루트 시드를 파생시킨 니모닉을 전송하는 것만으로도 수천 혹은 수백만 개의 키가 포함된 HD 지갑의 내보내기, 백업, 복원, 가져오기를 쉽게 만든다.

2-3. HD 지갑(BIP-32)과 경로(BIP-43/44)

대부분의 HD 지갑은 결정적 키 생성을 위한 산업계의 사실상 표준인 BIP-32 표준을 따른다.

여기서 BIP-32의 세부적인 부분까지 논의하지는 않을 것이나, 지갑에서 어떻게 사용되는지 이해하는 데 필요한 구성요소만 살펴볼 것이다. 가장 중요한 측면은 [그림 5-1]에서 볼 수 있듯이 파생된 키가 가질 수 있는 트리 같은 계층적인 관계다. 또한 다음 절에서 설명하는 확장 키(extended key)강화 키(hardened key)의 아이디어를 이해하는 것이 중요하다.

상호운용이 가능한 수십 가지의 BIP-32 구현은 많은 소프트웨어 라이브러리에서 제공하고 있다. 이들은 대부분 비트코인 지갑용으로 설계되었다. 비트코인은 이더리움과 다른 방법으로 주소를 구현하지만, BIP-32 호환 가능 지갑과 동일한 키 파생(key-derivation) 구현을 공유한다. 이더리움을 위해 설계된 지갑을 사용하거나 이더리움 주소 인코딩 라이브러리를 추가하여 비트코인에서 채택한 것을 사용하라.

BIP-32로 테스트하고 실습하기에 아주 유용한 독립형 웹 페이지로 구현된 BIP-32 생성기가 있다.

독립형 BIP-32 생성기는 HTTPS 사이트가 아니다. 그것은 이 도구를 사용하는 것이 안전하지 않다는 사실을 상기시켜 주는 것이다. 오직 테스트용이므로, 이 사이트에서 생성된 키를 실제 자금에 사용해선 안 된다.

확장된 공개키와 개인키

BIP-32의 용어로 말하자면, 키는 '확장(extended)'될 수 있다. 적절한 수학적 연산을 사용하여 확장된 '부모(parent)' 키는 '자식(child)' 키를 파생시킬 수 있게 되고, 앞서 설명한 키와 주소의 계층 구조를 만들 수 있게 된다. 맨 앞에 있을 필요는 없으며, 트리 계층 구조의 어느 곳에서든 부모 키를 선택할 수 있다. 키를 확장하는 것은 키 자체를 가져와서 특수 체인 코드(chain code)를 추가하는 것이다. 체인 코드는 자식 키를 생성하기 위해 각 키와 혼합된 256비트 이진 문자열이다.

만약 키가 개인키이면 접두어 xprv로 구분되는 확장된 개인키(extended private key)가 된다.

xprv9s21ZrQH143K2JF8RafpqtKiTbsbaxEeUaMnNHsm5o6wCW3z8ySyH4UxFVSfZ8n7ESu7fgir8i...

확장된 공개키(extended public key)는 접두어 xpub으로 구분된다.

xpub661MyMwAqRbcEnKbXcCqD2GT1di5zQxVqoHPAgHNe8dv5JP8gWmDproS6kFHJnLZd23tWevhdn...

HD 지갑의 매우 유용한 특징은 개인키가 없는 부모 공개키에서 자식 공개키를 파생할 수 있는 능력이다. 자식 공개키를 파생하기 위한 방법은 두 가지가 있다. 자식 개인키로부터 직접 파생하는 방법과 부모 공개키로부터 직접 파생하는 방법이다.

따라서 확장된 공개키는 HD 지갑 구조의 해당 분기(branch)에서 모든 공개키(그리고 단지 공개키들만)를 파생하는 데 사용될 수 있다.

이런 방법은 매우 안전한 공개키 전용 배포를 만드는 데 사용할 수 있다. 여기서 서버 또는 애플리케이션에는 확장된 공개키의 사본이 있지만 개인키는 없다. 이러한 배포는 무한한 수의 공개키와 이더리움 주소를 생성할 수 있지만, 그 주소로 보낸 돈은 쓸 수 없다. 한편 다른 보안 서버에서 확장된 개인키는 트랜잭션에 서명하고 돈을 사용하기 위해 관련된 모든 개인키를 파생할 수 있다.

이러한 방법의 일반적인 애플리케이션 중 하나는 전자상거래 애플리케이션을 제공하는 웹 서버에 확장된 공개키를 설치하는 것이다. 웹 서버는 공개키 파생 함수를 사용하여 모든 트랜잭션(예: 고객 쇼핑 카트)에 대한 새로운 이더리움 주소를 만들 수 있는 반면, 도난에 취약한 개인키는 가지고 있을 필요가 없다. HD 지갑 없이 이를 수행하는 유일한 방법은 별도의 보안 서버에서 수천 개의 이더리움 주소를 생성한 후에 전자상거래 서버에 미리 로드(preload)하는 것이다. 이 접근법은 번거럽고 서버의 키가 다 떨어지지 않도록 지속적인 유지보수가 필요하므로 HD 지갑의 확장된 공개키르 사용하는 것이 좋다.

이 솔루션의 또 다른 일반적인 애플리케이션은 콜드 스토리지(cold-storage) 또는 하드웨어 지갑이다. 이 시나리오에서 확장된 개인키는 하드웨어 지갑에 저장할 수 있으며, 확장된 공개키는 온라인으로 보관할 수 있다. 사용자는 '수신(receive)' 주소를 자유롭게 만들 수 있으며, 개인키는 오프라인으로 안전하게 저장된다. 자금을 사용하기 위해 사용자는 오프라인 서명 이더리움 클라이언트에서 확장된 개인키를 사용하거나 하드웨어 지갑 장치에서 트랜잭션을 서명할 수 있다.

강화된 자식 키의 파생

확장된 공개키 또는 xpubㅇ로부터 공개키의 분기를 파생하는 능력은 매우 유용하지만 잠재적인 위험이 있다. xpub에 대한 접근이 자식 개인키에 대한 접근을 제공하지 않는다. 그러나 xpub이 체인 코드(부모 공개키에서 자식 공개키를 파생하는 데 사용)를 포함하므로, 만약 하위 개인키가 알려져 있거나 유출된 경우 다른 모든 자식 개인키를 파생시키기 위한 체인 코드로 될 수 있다. 유출된 하나의 자식 개인키와 부모 체인 코드는 모든 자식의 개인키를 노출시킬 수 있다. 더욱이, 부모 체인 코드와 함께 자식 개인키를 사용하여 부모 개인키를 추론할 수 있다.

이러한 위험에 대응하기 위해 HD 지갑은 강화 파생(hardened derivation)이라고 하는 대체 가능 파생 함수를 사용한다. 이 파생 하수는 부모 공개키와 자식 체인 코드 간의 관계를 '끊는다(break)'. 강화 파생 함수는 자식 체인 코드를 파생하기 위해 부모 공개키 대신에 부모의 개인키를 사용한다. 이것은 체인 코드를 이용하여 부모/자식 시퀀스 '방화벽'을 생성하며, 이때 체인 코드는 부모 또는 형제 개인키를 유출하는 데 사용할 수 없다.

간단히 말해서, 유출된 체인 코드의 위험에 노출되지 않고 편리하게 xpub을 이용해 공개키의 분기를 파생하기 위해서는 일반적인 부모가 아닌 강화된 부모로 공개키 분기를 파생해야 한다. 마스터 키의 유출을 방지하기 위해서는 항상 강화 파생으로 파생된 마스터 키의 1단계 자식 사용을 강력히 추천한다.

일반 및 강화 파생을 위한 인덱스 번호

하나의 부모 키에서 여러 개의 자식 키를 파생할 수 있다. 이를 관리하기 위해 인덱스 번호를 사용한다. 각 인덱스 번호는 특수한 자식 파생 함수를 사용해 부모 키와 결합될 때 각각 다른 자식 키를 만들어낸다. BIP-32 부모-자식 파생 함수에 사용하는 인덱스 번호는 32비트 정수다. 강화 파생을 통해 파생된 키와 일반(비강화된) 파생 함수를 통해 파생된 키를 쉽게 구별하기 위해 인덱스 번호는 두 범위로 나뉜다. 0과 2^31 - 1 사이의 인덱스 번호(0x0 ~ 0x7FFFFFFF)는 오직 일반 파생을 위해서만 사용된다. 2^31과 2^32 - 1 사이의 인덱스 번호(0x80000000 ~ 0xFFFFFFFF)는 오직 강화 파생에만 사용된다. 그러므로 인덱스 번호가 2^31보다 작으면 자식은 일반이고, 반면에 인덱스 번호가 2^31 이상이면 자식은 강화된다.

인덱스 번호를 좀 더 읽기 쉽도록 표시하기 위해 강화된 자식 인덱스 번호가 소수 기호이지만, 0부터 시작하는 것으로 표현된다. 따라서 첫 번째 일반 자식 키는 0으로 표시되지만, 첫 번째 강화된 자식(인덱스 0x80000000)은 0'으로 표시된다. 순서대로 두 번째 강화된 키는 0x80000001의 인덱스를 가지며 1'로 표시된다. HD 지갑 인덱스 i'가 표시되면 이는 2^31 + i를 의미한다.

HD 지갑 키 식별자

HD 지갑의 키는 '경로(path)' 이름 규칙을 사용하여 식별하며, 트리의 각 레벨은 슬래시(/) 문자로 구분한다. 마스터 개인키에서 파생된 개인키는 m으로 시작하며, 마스터 공개키에서 파생된 공개키는 M으로 시작한다. 따라서 마스터 개인키의 첫 번째 자식 개인키는 m/0이며, 첫 번째 자식 공개키는 M/0이다. 첫 번째 자식의 두 번째 자식은 m/0/1이고, 나머지도 마찬가지다.

HD 지갑 경로 예제

HD 경로키 설명
m/0마스터 개인키(m)의 첫 번째(0) 자식 개인키
m/0/0첫 번째 자식(m/0)의 첫 번째 자식 개인키
m/0'/0첫 번째 강화된 자식(m/0')의 첫 번째 일반 자식
m/1/0두 번째 자식(m/1)의 첫 번째 자식 개인키
M/23/17/0/024번째 자식의 18번째 자식의 첫 번째 자식의 첫 번째 자식 공개키

키의 '조상(ancestry)'은 키를 파생한 마스터 키에 도달하 때까지 오른쪽에서 왼쪽으로 읽는다. 예를 들어, m/x/y의 z번째 자식 키는 식별자 m/x/y/z, m/x의 y번 째 자식키는 m/x/y, m의 x번째 자식은 m/x로 표현한다.

HD 지갑 트리 구조 탐색

HD 지갑 트리 구조는 대단히 유연하다. 뒤집어 말하면, 무한한 복잡성을 허용한다는 뜻이다. 각 부모의 확장 키는 40억 개의 자식을 가질 수 있다(20억 개의 정상적인 자식들과 20억 개의 강화된 자식들). 그 자식들은 각각 40억 개의 자식들을 가질 수 있다. 트리는 여러분이 원하는 만큼 깊을 수 있으며 무한한 세대가 될 수 있다. 이와 같은 잠재력 때문에 매우 큰 트리를 탐색하는 일이 상당히 어려워질 수 있다.

두 가지 BIP는 HD 지갑 구조의 표준을 만들어 잠재적인 복잡성을 관리할 수 있는 방법을 제공한다. BIP-43은 강화된 첫 번째 자식 인덱스를 트리 구조의 '목적(purpose)'을 나타내는 특수 식별자로 사용하도록 제안한다. BIP-43을 기반으로 한 HD 지갑은 트리의 구조와 나머지 레벨의 네임스페이스를 식별하여, 지갑의 목적을 정의하는 인덱스 번호와 함께 트리 레벨 1 분기만 사용해야 한다. 좀 더 구체적으로 말하자면, m/i'/... 분기만을 사용하는 HD 지갑은 특정 목적을 나타내기 위한 것이고, 그 목적은 인덱스 번호 i로 식별된다.

이 사양을 확장하여 BIP-44는 '목적' 번호를 44'로 설정하여 복수화폐 복수계정 구조를 제안한다. BIP-44를 따르는 모든 HD 지갑 구조는 단지 하나의 트리 분기(m/44'/*)만을 사용한다는 사실에 의해 식별된다.

BIP-44는 미리 정의된 다섯 가지 트리 레벨로 구성된 구조를 지정한다.

m / purpose' / coin_type' / account' / change' / address_index'

첫 번째 레벨인 purpose'는 항상 44'로 설정하는 것이다. 두 번째 레벨인 coin_type'은 암호화폐 동전의 유형을 지정하며, 각 통화가 두 번째 레벨 아래에 자체 하위 트리를 갖는 화폐 HD 지갑을 허용하는 것이다. SLIP0044라는 표준 문서에는 여러 화폐가 정의되어 있다. 예를 들어 이더리움은 m/44'/60'이고, 이더리움 클래식은 m/44'/61', 비트코인은 m/44'/0', 모든 화폐의 테스트넷은 m/44'/1'이다.

트리의 세 번째 레벨은 account'이며, 사용자는 지갑을 화폐 또는 조직 목적을 위한 별도의 논리적 하위 계좌로 세분화할 수 있다. 예를 들어, HD 지갑에는 2개의 이더리움 '계정'(m/44'/60'/0', m/44'/60'/1')을 포함할 수 있다. 각 계정은 자체 하위 트리의 루트다.

BIP-44는 원래 비트코인을 위해 제작되었기 때문에 이더리움 세계와 관련이 없는 '특이점(quirk)'이 포함되어 있다. 경로의 네 번째 레벨인 change에서, HD 지갑에는 2개의 하위 트리가 있다. 하나는 입금 주소 작성용이고, 다른 하나는 잔액 주소 작성용이다. 이더리움은 비트코인에 있는 잔액 주소가 필요하지 않으므로 단지 '입금(receive)' 경로만 사용한다. 이전 레벨은 강화 파생을 사용했지만, 이 레벨은 일반 파생을 사용한다는 사실을 알아두자. 이는 비보안 환경에서 사용할 수 있도록 확장된 공개키를 트리의 계정 수준에서 내보낼 수 있게 한다. 사용 가능한 주소는 네 번째 레벨의 자식으로서 HD 지갑에서 파생된다. 트리의 다섯 번째 레벨을 address_index로 만드는 것이다. 예를 들어, 주 메인 계정에서 이더리움 지급을 위한 세 번째 입금 주소는 M/44'/60'/0'/0/2가 될 것이다.

BIP-44 HD 지갑 구조의 예

HD 경로키 설명
M/44'/60'/0'/0/2메인 이더리움 계정에 대한 세 번째 수신 공개키
M/44'/0'/3'/1/144번째 비트코인 계정으 ㅣ15번째 주소 변경 공개키
m/44'/2'/0'/0/1트랜잭션 서명을 위한 라이트코인 메인 계정의 두 번째 개인키

3. 결론

지갑은 사용자를 상대하는 모든 블록체인 애플리케이션의 기본이다. 지갑을 이용하여 사용자는 키와 주소들을 관리한다. 또한 지갑을 사용하면 사용자가 이더의 소유권을 입증하고 디지털 서명을 적용하여 트랜잭션을 승인할 수 있다.

profile
블록체인 개발 어때요

0개의 댓글