1. 程式人生 > >智慧合約的安全

智慧合約的安全

智慧合約的安全問題一直是編寫智慧合約的關鍵點。多數的智慧合約都是開源的,原始碼公佈更容易被黑客找到攻擊的漏洞。 
這裡將一些常見的,易犯的錯誤。首先我們先看看下面這段程式碼:

contract text{
    address owner;
    function userWallet() public{
        owner == msg.sender;
    }
    
    function transferto(address add,uint num) public payable{
        if(tx.origin == owner){
            add.transfer(num);
        }
    }
}

  

這裡先講講其中tx.origin和msg.sender不同。msg.sender指的是呼叫合約的地址,而tx.origin指的是發起transaction的地址。舉個例子,看下面的程式碼。

pragma solidity^0.4.7;
contract c1{
    address add1;
    address add2;
    function findAdd() public {
        add1 = msg.sender;
        add2 = tx.origin;
    }
    function getAdd1() public returns(address){
        return add1;
    }
    function getAdd2() public returns(address){
        return add2;
    }
}
contract differ{
    address public a1;
    address public a2;
    function f1() public {
        c1 c = new c1();
        c.findAdd();
        a1 = c.getAdd1();
        a2 = c.getAdd2();
    }
}

  

執行完合約後,a1就是合約differ的地址,而a2是呼叫合約diiffer的地址,也就是發起transaction的地址。上面簡單講了tx.origin和msg.sender的區別。接下來我們回到第一個合約中,這個合約實現了一個轉賬的功能·。但這個合約存在bug。黑客可以利用這個漏洞進行攻擊。比如下面這段程式碼

contract attack{
  address hack;
  constructor() public{
    hack = msg.sender;
  }
  function () external{
    text(msg.sender).transferto(hack,msg.sender.balance);
  }
}

  

只要讓第一個text合約就會觸發attack合約中的匿名函式,這時就會向hack地址轉賬了。因此在text合約中應該使用msg.sender而不是tx.origin。

接下來還有一個不容易被找出來的錯誤,比如下面的合約

pragma solidity^0.4.7;
contract fund{
    mapping(address=>uint) num;
    function transferto(address add)public payable{
        if(num[add] != 0){
            add.transfer(num[add]);
            num[add] = 0;
        }
    }
}

  

這個合約實現一個兌換的功能,可以將每個address中所佔的數兌換成以太幣,黑客可以實現這樣一個合約

contract attack{
    address hack;
    constructor()public{
        hack = msg.sender;
    }
    function () external {
        fund(msg.sender).transferto(hack);
    }
}

  

這樣合約fund每次轉賬都會呼叫attack合約的匿名函式,而匿名函式中又會呼叫合約fund中的轉賬。便會一直重複,這時候為了防止這種情況。可以改成下面的程式碼

contract fund{
    mapping(address=>uint) num;
    function transferto(address add)public payable{
        uint temp = num[add];
        if(temp != 0){
            num[add] = 0;
            add.transfer(temp);
        }
    }
}

  

將兌換的num在轉賬之前重置為0。這樣即使用上述的程式碼進行攻擊亦不會再次執行轉賬了。