✳ SOLANA ACADEMYCourse overviewGlossary

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.

A signed action is checked for account permissions and freshness, then a claim receipt prevents replay before execution or rejection.
2. Authority, signatures, and replay resistance workflow. A signed action is checked for account permissions and freshness, then a claim receipt prevents replay before execution or rejection.
The transaction layer checks signed message identity, recent blockhash age or durable nonce, and recent duplicate processing. A new blockhash creates a different transaction message. The application layer must independently validate the claim domain, parties, terms, expiry, and receipt PDA, then atomically consume the claim with the protected action. Reuse of a consumed claim is rejected without changing protected state.
Transaction freshness and business claim replay are separate defenses. The transaction layer checks signed message identity, recent blockhash age or durable nonce, and recent duplicate processing. A new blockhash creates a different transaction message. The application layer must independently validate the claim domain, parties, terms, expiry, and receipt PDA, then atomically consume the claim with the protected action. Reuse of a consumed claim is rejected without changing protected state.
A durable nonce must validate before execution. If nonce validation fails, the transaction is dropped with no network fee or application-state changes. If it validates, the nonce advances and the network fee is deducted before program execution. Successful execution commits program state. Instruction failure rolls back program state but keeps the advanced nonce and fee. A separately consumed application receipt is still required to prevent the same voucher from being used in a newly signed transaction.
Durable nonce validation, execution, fee, and application-state outcomes. A durable nonce must validate before execution. If nonce validation fails, the transaction is dropped with no network fee or application-state changes. If it validates, the nonce advances and the network fee is deducted before program execution. Successful execution commits program state. Instruction failure rolls back program state but keeps the advanced nonce and fee. A separately consumed application receipt is still required to prevent the same voucher from being used in a newly signed transaction.

Read the workflow

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.

  1. SIGNED ACTION + ACCOUNT METAS
  2. MESSAGE AGE + STATUS CACHE
  3. CLAIM + RECEIPT CHECK
  4. ATOMIC ACTION OR REJECT

FIELD NOTE 01

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.

FIELD NOTE 02

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.

FIELD NOTE 03

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.

FIELD NOTE 04

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.

FIELD NOTE 05

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.

FIELD NOTE 06

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.

FIELD NOTE 07

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.

FIELD NOTE 08

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.

MAKE THE CONNECTION

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.

Illustrative Anchor account-validation excerpt. The handler must also verify expiry and claim authenticity, perform the protected CPI, and populate the receipt in this same instruction. Account types and error definitions are omitted.
#[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. Unless a lesson explicitly says “read-only Devnet,” use paper or a local test and do not connect to a public network.

  1. Write the release invariant and draw its trust boundary: voucher issuer, beneficiary, escrow program, Token Program, and every account the caller can substitute.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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?

  1. A durable application record that atomically marks the business claim as used, in addition to signature and expiry checks.
  2. Expiry alone; once the first message expires, the benefit is permanently consumed.
  3. The transaction signature cache, even when the new transaction has a different blockhash and signature.
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?

  1. Accept it because a valid signature overrides any course revision named inside the signed message.
  2. Reject it; the signed revision must match.
  3. A signature for one course revision can be silently changed to authorize any later revision.
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?

  1. Restore the nonce.
  2. A valid signature proves the nonce has not been used and can be accepted repeatedly without a durable record.
  3. Reject replay and preserve the consumed nonce.
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.

Scored checkpoints and level exams are available online through a free account. They are not included in this pack.

Optional lab files

These files are included with the pack. Follow the safety and toolchain notes in the lesson before running a lab; a Devnet read requires an internet connection.

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 when you reconnect.