Random Numbers
Generating secure random numbers in blockchain environments is challenging because blockchains are deterministic. NEAR Protocol provides a random seed mechanism, but understanding its properties and limitations is crucial for building secure applications that rely on randomness.
How NEAR Random Seed Works
NEAR provides a random seed that enables smart contracts to create random numbers and strings. This seed has unique properties:
Deterministic and Verifiable
The random seed is deterministic and verifiable:
- It comes from the validator that produced the block
- The validator signs the previous block-hash with their private key
- The signature becomes the random seed for the current block
- Anyone can verify the seed, but only the validator knows it in advance.
Security Properties
The way the random seed is created provides two important security properties:
1. Only Validator Can Predict
- Only the validator mining the transaction can predict which random number will be generated
- No one else can predict it because nobody knows the validator's private key (except the validator)
- This provides some security, but creates a centralization risk.
2. Validator Cannot Interfere
- The validator cannot interfere with the random number being created
- They must sign the previous block-hash, over which they (with high probability) had no control
- The previous block was likely produced by a different validator.
Important: While validators cannot directly manipulate the seed, they can still exploit it through other means.
Attack Type 1: Gaming the Input
The Vulnerability
If your contract asks users to provide an input and rewards them if it matches the random seed, validators can exploit this:
Example:
- Contract asks user to choose a number between 1-100
- If user's number matches the random seed, they win a prize
- Validator knows the random seed before the block is mined
- Validator creates a transaction with the winning number
- Validator includes their transaction in the block
- Validator wins the prize
// ❌ VULNERABILITY: Gaming the input - validator can predict and win
// User provides input and random seed is generated in same block
pub fn guess_number(&mut self, guess: u8) {
let account_id = env::signer_account_id();
// Store user's guess
self.bets.insert(account_id.clone(), guess.to_string());
// Generate random number in SAME block - validator knows it!
let random_seed = env::random_seed();
let random_number = (random_seed[0] % 100) as u8;
// Validator can see user's guess and create winning transaction
if guess == random_number {
let reward = NearToken::from_near(1);
let previous_user_reward = self.rewards.get(&account_id).unwrap_or(&NearToken::ZERO);
let user_reward = previous_user_reward.saturating_add(reward);
self.rewards.insert(account_id, user_reward);
}
}
Why This Works
Since the validator knows which random seed will be generated in their block, they can:
- Calculate the winning input
- Create a transaction with that input
- Include their transaction in the block
- Win every time
Result: Validators can guarantee wins in single-block games.
Attack Type 2: Refusing to Mine the Block
The Two-Stage Solution
One way to fix "gaming the input" is to use a two-stage process:
- Bet Stage: User sends their input/choice (e.g., "heads" or "tails")
- Resolve Stage: Contract generates random number and determines winner (in a later block)
This prevents validators from gaming the input because:
- User's choice is locked in the first block
- Random seed is generated in a different (later) block
- Validator cannot know the random seed when user makes their choice
The Remaining Vulnerability
However, validators can still exploit this through selective block mining:
Attack Process:
- Validator creates a "bet" transaction with their account
- Validator chooses either input (doesn't matter which)
- When it's the validator's turn to validate:
- They check what random seed will be generated
- If their bet would win, they include the "resolve" transaction in the block
- If their bet would lose, they skip the "resolve" transaction
- Other validators might mine it, but validator increases their win rate
Result: Validator improves their probability of winning, though doesn't guarantee it.
Coin Flip Example: Probability Manipulation
Fair Coin Flip (Without Attack)
In a fair coin-flip game:
- You choose "heads" or "tails" in the bet stage
- Random seed determines outcome in resolve stage
- Probability of winning: 50% (1/2)
With Refusing to Mine Attack
If you're a validator and can refuse to mine losing blocks:
Scenarios:
- You bet "heads"
- If random seed = "heads": You mine the block and win ✅
- If random seed = "tails": You skip the block (other validator might mine it)
- If other validator mines: You lose ❌
- If no one mines: Transaction delayed, you try again later
Mathematical Analysis:
If you always bet "heads" and can choose to flip again when "tails" comes out:
Possible outcomes (H = heads, T = tails):
- H H: Win ✅
- T H: Win ✅ (retry after T)
- H T: Win ✅ (retry after H)
- T T: Lose ❌ (retry after T, then T again)
Result: You win in 3 out of 4 scenarios = 75% win rate (3/4)
Improvement: From 50% to 75% = 25% increase in win probability
Note: These odds dilute in games with more possible outcomes (e.g., dice with 6 sides).
Best Practices for Randomness
1. Use Multi-Block Randomness
- Separate bet and resolve into different blocks
- Prevents input gaming attacks
- Still vulnerable to selective mining, but reduces risk.
2. Use Commit-Reveal Schemes
- Users commit to their choice (hash of choice + secret)
- Later reveal the choice and secret
- Random seed generated after reveal
- Prevents both input gaming and selective mining.
3. Use External Oracles
- Use trusted external randomness sources
- Chainlink VRF or similar services
- More secure but requires external dependencies.
4. Accept Validator Advantage
- For non-critical randomness, accept that validators have slight advantage
- Use for games where small advantage is acceptable
- Not suitable for high-stakes applications.
5. Use Multiple Validators
- Distribute randomness across multiple validators
- Reduces single validator control
- More decentralized approach.