The PDA Blueprint
A program-derived address (PDA) is a Solana account address that a program can control without holding a private key. Seeds and a program ID fix the address; a bump makes it land off the ed25519 curve so no ordinary keypair can sign for it. Once you see derivation, creation, and invoke_signed as one pipeline, vaults, escrows, and mint authorities stop looking like special account types and start looking like the same account model with a program-owned signing path.
PDA Basics is the hands-on entry; Deriving PDAs, Canonical Bumps, PDA Account Creation, PDAs as Authorities, and PDA Security Pitfalls each zoom into one mechanism. This page sits underneath: how derivation, bumps, creation, signing, and authority patterns form one blueprint.
Summary
- A PDA is a deterministic, off-curve address produced from seeds + program ID + bump; only the owning program may authorize it by supplying those seeds to
invoke_signed. - Insight: Solana programs are stateless executables. PDAs give them durable, program-controlled addresses for config, vaults, escrows, and mint authority without embedding secret keys on-chain.
- Key Concepts: seeds,
find_program_address, bump / canonical bump, off-curve, PDA account creation,invoke_signed, PDA as authority, on-chain re-derivation. - When to Use: Designing per-user or per-mint state, program-owned treasuries, token mint/freeze authority, escrow release flows, or client address discovery before building transactions.
- Limitations/Trade-offs: Seed design is permanent for a given program ID; creation still needs rent and System Program CPI; wrong seeds or bumps fail obscurely; a bug in signing paths is equivalent to giving away the vault key.
- Related Topics: PDA basics, deriving PDAs, canonical bumps, account creation, authorities, security pitfalls.
Foundations
Solana accounts live at public keys. Most addresses are on-curve: they correspond to an ed25519 keypair someone can hold. A PDA is deliberately off-curve: the runtime will never treat it as a normal wallet signer. That is the whole point. The program that owns the derivation (the program ID in find_program_address) can later prove control by replaying the seed bytes into the runtime's PDA signer check.
Nothing about the account record itself is special. After creation, a PDA account still has lamports, data, owner, executable, and rent_epoch. What is special is how the address was chosen and how signatures are produced.
Think of one derivation equation:
seeds: [ "vault", user_pubkey, ... ]
program_id: YourProgram
bump: u8 (try 255, 254, ... until off-curve)
|
v
PDA address ----no private key----> cannot wallet-sign
|
+-- your program may invoke_signed(seeds + bump)Seeds are ordered byte slices (at most 16 seeds, each at most 32 bytes). Typical pieces: a constant namespace (b"vault"), a user or mint pubkey (32 bytes), and fixed-endian integers (id.to_le_bytes()). Order and encoding are part of the address. Change either and you get a different account.
Program ID is the second half of identity. The same seeds under a different program derive a different PDA. Clients must use the deployed program id, not a hard-coded guess from another environment.
Bump is the final one-byte seed that the finder appends while searching for an off-curve point. The search starts at 255 and steps downward. The first valid bump is the canonical bump. Production programs store that value and refuse non-canonical bumps so alternate addresses under the same logical seeds cannot be used for griefing.
Off-chain clients and on-chain programs both call the same idea: hash seeds, program id, and bump until the result is a valid PDA. Off-chain, that is how you populate the transaction account list. On-chain, that is how you verify a client-supplied account really is the expected PDA before writing data or signing CPIs.
Mechanics & Interactions
find_program_address
Pubkey::find_program_address(seeds, program_id) returns (pda, bump) for the canonical address. Related helpers (create_program_address) take an explicit bump and succeed only if that bump yields an off-curve address.
Conceptually:
- For bump from 255 down to 0, hash seeds + bump + program id under the PDA domain separator.
- Stop at the first off-curve result.
- Return that address and the bump used.
Cost note: on-chain, a full find can try many hashes. Prefer store the canonical bump at init and later pass &[stored_bump] into signer seeds, or use create_program_address when you already know the bump.
Anchor 0.32.1 surfaces this with seeds / bump account constraints so the framework re-derives and checks for you. Native programs should centralize a small assert_pda helper.
Creating a PDA account
Derivation alone does not create state. An unused PDA address has no account until something funds, allocates, and assigns it. The usual path is a System Program create_account CPI where:
- A wallet payer signs and pays rent-exempt lamports for the intended data size.
- The new account key is the PDA (listed in the transaction; it is not a wallet signer).
- The program supplies signer seeds so the PDA can "sign" the create.
- The owner assigned is typically your program id so only you write
data.
payer (wallet) --lamports--> PDA account
program --invoke_signed(seeds+bump)--> System Program create_account
result: PDA funded, sized, owner = your programClients pass the PDA pubkey they derived off-chain. On-chain you re-derive and compare keys (and usually the canonical bump) before creating or initializing. After create, write discriminators and fields so later instructions can reject empty or reinit attempts.
Signing with invoke_signed
invoke forwards an instruction using only external signers already present on the transaction. invoke_signed additionally tells the runtime: treat this program's PDA as a signer if the provided seed groups hash to that account's key.
// Conceptual seed tuple for signing
let seeds: &[&[u8]] = &[b"vault", owner.key.as_ref(), &[bump]];
invoke_signed(&ix, account_infos, &[seeds])?;Rules that never relax:
- Seed order, bytes, and bump must match derivation exactly.
- Only the program whose id was used in derivation can successfully sign that PDA.
- The CPI account metas must mark the PDA as a signer when the callee requires a signature (System transfer, token transfer with PDA authority, and so on).
Common uses: move SOL out of a vault PDA, authorize SPL Token transfers when the token account owner or delegate is a PDA, set or exercise mint authority held by a PDA, and create nested accounts under program control.
PDAs as authorities
An authority is whoever a program (Token, your app, a marketplace) checks before a privileged action. With PDAs, that "who" is program logic, not a cold wallet:
| Role | Pattern |
|---|---|
| Config / global state | Seeds like [b"config"] or [b"config", version] |
| Per-user profile | [b"user", user_pubkey] |
| Vault / escrow | [b"escrow", order_id] or buyer/seller pair |
| Mint authority | [b"mint_auth", mint] then Token mint_to via invoke_signed |
The program must gate every invoke_signed with business checks (state machine, expiry, correct signer of the user, correct remaining accounts). The PDA does not decide; your instruction handler does. A public helper that signs for arbitrary seeds is effectively a master key.
How the pieces interact in one flow
A typical escrow release:
- Client derives escrow PDA + bump with the same seeds the program documents.
- Transaction lists user, escrow PDA (writable), token accounts, Token Program, your program.
- Your program re-derives the PDA, checks owner and data, validates release conditions.
- It builds a token transfer instruction with the PDA as authority.
invoke_signedwith stored bump completes the transfer; failure reverts the whole transaction.
Derivation without verification is incomplete. Signing without condition checks is incomplete. Creation without rent and owner assign is incomplete. The blueprint is the closed loop.
Advanced Considerations & Applications
Seed design is API design. Namespaces (b"vault" vs b"meta") prevent collisions across account roles under one program. Include the binding identity (user, mint, market id) so one user's PDA cannot be substituted for another. Prefer fixed-width encodings for integers. Document seed tuples for every client language you ship (Rust, TypeScript with @solana/kit 7.0.0, etc.).
Canonical bumps and CU. Persist the bump in account data at init. Hot paths should not re-run a full find_program_address unless necessary. Reject client bumps that are valid but non-canonical so attackers cannot maintain parallel accounts that hash under the same logical seeds with a lower bump.
Ownership vs authority. The account owner field is still a program id (often yours, or Token for token accounts). A PDA may be the address of a data account your program owns, or the authority pubkey stored inside another program's account (for example mint authority). Do not confuse "PDA owns the account" with "PDA is listed as authority in Token state"; both appear in real designs, and the CPI targets differ.
Parallelism. Per-entity PDAs (one vault per user) keep writable locks narrow so Sealevel can run non-overlapping users together. A single global PDA for all balances serializes the protocol.
Upgrades and program id. PDAs are tied to program id. Upgradeable loaders keep program identity stable across bytecode upgrades when identity is the program account, but redeploying under a new program id orphans old PDAs. Treat seed schemes and program identity as long-lived contracts.
| Pattern | Strength | Weakness | Best fit |
|---|---|---|---|
| Global config PDA | One known address | Hot lock; single point of contention | Protocol parameters |
| Per-user PDA | Parallelism; clear isolation | More rent; longer account lists | Profiles, positions |
| Escrow PDA | Program-gated release | Stuck funds if logic freezes | Conditional trades |
| Mint-authority PDA | Automated supply rules | Bug mints forever | Managed token supply |
Common Misconceptions
- "A PDA is a different account type on-chain." It is a normal account at a special address. Same fields, same rent rules, same owner-writes model.
- "
find_program_addresscreates the account." It only computes the address and bump. Creation still needs System Program (or equivalent) funding and allocate/assign. - "Any bump that works is fine." Multiple bumps can theoretically be valid; production uses the canonical (highest) bump and stores it to prevent alternate-address attacks and CU waste.
- "The client can pass any seeds and the program should sign." Seeds must be bound by program-chosen prefixes and on-chain checks. User-controlled seed-only signing is a classic exploit class.
- "PDAs sign like wallets in the transaction message." Users do not attach a PDA signature. The program supplies seeds to
invoke_signed; the runtime marks the PDA as a signer for that CPI. - "If I verify the PDA address, I am done." Also check owner, discriminator/layout, and related accounts (token mint, ATAs). Address equality alone does not prove the full account graph is safe.
FAQs
What is a program-derived address in one sentence?
A PDA is a deterministic, off-curve account address derived from seeds and a program ID so the program can control it without a private key.
Why must PDAs be off the ed25519 curve?
So no ordinary keypair can produce signatures for that address. Control is reserved for the deriving program via invoke_signed.
What does find_program_address return?
The canonical PDA pubkey and the bump seed (highest valid from 255 downward) that makes the address off-curve for those seeds and program id.
What are the seed limits?
At most 16 seeds, each at most 32 bytes. Keep namespaces short and pack integers with fixed endianness.
What is the canonical bump?
The first (highest) bump find_program_address accepts. Store it at account init and require it for later verification and signing.
Should I call find_program_address on every instruction?
Prefer storing the bump and verifying with create_program_address or a single expected-key compare. Full finds cost more compute units on hot paths.
How do I create an account at a PDA?
Derive and verify the address, then CPI create_account (or Anchor init) with invoke_signed using the seed tuple and bump, funded by a payer to rent-exempt minimum for the data size.
Why does invoke_signed fail with MissingRequiredSignature?
Usually wrong seeds, wrong order, missing bump byte, wrong program id, or the CPI meta did not mark the PDA as a signer when required.
Can one instruction sign for multiple PDAs?
Yes. Pass multiple seed groups to invoke_signed, one group per PDA you need marked as a signer.
Who "owns" a PDA vault of SPL tokens?
The token account is owned by the Token Program; the vault PDA is typically the token account's owner authority. Your program signs transfers by proving PDA seeds.
Is a PDA the same as an account owner field?
No. Owner is always a program id in account metadata. A PDA is an address that may identify a data account or appear as an authority pubkey inside another program's state.
What is bump hunting?
Using a non-canonical but valid bump to open a parallel account under the same logical seed story when the program accepts any bump. Fix by enforcing the canonical bump.
How do clients discover PDA addresses?
They run the same seed recipe off-chain (Rust, or TypeScript via @solana/kit 7.0.0 helpers) with the deployed program id, then include the resulting pubkey in the transaction account list.
Can another program sign my PDA?
No. Only the program id used in derivation can supply successful signer seeds for that PDA. Other programs must CPI into yours if they need your PDA's authority.
What should every PDA instruction verify?
Re-derived address matches the account key, bump is canonical (or matches stored), owner and layout/discriminator are expected, and any authority or linked accounts in the graph are correct before invoke_signed.
Related
- PDA Basics - hands-on introduction to PDAs and seeds
- Deriving PDAs - seeds,
find_program_address, and bump mechanics - Canonical Bumps - store and enforce the highest valid bump
- PDA Account Creation - allocate, fund, and initialize PDA state
- PDAs as Authorities - vaults, mint authority, and escrow patterns
- PDA Security Pitfalls - verification gaps, bump hunting, and signing risks
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.