Hay un problema que teníamos que abordar en el curso del desarrollo de nuestros juegos. Es complicado generar un número aleatorio en una red distribuida. Casi todas las cadenas de bloques ya se han enfrentado a este problema. De hecho, en redes donde no hay confianza entre nadie, la creación de un número aleatorio resuelve una amplia gama de problemas.
En este artículo, explicamos cómo resolvimos este problema para nuestros juegos. El primero de ellos fue
Waves Xmas Tree .

Inicialmente, planeamos generar un número utilizando información de la cadena de bloques. Sin embargo, en una investigación adicional, quedó claro que el proceso utilizado para crear un número de esta manera podría ser manipulado. Tuvimos que descartar esta solución.
Se nos ocurrió una solución alternativa, utilizando un esquema de 'confirmación de revelado'. El servidor propuso un número del 1 al 5, le agregó 'sal' y agregó el resultado utilizando la
función Keccak . El servidor depuró previamente un contrato inteligente con un número ya guardado. El resultado fue que el juego se redujo efectivamente al usuario adivinando el número oculto por el hash.
El jugador hizo su apuesta y el servidor envió un número oculto y 'sal' a un contrato inteligente. Para decirlo de otra manera, las cartas fueron reveladas. Posteriormente, el servidor verificó los números y decidió si el usuario había ganado o perdido.
Si el servidor no envió el número y 'sal' para verificación, entonces el usuario ganó. En este caso, era necesario implementar un contrato inteligente por adelantado y organizar las ganancias potenciales para cada juego. Esto era inconveniente, costoso y lento. En ese momento, sin embargo, no había otra solución segura.
Poco después, el equipo de Tradisys propuso agregar la función
rsaVerify () al protocolo Waves. Esto verifica la validez de una firma RSA basada en claves públicas y privadas. Como resultado de nuestra propuesta, se agregó la función.
Creamos tres juegos nuevos:
Dice Roller ,
Coin Flip y
Ride On Waves . En cada uno de ellos, se implementó la nueva tecnología de números aleatorios. Echemos un vistazo más de cerca a cómo funciona.

Veamos primero la generación de números aleatorios. Puede encontrar el contrato inteligente
aquí .
Vaya a la
pestaña Script y elija
Descompilado . Verá el código del contrato inteligente (o script).

El código de contrato inteligente consta de una lista de funciones. Los que son @Callable se pueden ejecutar a través de
transacciones de invocación . Estamos interesados en dos de ellos:
apostar y
retirar :
- apuesta de func (playerChoice)
- Func retirada (gameId, rsaSign)
1. El usuario elige el rango y el tamaño de la apuesta.

2. El cliente organiza la función de apuesta. Para la imagen de arriba se
apostaría ("50")3. El cliente envía una transacción de invocación a la dirección del contrato inteligente (difusión InvocationTx). Una transacción como parámetro de llamada contiene la función de apuesta. Esto significa que la transacción de Invocación inicia la ejecución de la función de apuesta en el contrato inteligente (opción: Cadena).

4. Veamos la función de apuesta:
@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 función registra un nuevo juego en el estado de contrato inteligente:
- Nueva identificación única del juego (identificación del juego)
- Estado del juego = ENVIADO
- Elección del jugador (el rango es 50)
- Clave pública
- Recompensa potencial (depende de la apuesta del jugador)

Así es como se ve la base de datos de valores clave en la cadena de bloques:
{ "type": "string", "value": "03WON_0283_448t8Jn9P3717UnXFEVD5VWjfeGE5gBNeWg58H2aJeQEgJ_06574069_09116020000_0229", "key": "2GKTX6NLTgUrE4iy9HtpSSHpZ3G8W4cMfdjyvvnc21dx" }
'Clave' es la
identificación del
juego para un nuevo juego. Los datos restantes están contenidos en el campo 'valor'. Estas entradas se almacenan en la pestaña
Datos del contrato inteligente:


5. El servidor encuentra la transacción enviada (el nuevo juego) a través de la API blockchain. La identificación del juego ya está registrada en la cadena de bloques, por lo que es imposible cambiarla o eliminarla.
6. El servidor forma una función de retirada (gameId, rsaSign) como:
retirar ( «FwsuaaShC6DMWdSWQ5osGWtYkVbTEZrsnxqDbVx5oUpq», «base 64: Gy69dKdmXUEsAmUrpoWxDLTQOGj5 / qO8COA + QjyPVYTAjxXYvEESJbSiCSBRRCOAliqCWwaS161nWqoTL / TltiIvw3nKyd4RJIBNSIgEWGM1tEtNwwnRwSVHs7ToNfZ2Dvk / GgPUqLFDSjnRQpTHdHUPj9mQ8erWw0r6cJXrzfcagKg3yY / 0wJ6AyIrflR35mUCK4cO7KumdvC9Mx0hr / ojlHhN732nuG8ps4CUlRw3CkNjNIajBUlyKQwpBKmmiy3yJa / QM5PLxqdppmfFS9y0sxgSlfLOgZ51xRDYuS8NViOA7c1JssH48ZtDbBT5yqzRJXs3RnmZcMDr / q0x6Bg ==»)
7. El servidor envía una transacción de Invocación al contrato inteligente (difusión InvocationTx). La transacción contiene una llamada a la función de retiro generada (gameId, rsaSign):

La función contiene la
identificación del juego y una firma RSA de una identificación única. El resultado de la firma es inmutable.
¿Qué significa esto?
Tomamos el mismo valor (identificación del juego) y le aplicamos el método de firma RSA. Así es como funciona el algoritmo RSA. Es imposible manipular el número final porque la
identificación del juego y el resultado del algoritmo RSA son desconocidos. Tampoco tiene sentido tratar de adivinar un número.
8. La cadena de bloques recibe una transacción que ejecuta la función de retiro (gameId, rsaSign).
9. Hay una llamada para la función GenerateRandIn dentro de la función de retiro (gameId, rsaSign). Este es un generador de números aleatorios.
# @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 es un número aleatorio
En primer lugar, se toma la cadena que es el resultado de la firma RSA. Luego, se procesa mediante SHA-256 (
sha256 (rsaSign) ).
No podemos predecir el resultado de la firma y el hashing posterior. Por lo tanto, es imposible afectar a su generación. Para obtener un número en un rango específico (por ejemplo, de 1 a 100), se aplican las funciones de conversión a Int y% 100 (
mod analógico).
Al comienzo del artículo, mencionamos la función
rsaVerify () que permite verificar la validez de una firma RSA por una clave privada contra una pública. Aquí hay una parte de GenerateRandInt (gameId, rsaSign):
rsaVerify (SHA256, toBytes (gameId), rsaSign, RSAPUBLIC)
Para empezar, se toman la clave pública RSAPUBLIC y la cadena rsaSign. Se verifica la validez de la firma. Si la verificación es exitosa, se genera el número. De lo contrario, el sistema considera que la firma no es válida (firma RSA no válida).
El servidor tiene que firmar la identificación del juego con una clave privada y enviar una firma RSA válida dentro de 2.880 bloques. La opción se administra mientras se implementa el contrato inteligente. Si no sucede nada en el tiempo establecido, el usuario gana. En este caso, la recompensa debe ser enviada por el usuario de forma independiente. Resulta que hacer trampa no es rentable para el servidor porque esto lleva a una pérdida. Hay un ejemplo a continuación.

El usuario juega
Dice Roller . Elige 2 de 6 caras de cubo, con una apuesta de 14 ONDAS. Si el servidor no envía una firma RSA válida al contrato inteligente dentro de un tiempo establecido (2.880 bloques), el usuario recibirá 34.44 ONDAS.
Para la generación de números, utilizamos un oráculo, un sistema externo en lugar de la cadena de bloques. El servidor implementa una firma RSA para la identificación del juego. El contrato inteligente verifica la validez de la firma y determina el ganador. Si el servidor no envía nada, el usuario ganará automáticamente.
Este método asegura que la manipulación sea técnicamente imposible. Todos los juegos de Tradisys se basan en el algoritmo descrito anteriormente, lo que garantiza que nuestros juegos sean justos y transparentes. Todo se puede auditar públicamente para garantizar la honestidad.