1. 개념

MultiSig(Multi-Signature) Wallet 은 여러 명의 사용자가 공동으로 자금을 관리하도록 설계된 스마트 컨트랙트입니다. 특정 수의 승인자가 서명해야만 트랜잭션이 실행되도록 설정할 수 있어 보안성을 강화할 수 있습니다. 


 

2. 주요특징

  • 다중 서명 요구: 단일 개인키가 아닌 여러 개인 키가 필요하여 보안성이 높음.
  • M-of-N 승인 방식 : N명의 소유자 중 M 명이 서명해야 트랜잭션이 실행됨 (e.g. 2-of-3)
  • 보안성: 개인 키가 유출되더라도 다중 서명이 필요하여 해킹 위험이 감소

 

3. 코드 구현

다음 기능들을 반영한 예제 코드를 작성해 봅시다. 

  • 지갑 소유자 등록 및 병경 가능
  • 지정된 최소 서명 개수만큼 서명이 모이면 트랜잭션 실행
  • 트랜잭션이 승인되지 않으면 실행되지 않음
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MultiSigWallet {
    address[] public owners;  // 다중 서명 지갑 소유자 목록
    uint public requiredSignatures; // 필요한 서명 개수

    struct Transaction {
        address to;
        uint value;
        bytes data;
        bool executed;
        uint numConfirmations;
    }

    mapping(uint => mapping(address => bool)) public isConfirmed; // 특정 트랜잭션에 대한 승인 상태
    Transaction[] public transactions;

    event Deposit(address indexed sender, uint amount);
    event TransactionSubmitted(uint indexed txIndex, address indexed to, uint value, bytes data);
    event TransactionConfirmed(uint indexed txIndex, address indexed owner);
    event TransactionExecuted(uint indexed txIndex, address indexed to, uint value);
    
    modifier onlyOwner() {
        require(isOwner(msg.sender), "Not an owner");
        _;
    }

    modifier txExists(uint _txIndex) {
        require(_txIndex < transactions.length, "Transaction does not exist");
        _;
    }

    modifier notExecuted(uint _txIndex) {
        require(!transactions[_txIndex].executed, "Transaction already executed");
        _;
    }

    modifier notConfirmed(uint _txIndex) {
        require(!isConfirmed[_txIndex][msg.sender], "Transaction already confirmed");
        _;
    }

    constructor(address[] memory _owners, uint _requiredSignatures) {
        require(_owners.length > 0, "At least one owner required");
        require(_requiredSignatures > 0 && _requiredSignatures <= _owners.length, "Invalid required signatures");

        owners = _owners;
        requiredSignatures = _requiredSignatures;
    }

    function isOwner(address _address) public view returns (bool) {
        for (uint i = 0; i < owners.length; i++) {
            if (owners[i] == _address) {
                return true;
            }
        }
        return false;
    }

    receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }

    function submitTransaction(address _to, uint _value, bytes memory _data) public onlyOwner {
        uint txIndex = transactions.length;

        transactions.push(Transaction({
            to: _to,
            value: _value,
            data: _data,
            executed: false,
            numConfirmations: 0
        }));

        emit TransactionSubmitted(txIndex, _to, _value, _data);
    }

    function confirmTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) {
        Transaction storage transaction = transactions[_txIndex];
        transaction.numConfirmations += 1;
        isConfirmed[_txIndex][msg.sender] = true;

        emit TransactionConfirmed(_txIndex, msg.sender);

        if (transaction.numConfirmations >= requiredSignatures) {
            executeTransaction(_txIndex);
        }
    }

    function executeTransaction(uint _txIndex) internal txExists(_txIndex) notExecuted(_txIndex) {
        Transaction storage transaction = transactions[_txIndex];

        require(transaction.numConfirmations >= requiredSignatures, "Not enough confirmations");

        transaction.executed = true;

        (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
        require(success, "Transaction execution failed");

        emit TransactionExecuted(_txIndex, transaction.to, transaction.value);
    }

    function getTransaction(uint _txIndex) public view returns (
        address to,
        uint value,
        bytes memory data,
        bool executed,
        uint numConfirmations
    ) {
        Transaction storage transaction = transactions[_txIndex];
        return (
            transaction.to,
            transaction.value,
            transaction.data,
            transaction.executed,
            transaction.numConfirmations
        );
    }
}

 


 

4. 코드 설명

  • 지갑 소유자 등록
    • 생성자에서 MultiSig 지갑의 소유자 목록을 등록
    • 특정 숫자의 서명이 필요함(requiredSignatures)
  • 입금 기능
    • receive() 함수를 통해 ETH 입금 기능
  • 트랜잭션 제출
    • submitTransaction() 함수를 호출하여 출금 트랜잭션을 제출
  • 트랜잭션 스인
    • confirmTransaction() 을 호출하여 각 소유자가 서명
    • 서명이 requiredSignatures 이상 모이면 자동으로 실행
  • 트랜잭션 실행
    • 서명이 충분하면 executeTransaction() 을 호출하여 송금 수행

 

5. MultiSig 의 장점과 단점

  • 장점
    • 보안성: 단일 키를 탈취해도 자금 이동 불가능
    • 거버넌스: 팀이나 조직이 공동으로 자금을 관리할 수 있음
    • 탈중앙화: 신뢰할 수 있는 단일 관리자가 필요 없음
  • 단점
    • 사용성: 다수의 서명자가 직접 승인해야 하므로 빠른 트랜잭션 실행이 어려움
    • 비용: 다중 서명 검증을 위한 추가적인 가스 비용이 발생

 

6. MultiSig Wallet 이 사용되는 사례

  • DAO: 팀이 공동 자금을 관리
  • 크립토 펀드: 투자금 보호
  • 스마트 계약 기반 서비스: 보안이 중요한 대규모 거래에서 활용

 

7. 결론

  • MultiSig Wallet 은 보안과 분산화를 강화하는 필수적인 스마트 컨트랙트.
  • 2-of-3, 3-of-5 등의 설정을 통해 다양한 협업 시나리오에 활용 가능.
  • DeFi, DAO 등에서 신뢰 없이 공동 자금을 관리하는 솔루션으로 널리 사용됨.

 

 

+ Recent posts