Winning Number
Anyone can reproduce the winning number using public Solana data.
What you need
- The final slot of the epoch
- The finalized blockhash for that slot (base58)
That’s it. With those two values, the winning number is deterministic.
Steps (plain English)
When an epoch ends:
- Take the final slot of that epoch.
- Get the finalized blockhash from that slot.
- Combine them as bytes:
- Slot as u64 little-endian (8 bytes)
- Blockhash decoded from base58 (32 bytes)
- Run SHA-256 over the resulting 40-byte message.
- Add all 32 digest bytes together.
- Take the remainder after dividing by 10 (mod 10).
- The remainder (0–9) is the winning number.
Reference implementation (TypeScript)
This matches the verifier logic: u64 LE slot bytes + base58-decoded blockhash bytes.
import bs58 from "bs58";
import { sha256 } from "@noble/hashes/sha256";
export type VerifyResult = {
slot: bigint;
blockhash: string;
winningNumber: number;
debug: {
slotU64LeHex: string;
blockhashBytesHex: string;
messageHex: string;
digestSha256Hex: string;
digestSumU64: bigint;
};
};
// --- helpers ---
function slotToU64LeBytes(slot: bigint): Uint8Array {
if (slot < 0n) throw new Error("slot must be >= 0");
if (slot > 0xffff_ffff_ffff_ffffn) throw new Error("slot exceeds u64");
const out = new Uint8Array(8);
let x = slot;
for (let i = 0; i < 8; i++) {
out[i] = Number(x & 0xffn);
x >>= 8n;
}
return out; // little-endian
}
function bytesToHex(bytes: Uint8Array): string {
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
}
export function reproduceWinningNumber(
slot: bigint,
blockhashBase58: string,
range = 10
): VerifyResult {
if (!Number.isInteger(range) || range <= 0) {
throw new Error("range must be a positive integer");
}
// 1) final slot
const finalSlot: bigint = slot;
// 2) finalized blockhash at that slot
const finalizedBlockhash: string = blockhashBase58;
// 3) combine bytes (u64 LE + 32-byte blockhash)
const slotLe = slotToU64LeBytes(finalSlot); // 8 bytes u64 LE
const blockhashBytes = bs58.decode(finalizedBlockhash); // should be 32 bytes
if (blockhashBytes.length !== 32) {
throw new Error(
`decoded blockhash must be 32 bytes, got ${blockhashBytes.length}`
);
}
const msg = new Uint8Array(8 + 32); // 40 bytes
msg.set(slotLe, 0);
msg.set(blockhashBytes, 8);
// 4) SHA-256(message)
const digest = sha256(msg); // Uint8Array(32)
// 5) sum 32 digest bytes
let total = 0n;
for (const b of digest) total += BigInt(b);
// 6-7) mod range (default: 10)
const winningNumber = Number(total % BigInt(range));
return {
slot: finalSlot,
blockhash: finalizedBlockhash,
winningNumber,
debug: {
slotU64LeHex: bytesToHex(slotLe),
blockhashBytesHex: bytesToHex(blockhashBytes),
messageHex: bytesToHex(msg),
digestSha256Hex: bytesToHex(digest),
digestSumU64: total,
},
};
}
Run it locally (quick start)
If someone wants to reproduce the winning number locally in under a minute:
npm init -y
npm i bs58 @noble/hashes
Create a file named reproduce.js and paste the function above (or transpile TS). Then call it:
import { reproduceWinningNumber } from "./reproduce.js";
const slot = 400031999n; // example
const blockhash = "BASE58_BLOCKHASH_HERE"; // example
console.log(reproduceWinningNumber(slot, blockhash).winningNumber);
Tip: The returned
debugobject includes hex dumps (slot bytes, message bytes, digest) to help confirm the exact byte-level reproduction.
Verifier repository (download + reproduce)
Clone the official verifier repo to reproduce results with the same logic used by the project:
https://github.com/IC42N/iseefortune-verifier
That repo includes the reproduction code, helpful scripts, and usage instructions.