This repository contains scripts enabling anyone to quickly audit the random number generator (RNG) used in the Verifiable Draws project.
It is separated in 2 parts:
- Audit of the Chainlink VRF random numbers that Verifiable Draws uses as a source of randomness
- Audit of the observed drawing outcomes
For each part we collect a huge sample of randomness which can then be manually analyzed by simply looking at the data and realizing that it looks random. Alternatively, you can perform a more in-depth analysis by using the collected data as inputs for the Diehard tests to precisely measure the quality of the random number generator.
Clone this repository on your local machine:
git clone https://github.com/lancelot-c/rng-audit.git
This is the testing of the raw randomness received from Chainlink VRF requests.
A smart contract is needed to generate VRF randomness on-demand and make it available for export.
Such a contract is available at src/OnlyRaw.sol
and is deployed for your convenience at 0xBBCD0c8DBdC112dd29af8c57Ee8740bD9feE084B. The variable wordsCounter is the number of random values that the contract has already generated. At the time of writing, the contract holds a total of 3,125,210 random words, which we consider enough to perform a relevant analysis.
If you need more randomness, the easiest way is to registrer a time-based Upkeep for this contract on the function makeVrfRequest(110)
with the CRON expression */1 * * * *
for an execution every minute. Make sure the VRF subscription for this contract is sufficiently funded, otherwise add funds to the subscription with your own wallet: Connect Wallet > Actions dropdown > Fund subscription
.
ℹ️ Even though Chainlink says you can generate a maximum number of 500 random words per VRF request, it turns out in practice that 110 is the maximum you can ask for (at least on Arbitrum Sepolia), any value greater than that will return an error.
Now we want to export all this raw randomness out of the smart contract.
The OnlyRaw
script is doing precisely that:
cd scripts/OnlyRaw
Set the variables in the .env
depending on what values you want to export, for example if you want the first 1,000,000 values:
START_AT=1
HOW_MANY=1000000
Then run the script:
npm install
node OnlyRaw.js
The output file is located at script-outputs/OnlyRaw-<START_AT>-<HOW_MANY>.txt
.
If you need additionnal values, let's say 5,000,000 more values, you can run the same script again but this time by ignoring the values that you've already exported:
START_AT=1000001
HOW_MANY=5000000
ℹ️ Verifiable Draws is using 64-bit numbers as a source of randomness, however each random word received from Chainlink is 256-bit so each random word is splitted into 4 numbers of 64-bit each and these 64-bit numbers are the ones being exported. This means that when you are exporting 1,000,000 values with this script, you are actually only exporting 250,000 random words from Chainlink. It is important to know this when setting the value for the HOW_MANY
variable. The maximum value of HOW_MANY
is therefore the value of wordsCounter multiplied by 4.
For faster execution, you can run several instances of this script in parallel with different .env
values.
We would like to collect the following 20 datasets:
Range | Positions | Replacement | Draws |
---|---|---|---|
2 | 1 | N/A | 1,000,000 |
17 | 5 | No | 20,000,000 |
31 | 12 | No | 50,000,000 |
57 | 20 | No | 50,000,000 |
105 | 7 | No | 100,000,000 |
194 | 30 | No | 100,000,000 |
358 | 20 | No | 100,000,000 |
660 | 40 | No | 50,000,000 |
1,217 | 50 | No | 50,000,000 |
2,243 | 10 | No | 200,000,000 |
4,135 | 12 | No | 200,000,000 |
7,622 | 4 | No | 500,000,000 |
14,050 | 9 | No | 200,000,000 |
25,899 | 75 | No | 20,000,000 |
47,742 | 10 | No | 200,000,000 |
65,536 | 5 | No | 500,000,000 |
72,859 | 10 | No | 200,000,000 |
80,989 | 25 | No | 50,000,000 |
90,007 | 15 | No | 100,000,000 |
100,000 | 100 | No | 10,000,000 |
This is roughly 32 billion samples total. Now, this is quite a lot of data to collect so instead of using Chainlink VRF as a source of randomness we can use fuzz testing which is a built-in feature of Foundry.
Install Foundry by running the following command in your terminal, then follow the onscreen instructions:
curl -L https://foundry.paradigm.xyz | bash
Install the project dependencies:
cd rng-audit
forge install lancelot-c/verifiable-draws
forge install smartcontractkit/chainlink
forge install OpenZeppelin/openzeppelin-contracts
We will run the test file OnlyOutcomes.t.sol
which contains an equivalent of the checkDrawWinners function of the Verifiable Draws smart contract adapted for fuzz testing.
Make sure your .env
has the desired dataset parameters, for example if you want to collect dataset #2 your .env
should have:
RANGE=17
POSITIONS=5
FOUNDRY_FUZZ_RUNS=20000000
Then run:
forge test
The output file is located at script-outputs/OnlyOutcomes-<RANGE>-<POSITIONS>-<FOUNDRY_FUZZ_RUNS>.txt
.
On a regular consumer laptop the dataset #1 should take approximatively 90 seconds to generate.
For faster execution, each dataset can be generated in parallel if you launch several instances of this test on different machines with different .env
values.
Our Discord is the best place to ask for help.