ألعاب مخصصة - Provably Fair
كيف يتم استخدامها في الألعاب المخصصة
تبدأ كل رهان في لعبة YEET Original من بذرة جولة. بذرة الجولة هي HMAC-SHA256 لبذرة العميل الخاصة بك وnonce الرهان، مشفرة ببذرة خادم تختارها YEET. تستمد اللعبة بعد ذلك كل قيمة عشوائية تحتاجها لهذا الرهان من بذرة الجولة. يتم عرض بذرة الخادم لك بشكل مجزأ (hashed) قبل اللعب، ويتم الكشف عنها عند تدوير زوج البذور الخاص بك، حتى تتمكن من إعادة حساب بذرة الجولة لأي رهان سابق والتحقق من أن نتيجته لم تتغير بعد أن قمت بالمراهنة.
قد لا تتطابق أدوات التحقق الأخرى
مواقع التحقق المخصصة للكازينوهات الأخرى قد تولد أرقاماً عشوائية بطريقة مختلفة، لذا فإن التحقق من رهان YEET هناك قد يعطي نتيجة مختلفة. تحقق من رهانات YEET في علامة تبويب التحقق (Verify) ضمن نافذة النزاهة (Fairness) للعبة، أو باستخدام الكود الموجود في هذه الصفحة.
توليد الأرقام العشوائية
يتم إنشاء كل رهان على خطوتين. أولاً، ينتج عن زوج البذور الخاص بك وnonce الرهان بذرة جولة واحدة. ثانياً، تنتج بذرة الجولة تياراً من البايتات العشوائية، وتقرأ اللعبة أرقاماً صحيحة غير متحيزة من ذلك التيار. المدخلات نفسها تعطي دائماً نفس الأرقام، وهذا ما يجعل النتيجة قابلة للتحقق.
1. المدخلات
- سلسلة سداسية عشرية عشوائية مكونة من 64 حرفًا تم إنشاؤها بواسطة خادومنا
- سلسلة يمكنك تعديلها في أي وقت
- عداد يزداد مع كل لعبة يتم لعبها
- المؤشر — الجزء الأخير من رسالة بذرة الجولة. وهو دائماً 0.
2. بذرة الجولة
بذرة الجولة هي HMAC-SHA256 مشفرة ببذرة الخادم (كـ 32 بايت) عبر النص clientSeed:nonce:0. النتيجة هي 32 بايت، مكتوبة كـ 64 رمزاً ست عشرياً. الرهان الواحد له بذرة جولة واحدة بالضبط.
async function deriveGameSeed(serverSeed, clientSeed, nonce) {
const seedBytes = hexToBytes(serverSeed);
if (seedBytes.length !== 32) {
throw new Error(`Server seed must be 32 bytes (64 hex characters), got ${seedBytes.length}.`);
}
const key = await crypto.subtle.importKey(
'raw', toArrayBuffer(seedBytes), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
const message = new TextEncoder().encode(`${clientSeed}:${nonce}:0`);
return bytesToHex(new Uint8Array(await crypto.subtle.sign('HMAC', key, message)));
}3. العشوائية من بذرة الجولة
بذرة الجولة هي مفتاح HMAC-SHA256 ثانٍ. يقوم كل استدعاء بتوقيع عداد من 8 بايت (بترتيب little-endian، بدءاً من 0) ويعطي كتلة من 32 بايت؛ يزداد العداد بمقدار واحد لكل كتلة. تأخذ عمليات القراءة بايتات من الكتلة الحالية بالترتيب. عندما تحتاج عملية القراءة إلى بايتات أكثر مما تبقى في الكتلة، يتم تخطي بقية الكتلة ويتم إنشاء الكتلة التالية.
class GameEntropy {
constructor(seedBytes) {
if (seedBytes.length !== 32) throw new Error('Seed must be exactly 32 bytes (64 hex characters).');
this.buffer = new Uint8Array(32);
this.offset = 32;
this.cursor = 0;
this.key = crypto.subtle.importKey(
'raw', toArrayBuffer(seedBytes), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
}
async next(count) {
if (count < 1 || count > 32) throw new RangeError('count must be between 1 and 32');
if (this.offset + count > 32) {
const message = new ArrayBuffer(8);
const view = new DataView(message);
view.setUint32(0, this.cursor >>> 0, true);
view.setUint32(4, Math.floor(this.cursor / 0x100000000) >>> 0, true);
const hash = await crypto.subtle.sign('HMAC', await this.key, message);
this.buffer.set(new Uint8Array(hash));
this.cursor++;
this.offset = 0;
}
const slice = this.buffer.slice(this.offset, this.offset + count);
this.offset += count;
return slice;
}
}4. أرقام غير منحازة
يتم سحب النتائج كأرقام صحيحة. تقوم readUint32 بقراءة 4 بايت كعدد صحيح غير موقع بـ 32 بت (little-endian). تعيد unbiasedUint(entropy, n) رقماً من 0 إلى n − 1، بفرص متساوية للجميع. عندما يكون n قوة للعدد اثنين، يتم أخذ الباقي مباشرة. وبخلاف ذلك، فهي تقبل فقط القيم التي تقل عن أكبر مضاعف لـ n يتناسب مع 32 بت، وتعيد القراءة عندما تكون القيمة مساوية لها أو أكبر منها، لضمان عدم تفضيل أي نتيجة.
async function readUint32(entropy) {
const bytes = await entropy.next(4);
return new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, true);
}
async function unbiasedUint(entropy, range) {
if (range <= 0) throw new Error('Range must be positive');
if (range === 1) return 0;
if ((range & (range - 1)) === 0) {
return (await readUint32(entropy)) % range;
}
const maxValue = 0xffffffff;
const threshold = maxValue - (maxValue % range) - 1;
for (;;) {
const raw = await readUint32(entropy);
if (raw <= threshold) return raw % range;
}
}5. أدوات مساعدة
function hexToBytes(hex) {
if (typeof hex !== 'string') throw new TypeError('Hex value must be a string.');
const clean = hex.trim().replace(/^0x/i, '');
if (clean.length % 2 !== 0) throw new Error('Hex value must have an even length.');
if (!/^[0-9a-fA-F]*$/.test(clean)) throw new Error('Hex value contains non-hex characters.');
const bytes = new Uint8Array(clean.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
function bytesToHex(bytes) {
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
}
function toArrayBuffer(bytes) {
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}6. التحقق من الرهان
- افتح الرهان من سجل رهاناتك وانسخ مفتاح العميل (client seed)، ورمز الـ nonce، وبصمة مفتاح الخادم (hashed server seed).
- قم بتدوير زوج المفاتيح الخاص بك للكشف عن مفتاح الخادم، وتأكد من أن بصمة SHA-256 الخاصة به تطابق البصمة التي رأيتها قبل الرهان.
- قم بتشغيل
deriveGameSeedباستخدام مفتاح الخادم، ومفتاح العميل، والـ nonce للحصول على مفتاح الجولة. - أنشئ
GameEntropyمن مفتاح الجولة، وطبق عملية التحويل الخاصة باللعبة (انظر التحويلات) وقارن النتيجة برهانك.
