LIP: 0046
Title: Define state and state transitions of Random module
Author: Iker Alustiza <[email protected]>
Ishan Tiwari <[email protected]>
Discussions-To: https://research.lisk.com/t/define-state-and-state-transitions-of-random-module
Status: Draft
Type: Standards Track
Created: 2021-06-30
Updated: 2023-01-30
Requires: 0022, 0040
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 genesis block processing, common block processing, and the functions that can be called from other modules or off-chain services.
This LIP is licensed under the Creative Commons Zero 1.0 Universal.
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 during block processing, the genesis block processing, and the functions exposed to other modules and to off-chain services.
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 PoS chains) and to select the standby validators (in certain PoS 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.
In this section, we specify the substores that are part of the Random module store, and the protocol logic called during the block processing and genesis block processing.
The Random module has name MODULE_NAME_RANDOM
(see the table below). It is a module with no dependencies.
We define the following constants:
Name | Type | Value | Description |
---|---|---|---|
Constants | |||
MODULE_NAME_RANDOM |
string | "random" | Module's name. |
SUBSTORE_PREFIX_RANDOM |
bytes | 0x0000 |
Prefix of the random substore. |
ADDRESS_LENGTH |
uint32 | 20 | Length in bytes of type Address. |
SEED_LENGTH |
uint32 | 16 | Length in bytes of a valid seed revealed. |
Config parameters | Mainchain value | ||
MAX_LENGTH_VALIDATOR_REVEALS |
uint32 | 206 | Maximum length of the validatorReveals array. |
Name | Type | Validation | Description |
---|---|---|---|
Address |
bytes | Must be of length ADDRESS_LENGTH . |
Address of an account. |
The key-value pairs in the module store are organized in the following substore.
The entry in the random substore is defined as follows:
- The substore prefix is set to
SUBSTORE_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. - Notation: For the rest of this proposal, let
validatorReveals
be the entry in the random substore, deserialized usingseedRevealSchema
schema.
seedRevealSchema = {
"type": "object",
"required": ["validatorReveals"],
"properties": {
"validatorReveals": {
"type": "array",
"fieldNumber": 1,
"items": {
"type": "object",
"required": [ "generatorAddress", "seedReveal", "height", "valid"],
"properties": {
"generatorAddress": {
"dataType": "bytes",
"length": ADDRESS_LENGTH,
"fieldNumber": 1
},
"seedReveal": {
"dataType": "bytes",
"length": SEED_LENGTH,
"fieldNumber": 2
},
"height": {
"dataType": "uint32",
"fieldNumber": 3
},
"valid": {
"dataType": "boolean",
"fieldNumber": 4
}
}
}
}
}
}
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.seedReveal
: The value revealed by the generator of the block for the commit and reveal process.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.
The Random module has the following internal functions.
A new hashing function, H
, is defined for this module. It receives as input a bytes value of arbitrary length and returns the first SEED_LENGTH
bytes of its SHA-256 hash.
def H(input: bytes) -> bytes:
t = SHA-256(input)
digest = t[0:SEED_LENGTH]
return digest
This function assesses the validity of the revealed seed as input for the random seed computation.
generatorAddress
: The address of the generator of a certain block.seedReveal
: ASEED_LENGTH
-bytes value with the seed revealed by the generator of a certain block.
This function returns True
if seedReveal
is valid input for the random seed computation, otherwise, False
.
def isSeedValidInput(generatorAddress: Address, seedReveal: bytes) -> bool:
lastSeed = last element seedObject in validatorReveals array with seedObject.generatorAddress == generatorAddress
if not lastSeed:
return False
if lastSeed.seedReveal == H(seedReveal):
return True
return False
This module does not define any command.
The Random module exposes the following logic to other modules.
This function assesses the validity of the seedReveal
property of a block.
generatorAddress
: The address of the generator of a certain block.seedReveal
: ASEED_LENGTH
-bytes value with the seed revealed by the generator of a certain block.
This function returns True
if seedReveal
was correctly revealed, otherwise, False
.
It is specified as:
def isSeedRevealValid(generatorAddress: Address, seedReveal: bytes) -> bool:
lastSeed = last element seedObject in validatorReveals array with seedObject.generatorAddress == generatorAddress
if not lastSeed:
return True
if lastSeed.seedReveal == H(seedReveal):
return True
return False
This function is used to return the random seed as a SEED_LENGTH
-bytes value.
height
: An integer with the height of a certain block.numberOfSeeds
: An integer with the number of seeds to consider for the computation.
randomSeed
: A SEED_LENGTH
-bytes value representing the random seed.
It is specified as:
def getRandomBytes(height: uint32, numberOfSeeds: uint32) -> bytes:
randomSeed = H(height + numberOfSeeds)
currentSeeds = [seedObject for seedObject in validatorReveals if height <= seedObject.height < height + numberOfSeeds]
for every seedObject element in currentSeeds:
if seedObject.valid == True:
randomSeed = randomSeed XOR seedObject.seedReveal
return randomSeed
The Random module exposes the following function.
This function has exactly the same logic, inputs and outputs as the isSeedRevealValid
function specified in the previous section.
The following step is executed as part of the genesis block processing, see the LIP 0060 for details.
After the genesis block g
is executed, the following logic is executed:
- Create the entry in the random substore as specified above.
- Set the
validatorReveals
array in the store value of the random substore to an empty array.
The following steps are executed as part of the block processing, see LIP 0055 for details.
As part of the verification of the assets
property 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. - The property
blockAssetObject.seedReveal
has to have a length ofSEED_LENGTH
bytes.
After all the transactions of a block b
are 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.
- 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.
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",
"required": ["seedReveal"],
"properties": {
"seedReveal": {
"dataType": "bytes",
"length": SEED_LENGTH,
"fieldNumber": 1
}
}
}
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.
TBA