There is a problem - it is difficult to generate a random number in a decentralized network. Almost all blockchains have already faced this. Indeed, in networks where there is no trust between users, the creation of an undeniable random number solves many problems.
In the article we tell how we managed to solve the problem using the example of games. The first of these was
Initially, we planned to generate a number based on information from the blockchain. However, then it became clear: the number can be manipulated, which means that the solution is not suitable.
We came up with a workaround: use the "commit-discovery" scheme. The server “guessed” a number from 1 to 5, added a “salt” to it, and then hashed the result with
The player made a bet, and the server sent the hidden number and “salt” to the smart contract. In simple terms, he opened the cards. After that, the server checked the numbers and decided whether the user won or lost.
If the server did not send a number or "salt" for verification, the user won. In this case, for each game, it was necessary to deploy a smart contract in advance and put a potential win into it. It turned out to be inconvenient, long and expensive. At that time, there was no other safe solution.
Recently, the Tradisys team proposed adding a feature to the Waves protocol rsaVerify(). It checks the validity of an RSA signature based on the public and private keys. As a result, the function was added.
We have developed three games:
Consider the generation of a random number on the example of Ride on Waves. Smart contract can be found
Go to the tab Script and then decompiled. See the smart contract code (aka script).
The smart contract code contains a set of functions. Those marked with @Callable can be launched with Invocation transactions. We are interested in two functions: bet и withdraw:
- func bet (playerChoice)
- func withdraw(gameId,rsaSign)
1. The user chooses the length of the segment and the size of the bet.
2. The client generates a bet function. For the image above it would be bet("50").
3. The client sends an Invocation transaction to the address of the smart contract (broadcast InvocationTx). The transaction contains the bet function as a call parameter. This means that the Invocation transaction starts the execution of the bet function (choice: String) on the smart contract.
4. Consider the bet function:
@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)))
}
}
The function writes a new game to the smart contract state. Namely:
- Unique identifier for the new game (game id)
- Game state = SUBMITTED
- Player's choice (line length 50)
- public key
- Potential winnings (depends on the player's bet)
This is how the data entry in the blockchain (key-value) looks like:
{
"type": "string",
"value": "03WON_0283_448t8Jn9P3717UnXFEVD5VWjfeGE5gBNeWg58H2aJeQEgJ_06574069_09116020000_0229",
"key": "2GKTX6NLTgUrE4iy9HtpSSHpZ3G8W4cMfdjyvvnc21dx"
}
"Key" (key) - game id new game. The rest of the data is contained in the value field line. These records are stored in the tab Data smart contract:
5. The server “looks” at the smart contract and finds the sent transaction (new game) using the Api blockchain. The game id of the new game is already recorded in the blockchain, which means that it can no longer be changed or influenced
6. The server generates the withdraw function (gameId, rsaSign). For example, this one:
withdraw ("FwsuaaShC6DMWdSWQ5osGWtYkVbTEZrsnxqDbVx5oUpq", "base64:Gy69dKdmXUEsAmUrpoWxDLTQOGj5/qO8COA+QjyPVYTAjxXYvEESJbSiCSBRRCOAliqCWwaS161nWqoTL/TltiIvw3nKyd4RJIBNSIgEWGM1tEtNwwnRwSVHs7ToNfZ2Dvk/GgPUqLFDSjnRQpTHdHUPj9mQ8erWw0r6cJXrzfcagKg3yY/0wJ6AyIrflR35mUCK4cO7KumdvC9Mx0hr/ojlHhN732nuG8ps4CUlRw3CkNjNIajBUlyKQwpBKmmiy3yJa/QM5PLxqdppmfFS9y0sxgSlfLOgZ51xRDYuS8NViOA7c1JssH48ZtDbBT5yqzRJXs3RnmZcMDr/q0x6Bg==")
7. The server sends an Invocation transaction to the smart contract (broadcast InvocationTx). The transaction contains a call to the generated withdraw function (gameId, rsaSign):
Function contains game id new game and the result of the RSA signature of the unique identifier with the private key. The result of the signature is unchanged.
What does this mean?
We take the same value (game id) and apply the RSA signature method to it. We will always get the same result. This is how the RSA algorithm works. The final number cannot be manipulated because the game id and the result of applying RSA is not known. Selecting a number is also pointless.
8. The blockchain accepts the transaction. It runs the withdraw function (gameId, rsaSign)
9. Withdrawal occurs inside the withdraw function GenerateRandInt Functions (gameId, rsaSign). This is the random number generator
# @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")
}
all - is a random number.
First, the string is taken, which is the result of the RSA signature game id private key (rsaSign). Then hashed with SHA-256 (sha256(rsaSign)).
We cannot predict the outcome of the signature and subsequent hashing. Therefore, it is impossible to influence the generation of a random number. To get a number in a certain range (for example, from 1 to 100), the toInt conversion function and %100 are used (similar to
At the beginning of the article, we mentioned the function rsaVerify(), which allows you to check the validity of an RSA signature with a private key against a public one. Here is the GenerateRandInt(gameId,rsaSign) part:
rsaVerify (SHA256, toBytes(gameId), rsaSign, RSAPUBLIC)
The public key RSAPUBLIC and the string rsaSign are passed to the input. The signature is checked for validity. The number is generated in case of successful validation. Otherwise, the system considers that the signature is not valid (Invalid RSA signature).
The server must sign the game id of the game with a private key and send a valid Rsa signature within 2880 blocks. The parameter is configured when deploying a smart contract. If nothing happens within the allotted time, the user wins. In this case, the prize must be sent to your address yourself. It turns out that it is “not profitable for the server to cheat”, because this leads to a loss. Below is an example.
The user is playing
To generate numbers in games, we use an oracle - an external, non-blockchain system. The server performs the RSA signature of the game id. The smart contract checks the validity of the signature and determines the winner. If the server did not send anything, then the user automatically wins.
This is an honest generation method, because manipulation is technically impossible. All Tradisys games work on the basis of the described algorithm. This is how blockchain games work. Everything is transparent and verifiable. There are no analogues of such a system in any other blockchain. This is an honest random.
Source: habr.com