new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Solana & Anchor Auditor · all subjects

PDAs and bumps

6 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Is a non-canonical bump accepted for this PDA?

`find_program_address` returns the CANONICAL bump — the first value from 255 downward that lands off the ed25519 curve. But `create_program_address` accepts any valid (seeds, bump) pair, and a given seed set can have multiple valid bumps. If a program derives an address with a user-supplied bump instead of re-running `find_program_address` (or comparing against the canonical bump stored at init), an attacker can create a SECOND account at the same seeds with a different bump — splitting state the protocol assumed was unique (two "global" configs, two vaults for one user). Fix: in Anchor, store the bump at init and use `seeds = [...], bump = state.bump`; in raw code, recompute with `find_program_address` and compare. Detection signal: `bump` taken from instruction args or unconstrained in `#[account(seeds, bump)]` without a stored value.

Are the PDA seeds themselves fully validated?

Checking that an account IS a PDA of your program is not enough — the seeds encode WHICH PDA, and partial seed validation is a whole bug class. Common misses: deriving with `find_program_address(&[b"vault"], ...)` and forgetting the `user.key()` seed, so every user maps to one shared vault; accepting a `vault` account whose seeds bind it to `mint_A` in an instruction operating on `mint_B`; seeds that include an attacker-controlled field (a "name" string) allowing collision or pre-computed squatting on expected addresses. Rule: list every domain separator the PDA conceptually depends on (user, mint, market, epoch) and confirm each appears in the `seeds = [...]` constraint with the values coming from validated accounts, not raw instruction arguments. In Anchor, `seeds` + `bump` re-derives and compares the address — but only with the seeds you actually wrote.

Is one PDA reused across security domains?

PDA sharing: using the same PDA as authority for multiple unrelated resources — e.g., one program PDA that is simultaneously the token-vault authority, the mint authority for a receipt token, and the upgrade authority of an aux program. Any code path (or CPI to a compromised/malicious program) that can make the PDA sign for ONE purpose can be leveraged for the others; blast radius multiplies silently. It also breaks the principle that seeds encode intent: `seeds = [b"vault", market]` vs `seeds = [b"mint_auth", market]` cost nothing. Rule: one PDA per authority role, namespaced by seed prefix; never let a PDA that signs user-triggered CPIs also hold mint/freeze/upgrade authority unless that exact composition is the design. Audit move: enumerate every account whose authority/owner field equals a program PDA and check whether one PDA appears in more than one role.

Can this PDA be front-run at initialization?

PDA addresses are deterministic, so anyone can compute them — and several init flows are raceable. If initialization seeds don't include the initializer's key (e.g., `seeds = [b"config"]` for a singleton, or `[b"stake", mint]` for a per-mint pool), an attacker can initialize the account FIRST with themselves as stored authority, then the protocol either fails to launch or — worse — the protocol's later code treats the attacker-initialized account as valid because only seeds/bump are checked, not the stored authority. Variants: `init_if_needed` on an account an attacker pre-created via raw system instructions (Anchor checks discriminator, so pre-creation with the RIGHT discriminator by your own program is the real risk). Rule: singleton PDAs should be initialized by a hardcoded admin or include the authority in seeds; verify stored authority fields, not just derivability.

Can two PDAs collide when seeds aren't domain-separated?

A PDA is just `hash(seeds || program_id || bump)` — pure math with no registry. If a program derives conceptually different accounts from overlapping or attacker-controlled seeds, collisions and hijacks follow. Failure shapes: two roles derived from the same seed set (`find_program_address(&[b"authority"])` for both a config authority and a vault authority → one address, confused privileges); seeds built from user-supplied strings/keys with no domain prefix, so an attacker crafts inputs that land on a victim's expected address; and code that never re-validates derivation inputs, trusting a passed account because "it's a PDA of ours". Rule: every PDA role gets a unique hardcoded seed prefix (`b"vault"`, `b"config"`, `b"stake"`); all variable seeds come from validated accounts, never raw instruction bytes; and the program re-derives and compares the address (Anchor `seeds` + `bump`) on every use.

Is a treasury PDA that never signs still safe receiving lamports?

Often yes — receiving value is inherently safer than spending it, and the audit bar differs per direction. A treasury/vault PDA that only RECEIVES lamports or tokens needs no signer check (it can't sign anyway); what it needs is ADDRESS VALIDATION: the recipient must be re-derived from its seeds (Anchor `seeds` + `bump`) or pinned to a hardcoded/stored address, so deposits can't be redirected to an attacker's lookalike. Hijack requires spoofing the derivation: if any seed comes from caller input without validation, the attacker derives a "treasury" whose spending path they control. The spending direction is where full rigor applies: debiting the PDA's lamports (only your program, as owner, can) or moving its tokens via `invoke_signed` demands authority checks, recipient validation, and usually governance gating. Audit rule: receive-only PDA → verify derivation integrity (medium if missing); spend path → verify authority + destination (critical if missing).

Give your agent this brain