이더리움 Multisignature 월렛을 이용한 안전한 ICO 자산 관리
많은 ICO 팀들이 ICO과정에서 이더(Ether)를 취득하고 있다. 다량의 이더를 취득하고 보관하는 작업은 은행에서 계좌를 발급받아서, 이 계좌를 통해 현금을 이체받고 이 현금을 출금하는 것만큼 간단하고 안전하지는 않다. 많은 사항을 준비하고, 고려하고, 결정해야 하는데, 이 글에서 그 과정 및 고려사항을 세부적으로 정리하고자 한다.
이더리움 월렛의 위험성
비밀키를 잃어버려서 눈앞에 보이는 이더를 꺼내지 못하는 상황이 있을 수 있고, 해킹을 당해서 이더를 모두 도둑맞을 수도 있다. 비밀키를 가진 팀 멤버가 해킹을 가장해서 이더를 빼돌릴 수도 있다.
Parity Technologies라는 ICO 팀은 이더리움 재단과 Multisignature 월렛을 함께 개발해서 사용했는데, 이 월렛에 오류가 있어서, 스마트 컨트랙트가 deconstruct 되면서, 비밀키를 가지고 있음에도 불구하고 51만여 이더가 잠겨버리는 사고를 겪었고, 영원히 이를 사용하지 못할 것으로 보인다.
골치아프니 그냥 암호화폐 거래소에서 이더 지갑을 발급받는다면? 거래소가 파산할 수도 있고, 국가에 따라서는 당장 세금, 회계 문제가 발생할 수도 있다. 거래소가 발급해주는 지갑 주소는 비밀키를 거래소가 갖고 있으니 나에게 근본적인 제어권이 있는 것은 아님을 잊지 말자.
여러 위험한 상황을 고려하여 최악의 상황을 미연에 방지하기 위해 세부 목표를 세우고, 각 목표를 달성할 방안을 도출해보자.
안전한 ICO 자산 관리의 세부 목표
설명의 편의를 위해, 3인 중에 2인이 서명하면 자산을 모두 인출할 수 있는 Multisignature 월렛을 만들어 활용하고자 한다.
- 1인이 모든 자산을 인출하지 못하도록 하고, 모든 자산을 인출하려면 최소 2인이 서명하도록 한다.
- 비밀키가 유실되어도 복원할 수 있도록 한다.
- 1인의 비밀키 혹은 월렛 니모닉이 유출되어도 자산 유실을 최소화하고, 남은 모든 자산을 즉시 인출할 수 있도록 한다.
- 2인의 비밀키를 영구적으로 잃어버린 경우에도 장시간에 걸쳐 자산을 인출하도록 한다.
- 비밀키, 키스토어 파일/비밀번호, 니모닉 등을 수집하는 malware로부터 월렛을 보호하기 위해 하드웨어 월렛을 사용한다.
이더리움 계정과 Multisignature 월렛에 대한 이해
Multisignature 월렛을 사용하기 위해 동작원리까지 알아야 하는 것은 아니지만, 알면 좀 더 안전하게 사용할 수 있다.
이더리움 계정은 이더리움에 포함된 구성요소이다. 그 주소는 ‘0xDA0F0f540986B62783E89f1AF281C0b8654382bC’ 등과 같이 표현된다. 이 주소에 포함된 이더를 인출하려면 대응되는 비밀키로 서명해야 한다.
Multisignature 월렛은 이더리움의 구성요소가 아니고 스마트 컨트랙트로 구현하는 일종의 DApp이다. 특정 조건에 맞으면 스마트 컨트랙트가 보유하고 있는 이더를 인출할 수 있도록 프로그래밍 한 것이다.
스마트 컨트랙트를 배포하면 주소가 생성되는데 그 형식은 이더리움 계정과 동일하고, 스마트 컨트랙트는 이더를 보유할 수 있다. 다음의 간단한 스마트 컨트랙트를 배포하고 그 주소로 이더를 보내면, 이름이 없는 fallback 함수가 실행되고 첨부된 이더는 SinkContract가 보유하게 된다.
contract SinkContract {
//fallback
function() payable { }
function transfer(address to, uint amount) {
to.transfer(amount);
}
}
ERC20 규격을 보면 balanceOf 메소드를 통해 address와 토큰 수량을 매핑하는데, 여기서 address는 계정일수도 있고, 스마트 컨트랙트일 수도 있는 것이다.
contract ERC20Basic {
uint256 public totalSupply;
function balanceOf(address who) constant returns (uint256);
function transfer(address to, uint256 value) returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}contract BasicToken is ERC20Basic {
mapping(address => uint256) balances;
}
그러니까 이더나 ERC20 토큰 입장에서 계정이나 스마트 컨트랙트는 차이가 없다. 해당 주소 계정으로부터 서명을 받은 인출 요청을 제시받거나, 해당 주소의 스마트 컨트랙트로부터 전송 요청 메시지를 받으면, 인출이 실행 되는 것이다.
그렇다면 나의 Multisignature 월렛과 동일한 주소의 계정을 사용하는 사람이 있다면, 그 사람이 나의 이더 자산을 인출할 수 있다는 말인가? 물론이다. 다만 그 확률이 너무 낮을 뿐. 계정 생성 시 유일성이 보장되는게 아니니, 이 문제는 계정에도 동일하게 적용된다.
서명자 3인의 계정 생성 및 이더 준비
편의상 3명 중 2명이 서명하면 모든 이더를 인출할 수 있도록 해보자.
Multisignature 월렛의 서명자로 참여할 3인은 각자 이더리움 계정을 생성해야한다. 계정 생성을 위해 MetaMask, 하드웨어 월렛, Ethereum Wallet, Mist Browser, MyEtherWallet 등 다양한 도구를 이용할 수 있다. 암호화폐 거래소에서 제공하는 계정은 사용하면 안된다.
생성 후 계정 주소만 서로 공유하도록 하고, 비밀키나 니모닉은 보안상 다른 사람과 공유하지 말아야 한다. 또한 종이에 기록해서 안전한 장소에 보관해야 한다.
Mist 브라우저에서 Multisignature 월렛을 생성할 예정이니, 편의상 1인은 Mist에서 계정을 생성하도록 한다. 다른 곳에서 생성한 계정도, 비밀키로부터 키스토어 파일을 만들어서 Mist에 import할 수는 있다.
Mist에서 생성한 계정은 A, 나머지는 B, C라 하자.
Multisignature 월렛 스마트 컨트랙트를 배포하거나, 토큰을 보내거나, 서명을 하기 위해서는 각자 약간의 이더를 가스로 사용해야 한다. 단, Multisignature 월렛을 배포하기 위해 Mist가 1이더 이상 갖고 있도록 요구하므로, A는 1이더 이상을 보유해야 한다. 각자의 계정에 적당량의 이더를 전송하도록 하자. Ropsten 테스트넷의 경우 “ether faucet”라고 검색하면 무료로 테스트용 이더를 제공하는 사이트를 찾을 수 있다.
Multisignature 월렛 생성
Multisignature 월렛은 스마트 컨트랙트로 만든 DApp이라고 했으므로, 직접 스마트 컨트랙트를 구현해서 만들 수 있지만, 안전하게 이더리움 재단에서 제공하는 Mist 브라우저의 내장 Multisignature 월렛 기능을 활용하고자 한다.
Mist에서 Develop > Network 메뉴에서 “Main Network” 혹은 “Ropsten — Test Network” 중에서 선택하고, 좌측 하단에 블록 개수가 양수가 될때까지 몇시간 혹은 며칠 기다려야 한다. (음수의 경우 추가로 읽어야 할 개수를 의미한다.) 너무 오래 걸려도 진행이 단되면 프로그램을 재시작시키고 “LAUNCH APPLICATION” 버튼을 누르지 말고 기다리면 빨리 된다.
Mist에서 Multisignature 월렛을 생성하려면 계정이 필요하니 기존 계정을 import하거나 여기서 생성해야 한다.
“Wallet Contracts” 섹션 밑의 “Add Wallet Contract” 버튼을 누르고, 소유자로 A계정을 선택한다. “Wallet Contract Type”으로 “Multisignature Wallet Contract”를 선택한다. 이후, 1일 단독으로 전송할 수 있는 이더 양과 총 서명자 수, 필요 서명자 수를 각각 설정해야 하는데, 여기서는 각각 1이더, 3인, 2인으로 설정한다. 이후 B와 C 계정 주소를 입력하고 배포한다. 현재 선택된 계정으로 서명을 하게 되고 현재 계정의 이더가 가스로 사용된다.
잠시 후, Multisignature 월렛이 Mist에 나타나고, 그 주소를 확인할 수 있다. 이제 이 주소를 공개하고, 이 주소에 이더를 보내달라고 하면 된다. 이 주소로 이더를 보내면 Mist에서 자동으로 잔고를 갱신해서 보여준다.
필자가 생성한 Multisignature 월렛 주소는 0x0781C560B175923a43A0Ad4050a15337b6C31481이고 다음페이지에서 트랜잭션을 확인할 수 있다.
https://ropsten.etherscan.io/address/0x0781C560B175923a43A0Ad4050a15337b6C31481
Mist를 이용하여 Multisignature 월렛에서 이더 인출
인출 발의
앞에서 1일 1이더까지는 단독으로 전송 가능하도록 설정하였으니, Multisignature 월렛에 2이더를 전송하도록 한다.
Multisignature 월렛을 선택하고 보내기 버튼을 누르고, 받을 주소를 적고, 보낼 양에 1.1을 입력하고 보내기를 누르고 A 계정으로 서명한다. 1 이하로 보내면 A만 서명하면 충분하니, 2명 이상 서명하기 위해서는 1 초과로 보내야 한다.
이렇게 서명해서 배포하면 자동으로 1명은 승인한 것이 되므로 1명의 추가 승인만 받으면 된다.
인출 승인
조금 기다리면 1.1 이더를 보내는 데, A, B, C 3인 중 A는 승인했다고 나오고, “Approve”, “Revoke” 버튼이 나타난다. B, C가 승인해야 하니, B가 자신의 PC에서 Mist에 자신의 계정을 import하고, multisignature 월렛도 import해야 한다.
Multisignature 월렛 생성 버튼을 누르고, “Wallet Contract Type” 타입으로 “IMPORT WALLET”을 선택하고 주소만 입력하면 된다.
조금 기다리면 import된 주소에, 앞서 1.1이더를 보낸 건이 나타나고 “Approve”, “Revoke” 버튼이 동일하게 나타난다. 이 버튼을 누르고 B 혹은 C로 서명하고 기다리면 1.1 이더 전송이 완료된 것으로 나타나게 된다.
MyEtherWallet을 이용하여 Multisignature 월렛에서 이더 인출
Mist나 EthereumWallet을 3인이 모두 사용하면 사용자 친화적인 GUI를 이용할 수 있다. 하지만, Mist나 EthereumWallet에 계정을 import 하려면 키스토어 파일이 필요하고, 이 파일과 대응되는 비밀번호는 malware의 주요 수집대상이다. 따라서 보안상 안전한 하드웨어 월렛으로 서명할 수 있도록 해주는 MyEtherWallet(https://www.myetherwallet.com)을 이용해서 이더 전송을 발의하고 승인해보자.
인출 발의
MyEtherWallet에 접속하여 Contracts로 이동한다. Contract address에는 생성된 Multisignature 월렛 주소를 넣는다. ABI / JSON Interface에는 Mist의 Multisignature 월렛을 선택하고 “Show Contract Info”, “Show Interface” 버튼을 차례로 누르면 나타나는 JSON 텍스트를 복사해서 붙여넣는다.
Read / Write Contract는 execute를 선택한다. _to에는 이더를 받을 주소를, _value에는 보낼 이더를 wei 단위로 입력한다.
주의! 1.1이더를 보내려면 1100000000000000000을 입력한다. 1.1을 입력하면 1일 한도 이하라서 추가 승인이 불필요하다.
_data는 생략해도 된다. 이후 A, B, C 중 한 계정을 포함하는 월렛을 선택하고 서명 및 배포를 진행하면 된다. Ledger Wallet, TREZOR, Digital Bitbox, Sacalot은 하드웨어 월렛이다.
인출 발의 확인
배포된 이후 https://etherscan.io나 https://ropsten.etherscan.io(Ropsten 테스트넷 용)에서 스마트 컨트랙트의 트랜젹션을 찾아보자. https://ropsten.etherscan.io/tx/0xc7149583f3100d0e813a38724323a73f41f04cc5d1c8a7d7b15046a7ae40c41e 에 보면 다음과 같이 execute가 실행된 것을 볼 수 있다.
Function: execute(address _to, uint256 _value, bytes _data) ***MethodID: 0xb61d27f6
[0]: 000000000000000000000000cc8d141d60d29b62e7448961e592e2930677f879
[1]: 0000000000000000000000000000000000000000000000000f43fc2c04ee0000
[2]: 0000000000000000000000000000000000000000000000000000000000000060
[3]: 0000000000000000000000000000000000000000000000000000000000000000
[4]: 0000000000000000000000000000000000000000000000000000000000000000
이 페이지 상단의 Event Logs(2) 탭을 클릭하면 이 함수가 실행되면서 발생한 두개의 이벤트를 확인할 수 있다.
여기서 두번째 이벤트는 ConfirmationNeeded 이벤트이고 그 첫번째 파라미터가 operation 혹은 _h 혹은 _r 값이다. 세번째 파라미터가 1100000000000000000인데 이는 1.1이더이다. 네번째 파라미터는 이더를 받을 주소이다. 이는 이 이벤트의 첫번째 파라미터(위에서는 0x64e35c3e7d29950c50c28ef0c0bf8b0a43e6dbd6ad76adaf263ab1fa46d24f92)에 대해 다른 서명자의 승인이 필요하다는 뜻이다.
다음 Multisignature Wallet 스마트 컨트랙트 코드를 보면 1일 한도 이하이면(underLimit)이면 즉시 _to에게 전송하고, 그렇지 않으면 서명(confirm(_r_))을 하고 ConfirmationNeeded 이벤트를 발생시킨다.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r)
{
// first, take the opportunity to check that we're under the daily limit.
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call.
_to.call.value(_value)(_data);
return 0;
}
// determine our operation hash.
_r = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
m_txs[_r].data = _data;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
}
}
이 이벤트의 첫번째 파라미터(위에서는 0x64e35c3e7d29950c50c28ef0c0bf8b0a43e6dbd6ad76adaf263ab1fa46d24f92)에 대해 다른 서명자의 승인이 필요하다는 뜻이다.
인출 승인
MyEtherWallet에 접속하여 Contracts로 이동한다. Contract address, ABI / JSON Interface에는 이전과 동일한 값을 넣는다.
이번에는 Read / Write Contract는 confirm을 선택한다. _h에는 앞에서 살펴본 이벤트의 첫번째 파라미터를 넣는다.
주의! _h 값 앞에 0x를 붙여야 한다.
트랜잭션을 서명/배포하고 기다리면 다음과 같은 confirm 실행 및 1.1 이더 전송 기록을 볼 수 있다. 그림 중간에, 0x0781C560B175923a43A0Ad4050a15337b6C31481 스마트 컨트랙에서 1.1이더가 인출된 것을 볼 수 있다.
https://ropsten.etherscan.io/tx/0xad377495ace187827340d524e3f1acf1c47458c9faae7f20ab437bff8aca088f 에서도 볼 수 있다.
하드웨어 월렛, Mist, MyEtherWallet의 혼용
하드웨어 월렛을 이용하면 니모닉 및 비밀키를 하드웨어 밖에서 디지탈화 할 필요가 없어서 보안상 가장 안전할텐데, Multisignature 월렛을 Mist에서 생성하려면, 니모닉과 지갑 경로로부터 비밀키를 디지탈화하고, 이로부터 geth 프로그램 등을 이용하여 키스토어 파일을 생성해야 한다.
또한, Mist를 활용해야 GUI로 명확히 확인하고 전송 발의 및 승인을 할 수 있는 것을 알았다.
따라서 3인중 1명만 Mist를 이용하여 스마트 컨트랙트를 배포하고, 내용을 확인 후 승인을 하면 되겠다.
다른 2인은 MyEtherWallet으로 전송 발의를 하고 하드웨어 월렛으로 서명을 하면 된다.
2인은 니모닉과 비밀키를 전혀 디지탈화하지 않아도 된다.
안전한 ICO 자산 관리의 세부 목표 달성 확인
- 1인이 모든 자산을 인출하지 못하도록 하고, 모든 자산을 인출하려면 최소 2인이 서명하도록 한다. → 1일 1이더 이상인 경우 2인이 서명해야 하도록 설정하였다.
- 비밀키가 유실되어도 복원할 수 있도록 한다. → 자세히 언급하지는 않았으나 하드웨어 월렛 사용시 생성된 니모닉을 종이에 적어 잘 보관해야 한다. Mist 사용자는 키스토어 파일과 비밀번호를 백업하거나, MetaMask에서 계정을 생성하고 Seed Words를 백업하면 된다. 이후 geth를 이용하여 비밀키로부터 키스토어 파일을 생성하고 이를 Mist에서 import 하면 해당 계정을 이용할 수 있다.
- 1인의 비밀키 혹은 월렛 니모닉이 유출되어도 자산 유실을 최소화하고, 남은 모든 자산을 즉시 인출할 수 있도록 한다. → 1인은 1일 최대 1이더만 인출 가능하니, 남은 2인이 모두 인출하던가, 스마트 컨트랙트의 removeOwner, addOwner 메소드를 실행시켜서 유출된 1인을 교체할 수 있다.
- 2인의 비밀키를 영구적으로 잃어버린 경우에도 장시간에 걸쳐 자산을 인출하도록 한다. → 1인의 비밀키로 매일 1이더씩 인출할 수 있다.
- 비밀키, 키스토어 파일/비밀번호, 니모닉 등을 수집하는 malware로부터 월렛을 보호하기 위해 하드웨어 월렛을 사용한다. → 2인은 하드웨어 월렛외에서 한 번도 니모닉과 비밀키를 디지탈화 하지 않으면서도 MyEtherWallet을 이용하여 인출 발의 및 서명하는 방법을 알아보았다.
추가 보안 조치
- 월렛 경로를 일반적으로 많이 사용하는 것을 사용하지 않고 좀 복잡하게 하자. 이렇게 하면 니모닉이 유출되도 해당 경로까지 확인할 가능성이 낮아지므로 자산을 잃어버릴 가능성이 훨씬 줄어든다.
- Multisignature 월렛을 배포한 후 해당 PC는 포맷하고 3인 모두 하드웨어 월렛을 사용하면 좋다. 이렇게 하려면 니모닉으로부터 키스토어 파일을 생성해서 Mist 브라우저에서 import 해야 한다.
- 불안하니 하드웨어 월렛은 모두 다른 제조사 제품을 사용하도록 하자. 하드웨어 월렛에서 신규 월렛을 생성하는 경우, 해당 하드웨어 월렛이 신뢰할만해야 한다. 신규 월렛 생성 기능은 보통 니모닉 조합을 생성하는데, 이 과정이 안전한 것은 완전한 랜덤 기능을 사용하고 그 조합의 개수가 너무 많기 때문이다. 그런데 제조사만 아는 규칙으로 100억개 정도의 범위 내에서 랜덤으로 생성하도록 했다고 생각해보자. 이 제조사는 100억개에 대해 계정과 비밀키 조합을 미리 도출 및 저장해두고, 이더리움 블록들을 모니터링해서 매칭되는 계정을 찾을 것이다. 이 계정들에 어느정도 이더가 쌓이면 알고 있는 비밀키를 이용하여 일괄 인출해서 잠적할 것이다.
- 종이에 백업된 니모닉/월렛은 사진촬영하지 말고, 여러 카피를 만들고 가족 등에게도 보관 위치를 알려야 한다.
이상으로 하드웨어 월렛, Mist 브라우저, 이더리움 Multisignature 월렛을 활용한 안전한 ICO 자산 관리 기법에 대해 살펴보았다.