Skip to content

el resultado del dado se puede elegir antes de apostar #1

Description

@leocagli

En dice-duel, el resultado de la partida queda determinado por datos que quien
arma la transaccion conoce y controla antes de que nadie apueste.

La evidencia

contracts/dice-duel/src/lib.rs:274:

// Seed components (all deterministic and identical between sim/submit):
// 1. Session ID - unique per game
// 2. Player addresses - both players contribute
//
// Note: We do NOT include ledger sequence or timestamp because those differ
// between simulation and submission, which would cause different winners.
let mut seed_bytes = Bytes::new(&env);
seed_bytes.append(&Bytes::from_array(&env, &session_id.to_be_bytes()));
seed_bytes.append(&game.player1.to_string().to_bytes());
seed_bytes.append(&game.player2.to_string().to_bytes());
let base_seed = env.crypto().keccak256(&seed_bytes);

O sea: semilla = keccak256(session_id || player1 || player2).

Y en start_game (linea 135), el session_id es un parametro que elige quien
llama
:

pub fn start_game(
    env: Env,
    session_id: u32,
    player1: Address,
    player2: Address,
    player1_points: i128,
    player2_points: i128,
) -> Result<(), Error> {

Por que eso importa

Los tres ingredientes de la semilla se conocen antes de firmar. Entonces quien arma
la partida puede, offline y gratis, calcular el resultado para cada session_id
posible y presentar a la firma solamente uno donde gana. Cada intento es un keccak;
encontrar uno favorable lleva unos pocos.

El rival firma con require_auth_for_args([session_id, points]). Ve el numero, pero
no tiene ningun motivo para sospechar que ese numero decide la partida, y para
saberlo tendria que correr el mismo keccak.

Y hay un detalle que lo hace peor: roll() (linea 208) solo marca banderas. Los
dados se generan recien en reveal_winner. O sea que tirar es teatro; el resultado
ya estaba resuelto cuando se creo la partida.

Para un contrato que sostiene apuestas, esto es la falla que importa: no es que
alguien pueda hacer trampa con esfuerzo, es que el resultado se elige en vez de
sortearse.

El comentario del codigo explica por que se llego aca

Y la razon es legitima: incluir el ledger rompia la coincidencia entre simulacion y
envio. Es un problema real de Soroban, no un descuido. Lo que hay que cambiar es la
salida, no el diagnostico.

Direccion del arreglo

El patron que resuelve esto es compromiso y revelacion, que separa el momento de
apostar del momento en que se conoce la aleatoriedad:

  1. Cada jugador publica hash(su_numero_secreto + sal) junto con la apuesta.
  2. Nadie puede calcular el resultado, porque le falta el secreto del otro.
  3. Vencido el plazo, los dos revelan; el contrato comprueba los hashes.
  4. La semilla sale de los dos secretos combinados.
  5. Si uno no revela, el otro cobra por incomparecencia.

Eso mantiene la propiedad que el comentario buscaba (simulacion y envio dan lo mismo,
porque en el momento de liquidar todo es dato firme) y saca la que sobra (que el
resultado se pueda anticipar).

  • Fase de compromiso: hash(secreto + sal) de cada jugador junto con la apuesta
  • Fase de revelacion con plazo, y validacion del hash contra lo revelado
  • La semilla se deriva de los dos secretos, no del session_id
  • Camino de incomparecencia: si uno no revela, el otro cobra
  • Ligar el compromiso a este contrato y a esta partida, para que no se pueda
    reusar un compromiso viejo en otra partida
  • Tests del camino feo: revelar algo que no coincide con el hash, no revelar,
    revelar dos veces, reusar el compromiso de otra partida

Antes de escribir codigo

Revisar si number-guess y twenty-one tienen la misma forma. En number-guess la
semilla incluye session_id y las dos apuestas (contracts/number-guess/src/lib.rs:280),
que es mejor pero puede compartir el problema si las dos apuestas viajan en la misma
llamada. Conviene arreglar los tres con el mismo patron en vez de tres parches
distintos.


Como se toma este trabajo: comentá la issue con un plan concreto (que archivo, que
funcion, y como lo vas a verificar). Se asigna por la calidad de ese plan, no por
orden de llegada. La PR va despues de la asignacion, con el cambio real adentro.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions