// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
玩家必須將合約解鎖(將 locked 改為 false)
雖然在 Day 11 我們曾初步討論了 Solidity 的 slot,相信大家或多或少是有印象的,不過倒也無妨,在第 12 關 Privacy 裡我們再來看一遍吧。
區塊鏈上所有的資料都是公開的,即使將智能合約上的變數設定為 Private 也一樣能輕鬆的挖出其所含的值,而查找方法便是查詢智能合約裡的 slot;每個 slot 為 32bytes,而每份智能合約中最多能夠使用 2^256 個 slot,而在智能合約中宣告的變數會直接儲存到 slot 上,依照每個變數所使用的 bytes 數完整的排列在 slot 上,舉個例子
uint8 a;
uint8 b;
uint256 c;
uint8 d;
變數 a、b 因為並未超過 slot[0] 的大小限制,因此 a b 將會被儲存至 slot[0],而 slot[0] 內所剩餘的 bytes 並無法容納變數 c,因此變數 c 將被儲存至 slot[1] 並填滿,而變數 d 將被儲存至 slot[2]。
值得一提的是,slot 的使用多寡將會直接影響智能合約所消耗的 gas,因此若是將程式碼改為
uint8 a;
uint8 b;
uint8 d;
uint256 c;
便能降低 gas 的消耗。
而如何查看 slot 呢 ? 可以使用 web console 的指令來查看
web3.eth.getStorageAt(contract address, slot_index)
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
那麼也和第8關一樣,來一起算算 slot 吧
addr = await contract.address
await web3.eth.getStorageAt(addr, 0).then(v => v.toString())
// '0x0000000000000000000000000000000000000000000000000000000000000001'
可以看到,第一個變數 locked 占用一個 bit,儲存在 slot[0],而由於 uint256 將會獨自占用一整個 slot,所以可想而知,slot[1] 為存放變數 ID 的 slot
addr = await contract.address
await web3.eth.getStorageAt(addr, 1).then(v => v.toString())
// '0x0000000000000000000000000000000000000000000000000000000062f94939'
接著呢是 3 個 uint,分別為 8、8、16,放置在一起也不會超過 slot 的大小,由此可知,slot[2] 存放的是變數 flattening、denomination、awkwardness
addr = await contract.address
await web3.eth.getStorageAt(addr, 2).then(v => v.toString())
// '0x000000000000000000000000000000000000000000000000000000004939ff0a'
註 :
0a 為 16 進位的 10
ff 為 16 進位的 255
4939 則為 awkwardness 變數內值
最後則是三個 bytes32,依序被儲存至 slot[3] slot[4] slot[5],可以依序查看
addr = await contract.address
await web3.eth.getStorageAt(addr, 3).then(v => v.toString())
// '0xc21f726e9f3dfa8f5f792cc02cf7fc2e21748193836679c4400b775a6b6c3c93'
addr = await contract.address
await web3.eth.getStorageAt(addr, 4).then(v => v.toString())
// '0x1cc5cc24454501831a6781978b2ff95eeceaf363e067989a74fb5d56991ae923'
addr = await contract.address
await web3.eth.getStorageAt(addr, 5).then(v => v.toString())
// '0xc267aea7f653fccc5d6f1373768685d968f07ec9385ac3baa00a5f22b06cef43'
那麼希望大家都學會了如何計算 slot,接著就進入通關的部分 :D
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
可以看到,只要我們將 data[2] 取出便能通關,而 data[2] 就儲存於 slot[5]
addr = await contract.address
await web3.eth.getStorageAt(addr, 5).then(v => v.toString())
// '0xc267aea7f653fccc5d6f1373768685d968f07ec9385ac3baa00a5f22b06cef43'
而 key 就是前半的 0xc267aea7f653fccc5d6f1373768685d9
key = await web3.eth.getStorageAt(addr, 5).then(v => v.slice(0, 34))
await contract.unlock(key)
把 key 送進去就可以通關囉~
⎦˚◡˚⎣ ⎦˚◡˚⎣ ⎦˚◡˚⎣ ⎦˚◡˚⎣