- 거래소의 ERC20 입금 주소
- 컨트랙트 주소값이 생성되는 원리
- ETC → ERC20 오입금 상황 설명
- 해결방법
- 발생할 수 있는 이슈-1 : 보안문제..?
- 발생할 수 있는 이슈-2 : 컨트랙트가 컨트랙트를 만드는 경우
- nonce와 sender를 이용한 주소 생성 코드 예시
- 소비자의 권리와 책무
- Related Links
안녕하세요 철학자입니다.
오늘은 거래소 이용자의 소비자 권익을 위한 오입금 문제에 대해서 좀 다뤄보고자 합니다.
얼마전에 어떤 분께서 “이더 클래식을 ERC20 주소로 오입금을 했는데, 거래소쪽에서 해결 방법이 없다고 합니다. 실화인가요?”라고 물어보셨습니다.
제가 좋아하는 결론부터 말씀드리면, 가능합니다. 불가능하긴요^^;; 거래소쪽에서 성가셔서 안해주는겁니다.
몇 가지 구성요소에 대해서 이야기를 나누고, 이게 왜 가능한 것인지 이더리움 구조와 원리를 통해서 이야기를 나눠보도록 하겠습니다.
거래소의 ERC20 입금 주소
ERC20과 이더리움, 이더리움 클래식은 동일한 주소체계를 가지고 있습니다. 0x로 시작하는 160비트의 핵사코드로 이뤄져 있죠. 이더리움 클래식이 이더리움과 같은 주소 체계를 쓰는 이유는 과거 하드포킹을 통해서 나눠졌지만 같은 뿌리를 두고 있기 때문이며, ERC20은 이더리움 프로토콜 위에서 동작해 동일한 식별자를 사용하기 때문입니다.
그런데 그 이더리움 계정은 2가지 종류가 있습니다.
- 외부 계정(External Account; EA)
- 컨트랙트 계정(Contract Account; CA)
그리고 거래소는 사용자에게 ERC20 주소를 부여할 때, 컨트랙트 계정을 이용합니다.
그 이유는 거래소가 입금주소와 출금주소를 달리하는 방식을 택하고 있기 때문입니다.
거래소는 일단 사용자들에게 코인 혹은 토큰을 입금 받으면 소수 혹은 단일의 모( 母)계정(이런 표현을 쓰더군요,* 엄마 지갑*이 아닙니다)으로 몰아 놓습니다.
그런데 토큰과 코인이 수십, 수백종으로 늘어나면 이렇게 모으는 과정(gathering)을 자동화 할 필요가 있습니다. 이러한 자동화에 스마트 컨트랙트(Smart Contract)가 빠질 수 없겠죠.
구체적인 컨트랙트 구현은 거래소마다 조금씩 다를 수 있습니다만, 보통 다음과 같은 로직으로 만들게 되어 있습니다.
위의 코드는 bittrex가 사용하는 입금용 지갑 컨트랙트 입니다. 사용자에게 userWallet컨트랙트를 만들어서 입금용 지갑을 발급하고, 입금이 되면 sweep함수를 콜해서 지갑이 보유한 토큰을 거래소 모지갑으로 가져가는 방식으로 작업이 이뤄집니다.
어찌되었건 거래소가 사용자에게 부여한 지갑 주소는 EA주소가 아닌 컨트랙트 주소입니다. 그리고 컨트랙트 주소는 생성되는 규칙과 방식이 정해져 있습니다. 그리고 이더리움과 이더리움 클래식은 동일한 주소 생성 규칙을 가지고 있기 때문에, 만약 이더 클래식을 이더리움 메인넷의 ERC20 컨트랙트 주소로 잘못 입금했다면, 동일한 방식으로 이더리움 클래식 네트워크에서 컨트랙트 주소를 만들어 돌려받은 다음 오입금한 사용자에게 돌려주면 됩니다.
일단 이 논의를 더 구체화 하기 전에 주소값이 생성되는 원리에 대해서 조금 더 알아보도록 하겠습니다.
컨트랙트 주소값이 생성되는 원리
이더리움의 기술 스펙은 가빈 우드가 작성한 황서(Yellow Paper)에 모두 적혀 있습니다.(그리고 이더리움이 업그레이드 되어가면서 황서도 지속적인 업데이트가 되고 있습니다.) 그리고 컨트랙트 주소 생성에 관한 내용은 수식-77에 자세히 적혀있습니다.(e94ebda — 2018–06–05기준)
</br>
이게 무슨 외계어냐.. 싶겠지만 하나씩 읽어보면서 해석해보도록 하겠습니다.
새로운 (컨트랙트의)주소는 보내는 사람(sender)과 계정의 논스(account nonce)를 RLP인코딩한 후 Keccak(SHA-3해시함수)를 이용해 256비트를 추출한 후에 우측 160비트로 정의된다
The address of the new account is defined as being the rightmost 160 bits of the Keccak hash of the RLP encoding of the structure containing only the sender and the account nonce. Thus we define the resultant address for the new account a: 77
77번 식의 노테이션의 의미는
a : 주소
B : 바이트 배열(그렇다면 B_96..255은 우측에 160비트를 뜻하겠죠)
KEC : Keccak함수
RLP : RLP인코딩
s : sender의 주소
σ : 이더리움의 State를 뜻합니다
$σ[s]$ : s라는 계정의 state을 말하고
\(σ[s] _ n\) : s라는 계정의 state중 현재의 nonce값을 뜻하죠
여기서 논스(nonce)라는 용어가 나왔는데, 이더리움의 논스는 2개가 있습니다. 기존의 비트코인에서 블록의 PoW값을 맞추는 논스와 state에 기록된 계정이 여태 몇 개의 트랜잭션을 생성했는지 나타내는 nonce 두 개가 있죠. 여기서 정의된 nonce는 후자입니다.
결국 컨트랙트 주소는 컨트랙트를 i)만든 사람(sender; s)과 ii) 만든사람의 state에 기록된 논스값(σ[s]_n)두개만 알면 됩니다.
RLP와 Keccak알고리즘은 정해져 있으니, 컨트랙트를 만든 사람과 해당 state 논스값만 같다면(즉 이더리움과 이더리움 클래식에서 네트워크에서 같은 수의 트랜잭션을 생성했다면) 이더리움과 이더 클래식에서 코드의 내용과 상관없이 같은 주소를 만들어 낼 수 있게 됩니다.
ETC → ERC20 오입금 상황 설명
거래소는 한개의 EA를 이용해 수천, 수만명의 입금주소(CA)를 만듭니다. 그렇게 만들어진 주소 중 오입금된 ERC20주소가 x번째 만들어졌다고 가정 해 보겠습니다. 물론 이 ERC20주소가 만들어지기까지 생성된 모든 tx는 이더리움 메인넷에 기록되어 있습니다.
그런데 문제는 “입금”이라는 transaction이 이더리움 메인네트워크가 아닌 이더리움 클래식 네트워크에서 이뤄졌다는 점 입니다. 이러한 상황을 그림으로 표현해보면 다음과 같죠.
거래소는 이더리움 메인 네트워크에서 x개의 컨트랙트 생성을 했는데, 사용자는 이더리움 클래식 네트워크에 x번째 어카운트로 이더리움 클래식을 입금 한 상황입니다. x번째 어카운트는 개인키를 통해서 생성된 계정이 아니라, 컨트랙트 생성을 통해 만들어진 계정이기 때문에 계정에 있는 내용물(이더 혹은 이더클래식, ERC20토큰 등)의 통제권은 코드로 만들어야 됩니다.
그리고 오입금 상태를 묘사하면 이더리움 네트워크에는 x개의 tx가 존재하지만, 이더리움 클래식에는 해당 숫자만큼의 tx가 없는 상황인것이죠.
결국 이더리움 클래식에 x번째 어카운트로 입금된 ETC를 꺼내기 위해서는 이더리움 클래식 네트워크에서 s계정에서 x-1번 만큼의 transaction을 생성한 뒤, x번째 tx에 계정에 들어있는 ETC를 출금하는 로직을 담은 코드를 배포해야 됩니다.
해결방법
앞에서 설명을 했지만, 다시 정리하면 다음 절차를 통해서 오입금된 ETC를 꺼낼 수 있습니다.
- 이더리움 메인넷에서 입금용 컨트랙트 생성에 사용된 개인키를 추출
- 추출된 개인키로 x-1개의 트랜잭션을 생성하여 이더리움 클래식 네트워크 에 기록
- 컨트랙트 계정에 담긴 ETC를 꺼낼 수 있는 “특별한 컨트랙트”를 x번째에 배포
- SUCCESS!
특별한 컨트랙트
라는 말이 나왔는데, 사실 별로 특별할 것은 없습니다. 컨트랙트 배포와 동시에 계정에 담긴 ETC를 꺼내는 로직을 담고 있는
코드를 배포하는 것인데, 예를 들면 다음과 같습니다.
클래식 네트워크에 배포한 후 ETC를 돌려받을 주소를 파라미터로 담아 kill함수를 호출하면, 컨트랙트에 담긴 ETC가 모두 sender에게 돌아갑니다.
발생할 수 있는 이슈-1 : 보안문제..?
거래소 입장에서 기존에 주소를 생성할 때 쓰던 EA는 다른 것으로 변경해야 겠죠. 왜냐하면 이 작업과정에서 작업자가 개인키를 유출 할 수도 있기 때문입니다.
사실 위 이슈가 문제가 없다고 한다면 계정 서명 원리를 구조적으로 봤을 때 거래소는 발급용 EA를 교체하지 않아도 됩니다.
그런데..
이 해결책을 전달했을 때, 거래소는 이런 대답을 했다고 합니다.
ETC네트워크에서 x번 만큼의 tx를 날리는 동안 ETH네트워크의 기존 tx혹은 앞으로 발생할 tx들이 위험해진다
얼핏 들으면 이더리움에서 쓰던 키를 클래식에서 쓴다니까 위험해 보일수도 있습니다만, 사실 원리를 알면 위험해질 이유가 하나도 없습니다.
이더리움와 클래식이 포킹되었던 초기에 replay attack이라는 취약점이 있었습니다. 그 내용은 포킹이 되었던 1920000블록을 기준으로 ETH와 ETC양쪽의 잔액 등 state상태가 동일했기 때문에, 만약 ETC발생한 tx를 그대로 ETH에서 발생시키면 유효간 트랜잭션이 되어버리기 때문이었습니다.
그런데 블록이 높아지고, 양쪽 체인의 상태가 매우 달라지게 되면서 이러한 이슈는 더이상 발생하지 않고 있습니다.
이러한 이슈가 발생하지 않는 이유는 거래 서명에 사용되는 nonce, data, balance등의 state이 블록에 쌓여가면서 매우 달라졌기 때문입니다.
이 사례에서 봤을 때도, ETC네트워크에서 x번만큼의 tx를 생성했다고 하더라도, ETH네트워크에서 만들었던 tx와 내용을 달리하면(data필드의 내용을 바꾸는 등) 기존 ETH네트워크에서 발생했던 tx들이 위험해질 이유는 전혀 없습니다.
그래도 혹여나 찜찜하면, 기존 ETH네트워크에 있던 자산(이더 등)을 안전한 곳으로 옮기고, 거래소가 발급용 계정으로 사용하는 EA를 바꿔버리면 되죠.
“ETC네트워크에서 tx를 날리는 동안 기존 ETH네트워크에 tx들이 위험해진다”는 말은 논리적으로 봤을 때 거짓말 이 됩니다.
발생할 수 있는 이슈-2 : 컨트랙트가 컨트랙트를 만드는 경우
위의 지갑 생성 소스코드를 살펴보면 Controller컨트랙트가 makeWallet컨트랙트를 이용해서 userWallet컨트랙트를 만들게 됩니다. 만약 컨트랙트가 컨트랙트를 만들 경우, sender는 컨트랙 생성을 요청한 컨트랙트 주소가 되고(이 경우 Controller의 주소), nonce는 1부터 시작합니다.
nonce가 1부터 시작된 이유는 EIP161때문이고, DoS공격으로 쌓인 가짜 계정들을 청소하는 하드포크인 Spurious Dragon (Block >= 2,675,000)부터 적용되었습니다.
문제는 이러한 경우 x개의 tx를 만들 때, 어디까지가 EA가 만든 컨트랙트이고, 어디까지가 CA가 만든 컨트랙트인지 구분해야된다는 것 입니다.
즉 EA → CA → CA 구조이고, EA → CA 로 이어지는 tx가 몇번 이뤄졌는지 파악한 후에, 다시 CA → CA로 이어지는 tx가 몇번 이뤄졌는지 파악해서 트랜잭션과 컨트랙트를 만들어야 합니다.
nonce와 sender를 이용한 주소 생성 코드 예시
실제 transaction데이터를 바탕으로 nonce와 sender의 주소를 이용해 컨트랙트 주소를 만들어 내는 코드 예시는 다음과 같습니다.
0x39.. 는 sender, nonce는 15300일때 만들어지는 컨트랙트 주소는 0x4f임을 알 수 있습니다.(etherscan에서 확인하실 수 있습니다.)
소비자의 권리와 책무
2018년 5월1일부터 시행되는 소비자기본법에는 소비자의 8대 기본적 권리와 3대 책무를 명시하고 있습니다.
4조 5항, 5조1~2항에 따르면 소비자는 필요한 교육을 받을 권리 와 이 권리를 정당하게 행사 해야 하며, 스스로의 권익 증진을 위한 정보 습득의 노력 할 것을 명시하고 있습니다.
한국의 하루 암호화폐 거래량은 4천억을 넘고(2018–6–10기준), 관련 앱 사용자가 200만명이 넘는다고 합니다. 저 또한 그 중 한명이구요.
오입금 문제는 거래소가 비트코인 이외의 자산을 거래하기 시작했을 때부터 제기되었던 문제입니다만, 오입금에 대한 정책을 마련하고 오입금 사용자들에 대한 복구 서비스를 제공을 시작한 것은 단지 몇달 전입니다.
시장이 폭발적으로 성장하면서 사용자는 늘어갔는데, 서비스의 질과 양은 이를 못따라 가고 있는 것 같습니다. 이러한 상황에서 서비스 제공자는 소비자들에게 구체적인 설명과 적극적인 보상을 하기는 커녕, 기술적 제약을 앞세워 소비자를 기만하거나 무시하기 바쁘죠. 반명 소비자의 책무를 다하기 위한 정보와 기술의 장벽은 높습니다.
암호화폐 거래 서비스를 이용하는 소비자로써 우리는 정당한 권리를 누리고 있습니까?
이 글이 이더리움 클래식을 ERC20지갑으로 오입금한 분들에게 조금이라도 도움이 되기를 바라며 이만 글을 마치겠습니다.
감사합니다.