Good evening Bitcointalk, as normal avenues have failed and the transcripts and conversations I've seen by Winna admins have been uninspiring, I am here to first lodge a complaint in our venerable community. Although you have seen better days, I believe this is a fitting place to post this accusation, as I have seen Bennett post here in reply to various topics, so he may well see mine, and perhaps then I will get a real answer or admittal.
My credibility: Some people in sections of the gambling community more adjacent to Stake may know me better as Ruby, of SealStats/formerly StakeStats. I have a background in cryptography and am sometimes sought out for my knowledge on this particular subject. I have received bug bounties from websites for pointing out issues similar to this in the past. I do not work off of feelings or off of belief, I make conclusions from facts. The fact of the matter is that I can't verify or substantiate Winna's claims to being provably fair.
If you cannot give me a good answer to this post, Bennett, you are not going to be able to brush it off this time; everyone else who has tried to fight you was not well informed enough to make a good point.
Core accusation:
Winna does not provide an appropriate platform for provable fairness, and the scheme is not provably committed.I am not accusing them of
actually displacing or replacing bets, but I
am accusing them of
creating an environment where it is easily possible for them to do so.
I will try to explain what exactly is required in a provably fair system as well, so that if you correct your lossy algorithm, then maybe it can actually be provably fair, and not effectively 'trust me bro'.
How does Winna's provable fairness work?Like other sites, an active server and client seed are used during bet generation, starting with a 0 nonce (number only used once) which iterates up with each
non-zero bet.
- It is important to note that the nonce
does not increase on zero bets, ...but a bet result is given, in the api as well, not just in the UI. The result is different eery time. This implies that there is a calculation happening even[/b] without all the necessary parts of a provable bet[/b].
- On any other site, zero bets and non-zero bets are identical, and both should iterate a nonce up, as the entropy is consumed. Whether psychology permits it or not,
it should not matter if there is a value or not to the bet, in whether it's valid or skipped.
There is no mention of a cursor value, (aka round), which is the final part of the bet calculation in several games (notably card games, and multi-step games like tower or mines). There are no formulas available to reverse or manually verify any bets. However,
it is important to note that for the most part, these appear to be 1:1 Stake-style (hmac client:nonce:round) construction style algorithms, as they are in fact repeatable through typical verifiers. This is rather unusual, since it implies they do not realize what is actually important in a provable committment scheme, aka, the server seed provision as a hash to the player.
How is the server seeed handled?
ACTIVE CLIENT
e5ddc392815068901e5357368ec017d0
PREVIOUS CLIENT
e5ddc392815068901e5357368ec017d0
ACTIVE SERVER
a3d6af6b3b90ddfcc781a468891209ccf72d1903e55883f70d3aea8a80846343
UNHASHED SERVER
f6e72b075f6536f4b1f053ad8d19eafe
Here is a sample of some seeds which I just rotated.
Note: You cannot rotate a seed at 0 nonce. You must make at least one bet; that is, a wager bet, as remember: zero bets do not iterate nonce up. What is interesting to note is that the unhashed server seed is a 32 byte string
f6e72b075f6536f4b1f053ad8d19eafe
, which appears to be an MD5 hash.
This is rather strange, it would be better and more appropriate to do the standard thing and use a SHA256/512 hash as the server seed. --- I wonder why they are generating such small values, and assigning them with client seeds that are pre-paired?
There is no way to enter in my own client seed- so I am stuck with a similarly weak MD5 hash, just 32 bytes of weak entropy..
So, regardless of that, what kind of algorithm has been used to hash the server seed, so that we can
prove it's the same one they gave us beforehand?
Erm, well, about that...
I can't figure it out.
When asked, no one else could either. I even held a challenge in our discord, and prize was up to around $500 by the end, to just match the damn hash with a real algorithm. One guy tried so damn hard I gave him a consolation prize. I tried as str, I tried as bytes... I tried a lot of different ways. I gave it to a professional, who also couldn't do it. So what is the damn hash algorithm? Why should we trust Winna if we can't even trust the hash?
So far, I know that it's not any of these:
sha1: no match
sha224: no match
sha256: no match
sha384: no match
sha512: no match
sha3_224: no match
sha3_256: no match
sha3_384: no match
sha3_512: no match
md5: no match
blake2b: no match
blake2s: no match
shake_128_32: no match
shake_256_64: no match
ripemd160: no match
md4: no match
md2: no match
sha256_sha256: no match
sha256_sha512: no match
sha512_sha256: no match
sha3_384_sha3_224: no match
sha3_256_sha3_512: no match
sha3_512_sha3_256: no match
sha256_ripemd160: no match
md5_sha256: no match
sha256_md5: no match
sha1_sha256: no match
sha256_sha1: no match
sha512_sha3_512: no match
sha3_512_sha512: no match
blake2b_sha256: no match
sha256_blake2b: no match
sha256_sha3_256: no match
sha3_256_sha256: no match
sha256_sha3_512: no match
sha3_512_sha256: no match
sha256_sha3_224: no match
sha3_224_sha256: no match
sha256_sha3_384: no match
sha3_384_sha256: no match
sha256_blake2s: no match
blake2s_sha256: no match
sha256_md4: no match
md4_sha256: no match
pbkdf2_sha256_1000: no match
pbkdf2_sha512_1000: no match
pbkdf2_sha256_10000: no match
pbkdf2_sha512_10000: no match
CODE USED:
import hashlib
import Crypto.Hash.RIPEMD160 as ripemd
def detectHashAlgo(hexStr):
b = bytes.fromhex(hexStr)
hashes = {}
# single hash funcs
hashes['sha1'] = hashlib.sha1(b).hexdigest()
hashes['sha224'] = hashlib.sha224(b).hexdigest()
hashes['sha256'] = hashlib.sha256(b).hexdigest()
hashes['sha384'] = hashlib.sha384(b).hexdigest()
hashes['sha512'] = hashlib.sha512(b).hexdigest()
hashes['sha3_224'] = hashlib.sha3_224(b).hexdigest()
hashes['sha3_256'] = hashlib.sha3_256(b).hexdigest()
hashes['sha3_384'] = hashlib.sha3_384(b).hexdigest()
hashes['sha3_512'] = hashlib.sha3_512(b).hexdigest()
hashes['md5'] = hashlib.md5(b).hexdigest()
hashes['blake2b'] = hashlib.blake2b(b).hexdigest()
hashes['blake2s'] = hashlib.blake2s(b).hexdigest()
hashes['shake_128_32'] = hashlib.shake_128(b).hexdigest(32)
hashes['shake_256_64'] = hashlib.shake_256(b).hexdigest(64)
# extra algos
try:
hashes['ripemd160'] = ripemd.new(b).hexdigest()
except Exception:
hashes['ripemd160'] = None
try:
hashes['md4'] = hashlib.new('md4', b).hexdigest()
except Exception:
hashes['md4'] = None
try:
hashes['md2'] = hashlib.new('md2', b).hexdigest()
except Exception:
hashes['md2'] = None
# combos
hashes['sha256_sha256'] = hashlib.sha256(hashlib.sha256(b).digest()).hexdigest()
hashes['sha256_sha512'] = hashlib.sha512(hashlib.sha256(b).digest()).hexdigest()
hashes['sha512_sha256'] = hashlib.sha256(hashlib.sha512(b).digest()).hexdigest()
hashes['sha3_384_sha3_224'] = hashlib.sha3_224(hashlib.sha3_384(b).digest()).hexdigest()
hashes['sha3_256_sha3_512'] = hashlib.sha3_512(hashlib.sha3_256(b).digest()).hexdigest()
hashes['sha3_512_sha3_256'] = hashlib.sha3_256(hashlib.sha3_512(b).digest()).hexdigest()
hashes['sha256_ripemd160'] = None
try:
hashes['sha256_ripemd160'] = ripemd.new(hashlib.sha256(b).digest()).hexdigest()
except Exception:
pass
hashes['md5_sha256'] = hashlib.sha256(hashlib.md5(b).digest()).hexdigest()
hashes['sha256_md5'] = hashlib.md5(hashlib.sha256(b).digest()).hexdigest()
hashes['sha1_sha256'] = hashlib.sha256(hashlib.sha1(b).digest()).hexdigest()
hashes['sha256_sha1'] = hashlib.sha1(hashlib.sha256(b).digest()).hexdigest()
hashes['sha512_sha3_512'] = hashlib.sha3_512(hashlib.sha512(b).digest()).hexdigest()
hashes['sha3_512_sha512'] = hashlib.sha512(hashlib.sha3_512(b).digest()).hexdigest()
hashes['blake2b_sha256'] = hashlib.sha256(hashlib.blake2b(b).digest()).hexdigest()
hashes['sha256_blake2b'] = hashlib.blake2b(hashlib.sha256(b).digest()).hexdigest()
hashes['sha256_sha3_256'] = hashlib.sha3_256(hashlib.sha256(b).digest()).hexdigest()
hashes['sha3_256_sha256'] = hashlib.sha256(hashlib.sha3_256(b).digest()).hexdigest()
hashes['sha256_sha3_512'] = hashlib.sha3_512(hashlib.sha256(b).digest()).hexdigest()
hashes['sha3_512_sha256'] = hashlib.sha256(hashlib.sha3_512(b).digest()).hexdigest()
hashes['sha256_sha3_224'] = hashlib.sha3_224(hashlib.sha256(b).digest()).hexdigest()
hashes['sha3_224_sha256'] = hashlib.sha256(hashlib.sha3_224(b).digest()).hexdigest()
hashes['sha256_sha3_384'] = hashlib.sha3_384(hashlib.sha256(b).digest()).hexdigest()
hashes['sha3_384_sha256'] = hashlib.sha256(hashlib.sha3_384(b).digest()).hexdigest()
hashes['sha256_blake2s'] = hashlib.blake2s(hashlib.sha256(b).digest()).hexdigest()
hashes['blake2s_sha256'] = hashlib.sha256(hashlib.blake2s(b).digest()).hexdigest()
hashes['sha256_md4'] = None
try:
hashes['sha256_md4'] = hashlib.new('md4', hashlib.sha256(b).digest()).hexdigest()
except Exception:
pass
hashes['md4_sha256'] = None
try:
hashes['md4_sha256'] = hashlib.sha256(hashlib.new('md4', b).digest()).hexdigest()
except Exception:
pass
# PBKDF2 combos
try:
hashes['pbkdf2_sha256_1000'] = hashlib.pbkdf2_hmac('sha256', b, b'salt', 1000).hex()
hashes['pbkdf2_sha512_1000'] = hashlib.pbkdf2_hmac('sha512', b, b'salt', 1000).hex()
hashes['pbkdf2_sha256_10000'] = hashlib.pbkdf2_hmac('sha256', b, b'salt', 10000).hex()
hashes['pbkdf2_sha512_10000'] = hashlib.pbkdf2_hmac('sha512', b, b'salt', 10000).hex()
except Exception:
hashes['pbkdf2_sha256_1000'] = None
hashes['pbkdf2_sha512_1000'] = None
hashes['pbkdf2_sha256_10000'] = None
hashes['pbkdf2_sha512_10000'] = None
for algo, h in hashes.items():
print(f'{algo}: {h}')
for algo, h in hashes.items():
if h == 'a3d6af6b3b90ddfcc781a468891209ccf72d1903e55883f70d3aea8a80846343':
print(algo)
else:
print(f'{algo}: no match')
detectHashAlgo('e5ddc392815068901e5357368ec017d0')
This is a small portion and, in my opinion, the most obvious few flaws visible in Winna's claim to being provably fair. YES, Bennett, picking your own client seed DOES MATTER, it is the only thing that makes this fair at all!
As a short summary:
-- Winna's server seed hashing implementation is
not provable, or provably fair.
-- Winna's zero-bet nonce disuse is suspicious and the code being used in the backend should be audited by a third party, as all steps are not externally repeatable.
-- Winna not allowing you to pick your own client seed is a clear violation of standard provable fairness practices.
-- Despite these issues, they still claim their system is fair, and provable. They are either ignorant or malicious; and when in doubt as far as casinos go, they should be assumed malicious.
-- Winna's laggy originals imply excessive processing in the backend, or highly unoptimized code.
-- Winna has ties to a recent rug, Heybets, which is clear from even minor snooping in the DNS and page designs/structure.
-- Some questionable affiliate practices which are not the core of this complaint.
I hope that a satisfactory answer can be given on this matter, because I would be happy to change my opinion on what appears to me to be very clever fraud. I'm sorry it took me so long to get this out, as I was planning on posting it months ago, at this point.
If you need someone to fix your system since apparently it's either broken and vibecoded or intentionally malicious. If the former, then I offer my services at cost, and you will never have this issue again. Feel free to contact me directly at
ruby@stakestats.net, if you want to make this a professional discussion. If it was just broken, then I expect a bounty.
Additional seed pairs, tested from multiple different users as sources of different account generation:
https://docs.google.com/spreadsheets/d/10Bb7O0FUZoOi_npKMAdJqQQOSpsk98prA_c153LMEoc/edit?usp=sharingI hope I am mistaken and simply overlooked something critical, but I await a reply from Winna. Thank you.