Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Severity: medium
Valid

Predictable Randomness from On-Chain Timestamp

Problem : Predictable Randomness from On-Chain Timestamp

Root + Impact

Description

The contract is designed to assign a random airdrop amount between 100 and 500 APT to a user upon registration. The issue is that the randomness is derived from timestamp::now_microseconds(), which is a predictable on-chain value, allowing for the outcome to be manipulated.

// sources/pizza_drop.move
#[randomness]
entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
@> let time = timestamp::now_microseconds();
@> let random_val = time % 401;
@> let random_amount = 100 + random_val;
// 100-500 APT (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Risk

Likelihood:

  • This will occur when a sophisticated user or validator monitors the blockchain's timestamp.

  • They can then precisely time their transaction to be included in a block with a timestamp that results in a maximum or high-value payout.

Impact:

  • Loss of Fairness: The core promise of a random and fair distribution is broken, as certain users can guarantee themselves a larger share of the airdrop pool.

  • Reputational Damage: The project's credibility is damaged when the distribution mechanism is proven to be gameable, leading to a loss of community trust.

Proof of Concept

An attacker can predict the outcome of the "random" number generation by reading the current block timestamp. By knowing the timestamp, they can calculate the random_amount before the transaction is even executed.

#[randomness]
entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
- let time = timestamp::now_microseconds();
- let random_val = time % 401;
- let random_amount = 100 + random_val;
+ // 1. Attacker reads the current `timestamp::now_microseconds()` value from a node.
+ // 2. Attacker calculates `(timestamp % 401) + 100` to predict the `random_amount`.
+ // 3. If the amount is high, the attacker proceeds to have the owner register them. If not, they wait for a better timestamp.
// 100-500 APT (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Recommended Mitigation

The mitigation requires replacing the insecure timestamp-based logic with a call to a secure on-chain randomness source, as intended by the #[randomness] attribute.

- let time = timestamp::now_microseconds();
- let random_val = time % 401;
- let random_amount = 100 + random_val;
+ // 1. Utilize the secure on-chain randomness feature provided by the Aptos framework.
+ // This is typically done by using a value injected by the VM when the #[randomness] attribute is present.
+ let random_bytes = aptos_framework::randomness::get_random_bytes();
+ // 2. Convert the random bytes to a u64 integer.
+ let random_u64: u64 = ...; // (implementation for bytes-to-int conversion)
+ // 3. Scale the number to the desired range [100, 500].
+ let random_amount = 100 + (random_u64 % 401);

Updates

Appeal created

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

Predictable randomness

The `get_random_slice` function should only be called by the owner via the `register_pizza_lover` function. Also, the `owner` is trusted and will not choose a specific time for a new user to register. Therefore, I disagree with the claim of most reports in this group that an attacker can manipulate the random number of pizza slices. But I agree with the root cause of the reports in this group, that the random distribution is not completely random.

Support

FAQs

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