One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
View results
Submission Details
Severity: high
Valid

One Shot: Reloaded

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;
Updates

Lead Judging Commences

bube Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Predictable randomness

Support

FAQs

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