Focus mode
Ah, the egg. Or as they're officially known: Program Derived Addresses. We've cooked with these before. Let's crack 'em open to see how they work.
PDAs serve two main functions:
In other words, they're a secure key-value store for storage on the Solana network.
So far every time we've needed to derive an address we've used a handy function provided to us. What does this function actually do? To find out, we need to learn how all Solana keypairs are made.
Think back to what the point of a keypair is. It's a way to prove that you are who you say you are. We do this using a digital signature system. Solana keypairs are found on what is called the Ed25519 Elliptic Curve (you don't need to know wtf this is).
Since PDAs are controlled by programs, they donāt need private keys. So we make PDAs from addresses that are not on the Ed25519 curve. This effectively means they are public keysĀ withoutĀ a corresponding private key.
Thatās it. You donāt need to understand Ed25519, or even what a digital signature algorithm is. All you need to know is that PDAs look like regular Solana addresses and are controlled by programs. If you feel like you want to learn this further, check out this cool video on Digital Signatures from Computerphile.
To find a PDA within a Solana program, we'll use theĀ find_program_address
Ā function.
āSeedsā are optional inputs used in theĀ find_program_address
Ā function to derive a PDA.For example, seeds can be any combination:
TheĀ find_program_address
Ā function provides an additional seed called a "bump seed" to ensure that the resultĀ is notĀ on the Ed25519 curve
Once a valid PDA is found, the function returns both:
let (pda, bump_seed) = Pubkey::find_program_address(&[user.key.as_ref(), user_input.as_bytes().as_ref(), "SEED".as_bytes()], program_id)
find_program_address
find_program_address
is an imposter - it actually passes the inputĀ seeds
Ā andĀ program_id
Ā to the try_find_program_address
Ā function
pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
Self::try_find_program_address(seeds, program_id)
.unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
}
TheĀ try_find_program_address
Ā function then introduces theĀ bump_seed
.
TheĀ bump_seed
Ā is aĀ u8
Ā variable with a value ranging between 0 to 255, which is appended to the optional input seeds which are then passed to theĀ create_program_address
Ā function:
pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
let mut bump_seed = [std::u8::MAX];
for _ in 0..std::u8::MAX {
{
let mut seeds_with_bump = seeds.to_vec();
seeds_with_bump.push(&bump_seed);
match Self::create_program_address(&seeds_with_bump, program_id) {
Ok(address) => return Some((address, bump_seed[0])),
Err(PubkeyError::InvalidSeeds) => (),
_ => break,
}
}
bump_seed[0] -= 1;
}
None
}
TheĀ create_program_address
Ā function performs a set of hash operations over the seeds andĀ program_id
. These operations compute a key, then verify if the computed key lies on the Ed25519 elliptic curve or not.
If a valid PDA is found (i.e. an address that isĀ offĀ the curve), then the PDA is returned. Otherwise, an error is returned.
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError> {
let mut hasher = crate::hash::Hasher::default();
for seed in seeds.iter() {
hasher.hash(seed);
}
hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
let hash = hasher.result();
if bytes_are_curve_point(hash) {
return Err(PubkeyError::InvalidSeeds);
}
Ok(Pubkey::new(hash.as_ref()))
}
To recap:
find_program_address
Ā function passes our input seeds andĀ program_id
Ā to theĀ try_find_program_address
Ā function.
try_find_program_address
Ā function adds aĀ bump_seed
Ā (starting from 255) to our input seeds, then calls theĀ create_program_address
Ā function until a valid PDA is found.
bump_seed
Ā are returned.You don't need to remember all of that! The important thing is understanding what's happening when you call the find_program_address
function at a high level.
bump_seed
Ā returned byĀ find_program_address
Ā will always be the first valid PDA found.bump_seed
Ā is commonly referred to as the "canonical bump".find_program_address
Ā function only returns a Program Derived Address and the bump seed used to derive it.find_program_address
Ā function doesĀ notĀ initialize a new account, nor is any PDA returned by the function necessarily associated with an account that stores data.Since programs themselves are stateless, all program state is managed through external accounts. This means we need to do a bunch of mapping to keep things linked.
The mappings between seeds and PDA accounts that you use will be highly dependent on your specific program. While this isn't a lesson on system design or architecture, it's worth calling out a few guidelines:
This was a lot! Again, you don't need to remember everything we've covered here. Let's build an on-chain commenting system to see how this all works in practice!
Programs to Accelerate Your Progress in a Software Career
Join our 4-8 month intensive Patika+ bootcamps, start with the fundamentals and gain comprehensive knowledge to kickstart your software career!
You need to enroll in the course to be able to comment!