132010년 8월 15일
1840억 비트코인이 만들어진 날
한 트랜잭션이 금액 검사를 통과하면서 예정된 공급량을 훨씬 웃도는 비트코인을 만들어냈다. 몇 시간 만에 수정이 나왔지만, 이 사건은 화폐의 규칙을 지키려면 그 규칙을 실행하는 코드부터 정확해야 한다는 사실을 보여 주었다.
2010년 8월 15일 일요일, Jeff Garzik은 막 채굴된 블록 74 638과 그 안에 있는, 존재해서는 안 되는 금액을 보고했다. 단 하나의 트랜잭션이 두 출력을 만들었는데, 각각 920억 BTC를 넘었다. 합계는 Bitcoin의 계획된 공급량의 거의 9천 배에 달했다. Garzik은 18:08:49 UTC에 포럼에서 “Strange block 74638”이라는 제목의 글을 열고 블록 데이터를 올렸다. 몇 분 만에 이것이 블록 탐색기의 오류도, 소수점 위치를 잘못 표시한 문제도 아니라는 사실이 드러났다. 네트워크가 스스로의 통화 규칙을 어긴 블록을 받아들인 것이다. 코인을 마음대로 만들어낼 수 없다는 믿음을 유지하려면, 먼저 결함을 찾아 소프트웨어를 고치고 충분한 참가자가 다른 기록 분기를 이어가도록 설득해야 했다.
이상한 블록 74 638
Garzik의 글은 짧고 사실에 충실했다. 트랜잭션 해시와 거대한 두 출력이 제시되어 있었다. 같은 토론에 나중에 올라온 printblock 기록은 정확한 값을 보여주었다. 각 출력에는 92 233 720 368.54275808 BTC가 들어 있었다. 블록에는 채굴자에게 50.51 BTC를 지급하는 별도의 coinbase 트랜잭션도 포함되어 있었다. 평소 기본 보상이 50비트코인이었다는 점을 생각하면, 그 두 출력은 터무니없었다. 누군가 방금 1840억 개가 넘는 단위를 만들어낸 것이다.
이 트랜잭션을 만든 사람의 이름이나 공개적인 설명은 알려지지 않았다. 남아 있는 체인 기록은 무슨 일이 일어났는지는 보여 주지만, 누가 어떤 목적으로 버그를 이용했는지는 알려 주지 않는다. 특정 해커의 소행이라고 단정하면 사료에 없는 내용을 이야기에 보태는 셈이다. 확인할 수 있는 것은 검증의 취약점을 이용하도록 트랜잭션이 만들어졌다는 사실이다.
기존 코드는 개별 출력을 확인한 뒤 합계를 검사했고, 네트워크는 그 결과에 따라 트랜잭션을 받아들였다. 두 출력은 각각 사용 중인 숫자 자료형의 범위 안에 있었지만, 더한 값은 상한을 넘었다. 수가 음수 영역으로 넘어가면서 이후 검사는 허용되지 않는 양의 금액이 만들어지지 않았다고 잘못 판단했다.
따라서 서명 위조, 기존 트랜잭션의 변경, 또는 해시레이트의 다수 장악이 아니었다. 공격자는 특정 구현이 정수를 처리하는 방식을 악용했다. '출력이 입력보다 많으면 안 된다'는 수학적 규칙은 올바랐지만, 프로그램은 오버플로 때문에 사람들이 의도한 대로 그 규칙을 실행하지 못했다.
이 블록은 이미 네트워크가 따르던 체인에 들어가 있었다. 결함이 있는 버전의 노드는 모두 이를 유효하다고 보았고, 다른 채굴자들은 그 위에 새 블록을 쌓을 수 있었다. 문제를 공개하는 것만으로는 부족했다. 잘못된 이력이 더 퍼지고 거대한 출력이 다시 지출되기 전에 커뮤니티가 대응해야 했다.
규칙과 기계 사이의 오류
2100만 개라는 한도는 흔히 Bitcoin의 불변 법칙으로 설명된다. 이 사건은 그보다 정확한 모습을 보여주었다. 한도는 일련의 합의 규칙이며, 각 프로그램과 모든 Full Node가 이를 오류 없이 실행해야 한다. 널리 쓰이는 노드가 모두 같은 결함이 있는 구현을 공유한다면, 원래 의도와 어긋나는 상태를 일시적으로 받아들일 수 있다.
정수 오버플로는 오래된 프로그래밍 문제다. 컴퓨터 레지스터의 범위는 무한하지 않으며, 한계값을 초과하면 이후의 산술 연산이 수직선의 완전히 다른 쪽에 도달할 수 있다. 회계 시스템에서는 이런 사소한 문제가 결정적이다. 합은 계산 이후뿐 아니라 계산 도중에도 검증되어야 하고, 각 금액은 허용 범위 안에 머물러야 한다.
오늘날 Bitcoin Core가 여러 검사를 따로 수행하는 이유다. 출력은 음수여서는 안 되고 최대 공급량을 넘어서는 안 되며, 값을 더할 때마다 누적 합계도 다시 검사한다. 이 코드는 막연한 조심성에서 나온 것이 아니다. 보호 장치 하나의 누락으로 네트워크가 경제적으로 불가능한 트랜잭션을 기록했던 사건의 경험이 담겨 있다.
이 버그는 기술 명세와 사람들이 기대하는 규칙 사이의 차이도 보여 준다. 당시 포럼에서 구버전 소프트웨어가 받아들였다는 이유만으로 터무니없는 금액을 정당하다고 진지하게 주장한 사람은 없었다. 참가자들은 원래 의도한 규칙이 위반되었다는 데 동의했다. 다만 그 합의를 실행하려면 결함이 있는 블록을 거부하는 새 버전이 필요했다.
여기서 코드와 사람의 역할이 맞물린다. 코드는 정해진 절차에 따라 노드가 어떤 블록을 받아들일지 결정한다. 사람은 코드가 의도한 규칙과 어긋난다는 사실을 발견했을 때 어떤 코드를 설치할지 결정한다. 이 사건은 추가 발행을 허용할지 투표하는 과정이 아니었다. 커뮤니티가 이미 시스템의 토대로 여겼던 규칙을 되살리기 위한 시간과의 싸움이었다.
오버플로가 까다로운 이유는 사람이 즉시 알아차리는 터무니없는 상황을 기계는 인식하지 못하기 때문이다. 프로세서는 제한된 공간에 저장된 수로 계산한다. 계산이 그 경계를 넘으면 결과가 표현 범위의 다른 쪽으로 돌아갈 수 있다. 기존 코드는 각 출력을 검사했지만, 더하는 동안 누적 합계를 안전한 범위에 유지하지 못했다. 그래서 트랜잭션은 개별적으로는 타당한 여러 조건을 통과하면서도 그 조건들이 함께 지키려던 원칙을 어겼다. 수정은 구체적이어야 했다. 주석에 비트코인은 2100만 개를 넘지 않는다고 적는 것만으로는 부족했다. 모든 노드는 이 문장을 매번의 계산에서 실제로 지킬 수 있는 규칙을 필요로 했다.
시간에 쫓긴 수정 작업
공지 이후 포럼에 트랜잭션 수신을 중단하고 일시적으로 채굴을 멈추라는 권고가 올라왔다. 결함이 있는 트랜잭션 위에 생성되는 각 새로운 블록은 포기해야 할 작업량을 늘리게 된다. 중앙 스위치가 없는 네트워크에서 사토시는 채굴을 멈출 수 없었다. 그가 할 수 있는 것은 패치를 배포하고 운영자들이 이를 실행하도록 설득하는 것뿐이었다.
버전 0.3.10은 금액을 올바르게 검사하고 블록 74 638을 거부했다. 수정된 노드들은 마지막으로 공유했던 유효 지점으로 돌아갔고, 채굴자들은 공급량을 부풀린 트랜잭션이 없는 대체 분기를 쌓기 시작했다. 한동안 두 이력은 각 분기에 누적된 작업량을 기준으로 경쟁했다.
이후 Satoshi는 올바른 체인이 잘못된 체인을 추월했다고 알렸다. 업데이트된 소프트웨어가 체인 재구성을 수행하면서 결함이 있는 블록은 활성 이력에서 사라졌다. 버려진 분기에 있더라도 그 자체로 유효한 트랜잭션은 대기 목록으로 돌아가 나중에 다시 블록에 포함될 수 있었다. 그러나 거대한 출력들은 새 검사를 통과할 수 없었다.
복구에는 대가가 따랐다. 버려진 분기의 채굴자들은 보상을 잃었고, 사용자들은 몇 시간 동안 쌓인 이력이 이어지지 않는다는 사실을 받아들여야 했다. 2010년에는 네트워크가 작아 조율이 빨랐다. 훨씬 커진 생태계에서 같은 조치를 취한다면 더 많은 경제 주체와 소프트웨어 버전을 고려해야 하고, 어디까지를 수정으로 볼 것인지를 둘러싼 논쟁도 늘어날 것이다.
빠른 대응은 종종 Satoshi의 기적 같은 개입으로 묘사된다. 그러나 실제로는 기록으로 확인할 수 있는 여러 행동이 이어졌다. Garzik이 문제를 공개했고, 다른 사람들이 오버플로를 분석했으며, 개발자들이 수정을 준비했다. 운영자들은 새 버전의 Bitcoin 클라이언트를 설치했고, 채굴자들은 올바른 분기에 작업을 제공했다. 작성자는 수정안을 제안할 수 있었지만, 이를 실제로 채택해야 하는 쪽은 네트워크였다.
가장 엄격한 규칙은 가장 겸허한 개발을 필요로 한다
수정 이후 유효한 비트코인의 수는 원래 의도된 범위로 돌아왔고, 결함이 있는 수십억 개는 이후 체인에서 지출할 수 없었다. 따라서 이 사건은 영구적인 인플레이션으로 끝나지 않았다. 그렇다고 해서 베타 버전의 단순한 기이한 사례에 불과한 것은 아니다. 이는 화폐적 확신이 구호에서 나오는 것이 아니라 반복적으로 검증된 프로그램에서 나온다는 것을 상기시켜 준다.
Bitcoin은 중앙 기관이 결정 하나로 공급량을 바꾸는 시스템과 자주 대비된다. 그렇다고 사람의 역할이 사라진 것은 아니다. 개발자는 구현을 작성하고, 검토자는 오류를 찾고, 운영자는 버전을 선택하며, 채굴자는 블록의 순서를 만든다. 차이는 개입이 공개적으로 이루어지고, 각 노드가 동의하지 않는 규칙을 스스로 거부할 수 있다는 점이다.
이 사건은 오늘날 합의 코드를 신중하게 고치는 이유도 설명해 준다. 작은 산술 최적화 하나가 화폐 공급에 영향을 줄 수 있다. 테스트와 여러 단계의 구현 검증, 시간을 들인 코드 검토는 불필요한 행정 절차가 아니다. 몇 비트 때문에 허용할 수 없는 수십억 단위의 금액이 유효한 것처럼 처리되는 일을 되풀이하지 않기 위한 방어 장치다.
제프 가르직은 하나의 포럼 게시글로 네트워크를 안전하게 만들지 않았지만 공개 장부의 이점을 보여주었다. 모든 사람이 이상한 블록을 볼 수 있었고, 다시 계산하여 출력을 확인하고 수정을 검사할 수 있었다. 오류는 운영자의 내부 데이터베이스에 숨겨져 있지 않았다. 그것은 전체 커뮤니티 앞에 놓여 있었고, 오래된 노드들이 그것을 수용한 증거와 함께 놓여 있었다.
공급 한도는 이 사건을 견뎌냈지만, “불변”이라는 말에서 흔히 떠올리는 방식은 아니었다. 소프트웨어가 실패할 수 없어서가 아니었다. 실패를 누구나 볼 수 있었고, 돌아갈 수 있는 공통의 규칙이 있었으며, 충분한 참가자가 실제로 수정을 적용했기 때문이다. 1840억 비트코인의 이야기는 고정된 공급량을 부정하지 않는다. 그 공급량을 지키려면 무엇이 필요한지를 보여주는 역사다.
이 사건은 또한 코드 공개 자체가 버그를 제거하지는 않는다는 사실을 상기시킨다. 오픈소스는 단일 공급자에 의존하지 않고 문제를 찾아내고, 비교하고, 수정할 수 있게 했지만, 결정적이었던 것은 특정 참여자들의 주의였다. 누군가는 불가능한 숫자를 알아채야 했고, 누군가는 결함이 있는 조건을 찾아야 했으며, 운영자들은 업데이트를 수용해야 했다. 투명성은 감시의 가능성을 만들었고, 비로소 행동이 그것을 네트워크의 방어력으로 전환했다. 그래서 이 이야기는 수년이 지난 지금도 중요하다. 비트코인의 안전성은 완벽한 원본 프로그램이라는 상상이 아니라, 정확한 규칙, 독립적 검증, 그리고 오류를 은폐하지 않고 대응할 수 있는 능력의 결합에 있다.
아틀라스의 인물과 개념
출처와 이야기의 한계
- 01BitcoinTalk — Strange block 74638
제프 가르직의 최초 경고, 정확한 출력, 오버플로우 분석 및 대응 타임라인
- 02BitcoinTalk — overflow bug SERIOUS
당시 개발자들의 협조, 검증 오류 수정, 수정된 체인으로의 전환
- 03Bitcoin Core — transaction consensus checks
현재의 구현은 각 출력의 범위와 그 합을 확인한다.