Intermediate · LESSON 03 OF 06
3. Program-derived addresses
By the end, you can design deterministic state addresses without inventing private keys.
Read the workflow
The program identifier and seed components are hashed to derive an off-curve address. Variable-length components need explicit boundaries so different logical tuples cannot collapse into the same seed bytes. Canonical bump search selects the expected address. The result has no private key; derivation does not create an account or grant a user permission.
- NAMESPACE
- WALLET + COURSE
- CANONICAL SEEDS
- UNIQUE CLAIM PDA
FIELD NOTE 01
Deterministic addressing
A program-derived address is derived from seeds, a program ID, and a bump. It is off the Ed25519 curve and has no corresponding private key. The runtime can grant signer privilege for that address to its deriving program during an appropriately formed cross-program invocation.
FIELD NOTE 02
Address design is product design
For a learner credential, a seed namespace, learner public key, course identifier, and revision can define one deterministic claim address. This makes duplicate detection part of the account model. Validate seed lengths and encode variable input unambiguously. Creating the address and creating an account at that address are separate operations.
FIELD NOTE 03
Keep authority explicit
A predictable address is not a password. Everyone can derive it. Authorization comes from program checks and required signatures. Validate the expected derivation rather than accepting an arbitrary account merely because its data looks plausible.
FIELD NOTE 04
Seeds are a public address schema
A program-derived address is deterministically computed from a program ID, a sequence of seed bytes, and a bump that keeps the address off the Ed25519 curve. No private key exists for a PDA. A program can authorize it during a cross-program invocation by signing with the exact seeds and bump, which is why seed design defines a real authority boundary. Seeds are concatenated before hashing: for example, the tuples ["ab", "cd"] and ["abcd"] produce the same seed bytes if no boundary is encoded. Use a fixed domain prefix and unambiguous encodings such as fixed-width fields or explicit lengths. Validate the canonical bump and all user-controlled seeds. Document the byte-level formula as part of the program interface, then test expected derivations, wrong users, wrong mints, ambiguous inputs, and attempts to substitute another program’s PDA.
FIELD NOTE 05
Specify bytes, boundaries, and domain
A seed is a byte string, so specify the encoding of every input, not just its label. Use a fixed domain prefix to distinguish this account family from other addresses in the same program. Prefer fixed-size public-key bytes and bounded identifiers. Seeds are concatenated before hashing: ["ab", "cd"] and ["abcd"] therefore produce the same bytes unless your schema encodes their boundary. Use fixed widths, explicit lengths, or another injective encoding. Solana allows at most 16 seed slices in one derivation, each no longer than 32 bytes; the bump is appended as a seed, so count it in the 16. Encode oversized values deliberately across multiple bounded seeds without losing boundaries.
FIELD NOTE 06
Work a badge address from inputs to validation
Use a versioned schema such as [b"badge/v1", learner_pubkey_as_32_bytes, level_as_one_byte, revision_digest_as_32_bytes]. Each item is one bounded seed slice; the derivation helper appends the canonical bump as another slice. The prefix separates this account family and gives the byte layout a version. The client can calculate the address before it asks a program to act, but derivation alone neither creates the account nor authorizes a change. In Anchor, a typed account constraint can check the supplied address against these seeds and canonical bump, while `has_one` and field constraints compare the stored learner, level, and revision to the request. Those checks still do not replace the instruction's business authorization or account-creation rules. Changing the level or revision intentionally changes the address, so discovery and migration behavior must be part of the product policy.
FIELD NOTE 07
A public derivation is not private data
Anyone who knows the program ID and seed values can derive the PDA. Hashing or deriving an address does not conceal the learner, course, or revision when those inputs are public or enumerable. If a public account would reveal a sensitive relationship, do not put sensitive fields into its seeds and do not claim that an opaque-looking address provides privacy. For this academy, the public wallet address and level are already visible when a credential is minted, while email, legal name, score, and answer choices must stay off chain.
FIELD NOTE 08
Make canonical bumps part of validation
The SDK searches bump values in descending order and returns the first value that produces an off-curve address: the canonical bump. Other bump values can also yield valid off-curve addresses for the same application seeds. If a program accepts any such address, one logical record can have multiple PDAs, undermining one-account-per-learner or one-time-claim assumptions. Re-derive and validate the canonical address on chain; Anchor’s `seeds` and `bump` constraints are one way to enforce the expected relation. A bump is public and is not authorization. A derived address is also not an initialized account: account creation requires a separate instruction, payer, storage funding, and owner assignment. The lab tests address math only; local program execution and negative tests are needed to verify on-chain account rules.
MAKE THE CONNECTION
A detail worth keeping
Every seed scheme has implications for uniqueness, privacy, and migration. Public keys and clear course labels in seeds can reveal relationships through account enumeration. Prefer deterministic but intentionally designed seed inputs, and do not mistake hashing public inputs for confidentiality.
Work through an example
A unique claim address derived from a namespace, wallet, tier, and revision distinguishes a retake or curriculum update if policy permits it. Define what a retake means before changing the seed: changing it without policy could accidentally let the same level mint again.
#[derive(Accounts)]
#[instruction(level: u8, revision: [u8; 32])]
pub struct ReadBadge<'info> {
pub learner: Signer<'info>,
#[account(
seeds = [b"badge/v1", learner.key().as_ref(), &[level], revision.as_ref()],
bump,
has_one = learner,
constraint = badge.level == level,
constraint = badge.revision == revision
)]
pub badge: Account<'info, BadgeRecord>,
}Specify the exact seed byte schema for one badge per learner per level per curriculum revision. Include a fixed domain prefix, fixed-width wallet bytes, normalized level and revision encodings, unambiguous boundaries, and canonical bump policy. Demonstrate with fictional bytes why ["ab", "cd"] collides with ["abcd"] without boundary encoding, then show an injective encoding. Count the appended bump within the maximum 16 seed slices and keep each slice at or below 32 bytes. Explain how a noncanonical bump can create an alternate record address and name the on-chain check that rejects it. Use no real learner data.
GUIDED PRACTICE · INTERMEDIATE
Do the work, one step at a time
Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Unless a lesson explicitly says “read-only Devnet,” use paper or a local test and do not connect to a public network.
- Download and unzip PDA workbook 01; inspect its seed tuple before running code.
- Run npm ci with Node.js 20 or later to install the pinned Solana Kit dependency.
- Run npm test and explain how the suite checks stable derivation and learner/course/program separation.
- Derive a sample PDA using a public program address, public learner address, and lowercase course slug.
- Reject uppercase, Unicode, empty, and over-32-byte seed values; explain which authorization checks still belong on chain.
- Optionally compare the same fictional typed seeds with `solana find-program-derived-address` after checking the installed CLI version; this read-only derivation does not create an account or submit a transaction.
Before you move on, check your reasoning
- Specify exact seed order and byte encoding.
- Check the program-derived address for two fictional learners.
- Explain why knowing a claim address does not authorize changing it.
OPEN SELF-CHECK · UNGRADED
Pause and test your reasoning
Several bumps produce viable off-curve addresses for the same seeds. Which address should a canonical PDA design use?
- The canonical bump returned by the standard derivation for those seeds and program ID.
- Any bump supplied by an untrusted caller, because every off-curve result is interchangeable.
- A random on-curve public key stored beside the PDA.
Reveal one best answer and its explanation
One best answer: The canonical bump returned by the standard derivation for those seeds and program ID.
A PDA is derived from seeds, a program ID, and a bump that yields an off-curve address. Use the canonical derivation consistently and validate it on-chain; accepting arbitrary alternatives can create multiple addresses for one logical record.
Sources: Program Derived Addresses (PDAs) ↗ · Program Derived Address with Anchor ↗
This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.
Scored checkpoints and level exams are online and require a free account. They are not included in this pack.
Optional lab files
These files are included with the pack. Follow the safety and toolchain notes in the lesson before running a lab; a Devnet read requires an internet connection.
READ THE SOURCE
Go deeper in the official docs
Reviewed on 2026-09-28. Protocol documentation can change; check the linked page’s current version when you reconnect.