Skip to main content

Castle Roulette PF

Written by Birate

terms used

server seed: 64-character hex string generated using crypto.randomBytes(32) and hashed with SHA-256

drand seed: essentially your client seed from other modes, Duel's is from https://www.drand.love/ - it's public, unpredictable and verifiably random information

nonce: always a 0, but means the round number in other modes (exists for consistency with other games and does not change between rounds)

drand round ID: points to a future randomness value on the Drand's public timeline

HMAC: cryptographic function that mixes unhashed server seed, drand seed and nonce to produce a result

here's a 3rd party HMAC tool to verify each step for yourself: https://cryptii.com/pipes/hmac - (or any other one you prefer)


how HMAC is used for roulette multiplier, example round to demonstrate

server seed:
d8580640807ed36fd1c07bb56f08c76cbfb8bf35ae436b985ea6af0fd339d45

drand: 8f1faba9214b2f6e23288e35c566c80b2ee5c7ed3f3ec5f0bfc746196fae8d7cd47c1c6a89540132b40bce788ba7971b

first, the drand randomness is converted into bytes and decoded to UTF-8
invalid sequences are replaced by unicode character " " (U+FFFD)

some byte sequences can't exist in UTF-8, this is standard for text decoder and doesn't affect fairness

use this command in browser console to easily decode the Drand

const d='REPLACEWITHDRAND';console.log(Array.from(new TextEncoder().encode(new TextDecoder().decode(Uint8Array.from(d.match(/.{2}/g).map(x=>parseInt(x,16)))))).map(b=>b.toString(16).padStart(2,'0')).join('')+'3a30');
(the 3a30 added to the end is due to the nonce value, this is 0 for every round which means it doesn't change)


here's what the provided Drand Randomness looks like as hexadecimal bytes: efbfbd1fefbfbdefbfbd214b2f6e2328efbfbd35efbfbd66efbfbd0b2eefbfbdefbfbdefbfbd3f3eefbfbdefbfbdefbfbd46196fefbfbdefbfbd7cefbfbd7c1c6aefbfbd540132efbfbd0befbfbd78efbfbdefbfbdefbfbd1b3a30

that with the HMAC key (unhashed server seed) gives the following hexadecimal value:

93a0be7b91717e7883237334769ca4d802e89709a17885af03a8e5ad06908085


okay cool but how does that equate to a roulette number?

the first 8 hex characters are used from the final string, in this case "93a0be7b" then converted from hexadecimal decimal (93a0be7b 2476785275)

here's the formula to calculate roulette result using the decimal value:
2476785275 % 48 = 11

(essentially meaning 'if you divide x by 48, what number is left over')

the final roulette result was 8x as can be verified here

individual segments of the roulette wheel aren't tied to a specific 0-47 number, meaning that two consecutive rounds with a roll value of '11' can land on two different spaces containing the same multiplier (8x).


payout table

Roll values

Count

Probability

Payout

0

1

1 / 48

48x

1-2

2

2 / 48

24x

3-5

3

3 / 48

16x

6-11

6

6 / 48

8x

12-23

12

12 / 48

4x

24-47

24

24 / 48

2x


yeah but you're still doing tricks in the backend to rig the game

Drand randomness is public and verifiable, Duel doesn't have control over it. the hashed server seed and drand round ID's are visible before the round starts, this locks in the result - proving it can't be altered or manipulated by anyone
Drand round ID and the corresponding Drand Randomness string can be found here: https://api.drand.sh/v2/beacons/quicknet/rounds/25193852 with 25193852 being the round above


TL;DR

• drand acts as the 'client seed' from other modes

• duel's server seed is chosen before the drand is revealed, meaning that the final HMAC output can't be predicted

• duel can't control the drand value

• all rounds can be verified using 3rd party tools

• if drand round is commited to and hashed server seed shown before the round starts, it's provably fair.

• the midget pulling levers has no say in what the outcome is

Did this answer your question?