Si estoy escribiendo un contrato inteligente para algún tipo de juego (de apuestas), ¿cómo puedo generar un número aleatorio de forma segura? ¿Cuáles son las mejores prácticas y compensaciones de seguridad a tener en cuenta?
Hay algunas compensaciones y puntos clave a tener en cuenta en esta área.
Cualquier decisión que tome un usuario que afecte el resultado le da a ese usuario una ventaja injusta. Ejemplos incluyen:
Todo lo que ve el contrato, lo ve el público.
El EVM no superará a una computadora física.
Ahora la técnica:
Compromiso no maleable al estilo de la lotería perfectamente descentralizado:
N
N
con su dirección: bytes32 hash = sha3(N,msg.sender)
1
Una vez que finaliza la ronda de sumisión, comienza la ronda de revelación.
N
al contratosha3(N,msg.sender)
coincida con la presentación original.Si el usuario no presenta un válido N
a tiempo, su depósito se perderá.
N
s están todos XOR
juntos para generar el número aleatorio resultante.N % numUsers
) 3Notas y alternativas:
1. Los usuarios deben concatenar su dirección a su N
antes del hashing. De lo contrario, otro usuario podría simplemente enviar un hash idéntico, luego esperar a N
que se revele y luego enviarlo. N XOR N
es igual a 0, por lo que esto podría usarse para cancelar todos los envíos excepto los del atacante.
2. Aquí es donde entran las compensaciones. La última persona en revelar su N
decisión tiene la opción de revelar o no revelar. Esto esencialmente les da una doble oportunidad de ganar. Ingrese suficientes veces y obtendrá una nueva opción para cada entrada. Sugerencia: los mineros eligieron el orden de las transacciones en un bloque. Para desalentar esto, los usuarios deben realizar un gran depósito de seguridad, igual al valor que ganarían manipulando el número aleatorio. Esto podría ser un problema para muchos usuarios, especialmente para grandes premios mayores, incluso con optimizaciones de teoría de juegos.
3. Es necesaria una recompensa para fomentar la competencia entre los participantes. Provoca una situación clásica de tragedia de los comunes/dilema del prisionero. La colusión entre los participantes les permitiría ganar un bote grande y dividirlo entre ellos, pero si todos saben lo que los demás enviarán, sabrán lo que deben enviar ellos mismos para ganar la recompensa. Por lo tanto, si la recompensa es mayor que el valor del número aleatorio dividido por la cantidad de jugadores, se incentiva a todos a mantener su propio número en secreto para tener una mejor oportunidad de obtener la recompensa. Tenga en cuenta que solo uno de los participantes debe enviar un buen número aleatorio y el resultado será impredecible.
Ejemplos: Chainlink VRF , RANDAO , El hilo donde pensé por primera vez en esto
m
entradas N0, ..., Nm
donde m
está el número de bits en cada entrada y Ni
son m-(i+1)
ceros seguidos de un 1 seguido de i
más ceros, por ejemplo, N3
= 0...01000. En la etapa de revelación, ahora puede voltear el j
bit menos significativo en la combinación de entradas XOR al revelar su entrada correspondiente Nj
. Por lo tanto, puede cambiar la combinación XOR'd a lo que quiera y, en particular, asegurarse de que N % numPlayers
sea igual a una de sus entradas reveladas.N
s y BLOCKHASH
todos juntos también podrían ser XOR
'd para generar otro número aleatorio? @Tjaden Hess♦N
puede poner en peligro la aleatoriedad de la salida? (como sugiere en su primera nota al pie, diciendo " N XOR N
") Estaba pensando en concatenar todo N
, luego hacer un hash del resultado. De esa manera, nadie puede "cancelar" la entropía, solo dejar de contribuir a ella (por ejemplo, con un valor notable como 0
). ¡Ah, y gracias por sus contribuciones a esta pregunta!Tenga en cuenta que con la sugerencia de linagee, no solo está confiando en random.org, también está confiando en Oraclize. Oraclize publica una prueba notarial de TLS para demostrar que los datos que le brindan realmente provienen de random.org, pero eso no es suficiente para este caso: necesitamos saber que estos fueron los únicos datos que obtuvieron de random.org. De lo contrario, podrían seguir intentándolo y descartando números aleatorios de random.org hasta que consiguieran uno que ganara su apuesta, y no tendrías forma de detectarlo.
Puede usar https://api.random.org/json-rpc/1/ que le brinda una fuente aleatoria de datos a través de JSON y Oraclize, lo que le permite utilizar el feed dentro de un contrato de Ethereum y, opcionalmente, autenticarlo fuertemente. como si hubiera venido de random.org. (Junto con los métodos existentes para usar el hash del bloque, la marca de tiempo y demás).
Estaría "confiando" en random.org para proporcionarle datos aleatorios. Puede mitigar el riesgo utilizando múltiples fuentes de aleatoriedad.
Dado que los diferentes contratos aseguran diferentes cantidades de valor , en el extremo opuesto del espectro de RANDAO se encuentra el simple BLOCKHASH .
Si BLOCKHASH puede satisfacer su propósito, se recomienda encarecidamente que revise la pregunta (a continuación se muestra solo un fragmento):
¿Cuándo se puede usar BLOCKHASH de manera segura para un número aleatorio? ¿Cuándo sería inseguro?
Como regla general, BLOCKHASH solo se puede usar de manera segura para un número aleatorio si la cantidad total de valor que se basa en la calidad de esa aleatoriedad es menor que lo que gana un minero extrayendo un solo bloque.
Para hacerlo en la práctica, use un Chainlink VRF .
Conceptualmente, use esta respuesta .
Para hacer el número aleatorio, necesitamos algunas variables:
keyhash
: El hash de clave pública del oráculo(s). Esto es para ASEGURARSE de que están haciendo un número aleatorio basado en la semilla que le damos). Vamos a utilizar el oráculo VRF de Chainlink de ropsten.
userProvidedSeed
: Una semilla de nuestra elección, esta es otra pieza que vamos a usar para demostrar que nos están haciendo un número aleatorio. Cada vez que llamamos a rollDice, debemos usar una semilla diferente. Consulte la selección de una semilla para obtener más información. Estos son los pasos n.º 2 "Cada usuario genera su propio número aleatorio secreto N" y n.º 3 "Los usuarios crean su compromiso codificando su N con su dirección: bytes32 hash = sha3(N,msg.sender)1" en esta respuesta .
_vrfcoordinator
y _link
: Estas son la dirección del contrato del vrfcoordinator y el token Chainlink. Están codificados para Ropsten en este contrato.
A continuación se muestra un ejemplo completo.
Para implementar el contrato, use las direcciones _vrfCoordinator y _link proporcionadas en los comentarios. Luego use su número hash como el userProvidedSeed
.
pragma solidity 0.6.2;
import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/contracts/src/v0.6/VRFConsumerBase.sol";
contract Verifiable6SidedDiceRoll is VRFConsumerBase {
using SafeMath for uint;
bytes32 internal keyHash;
uint256 internal fee;
event RequestRandomness(
bytes32 indexed requestId,
bytes32 keyHash,
uint256 seed
);
event RequestRandomnessFulfilled(
bytes32 indexed requestId,
uint256 randomness
);
/**
* @notice Constructor inherits VRFConsumerBase
* @dev Ropsten deployment params:
* @dev _vrfCoordinator: 0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb
* @dev _link: 0x20fE562d797A42Dcb3399062AE9546cd06f63280
*/
constructor(address _vrfCoordinator, address _link)
VRFConsumerBase(_vrfCoordinator, _link) public
{
vrfCoordinator = _vrfCoordinator;
LINK = LinkTokenInterface(_link);
keyHash = 0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205; // hard-coded for Ropsten
fee = 10 ** 18; // 1 LINK hard-coded for Ropsten
}
/**
* @notice Requests randomness from a user-provided seed
* @dev The user-provided seed is hashed with the current blockhash as an additional precaution.
* @dev 1. In case of block re-orgs, the revealed answers will not be re-used again.
* @dev 2. In case of predictable user-provided seeds, the seed is mixed with the less predictable blockhash.
* @dev This is only an example implementation and not necessarily suitable for mainnet.
* @dev You must review your implementation details with extreme care.
*/
function rollDice(uint256 userProvidedSeed) public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) > fee, "Not enough LINK - fill contract with faucet");
uint256 seed = uint256(keccak256(abi.encode(userProvidedSeed, blockhash(block.number)))); // Hash user seed and blockhash
bytes32 _requestId = requestRandomness(keyHash, fee, seed);
emit RequestRandomness(_requestId, keyHash, seed);
return _requestId;
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) external override {
uint256 d6Result = randomness.mod(6).add(1);
emit RequestRandomnessFulfilled(requestId, randomness);
}
}
Nota: En este momento, Chainlink VRF no tiene la XOR
función de envío descentralizado, esto aún está en desarrollo. Consulte el blog de Chainlink para obtener más detalles.
La respuesta corta es que no puedes. RANDAO funciona, pero es lento, y si su juego es popular, habrá un fuerte incentivo para que la gente juegue aplicando el último número.
Sugiero usar un oráculo para proporcionar aleatoriedad. (como se menciona en Notas y alternativas 2c de Tjaden Hess). Los dos principales beneficios son que puede afirmar firmemente que su número es independiente de cualquier otra apuesta, lo cual es vital para valorar el riesgo de usar ese número. En segundo lugar, efectivamente no hay límite en el rendimiento de entropía. Si hay un mercado de oráculos, puede aprovechar el historial irrevocable de la cadena de bloques para modelar la probabilidad de que un oráculo determinado esté en connivencia con los jugadores o no. Por supuesto, los jugadores, la casa y el oráculo proporcionan su propia entropía. Se pueden agregar otras características, como listas blancas, listas negras, bonos que afirman la no colusión, etc., según se desee.
Me parece que las diversas sugerencias presentadas aquí tienen en común un modelo en el que un contrato inteligente actúa como servidor en lo que es esencialmente una arquitectura normal de un solo servidor/cliente múltiple, y gran parte de la preocupación es que un Miner jugará con el sistema, que es el problema que siempre ha existido cuando hay un solo servidor confiable.
¿Por qué no poner las cosas de lado y simplemente no involucrar el contrato en absoluto en el proceso de generación de números aleatorios?
Un juego similar a la lotería podría funcionar de esta manera:
Cada jugador envía una transacción al contrato de lotería que contiene la tarifa, un número "seleccionado" deseado y un valor semilla aleatorio encriptado. Si el mismo jugador quiere elegir otro número, solo se requiere la tarifa y la selección: la semilla es por jugador, no por apuesta.
Después de que se cierran las apuestas, el jugador envía otra transacción, esta vez que no contiene nada más que la clave para descifrar su semilla.
Después de que todos hayan proporcionado las claves de descifrado (o haya transcurrido una cantidad de tiempo suficiente), cada jugador obtiene todas las claves y las semillas cifradas y calcula el número aleatorio "real", probablemente solo una suma modular de las semillas descifradas. Si es una de las selecciones del jugador, se envía una transacción al contrato que hace que el contrato verifique que el jugador proporcionó una semilla, verifique que el número ganador proporcionado en la transacción sea correcto y que haya sido seleccionado por el jugador, y luego otorgar las ganancias.
Aquí hay muchos detalles que agitan las manos y se omiten, pero creo que la idea básica es sólida.
¿Por qué no utilizar simplemente las cifras oficiales de la lotería de la televisión? Por ejemplo, el Lotto Zahlen alemán. Luego, crea un mecanismo de votación en el que los jugadores pueden votar los números ganadores después de que los números de la lotería hayan sido sorteados y publicados en la televisión. Para incentivar también a los perdedores a votar, podría ofrecer a los jugadores que reembolsen, digamos, el 5 % de su apuesta si deciden votar y su voto resulta ser consensuado entre todos los jugadores.
He publicado eth-random , que es una guía simplificada sobre cómo implementamos RNG en CryptoKitties.
Hay algunas advertencias al respecto, pero hasta este momento no he visto un solo intento de pirateo fundado en él.
Actualización 1: para ver un ejemplo práctico de la implementación, consulte el código fuente del contrato CK - funciones _breedWith
(actuar como confirmación) y giveBirth
+ geneScience.mixGenes
(actuar como revelación).
Pensemos que está utilizando un hash de bloque para decidir un ganador de lotería y el minero A participa en esa lotería y compra un boleto. Luego extrae muchos bloques válidos y los tira, si no se ajustan a su boleto.
En la práctica, cada bloque válido que tira un minero le cuesta 5 éteres. Si las probabilidades de su lotería son tales, ese minero A tiene que, por ejemplo, crear 1 000 000 de bloques válidos hasta que encuentre un bloque adecuado para ganar, tirará 5 000 000 éteres.
Si la ganancia principal de su lotería es 1 000 000 éteres, no tiene sentido que un minero tire 5 000 000 eth para ganar 1 000 000.
Tengo una idea que se basa en el protocolo de Tjaden que describo aquí:
¿Es este enfoque de una lotería ethereum sonido y/o novedoso?
Resuelve el problema de tener que poner un gran depósito de seguridad en las loterías. Agradecería algunos comentarios.
No conozco el mejor método seguro, pero vea a continuación una lista de implementaciones vulnerables (así que finalmente no lo use ):
Puede echar un vistazo a la predicción de números aleatorios en los contratos inteligentes de Ethereum , que explica todos los problemas de seguridad relacionados con los diferentes tipos de generadores de números pseudoaleatorios (PRNG) implementados en (muchos de los existentes) contratos inteligentes.
Solo una actualización de esta pregunta, pero esta vez usa una función de retraso verificable de llamada técnica (vdf). Fue construido en base al rompecabezas de bloqueo de tiempo inventado por Ronald L Rivest et al . La idea central detrás es que si no puede calcular el resultado en un período de tiempo determinado, entonces el resultado es pseudo aleatorio. Entonces, con eso en mente, vdf es una función que toma una entrada y puede tardar 1 hora en calcularse (puede cambiar el tiempo en la fase de configuración de esta función), luego muestra el resultado y una evidencia de que este resultado es de hecho, calcule usando esa entrada en poco tiempo.
Entonces, la idea es como el Compromiso no maleable de estilo de lotería perfectamente descentralizado que se propuso en la respuesta de esta pregunta, pero solo necesitamos agregar vdf en un paso antes
- Todas las N se combinan con XOR para generar el número aleatorio resultante.
Entonces nuestro paso es:
Con ese resultado, dejamos que se ejecute a través de nuestra función vdf (el tiempo para calcularlo debe ser más largo que el tiempo de envío para garantizar que el último envío no pueda calcular el resultado final).
Luego, publica el resultado de la función vdf para todos, de modo que todos puedan verificar que el resultado se calculó usando la entrada
La recompensa es la misma que la propuesta de respuesta.
Pero solo simplifico la idea central detrás de esto, la belleza de esto está en las matemáticas que lo implementan.
Documento de función de retardo verificable: https://eprint.iacr.org/2018/601.pdf
También puede consultar este documento que habla de 2 vdf: https://crypto.stanford.edu/~dabo/pubs/papers/VDFsurvey.pdf
La generación segura de un número aleatorio es difícil debido a la naturaleza determinista de la EVM. Hay muchos enfoques. Cuál funciona mejor depende de sus requisitos de seguridad y contexto. Aquí hay algunos:
Si su número aleatorio no es crítico para la seguridad, simplemente puede usar el hash de bloque anterior. Por ejemplo, un número aleatorio de 0 a 99:uint256(block.blockhash(block.number-1)) % 100
Utilice un oráculo en el que confíe para obtener un número y realizar una devolución de llamada a su contrato inteligente.
A no debe poder predecir con qué evento se comprometerá B.
A tiene pruebas de que B no podría haber sabido el número cuando B se comprometió con el evento
B tiene pruebas de que el número se generó antes de comprometerse con el evento aleatorio
¿Qué tal este flujo:
1) Cada usuario genera una cadena "Soy parte de la lotería y mi número es 8272143" (en secreto) donde 8272143 es el número elegido por él mismo.
2) Cada usuario encripta su propia cadena con una contraseña elegida por él mismo (secreta)
3) Cada usuario publica la cadena cifrada, por lo que ahora todos pueden ver todas las cadenas cifradas
4) Cuando se decide no permitir más participantes, todos los usuarios publican sus contraseñas elegidas por ellos mismos y todas las cadenas se descifran. Los usuarios que no publiquen sus contraseñas quedan excluidos de ganar.
5) Todos pueden confirmar que cada jugador dice la verdad sobre su contraseña, ya que la primera parte de la cadena debe ser "Soy parte de la lotería y mi número es ", por lo que no hay forma de falsificar otro número en este punto. .
6) Todas las cadenas ahora están concatenadas y se genera un hash. ¡Este hash es el número aleatorio final! En el caso de una lotería, puede elegir el número más cercano al hash, pero esto es solo un detalle específico del caso.
De esta manera, no habrá que esperar a que se completen los bloques. Solo el descifrado por fuerza bruta de las cadenas permitirá obtener una ventaja. Esto se evitará mediante el uso de un cifrado lo suficientemente fuerte.
(Soy un novato total en este campo, así que no me digas si esto es una tontería).
ACTUALIZAR:
Me doy cuenta de que si 2 clientes trabajan juntos, el último cliente puede elegir esperar a publicar su contraseña y ser excluido en caso de que su amigo gane de esta manera. Una solución puede ser que todos los usuarios tengan una cantidad de ETH en la línea que tendrán que pagar si no publican su contraseña. Con todo, esta solución no es muy sexy, lo admito.
Otra forma de generar números aleatorios podría ser distribuir varias fuentes de aleatoriedad en la plataforma para generar el boleto ganador. Por ejemplo, podríamos usar los centavos de los precios de cierre de todas las acciones negociadas en las principales bolsas de valores del mundo. Sería casi imposible hacer que todas las bolsas de valores trabajen juntas para manipular la lotería.
Los precios de cierre son mostrados por varios servicios, incluidos Yahoo, Google, Bloomberg, etc., por lo que pueden funcionar como una especie de libro de contabilidad público para verificar que los precios sean correctos.
Además, dado que tantos comerciantes directos, algoritmos, comerciantes de empresas, etc. están involucrados en cambiar esos precios, sería casi imposible predecir cuál será el último precio negociado o esperar hasta el último segundo para ser la última persona en negociar un Valores.
Pruebe el siguiente algoritmo para evitar trampas.
La idea es usar el hash de un bloque, pero los mineros/participantes y organizadores no conocen el número de bloque para evitar trampas.
Para la persona que quiere adivinar el número ganador, no es posible obtener el número; en el contrato, solo tenemos un hash del número. El número es bastante grande uint64 (o podría ser incluso mayor) para evitar "deshacer" el número. Para los organizadores, el número es fijo y es inútil porque se deben conocer todas las direcciones de los propietarios de boletos para calcular el ganador.
¿Opiniones? ¿Algún agujero en la lógica?
Creé una pregunta separada, pero también podría responderse aquí.
Necesita usar un tercero para generar aleatoriedad.
Las cuentas de contrato solo realizan una operación cuando una cuenta de propiedad externa se lo indica. Por lo tanto, no es posible que una cuenta de contrato realice operaciones nativas, como la generación de números aleatorios o llamadas API; solo puede hacer estas cosas si se lo solicita una cuenta de propiedad externa. Esto se debe a que Ethereum requiere que los nodos puedan ponerse de acuerdo sobre el resultado del cálculo, lo que requiere una garantía de ejecución estrictamente determinista.
Lea más: http://ethdocs.org/en/latest/introduction/what-is-ethereum.html#how-does-ethereum-work
Hay oráculos de aleatoriedad como Chainlink VRF disponibles hoy en día.
Marco de Oracle aquí. Podría utilizar la fuente de datos aleatoria de Oraclize, que aprovecha un entorno de ejecución de confianza de libro mayor .
Puedes leer una introducción aquí y algunos ejemplos aquí .
Random Data Source es significativamente más seguro que usar Random.org con TLSNotary y usar blockhash para determinar un número aleatorio, y es menos difícil y costoso que otros esquemas de confirmación y revelación.
Kozuch
DFF