Expert · LESSON 02 OF 07
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.
Look up Solana terms in the glossary
Optional reading history is saved only in this browser. It is not a scored checkpoint or exam result.
In this lesson8 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
FREE INTERACTIVE CASEBOOK · ONLINE OR OFFLINE
Reconcile before you retry.
Trace five fictional incidents across the transaction signature, nonce account, network fee, application receipt, and protected state. The local exercise teaches the safe next action without connecting to a wallet, RPC, or network.
Open the Nonce and Claim Replay Tabletop Download the offline casebookA timeout is not proof that nothing happened. Keep transaction and application-level replay controls separate.
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.
Separate nonce-validation failure from program failure
A durable-nonce transaction must start with the System Program’s `AdvanceNonceAccount` instruction; its stored nonce is the transaction’s `recent_blockhash`, and the nonce account must be initialized and writable. The authority that may advance it must also sign. If those nonce checks fail—because the account is missing, the value no longer matches, or the required authority did not sign—the validator drops the transaction before collecting its fee; application state is unchanged. If validation succeeds, the runtime advances the nonce and deducts the network fee before instruction execution. A later program error rolls back the program’s account changes, but the advanced nonce and deducted fee persist. A “failed” wallet result therefore needs stage-level evidence: preserve the original signature, inspect its status and error, compare nonce and receipt state, and only build a fresh transaction after reconciling the outcome. A durable transaction nonce prevents reuse of that transaction lifetime; it still does not consume a business voucher submitted in a different transaction. Solana currently warns that durable nonces may be deprecated in a future release, so check the live compatibility guidance before choosing them for new long-lived workflows.
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>,
}
Use Nonce and Claim Replay Tabletop 01 to trace five synthetic cases: an unknown submission, an expired recent blockhash, a valid durable nonce followed by program failure, a nonce-validation failure, and the same signed business voucher inside a new transaction. For every case, record the failing or last-confirmed stage, signature evidence, nonce before/after, network fee, application receipt, protected-state result, and next safe action. Then implement the receipt-PDA check for the voucher case and add a negative regression test that proves both rejection and unchanged protected state. These are separate questions: can the exact transaction execute again, and can the same economic claim be executed in a different transaction?
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.
- For each case in the offline Nonce and Claim Replay tabletop, identify the failing stage and record the original signature, nonce before/after, network-fee result, receipt state, and protected account state.
- Write the next safe action for each case. Distinguish an unsent or nonce-validation failure from an execution failure and an unknown submission result; never infer a safe retry from a wallet’s single word “failed.”
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.
- For a durable nonce, classify rejection before nonce validation separately from an instruction error after successful nonce advancement. Record the validator outcome, network fee, durable-nonce value, application receipt, and protected program state as separate evidence; never collapse them into one “transaction failed” label.
OPEN SELF-CHECK · UNGRADED
Pause and test your reasoning
Practice 1 of 3
A signed eligibility message expires, but a client later signs a fresh message for the same benefit. What prevents a second claim?
Reveal the answer and 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
Practice 2 of 3
A signed authorization is valid cryptographically but names the wrong course revision. What should the verifier do?
Reveal the answer and explanation
One best answer: Reject it; the signed revision must match.
Cryptographic validity proves that the signer approved particular bytes. The application must compare every business field to the intended claim before atomically consuming it. Bind the revision so a valid signature cannot authorize a different course version.
Sources: Transactions · Partial Signing · Account Constraints
Practice 3 of 3
A message signature verifies, but the server has already consumed its nonce. What is the safe result?
Reveal the answer and explanation
One best answer: Reject replay and preserve the consumed nonce.
Signature verification does not track application history. One-time use requires durable server or program state that records consumption and is updated atomically with the protected action.
Sources: Transactions · Program Derived Addresses (PDAs) · Account Constraints
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-10-02. Protocol documentation can change; check the linked page’s current version as you use this material.
YOUR PROGRESS, SAVED
Ready for the checkpoint?
Make a free account to save your progress and unlock the quizzes.
Create free account