Solidity 中的私有變量不私有

0x01 看下面被極度簡化過的合約代碼

// SPDX-License-Identifier: MIT

pragma solidity =0.8.19;

contract Auth {

    string private secret;

    constructor(string memory secret_) {
        secret = secret_;
    }
}

這個代碼里聲明了一個私有狀態變量 secret,部署合約的時候我往里面傳了一個值,這個變量的值是可以被讀到的么?

0x02 玻璃罩子

有時候感覺智能合約就像是放在區塊鏈這個公開透明的玻璃罩子里面,對這個玻璃罩子外面的人來說,里面是沒有任何隱私可言的,不管智能合約的狀態變量是 private 的還是 public 的,我們都可以很輕松的讀取里面的值。

這里的 private 只對同在這個罩子里的其它智能合約起作用,也就是說,如果一個狀態變量聲明為 private, 其它智能合約是不能讀取這個值的,目前的 SLOAD 指令只能讀取當前智能合約的值。

0x03 如何讀取

其實我們有不止一種辦法來讀取到私有狀態變量 secret。
最常見的,我們可以使用 getStorageAt,我已經把這個簡單合約部署到 BSC 測試網上,地址為 0x4332401C3Ea3aeebF9813dFA3Fe3Ee581ef8572d
下面是我的操作步驟:

  1. 連上節點
> geth attach https://rpc.ankr.com/bsc_testnet_chapel
Welcome to the Geth JavaScript console!

instance: erigon/2.39.0/linux-amd64/go1.19.3
at block: 28080565 (Thu Mar 16 2023 09:19:09 GMT+0800 (CST))
 modules: debug:1.0 erigon:1.0 eth:1.0 net:1.0 rpc:1.0 trace:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d
  1. 調用 eth.getStorageAt
> eth.getStorageAt("0x4332401C3Ea3aeebF9813dFA3Fe3Ee581ef8572d",0)
"0x2431303000000000000000000000000000000000000000000000000000000008"

"0x2431303000000000000000000000000000000000000000000000000000000008" 就是私有狀態變量 secret 的值,因為這個變量是字符串類型,可以使用工具 https://codebeautify.org/hex-string-converter 進行數據轉換,把16進制數字轉換為一個字符串。

細心一點兒的話,可以會注意到我們獲取的數據最后面多了一個數字 8,這是因為字符串本質上是變長數組,需要一些特別處理,這里有更詳細的說明:https://docs.soliditylang.org/en/v0.8.10/internals/layout_in_storage.html#mappings-and-dynamic-arrays

getStorageAt 需要傳入兩個參數,第一個參數是合約地址,第二個參數是要讀取的狀態變量的存儲位置,只要我們知道的變量的位置,就能讀取到所存儲的值。不過很多時候計算存儲位置也是要費點兒功夫的。

其實有時候可能另一種辦法更簡單,就是直接從設置數據的交易里來讀取數據,比如 secret 這個變量是通過構造函數還設置的,那我去看部署合約的交易就好了。連上節點后,通過調用 eth.getTransaction,可以獲取到交易的 input 值。

> eth.getTransaction("0xfa73be19403e5361c97d86dc64f25b2e45af330780e301b213107bdbc611c4a2")
......
input: "0x60806040523480156100......05050565b603f806105076000396000f3fe6080604052600080fdfea2646970667358221220b8f76d5e70f478ae42c73d84dfdbe6c705c91a0db013260e4a6d0b5cdd4b620a64736f6c63430008130033000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000042431303000000000000000000000000000000000000000000000000000000000"
......

合約部署交易 input 值的最后一段 “42431303000000000000000000000000000000000000000000000000000000000” 就是要設置的 secret 相關內容,“24313030” 是 secret 參數的值,前面多出來的 "4" 代表的是參數的長度 4 個字節。不過這個本質上是讀取參數的值,而不是變量的值,參數的值和變量的值有時候是相同,有時候是不相同的。

0x04 如何在合約中保存秘密

很多時候這需要比較嚴密的設計,比如我提前給你準備了一個大禮包,這個禮包里放了一大筆 ETH 資產,但只有我把密碼告訴你之后你才能使用密碼把這個大禮包打開。這個時候,我們把密碼直接放合約肯定不行,那么把密碼的哈希值放合約里行么?其實也不行,如果不對地址進行校驗,當我把密碼告訴你之后,很多人都有可能跑在你前面把錢取走,那么多搶跑機器人都在那里蹲著呢。
下面是應對這種場景的簡單代碼,同時對密碼和賬戶地址進行了校驗。

// SPDX-License-Identifier: MIT

pragma solidity =0.8.19;

contract Gift {

    bytes32 private secretHash;
    address private owner;

    constructor(bytes32 secretHash_, address owner_) {
        secretHash = secretHash_;
        owner = owner_;
    }

    function withdraw(string calldata secret) external {
        require(keccak256(abi.encodePacked(secret)) == secretHash);
        require(msg.sender == owner);
        payable(owner).transfer(address(this).balance);
    }

    receive() external payable{}
}

0x05 啟發

其實還有很多其它場景,比如口令紅包啥的,都有通過智能合約對某個秘密進行校驗的需求,要時刻謹記,智能合約里沒有秘密。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容