<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[CryptoPlay Guide]]></title><description><![CDATA[CryptoPlay Guide]]></description><link>https://cryptoplayguide.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 12:25:44 GMT</lastBuildDate><atom:link href="https://cryptoplayguide.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Crash Game Math: From a Hash Chain to Expected Return]]></title><description><![CDATA[A rising multiplier makes Crash look like a timing problem. The mathematics asks a different question: how often does a round reach a chosen target, and…]]></description><link>https://cryptoplayguide.hashnode.dev/crash-game-math-from-a-hash-chain-to-expected-return</link><guid isPermaLink="true">https://cryptoplayguide.hashnode.dev/crash-game-math-from-a-hash-chain-to-expected-return</guid><category><![CDATA[Mathematics]]></category><category><![CDATA[Cryptography]]></category><category><![CDATA[probability]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[CryptoPlay Guide]]></dc:creator><pubDate>Thu, 29 Oct 2026 05:31:34 GMT</pubDate><content:encoded><![CDATA[<p>A rising multiplier makes Crash look like a timing problem. The mathematics asks a different question: how often does a round reach a chosen target, and what does that probability imply for the return on a stake?</p>
<p>In Crash, the multiplier starts at 1.00× and rises until the round ends. Cashing out before the crash pays stake multiplied by the cashout multiplier; remaining in the round when it crashes loses the stake. Players in the same round share its result.</p>
<p>The model below is the published bustabit model described in the original CryptoPlay Guide article. Its 1% house edge is an assumption tied to that formula. Other operators may use different edges or rules, so this is not a universal specification for every game called Crash.</p>
<h2>Begin with the target probability</h2>
<p>For this model, the probability of reaching a target multiplier is:</p>
<pre><code class="language-text">P(result ≥ m) = 0.99 / m
</code></pre>
<p>The practical consequence is that a higher target pays more when reached but is reached less often. For example:</p>
<table>
<thead>
<tr>
<th>Cashout target</th>
<th>Chance the round reaches it</th>
<th>Expected return per 1 USDT</th>
</tr>
</thead>
<tbody><tr>
<td>1.01×</td>
<td>98.02%</td>
<td>0.99</td>
</tr>
<tr>
<td>1.5×</td>
<td>66.00%</td>
<td>0.99</td>
</tr>
<tr>
<td>2×</td>
<td>49.50%</td>
<td>0.99</td>
</tr>
<tr>
<td>3×</td>
<td>33.00%</td>
<td>0.99</td>
</tr>
<tr>
<td>5×</td>
<td>19.80%</td>
<td>0.99</td>
</tr>
<tr>
<td>10×</td>
<td>9.90%</td>
<td>0.99</td>
</tr>
<tr>
<td>100×</td>
<td>0.99%</td>
<td>0.99</td>
</tr>
</tbody></table>
<p>The constant expected return is the important relationship. Raising the cashout target changes win frequency and volatility, while the expected return remains the same under the stated model.</p>
<p>For a fixed target, the expected net result per unit staked is:</p>
<pre><code class="language-text">m × 0.99/m − 1 = −1%
</code></pre>
<p>Auto-cashout can select the target. It does not remove the factor responsible for that negative expectation.</p>
<h2>Trace the result back to the hash chain</h2>
<p>Crash games usually use a hash chain rather than separate per-player seed pairs. In this model, each game hash is the SHA-256 hash of the next one. The operator publishes the final hash and a salt in advance, then plays through the chain backwards.</p>
<p>That gives verification something concrete to check: the relationship between a revealed game hash and the previously committed chain value.</p>
<p>The bustabit calculation is:</p>
<pre><code class="language-text">h = HMAC_SHA256(key = salt, message = game hash)
X = (first 52 bits of h) / 2⁵²
(uniform in [0, 1))
result = max(1, floor(99 / (1 − X)) / 100)
</code></pre>
<p>Notice both the key placement and the mapping after the HMAC. The salt is the key, and the game hash is the message. The calculation takes the first 52 bits, then applies the multiplier formula and rounding.</p>
<p>The <code>99</code> in that formula, rather than <code>100</code>, supplies the model's 1% house edge. A reproducible result can therefore be entirely consistent with a negative expected return for the player.</p>
<h2>Check the commitment and the multiplier separately</h2>
<p>Verification has two parts. First, hash the game hash with SHA-256 and compare it with the previous round's hash. A matching chain relationship checks that the round fits the committed sequence.</p>
<p>Second, recompute the multiplier using the game hash, public salt and documented formula. This checks the transformation from the committed input into the displayed result.</p>
<p>CryptoPlay Guide's browser verifier includes a Crash mode at <a href="https://cryptoplay.guide/tools/provably-fair-verifier/">https://cryptoplay.guide/tools/provably-fair-verifier/</a>. Its presence does not remove the need to check which model the operator uses. Reproducing the wrong model is not verification of a different operator's rules.</p>
<p>A matching chain and multiplier show that a result follows the commitment and calculation. They do not predict the next crash or establish that an operator will reliably pay withdrawals.</p>
<h2>Rounding matters near the start</h2>
<p>Under this formula, about 1.98% of rounds end at 1.00×. This follows from the condition <code>99/(1−X) &lt; 101</code>: results below 1.01× round down to 1.00×.</p>
<p>That behavior belongs to the specific formula, including its rounding. When comparing a mathematical model with a live game, read the fairness documentation and the cashout rules together. CryptoPlay Guide's safety guide also calls out auto-cashout, ties and maximum-win rules as checks to make before relying on a formula.</p>
<p>A general probability calculation cannot establish those operating details by itself.</p>
<h2>Changing stakes does not change the edge</h2>
<p>A loss-recovery betting system changes how much is exposed on later bets. It does not change the expected return of the underlying game.</p>
<p>Consider Martingale at 2×: eight consecutive losses, approximately 0.42% from a given start in this model, multiply the stake by 256×. The increased exposure is part of the system, while the bets still carry negative expectation.</p>
<p>Likewise, a simulation or losing-streak calculator is a way to inspect a model. It cannot identify the next round's crash point. Choosing a higher multiplier or a different staking sequence does not turn verification into forecasting.</p>
<p>The useful conclusion is modest: inspect the commitment, reproduce the formula, and evaluate the house edge separately. Set an affordable loss limit and session time before playing. A cashout target controls a round; a spending limit controls how much you have decided to risk.</p>
<p>Originally published at <a href="https://cryptoplay.guide/guides/crash-game-math-explained/">https://cryptoplay.guide/guides/crash-game-math-explained/</a></p>
<p>CryptoPlay Guide, the site this was originally published on, earns affiliate commissions from some casinos it covers. This post contains no affiliate links.</p>
<p>18+ (or the higher legal age where you live). Gambling involves risk of loss; only play where legal, set deposit, loss and time limits, and seek help if gambling causes harm, for example from BeGambleAware.org.</p>
]]></content:encoded></item><item><title><![CDATA[From HMAC-SHA256 to Verifiable Results: A JavaScript Walkthrough]]></title><description><![CDATA[Recompute a seed commitment, decode four digest bytes, and check an open-source verifier against an independent implementation.]]></description><link>https://cryptoplayguide.hashnode.dev/from-hmac-sha256-to-verifiable-results-a-javascript-walkthrough</link><guid isPermaLink="true">https://cryptoplayguide.hashnode.dev/from-hmac-sha256-to-verifiable-results-a-javascript-walkthrough</guid><category><![CDATA[Cryptography]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Mathematics]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[CryptoPlay Guide]]></dc:creator><pubDate>Wed, 28 Oct 2026 10:16:55 GMT</pubDate><content:encoded><![CDATA[<p>A result can look random while still being reproducible. That is the useful idea behind a provably fair protocol: commit to a secret input, combine it with public inputs through a documented algorithm, and reveal the secret later so someone else can repeat the calculation.</p>
<p>This tutorial follows the actual <code>provably-fair-verifier</code> implementation. Its Dice and Limbo paths use HMAC-SHA256 with the server seed as the key. We will reproduce its example vector using Node's crypto module, then compare it with the repository's Web Crypto implementation. These are demonstration seeds, not records of real play. No account, deposit, or wager is needed.</p>
<h2>Start with a commitment</h2>
<p>Before a round, the server publishes <code>SHA256(serverSeed)</code>. After the seed is retired, it reveals the seed itself. Hashing that revealed string must reproduce the commitment saved earlier.</p>
<p>The timing matters. A hash obtained only after the result gives no evidence that the server committed beforehand. Save the original commitment alongside the client seed, nonce, recorded result, and algorithm version. Record the selected game and formula source too, since the same seed data can be interpreted through several incompatible result mappings.</p>
<p>A hash is not encryption, and verification does not decrypt it. Under the usual SHA-256 security assumptions, finding a different seed that satisfies the saved commitment is computationally infeasible. Concealing the original seed also depends on its entropy: a predictable seed can be guessed and checked against its hash.</p>
<p>Commitment restricts later substitution; it does not establish that the seed was selected without bias. Choosing an unpredictable client seed after the server commitment helps constrain advance selection against known client inputs. The order of events remains part of the protocol being audited.</p>
<h2>Specify the exact HMAC message</h2>
<p>The shorthand <code>HMAC-SHA256(server seed, client seed:nonce)</code> describes a family of schemes, not a universal wire format. In this repository, Dice and Limbo use the UTF-8 message <code>clientSeed:nonce:0</code>. The final zero identifies the first HMAC output block in the documented byte generator. Omitting it produces a different digest. See the operator's <a href="https://stake.com/provably-fair/implementation">implementation documentation</a> for that serialization.</p>
<p>The server seed is the HMAC key; the client seed and nonce are message components. HMAC is the keyed construction described in <a href="https://www.rfc-editor.org/rfc/rfc2104">RFC 2104</a>, with separate inner and outer hashing stages. Concatenating a secret and a message and hashing once does not implement HMAC.</p>
<p>For SHA-256, HMAC works with a 64-byte key block. Longer keys are hashed first; shorter ones are padded with zeros. The inner stage hashes the key block XORed with a repeated inner pad, followed by the message. The outer stage hashes a differently padded key block followed by that inner digest. A standard crypto library handles these details, so application code only needs to supply the correct key bytes and message bytes.</p>
<p>For a fixed key and message, HMAC is deterministic. With an unpredictable secret key, its output is intended to behave like pseudorandom bytes. Incrementing a nonce changes the message; it does not make the algorithm nondeterministic or promise that outcomes cannot repeat.</p>
<p>Encoding is equally important. Although the example server seed looks hexadecimal, this Dice/Limbo implementation hashes and keys with its literal UTF-8 characters. Decoding it into binary first changes the calculation. Preserve client seed spelling, case, spaces, and punctuation. Do not append a newline.</p>
<h2>Recompute the bytes independently</h2>
<p>With Node.js 18 or later, run this complete terminal command. It uses only built-in modules and needs no package installation:</p>
<pre><code class="language-bash">node --input-type=module &lt;&lt;'JS'
import { createHash, createHmac } from 'node:crypto';

const server = 'e3f1b5c2a9d84f6e7b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6';
const client = 'example-client-seed';
const nonce = 3;
const message = `${client}:${nonce}:0`;
const bytes = createHmac('sha256', server).update(message, 'utf8').digest();
const value = bytes.readUInt32BE(0) / 2 ** 32;
const dice = Math.floor(value * 10001) / 100;
const limbo = Math.max(Math.floor(1e8 / (value * 1e8) * 0.99 * 100) / 100, 1);

console.log('commitment=' + createHash('sha256').update(server, 'utf8').digest('hex'));
console.log('message=' + message);
console.log('digest=' + bytes.toString('hex'));
console.log('first4=' + [...bytes.subarray(0, 4)].join(','));
console.log('value=' + value);
console.log('dice=' + dice.toFixed(2));
console.log('limbo=' + limbo.toFixed(2));
JS
</code></pre>
<p>Actual output, checked with Node.js 20.19.2:</p>
<pre><code class="language-text">commitment=1c7bc4411405303e0f84ab57dea9691c6376ee6891b289a65edac408519e6009
message=example-client-seed:3:0
digest=6d1e1f338f1fdebd53787f1a0149cf4db00e3ee20a292c692ea92ef45364ce5b
first4=109,30,31,51
value=0.42624087329022586
dice=42.62
limbo=2.32
</code></pre>
<h2>Turn four bytes into a result</h2>
<p>SHA-256 outputs 32 bytes, but this calculation consumes only the first four. If those bytes are <code>b0</code>, <code>b1</code>, <code>b2</code>, and <code>b3</code>, their big-endian integer is <code>N = b0 × 256³ + b1 × 256² + b2 × 256 + b3</code>.</p>
<p>Dividing by <code>2³²</code> gives the same value as the repository's sum <code>b0/256 + b1/256² + b2/256³ + b3/256⁴</code>. It lies in <code>[0, 1)</code>: zero is possible; one is excluded. The denominator is 4,294,967,296, not the largest unsigned 32-bit integer.</p>
<p>Here, the prefix <code>6d1e1f33</code> represents the integer 1,830,690,611. Dividing that integer by the denominator reproduces the printed value. This also explains why byte order matters: treating the same four bytes as a little-endian integer changes their positional weights. Keep hexadecimal digest text separate from the raw bytes it represents; the first four bytes occupy eight hexadecimal characters.</p>
<p>Dice maps this value using <code>floor(value × 10001) / 100</code>. That produces hundredth-sized outcomes from 0.00 through 100.00. Integer division does not partition all <code>2³²</code> possible inputs into 10,001 exactly equal groups, so “perfectly uniform” would overstate the discrete mapping.</p>
<p>Limbo instead scales an inverse: approximately <code>0.99/value</code>, rounded down to two decimal places and bounded below by 1. The snippet preserves the repository's operation order. Its 0.99 coefficient encodes the documented 1% edge; a cryptographic commitment does not remove it. The <a href="https://stake.com/provably-fair/game-events">game-event documentation</a> describes the conversions.</p>
<p>There is also an implementation edge case: if all four bytes are zero, the current Limbo calculation returns JavaScript <code>Infinity</code>. The repository does not supply a finite maximum for that case. That is a limitation to review, rather than silently replace with an invented cap.</p>
<h2>Check the repository implementation</h2>
<p>Download the source from <a href="https://github.com/cryptoplayguide/provably-fair-verifier">GitHub</a> or <a href="https://gitlab.com/cryptoplay-guide/provably-fair-verifier">GitLab</a>, and open a terminal in the repository root. It is an MIT-licensed JavaScript module with no external dependencies. The following command imports the real exported function and explicitly supplies Node's Web Crypto object:</p>
<pre><code class="language-bash">node --input-type=module &lt;&lt;'JS'
import { webcrypto } from 'node:crypto';
import { fairResult } from './src/index.js';

const input = {
  game: 'dice',
  server: 'e3f1b5c2a9d84f6e7b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6',
  hash: '1c7bc4411405303e0f84ab57dea9691c6376ee6891b289a65edac408519e6009',
  client: 'example-client-seed',
  nonce: '3',
  salt: ''
};
const dice = await fairResult(input, webcrypto);
const limbo = await fairResult({ ...input, game: 'limbo' }, webcrypto);
const next = await fairResult({ ...input, nonce: '4' }, webcrypto);
const badHash = await fairResult({ ...input, hash: '0'.repeat(64) }, webcrypto);

console.log(`dice: commitment=${dice.matches}; result=${dice.result}`);
console.log(`limbo: commitment=${limbo.matches}; result=${limbo.result}`);
console.log('saved roll matches=' + (dice.result === 42.62));
console.log(`nonce 4: commitment=${next.matches}; result=${next.result}`);
console.log(`wrong hash: commitment=${badHash.matches}; result=${badHash.result}`);
JS
</code></pre>
<p>Actual output:</p>
<pre><code class="language-text">dice: commitment=true; result=42.62
limbo: commitment=true; result=2.32
saved roll matches=true
nonce 4: commitment=true; result=68.78
wrong hash: commitment=false; result=42.62
</code></pre>
<p>The changed nonce illustrates a crucial API distinction: <code>matches</code> checks only the server seed commitment. It does not compare an observed result. A wrong nonce can therefore produce <code>matches: true</code> while recomputing the wrong round. Conversely, a wrong commitment does not prevent the function from returning a number. Treat a failed commitment as a failed verification.</p>
<p>For your own records, supply the revealed seed, previously saved hash, exact client seed, and recorded nonce. Check the commitment, then compare the calculated outcome with the recorded outcome under the correct formula. The module validates nonnegative safe-integer nonces and normalizes their numeric representation, so <code>'003'</code> becomes <code>3</code> in its message.</p>
<p>When a result differs, compare intermediate values before changing anything. A commitment mismatch points to the saved hash, revealed seed, or encoding. A matching commitment but different digest points to the HMAC key, message serialization, or nonce. An identical digest but different outcome points to byte selection, byte order, rounding, or the game conversion. Keep the original inputs intact while investigating; silently trimming or repairing them can hide the actual discrepancy.</p>
<p>You can cross-check those inputs in CryptoPlay Guide's <a href="https://cryptoplay.guide/tools/provably-fair-verifier/">browser verifier</a>; the <a href="https://cryptoplay.guide/guides/verify-provably-fair-step-by-step/">step-by-step verification guide</a> provides another worked example. Both are resources from the project publisher.</p>
<h2>Keep the conclusion narrow</h2>
<p>The repository also implements Crash, but through a separate path: it hashes decoded game-hash bytes for the commitment, uses the public salt as the HMAC key, and derives a value from the first 52 digest bits. Do not apply the Dice message format to it or assume every operator uses identical Crash rules.</p>
<p>A successful check establishes consistency between supplied inputs and a specified computation, provided the commitment was recorded at the right time. It does not prove unbiased seed generation, complete round history, payout reliability, or positive expected returns. The practical skill is preserving evidence and reproducing a calculation precisely enough to know what has actually been checked.</p>
]]></content:encoded></item></channel></rss>