Module iota::random
This module provides functionality for generating secure randomness.
use iota::address;
use iota::dynamic_field;
use iota::hex;
use iota::hmac;
use iota::object;
use iota::transfer;
use iota::tx_context;
use iota::versioned;
use std::ascii;
use std::bcs;
use std::option;
use std::string;
use std::vector;
Module Functions
prv create
Create and share the Random object. This function is called exactly once, when the Random object is first created. Can only be called by genesis or change_epoch transactions.
fun create(ctx: &mut iota::tx_context::TxContext)
Implementation
fun create(ctx: &mut TxContext) {
assert!(ctx.sender() == @0x0, ENotSystemAddress);
let version = CURRENT_VERSION;
let inner = RandomInner {
version,
epoch: ctx.epoch(),
randomness_round: 0,
random_bytes: vector[],
};
let self = Random {
id: object::randomness_state(),
inner: versioned::create(version, inner, ctx),
};
transfer::share_object(self);
}
Structs
struct Random
Singleton shared object which stores the global randomness state. The actual state is stored in a versioned inner field.
public struct Random has key
Fields
id: iota::object::UIDinner: iota::versioned::Versioned
pub new_generator
Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes.
Using randomness can be error-prone if you don't observe the subtleties in its correct use, for example, randomness dependent code might be exploitable to attacks that carefully set the gas budget in a way that breaks security. For more information, see: https://docs.iota.org/developer/advanced/onchain-randomness
public fun new_generator(r: &iota::random::Random, ctx: &mut iota::tx_context::TxContext): iota::random::RandomGenerator
Implementation
public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator {
let inner = r.load_inner();
let seed = hmac_sha3_256(
&inner.random_bytes,
&ctx.fresh_object_address().to_bytes(),
);
RandomGenerator { seed, counter: 0, buffer: vector[] }
}
prv load_inner
fun load_inner(self: &iota::random::Random): &iota::random::RandomInner
Implementation
fun load_inner(self: &Random): &RandomInner {
let version = self.inner.version();
// Replace this with a lazy update function when we add a new version of the inner object.
assert!(version == CURRENT_VERSION, EWrongInnerVersion);
let inner: &RandomInner = self.inner.load_value();
assert!(inner.version == version, EWrongInnerVersion);
inner
}
prv load_inner_mut
fun load_inner_mut(self: &mut iota::random::Random): &mut iota::random::RandomInner
Implementation
fun load_inner_mut(self: &mut Random): &mut RandomInner {
let version = versioned::version(&self.inner);
// Replace this with a lazy update function when we add a new version of the inner object.
assert!(version == CURRENT_VERSION, EWrongInnerVersion);
let inner: &mut RandomInner = self.inner.load_value_mut();
assert!(inner.version == version, EWrongInnerVersion);
inner
}
prv update_randomness_state
Record new randomness. Called when executing the RandomnessStateUpdate system transaction.
fun update_randomness_state(self: &mut iota::random::Random, new_round: u64, new_bytes: vector<u8>, ctx: &iota::tx_context::TxContext)
Implementation
fun update_randomness_state(
self: &mut Random,
new_round: u64,
new_bytes: vector<u8>,
ctx: &TxContext,
) {
// Validator will make a special system call with sender set as 0x0.
assert!(ctx.sender() == @0x0, ENotSystemAddress);
// Randomness should only be incremented.
let epoch = ctx.epoch();
let inner = self.load_inner_mut();
if (inner.randomness_round == 0 && inner.epoch == 0 && inner.random_bytes.is_empty()) {
// First update should be for round zero.
assert!(new_round == 0, EInvalidRandomnessUpdate);
} else {
// Subsequent updates should either increase epoch or increment randomness_round.
// Note that epoch may increase by more than 1 if an epoch is completed without
// randomness ever being generated in that epoch.
assert!(
(epoch > inner.epoch && new_round == 0) ||
(new_round == inner.randomness_round + 1),
EInvalidRandomnessUpdate,
);
};
inner.epoch = ctx.epoch();
inner.randomness_round = new_round;
inner.random_bytes = new_bytes;
}
struct RandomInner
public struct RandomInner has store
Fields
version: u64epoch: u64randomness_round: u64random_bytes: vector<u8>
struct RandomGenerator
Unique randomness generator, derived from the global randomness.
public struct RandomGenerator has drop
Fields
seed: vector<u8>counter: u16buffer: vector<u8>
pub generate_bool
Generate a boolean.
public fun generate_bool(g: &mut iota::random::RandomGenerator): bool
Implementation
public fun generate_bool(g: &mut RandomGenerator): bool {
(uint_from_bytes!(g, 1) & 1) == 1
}
pub generate_bytes
Generate n random bytes.
public fun generate_bytes(g: &mut iota::random::RandomGenerator, num_of_bytes: u16): vector<u8>
Implementation
public fun generate_bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector<u8> {
let mut result = vector[];
// Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer.
let num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN;
num_of_blocks.do!(|| result.append(g.derive_next_block()));
// Fill the generator's buffer if needed.
let num_of_bytes = num_of_bytes as u64;
let remaining = num_of_bytes - result.length();
if (g.buffer.length() < remaining) {
let next_block = g.derive_next_block();
g.buffer.append(next_block);
};
// Take remaining bytes from the generator's buffer.
remaining.do!(|| result.push_back(g.buffer.pop_back()));
result
}
pub generate_u128
Generate a u128.
public fun generate_u128(g: &mut iota::random::RandomGenerator): u128
Implementation
public fun generate_u128(g: &mut RandomGenerator): u128 {
uint_from_bytes!(g, 16)
}
pub generate_u128_in_range
Generate a random u128 in [min, max] (with a bias of 2^-64).
public fun generate_u128_in_range(g: &mut iota::random::RandomGenerator, min: u128, max: u128): u128
Implementation
public fun generate_u128_in_range(g: &mut RandomGenerator, min: u128, max: u128): u128 {
uint_in_range!(g, min, max, 24)
}
pub generate_u16
Generate a u16.
public fun generate_u16(g: &mut iota::random::RandomGenerator): u16
Implementation
public fun generate_u16(g: &mut RandomGenerator): u16 {
uint_from_bytes!(g, 2)
}
pub generate_u16_in_range
Generate a random u16 in [min, max] (with a bias of 2^-64).
public fun generate_u16_in_range(g: &mut iota::random::RandomGenerator, min: u16, max: u16): u16
Implementation
public fun generate_u16_in_range(g: &mut RandomGenerator, min: u16, max: u16): u16 {
uint_in_range!(g, min, max, 10)
}
pub generate_u256
Generate a u256.
public fun generate_u256(g: &mut iota::random::RandomGenerator): u256
Implementation
public fun generate_u256(g: &mut RandomGenerator): u256 {
uint_from_bytes!(g, 32)
}
pub generate_u32
Generate a u32.
public fun generate_u32(g: &mut iota::random::RandomGenerator): u32
Implementation
public fun generate_u32(g: &mut RandomGenerator): u32 {
uint_from_bytes!(g, 4)
}
pub generate_u32_in_range
Generate a random u32 in [min, max] (with a bias of 2^-64).
public fun generate_u32_in_range(g: &mut iota::random::RandomGenerator, min: u32, max: u32): u32
Implementation
public fun generate_u32_in_range(g: &mut RandomGenerator, min: u32, max: u32): u32 {
uint_in_range!(g, min, max, 12)
}
pub generate_u64
Generate a u64.
public fun generate_u64(g: &mut iota::random::RandomGenerator): u64
Implementation
public fun generate_u64(g: &mut RandomGenerator): u64 {
uint_from_bytes!(g, 8)
}
pub generate_u64_in_range
public fun generate_u64_in_range(g: &mut iota::random::RandomGenerator, min: u64, max: u64): u64
Implementation
public fun generate_u64_in_range(g: &mut RandomGenerator, min: u64, max: u64): u64 {
uint_in_range!(g, min, max, 16)
}
pub generate_u8
Generate a u8.
public fun generate_u8(g: &mut iota::random::RandomGenerator): u8
Implementation
public fun generate_u8(g: &mut RandomGenerator): u8 {
uint_from_bytes!(g, 1)
}
pub generate_u8_in_range
Generate a random u8 in [min, max] (with a bias of 2^-64).
public fun generate_u8_in_range(g: &mut iota::random::RandomGenerator, min: u8, max: u8): u8
Implementation
public fun generate_u8_in_range(g: &mut RandomGenerator, min: u8, max: u8): u8 {
uint_in_range!(g, min, max, 9)
}
pub shuffle
Shuffle a vector using the random generator (Fisher–Yates/Knuth shuffle).
public fun shuffle<T>(g: &mut iota::random::RandomGenerator, v: &mut vector<T>)
Implementation
public fun shuffle<T>(g: &mut RandomGenerator, v: &mut vector<T>) {
let n = v.length();
if (n == 0) return;
assert!(n <= U16_MAX, EInvalidLength);
let n = n as u16;
let end = n - 1;
end.do!(|i| {
let j = g.generate_u16_in_range(i, end);
v.swap(i as u64, j as u64);
});
}
prv derive_next_block
Get the next block of 32 random bytes.
fun derive_next_block(g: &mut iota::random::RandomGenerator): vector<u8>
Implementation
fun derive_next_block(g: &mut RandomGenerator): vector<u8> {
g.counter = g.counter + 1;
hmac_sha3_256(&g.seed, &bcs::to_bytes(&g.counter))
}
prv uint_from_bytes (macro)
macro fun uint_from_bytes<$T: drop>($g: &mut iota::random::RandomGenerator, $num_of_bytes: u8): $T
Implementation
macro fun uint_from_bytes<$T: drop>($g: &mut RandomGenerator, $num_of_bytes: u8): $T {
let g = $g;
let num_of_bytes = $num_of_bytes;
if (g.buffer.length() < num_of_bytes as u64) {
let next_block = g.derive_next_block();
g.buffer.append(next_block);
};
// TODO: why regression test fails if we use $T instead of u256
let mut result: u256 = 0;
num_of_bytes.do!(|_| {
let byte = g.buffer.pop_back() as u256;
result = (result << 8) + byte;
});
result as $T
}
prv uint_in_range (macro)
Helper macro to generate a random uint in [min, max] using a random number with num_of_bytes bytes. Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias (e.g., 8 bytes larger than the actual type used by the caller function to limit the bias by 2^-64).
macro fun uint_in_range<$T: drop>($g: &mut iota::random::RandomGenerator, $min: $T, $max: $T, $num_of_bytes: u8): $T
Implementation
macro fun uint_in_range<$T: drop>(
$g: &mut RandomGenerator,
$min: $T,
$max: $T,
$num_of_bytes: u8,
): $T {
let min = $min;
let max = $max;
assert!(min <= max, EInvalidRange);
if (min == max) return min;
// Pick a random number in [0, max - min] by generating a random number that is larger than max-min, and taking
// the modulo of the random number by the range size. Then add the min to the result to get a number in
// [min, max].
let range_size = (max - min) as u256 + 1;
let rand = uint_from_bytes!($g, $num_of_bytes);
min + (rand % range_size as $T)
}
Constants
err ENotSystemAddress
const ENotSystemAddress: u64 = 0;
err EWrongInnerVersion
const EWrongInnerVersion: u64 = 1;
err EInvalidRandomnessUpdate
const EInvalidRandomnessUpdate: u64 = 2;
err EInvalidRange
const EInvalidRange: u64 = 3;
err EInvalidLength
const EInvalidLength: u64 = 4;
const CURRENT_VERSION
const CURRENT_VERSION: u64 = 1;
const RAND_OUTPUT_LEN
const RAND_OUTPUT_LEN: u16 = 32;
const U16_MAX
const U16_MAX: u64 = 65535;