יש בעיה - קשה לייצר מספר אקראי ברשת מבוזרת. כמעט כל הבלוקצ'יין כבר נתקלו בזה. ואכן, ברשתות שבהן אין אמון בין משתמשים, יצירת מספר אקראי שאין להכחישה פותרת בעיות רבות.
במאמר זה אנו מספרים לכם כיצד הצלחנו לפתור את הבעיה באמצעות משחקים כדוגמה. הראשון שבהם היה . לצורך הפיתוח, היינו צריכים מחולל מספרים אקראיים.

בתחילה, תכננו ליצור מספר המבוסס על מידע מהבלוקצ'יין. עם זאת, אז התברר: ניתן לשנות את המספר, מה שאומר שהפתרון אינו מתאים.
המצאנו פתרון לעקיפת הבעיה: השתמש בסכימת commit-expand. השרת ניחש מספר מ-1 עד 5, הוסיף לו מלח ואז גיבש את התוצאה באמצעות . השרת פרס את החוזה החכם עם המספר שכבר שמור מראש. מסתבר שהמשחק מסתכם בכך שהמשתמש מנחש את המספר שמסתיר ה-hash.
השחקן ביצע הימור, והשרת שלח את המספר החבוי וה"מלח" לחוזה החכם. במילים פשוטות, הוא חשף את הקלפים. לאחר מכן, השרת בדק את המספרים והחליט אם המשתמש ניצח או הפסיד.
אם השרת לא שלח מספר או "מלח" לאימות, המשתמש ניצח. במקרה זה, עבור כל משחק היה צורך לפרוס חוזה חכם מראש ולכלול בו זכיות פוטנציאליות. התברר שזה לא נוח, לוקח זמן ויקר. באותה תקופה לא היה פתרון בטוח אחר.
לאחרונה, צוות Tradisys הציע להוסיף פונקציה לפרוטוקול Waves rsaVerify(). הוא בודק את תקפות חתימת ה-RSA בהתבסס על המפתח הציבורי והפרטי. כתוצאה מכך, התכונה נוספה.
פיתחנו שלושה משחקים: , и . כל אחד מהם מיישם טכנולוגיית מספרים אקראיים. בואו נבין איך זה עובד.

בואו נסתכל על יצירת מספר אקראי באמצעות Ride on Waves כדוגמה. ניתן למצוא את החוזה החכם .
עבור לכרטיסייה תסריט ובחר פורק קומפילציה. תראה את קוד החוזה החכם (המכונה גם סקריפט).

קוד החוזה החכם מכיל קבוצה של פונקציות. אלה המסומנים כ-@Callable ניתן להפעיל באמצעות עסקאות הזמנה. אנו מעוניינים בשתי פונקציות: להמר и לסגת:
- הימור func (בחירת שחקן)
- func draw(gameId,rsaSign)
1. המשתמש בוחר את אורך הקטע ואת גודל ההימור.

2. הלקוח יוצר פונקציית הימור. עבור התמונה למעלה זה יהיה bet("50").
3. הלקוח שולח עסקת Invocation לכתובת החוזה החכם (broadcast InvocationTx). העסקה מכילה את פונקציית ההימור כפרמטר שיחה. המשמעות היא שעסקת ה-Invocation מפעילה את ביצוע פונקציית ההימור (בחירה: String) בחוזה החכם.

4. שקול את פונקציית ההימור:
@Callable(i)
func bet (playerChoice) = {
let newGameNum = IncrementGameNum()
let gameId = toBase58String(i.transactionId)
let pmt = extract(i.payment)
let betNotInWaves = isDefined(pmt.assetId)
let feeNotInWaves = isDefined(pmt.assetId)
let winAmt = ValidateBetAndDefineWinAmt(pmt.amount, playerChoice)
let txIdUsed = isDefined(getString(this, gameId))
if (betNotInWaves)
then throw ("Bet amount must be in Waves")
else if (feeNotInWaves)
then throw ("Transaction's fee must be in Waves")
else if (txIdUsed)
then throw ("Passed txId had been used before. Game aborted.")
else {
let playerPubKey58 = toBase58String(i.callerPublicKey)
let gameDataStr = FormatGameDataStr(STATESUBMITTED, playerChoice, playerPubKey58, height, winAmt, "")
ScriptResult(WriteSet(cons(DataEntry(RESERVATIONKEY, ValidateAndIncreaseReservedAmt(winAmt)), cons(DataEntry(GAMESCOUNTERKEY, newGameNum), cons(DataEntry(gameId, gameDataStr), nil)))), TransferSet(cons(ScriptTransfer(SERVER, COMMISSION, unit), nil)))
}
}הפונקציה כותבת משחק חדש למצב החוזה החכם. כלומר:
- מזהה ייחודי למשחק חדש (מזהה משחק)
- מצב המשחק = נשלח
- בחירת שחקן (אורך קטע 50)
- מפתח ציבורי
- זכיות פוטנציאליות (תלוי בהימור של השחקן)

כך נראית רשומת נתונים בבלוקצ'יין (ערך-מפתח):
{
"type": "string",
"value": "03WON_0283_448t8Jn9P3717UnXFEVD5VWjfeGE5gBNeWg58H2aJeQEgJ_06574069_09116020000_0229",
"key": "2GKTX6NLTgUrE4iy9HtpSSHpZ3G8W4cMfdjyvvnc21dx"
}"מפתח" (מפתח) - מזהה משחק משחק חדש. שאר הנתונים כלולים בשורת השדה "ערך". ערכים אלה מאוחסנים בכרטיסייה נתונים חוזה חכם:


5. השרת "מסתכל" על החוזה החכם ומוצא את העסקה שנשלחה (משחק חדש) באמצעות הבלוקצ'יין Api. מזהה המשחק של המשחק החדש כבר מתועד בבלוקצ'יין, מה שאומר שכבר לא ניתן לשנות אותו או להשפיע עליו
6. השרת יוצר פונקציית משיכה (gameId, rsaSign). לדוגמה, כך:
withdraw ("FwsuaaShC6DMWdSWQ5osGWtYkVbTEZrsnxqDbVx5oUpq", "base64:Gy69dKdmXUEsAmUrpoWxDLTQOGj5/qO8COA+QjyPVYTAjxXYvEESJbSiCSBRRCOAliqCWwaS161nWqoTL/TltiIvw3nKyd4RJIBNSIgEWGM1tEtNwwnRwSVHs7ToNfZ2Dvk/GgPUqLFDSjnRQpTHdHUPj9mQ8erWw0r6cJXrzfcagKg3yY/0wJ6AyIrflR35mUCK4cO7KumdvC9Mx0hr/ojlHhN732nuG8ps4CUlRw3CkNjNIajBUlyKQwpBKmmiy3yJa/QM5PLxqdppmfFS9y0sxgSlfLOgZ51xRDYuS8NViOA7c1JssH48ZtDbBT5yqzRJXs3RnmZcMDr/q0x6Bg==")7. השרת שולח עסקת Invocation לחוזה החכם (broadcast InvocationTx). העסקה מכילה קריאה לפונקציית המשיכה שנוצרה (gameId, rsaSign):

הפונקציה מכילה מזהה משחק משחק חדש והתוצאה של חתימת RSA של מזהה ייחודי עם מפתח פרטי. תוצאת החתימה ללא שינוי.
מה זה אומר?
אנחנו לוקחים את אותו ערך (מזהה משחק) ומחילים עליו את שיטת החתימה RSA. תמיד נקבל את אותה תוצאה. כך עובד אלגוריתם RSA. לא ניתן לתמרן את המספר הסופי, מכיוון שמזהה המשחק והתוצאה של החלת RSA אינם ידועים. גם בחירת מספר היא חסרת טעם.
8. Blockchain מקבל את העסקה. הוא מפעיל את פונקציית הנסיגה (gameId, rsaSign)
9. בתוך פונקציית הנסיגה מתרחשת נסיגה פונקציות GenerateRandInt (gameId, rsaSign). זהו מחולל מספרים אקראיים
# @return 1 ... 100
func GenerateRandInt (gameId,rsaSign) = {
# verify RSA signature to proof random
let rsaSigValid = rsaVerify (SHA256, toBytes(gameId), rsaSign, RSAPUBLIC)
if (rsaSigValid)
then {
let rand = (toInt(sha256(rsaSign)) % 100)
if ((0 > rand))
then ((-1 * rand) + 1)
else (rand + 1)
}
else throw ("Invalid RSA signature")
}שורה - ויש מספר אקראי.
ראשית, המחרוזת נלקחת, שהיא תוצאה של חתימת RSA מזהה משחק מפתח פרטי (rsaSign). לאחר מכן גיבוב עם SHA-256 (sha256(rsaSign)).
איננו יכולים לחזות את התוצאה של החתימה וה-hashing שלאחר מכן. לכן, אי אפשר להשפיע על יצירת מספר אקראי. כדי לקבל מספר בטווח מסוים (לדוגמה, מ-1 עד 100), השתמש בפונקציית ההמרה toInt וב-%100 (בדומה ל ).
בתחילת המאמר הזכרנו את הפונקציה rsaVerify(), המאפשר לבדוק את תקפות חתימת RSA באמצעות מפתח פרטי מול מפתח ציבורי. הנה החלק GenerateRandInt(gameId,rsaSign):
rsaVerify (SHA256, toBytes(gameId), rsaSign, RSAPUBLIC)המפתח הציבורי RSAPUBLIC והמחרוזת rsaSign מועברים לקלט. החתימה נבדקת לגבי תוקף. המספר נוצר אם הבדיקה הצליחה. אחרת, המערכת מחשיבה שהחתימה אינה חוקית (חתימת RSA לא חוקית).
על השרת לחתום על מזהה המשחק עם מפתח פרטי ולשלוח חתימת Rsa חוקית בתוך 2880 בלוקים. הפרמטר מוגדר בעת פריסת החוזה החכם. אם שום דבר לא קורה בזמן המוקצב, המשתמש מנצח. במקרה זה, יש לשלוח את הפרס לכתובת שלך בעצמך. מסתבר ש"לא משתלם לשרת לרמות", כי זה מוביל להפסד. להלן דוגמה.

המשתמש משחק . בחרתי 2 מתוך 6 הצדדים של הקוביה, ההימור הוא 14 WAVES. אם השרת לא ישלח חתימת RSA חוקית לחוזה החכם בתוך הזמן שצוין (2880 בלוקים), המשתמש ייקח 34.44 WAVES.
כדי ליצור מספרים במשחקים, אנו משתמשים באורקל - מערכת חיצונית שאינה בלוקצ'יין. השרת מבצע חתימת RSA של מזהה המשחק. החוזה החכם בודק את תוקף החתימה וקובע את הזוכה. אם השרת לא שולח כלום, המשתמש מנצח אוטומטית.
זוהי שיטת דור כנה, כי מניפולציה היא בלתי אפשרית מבחינה טכנית. כל משחקי Tradisys עובדים על בסיס האלגוריתם המתואר. כך פועלים משחקי בלוקצ'יין. הכל שקוף וניתן לאימות. אין אנלוגים למערכת כזו באף בלוקצ'יין אחר. זהו אקראי הוגן.
מקור: www.habr.com
