One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
Submission Details
Impact: medium
Likelihood: medium

One Shot: Reloaded

Author Revealed upon completion

Root + Impact

Description

  • The protocol is designed so that two players stake equal CRED wagers, and the battle outcome is determined using each Rapper’s stats combined with randomness. The winner receives the pooled CRED and has their NFT’s win record updated.

  • Instead of using a secure source of randomness, the contract derives randomness from timestamp::now_seconds(). Timestamps on Aptos are predictable and subject to validator control within protocol tolerances. This makes battle outcomes manipulable by anyone with influence over transaction ordering or block production.

let t = timestamp::now_seconds(); //@> Insecure randomness source
let seed = hash(rapper_stats, t);
let winner = seed % 2;

Risk

Likelihood:

  • Validators/proposers can directly manipulate timestamps when producing blocks.

  • Attackers can time their transactions to land on favorable timestamps since outcomes can be simulated off-chain.

Impact:

  • Players can consistently win wagers by predicting or forcing favorable seeds.

  • Protocol’s CRED pools and fairness of battles are compromised, leading to economic loss and reputational damage.

Proof of Concept

Attack flow:

  1. Attacker simulates outcomes locally for likely timestamps.

  2. They only broadcast transactions that guarantee favorable results.

  3. Validators colluding (or the attacker as proposer) can set timestamps within allowable drift to bias the outcome.

  4. Attacker consistently drains CRED pools.

// Root cause
let t = timestamp::now_seconds(); //@> attacker can predict/control this
let seed = hash(rapper_stats, t);
let winner = seed % num_players;

Recommended Mitigation

Replace timestamp::now_seconds() with Aptos’ secure randomness API:

  • aptos_framework::randomness::u64_integer() or randomness::bytes()

  • Optionally use a commit–reveal scheme where both players contribute entropy to the random seed.

  • Ensure randomness is not solely dependent on validator-controlled or predictable values.

let r: u64 = randomness::u64_integer(); //@> secure randomness
let seed = hash(rapper_stats, bcs::to_bytes(&r));
let winner = seed % num_players;

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.