On probleem – raske genereerida juhuslikku arvu detsentraliseeritud võrgus. Peaaegu kõik plokiahelad on sellega silmitsi seisnud. Usu puudumisel kasutajate vahel lahendab vaieldamatu juhuslik number mitmeid probleeme.
Artiklus räägib, kuidas suudsime probleemi lahendada mängude näitel. Esimene neist oli . Arendamiseks vajasime juhuslike numbrite generaatorit.

Alguses plaanisime genereerida numbri plokiahela teabest. Siiski sai kiiresti selgeks, et numbrit võib võltsida, seega see lahendus ei sobinud.
Käisime välja alternatiivse tee: kasutada skeemi "komiteerimine-avamine". Server "mõtles välja" numbri vahemikus 1 kuni 5, lisas sellele "soola" ja seejärel hash'iks tulemuse kasutades . Server käivitas eelnevalt nutilepingu, kus oli juba salvestatud number. Niisiis oli mäng kokkuvõttes selles, et kasutaja pidi arvama numbrit, mis oli peidetud hash'i taha.
Mängija tegi panuse, samal ajal kui server saatis mõeldud numbri ja „soola” nutitulepingule. Lihtsalt öeldes, avas ta kaardid. Pärast seda võrreldes server numbreid ja otsustas, kas kasutaja võitis või kaotas.
Kui server ei saatnud numbrit või „soola” kontrollimiseks, võitis kasutaja. Sellisel juhul oli iga mängu jaoks vajalik eelnevalt seadistada nutituleping ja määrata sellesse potentsiaalne võit. Selgus, et see oli ebamugav, aeglane ja kallis. Sel hetkel polnud muud turvalist lahendust.
Hiljuti pakkus Tradisys meeskond välja, et lisada Waves protokolli funktsioon rsaVerify(). See kontrollib RSA allkirja kehtivust avaliku ja privaatse võtme alusel. Lõpuks lisati funktsioon.
Olemas on kolm mängu: , ja . Igas mängus on rakendatud juhuslike numbrite tehnoloogiat. Vaatame, kuidas see töötab.

Vaatame juhusliku numbri genereerimist mängu Ride on Waves näitel. Nutitulepingu leiate .
Mine vahekaardile Script ja vali Decompiled. Näete nutitulepingu koodi (ehk skripti).

Nutitulepingu kood sisaldab funktsioonide kogumit. Need, mis on märgitud kui @Callable, saavad käivituda Invocation-tehingute abil. Meie huvitavad kaks funktsiooni: bet ja withdraw.:
- func bet (mängijaValik)
- func withdraw (mänguId, rsaSign)
1. Kasutaja valib lõigu pikkuse ja panuse suuruse.

2. Klient genereerib bet-funktsiooni. Ülaltoodud pildi jaoks on see bet ("50").
3. Klient saadab Invocation-tehingu nutilepingule (broadcast InvocationTx). Tehing sisaldab Call-parameetrit, mis sisaldab bet-funktsiooni. See tähendab, et Invocation-tehing käivitab bet-funktsiooni (valik: String) nutilepingus.

4. Vaatleme bet-funktsiooni:
@Callable(i)
func bet (mängijaValik) = {
let uusMänguNum = IncrementGameNum()
let mänguId = toBase58String(i.transactionId)
let pmt = extract(i.payment)
let betMitteWaves'is = isDefined(pmt.assetId)
let tasuMitteWaves'is = isDefined(pmt.assetId)
let võitSuurus = ValidateBetAndDefineWinAmt(pmt.amount, mängijaValik)
let txIdKasutatud = isDefined(getString(this, mänguId))
if (betMitteWaves'is)
then throw ("Panuse kogus peab olema Waves'is")
else if (tasuMitteWaves'is)
then throw ("Tehingu tasu peab olema Waves'is")
else if (txIdKasutatud)
then throw ("Edastatud txId on varem kasutatud. Mäng katkestatud.")
else {
let mängijaAvalikVõti58 = toBase58String(i.callerPublicKey)
let mänguAndmeteStr = FormatGameDataStr(STATESUBMITTED, mängijaValik, mängijaAvalikVõti58, height, võitSuurus, "")
ScriptResult(WriteSet(cons(DataEntry(RESERVATIONKEY, ValidateAndIncreaseReservedAmt(võitSuurus)), cons(DataEntry(GAMESCOUNTERKEY, uusMänguNum), cons(DataEntry(mänguId, mänguAndmeteStr), nil)))), TransferSet(cons(ScriptTransfer(SERVER, COMMISSION, unit), nil)))
}
}Funktsioon salvestab smartratelepingu staatuse uut mängu. Nimelt:
- Uue mängu unikaalne identifikaator (mängu id)
- Mängu staatus = SUBMITTED
- Mängija valik (lõigu pikkus 50)
- Avalik võti
- Võimalik võit (sõltub mängija panusest)

Nii näeb välja andmete salvestamine plokiahelas (võti-väärtus):
{
"type": "string",
"value": "03WON_0283_448t8Jn9P3717UnXFEVD5VWjfeGE5gBNeWg58H2aJeQEgJ_06574069_09116020000_0229",
"key": "2GKTX6NLTgUrE4iy9HtpSSHpZ3G8W4cMfdjyvvnc21dx"
}«Võti» (key) – mängu id uus mäng. Ülejäänud andmed sisaldavad välja «väärtus» (value) rea. Need märkmed salvestatakse smartratelepingu vahekaardile: Andmed smartratelepingu:


5. Server „vaatab“ smartratelepingut ja leiab saatedud tehingu (uus mäng) läbi plokiahela Api. Uue mängu mängu id on juba salvestatud plokiahelasse, seega ei saa seda muuta ega mõjutada.
6. Server genereerib withdraw-funktsiooni (gameId, rsaSign). Näiteks sellise:
withdraw ("FwsuaaShC6DMWdSWQ5osGWtYkVbTEZrsnxqDbVx5oUpq", "base64:Gy69dKdmXUEsAmUrpoWxDLTQOGj5/qO8COA+QjyPVYTAjxXYvEESJbSiCSBRRCOAliqCWwaS161nWqoTL/TltiIvw3nKyd4RJIBNSIgEWGM1tEtNwwnRwSVHs7ToNfZ2Dvk/GgPUqLFDSjnRQpTHdHUPj9mQ8erWw0r6cJXrzfcagKg3yY/0wJ6AyIrflR35mUCK4cO7KumdvC9Mx0hr/ojlHhN732nuG8ps4CUlRw3CkNjNIajBUlyKQwpBKmmiy3yJa/QM5PLxqdppmfFS9y0sxgSlfLOgZ51xRDYuS8NViOA7c1JssH48ZtDbBT5yqzRJXs3RnmZcMDr/q0x6Bg==")7. Server saadab smarthanke jaoks Invocation-tehingu (broadcast InvocationTx). Tehing sisaldab loodud withdraw-funktsiooni (gameId, rsaSign) kutsumist:

Funktsioon sisaldab mängu id uue mängu ja RSA-allkirja tulemuse eristamiseks, mis on saadud privaatvõtmega. Allkirja tulemus on muutumatu.
Mida see tähendab?
Võtame sama väärtuse (mängu ID) ja rakendame sellele RSA-allkirja meetodi. Saame alati sama tulemuse. Nii töötab RSA-algoritm. Lõpptulemust ei saa manipuleerida, kuna mängu ID ja RSA rakendamise tulemus ei ole teada. Arvu kontrollimine on ka mõttetuks.
8. Blokeerimine aktsepteerib tehingu. See käivitab withdraw-funktsiooni (gameId, rsaSign)
9. Withdraw-funktsiooni sees toimub väljaveo GenerateRandInt-funktsioon (gameId, rsaSign). See on juhuslike arvude generaator.
# @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")
}rand on juhuslik number.
Esiteks võetakse rida, mis on RSA-allkirja tulemus mängu id privaatvõtmega (rsaSign). Siis hajutatakse see SHA-256 abil (sha256(rsaSign)).
Мы не можем предсказать результат подписи и последующего хэширования. Поэтому невозможно повлиять на генерацию случайного числа. Чтобы получить число в определенном диапазоне (например, от 1 до 100), применяется функция преобразования toInt и %100 (аналог ).
Artikli alguses mainisime funktsiooni rsaVerify(), mis võimaldab kontrollida RSA-allkirja kehtivust privaatvõtme ja avaliku võtmega. Siin on osa GenerateRandInt (gameId,rsaSign):
rsaVerify (SHA256, toBytes(gameId), rsaSign, RSAPUBLIC)Sisendiks on avalik võti RSAPUBLIC ja rida rsaSign. Allkiri kontrollitakse kehtivuse osas. number genereeritakse, kui kontroll on edukas. Vastasel juhul peab süsteem allkirja kehtetuks (Invalid RSA signature).
Server peab mängu ID-d allkirjastama privaatvõtmega ja saatma kehtiva RSA-allkirja 2880 ploki jooksul. Parameeter seadistatakse nutilepingu juurutamisel. Kui antud ajavahemikul midagi ei toimu, võidab kasutaja. Sellisel juhul peab auhind saadetama isiklikule aadressile. Tulemuseks on see, et server ei saa endale «pettust» lubada, kuna see viib kaotusse. Allpool on näide.

Kasutaja mängib . Valisin 2 kuue külje hulgast, panus – 14 WAVES. Kui server ei saada nutilepingule kehtivat RSA-allkirja määratud aja jooksul (2880 plokki), saab kasutaja tagasi 34.44 WAVES.
Numbrite genereerimiseks mängudes kasutame oraaklit – välist, mitte plokiahela süsteemi. Server koostatakse RSA-allkiri mängu id-le. Nutileping kontrollib allkirja kehtivust ja määrab võitja. Kui server ei saatnud midagi, siis kasutaja võidab automaatselt.
See on aus meetod genereerimiseks, kuna manipulatsioon on tehniliselt võimatu. Kõik Tradisys mängud töötavad antud algoritmi alusel. Nii töötavad mängud plokiahelas. Kõik on läbipaistev ja kontrollitav. Sarnast süsteemi pole üheski teises plokiahelas. See on aus juhus.
Allikas: habr.com
