Fair & Square RNG
At Alpha Dune Network, we are committed to providing a transparent, trustworthy, and fair experience for all participants. Our Fair & Square RNG (Random Number Generator) system is at the heart of this commitment, ensuring every result is random, verifiable, and auditable.
Currently used in Loot Dynasty—where players open epic boxes, score loot, and can cash out or claim physical goods—this custom-built RNG ensures that every outcome is as fair and reliable as possible.
What Makes Our RNG Fair & Square?
Our RNG is designed to provide the highest level of fairness and transparency, ensuring that all results are both random and verifiable. Here’s how it works:
Transparent and Auditable: Every outcome generated by the RNG is public and auditable, meaning that anyone can verify the fairness of each result.
On-Chain Integrity: The randomness is derived on-chain, ensuring that results cannot be tampered with and are secured by the blockchain’s inherent features.
Immutable Records: Every result is recorded and can be cross-checked by anyone, reinforcing trust in the fairness of the system.
Beyond Loot Dynasty: Expanding RNG Across Alpha Dune Network
While the Fair & Square RNG is currently being used in Loot Dynasty, we are expanding its use to support other campaigns across the Alpha Dune Network. Whether it’s sweepstakes, raffles, or random distributions, our RNG guarantees that every participant has an equal, transparent, and fair chance to win.
The Core of Openness and Transparency
Our RNG system reflects our commitment to creating a network that prioritizes openness and trust. The ability for participants to independently verify outcomes not only reinforces fairness but also encourages a more engaged and confident community.
As we continue to grow, our RNG will remain central to how we maintain a balanced and transparent experience across all applications on Alpha Dune. Whether you’re claiming rewards, participating in campaigns, or unlocking loot, you can be confident that everything is done Fair & Square.
How it Works: Deep Technical Dive
The core concept of this RNG lies in the concatenation of two key sources of randomness:
The Server Seed: The "server seed" is provided by the next block hash from multiple blockchain networks. This ensures that the result is tied to a public, immutable source.
The User Seed: Each player has a unique, secret user seed that ensures that their interactions with the RNG are personalized and not entirely predictable by others.
The combination of these two seeds guarantees that the random outcomes are cryptographically secure, tamper-proof, and truly unpredictable. This setup enhances both fairness and transparency, which is crucial for gaming and asset distribution mechanisms like loot boxes, raffles, and sweepstakes.
Step-by-Step Overview of the RNG Process
The main goal of the RNG mechanism in Loot Dynasty is to generate fair and unpredictable results when users open loot packs. Let's break down how the system works in detail:
1. Seed Generation:
The
getRandomness
function is responsible for generating the actual randomness. It takes two inputs:userKey
: A unique string associated with the user. This could be derived from user-specific data or account settings.serverKey
: The next block hash of the blockchain. This ensures that the randomness is linked to the blockchain’s immutable state, making the outcome verifiable and auditable.
The function concatenates these two strings and generates a hash using
keccak256
, Ethereum's standard cryptographic hash function. This creates a random number tied to both the user and the blockchain state.
function getRandomness(
string memory userKey,
string calldata serverKey
)
public
pure
returns (uint256)
{
return uint256(keccak256(abi.encodePacked(serverKey, userKey)));
}
This randomness value will be used in subsequent steps to determine what item the user wins from the loot pack.
2. Loot Pack Definition:
Loot packs consist of various items, each with a specific probability of being won. The
Pack
struct defines each loot pack, including:ids
: List of item IDs that could be won.chances
: List of cumulative probabilities that correspond to each item.prices
: Price of the loot pack in USD cents.
The loot pack configuration allows for detailed control over the probability of winning specific items, ensuring that the loot box experience is dynamic and engaging.
3. Item Selection:
Once the randomness is generated using the
getRandomness
function, it is used to determine which item the user will win. This process is carried out by thegetItemWon
function, which maps the generated randomness value to the item list using the probabilities defined in the loot pack.The function iterates through the
chances
array (which holds the cumulative chances) and selects the item whose probability range the randomness falls into.
function getItemWon(
Pack memory pack,
uint256 chanceValue
)
public
pure
returns (uint256)
{
for (uint256 j; j < pack.chances.length - 1; ++j) {
if (chanceValue > pack.chances[j] && chanceValue <= pack.chances[j+1]) {
return pack.ids[j];
}
}
revert NoWonItemFound();
}
4. Opening Creation:
When an item is won, the
batchValidateOpens
orbatchValidateVirtualOpens
functions are used to process the result. These functions receive multiple user IDs, pack IDs, and block hashes (for server seeds).The system ensures that only authorized validators can trigger these operations (to prevent unauthorized access), which maintains the integrity of the system.
The result is logged with an
Opening
event, containing detailed information about the pack, the item won, the price, and the randomness involved.
This transparency ensures that every user can audit the result, as each "opening" is tied to both the blockchain data (via the block hash) and the user-specific seed (via the user key).
5. Virtual vs. Real Openings:
The system distinguishes between virtual openings and real ones. Virtual openings are typically used for virtual currency or free play rewards, while real openings are used for cash play with real currency.
This distinction is indicated by the
isVirtual
flag in theOpening
struct, which helps differentiate between different types of loot interactions.
6. Validator Mechanism:
Validators play a critical role in the system by verifying the accuracy and fairness of each opening. Only authorized addresses (set by the contract owner) can validate transactions, ensuring that the system is resistant to manipulation.
The
setWhitelist
function allows the owner to set and update the list of authorized validators.
Why Next Block Hash?
The next block hash plays a crucial role in making our RNG system secure, fair, and transparent. But you might be wondering: Why use the next block hash? The answer lies in the nature of blockchains and how they ensure immutability and security.
In simple terms, the block hash is a unique, cryptographic identifier for a block of transactions in a blockchain. Each block is linked to the previous one in a chain, creating an unchangeable record. The next block hash is even more special because it is determined by the contents of the current block, and no one can predict or alter it in advance.
Here's why using the next block hash is important:
Unpredictability: The next block hash is impossible to predict ahead of time. It depends on the data inside the current block, which is not available until the block is mined. This makes it an excellent source of randomness.
Security: Since each block in the blockchain is linked to the previous one, tampering with the blockchain is incredibly difficult. In fact, to alter a block, you would need to change not just that block, but every block after it. This would require controlling 51% of the network's mining power — which is essentially impossible on large, decentralized blockchains.
Immutability: Once a block is added to the blockchain, it cannot be changed or removed. So, when we use the next block hash in our RNG system, we can be sure that it has already been recorded on the blockchain and is secure. This makes the randomness truly auditable by anyone who has access to the blockchain, ensuring that the results are fair and transparent.
Why is this important for Random Number Generation?
In our system, the next block hash provides a cryptographic guarantee that the randomness is fair, transparent, and tamper-proof. Since the block hash can't be predicted or manipulated unless you control the majority of the network (something nearly impossible), it ensures that the random outcomes are trustworthy and not influenced by any malicious actors.
To summarize: By using the next block hash, we leverage the security and unpredictability of the blockchain to generate a random number that is extremely difficult to manipulate. This helps create a level of trust that users can rely on when interacting with loot boxes, raffles, and other random-based features in the Alpha Dune ecosystem.
Key Features of the RNG System for Loot Dynasty
Fairness:
The combination of blockchain data (block hashes) and user-specific data ensures that the RNG process is fair, transparent, and independent of any centralized authority.
Transparency:
All users can audit the results by reviewing the public blockchain data (block hashes) used in the RNG. This means the results are not only generated fairly but also verifiable by anyone.
Decentralized Validators:
The validator mechanism ensures that no single entity controls the RNG system. Validators are trusted parties who help validate the fairness of the system's results.
Customizable Loot Packs:
Game developers and app creators can easily define loot packs with custom probabilities, ensuring that the system is adaptable to various applications and user needs.
Scalability:
The system is built to handle batch validations of multiple users and loot packs simultaneously, ensuring that it can scale effectively with increasing demand.
Security:
The use of
keccak256
ensures that the random number generation is cryptographically secure. Additionally, the use of Ethereum's next block hash prevents any manipulation of the randomness.
Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./Ownable.sol";
error Forbidden();
error NoWonItemFound();
contract LootKingdom is Ownable {
event UsersKeysUpdated(uint256[] userIds, string[] updatedKeys);
event NewOpening(uint256 indexed id, Opening opening);
event NewVirtualOpening(uint256 indexed id, Opening opening);
struct Pack {
string name;
uint256[] ids;
uint256[] chances;
uint256[] prices; // in USD cents
uint256 price; // in USD cents
}
struct Opening {
uint256 userId;
uint256 packId;
uint256 randomness;
uint256[] ids;
uint256[] chances;
uint256[] prices; // in USD cents
uint256 price; // in USD cents
uint256 itemWon;
string key;
string hash;
bool isVirtual;
}
uint256 public id;
mapping(uint256 => Pack) public packs;
mapping(address => bool) public validators;
mapping(uint256 => string) public userIdToKey;
mapping(uint256 => Opening) public openings;
address public houseAddress;
constructor(
address _houseAddress
)
Ownable(msg.sender)
{
houseAddress = _houseAddress;
}
function setPack(
uint256 packId,
Pack calldata pack
)
external
onlyOwner
{
packs[packId] = pack;
}
function getPackArrays(
uint256 packId
)
external
view
returns(
uint256[] memory,
uint256[] memory,
uint256[] memory
)
{
return (
packs[packId].ids,
packs[packId].chances,
packs[packId].prices
);
}
function getOpeningArrays(
uint256 openingId
)
external
view
returns(
uint256[] memory,
uint256[] memory,
uint256[] memory
)
{
return (
openings[openingId].ids,
openings[openingId].chances,
openings[openingId].prices
);
}
function setWhitelist(
address[] calldata proposedValidators
)
external
onlyOwner
{
for (uint i; i < proposedValidators.length; ++i) {
validators[proposedValidators[i]] = !validators[proposedValidators[i]];
}
}
function setUserKeys(
uint256[] calldata userIds,
string[] calldata updatedKeys
)
external
{
if (!validators[msg.sender]) {
revert Forbidden();
}
for (uint256 i; i < userIds.length; ++i) {
userIdToKey[userIds[i]] = updatedKeys[i];
}
emit UsersKeysUpdated(userIds, updatedKeys);
}
function getRandomness(
string memory userKey,
string calldata serverKey
)
public
pure
returns (uint256)
{
return uint256(keccak256(abi.encodePacked(serverKey, userKey)));
}
function getItemWon(
Pack memory pack,
uint256 chanceValue
)
public
pure
returns (uint256)
{
for (uint256 j; j < pack.chances.length - 1; ++j) {
if (chanceValue > pack.chances[j] && chanceValue <= pack.chances[j+1]) {
return pack.ids[j];
}
}
revert NoWonItemFound();
}
function batchValidateVirtualOpens(
uint256[] calldata userIds,
uint256[] calldata packIds,
string[] calldata blocksHash
)
external
{
if (!validators[msg.sender]) {
revert Forbidden();
}
for (uint256 i; i < packIds.length; ++i) {
uint256 rand = getRandomness(userIdToKey[userIds[i]], blocksHash[i]);
Pack memory pack = packs[packIds[i]];
uint256 chanceValue = rand % pack.chances[pack.chances.length-1];
uint256 itemWon = getItemWon(pack, chanceValue);
_handleOpeningCreation(pack, packIds[i], userIds[i], rand, itemWon, blocksHash[i], true);
}
}
function batchValidateOpens(
uint256[] calldata userIds,
uint256[] calldata packIds,
string[] calldata blocksHash
)
external
{
if (!validators[msg.sender]) {
revert Forbidden();
}
for (uint256 i; i < packIds.length; ++i) {
uint256 rand = getRandomness(userIdToKey[userIds[i]], blocksHash[i]);
Pack memory pack = packs[packIds[i]];
uint256 chanceValue = rand % pack.chances[pack.chances.length-1];
uint256 itemWon = getItemWon(pack, chanceValue);
_handleOpeningCreation(pack, packIds[i], userIds[i], rand, itemWon, blocksHash[i], false);
}
}
function _handleOpeningCreation(
Pack memory pack,
uint256 packId,
uint256 userId,
uint256 rand,
uint256 itemWon,
string calldata blockHash, // next block hash
bool isVirtual
) private {
openings[id].ids = pack.ids;
openings[id].packId = packId;
openings[id].chances = pack.chances;
openings[id].prices = pack.prices;
openings[id].userId = userId;
openings[id].randomness = rand;
openings[id].price = pack.price;
openings[id].itemWon = itemWon;
openings[id].hash = blockHash;
openings[id].key = userIdToKey[userId];
openings[id].isVirtual = isVirtual;
if (isVirtual) {
emit NewVirtualOpening(id, openings[id++]);
} else {
emit NewOpening(id, openings[id++]);
}
}
}
Last updated