// rap_battle.move
public fun resolve_battle(defender: address, challenger: address, ...) {
let ts = timestamp::now_seconds(); // @> Predictable RNG source
let rand = (ts + defender_stat + challenger_stat) % 100; // @> Directly used to pick winner
if (rand Single authority is held by @battle_addr with no multisig or limits
public fun mint_cred(signer: &signer, amount: u64) {
// Only signer with capability (module owner) can mint
// @> Allows arbitrary inflation of CRED
}
// streets.move
resource struct Custody { owner: address, object_id: ObjectId }
// @> NFTs transferred into module custody, no safe withdrawal path
public fun stake_rapper(rapper: ObjectId, owner: address) {
// @> Object moved into module account, requires module signer for release
}
Normal behavior
Players mint Rapper NFTs, stake them in streets to earn CRED, then enter rap_battle where two players wager equal CRED and the protocol computes an on-chain battle winner. Winners receive the pooled CRED and the Rapper accrues a win.
Observed issue
The battle outcome entropy is derived directly from timestamp::now_seconds() (or another predictable on-chain timestamp) and the module owner (@battle_addr) centrally mints/burns CRED and custodies NFTs. An attacker who times transactions (or colludes with/compromises the module owner) can bias outcomes and drain prize pools or mint/transfer CRED and NFTs without owner consent.
Likelihood:
When an attacker times a challenger transaction to match a second that yields a favorable rand result (deterministic outcome).
When an attacker compromises or colludes with the module owner (or module owner acts maliciously), enabling arbitrary minting of CRED or withholding NFTs.
Impacts:
Direct loss of staked/wagered CRED for players.
Arbitrary inflation of CRED supply (value destruction / exploitable trading).
Permanent loss/lock of Rapper NFTs if module signer lost or malicious.
Reputation / marketplace damage.
Deploy the repository locally to an Aptos devnet (Aptos CLI 7.7.0, same modules)
Create accounts: battle_addr (module owner), alice (defender), bob (challenger).
battle_addr mints two Rapper NFTs to alice and bob.
Repeat the following loop while logging the timestamp::now_seconds() observed in transactions:
In second S: alice calls defend(bet, rapper_id_alice)
Immediately in same second S: bob calls challenge(bet, rapper_id_bob)
Record winner. Repeat at S+1, S+2..
5.If winner correlates deterministically with ts (same ts -> same winner), RNG is predictable.
Minimal PoC unit-test idea (attach as tests/predictable_rng_test.move): call battle resolution repeatedly while stubbing or observing timestamp::now_seconds() and assert outcomes are correlated to timestamp.
Replace timestamp::now_seconds() with a verifiable randomness source (VRF, oracle, or commit–reveal).
Use block metadata + hashing or player commitments to generate unpredictable entropy.
Remove direct dependency on timestamps, which are predictable and miner-influenced.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.