어제 ERC20 기반 토큰인 BEC에서 엄청나게 큰 토큰을 전송하는 문제가 발생하였습니다. 뉴스에 나온 이야기는 엄청나게 큰 수의 토큰이 정상적으로 etherscan.io에 등록이 되었다는 것입니다. 이를 통보받은 거래소는 바로 거래를 중지하였고, 원인을 파악한다고 했습니다. 이 사실이 알려지자 암호화폐에 대한 신뢰가 무너지면서 전 종목이 급락을 하였습니다.
저는 사건이 터지고 한참뒤에 ERC20에 문제가 있다는 이야기를 들었고, 그 소식을 듣자마자 이더리움을 매도하였습니다. 이더리움이 이전에도 문제가 생겨서 이더리움클래식으로 하드포크를 한 이력도 있어서 일단 매도 후 사태를 파악하기로 했습니다.
그 후 문제의 원인이 BatchTransfer라는 smart contract의 프로그램 오류로 밝혀졌습니다. 그동안 지적되었던 smart contract의 소스코드의 불완전성이 다시 도마에 올라왔습니다. 약간 전문적인 부분이 될 수 있겠지만 이번 BatchTransfer이 어떤 문제이고, 왜 ERC20 생태계에 큰 문제가 없는가에 대하여 정리하고자 합니다.
우선 컴퓨터 프로그램에서 정수를 표현하는 방법에 대하여 설명을 합니다. 컴퓨터에서 숫자를 표현하기 위해서는 그 값을 저장할 수 있는 메모리가 필요합니다. 보통 언어에서 int 라고 하는 정수형 변수는 일반적으로 4 bytes를 많이 사용하였는데(32bit 컴퓨터) 요즘은 8 bytes(64bit 컴퓨터)를 int로 사용합니다. 한 바이트는 8bits로 구성되어 있으므로, 8 bytes를 int로 사용하는 경우에 최대로 표현할 수 있는 수는 2^64 - 1 입니다.
앞에서 8 bytes를 정수로 사용하는 컴퓨터는 나타낼 수 있는 최댓값은 2^64 - 1이라고 했습니다. 그럼 여기에 1을 더하면 어떻게 될까요? 이렇게 해당 변수가 표현할 수 있는 숫자 이상의 값을 표현하고자 하면 오버플로워(overflow)가 생기고 그 이하로 가면 언더플로워(underflow)가 생깁니다. 이번에 생긴 것은 오버플로워 문제입니다.
실제 프로그램 상에서 오버플로워가 생기면 어떤 현상이 벌어질까요? 이 질문의 답은 case-by-case입니다. 사용하는 프로그래밍 언어에 따라서 overflow가 발생할 수도 있고 그렇지 않을 수도 있습니다. 일단 파이썬의 경우에는 overflow가 발생하지 않습니다. 특정 변수 형이 표현할 수 있는 값 보다 더 큰 값이 들어보면 바이트를 늘리면서 알아서 증가시킵니다. 대신에 프로그램 실행속도가 느려지는 단점은 있습니다.
반면에 이더리움은 Solicity라는 언어를 사용하는데, 이 언어에서는 overflow를 확인하지 않고 그냥 처리합니다. 그래서 프로그래머가 overflow 여부를 확인해야 합니다. 이러한 경우를 대비하여 Smart contract를 만들 때 SafeMath.sol에 나오는 safeAdd라는 함수를 사용하기를 권고하고 있습니다.
코드 내용은 간단합니다. 입력으로 들어오는 두 값을 더한 후 더한 값이 입력 값보다 적으면 오류를 발생하는 코드입니다. 여기에서 입력값은 0보다 큰 값입니다.
function safeAdd(uint256 x, uint256 y) internal returns(uint256) {
uint256 z = x + y;
assert((z >= x) && (z >= y));
return z;
}
문제의 smart contract를 보겠습니다.
https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code
이 코드는 _value에 수량의 토큰을 복수의 _receivers에게 보내기 위한 함수입니다. 정상적인 거래인지를 확인하기 위하여 아래 두 가지 경우에 대하여 점검을 합니다. (258, 259라인)
이 함수는 같은 수량의 토큰을 여러 명에게 보내는 코드이므로 중간에 amount라는 값을 계산합니다. 즉 amount 값은 보낼 사람 수 곱하기 보낼 토큰의 수입니다.
코드 상 큰 문제는 없어 보입니다. 하지만 해커는 이 코드에 있는 문제점을 발견하고 이를 이용하여 엄청나게 큰 토큰을 누군가에 보내는 거래를 만듭니다.
저는 이 코드를 보면서 처음에는 문제를 발견하지 못하였습니다. 하지만 관련 글을 읽고 보니 역시 해커는 천재라는 생각이 들었습니다[1]
일단 문제가 되는 거래의 입력은 아래와 같습니다.
257라인에서 amount를 계산하면 2^256이 됩니다. 이 값은 overflow가 발생하여 프로그램상에서는 0으로 인지가 됩니다. 이 상태에서 258-259 라인의 점검을 무사히 통과합니다. 보내는 사람의 balance는 무조건 0보다 클테니까요. 그 후로는 점검하는 코드가 없기 때문에 일사천리로 엄청나게 큰 토큰이 누군가에게 전달되는 거래가 생성이 되는 겁니다.
문제의 거래입니다. 앞에서 설명한 이유로 value 값이 0으로 나옵니다.
https://etherscan.io/tx/0x0775e55c402281e8ff24cf37d6f2079bf2a768cf7254593287b5f8a0f621fb83
이 조건을 발견한 해커도 대단한 사람인 것으로 보입니다.
해결책은 여러 방법이 있습니다.
우선은 256번 라인에 있는 곱하기 대신에 SafeMath.sol 에 있는 safe 함수를 사용하면 됩니다.
그리고 또 모를 문제가 있을 수 있으니 점검하는 코드를 더 넣으면 좋겠지요? 예를들어 보내는 사람의 잔고가 보내고자 하는 token의 수 보다 많은지 등도 점검을 하는 것이 좋을 것 같습니다.
이번 문제를 보고 이번에 삼성증권에서 발생한 증권 이체 문제가 떠 올랐습니다. 이번 사태는 운영자가 금액을 입금해야 하는데 수량을 넣어서 발생한 문제이지만 이체를 하기 전에 점검하는 코드가 더 많이 있었더라면 사전에 검출이 되었을 것입니다.
소프트웨어 개발이 이렇게 단순한 것 같으면서도 어렵습니다. 해당 코드에서 발생할 수 있는 모든 문제점을 잘 인지하고 개발을 해야하기 때문입니다. 제 경험상 특정 함수를 개발할 때 필요한 로직을 개발하는 것이 20이라면 나머지 80은 해당 코드가 맞는지, 입력 조건이 맞는지 그리고 결과가 잘 나왔는지를 점검하는 코드가 80이라고 생각합니다.
문제가 되는 코드를 봐서 하시겠지만 이것은 이더리움의 문제라기 보다는 smart contract를 짠 프로그래머의 문제입니다. 그래도 불안한 마음이 드시겠지만 개발자 관점에서는 큰 문제는 아니라고 봅니다. 암호화폐 시장에서 어떻게 받아드리느냐는 다른 문제겠지만요.