Ethereum ain’t hiding your secrets

Technical walkthrough on how to reveal states in contract private variables

Spammy Fun Image from Blog 4Linux

Foreword

This is a technical tutorial that takes you through the exact steps of retrieving states from the private variables of an Ethereum smart contract. All the techniques here can be applied to real life.

This tutorial is encouraged by Ethernaut Lvl8 challenge.

What you might need

truffle / ganache-cli / Node.js / curl

Contract code

Here is a very simple contract with four private variables with four different types: uint256, boolean, uint16, and mapping. The values are initialized when the contract is deployed to the network. Let’s assume the contract has been deployed to “0xb3b4546a6d8c8cd1081ac94a7e06a24b207c863d”.

contract PrivateStorage {
// Private Variables
uint256 private v0;
bool private v1;
uint16 private v2;
mapping(uint => uint) private v3;
    // Initializations    
constructor() {
v0 = 5;
v1 = true;
v2 = 6;
v3[1] = 7;
}
}

Technique (1) — Revealing uint256

It is impossible to access the value of the contract by calling from external. To skip the barrier, we need to directly access the contract’s storage by the Web3 API “eth_getStorageAt”. By executing the following command in the console/terminal, we are accessing the contract at “0xb3b45…” at the 0th state, which is the first declared variable v0.

curl -X POST - data '{"jsonrpc":"2.0", "method": "eth_getStorageAt", "params": ["0xb3b4546a6d8c8cd1081ac94a7e06a24b207c863d", "0x0", "latest"], "id": 1}' localhost:8545

The returned answer will be:

{"id":1,"jsonrpc":"2.0","result":"0x05"}

Indicating that the 0th value of the contract at “0xb3b45…” is 5!!!

Technique (2) — Revealing bool & uint16

Since EVM prone to squeeze multiple small states into the same slot, the boolean value v1 and the uint16 value v2 are assigned to the same storage slot position —the 1st position. By executing the similar command as technique (1), we can reveal the combined answer of both v1 and v2.

curl -X POST --data '{"jsonrpc":"2.0", "method": "eth_getStorageAt", "params": ["0xb3b4546a6d8c8cd1081ac94a7e06a24b207c863d", "0x1", "latest"], "id": 1}' localhost:8545

The response will be:

{"id":1,"jsonrpc":"2.0","result":"0x0601"}

Showcasing that the 1st private state v1 has value 1 (which is true), and 2nd private state v2 has value 6, that are concatenated together, one by one.

Technique (3) — Revealing mapping

Mapping has a different storage indicator, which is the hash of the mapping key concatenated with the mapping position in the contract. E.g. hash([mapping_key | mapping_position]). To find the concatenation, we have mapping key 1 to be appended with mapping position 2nd (because that the other private states v0, v1, v2 use up the first two positions.)

[mapping_key | mapping_position] = "0x1" + "0x2"

Technically, we need to create a variable comb in truffle or somewhere:

var comb = "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002"

We hash this comb to get the final indicator for the eth_getStorageAt accessing.

var indicator = web3.sha3(comb, {"encoding": "hex"})

In this case, we will get the indicator as “0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0”, which is used in the following command:

curl -X POST --data '{"jsonrpc":"2.0", "method": "eth_getStorageAt", "params": ["0xb3b4546a6d8c8cd1081ac94a7e06a24b207c863d", "0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0", "latest"], "id": 1}' localhost:8545

The successful result will be:

{"id":1,"jsonrpc":"2.0","result":"0x07"}

That’s it. We are done here. :)

Summary

There is no way that you can hide information by purely leveraging a private variable on the smart contract. It is highly suggested that you encode or encrypt any secret before sending it up to the blockchain that is always public for everyone to play with.

Great references

This story is published in The Startup, Medium’s largest entrepreneurship publication followed by + 372,020 people.

Subscribe to receive our top stories here.