Skip to main content

How to Generate Pseudo-Random Numbers in Ink! Smart Contract

Generating random numbers is an essential feature of many decentralized applications, but generating truly random numbers in a trustless, decentralized environment is a challenging problem. In this article, we will show you how to implement a simple pseudo-random function in your Ink! smart contract and generate pseudo-random numbers within a specified range.

Implementation

First, create a new Ink! smart contract and modify the PseudoRandom struct to include the salt variable. The salt will be incremented by 1 each time the get_pseudo_random function is called.

#[ink(storage)]
pub struct PseudoRandom {
salt: u64,
}

Then, update the get_pseudo_random function to take an input parameter for the maximum value in the range, and to return a number between 0 and the maximum value in the range using the following code:

use ink::env::hash;

#[ink(message)]
pub fn get_pseudo_random(&mut self, max_value: u8) -> u8 {
let seed = self.env().block_timestamp();
let mut input: Vec<u8> = Vec::new();
input.extend_from_slice(&seed.to_be_bytes());
input.extend_from_slice(&self.salt.to_be_bytes());
let mut output = <hash::Keccak256 as hash::HashOutput>::Type::default();
ink::env::hash_bytes::<hash::Keccak256>(&input, &mut output);
self.salt += 1;
let number = output[0] % (max_value + 1);
number
}

This function generates a hash value that is based on the block timestamp and the incremented salt value. The max_value parameter is used to determine the maximum value in the range. The modulo operator % (max_value + 1) is then used to return a number between 0 and the maximum value in the range.

Usage

To generate a pseudo-random number within a specified range, simply call the get_pseudo_random function with the maximum value in the range as the input parameter. For example, to generate a number between 0 and 99, you would call the function with a max_value of 99:

let mut my_contract = PseudoRandom::new();
let random_number = my_contract.get_pseudo_random(99);

Example Unit Test

To ensure that the get_pseudo_random function works as expected, you can write a unit test that calls the function with different max_value parameters and checks that the generated random numbers are within the expected range. Here's an example unit test that you can add to your Ink! smart contract:

#[test]
fn test_get_pseudo_random() {
let mut contract = PseudoRandom::new();
for max_value in 1..=100 {
let result = contract.get_pseudo_random(max_value);
assert!(result <= max_value);
}
}

Conclusion

By implementing a pseudo-random function in your Ink! smart contract, you can generate pseudo-random numbers within a specified range in a decentralized and trustless environment. However, it is important to note that the get_pseudo_random function does not provide the same level of security and trust as a true verifiable random function (VRF).

While the function uses the block timestamp and a salt value to generate a hash value, which is then used to generate a pseudo-random number, it may still be possible for an attacker to predict the output of the function. Additionally, this implementation may not be suitable for applications that require high levels of security, such as gambling or financial applications.

If you require a truly verifiable and secure random function for your smart contract, you may want to consider using an external oracle solution or a specialized random number generator that is specifically designed for use in smart contracts.