시작하는 말
멀티시그 월렛이란?
멀티시그 월렛은 단일 서명을 통해 지갑에서 암호화폐를 출금하던 방식과 다르게 다중 서명을 통해 출금이 가능하도록 한 스마트 컨트랙트이다.
보통 개인이 사용하기보다는 거래소나 기업들처럼 공동의 자산을 보관하고 사용하는 곳에서 주로 사용된다.
이더리움은 트랜잭션 생성 시 하나의 서명만 할 수 있기 때문에 다중 서명(예를 들어 2 of 3)을 위해 스마트 컨트랙트를 이용한다.
이러한 방식의 멀티시그 월렛은 일반 월렛에 비해 보안이 더 우수하고 실수나 해킹으로 인한 사고를 방지할 수 있기를 기대하고 사용한다.
하지만 편의성이 부족하여 일반 월렛에 비해 사용하기가 쉽지 않고 해킹사고가 없었던 것도 아니다.
2017년 11월에 멀티시그 월렛(패리티)이 해킹되어 약 2억 8000만 달러가 묶여버리는 사고가 있었다.
또한 2019년 11월에는 업비트 핫 월렛에서 해커의 지갑으로 이더리움 34만 2천 개가 이동되는 사고가 있었다.
링크 기사에 보면 핫 월렛에 멀티시그가 적용되었다고 하는데 실제로 이더 스캔에서 주소를 검색해보면 Contract가 아닌 일반 Address로 검색된다.
컨트랙트를 이용하지 않고 다른 방식의 멀티시그인 것으로 추측된다.

- 컨트랙트를 이용한 멀티시그 월렛이라면 아래처럼 Contract로 표시되어야 한다.

멀티시그 월렛 종류
ConsenSys
- 멀티시그 컨트랙트에 필요한 모든 공통 기능을 포함하였으나, 꾸준한 유지 관리가 되지 않았다.
- Gnosis 멀티시그 월렛에서 계속 개발이 되고 있다.
- https://github.com/ConsenSysMesh/MultiSigWallet
Gnosis
- ConsenSys 멀티시그 컨트랙트를 기반으로 하지만 지속적인 개발이 이루어지고 있고 많은 사람들이 사용하고 있다.
- https://github.com/Gnosis/MultiSigWallet
Argent
- “Guardians” 기능으로 다른 Argent 사용자를 선택하여 모든 출금에 그 사용자의 승인이 필요하도록 한다.
- https://www.argent.xyz/security/
BitGo
- end user 가 off-chain에서 서명을 하고 이 데이터를 또 다른 signer가 서명하여 트랜잭션을 발생시켜서 2 of 3을 만족해야 출금이 되는 방식이다.
- https://github.com/BitGo/eth-multisig-v2
나만의 멀티시그 월렛을 만들고 사용해 보자
본 아티클에서 말하는 멀티시그 월렛은 스마트 컨트랙트라고 했으니 생성하려면 컨트랙트를 배포해야 한다. 위에 소개한 멀티시그 월렛 목록 중 필자는 Gnosis 멀티시그 컨트랙트를 이용해 보려고 한다.
1. Gnosis 멀티시그 컨트랙트 다운로드
- Gnosis git hub에서 MultiSigWallet Contract를 다운로드한다.
2. Remix에서 컴파일
크롬 브라우저를 실행하고 Remix 사이트로 이동한다.
다운로드한 MultiSigWallet.sol 파일을 Remix에서 컴파일 한다.
- 단, compiler 버전을 아무거나 하면 에러가 표시되었고, 0.4.16+commit.d7661dd9를 사용해야 에러 없이 컴파일이 되었다.

3. MultiSigWallet 배포
컴파일이 끝났으면 컨트랙트를 체인에 배포 한다.
좌측 메뉴 중 Deploy & Run Transactions를 선택한다.
Environment 를 “Injected Web3″로 변경한다.
메타마스크를 연결한다.
- Ropsten network 선택
Deploy 버튼 우측에 owner address로 쓸 주소 2개 이상과 필요한 confirm 수 2 이상을 입력 후 transact 버튼을 누른다.
- 주의할 사항은 메타마스크 지갑 하나에서 계정을 여러 개 생성해서 그것들을 owner address로 설정하면 나중에 confirm transaction 을 날릴 때 한 개는 성공해도 같은 지갑 다른 주소로 두 번째 날릴 때부터는 실패가 뜨게 된다.
- 그러므로 owner address는 각각 다른 지갑의 주소를 입력해 주도록 하자.
// 예를 들어 아래와 같이 입력한다. _OWNERS: ["0xBc42F665C48A3C9Fe9263994D02b87d6983f0665","0x1D2A53AF862D3315Dc61F1A21AE42A5C02e91Ee6"] _REQUIRED: 2

- 그러면 메타마스크에 확인 노티 팝업이 뜨는데 확인 버튼을 눌러 준다.
- 그러면 우측 아래 콘솔에 Ropsten 이더스캔 페이지의 url 이 출력되고 잠시 기다리면 배포 완료 메시지가 뜬다.

-
Deploy & Run Transactions 메뉴의 하단에 보면 MULTISIGWALLET AT 0xF70… 확장 메뉴가 보인다.
- 이를 확장해 보면 컨트랙트의 기능 목록이 보이고 트랜잭션을 날려볼 수 있는 UI가 보인다.

4. Ether 를 전송해 보자.
4-1. 이더 입금
- 우측 하단 콘솔창에 출력된 링크로 들어가서 생성된 Contract 주소를 클릭해 보면 아래와 같이 현재 멀티시그 월렛의 상태를 볼수 있다.
- 멀티시그 월렛 주소: 0xF70f1631B4Cf7d7aD54fE687B59bA970FAcD5B8d

멀티시그가 잘 되는지 확인해 보려면 저 멀티시그 월렛에 이더를 넣어야 한다.
메타마스크를 이용해 멀티시그 월렛 주소로 이더를 전송한다. (2 이더가 전송 완료되었다.)

4-2. 멀티시그 월렛에서 이더 전송
좌측 Deployed Contracts 항목에 표시된 MultiSigWallet 기능 중 submitTransaction 부분을 확장하여 이더를 전송할 주소, value, data를 넣고 transact 버튼을 클릭한다.
- data 항목에 아무것도 안넣으면 아래와 같은 메시지가 뜨고 transact가 실패하므로 0x라고 넣자.
- 여기서는 0.1 Ether를 보내보겠다.

- 이때 submitTransaction 하는 owner의 address에는 당연히 수수료가 있어야 한다.

- 아래와 같이 submit transaction이 멀티시그 월렛에 등록되었다.

- 하지만 아직 이더는 전송되지 않고 멀티시그 월렛에 2이더가 그대로 남아있다.
4-3. confirm transaction (Ether)
confirm transaction 을 하기 전에 submitTransaction의 confirm 수를 조회해 보자.
- 아무도 confirm 하지 않았는데 주소가 들어있다.
- 이는 submitTransaction 함수 안에 confirmTransaction까지 불러주는 코드가 있기 때문이다.

/// @dev Allows an owner to submit and confirm a transaction.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return Returns transaction ID.
function submitTransaction(address destination, uint value, bytes data)
public
returns (uint transactionId)
{
transactionId = addTransaction(destination, value, data);
confirmTransaction(transactionId);
}
transactionId는 최초 submitTransaction 이면 0일 것이다.
- 우측 하단의 콘솔 창에 submitTransaction 실행한 것을 확장해보면 transactionId를 확인할 수 있다.

- 또 다른 owner 계정으로 메타마스크에 로그인하여 confirmTransaction 을 실행해 보자.

- confirmTransaction 이 승인되면서 submitTransaction 이 실행된 로그가 보인다.

- getConfirmations 0 해보면 주소 두 개가 보인다.

- Ropsten scan으로 확인해 보면 트랜잭션들이 정상적으로 승인되고 1.9 Ether 가 남은 것을 볼 수 있다.

- 왜 executeTransaction 을 실행하지 않았는데도 이더가 전송되었을까? 그건 confirmTransaction 함수에서 executeTransaction 을 실행하도록 되어있기 때문에 confirm 수가 맞춰지는 confirmTransaction 이 들어오게 되면 자연스럽게 executeTransaction 이 실행된다.
/// @dev Allows an owner to confirm a transaction.
/// @param transactionId Transaction ID.
function confirmTransaction(uint transactionId)
public
ownerExists(msg.sender)
transactionExists(transactionId)
notConfirmed(transactionId, msg.sender)
{
confirmations[transactionId][msg.sender] = true;
Confirmation(msg.sender, transactionId);
executeTransaction(transactionId);
}
5. ERC20 토큰을 전송해 보자.
5-1. ERC 20 토큰 배포
- 미리 ERC 20 JUSTIN 토큰을 배포해 두었다.
- JUSTIN 토큰 주소: 0x30F5b49C8Eb7DE72C7B370805071FB4Aa49B457d

미리 필자가 만들어 뒀던 ERC20 JUSTIN 토큰을 멀티시그 월렛으로 100개를 전송해보면 아래와 같이 보이게 된다.

5-2. 멀티시그 월렛에 있는 토큰을 다른 주소로 전송해 보자.
- destination 은 JUSTIN token의 주소를 입력한다.
- value는 0 을 넣고,
- data에는 abi encoding 된 byte code를 넣어야 한다.
- 온라인 abi encoding 사이트가 있어서 사용했다.
- 위 사이트에서 아래쪽 “enter your parameters manually” 부분에 아래와 같이 입력하면 encoding 을 해준다.
- Address에는 실제 토큰을 받을 주소를 입력한다.

- 바이트 코드를 복사하고 앞에 “0x”를 붙여 submitTransaction의 data 항목에 넣고 transact 한다.

5-3. confirm transaction (ERC 20)
- 토큰 전송 submitTransaction에 대해 마찬가지로 다른 owner의 confirm 이 필요하다.
- 이번 transactionId는 1이다.

- 또 다른 owner로 confirmTransaction 을 해준다.

- 0.05 JUSTIN 토큰이 전송된것을 확인할 수 있다.

5-4. 전송된 토큰 확인
메타마스크로 들어가 보면 토큰이 전송되지 않은 상태이다.
- 토큰을 직접 추가해 줘야 보인다.
토큰 추가 버튼을 통해 토큰 주소를 입력하면 등록이 되고 전송된 0.05 JUSTIN 토큰이 보인다.
- JUSTIN 토큰 주소: 0x30F5b49C8Eb7DE72C7B370805071FB4Aa49B457d

6. 이더와 토큰을 동시에 보낼 수 있을까?
- 궁금해서 해봤는데 안 되는 것 같다.

- value 와 data 둘 다 입력하고 transact 하면 메타마스크 팝업이 뜨는데 value 가 0으로 되어있고 수정도 할 수 없다.

- 확인 누른 후 트랜잭션을 열어보면 value 는 0이고 data만 들어있는 걸 확인할 수 있다.

- 이더와 토큰을 동시에 전송하는 건 안되는 걸 알 수 있었다.
마치는 말
이렇게 특별히 코드 수정 없이도 나만의 이더리움 멀티시그 월렛이 간단하게 완성된 것을 확인할 수 있었다.
해킹 뉴스를 심심치 않게 볼 수 있는 요즘, 사용에 약간의 불편함이 있겠지만 중요한 자산인 경우 멀티시그 월렛을 사용하여 조금 더 안전하게 보관, 관리하면 좋을것 같다.
<참고>