Expert · LESSON 02 OF 06
2. Authority, signatures, and replay resistance
By the end, you can design a solana instruction authorization that binds the intended action and cannot be replayed.
Optional reading history is saved only in this browser. It is not a scored checkpoint or exam result.
In this lesson7 field notes · practice · sources · checkpoint
Read a text version of this diagram
A signed action is checked against the supplied account permissions and freshness rules before business logic runs. A status cache can reject stale or repeated message identifiers, while a claim receipt prevents the same eligibility from being consumed twice. The protected action and receipt update should commit atomically or both roll back on failure.
- SIGNED ACTION + ACCOUNT METAS
- MESSAGE AGE + STATUS CACHE
- CLAIM + RECEIPT CHECK
- ATOMIC ACTION OR REJECT
Separate identity from permission
A public key identifies an account; it does not grant every program permission to act for that account. An instruction should name the signer whose approval is required and the exact state transition that signer authorizes. A connected wallet, a copied address, or a signature from a different message is not equivalent to transaction approval. Start by listing the actor, protected asset, operation, and authority for each protocol action. If the expected signer is a PDA, explain which program derives and signs for it.
Validate the complete instruction context
A program receives an instruction plus account references supplied by the caller. Verify required signatures and writable permissions, then check each account’s owner, address, data type, and relationship to the other accounts. For example, a token withdrawal should validate the vault, mint, destination, and authority together. A correct signature over the wrong destination is still the wrong action. Derive canonical PDAs from a documented seed schema and reject caller-provided alternatives that do not match.
Bind signed messages to one purpose
When a protocol accepts an off-chain authorization, include an unambiguous domain, intended program or application, cluster, action, source state, recipient, amount or level, expiry, and unique identifier. Use a canonical byte representation and a versioned schema so fields cannot be reordered or interpreted differently. Domain separation helps prevent a message created for one application or environment from being accepted by another. Never sign a vague statement such as “approved” without defining what is approved and by whom.
Make one-time use durable
An expiry limits how long a message remains valid but does not make it single-use. A caller can replay an unexpired message unless the system records consumption. Store a nonce, sequence number, or deterministic receipt account and change it atomically with the authorized action. A PDA can provide a canonical address for a claim or receipt, but address derivation alone does not enforce policy: the program must check the expected seeds, signer, stored state, and transition. Define whether a nonce is per wallet, per action, or global.
Understand transaction freshness and application replay
The validator checks transaction age and a recent status cache that rejects a message hash it has already processed. A recent blockhash limits how long that exact message remains valid; a durable nonce provides a different lifetime mechanism. Current Solana docs warn durable nonces may be deprecated, so recheck their support before relying on them. This transaction layer is not a business receipt. Rebuilding the same voucher action with a new blockhash changes the signed transaction message, so transaction-level duplicate detection cannot establish that the voucher was already consumed. The program needs durable one-time-use state and must update that state atomically with the protected action. If a wallet prompt or RPC response times out, inspect the original signature and account state before preparing a replacement.
Handle delegated and composed authority
Signer and writable privileges passed to a CPI extend to the callee, but the callee cannot escalate beyond the privileges it received. With `invoke_signed`, the caller adds only a PDA derived under its own program ID to the signer set. Verify the callee program address and constrain every account forwarded to it; an arbitrary executable account must not decide where protected assets go. If a protocol accepts an Ed25519 signature-verification instruction, bind the exact verified public key and message bytes to the action and accounts being executed. The mere presence of a successful verification instruction does not authorize arbitrary state changes.
Test mutations at the enforcement layer
Build positive and negative tests from the authorization schema. Change the signer, program domain, cluster, amount, recipient, nonce, expiry, and account order one at a time. Attempt duplicate use, a wrong PDA bump, and an unintended CPI target. For every rejection, assert the protected state remains unchanged and the nonce is not consumed incorrectly. A server-side unit test can validate service policy, but only an execution test at the program boundary shows that an untrusted caller cannot bypass the on-chain check.
A detail worth keeping.
Treat replay as two separate questions. First, has this exact transaction message already been processed or expired? Second, has this economic authorization already been consumed by any transaction? A status cache answers only the first question for its recent window. The program must answer the second with a receipt PDA, monotonic sequence, or another durable uniqueness rule scoped to the issuer, action, and protected object.
Work through an example
In the excerpt, `init` gives a claim one canonical receipt address, while the escrow constraint binds the claim ID to stored state. The handler still verifies the signed claim and expiry, transfers only to the stored beneficiary, and writes the receipt in the same atomic instruction. If the transfer fails, receipt creation rolls back too. Do not close or reset a receipt in a way that makes the same claim ID usable again; test this across retries and after any account cleanup.
#[derive(Accounts)]
#[instruction(authorization_id: [u8; 32])]
pub struct Release<'info> {
#[account(
mut,
has_one = beneficiary,
constraint = escrow.authorization_id == authorization_id
)]
pub escrow: Account<'info, Escrow>,
#[account(
init,
payer = beneficiary,
space = 8 + ReleaseReceipt::INIT_SPACE,
seeds = [b"release", escrow.key().as_ref(), authorization_id.as_ref()],
bump
)]
pub receipt: Account<'info, ReleaseReceipt>,
#[account(mut)]
pub beneficiary: Signer<'info>,
pub system_program: Program<'info, System>,
}
Design a signed release authorization for a fictional token escrow. Specify its canonical schema and byte encoding, trusted signer, domain and program, escrow, mint, recipient, amount, expiry, and unique claim ID. Show how one program instruction validates and consumes the claim while moving the tokens. Then distinguish replaying the identical transaction from resubmitting the same claim in a transaction with a new blockhash.
GUIDED PRACTICE · EXPERT
Do the work, one step at a time.
Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Most exercises work on paper or with a local test; only a lesson that clearly says “read-only Devnet” makes a public network request.
- Write the release invariant and draw its trust boundary: voucher issuer, beneficiary, escrow program, Token Program, and every account the caller can substitute.
- Specify an unambiguous, versioned authorization schema. Include domain, program, network, action, escrow, mint, recipient, base-unit amount, expiry, and a unique claim ID; define the exact signed bytes.
- Use the receipt-PDA excerpt to define one-time consumption. State how the program validates issuer signature, escrow terms, canonical PDA, beneficiary, and expiry in the same instruction as the protected token CPI.
- Test wrong issuer, domain, program, recipient, mint, amount, expired voucher, wrong receipt PDA, and duplicate claim. For each rejection, assert the escrow, token balances, and receipt state are unchanged.
- Inspect the downloadable escrow lab’s persistent maker counter and replay-after-close tests. Explain what unique escrow IDs protect against and why they do not replace a receipt for a separate signed voucher.
Before you move on, check your reasoning
- Define one sensitive action and its authority.
- Specify canonical fields and the exact signed or on-chain context.
- Choose durable one-time-use state and explain its uniqueness rule.
OPEN SELF-CHECK · UNGRADED
Pause and test your reasoning
A signed eligibility message expires, but a client later signs a fresh message for the same benefit. What prevents a second claim?
Reveal one best answer and its explanation
One best answer: A durable application record that atomically marks the business claim as used, in addition to signature and expiry checks.
Signature verification proves control over exact message bytes and expiry bounds freshness, but neither enforces single use. Persist and atomically consume a unique claim identifier so a newly signed transaction cannot repeat the same business action.
Sources: Durable Nonces · Transaction Pipeline
This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.
Never enter a recovery phrase or private key into a learning site or lesson exercise.
DOWNLOADABLE LOCAL PROGRAM CASE STUDY
Trace replay resistance in the escrow lab.
The tested Intermediate Anchor lab keeps a maker-owned monotonic counter after escrow cleanup and checks replay after close. Inspect its SBF tests, then compare address-reuse protection with a receipt that consumes a signed business claim. These solve related but different uniqueness problems.
Download Token Escrow Lab 01Run locally in WSL Ubuntu with the toolchain described in the archive. The teaching lab does not connect to a cluster, use a wallet file, or spend real SOL; it is not an audited production protocol.
READ THE SOURCE
Go deeper in the official docs
Reviewed on 2026-09-29. Protocol documentation can change; check the linked page’s current version as you use this material.
OPEN LEARNING
Keep learning at your own pace.
This lesson, its guided practice, and all six course lessons are open to everyone. Account-based checkpoints, exams, and saved progress will open when free accounts are ready.
Continue to the next lesson ↗