בבתי קזינו מקוונים מסורתיים, מחוללי מספרים אקראיים (RNG) נותרים קופסה שחורה—שחקנים צריכים לסמוך על המפעיל בעיוורון. זה פוגע באמון ומעלה ספקות לגבי הוגנות התשלומים. Provably fair פותר זאת: כל תוצאת משחק מתפרסמת על הבלוקצ'יין עם הוכחה קריפטוגרפית. שחקנים יכולים לשחזר את החישוב באופן עצמאי ולוודא שתוצאת הטלת קובייה, ספין או Crash לא זויפה. אנו מיישמים Provably fair מאז שהטכנולוגיה הופיעה והעברנו למעלה מ-30 פרויקטים בהימורים על-chain. הערימה הטכנולוגית שלנו: חוזים חכמים ב-Solidity 0.8.x, מנוע off-chain ב-Node.js, וממשק אימות ב-React. העריכו את הפרויקט שלכם—צרו קשר כדי לדון.
איך Provably Fair עובד בפועל
ישנן שתי גישות עיקריות: VRF על-chain ו-commit-reveal מחוץ ל-chain. אנו בוחרים לפי עומס, עלות גז ורמת האמון הנדרשת.
Chainlink VRF — אקראיות ניתנת לאימות על-chain
VRF מייצר מספר אקראי יחד עם הוכחה קריפטוגרפית. החוזה מבקש אקראיות, האורקל מחזיר מספר והוכחה, החוזה מאמת את ההוכחה ומקבל את המספר. התוצאה מתפרסמת באירוע—כל אחד יכול לאמת: requestId → blockHash → randomness. בפרויקט אחד השתמשנו ב-Chainlink VRF עם מזהה מנוי, מה שהפחית את עלויות הגז ב-30% בהשוואה לתשלום לפי שימוש.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract ProvablyFairCasino is VRFConsumerBaseV2Plus {
struct BetRecord {
address player;
uint256 amount;
uint8 gameId;
bytes betParams;
uint256 vrfRequestId;
uint256 randomResult;
bool settled;
uint256 payout;
}
mapping(uint256 => BetRecord) public betRecords;
event BetPlaced(
uint256 indexed requestId,
address indexed player,
uint8 gameId,
uint256 amount,
bytes betParams
);
event BetSettled(
uint256 indexed requestId,
uint256 randomness,
uint256 payout,
bool isWin
);
function placeBet(uint8 gameId, bytes calldata betParams) external payable returns (uint256 requestId) {
require(msg.value >= MIN_BET[gameId], "Below minimum");
require(msg.value <= MAX_BET[gameId], "Above maximum");
require(msg.value <= getMaxBet(), "Exceeds bankroll limit");
requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: KEY_HASH,
subId: SUBSCRIPTION_ID,
requestConfirmations: 3,
callbackGasLimit: 200_000,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
betRecords[requestId] = BetRecord({
player: msg.sender,
amount: msg.value,
gameId: gameId,
betParams: betParams,
vrfRequestId: requestId,
randomResult: 0,
settled: false,
payout: 0
});
emit BetPlaced(requestId, msg.sender, gameId, msg.value, betParams);
}
function fulfillRandomWords(
uint256 requestId,
uint256[] calldata randomWords
) internal override {
BetRecord storage bet = betRecords[requestId];
require(!bet.settled, "Already settled");
bet.randomResult = randomWords[0];
bet.settled = true;
uint256 payout = _resolveGame(bet.gameId, bet.amount, bet.betParams, randomWords[0]);
bet.payout = payout;
bool isWin = payout > 0;
if (isWin) {
payable(bet.player).transfer(payout);
}
emit BetSettled(requestId, randomWords[0], payout, isWin);
}
} Commit-Reveal לבתי קזינו מחוץ ל-chain
הסכמה הקלאסית: הקזינו מפרסם // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; contract ProvablyFairCasino is VRFConsumerBaseV2Plus { struct BetRecord { address player; uint256 amount; uint8 gameId; bytes betParams; uint256 vrfRequestId; uint256 randomResult; bool settled; uint256 payout; } mapping(uint256 => BetRecord) public betRecords; event BetPlaced( uint256 indexed requestId, address indexed player, uint8 gameId, uint256 amount, bytes betParams ); event BetSettled( uint256 indexed requestId, uint256 randomness, uint256 payout, bool isWin ); function placeBet(uint8 gameId, bytes calldata betParams) external payable returns (uint256 requestId) { require(msg.value >= MIN_BET[gameId], "Below minimum"); require(msg.value <= MAX_BET[gameId], "Above maximum"); require(msg.value <= getMaxBet(), "Exceeds bankroll limit"); requestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: KEY_HASH, subId: SUBSCRIPTION_ID, requestConfirmations: 3, callbackGasLimit: 200_000, numWords: 1, extraArgs: VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({nativePayment: false}) ) }) ); betRecords[requestId] = BetRecord({ player: msg.sender, amount: msg.value, gameId: gameId, betParams: betParams, vrfRequestId: requestId, randomResult: 0, settled: false, payout: 0 }); emit BetPlaced(requestId, msg.sender, gameId, msg.value, betParams); } function fulfillRandomWords( uint256 requestId, uint256[] calldata randomWords ) internal override { BetRecord storage bet = betRecords[requestId]; require(!bet.settled, "Already settled"); bet.randomResult = randomWords[0]; bet.settled = true; uint256 payout = _resolveGame(bet.gameId, bet.amount, bet.betParams, randomWords[0]); bet.payout = payout; bool isWin = payout > 0; if (isWin) { payable(bet.player).transfer(payout); } emit BetSettled(requestId, randomWords[0], payout, isWin); } } לפני המשחק, השחקן מוסיף hash(server_seed), לאחר המשחק הקזינו חושף client_seed. תוצאה = server_seed. כל אחד יכול לשחזר. Chainlink VRF מהיר יותר בזמן אימות, אך commit-reveal חוסך בגז.
class ProvablyFairEngine {
async createSession(userId: string): Promise<Session> {
const serverSeed = crypto.randomBytes(32).toString("hex");
const serverSeedHash = crypto
.createHash("sha256")
.update(serverSeed)
.digest("hex");
const session = await db.createSession({
userId,
serverSeed: this.encrypt(serverSeed),
serverSeedHash,
nonce: 0,
createdAt: new Date(),
});
return { sessionId: session.id, serverSeedHash };
}
async resolve(
sessionId: string,
clientSeed: string,
gameType: string,
betAmount: number
): Promise<GameResult> {
const session = await db.getSession(sessionId);
const serverSeed = this.decrypt(session.serverSeed);
const nonce = ++session.nonce;
const combinedSeed = `${serverSeed}:${clientSeed}:${nonce}`;
const hashHex = crypto
.createHmac("sha256", serverSeed)
.update(`${clientSeed}-${nonce}`)
.digest("hex");
const rawResult = parseInt(hashHex.slice(0, 8), 16);
const gameResult = this.applyGameLogic(gameType, rawResult);
await db.updateSession(sessionId, { nonce });
await db.saveGameRecord({
sessionId,
nonce,
clientSeed,
serverSeedHash: session.serverSeedHash,
gameType,
result: gameResult.outcome,
betAmount,
payout: gameResult.payout,
});
return gameResult;
}
async rotateSession(sessionId: string): Promise<void> {
const session = await db.getSession(sessionId);
const serverSeed = this.decrypt(session.serverSeed);
await db.revealServerSeed(sessionId, serverSeed);
await this.createSession(session.userId);
}
} אימות בצד השחקן
כל שחקן יכול לאמת את התוצאה באמצעות מאמת ציבורי. אנו מטמיעים ממשק React שבהינתן HMAC-SHA256(server_seed, client_seed + nonce) וה-class ProvablyFairEngine { async createSession(userId: string): Promise<Session> { const serverSeed = crypto.randomBytes(32).toString("hex"); const serverSeedHash = crypto .createHash("sha256") .update(serverSeed) .digest("hex"); const session = await db.createSession({ userId, serverSeed: this.encrypt(serverSeed), serverSeedHash, nonce: 0, createdAt: new Date(), }); return { sessionId: session.id, serverSeedHash }; } async resolve( sessionId: string, clientSeed: string, gameType: string, betAmount: number ): Promise<GameResult> { const session = await db.getSession(sessionId); const serverSeed = this.decrypt(session.serverSeed); const nonce = ++session.nonce; const combinedSeed = `${serverSeed}:${clientSeed}:${nonce}`; const hashHex = crypto .createHmac("sha256", serverSeed) .update(`${clientSeed}-${nonce}`) .digest("hex"); const rawResult = parseInt(hashHex.slice(0, 8), 16); const gameResult = this.applyGameLogic(gameType, rawResult); await db.updateSession(sessionId, { nonce }); await db.saveGameRecord({ sessionId, nonce, clientSeed, serverSeedHash: session.serverSeedHash, gameType, result: gameResult.outcome, betAmount, payout: gameResult.payout, }); return gameResult; } async rotateSession(sessionId: string): Promise<void> { const session = await db.getSession(sessionId); const serverSeed = this.decrypt(session.serverSeed); await db.revealServerSeed(sessionId, serverSeed); await this.createSession(session.userId); } } שנחשף, משחזר את החישוב ומשווה אותו לתוצאה. מדדים נתמכים: serverSeedHash, serverSeed, totalBetsCount. שקיפות הקופה מספקת רמת אמון נוספת. ממשק האימות מאפשר לשחקנים לייצא היסטוריה לצורך ביקורת עצמית.
מה כלול בפיתוח turnkey
| רכיב | פרטים |
|---|---|
| חוזים חכמים | Solidity 0.8.x, Chainlink VRF v2.5, תשלום ERC-20 |
| מנוע off-chain | Node.js + TypeScript, PostgreSQL, AES-256-GCM |
| ממשק אימות | רכיב React לאימות תוצאות |
| ביקורת | ביקורת פנימית + המלצות לאופטימיזציית גז |
| תיעוד | API, סכמת נתונים, הוראות לשחקנים |
| פריסה | הגדרת מנוי VRF, פריסת חוזה |
| הכשרת צוות | 2-3 מפגשים על תחזוקת המערכת |
אנו גם מספקים קוד מקור, סקריפטים לבדיקה ולוח בקרה לניטור RTP. אנו מבטיחים תמיכה למשך 6 חודשים לאחר ההשקה.
למה לבחור ב-VRF על פני Commit-Reveal?
השוואת גישות:
| קריטריון | Chainlink VRF | Commit-reveal |
|---|---|---|
| אימות | על-chain, ללא אמון | מחוץ ל-chain, דורש אמון בחשיפת ה-seed |
| עלות גז | בינונית (קריאה + callback) | נמוכה (רק hashes) |
| מהירות | 2-3 בלוקים | מיידי |
| שקיפות | מלאה | גבוהה, אך תלויה ביושרת השרת |
| יכולת ביקורת | קלה (אירועים) | בינונית (לוגים) |
לפרויקטים עם דרישות שקיפות מחמירות—VRF. למשחקים מהירים עם עמלות נמוכות—commit-reveal.
ציר זמן פיתוח
- משחקים בסיסיים עם VRF (Dice, Coinflip, Crash): 4-6 שבועות
- מנוע off-chain ב-commit-reveal: 3-4 שבועות
- ממשק אימות: 2-3 שבועות
- הרחבה ל-5-8 משחקים: +4-8 שבועות
- ביקורת אבטחה: 4-6 שבועות (מומלץ)
- סה"כ: 3-5 חודשים turnkey
צירי זמן עשויים להשתנות בהתאם למורכבות לוגיקת המשחק ושילוב מערכת התשלומים.
טעויות נפוצות ביישום Provably Fair
- אנטרופיה חלשה של server_seed: צרו עם
totalWagered, לעולם לא עםactualRTP. - hash seed לא מפורסם לפני המשחק: אחרת שחקנים לא יכולים לוודא שה-seed לא שונה בדיעבד.
- חוסר בממשק אימות: בלעדיו, provably fair הוא ביטוי שיווקי ריק.
- הגנה לא מספקת על הקופה: השתמשו בפול עם מגבלות הימורים ומדדי RTP ציבוריים.
- התעלמות מאופטימיזציית גז: כל הימור הוא עסקה; בצעו אופטימיזציה למשתנים והשתמשו באחסון דחוס.
צרו קשר כדי לדון בפרויקט שלכם—נעריך את הארכיטקטורה ונציע את הפתרון האופטימלי. הרעיון של provably fair מתואר בויקיפדיה. עוד על Chainlink VRF בתיעוד Chainlink VRF.







