열쇠가 있어도 금고는 찾을 수 없다
시드 구문이 되살리는 것은 키뿐이다. 그 키로 어떤 주소를 만들지는 출력 디스크립터라는 한 줄이 정한다. 같은 키 세 개로 첫 주소가 다섯 갈래로 갈리는 실험, 디스크립터를 읽는 법과 여덟 글자 체크섬, 멀티시그에서 임계치만큼의 시드로도 금고를 못 여는 경우, 그리고 백업하는 법까지 정리한다.
개인키를 직접 다루기 시리즈 6/8편
- 단어 12개에 평생 모은 비트코인이 전부 담긴다
- 이 12개 단어를 잃어버리면, 수억 원이 영원히 사라집니다
- 인터넷에 연결된 지갑 vs 금고 속 지갑
- Coldcard·Jade·Trezor·Ledger 보안 모델 비교
- 하드웨어 지갑은 이전 거래 전체를 요구한다
- 열쇠가 있어도 금고는 찾을 수 없다
- 코인을 옮기지 않고 하는 소유 증명
- 내가 갑자기 죽으면, 내 비트코인은 어떻게 되는가
시드 구문 세 개가 있다. 하나는 모든 비트코인 지갑의 테스트 벡터로 쓰이는 abandon으로 시작하는 구문이고, 나머지 둘도 BIP-39 테스트 벡터에 실린 공개 구문이다. 셋에서 각각 멀티시그용 확장 공개키를 뽑아 둘만 서명하면 되는 2-of-3 금고를 만든다. 열쇠는 같은 세 개다. 그런데 이 금고의 첫 번째 받는 주소는 하나가 아니다.
| 조립 방식 | 첫 주소 |
|---|---|
| 네이티브 세그윗, 키 정렬 | bc1qm43n7nnev58aj3nrznz2xscgv98t7gxycq5pmp20a5vzfp5t0q2s7r6twa |
| 네이티브 세그윗, 키 순서 A·B·C | bc1qp38kn2cpud9u5z8px3ga9vtuwdp3n0cc6xly6td45u5g6nsgcl9sagut92 |
| 네이티브 세그윗, 키 순서 C·B·A | bc1qlklm7s3d48twp5rj4q39shf74ccfty96nyqc5gvm9y2fzxrdrdcs8t36am |
| 감싼 세그윗, 키 정렬 | 3HTVa2FKu5zq9e95qEoJX8Cy5vBo5y9Ljc |
| 레거시 P2SH, 키 정렬 | 39tmmWUjbK9SeTK4L6vjZi7kEkPx4DjcJb |
다섯 주소는 Bitcoin Core 31.1과 다른 라이브러리 하나로 따로 계산해 같은 값을 얻었다. 바뀐 것은 열쇠가 아니라 조립법이다. 스크립트를 어떤 형식으로 감쌌는지, 키를 정렬했는지 나열한 순서대로 넣었는지. 키 순서만 해도 여섯 가지가 나온다.
이제 이 금고의 열쇠 하나를 맡은 사람이 화재로 시드를 잃었다고 하자. 남은 시드는 둘이다. 2-of-3이니 서명은 충분하다. 그런데 셋째 사람의 공개키가 없으면 위의 어느 주소도 다시 계산할 수 없다. 주소는 세 공개키를 모두 넣은 스크립트의 해시이기 때문이다. 코인을 쓸 수 있는 서명이 손에 있는데 코인이 어디 있는지 모르고, 안다 해도 쓸 때 내야 하는 스크립트를 만들 수 없다.
이 빈자리를 채우는 것이 출력 디스크립터다. 어떤 키를, 어떤 경로로, 어떤 스크립트에 넣어 주소를 만드는지 적은 한 줄의 문자열이다.
단어가 되살리는 것은 키뿐이다
시드 구문은 개인키 나무의 뿌리를 담는다. 그 뿌리에서 무한히 많은 키를 다시 길러 낼 수 있지만, 그 키들로 어떤 주소를 만들어 왔는지는 단어 어디에도 적혀 있지 않다.
디스크립터를 규정한 BIP-380의 동기 절은 이 문제에서 시작한다. 지갑의 백업은 보통 개인키뿐이었고 지금은 주로 BIP-39 니모닉의 형태인데, 세그윗이 새로운 출력 유형을 더한 뒤로 이 백업만으로는 부족해졌다. 개인키만 주어지면 복원된 지갑은 어떤 종류의 스크립트와 주소를 만들어야 하는지 알 수 없고, 그것이 지갑끼리 백업을 복원하거나 감시 전용 지갑으로 내보낼 때의 비호환을 낳았다는 것이다.
파생 경로도 같은 문제를 키운다. BIP-44, 49, 84가 출력 유형마다 표준 경로를 정했지만 모든 지갑이 그 경로를 지원하거나 쓰는 것은 아니다. 같은 12단어를 다른 지갑에 넣었더니 잔고가 0으로 나오는 일은 대개 여기서 생긴다. 키는 맞는데 지갑이 다른 경로, 다른 주소 유형을 찾아본 것이다.
이 공백을 처음부터 의식한 지갑도 있었다. Electrum은 시드 구문에 버전 번호를 넣는다. 구문을 해시한 값의 앞자리가 어떤 파생 방식을 따를지 알려 주는 구조다. Electrum 문서는 BIP-39를 이렇게 비판한다. BIP-39 시드에는 버전 번호가 없으니 소프트웨어가 키와 주소를 만드는 법을 언제나 알고 있어야 하고, 여러 파생 방식을 하나씩 시도해 보는 것은 극도로 비효율적이며 미래의 지갑이 과거의 방식을 모두 지원한다는 가정에 기댄다는 것이다.
또 다른 우회로는 확장 공개키의 앞머리를 바꾸는 것이었다. xpub 대신 ypub, zpub라고 쓰면 감싼 세그윗인지 네이티브 세그윗인지 알 수 있게 하는 방식이다. BIP-380은 이를 계층 위반이라고 부른다. 키를 파생하는 일과 스크립트 유형의 의미는 분리되어야 하는데 둘을 한 덩어리로 묶었고, 그나마 특정 경로와 특정 스크립트 유형에만 통한다는 이유다.
디스크립터 한 줄 읽기
디스크립터는 이 정보를 사람이 읽을 수 있는 짧은 언어로 적는다. abandon 테스트 구문의 BIP-84 계정은 이렇게 쓴다.
wpkh([73c5da0a/84h/0h/0h]xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V/0/*)#afwvtk2s
바깥의 wpkh(...)는 스크립트 식이다. 공개키 하나의 해시로 잠그는 네이티브 세그윗 출력을 만들라는 뜻이다. 대괄호 안의 73c5da0a는 이 키가 출발한 마스터 키의 지문 여덟 자리이고, 뒤의 84h/0h/0h는 거기서 이 xpub까지 내려온 경로다. h는 강화 파생 표시로, 오래된 표기에서는 아포스트로피를 쓴다. xpub 뒤의 /0/*는 이 xpub에서 0번 가지의 모든 자식을 차례로 쓰라는 뜻이고, 끝의 #afwvtk2s는 체크섬이다.
이 디스크립터의 첫 주소는 bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu로, BIP-84 문서의 테스트 벡터와 같다. 바깥 함수만 바꾸면 같은 xpub에서 전혀 다른 주소가 나온다.
| 스크립트 식 | 첫 주소 |
|---|---|
pkh(...) | 1JaUQDVNRdhfNsVncGkXedaPSM5Gc54Hso |
sh(wpkh(...)) | 3GtVZYzsKF6Feikdjd4bDyPdAiyeHANY9b |
wpkh(...) | bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu |
tr(...) | bc1p8knh0enfv47gmpuf66528zd4jtkgjq4sv5w5l2gqwgk8exu2ynns9g8c9m |
주소 형식의 차이가 키의 차이가 아니라 이 바깥 함수의 차이라는 것이 한눈에 보인다. 디스크립터는 주소 하나가 아니라 주소의 목록 전체를 기술하고, 그 목록을 만드는 데 필요한 것을 빠짐없이 담는다. 그래서 개인키 없이 공개 정보만으로 된 디스크립터를 넣으면 잔고와 내역을 보는 감시 전용 지갑이 된다.
지갑은 받는 주소와 거스름돈 주소를 다른 가지에서 만든다. 관례로 0번이 받는 쪽, 1번이 거스름돈 쪽이다. 예전에는 디스크립터를 두 줄 적어야 했지만, BIP-389는 /<0;1>/*처럼 두 가지를 한 줄에 쓰는 표기를 제안했다. 문서는 아직 초안 상태이지만 Bitcoin Core는 29.0부터 이 표기를 받아들여 받는 쪽과 거스름돈 쪽 두 디스크립터로 펼쳐 저장한다.
멀티시그에서 빠지면 안 되는 것
멀티시그 디스크립터는 이 구조에 키를 여러 개 넣은 것이다. 처음의 2-of-3 금고는 이렇게 생겼다.
wsh(sortedmulti(2,[73c5da0a/48h/0h/0h/2h]xpub6DkF.../<0;1>/*,[b8688df1/48h/0h/0h/2h]xpub6FQy.../<0;1>/*,[28645006/48h/0h/0h/2h]xpub6DnE.../<0;1>/*))
줄인 부분은 각각 111자의 xpub이다. wsh는 네이티브 세그윗 스크립트 해시, 2는 필요한 서명 수, 그 뒤가 세 서명자의 키다. BIP-383에 따르면 xpub가 여럿이면 세 키는 같은 자식 번호로 나란히 파생된다. 5번 주소는 세 xpub 각각의 5번 자식 공개키로 만든다.
sortedmulti와 multi의 차이가 첫 표에서 주소를 갈랐다. multi는 디스크립터에 적힌 순서대로 키를 스크립트에 넣고, sortedmulti는 파생된 공개키를 사전순으로 정렬한 뒤 넣는다. 정렬 방식은 2015년 BIP-67이 P2SH 멀티시그 주소를 결정적으로 만들기 위해 정한 것이다. 정렬하면 키를 어떤 순서로 입력했는지 기억할 필요가 없고, 정렬하지 않으면 순서까지 백업해야 한다. 스크립트 식에 따라 넣을 수 있는 키의 수도 다르다. sh 안에서는 스크립트 원소 하나의 520바이트 한도 때문에 압축 공개키 15개까지이고, wsh 안에서는 20개까지다.
이제 처음의 질문으로 돌아가자. 시드를 잃은 사람이 있어도 디스크립터가 남아 있으면 남은 두 사람은 금고를 연다. 디스크립터에 셋째 xpub가 들어 있으니 주소를 모두 다시 만들 수 있고, 남은 두 시드로 서명하면 된다. 디스크립터가 없으면 셋째 공개키를 되살릴 길이 없다. 이미 한 번 돈을 꺼낸 주소라면 블록체인에 그 주소의 스크립트가 남아 세 공개키가 보이지만, 그것은 그 주소 하나의 자식 키일 뿐 다른 주소들의 키가 아니다.
거꾸로 세 시드가 모두 남아 있다면 디스크립터 없이도 대개 되찾을 수 있다. BIP-48 같은 표준 경로와 흔한 스크립트 유형, 정렬 여부를 조합해 차례로 시도하면 된다. 비효율적이지만 가능하다. 디스크립터가 정말로 결정적인 순간은 임계치만큼만 남았을 때다. 멀티시그가 대비하려던 바로 그 상황이다.
여덟 글자의 체크섬
디스크립터 끝의 # 뒤 여덟 글자는 bech32 주소와 같은 문자 집합을 쓰는 오류 검출 부호다. BIP-380은 성질을 이렇게 밝힌다. 기호 하나가 틀리면 항상 잡는다. 기호 두세 개가 틀려도 49,154자 이하의 디스크립터라면 항상 잡는다. 무작위로 망가졌을 때 검출을 빠져나갈 확률은 2의 40제곱분의 1이다.
체크섬은 키와 경로의 숫자까지 지킨다. 앞의 디스크립터에서 계정 경로 0h 하나를 1h로 바꿔 Bitcoin Core에 넣으면 이렇게 거부된다.
Provided checksum 'afwvtk2s' does not match computed checksum 'mu5cevdr'
손으로 옮겨 적은 디스크립터에 오타가 있으면 지갑이 받아들이기 전에 알려 준다는 뜻이다. 반대로 체크섬은 글자를 그대로 본다. 같은 경로를 84'/0'/0'로 쓰면 체크섬은 wc3n3van이 되고, 84h/0h/0h로 쓰면 afwvtk2s가 된다. Bitcoin Core는 26.0부터 아포스트로피 대신 h를 출력하면서 이 변경이 체크섬을 바꾼다고 릴리스 노트에 적었다. 디스크립터는 해석해서 옮기지 말고 지갑이 내보낸 문자열을 그대로 보관한다.
0.17에서 30.0까지
디스크립터는 표준 문서보다 구현이 먼저였다. 2018년 10월 3일 나온 Bitcoin Core 0.17.0에서 Pieter Wuille가 만든 디스크립터가 scantxoutset 명령의 인자로 처음 쓰였고, 언어를 설명하는 참조 문서가 함께 들어갔다. 2021년 1월 14일의 0.21.0은 디스크립터로 출력을 관리하는 새 지갑 형식을 실험 기능으로 넣었다. BIP 번호 380부터 386까지가 Wuille와 Ava Chow의 이름으로 배정된 것은 그해 6월 27일이다. 구현이 나오고 3년 가까이 지나서다.
2022년 4월 25일 23.0부터 새로 만드는 지갑은 기본으로 디스크립터 지갑이 되었고, 탭루트 단일서명 주소를 위한 tr() 디스크립터가 자동으로 들어갔다. 2025년 10월 10일 30.0은 마지막 단계를 밟았다. 예전 형식의 레거시 지갑은 더 이상 만들 수도 열 수도 없고, migratewallet으로 디스크립터 지갑으로 옮기는 길만 남았다. Bitcoin Core 안에서 키 묶음이 지갑이던 시대는 끝났고, 지갑은 이제 디스크립터의 목록이다.
하드웨어 지갑은 무엇을 확인하나
디스크립터가 위조되면 무슨 일이 생기는가. 멀티시그 설정 절차를 표준화한 BIP-129는 위험을 둘로 나눈다. 설정을 바꿀 수 있는 공격자는 사용자를 속여 엉뚱한 주소로 돈을 보내게 해서 훔치거나 인질로 잡을 수 있다. 설정을 바꾸지는 못하고 엿볼 수만 있는 공격자는 지갑의 거래를 지켜볼 수 있어 프라이버시가 무너진다.
첫째 위험이 하드웨어 지갑의 문제다. 컴퓨터가 감염되어 있다면 컴퓨터가 보여 주는 멀티시그 주소를 믿을 이유가 없다. 셋째 키를 공격자의 것으로 바꾼 디스크립터라면 금고는 여전히 2-of-3처럼 보이지만 공격자가 다른 서명자 하나만 속이면 열린다. BIP-388은 이를 지갑 정책 등록으로 푼다. 처음 쓸 때 하드웨어 지갑이 자기 화면에 정책과 모든 키를 보여 주고, 사용자는 인쇄해 둔 백업 같은 믿을 수 있는 원본과 대조해 승인한다. 그 뒤로 기기는 등록된 정책으로만 주소를 만들고 거스름돈을 판별한다. PSBT에서 기기가 거스름돈 출력을 자기 것으로 확인하는 과정이 멀티시그에서 성립하려면 이 등록이 먼저 있어야 한다.
BIP-388은 xpub를 @0, @1 같은 자리표시로 빼서 화면에 보여 줄 양을 줄인다. 문서가 밝힌 구현은 Ledger의 비트코인 앱 2.1.0, BitBox02 펌웨어 9.15.0, Blockstream Jade 1.0.24 이후다.
시간이 들어간 디스크립터
디스크립터 언어는 확장되도록 설계됐다. 그 확장의 큰 갈래가 미니스크립트다. BIP-379로 정리 중인 이 언어는 비트코인 스크립트의 부분집합을 구조적으로 적어, 지갑이 어떤 조건으로 코인을 쓸 수 있는지 분석하고 서명하게 해 준다. Bitcoin Core는 25.0에서 P2WSH 미니스크립트 출력을 쓸 수 있게 했고 26.0에서 탭루트 안으로 넓혔다.
이 조합으로 디스크립터 한 줄에 시간 조건이 들어간다. 평소에는 내 키 하나로 쓰고, 코인이 일정 기간 움직이지 않으면 가족의 키로도 쓸 수 있는 식이다. Wizardsardine이 2022년 말 처음 내놓은 Liana 지갑이 이 구조를 쓴다. 미니스크립트의 older(), 곧 BIP-112의 상대 타임락으로 복구 키를 잠그고, 시계는 코인마다 그 코인을 받은 블록에서 출발한다. 그래서 정해진 블록 수 안에 한 번씩 코인을 옮겨 시계를 되돌려야 한다.
이런 지갑에서 디스크립터는 백업의 보조물이 아니라 본체다. 시간 조건과 상속인의 키가 디스크립터에만 적혀 있기 때문이다. Liana의 사용 문서도 스크립트 틀인 디스크립터와 개인키를 둘 다 백업해야 하며, 디스크립터가 없으면 백업으로 복구할 수 없다고 적는다.
백업은 어떻게 하나
디스크립터는 비밀이 아니면서 공개해서도 안 되는 정보다. 개인키가 들어 있지 않으니 디스크립터만으로는 코인을 쓸 수 없다. 그러나 xpub가 모두 들어 있으니 디스크립터를 본 사람은 금고의 모든 주소와 잔고와 내역을 본다. 시드만큼 단단히 숨길 필요는 없지만 아무 데나 올려 둘 것도 아니다.
Sparrow 지갑은 정족수가 전체보다 작은 멀티시그를 저장할 때 경고창을 띄운다. 이 지갑을 복원하려면 적어도 정족수만큼의 시드와 모든 xpub가 필요하다는 내용이고, xpub를 위해 지갑 파일이나 출력 디스크립터를 백업하라고 권한다. 그 창에서 디스크립터를 QR이 담긴 PDF로 저장할 수 있다.
멀티시그라면 디스크립터 사본을 열쇠마다 하나씩 둔다. 시드 구문을 보관한 곳마다 디스크립터를 함께 두면 어느 열쇠가 사라져도 남은 열쇠들 곁에 지도가 있다. 디스크립터를 한 곳에만 두면 그곳이 새 단일 장애점이 된다. 멀티시그를 쓰는 이유를 생각하면 앞뒤가 맞지 않는다.
보관한 디스크립터는 한 번 검증해 둔다. 다른 지갑 소프트웨어에 감시 전용으로 불러와 첫 받는 주소가 쓰던 지갑의 첫 주소와 같은지 본다. 체크섬은 옮겨 적을 때의 오타를 잡아 주지만 처음부터 잘못된 디스크립터를 잡지는 못한다. 주소가 같으면 그 지도가 맞다. BIP-129가 정한 멀티시그 설정 기록이 넷째 줄에 지갑의 첫 주소를 적게 한 것도 같은 이유다.
상속 계획에서도 같다. 상속인에게 남기는 것이 시드 구문뿐이라면 상속인은 키를 받고도 어떤 지갑에서 어떤 경로와 어떤 형식으로 주소를 찾아야 하는지 알아내야 한다. 단일서명이라면 디스크립터 한 줄로 그 수고가 사라지고, 멀티시그라면 그 한 줄이 있느냐에 금고가 달린다.
열쇠와 지도
시드 구문은 열쇠다. 열쇠는 자물쇠를 열지만 자물쇠가 어디 있는지는 말하지 않는다. 단일서명 지갑에서는 표준 경로 몇 개를 더듬으면 대개 자물쇠가 나오기 때문에 이 차이가 잘 보이지 않았다. 멀티시그와 시간 조건이 들어간 지갑에서는 더듬을 수 있는 길의 수가 너무 많거나 아예 없다.
디스크립터는 그 길을 한 줄로 적은 지도다. 처음의 다섯 주소는 같은 열쇠 세 개로 만들 수 있는 금고 가운데 몇 개일 뿐이다. 어느 금고에 코인을 넣었는지는 디스크립터만 안다.