One of the most Common but Unheard Issues with Ethereum
If you have worked with Ethereum then you must have written smart contracts, and thus most probably used the keyword
private . It seems like it does its work well. Right? Well, actually not.
Anyone in this world with a blockchain explorer can see your so called private variables.
Sounds ridiculous, but yes its true. Let’s see how to hack a contract.
Let’s take a vulnerable contract.
It’s basically a game: an odd-even multiplayer game contract, which chooses the winner based on the numbers guessed. Each player chooses a number and if the sum is even then the first player wins, otherwise the second player wins.
The game contract stores the bets of two players in
players mapping. Since this variable is declared as private, the second player cannot read the data. Each player has to transfer 1 Ether to the contract in order to play. This condition is checked using
require. Once the second player places his bet, the winner is selected based on the odd-even logic and gets the whole bet amount of both players.
Let’s place our bet(execute
play(100)). As the number we bet is private so no one can see it. It’s like a secret…
But wait. Let’s see what actually happened when we executed this transaction.
State changes in Ethereum are usually done through a transaction. If the receiving account is a contract in a transaction, the EVM runs the contract’s code either to completion or until the execution runs out of gas.
Details, such as which method to call and input parameters are specified in the data field of each transaction. For example, for modifying a private state variable in the contract, you need to pass the “private” value to the setter method through a transaction. Considering the fact that every transaction data is visible to all the nodes, you could easily read private variables if you know the transaction.
Let’s look into the odd-event contract that we discussed above and see how you can decode the transaction data.
For every method call, the transaction data will have 2 fields:
- Method selector
- Method parameters
In our smart contract, call to the method will have the following transaction data. Let’s try to decode this.
First 4 bytes of the transaction data always points to the method signature. It is calculated by taking the first 4 bytes of
keccak hash of the method signature. In our example, it is
bytes4(keccak256('play(uint)')) which is
The following characters point to the parameter of the specified method. Each parameter is represented by the hex value of the input padded to 32 bytes. If 100 is the input parameter to
play method, then the transaction data for that parameter is:
Well, now any one with a block explorer and a bit of knowledge can place his 2nd bet accordingly and rip you off.
This can get more complex if there are more parameters and they are dynamic. You can get more details about argument encoding from the official solidity documentation.
The question now comes is that is there any way to avoid this kind of hack? Well there are some solutions to this:
- One is to use Quorum instead of Ethereum. It basically allows you to send private transactions. You can specify addresses of the recipients(in this case the address of the contract) in
privateForvariable which will only allow the recipients to see the data parameter; while others will receive a blank data parameter. (I have left a lot of details here on how the it works. You can find them here).
- Another way to tackle this problem while using Ethereum only is using something called commit reveal pattern. In this method, users first submit the hash of a secret information and when everyone else has submitted theirs, each participant reveals their vote, which can be back verified. This is not suited for all the applications, and it adds a lot of complexity on users, but it’s a starting point for further explorations.