Skip to content

feat: ResolvedSigner — typed signer resolution with cached classification#31

Merged
0xLeo-sqds merged 3 commits intofeat/external-signaturesfrom
feat/resolved-signer
Apr 15, 2026
Merged

feat: ResolvedSigner — typed signer resolution with cached classification#31
0xLeo-sqds merged 3 commits intofeat/external-signaturesfrom
feat/resolved-signer

Conversation

@0xLeo-sqds
Copy link
Copy Markdown
Collaborator

What

New ResolvedSigner Anchor account type that replaces raw AccountInfo for all signer/creator fields in async instructions. Provides type-safe signer classification and key resolution with OnceCell caching.

Why

Every V2 instruction that stores or emits a signer key had to manually call resolve_canonical_key() — scattered across 16 instructions, each doing its own classification scan of the signers list. Session keys needed resolution to parent keys, external signers needed classification for crypto verification. The pattern was error-prone (/// CHECK boilerplate, forgotten resolution calls, double classification in validate + handler).

How it works

ResolvedSigner wraps an AccountInfo with two OnceCells:

resolve(consensus)  → classifies signer (native/session/external), caches ClassifiedSigner + resolved key
classified()        → returns cached &ClassifiedSigner for verify_classified_signer
resolved_key()      → returns cached &Pubkey for storage/events

The Accounts trait is implemented manually — no owner check, no AccountNotInitialized guard (external signers can have 0 lamports). Anchor's CPI/client module boilerplate lives in instructions/mod.rs as direct mod declarations (required for Anchor's derive macro to find them as siblings).

verify_classified_signer is extracted from verify_signer on the Consensus trait. It accepts a pre-classified &ClassifiedSigner and skips the signer list scan entirely — goes straight to crypto verification, nonce/counter updates, and permission checks.

Changes

  • state/resolved_signer.rs — the type: OnceCell-backed resolve/classified/resolved_key, manual Anchor trait impls, Bumps type
  • interface/consensus_trait.rsClassifiedSigner gets Clone + resolved_key(), verify_classified_signer extracted, resolve_canonical_key renamed to resolve_signer_key
  • 16 async instructionsAccountInfoResolvedSigner, verify_signerresolve + verify_classified_signer, handler resolve_signer_keyresolved_key()
  • 3 sync instructions — rename only (resolve_canonical_keyresolve_signer_key)
  • utils/context_validation.rs — variable renames (canonical_keyresolved_key)
  • instructions/mod.rs — Anchor CPI/client boilerplate modules

What doesn't change

  • smart_account_create (creator: Signer, no consensus account)
  • use_spending_limit (signer: Signer, not consensus-based)
  • increment_account_index V1 (signer: Signer, native only)
  • transaction_buffer_close creator stays AccountInfo (Anchor's close = creator needs it)
  • Sync instructions don't use ResolvedSigner (signers come from remaining_accounts)
  • Wire format is identical — ResolvedSigner consumes 1 account from the slice, same as AccountInfo

Test plan

  • anchor build -- --features=testing compiles clean
  • Standalone test: create + close buffer via V2 with ResolvedSigner passes
  • Full test suite: zero regressions (33 failures = 30 pre-existing + 3 expected increment_account_index)

@0xLeo-sqds 0xLeo-sqds force-pushed the feat/resolved-signer branch 3 times, most recently from 34bf580 to fc1695d Compare April 9, 2026 18:29
…verify API

Replace per-instruction verify_signer() calls and ClassifiedSigner with a
single ResolvedSigner type that wraps AccountInfo + OnceCell<Pubkey>.
All instruction handlers now use creator.verify() / signer.verify() which
delegates to Consensus::verify_signer() and caches the canonical key for
later use via resolved_key().

- ResolvedSigner: lazy key resolution with Anchor trait impls
- Eliminated ClassifiedSigner enum, inlined verify logic into verify_signer
- Updated all 16 instruction files to use ResolvedSigner::verify() API
- Removed unused Consensus imports
- Regenerated IDL and fixed buffer test expectations
@0xLeo-sqds 0xLeo-sqds force-pushed the feat/resolved-signer branch from fc1695d to 5472ba4 Compare April 9, 2026 18:32
…nique discriminators

Addresses three sync-path vulnerabilities:
1. remaining_accounts now included in signed hash (prevents account swap)
2. account_index now included in signed hash (prevents vault swap)
3. Split 'squads-sync' into 'sync_transaction_v2', 'sync_transaction_legacy',
   and 'sync_settings_tx_v2' (prevents cross-instruction replay)
@0xLeo-sqds 0xLeo-sqds force-pushed the feat/external-signatures branch from 7e341be to 2d7832d Compare April 14, 2026 14:14
@0xLeo-sqds 0xLeo-sqds merged commit 2067cb6 into feat/external-signatures Apr 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant