Skip to content

Deriving a PDA — Seeds & the Bump

The previous page, Program Derived Addresses (Off-Curve Addresses), established what a PDA is: an address deliberately chosen to lie off the ed25519 curve, so that no private key can exist for it and a program — not a human with a keypair — becomes its authority. That page told you the property. It did not tell you how you actually get such an address, because you cannot just wish an address off-curve. You have to compute one.

This page is the algorithm. It is short and mechanical, and once you have it, the whole PDA story is closed: given a program id and a handful of seed bytes, there is a fully deterministic procedure that produces the same off-curve address every time, on any machine, with no network round-trip. That determinism is the entire point — it is how a client on a laptop and a program running in the cluster independently agree on where an account lives without ever talking to each other.

A PDA is derived from exactly two things you supply:

  • A list of seeds — an ordered list of byte-strings. Each seed is arbitrary bytes: a literal like b"vault", a user’s public key, a u64 counter written as 8 little-endian bytes, anything. The order and exact bytes matter; change one byte and you get a different address.
  • A program id — the 32-byte public key of the program that will own the PDA. This is what ties the address to your program: the same seeds under a different program id produce a completely different PDA.

The runtime concatenates the seeds, appends the program id (and a fixed marker string), and hashes the whole thing with SHA-256 to produce a candidate 32-byte address.

The derivation input, laid out byte-for-byte
─────────────────────────────────────────────
seed[0] ‖ seed[1] ‖ … ‖ seed[n] ‖ bump ‖ program_id ‖ "ProgramDerivedAddress"
└────────────── your seeds ──────────┘ └1 B┘ └── 32 bytes ─┘ └── fixed marker ──┘
SHA-256
candidate = 32 bytes ──► is this ON the ed25519 curve?

The fixed marker string ("ProgramDerivedAddress") is domain separation: it guarantees a PDA hash can never accidentally collide with some other SHA-256 use in the protocol. You never supply it; the runtime always appends it.

The on-curve test: why we might reject a hash

Section titled “The on-curve test: why we might reject a hash”

A SHA-256 output is 32 uniformly random-looking bytes. That is exactly the size of an ed25519 public key. So the candidate address we just computed might, by pure chance, be a valid point on the ed25519 curve — a genuine public key. And that is precisely the outcome we must forbid.

Recall the rule from the previous page: an address that is a valid curve point could have a matching private key. If a PDA landed on the curve, then somewhere in the space of private keys there is (in principle) a secret that signs for it — which would defeat the whole guarantee that only the program can authorize the account. So the derivation checks the candidate against the curve equation:

  • Off-curve → the 32 bytes are not a valid ed25519 point. No private key can exist. Accept it — this is a legal PDA.
  • On-curve → the 32 bytes are a valid ed25519 point. A private key could exist. Reject it and try again.
candidate 32 bytes
on the ed25519 curve?
┌────┴────┐
yes no
│ │
REJECT ACCEPT ── this is a valid PDA (off-curve, no private key)
(a key could exist)

Roughly half of all 32-byte values decode to a valid curve point and half do not, so on any given attempt there is about a 50% chance the candidate is off-curve and usable. Which means: we may need more than one attempt. That is what the bump is for.

The derivation makes the “try again” step deterministic by appending one extra byte to the seed list — the bump seed. It starts at 255 and counts down.

attempt with bump = 255 → SHA-256(seeds ‖ 255 ‖ program_id ‖ marker) → on-curve? yes → next
attempt with bump = 254 → SHA-256(seeds ‖ 254 ‖ program_id ‖ marker) → on-curve? yes → next
attempt with bump = 253 → SHA-256(seeds ‖ 253 ‖ program_id ‖ marker) → on-curve? NO → STOP
PDA found; canonical bump = 253

Because each bump value changes the hash input, each attempt produces a fresh, independent 32-byte candidate with its own ~50% chance of being off-curve. Starting from 255 and decrementing, the loop almost always finds an off-curve address within a handful of steps; needing many tries is astronomically unlikely, and if the search ever exhausted every bump from 255 down to 0 it would fail (this effectively never happens for real seed sets).

The function that runs this loop for you is find_program_address. You hand it seeds and a program id; it returns the PDA and the bump byte it stopped at.

Here is the subtlety that trips people up. There is not one off-curve address for a given seed set — there could be several, one for many of the bump values that happen to land off-curve. If a client used bump 253 and the program independently searched and used bump 251, they would compute two different addresses for “the same” logical account, and the account would effectively be in two places.

So the protocol fixes a convention: the canonical bump is the first (highest) bump, counting down from 255, that yields an off-curve address. find_program_address always returns that one, because it searches top-down and stops at the first success. A program that wants a single, unambiguous home for an account must always use the canonical bump — that is what makes the PDA deterministic and unique. Accepting a non-canonical bump is a real security bug: it lets a caller point your program at a different off-curve address than the one your validation logic assumed.

find_program_address vs create_program_address

Section titled “find_program_address vs create_program_address”

There are two derivation functions, and the difference is exactly who supplies the bump:

find_program_addresscreate_program_address
Inputsseeds + program idseeds + bump + program id
Runs the search loop?Yes — decrements from 255No — one single attempt
Returns(pda, canonical_bump)pda (errors if on-curve)
Costmany SHA-256 hashes (the loop)exactly one SHA-256 hash
Guarantees canonical?Yes, by constructionOnly if you feed it the canonical bump

The practical pattern falls right out of that table:

  1. Off-chain, once: the client calls find_program_address to discover the PDA and its canonical bump.
  2. Store or pass the bump: the bump byte is saved (often inside the account itself, or passed in as instruction data).
  3. On-chain, cheaply: the program calls create_program_address with the known bump — a single hash instead of a loop — and re-checks that it matches the stored canonical bump.

This matters because on-chain work costs compute units. The search loop in find_program_address can burn many hash operations; doing it inside every instruction is wasteful when the answer never changes. Computing the bump once off-chain and then verifying it on-chain with a single hash is the standard optimization — the same “compute once, remember the answer” move you make everywhere else in systems.

A concrete example: client and program agree without talking

Section titled “A concrete example: client and program agree without talking”

Put it together with a made-up but realistic case: a program that keeps one vault account per user.

  • Program id: Vau1t... (32 bytes, fixed and public).
  • Seeds: the literal b"vault" and the user’s wallet public key, e.g. Alice’s 9WzD....

Anyone — Alice’s browser, a backend service, the program itself — can compute:

pda, bump = find_program_address(
seeds = [ b"vault", alice_pubkey ],
program_id = Vau1t...
)
→ pda = <deterministic 32-byte off-curve address>
→ bump = <the canonical bump, e.g. 254>

Because the inputs are identical everywhere, the output is identical everywhere. Alice’s client computes the vault address to build a transaction that references it; the program, when it runs, re-derives the same address to confirm the account it was handed is really Alice’s canonical vault and not some look-alike. Neither side had to ask the other where the account lives — the derivation is a shared pure function, so both sides land on the same answer independently.

Change any input and you get a different, unrelated PDA:

[ b"vault", alice_pubkey ] under Vau1t... → Alice's vault
[ b"vault", bob_pubkey ] under Vau1t... → Bob's vault (different seed)
[ b"config", alice_pubkey ] under Vau1t... → a config account (different seed)
[ b"vault", alice_pubkey ] under Other... → unrelated PDA (different program id)

That is how a single program addresses an unbounded number of accounts — one per user, one per market, one per (user, market) pair — without a registry: the address itself encodes which logical slot it is. This is the account-model idea from Part 1 taken to its conclusion. The flat Pubkey → Account map has no directory, yet a program can still find “Alice’s vault” instantly, because the key is derived, not looked up.

Under the hood — why seeds are length-checked and order-sensitive

Section titled “Under the hood — why seeds are length-checked and order-sensitive”

Two rules shape derivation. First, each seed is at most 32 bytes and there is a cap on the number of seeds (16). This bounds the hash input. It does not, however, eliminate every ambiguity: because seeds are simply concatenated (not length-prefixed) before hashing, different seed groupings that concatenate to the same bytes collide — [b"ab", b"cd"] and [b"abcd"] derive the same PDA. This is a known property of the construction, so it is on you to choose seed layouts that cannot be reinterpreted (e.g. use fixed-width fields or an explicit separator seed). Second, seeds are hashed in order with the program id and marker appended last, so [a, b] and [b, a] — and the same seeds under two program ids — never collide. Determinism here is not a nicety; it is the safety property the whole PDA scheme rests on.

  • Why does it exist? Because a program has no keypair, so it cannot own an ordinary address the way a human does. The derivation manufactures an address that is provably keyless (off-curve), giving a program a home for state that only it can write to.
  • What problem does it solve? Deterministic, registry-free addressing. A client and a program must agree on where an account lives with no coordination and no lookup table — the derivation is the shared pure function that lets them.
  • What are the trade-offs? The on-curve rejection means derivation is a search, not a formula, so it costs a variable number of hashes; and the canonical-bump convention adds a validation obligation that, if skipped, becomes the bump-canonicalization vulnerability class.
  • When should I avoid it? When a plain keypair account is simpler and a human (or an off-chain signer) should hold the authority. PDAs are for program-owned state and for signing on a program’s behalf — not a default for every account.
  • What breaks if I remove it? Programs lose the ability to own state and to sign for it. Without deterministic PDAs there is no way for a program to control accounts without a private key, and no way for independent parties to agree on account addresses — the account model collapses back into needing a registry.
  1. What are the two inputs you supply to a PDA derivation, and what does the runtime append that you do not supply?
  2. Why is a candidate address sometimes rejected? Tie your answer to what “off-curve” guarantees about private keys.
  3. Where does the bump seed start, which direction does it move, and why does the loop terminate quickly instead of grinding forever?
  4. Define the canonical bump precisely, and explain why a program that accepts a non-canonical bump has a security bug.
  5. find_program_address and create_program_address differ in exactly one input. What is it, and why is the standard pattern “find once off-chain, verify on-chain”?
Show answers
  1. You supply a list of seeds (ordered byte-strings) and a program id (32 bytes). The runtime appends a bump byte (during the search) and a fixed marker string ("ProgramDerivedAddress") for domain separation, then hashes everything with SHA-256. You never supply the marker.
  2. Because a SHA-256 candidate might, by chance, be a valid ed25519 curve point — i.e. a real public key. If it is on-curve, a matching private key could exist, which would let someone other than the program sign for the account. Only an off-curve result guarantees no private key exists, so on-curve candidates are rejected.
  3. The bump starts at 255 and decrements. Each bump value changes the hash input, so each attempt is an independent ~50% coin flip at landing off-curve; the probability of needing many tries falls off geometrically (needing >8 attempts is well under 1%), so the search almost always finishes in a few steps.
  4. The canonical bump is the first (highest) bump, counting down from 255, that yields an off-curve address — exactly what find_program_address returns. A program that accepts any other valid bump can be pointed at a different off-curve address than its checks assume, letting an attacker create look-alike accounts or bypass validation (the “bump seed canonicalization” bug class).
  5. They differ in who supplies the bump: find_program_address searches for the canonical bump (a loop of hashes and returns (pda, bump)); create_program_address takes the bump as input and does a single hash. The standard pattern computes the bump once off-chain with find_program_address (paying the loop), stores/passes it, then on-chain uses create_program_address with the known bump — a single hash — and asserts it is the canonical value. This saves the program from re-running the search’s compute cost on every instruction.

Next, the part wraps up: Revision — Cryptography & Keys compresses keypairs, addresses, signatures, and PDAs into the minimum you carry into transactions and programs.