diff --git a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md index ced30faa6..603a62b55 100644 --- a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md +++ b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md @@ -12,9 +12,9 @@ description: "Use proper cryptographic randomness in your onchain programs." - Attempts at generating randomness within your program are likely to be guessable by users given there's no true randomness onchain. -- Verifiable Random Functions (VRFs) give developers the opportunity to +- Verifiable Random Functions (VRFs) allow developers to incorporate securely generated random numbers in their onchain programs. -- A VRF is a public-key pseudorandom function that provides proofs that its +- A VRF is a public-key pseudorandom function that proves its outputs were calculated correctly. - Switchboard offers a developer-friendly VRF for the Solana ecosystem. @@ -24,7 +24,7 @@ description: "Use proper cryptographic randomness in your onchain programs." Random numbers are **_not_** natively allowed onchain. This is because Solana is deterministic, every validator runs your code and needs to have the same result. -So if you wanted to create a raffle program, you'd have to look outside of the +So if you wanted to create a raffle program, you'd have to look outside the blockchain for your randomness. This is where Verifiable Random Functions (VRFs) come in. VRFs offer developers a secure means of integrating randomness onchain in a decentralized fashion. @@ -33,7 +33,7 @@ in a decentralized fashion. Before we dive into how random numbers can be generated for a blockchain, we must first understand how they are generated on traditional computer systems. -There are really two types of random numbers: _true random_ and _pseudorandom_. +There are two types of random numbers: _true random_ and _pseudorandom_. The difference between the two lies in how the numbers are generated. Computers can acquire _true random_ numbers by taking some type of physical @@ -51,7 +51,7 @@ in the sequence. Given the same seed, a PRNG will always produce the same sequence of numbers. It's important to seed with something close to true entropy: an admin-provided "random" input, the last system log, some combination of your system's clock time and other factors, etc.. Fun fact: some older video -games have been broken because speedrunners found out how their randomness was +games have been broken because speed runners found out how their randomness was calculated. One game in particular used the number of steps you've taken in the game as a seed. @@ -65,7 +65,7 @@ more. So we'll have to look outside of the blockchain for randomness with VRFs. ### What is Verifiable Randomness? A Verifiable Random Function (VRF) is a public-key pseudorandom function that -provides proofs that its outputs were calculated correctly. This means we can +provides proof that its outputs were calculated correctly. This means we can use a cryptographic keypair to generate a random number with a proof, which can then be validated by anyone to ensure the value was calculated correctly without the possibility of leaking the producer's secret key. Once validated, the random @@ -73,13 +73,13 @@ value is stored onchain in an account. VRFs are a crucial component for achieving verifiable and unpredictable randomness on a blockchain, addressing some of the shortcomings of traditional -PRNGs and the challenges with achieving true randomness in a decentralized +PRNGs and the challenges of achieving true randomness in a decentralized system. There are three key properties of a VRF: 1. **Deterministic** - A VRF takes a secret key and a nonce as inputs and - deterministically produces an output ( seeding ). The result is a seemingly + deterministically produces an output (seeding). The result is a seemingly random value. Given the same secret key and nonce, the VRF will always produce the same output. This property ensures that the random value can be reproduced and verified by anyone. @@ -91,7 +91,7 @@ There are three key properties of a VRF: generated by a VRF using the corresponding secret key and nonce. VRFs are not specific to Solana and have been utilized on other blockchains to -generate pseudorandom numbers. Fortunately switchboard offers their +generate pseudorandom numbers. Fortunately, switchboard offers their implementation of VRF to Solana. ### Switchboard VRF Implementation @@ -135,15 +135,15 @@ and consuming randomness from Switchboard looks like this: 6. Once VRF proof is verified, the Switchboard program will invoke the `callback` that was passed in as the callback in the initial request with the pseudorandom number returned from the Oracle. -7. Program consumes the random number and can execute business logic with it! +7. The program consumes the random number and can execute business logic with it! There are a lot of steps here, but don't worry, we'll be going through each step of the process in detail. -First there are a couple of accounts that we will have to create ourselves to +First, there are a couple of accounts that we will have to create to request randomness, specifically the `authority` and `vrf` accounts. The -`authority` account is a PDA derived from our program that is requesting the -randomness. So the PDA we create will have our own seeds for our own needs. For +`authority` account is a PDA derived from our program that is requesting +randomness. So the PDA we create will have our seeds for our needs. For now, we'll simply set them at `VRFAUTH`. ```typescript @@ -167,9 +167,9 @@ pub struct VrfAccountData { pub counter: u128, /// Onchain account delegated for making account changes. <-- This is our PDA pub authority: Pubkey, - /// The OracleQueueAccountData that is assigned to fulfill VRF update request. + /// The OracleQueueAccountData that is assigned to fulfill the VRF update request. pub oracle_queue: Pubkey, - /// The token account used to hold funds for VRF update request. + /// The token account used to hold funds for VRF update requests. pub escrow: Pubkey, /// The callback that is invoked when an update request is successfully verified. pub callback: CallbackZC, @@ -180,7 +180,7 @@ pub struct VrfAccountData { /// The number of builders. pub builders_len: u32, pub test_mode: bool, - /// Oracle results from the current round of update request that has not been accepted as valid yet + /// Oracle results from the current round of update requests that have not been accepted as valid yet pub current_round: VrfRound, /// Reserved for future info. pub _ebuf: [u8; 1024], @@ -188,15 +188,15 @@ pub struct VrfAccountData { ``` Some important fields on this account are `authority`, `oracle_queue`, and -`callback`. The `authority` should be a PDA of the program that has the ability -to request randomness on this `vrf` account. That way, only that program can +`callback`. The `authority` should be a PDA of the program that can + request randomness on this `vrf` account. That way, only that program can provide the signature needed for the vrf request. The `oracle_queue` field -allows you to specify which specific oracle queue you'd like to service the vrf -requests made with this account. If you aren't familiar with oracle queues on -Switchboard, checkout the +allows you to specify which specific oracle queue you’d like to service the vrf +requests made with this account. If you aren’t familiar with oracle queues on +Switchboard, check the [Oracles lesson in the Connecting to Offchain Data course](/content/courses/connecting-to-offchain-data/oracles)! Lastly, the `callback` field is where you define the callback instruction the -Switchboard program should invoke once the randomness result has be verified. +Switchboard program should be invoked once the randomness result has been verified. The `callback` field is of type `[CallbackZC](https://github.com/switchboard-xyz/solana-sdk/blob/9dc3df8a5abe261e23d46d14f9e80a7032bb346c/rust/switchboard-solana/src/oracle_program/accounts/ecvrf.rs#L25)`. @@ -234,7 +234,7 @@ const vrfCallback: Callback = { { pubkey: vrfClientKey, isSigner: false, isWritable: true }, { pubkey: vrfSecret.publicKey, isSigner: false, isWritable: true }, ], - // use name of instruction + // use the name of instruction instructionData: vrfInstructionCoder.encode("consumeRandomness", ""), // pass any params for instruction here } ``` @@ -306,7 +306,7 @@ context. - Queue Authority - Authority of the Oracle Queue chosen - [Data Buffer](https://github.com/switchboard-xyz/solana-sdk/blob/9dc3df8a5abe261e23d46d14f9e80a7032bb346c/rust/switchboard-solana/src/oracle_program/accounts/queue.rs#L57C165-L57C165) - Account of the `OracleQueueBuffer` account holding a collection of Oracle - pubkeys that have successfully hearbeated before the queues `oracleTimeout` + pubkeys that have successfully heartbeat before the queues `oracleTimeout` configuration has elapsed. Stored in the Oracle Queue account. - [Permission Account Data](https://docs.rs/switchboard-solana/latest/switchboard_solana/oracle_program/accounts/permission/struct.PermissionAccountData.html) - Escrow (Switchboard escrow account) - Token Account @@ -324,7 +324,7 @@ what it looks like in a Solana program via CPI. To do this, we make use of the `VrfRequestRandomness` data struct from the [Switchboard-Solana rust crate.](https://github.com/switchboard-xyz/solana-sdk/blob/main/rust/switchboard-solana/src/oracle_program/instructions/vrf_request_randomness.rs) This struct has some built-in capabilities to make our lives easier here, most -notably the account structure is defined for us and we can easily call `invoke` +notably, the account structure is defined for us and we can easily call `invoke` or `invoke_signed` on the object. ```rust @@ -373,7 +373,7 @@ it back to the Switchboard program. Once the result is verified, the Switchboard program then invokes the `callback` instruction defined in the `vrf` account. The callback instruction is where you would have written your business logic using the random numbers. In the -following code we store the resulting randomness in our `vrf_auth` PDA from our +following code, we store the resulting randomness in our `vrf_auth` PDA from our first step. ```rust @@ -418,9 +418,9 @@ yet and that's how the randomness is returned. Switchboard, gives you your randomness calling [`get_result()`](https://github.com/switchboard-xyz/solana-sdk/blob/9dc3df8a5abe261e23d46d14f9e80a7032bb346c/rust/switchboard-solana/src/oracle_program/accounts/vrf.rs#L122). This method returns the `current_round.result` field of the `vrf` account -SwitchboardDecimal format, which is really just a buffer of 32 random -[`u8`](https://github.com/switchboard-xyz/solana-sdk/blob/9dc3df8a5abe261e23d46d14f9e80a7032bb346c/rust/switchboard-solana/src/oracle_program/accounts/ecvrf.rs#L65C26-L65C26) -unsigned-integers. You can use these unsigned-integers however you see fit in +SwitchboardDecimal format, which is just a buffer of 32 random +`[u8](https://github.com/switchboard-xyz/solana-sdk/blob/9dc3df8a5abe261e23d46d14f9e80a7032bb346c/rust/switchboard-solana/src/oracle_program/accounts/ecvrf.rs#L65C26-L65C26)` +unsigned integers. You can use these unsigned integers however you see fit in your program, but a very common method is to treat each integer in the buffer as its own random number. For example, if you need a dice roll (1-6) just take the first byte of the array, module it with 6 and add one. @@ -450,7 +450,7 @@ is provided for you in [the main branch of the lab Github repository](https://github.com/solana-developers/burry-escrow). [the main branch of the lab Github repository](https://github.com/solana-developers/burry-escrow). -The repo contains a "Michael Burry" escrow program. This is a program that +The repo contains a "Michael Burry" escrow program. This program allows a user to lock up some SOL funds in escrow that cannot be withdrawn until SOL has reached a predefined price in USD chosen by the user. We will be adding VRF functionality to this program to allow the user to "Get out of jail" @@ -485,7 +485,7 @@ in our `Cargo.toml` file. [dependencies] anchor-lang = "0.28.0" anchor-spl = "0.28.0" -switchboard-solana = "0.28" +switchboard-solana = "0.28.0" ``` ### 3. Lib.rs @@ -574,7 +574,7 @@ hold the state of our dice rolls. It will have the following fields: will create this before we call `VrfClient`'s initialization function. - `escrow` - Public key of our burry escrow account. -We're also going to make the `VrfClient` context a `zero_copy` struct. This +We will also make the `VrfClient` context a `zero_copy` struct. This means that we will initialize it with `load_init()` and pass it into accounts with `AccountLoader`. We do this because VRF functions are very account intensive and we need to be mindful of the stack. If you'd like to learn more @@ -599,7 +599,7 @@ pub struct VrfClient { } ``` -Lastly we are going to add the `VRF_STATE_SEED` to PDA our VRF Client account. +Lastly, we are going to add the `VRF_STATE_SEED` to the PDA of our VRF Client account. ```rust pub const VRF_STATE_SEED: &[u8] = b"VRFCLIENT"; @@ -627,7 +627,7 @@ pub struct Escrow { pub struct VrfClient { pub bump: u8, pub result_buffer: [u8; 32], - pub dice_type: u8, // 6 sided + pub dice_type: u8, // 6 sided pub die_result_1: u8, pub die_result_2: u8, pub timestamp: i64, @@ -783,11 +783,11 @@ pub struct InitVrfClient<'info> { Notice the `vrf_state` account is a PDA derived with the `VRF_STATE_SEED` string and the `user`, `escrow_account`, and `vrf` public keys as seeds. This means a single user can only initialize a single `vrf_state` account, just like they can -only have one `escrow_account`. Since there is only one, If you wanted to be +only have one `escrow_account`. Since there is only one, If you want to be thorough, you might want to implement a `close_vrf_state` function to get your rent back. -Now, let's write some basic initialization logic for this function. First we +Now, let’s write some basic initialization logic for this function. First, we load and initialize our `vrf_state` account by calling `load_init()`. Then we fill in the values for each field. @@ -822,10 +822,10 @@ VRF Accounts: - `payer_wallet` - the token wallet that will pay for the VRF request; the `user` must be the owner of this account. -- `vrf` - The VRF account that was created by the client. -- `oracle_queue` - The oracle queue that will field the randomness result. +- `vrf` - The VRF account created by the client. +- `oracle_queue` - The oracle queue that will feed the randomness result. - `queue_authority` - The authority over the queue. -- `data_buffer` - The queue's data buffer account - used by the queue to +- `data_buffer` - The queue's data buffer account which is used by the queue to compute/verify the randomness. - `permission` - Created when creating the `vrf` account. It's derived from several of the other accounts. @@ -938,10 +938,10 @@ pub struct RequestRandomnessParams { } ``` -Now, we can work on the logic of this instruction. The logic should gather all -of the accounts needed and pass them to +Now, we can focus on implementing the logic of this instruction. The logic should collect all +the necessary accounts needed and pass them to `[VrfRequestRandomness](https://github.com/switchboard-xyz/solana-sdk/blob/fbef37e4a78cbd8b8b6346fcb96af1e20204b861/rust/switchboard-solana/src/oracle_program/instructions/vrf_request_randomness.rs#L8)`, -which is a really nice struct from Switchboard. Then we'll sign the request and +which is a well designed struct from Switchboard. After that we'll sign the request and send it on it's way. ```rust @@ -1093,17 +1093,17 @@ pub fn consume_randomness_handler(ctx: Context) -> Result <() } ``` -Now it's time to actually use the random result. Since we only use two dice we +Now it’s time to use the random result. Since we're working with two dice we only need the first two bytes of the buffer. To convert these random values into “dice rolls”, we use modular arithmetic. For anyone not familiar with modular arithmetic, -[Wikipedia can help](https://en.wikipedia.org/wiki/Modular_arithmetic). In -modular arithmetic, numbers "wrap around" upon reaching a given fixed quantity. -This given quantity is known as the modulus to leave as the remainder. Here, the +[this Wikipedia article](https://en.wikipedia.org/wiki/Modular_arithmetic) provides a helpful introduction. In +modular arithmetic, numbers "wrap around" when they reach a given fixed given quantity. +This given quantity is known as the modulus to leave as the remainder. In our case, the modulus is the `dice_type` stored on the `vrf_state` account. We hard-coded this to 6 when the account was initialized to represent a 6-sided die. When we use -`dice_type`, or 6, as the modulus, our result will be a number 0-5. We then add -one, to make the resulting possibilities 1-6. +`dice_type`, or 6, as the modulus, our result will be a number between 0 and 5. We then add +one to shift tha range, making the possible outcomes 1-6. ```rust pub fn consume_randomness_handler(ctx: Context) -> Result <()> { @@ -1242,7 +1242,7 @@ import { import { NodeOracle } from "@switchboard-xyz/oracle"; import { assert } from "chai"; -export const solUsedSwitchboardFeed = new anchor.web3.PublicKey( +export const solUsdSwitchboardFeed = new anchor.web3.PublicKey( "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR", ); @@ -1440,7 +1440,7 @@ describe.only("burry-escrow-vrf", () => { Now let's run the actual test. We'll structure the test to keep rolling dice until we get doubles, then we'll check that we can withdraw the funds. -First, we'll gather all of the accounts we need. The `switchboard` test context +First, we'll gather all the accounts we need. The `switchboard` test context gives us most of these. Then we'll need to call our `initVrfClient` function. Finally, we'll roll our dice in a loop and check for doubles. @@ -1557,7 +1557,7 @@ it("Roll till you can withdraw", async () => { await provider.connection.confirmTransaction(transaction, "confirmed"); console.log(`Created VrfClient Account: ${vrfClientKey}`); - // wait a few sec for switchboard to generate the random number and invoke callback instruction + // wait a few seconds for switchboard to generate the random number and invoke the callback instruction console.log("Rolling Die..."); let didUpdate = false;