Skip to content
This repository has been archived by the owner on Oct 27, 2024. It is now read-only.

Latest commit

 

History

History
204 lines (120 loc) · 5.53 KB

05_pda.md

File metadata and controls

204 lines (120 loc) · 5.53 KB

🛹 Program Derived Addresses


tl; dr


  • PDAs can be thought of as a way of mimicking Web2's databases by writing schemas. For instance, a dApp that needs to update its own data without having a 'client' authorizing those changes.

  • PDAs are addresses with special properties. They are not public keys (so they don't have an associated public key).

  • PDAs provide a mechanism to build hashmap-like structures on-chain, allowing programs to sign instructions.

    • findProgramAddress() will deterministically derive a PDA from a program_id and seeds (collection of bytes).
    • A bump (one byte) is used to push a potential PDA off the ed25519 elliptic curve.
    • Programs can sign for their PDAs by providing the seeds and bump to invoke_signed.
  • PDAs simplify the programming model and make programs more secure.



Generating PDA


When you use seeds to derive a public key, there is a chance that the seed you use and the public derived from them have an associated private key (depicted by the ed2559 elliptic curve).

So if the seeds derive a private key that exists in the curve, Solana will add an additional integer (a bump) to the seed list to make sure it bumps off the curve and cannot have a private key.



  • PDAs are created by hashing a number of seeds the user can choose with the program_id.

  • Seeds can be anything: pubkey, strings, an array of numbers, etc.

  • There is a 50% chance that this hash can result in a public key. This is how a bump can be searched:


fn find_pda(seeds, program_id) {
  for bump in 0..256 {
    let potential_pda = hash(seeds, bump, program_id);
    if is_pubkey(potential_pda) {
      continue;
    }
    return (potential_pda, bump);
  }
  panic!("Could not find pda after 256 tries.");
}

  • The first bump that results in a PDA is called a "canonical bump," and they are the recommended one for usage.


Interacting with PDAS


  • When a PDA is generated, findProgramAddress() returns both the address and the bump used to kick the address off the elliptic curve.

  • With a bump, a program can sign any instruction that requires its PDA, by passing the instruction, the list of accounts, and the seeds and bumps used to derive the PDA to invoke_signed.



Hashmap-like Structures with PDAs


  • PDAs are hashed from a bump, a program_id, and several seeds. These seeds can be used to build hashmap-like structures on-chain.

  • With PDAs, you can create structs that encode the information about a relationship between the user and some data account, so that PDAs serve as the address:


pub struct UserStats {
  level: u16,
  name: String,
  bump: u8
}


CPI Signing with PDAs


  • In some cases, it's possible to reduce the number of accounts needed by making a PDA storing state also sign a CPI instead of defining a separate PDA for that.

  • This means that programs can be given control over assets, which they then manage according to the rules defined in the code.



Examples of PDAs


  • A program with a global state:
const [pda, bump] = await findProgramAddress(Buffer.from("GLOBAL_STATE"), program_id)

  • A program with user-specific data:
const [pda, bump] = await web3.PublicKey.findProgramAddress(
  [
    publicKey.toBuffer()
  ],
  program_id
)

  • A program with multiple data items per user:
const [pda, bump] = await web3.PublicKey.findProgramAddress(
  [
    publicKey.toBuffer(), 
    Buffer.from("Shopping list")
  ],
  program_id,
);

💡 Tip: If find_program_address has to take a long time to find a valid address (i.e., it as a high bump) the compute unit usage might be high. Finding the PDAs after initialization can be optimized by saving the bump into an account.



Demos





Resources