Skip to content

Commit 27d9ef6

Browse files
authored
Merge pull request #168 from keep-network/implementation-details-rng
Select A Random Group
2 parents 60a3610 + 1b7ae5b commit 27d9ef6

1 file changed

Lines changed: 89 additions & 1 deletion

File tree

docs/implementation-details.md

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,92 @@ For an in-depth explanation of how this information is structured, refer to the
176176
Serialization](#branch-and-root-deserialization-and-serialization) section.
177177

178178
TODO: Joining and Leaving The Pool
179-
TODO: Selecting A Random Group
179+
180+
### Selecting A Random Group
181+
182+
To select a random group of of operators from the pool size `N`, we construct
183+
an array of size `N` to house the result, and then populate it, one random
184+
operator at a time, with replacement.
185+
186+
We start with a seed provided from the [random
187+
beacon](https://github.com/keep-network/keep-core/tree/main/solidity/random-beacon)
188+
and use that seed in combination with the total weight of the root node,
189+
calculatable by summing the weight of the slots: see [Branch And Root
190+
Deserialization And
191+
Serialization](#branch-and-root-deserialization-and-serialization) to generate
192+
a random uniform integer in `[0, root.totalWeight)`, as well as the next random
193+
seed for the next random operator. In order to generate this next random seed,
194+
we're using
195+
```
196+
newState = keccak256(abi.encodePacked(newState, address(this)));
197+
```
198+
which, according to [A Pseudorandom Number Generator with KECCAK Hash Function
199+
by A. Gholipour and S. Mirzakuchak](http://www.ijcee.org/papers/439-JE503.pdf),
200+
has "excellent pseudo randomness".
201+
202+
Once we have our random integer, we are able to descend down the tree according
203+
to the algorithm outlined in [building intuition](building-intuition.md).
204+
205+
```
206+
function pickWeightedLeaf(uint256 index, uint256 _root)
207+
internal
208+
view
209+
returns (uint256 leafPosition)
210+
{
211+
uint256 currentIndex = index;
212+
uint256 currentNode = _root;
213+
uint256 currentPosition = 0;
214+
uint256 currentSlot;
215+
216+
require(index < currentNode.sumWeight(), "Index exceeds weight");
217+
218+
// get root slot
219+
(currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);
220+
221+
// get slots from levels 2 to 7
222+
for (uint256 level = 2; level <= LEVELS; level++) {
223+
currentPosition = currentPosition.child(currentSlot);
224+
currentNode = branches[level][currentPosition];
225+
(currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);
226+
}
227+
228+
// get leaf position
229+
leafPosition = currentPosition.child(currentSlot);
230+
}
231+
232+
function pickWeightedSlot(uint256 node, uint256 index)
233+
internal
234+
pure
235+
returns (uint256 slot, uint256 newIndex)
236+
{
237+
unchecked {
238+
newIndex = index;
239+
uint256 newNode = node;
240+
uint256 currentSlotWeight = newNode & SLOT_MAX;
241+
while (newIndex >= currentSlotWeight) {
242+
newIndex -= currentSlotWeight;
243+
slot++;
244+
newNode = newNode >> SLOT_WIDTH;
245+
currentSlotWeight = newNode & SLOT_MAX;
246+
}
247+
return (slot, newIndex);
248+
}
249+
}
250+
```
251+
252+
At a particular root/branch node, we inspect the right-most 32 bits by applying
253+
a bitwise `&` against `2^32 - 1`, which leaves only the last 32 bits as
254+
potentially non-zero.
255+
256+
If this quantity if greater than our random number, we found our path of
257+
descent and repeat the process at the next layer. If it isn't, then we increase
258+
our slot counter, decrease our random number by the quantity, and shift our
259+
number over 32 bits to the right and repeat.
260+
261+
Eventually we will find a slot that exceeds our random number and be able to
262+
descend to the next layer where the process repeats until we get to the leaf
263+
layer where the task is finished.
264+
265+
We record our chosen operator in the result list, use the fresh seed from
266+
`keccak256` to generate a new random number and new seed, and repeat until we
267+
have a full group.

0 commit comments

Comments
 (0)