Hello everyone,
Today I would like to propose a LIP for the roadmap objective “Define state model and state root”. This LIP specifies the random module.
Looking forward to your feedback!
Here is the LIP draft:
LIP: <LIP number>
Title: Define state and state transitions of Random module
Author: Iker Alustiza <iker@lightcurve.io>
Ishan Tiwari <ishan.tiwari@lightcurve.io>
Type: Standards Track
Created: <YYYY-MM-DD>
Updated: <YYYY-MM-DD>
Requires: LIP 0022, LIP 0040
Abstract
The random module handles the validation of the inputs and computation of outputs for the commit and reveal process for a Lisk blockchain. In this LIP, we specify the state transitions logic defined within this module, i.e., the protocol logic injected during the block lifecycle, and the functions that can be called from other modules or off-chain services
Copyright
This LIP is licensed under the Creative Commons Zero 1.0 Universal.
Motivation
The random module handles the validation of the inputs for the commit and reveal process introduced in LIP 0022 as well as the computation of the random seeds from that process. In this LIP we specify the properties, serialization, and default values of the random module, as well as the protocol logic processed during a block lifecycle, and the functions exposed to other modules and to off-chain services
Rationale
Random Module Store
The random module defines a random substore whose value contains the validator reveals array.
This array contains the necessary information to:
- validate the seeds revealed by the validators as part of the commit and reveal process, and
- compute the random seed to be used as source of randomness to re-order the generator list for a new round (both in PoA and DPoS chains) and to select the standby delegates (in certain DPoS chains as well as the Lisk mainchain).
This array has a bounded length that is given as part of the configuration of the random module. On one hand, when a new block is executed, a new element is added to this array. On the other hand, the old revealed seeds are in most of the cases deleted from the array since they are not needed anymore.
It is worth noting that random seed computation from this commit and reveal process can be used for other applications that need a source of randomness. However, this random seed has certain limitations that have to be taken into account as explained in LIP 0022. Certain applications may require a different source of randomness to be secure.
Specification
In this section, we specify the substores that are part of the random module store, and the protocol logic called during the block lifecycle.
The random module has module ID MODULE_ID_RANDOM
(see the table below). It is a module with no dependencies.
Constants
Name | Type | Value | Description |
---|---|---|---|
MODULE_ID_RANDOM |
uint32 | TBD | ID of the random module |
STORE_PREFIX_RANDOM |
bytes | 0x0000 |
Prefix of the random substore |
MAX_LENGTH_REVEALS_MAINCHAIN |
uint32 | 206 | Maximum length of the validatorReveals array for the Lisk Mainchain |
Random Module Store
The key-value pairs in the module store are organized in the following substore.
Random Substore
Store Prefix, Store Key, and Store Value
The entry in the random substore is defined as follows:
- The store prefix is set to
STORE_PREFIX_RANDOM
. - The store key is set to empty bytes.
- The store value is the serialization of an object following the JSON schema
seedRevealSchema
presented below.
Seed Reveal Schema
seedRevealSchema = {
type: "object",
properties: {
"validatorReveals": {
type: "array",
"fieldNumber": 1,
items: {
"type": "object",
properties: {
"generatorAddress": {
"dataType": "bytes",
"fieldNumber": 1
},
"seedReveal": {
"dataType": "bytes",
"fieldNumber": 2
},
"height": {
"dataType": "uint32",
"fieldNumber": 3
},
"valid": {
"dataType": "boolean",
"fieldNumber": 4
}
},
"required": [
"generatorAddress",
"seedReveal",
"height",
"valid"]
}
},
"required": ["validatorReveals"]
}
}
Properties
In this section, we describe the properties in the validatorReveals
array of the seed reveal object:
-
generatorAddress
: The address of the generator of the block. A valid generator address is 20 bytes long. -
seedReveal
: The value revealed by the generator of the block for the commit and reveal process. A valid seed reveal is 16 bytes long. -
height
: The height of the block where the generator added their revealed seed. -
valid
: The flag stating the validity ofseedReveal
for the random seed computation.
The validatorReveals
array is kept ordered by increasing value of height.
Internal Functions
The random module has the following internal functions.
Hashing Function
A new hashing function, H
, is defined for this module.
Parameters
-
input
: A bytes value of arbitrary length.
Returns
The function returns a 16-bytes value, digest
.
Execution
H(input):
t = SHA-256(input)
digest = trunc(t)
return digest
where the function trunc
truncates its input to the 16 most significant bytes.
isSeedValidInput
This function assesses the validity of the revealed seed as input for the random seed computation.
Parameters
-
generatorAddress
: A 20-bytes value with the address of the generator of a certain block. -
seedReveal
: A 16-bytes value with the seed revealed by the generator of a certain block.
Returns
This function returns true
if seedReveal
is valid input for the random seed computation, otherwise, false
.
Execution
isSeedValidInput(generatorAddress, seedReveal):
let seedObject be an element in validatorReveals array
let lastSeed be the seedObject with the largest seedObject.height and seedObject.generatorAddress == generatorAddress
if not lastSeed:
return false
if lastSeed.seedReveal == H(seedReveal):
return true
return false
Protocol Logic for Other Modules
The random module exposes the following logic to other modules.
isSeedRevealValid
This function assesses the validity of the seedReveal
property of a block.
Parameters
-
generatorAddress
: A 20-bytes value with the address of the generator of a certain block. -
seedReveal
: A 16-bytes value with the seed revealed by the generator of a certain block.
Returns
This function returns true
if seedReveal
was correctly revealed, otherwise, false
.
Execution
It is specified as:
isSeedRevealValid(generatorAddress, seedReveal):
let seedObject be an element in validatorReveals array
let lastSeed be the seedObject with the largest seedObject.height and seedObject.generatorAddress == generatorAddress
if not lastSeed:
return true
if lastSeed.seedReveal == H(seedReveal):
return true
return false
getRandomBytes
This function is used to return the random seed as a 32-bytes value.
Parameters
-
height
: An integer with the height of a certain block. -
numberOfSeeds
: An integer with the number of seeds to consider for the computation.
Returns
randomSeed
: A 32-bytes value representing the random seed.
Execution
It is specified as:
getRandomBytes(height, numberOfSeeds):
randomSeed = H(height + numberOfSeeds)
let currentSeeds be an array with every seedObject element in validatorReveals so that height <= seedObject.height <= height + numberOfSeeds
for every seedObject element in currentSeeds:
if seedObject.valid == true:
randomSeed = randomSeed XOR seedObject.seedReveal
return randomSeed
Endpoints for Off-Chain Services
The random module exposes the following function.
IsSeedRevealValid
This function has exactly the same logic, inputs and outputs as the isSeedRevealValid
function specified in the previous section.
Protocol Logic During Block Lifecycle
After Genesis Block Execution
After the genesis block g
is executed, the following logic is executed:
- Set the
validatorReveals
array to an empty array.
Block Verification
As part of the verification of a block b
, the following checks are applied. If the checks fail the block is discarded and has no further effect. This logic is not called during the block creation process.
Let blockAssetBytes
be the bytes included in the block asset for the random module:
- Let
blockAssetObject
be the deserialization ofblockAssetBytes
according toblockHeaderAssetRandomModule
in the Block Initialization subsection. - Validate
blockAssetObject.seedReveal
as specified in the section “Validating New Block Header Property” of LIP 0022 (same logic asisSeedRevealValid
function above).
After Block Execution
After a block b
is executed, the following logic is applied:
-
While the size of the
validatorReveals
array is larger thanMAX_LENGTH_VALIDATOR_REVEALS
, delete the element ofvalidatorReveals
array with the smallest value ofheight
.- By construction this should be the first element of the
validatorReveals
array. - The value of the
MAX_LENGTH_VALIDATOR_REVEALS
constant is given in the initial configuration of the random module. It should be set as twice the maximum length of the chains validator set. For Lisk mainchain,MAX_LENGTH_VALIDATOR_REVEALS = MAX_LENGTH_REVEALS_MAINCHAIN
.
- By construction this should be the first element of the
-
Add a new element to the
validatorReveals
array with the following content:seedReveal = blockAssetObject.seedReveal
generatorAddress = b.header.generatorAddress
height = b.header.height
valid = isSeedValidInput(b.header.generatorAddress, blockAssetObject.seedReveal)
where the
isSeedValidInput
is the internal function specified above.
Block Creation
Block Initialization
The asset data created by the random module contains a serialized object following the blockHeaderAssetRandomModule
schema where the property seedReveal
, the seed revealed by the block generator, is set by the module. This can be done by implementing the hash onion algorithm introduced in the Appendix B of LIP 0022.
blockHeaderAssetRandomModule = {
"type": "object",
"properties": {
"seedReveal": {
"dataType": "uint64",
"fieldNumber": 1
}
},
"required": ["seedReveal"]
}
Backwards Compatibility
This LIP defines a new store interface for the random module, which in turn will become part of the state tree and will be authenticated by the state root. As such, it will induce a hardfork.