RNG explainer (AuRa + RandomAura Contract)
The following example uses methods from the RandomAura contract. A RANDAO methodology implemented by the validator nodes generates pseudorandom numbers.
The following is designed to work with Parity's AuRa consensus protocol v2.7.2+, slated for implementation on Kovan, Sokol, POA Core and/or xDai. In this protocol, validators take turns sealing blocks, one after the other, in a prescribed order.

Collection Rounds

A series of collection rounds are used for random number generation. The collection round length is configurable - in this example we use 38 blocks for a round, split into two equal 19 block phases:
    commit phase
    reveal phase
The length of each phase is 19 blocks ( 38 / 2 = 19). When one collection round finishes, the next collection round starts, and so on. For example:
1
Block number | Phase
2
...
3
101 Commit phase <-- collection round #1 started here
4
102 Commit phase
5
103 Commit phase
6
...
7
117 Commit phase
8
118 Commit phase
9
119 Commit phase
10
120 Reveal phase
11
121 Reveal phase
12
122 Reveal phase
13
...
14
136 Reveal phase
15
137 Reveal phase
16
138 Reveal phase <-- collection round #1 finished here
17
139 Commit phase <-- collection round #2 started here
18
140 Commit phase
19
141 Commit phase
20
...
Copied!
During each collection round, the RandomAura contract (see below) collects the random numbers generated during that round.

Commit Phase

1) Each validator in the set generates a random number locally with their node, hashes the secret, and calls the commitHash function when it is their turn to create a block.
1
/// @dev Called by the validator's node to store a hash and a cipher of the validator's number on each collection
2
/// round. The validator's node must use its mining address (engine_signer) to call this function.
3
/// This function can only be called once per collection round (during the `commit phase`).
4
/// @param _numberHash The Keccak-256 hash of the validator's number.
5
/// @param _cipher The cipher of the validator's number. Can be used by the node to restore the lost number after
6
/// the node is restarted (see the `getCipher` getter).
7
function commitHash(bytes32 _numberHash, bytes calldata _cipher) external {
8
address miningAddress = msg.sender;
9
10
require(block.coinbase == miningAddress);
11
require(isCommitPhase()); // must only be called in `commit phase`
12
require(_numberHash != bytes32(0));
13
require(!isCommitted(currentCollectRound(), miningAddress)); // cannot commit more than once
14
15
uint256 collectRound = currentCollectRound();
16
17
_commits[collectRound][miningAddress] = _numberHash;
18
_ciphers[collectRound][miningAddress] = _cipher;
19
}
Copied!
2) This function accepts the hash of the secret number and its cipher. The cipher is the number encrypted with a validator's key, and is needed for the reveal phase (see below).
For example, if there are three validators, they will call commitHash in the following order (for the sample case above):
1
Block number | Phase | What happens
2
...
3
101 Commit phase Validator1 calls `commitHash`
4
102 Commit phase Validator2 calls `commitHash`
5
103 Commit phase Validator3 calls `commitHash`
6
104 Commit phase Nothing happens
7
105 Commit phase Nothing happens
8
106 Commit phase Nothing happens
9
...
10
117 Commit phase Nothing happens
11
118 Commit phase Nothing happens
12
119 Commit phase Nothing happens
13
120 Reveal phase Validator1 calls `revealSecret`
14
121 Reveal phase Validator2 calls `revealSecret`
15
122 Reveal phase Validator3 calls `revealSecret`
16
123 Reveal phase Nothing happens
17
125 Reveal phase Nothing happens
18
126 Reveal phase Nothing happens
19
...
20
136 Reveal phase Nothing happens
21
137 Reveal phase Nothing happens
22
138 Reveal phase Nothing happens
23
139 Commit phase Validator1 calls `commitHash`
24
140 Commit phase Validator2 calls `commitHash`
25
141 Commit phase Validator3 calls `commitHash`
26
...
Copied!
When the commit phase finishes, the reveal phase starts.

Reveal Phase

When a validator's turn arrives to create a block:
1) The validator gets the cipher of the number using the getCommitAndCipher public getter.
1
/// @dev Returns the Keccak-256 hash and cipher of the validator's number for the specified collection round
2
/// and the specified validator stored by the validator through the `commitHash` function.
3
/// @param _collectRound The serial number of the collection round for which hash and cipher should be retrieved.
4
/// @param _miningAddress The mining address of validator (engine_signer).
5
function getCommitAndCipher(
6
uint256 _collectRound,
7
address _miningAddress
8
) public view returns(bytes32, bytes memory) {
9
return (_commits[_collectRound][_miningAddress], _ciphers[_collectRound][_miningAddress]);
10
}
Copied!
2) The validator decrypts the cipher with their key and retrieves the number.
3) The validator calls the revealNumberfunction to reveal their committed number (the function XORs the number with the previous seed to create a new random seed stored in the currentSeed state variable).
1
/// @dev Called by the validator's node to XOR its number with the current random seed.
2
/// The validator's node must use its mining address (engine_signer) to call this function.
3
/// This function can only be called once per collection round (during the `reveal phase`).
4
/// @param _number The validator's number.
5
function revealNumber(uint256 _number) external {
6
address miningAddress = msg.sender;
7
8
bytes32 numberHash = keccak256(abi.encodePacked(_number));
9
uint256 collectRound = currentCollectRound();
10
11
require(block.coinbase == miningAddress);
12
require(!isCommitPhase()); // must only be called in `reveal phase`
13
require(numberHash != bytes32(0));
14
require(numberHash == _commits[collectRound][miningAddress]); // the hash must be commited
15
require(!_sentReveal[collectRound][miningAddress]); // cannot reveal more than once during the same collect round
16
17
currentSeed = currentSeed ^ _number;
18
19
_sentReveal[collectRound][miningAddress] = true;
20
delete _commits[collectRound][miningAddress];
21
delete _ciphers[collectRound][miningAddress];
22
}
Copied!
Note: Randomness created in a deterministic manner, through computerized means, it is called pseudorandomness. Pseudorandom numbers exhibit the same properties as random numbers. The method described above is technically a pseudorandom number generator (PRNG)

RandomAura Contract Code

The RandomAura Contract interfaces with the Authority Round consensus process to store and iterate the currentSeed , control when the seed is revealed, and report on skipped reveals by Validators.
Below is the full RandomAura contract code (its POSDAO implementation is located at https://github.com/poanetwork/posdao-contracts/blob/master/contracts/RandomAuRa.sol)
1
pragma solidity 0.5.16;
2
3
4
/// @dev Generates and stores random numbers in a RANDAO manner (and controls when they are revealed by AuRa
5
/// validators) and accumulates a random seed.
6
contract RandomAuRa {
7
8
mapping(uint256 => mapping(address => bytes)) internal _ciphers;
9
mapping(uint256 => mapping(address => bytes32)) internal _commits;
10
mapping(uint256 => mapping(address => bool)) internal _sentReveal;
11
12
/// @dev The length of the collection round (in blocks).
13
uint256 public constant collectRoundLength = 38;
14
15
/// @dev The current random seed accumulated.
16
uint256 public currentSeed;
17
18
/// @dev Called by the validator's node to store a hash and a cipher of the validator's number on each collection
19
/// round. The validator's node must use its mining address (engine_signer) to call this function.
20
/// This function can only be called once per collection round (during the `commit phase`).
21
/// @param _numberHash The Keccak-256 hash of the validator's number.
22
/// @param _cipher The cipher of the validator's number. Can be used by the node to restore the lost number after
23
/// the node is restarted (see the `getCipher` getter).
24
function commitHash(bytes32 _numberHash, bytes calldata _cipher) external {
25
address miningAddress = msg.sender;
26
27
require(block.coinbase == miningAddress);
28
require(isCommitPhase()); // must only be called in `commit phase`
29
require(_numberHash != bytes32(0));
30
require(!isCommitted(currentCollectRound(), miningAddress)); // cannot commit more than once
31
32
uint256 collectRound = currentCollectRound();
33
34
_commits[collectRound][miningAddress] = _numberHash;
35
_ciphers[collectRound][miningAddress] = _cipher;
36
}
37
38
/// @dev Called by the validator's node to XOR its number with the current random seed.
39
/// The validator's node must use its mining address (engine_signer) to call this function.
40
/// This function can only be called once per collection round (during the `reveal phase`).
41
/// @param _number The validator's number.
42
function revealNumber(uint256 _number) external {
43
address miningAddress = msg.sender;
44
45
bytes32 numberHash = keccak256(abi.encodePacked(_number));
46
uint256 collectRound = currentCollectRound();
47
48
require(block.coinbase == miningAddress);
49
require(!isCommitPhase()); // must only be called in `reveal phase`
50
require(numberHash != bytes32(0));
51
require(numberHash == _commits[collectRound][miningAddress]); // the hash must be commited
52
require(!_sentReveal[collectRound][miningAddress]); // cannot reveal more than once during the same collect round
53
54
currentSeed = currentSeed ^ _number;
55
56
_sentReveal[collectRound][miningAddress] = true;
57
delete _commits[collectRound][miningAddress];
58
delete _ciphers[collectRound][miningAddress];
59
}
60
61
/// @dev Returns the serial number of the current collection round.
62
/// Needed when using `getCommit`, `isCommitted`, `sentReveal`, or `getCipher` getters (see below).
63
function currentCollectRound() public view returns(uint256) {
64
return (block.number - 1) / collectRoundLength;
65
}
66
67
/// @dev Returns the Keccak-256 hash and cipher of the validator's number for the specified collection round
68
/// and the specified validator stored by the validator through the `commitHash` function.
69
/// @param _collectRound The serial number of the collection round for which hash and cipher should be retrieved.
70
/// @param _miningAddress The mining address of validator (engine_signer).
71
function getCommitAndCipher(
72
uint256 _collectRound,
73
address _miningAddress
74
) public view returns(bytes32, bytes memory) {
75
return (_commits[_collectRound][_miningAddress], _ciphers[_collectRound][_miningAddress]);
76
}
77
78
/// @dev Returns a boolean flag indicating whether the specified validator has committed their number's hash for the
79
/// specified collection round.
80
/// @param _collectRound The serial number of the collection round for which the checkup should be done.
81
/// Should be read with `currentCollectRound()` getter.
82
/// @param _miningAddress The mining address of the validator (engine_signer).
83
function isCommitted(uint256 _collectRound, address _miningAddress) public view returns(bool) {
84
return _commits[_collectRound][_miningAddress] != bytes32(0);
85
}
86
87
/// @dev Returns a boolean flag of whether the specified validator has revealed their number for the
88
/// specified collection round.
89
/// @param _collectRound The serial number of the collection round for which the checkup should be done.
90
/// Should be read with `currentCollectRound()` getter.
91
/// @param _miningAddress The mining address of the validator (engine_signer).
92
function sentReveal(uint256 _collectRound, address _miningAddress) public view returns(bool) {
93
return _sentReveal[_collectRound][_miningAddress];
94
}
95
96
/// @dev Returns a boolean flag indicating whether the current phase of the current collection round
97
/// is a `commit phase`. Used by the validator's node to determine if it should commit the hash of
98
/// the number during the current collection round.
99
function isCommitPhase() public view returns(bool) {
100
uint256 commitPhaseLength = collectRoundLength / 2;
101
return ((block.number - 1) % collectRoundLength) < commitPhaseLength;
102
}
103
104
/// @dev Returns the number of the first block of the current collection round.
105
function currentCollectRoundStartBlock() public view returns(uint256) {
106
return currentCollectRound() * collectRoundLength + 1;
107
}
108
109
/// @dev Returns the number of the first block of the next (future) collection round.
110
function nextCollectRoundStartBlock() public view returns(uint256) {
111
uint256 remainingBlocksToNextRound = collectRoundLength - (block.number - 1) % collectRoundLength;
112
return block.number + remainingBlocksToNextRound;
113
}
114
115
/// @dev Returns the number of the first block of the next (future) commit phase.
116
function nextCommitPhaseStartBlock() public view returns(uint256) {
117
return nextCollectRoundStartBlock();
118
}
119
120
/// @dev Returns the number of the first block of the next (future) reveal phase.
121
function nextRevealPhaseStartBlock() public view returns(uint256) {
122
uint256 commitPhaseLength = collectRoundLength / 2;
123
if (isCommitPhase()) {
124
return currentCollectRoundStartBlock() + commitPhaseLength;
125
} else {
126
return nextCollectRoundStartBlock() + commitPhaseLength;
127
}
128
}
129
130
}
131
Copied!
Last modified 1yr ago