PDA Derivation

Summary

PDAs are derived by hashing seeds + program ID + bump via SHA-256 until the result is off the Ed25519 curve. The canonical bump is the first value that produces an off-curve address. Max 16 seeds, Max 32 bytes per seed.

Background

Solana Keypair values are points on the Ed25519 curve. A keypair consists of a public key (used as the account address) and a secret key (used to produce signatures). Anyone with the secret key can sign transactions for that address.

Two accounts with on-curve addressesTwo accounts with on-curve addresses

A PDA is intentionally derived to fall off the Ed25519 curve. Because it is not a valid curve point, no secret key exists, and no external party can produce a signature. Only the deriving program can authorize operations on the PDA through invoke_signed.

Off Curve AddressOff Curve Address

PDA vs keypair accounts

PropertyKeypair accountPDA account
Address typeOn Ed25519 curveOff Ed25519 curve
Has private keyYesNo
Can sign transactionsYes (with private key)No
Can sign during CPINo (unless signature included in transaction)Yes (via invoke_signed)
DerivationGenerate Ed25519 keypairDeterministic from seeds + program ID
Typical useUser wallets, Program IDProgram-owned data accounts

Optional Seeds

The optional seeds are user-defined byte strings that serve as inputs to the PDA derivation. They create unique, deterministic addresses scoped to a program. For example, using ["user", user_pubkey] as seeds derives a different PDA for each user.

Seeds must follow these constraints:

  • Maximum 16 seeds per derivation (MAX_SEEDS)
  • Maximum 32 bytes per seed (MAX_SEED_LEN)

Bump Seed

The bump seed is a single byte (0-255) appended to the optional seeds during derivation. find_program_address searches from 255 down to 0, calling create_program_address with each value until the result falls off the Ed25519 curve. The first value that succeeds is the canonical bump.

Programs should always use the canonical bump to ensure a unique, deterministic mapping from seeds to address.

Always use the canonical bump when deriving PDAs. Using a non-canonical bump creates a second valid address for the same seeds, which can lead to vulnerabilities where an attacker substitutes a different account than expected.

PDA DerivationPDA Derivation

Derivation Algorithm

The PDA derivation is implemented in the SDK's create_program_address function. The algorithm works as follows:

  1. Validate that the number of seeds does not exceed MAX_SEEDS (16) and no individual seed exceeds MAX_SEED_LEN (32 bytes). If either check fails, return PubkeyError::MaxSeedLengthExceeded.
  2. SHA-256 hash all seeds, the program ID, and the string "ProgramDerivedAddress" together to produce a 32-byte result.
  3. Check if the result is a valid point on the Ed25519 curve.
  4. If the result IS on the curve, return PubkeyError::InvalidSeeds (the address would have a corresponding private key, which violates the PDA security property).
  5. If the result is NOT on the curve, return it as the PDA.

Compute Unit Costs

The on-chain syscall for create_program_address charges 1,500 CUs per call.

The try_find_program_address syscall charges 1,500 CUs on entry (before the loop), then an additional 1,500 CUs for each failed bump attempt within the loop.

Common Seed Patterns

Seeds are application-specific. Common patterns include:

PatternSeedsUse case
Global singleton["global"]Single program-wide config account
Per-user account["user", user_pubkey]One account per user per program
Per-user-per-entity["vault", user_pubkey, mint_pubkey]Token vaults, per-user-per-token
Counter / sequential["order", user_pubkey, &order_id.to_le_bytes()]Sequential records per user

Seeds are concatenated before hashing, so ["ab", "cd"] and ["abcd"] produce the same PDA. Use fixed-length seeds or a separator to avoid collisions. For example, ["ab", "-", "cd"] is unambiguous.

Examples: Derive a PDA

Deriving a PDA computes an address only. It does not create an on-chain account at that address. The account must be explicitly created through a separate instruction (typically create_account via CPI).

The Solana SDKs provide functions for PDA derivation. Each function takes:

  • Program ID: The address of the program used to derive the PDA. This program can sign on behalf of the PDA.
  • Optional seeds: Predefined inputs such as strings, numbers, or other account addresses.
SDKFunction
@solana/kit (TypeScript)getProgramDerivedAddress
@solana/web3.js (TypeScript)findProgramAddressSync
solana_sdk (Rust)find_program_address

The examples below derive a PDA using the Solana SDKs. Click ▷ Run to execute the code.

Derive a PDA with a string seed

The example below derives a PDA using a program ID and an optional string seed.

import { Address, getProgramDerivedAddress } from "@solana/kit";
const programAddress = "11111111111111111111111111111111" as Address;
const seeds = ["helloWorld"];
const [pda, bump] = await getProgramDerivedAddress({
programAddress,
seeds
});
console.log(`PDA: ${pda}`);
console.log(`Bump: ${bump}`);
Console
Click to execute the code.

Derive a PDA with an address seed

The example below derives a PDA using a program ID and an optional address seed.

import {
Address,
getAddressEncoder,
getProgramDerivedAddress
} from "@solana/kit";
const programAddress = "11111111111111111111111111111111" as Address;
const addressEncoder = getAddressEncoder();
const optionalSeedAddress = addressEncoder.encode(
"B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address
);
const seeds = [optionalSeedAddress];
const [pda, bump] = await getProgramDerivedAddress({
programAddress,
seeds
});
console.log(`PDA: ${pda}`);
console.log(`Bump: ${bump}`);
Console
Click to execute the code.

Derive a PDA with multiple seeds

The example below derives a PDA using a program ID and multiple optional seeds.

import {
Address,
getAddressEncoder,
getProgramDerivedAddress
} from "@solana/kit";
const programAddress = "11111111111111111111111111111111" as Address;
const optionalSeedString = "helloWorld";
const addressEncoder = getAddressEncoder();
const optionalSeedAddress = addressEncoder.encode(
"B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address
);
const seeds = [optionalSeedString, optionalSeedAddress];
const [pda, bump] = await getProgramDerivedAddress({
programAddress,
seeds
});
console.log(`PDA: ${pda}`);
console.log(`Bump: ${bump}`);
Console
Click to execute the code.

Iterating All Bumps

The following examples show PDA derivation using all possible bump seeds (255 to 0), illustrating how find_program_address returns the canonical bump:

Kit example is not included because the createProgramDerivedAddress function isn't exported.

import { PublicKey } from "@solana/web3.js";
const programId = new PublicKey("11111111111111111111111111111111");
const optionalSeed = "helloWorld";
// Loop through all bump seeds (255 down to 0)
for (let bump = 255; bump >= 0; bump--) {
try {
const PDA = PublicKey.createProgramAddressSync(
[Buffer.from(optionalSeed), Buffer.from([bump])],
programId
);
console.log("bump " + bump + ": " + PDA);
} catch (error) {
console.log("bump " + bump + ": " + error);
}
}
Console
Click to execute the code.
bump 255: Error: Invalid seeds, address must fall off the curve
bump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6X
bump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4y
bump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHH
bump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdP
bump 250: Error: Invalid seeds, address must fall off the curve
...
// remaining bump outputs

In this example, bump 255 produces an on-curve address and fails. The first valid bump is 254, making it the canonical bump.

Is this page helpful?

सामग्री तालिका

पृष्ठ संपादित करें

द्वारा प्रबंधित

© 2026 सोलाना फाउंडेशन। सर्वाधिकार सुरक्षित।
जुड़े रहें