// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) public {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
使用者必須正確執行 unlock 函數即可通關 (取得 password)
slot 是 solidity 裡存儲資料的方式,合約上的所有資料都會被完整的分配在 slot 裡面,每個 slot 的大小為 32bytes,即相當於每個 slot 能夠儲存一個 uint256 大小的資料。
每個 contract 擁有 2**256 個 slot,變數都會有序的儲存於 slot 內部,並且由 slot[0] 開始做分配(除了 mapping 和 dynamically-sized array 以外)
並且若能恰當的將變數分配至 slot 內部,將合約所使用的儲存空間降至最低,甚至能夠達到節省 gas 的作用喔。
從區塊鏈的區塊鏈上所有的資料都是公開的,而智能合約自然不例外,這也就暗示了一件事 - 智能合約上所存儲的資料都是能夠從外界瀏覽的,自然,private 型態的變數也不例外。
在 ethernaut 中,玩家可以使用以下指令查看 slot 內所存儲的資料。
await web3.eth.getStoregeAt(address, slot_index)
因為這關是玩家們第一次用上 slot 因此就先初略的算看看 slot 的位子吧
(更詳細的 slot 儲存位子介紹,請詳閱 Day16 (等我發到 Day 16 就會有了xD) )
bool public locked;
bytes32 private password;
由於 locked 占用了 1 個 bits,所以他會被儲存在 slot[0],可以在 web console 中使用指令查看儲存資料。
await web3.eth.getStorageAt(await contract.address, 0)
可以看到,儲存在裡面的值為 1 (true),且也因為每個 slot 只有 32bytes,所以 password 一個變數就必須使用一整個 slot 做存儲。
那接下來就來看看下一個變數 password 在智能合約裡的存儲狀況
await web3.eth.getStorageAt(await contract.address, 1)
這樣就能看到 password 儲存在合約中的資料了,也可以發現即使我們將 password 設定為 private 型別,依然沒有辦法阻止外部使用者查閱資料,因此,Solidity 中的 private 型態能阻止其他合約取用變數,但不能阻止使用者查看變數內值(無法將變數保密)。
正如上述,我們可以直接查看 slot 內的 password 來取得密碼並輸入
password = await web3.eth.getStorageAt(await contract.address, 1)
await contract.unlock(password)
(‾ ⌣ ‾) (‾ ⌣ ‾) (‾ ⌣ ‾) (‾ ⌣ ‾)
由於區塊鏈上所有資料都是公開的,就連設定為 private 的變數也不例外,因此我們可以使用 private,只能避免其他 contract 的存取,並不能達到保密用途,Private 其實一樣是 Public 的,如果一定要使用到保密行為,應該將資料進行加密後在上傳到區塊鏈,並且鑰匙絕對不能上鏈,而如有這方面的需要,可以轉而研究 zk-SNARKs
https://medium.com/taipei-ethereum-meetup/solidity-data-collision-51e88f1529a8
https://medium.com/coinmonks/a-quick-guide-to-hack-private-variables-in-solidity-b45d5acb89c0