Program Derived Addresses (PDAs) are public keys that no private key corresponds to. They are derived deterministically from a set of seed bytes and a program ID, making them the canonical way to give a program ownership over on-chain state without a keypair. Quasar’s PDA implementation uses rawDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/blueshift-gg/quasar/llms.txt
Use this file to discover all available pages before exploring further.
sol_sha256 and sol_curve_validate_point syscalls directly, cutting the per-attempt compute cost from ~1,500 CU to ~544 CU compared to sol_try_find_program_address.
How PDA Derivation Works
A PDA is the SHA-256 hash ofseeds || bump_byte || program_id || "ProgramDerivedAddress" where the resulting 32 bytes fall off the Ed25519 curve. The derivation tries bump values from 255 down to 0 until an off-curve point is found.
verify_program_address (~200 CU) which only hashes once and checks the result against the known address, rather than iterating.
Declaring Seeds with #[seeds]
Attach a #[seeds] attribute to an #[account] struct to declare typed PDA seed specs for that account type. The macro generates a seeds(arg0, arg1, …) associated function:
(b"escrow", maker: Address) declares:
- a static byte slice prefix
b"escrow" - a dynamic argument
makerof typeAddress
Escrow::seeds(maker: &Address) function returns a seed spec that the address = constraint evaluates to verify (and if it’s an init, to derive) the PDA.
BUMP_OFFSET Fast Path
When the account struct contains a field named bump: u8, Discriminator::BUMP_OFFSET is automatically set to its byte offset within the account data. This lets verify_program_address (~200 CU) be used instead of based_try_find_program_address (~544 CU) when parsing already-existing accounts:
The address = Constraint
In a #[derive(Accounts)] struct, use address = <expr> to assert that the account’s public key equals the result of a seed spec:
init account the framework derives the PDA and stores the bump in ctx.bumps.escrow. For an existing account it reads the bump from account data (via BUMP_OFFSET) and calls verify_program_address.
PDA Functions
Quasar exposes three functions inquasar_lang::pda for working with PDAs directly:
verify_program_address
Verify that
expected equals sha256(seeds || program_id || "ProgramDerivedAddress"). Seeds must already include the bump byte. ~200 CU. Use when you have the bump stored.based_try_find_program_address
Iterate bump values 255→0, hashing each time and checking off-curve with
sol_curve_validate_point. Returns (Address, bump). ~544 CU for bump=255. Use for init paths.find_bump_for_address
Same iteration as above but replaces the
sol_curve_validate_point (~100 CU) with a keys_eq comparison (~10 CU). Use when the expected PDA address is already known.find_program_address_const
Compile-time PDA derivation using
const_crypto. Useful for deriving constant PDAs in const items or type-level computations.Direct Usage
CPI Signing with Stored Bumps
When a PDA must sign a CPI call, reconstruct the seed array from the stored bump and pass it viainvoke_signed:
invoke_signed accepts anything that implements CpiSignerSeeds — including [Seed<'_>; N] arrays and [Seed<'_>] slices.
Full Escrow PDA Workflow
The
address = constraint on an init account uses based_try_find_program_address which includes the on-curve check and is therefore safe even before the account exists on-chain. Non-init paths use find_bump_for_address which skips the on-curve check — correctness relies on the account already being in the transaction, meaning it exists on-chain and was therefore created as a valid off-curve PDA.