Aléatoire basé sur RSA Blockchain

Il y a un problème que nous devions résoudre lors du développement de nos jeux. Il est compliqué de générer un nombre aléatoire dans un réseau distribué. Presque toutes les blockchains ont déjà fait face à ce problème. En effet, dans les réseaux où il n'y a de confiance entre personne, la création d'un nombre aléatoire résout un large éventail de problèmes.

Dans cet article, nous expliquons comment nous avons résolu ce problème pour nos jeux. Le premier d'entre eux était Waves Xmas Tree .





Initialement, nous avions prévu de générer un nombre en utilisant les informations de la blockchain. Cependant, après une enquête plus approfondie, il est devenu clair que le processus utilisé pour créer un numéro de cette façon pouvait être manipulé. Nous avons dû rejeter cette solution.

Nous avons trouvé une solution de contournement, en utilisant un schéma de «validation-révélation». Le serveur a proposé un nombre de 1 à 5, y a ajouté du «sel» et haché le résultat en utilisant la fonction Keccak . Le serveur a pré-débogué un contrat intelligent avec un numéro déjà enregistré. Le résultat était que le jeu était effectivement réduit à l'utilisateur devinant le nombre caché par le hachage.

Le joueur a placé son pari et le serveur a envoyé un numéro caché et du «sel» à un contrat intelligent. Autrement dit, les cartes ont été révélées. Ensuite, le serveur a vérifié les numéros et a décidé si l'utilisateur avait gagné ou perdu.

Si le serveur n'a pas envoyé le numéro et le «sel» pour vérification, alors l'utilisateur a gagné. Dans ce cas, il était nécessaire de déployer un contrat intelligent à l'avance et d'organiser des gains potentiels pour chaque jeu. C'était gênant, coûteux et long. À cette époque, cependant, il n'y avait pas d'autre solution sûre.

Peu de temps après, l'équipe Tradisys a proposé d'ajouter la fonction rsaVerify () au protocole Waves. Ceci vérifie la validité d'une signature RSA basée sur des clés publiques et privées. À la suite de notre proposition, la fonction a été ajoutée.

Nous avons construit trois nouveaux jeux: Dice Roller , Coin Flip et Ride On Waves . Dans chacun d'eux, la nouvelle technologie des nombres aléatoires a été mise en œuvre. Examinons de plus près comment cela fonctionne.



Regardons d'abord la génération de nombres aléatoires. Vous pouvez trouver le contrat intelligent ici .

Accédez à l' onglet Script et choisissez Décompilé . Vous verrez le code (ou script) du contrat intelligent.



Le code de contrat intelligent se compose d'une liste de fonctions. Ceux qui sont @Callable peuvent être exécutés via des transactions d'invocation . Nous sommes intéressés par deux d'entre eux: parier et retirer :
  • func bet (playerChoice)
  • func retrait (gameId, rsaSign)


1. L'utilisateur choisit la plage et la taille du pari.



2. Le client organise la fonction de pari. Pour l'image ci-dessus, ce serait un pari ("50")

3. Le client envoie une transaction d'invocation à l'adresse du contrat intelligent (diffusion InvocationTx). Une transaction en tant que paramètre Call contient la fonction de mise. Cela signifie que la transaction d'invocation démarre l'exécution de la fonction de pari sur le contrat intelligent (choix: chaîne).



4. Regardons la fonction de pari:

@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))) } } 


La fonction enregistre un nouveau jeu dans l'état du contrat intelligent:

  • Nouvel identifiant de jeu unique (identifiant de jeu)
  • État du jeu = SOUMIS
  • Choix du joueur (la plage est de 50)
  • Clé publique
  • Récompense potentielle (dépend du pari du joueur)




Voici à quoi ressemble la base de données de valeurs-clés sur la blockchain:

 { "type": "string", "value": "03WON_0283_448t8Jn9P3717UnXFEVD5VWjfeGE5gBNeWg58H2aJeQEgJ_06574069_09116020000_0229", "key": "2GKTX6NLTgUrE4iy9HtpSSHpZ3G8W4cMfdjyvvnc21dx" } 


«Clé» est l' identifiant du jeu pour un nouveau jeu. Les données restantes sont contenues dans le champ «valeur». Ces entrées sont stockées dans l'onglet Données du contrat intelligent:




5. Le serveur trouve la transaction envoyée (le nouveau jeu) via l'API blockchain. L'identifiant du jeu est déjà enregistré dans la blockchain, il est donc impossible de le modifier ou de le supprimer.

6. Le serveur forme une fonction de retrait (gameId, rsaSign) telle que:

retirer ( «FwsuaaShC6DMWdSWQ5osGWtYkVbTEZrsnxqDbVx5oUpq», «base64: Gy69dKdmXUEsAmUrpoWxDLTQOGj5 / qO8COA + QjyPVYTAjxXYvEESJbSiCSBRRCOAliqCWwaS161nWqoTL / TltiIvw3nKyd4RJIBNSIgEWGM1tEtNwwnRwSVHs7ToNfZ2Dvk / GgPUqLFDSjnRQpTHdHUPj9mQ8erWw0r6cJXrzfcagKg3yY / 0wJ6AyIrflR35mUCK4cO7KumdvC9Mx0hr / ojlHhN732nuG8ps4CUlRw3CkNjNIajBUlyKQwpBKmmiy3yJa / QM5PLxqdppmfFS9y0sxgSlfLOgZ51xRDYuS8NViOA7c1JssH48ZtDbBT5yqzRJXs3RnmZcMDr ​​/ q0x6Bg ==»)

7. Le serveur envoie une transaction d'invocation au contrat intelligent (diffusion InvocationTx). La transaction contient un appel à la fonction de retrait générée (gameId, rsaSign):



La fonction contient un identifiant de jeu et une signature RSA d'un identifiant unique. Le résultat de la signature est immuable.

Qu'est-ce que cela signifie?


Nous prenons la même valeur (identifiant de jeu) et lui appliquons la méthode de signature RSA. C'est ainsi que fonctionne l'algorithme RSA. Il est impossible de manipuler le nombre final car l' identifiant du jeu et le résultat de l'algorithme RSA sont inconnus. Il est également inutile d'essayer de deviner un nombre.

8. La blockchain reçoit une transaction qui exécute la fonction de retrait (gameId, rsaSign).

9. Il y a un appel pour la fonction GenerateRandIn à l'intérieur de la fonction de retrait (gameId, rsaSign). Il s'agit d'un générateur de nombres aléatoires.

 # @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 est un nombre aléatoire

Tout d'abord, la chaîne résultant de la signature RSA est prise. Ensuite, il est haché via SHA-256 ( sha256 (rsaSign) ).

Nous ne pouvons pas prédire le résultat de la signature et le hachage ultérieur. Ainsi, il est impossible d'affecter sa génération. Pour obtenir un nombre dans une plage spécifique (par exemple de 1 à 100), les fonctions de conversion en Int et% 100 ( mod analogique) sont appliquées.

Au début de l'article, nous avons mentionné la fonction rsaVerify () qui permet de vérifier la validité d'une signature RSA par une clé privée par rapport à une clé publique. Voici une partie de GenerateRandInt (gameId, rsaSign):

rsaVerify (SHA256, toBytes (gameId), rsaSign, RSAPUBLIC)

Pour commencer, la clé publique RSAPUBLIC et la chaîne rsaSign sont prises. La validité de la signature est vérifiée. Si la vérification réussit, le numéro est généré. Sinon, le système considère que la signature n'est pas valide (signature RSA non valide).

Le serveur doit signer l'identifiant du jeu à l'aide d'une clé privée et envoyer une signature RSA valide dans 2 880 blocs. L'option est gérée pendant le déploiement du contrat intelligent. Si rien ne se passe dans le temps imparti, l'utilisateur gagne. Dans ce cas, la récompense doit être envoyée par l'utilisateur indépendamment. Il s'avère que la triche n'est pas rentable pour le serveur car cela conduit à une perte. Voici un exemple ci-dessous.



L'utilisateur joue Dice Roller . Il choisit 2 faces sur 6 cubes, avec une mise de 14 VAGUES. Si le serveur n'envoie pas de signature RSA valide au contrat intelligent dans un délai défini (2 880 blocs), l'utilisateur recevra 34,44 ONDES.

Pour la génération de nombres, nous utilisons un oracle, un système externe plutôt que la blockchain. Le serveur implémente une signature RSA pour l'ID de jeu. Le contrat intelligent vérifie la validité de la signature et détermine le gagnant. Si le serveur n'envoie rien, l'utilisateur gagnerait automatiquement.

Cette méthode garantit que la manipulation est techniquement impossible. Tous les jeux Tradisys sont basés sur l'algorithme décrit ci-dessus - garantissant que nos jeux sont équitables et transparents. Tout peut être audité publiquement pour garantir l'honnêteté.

Source: https://habr.com/ru/post/fr464395/


All Articles