[SNIP] Pluggable Signer Interface for Smart Accounts
A standard signer trait, a canonical kind tag registry, and a tagged signature envelope so one Starknet smart account class can authenticate owners signing under any of ten cryptographic primitives: Ed25519 (Phantom), secp256k1 (MetaMask raw, EIP-191, EIP-712), P-256 (raw and WebAuthn passkeys), STARK, BLS12-381, and ES256 JWT (Sign in with Apple, single-tenant and sub-bound multi-tenant), without forking a new account contract per curve.
This is the authentication-layer companion to the Session Keys SNIP (#163) merged into starknet-io/SNIPs on 2026-03-03. #163 standardized what a delegated key is allowed to do; this proposal standardizes which curve an owner uses and how it is verified. The two compose. An account implementing both gets session-key delegation and pluggable owner signers as orthogonal layers.
Draft and reference implementation:
- SNIP draft:
docs/snip-draft-pluggable-signer.mdonhaycarlitos/shhh-wallet-cairo, branchv8-robust, submission commitace887e(the 2026-05-11 SNIP-readiness review closeout). - Reference implementation (V8.3
ShhhAccountplus 10 verifier classes): same branch. - Live class hashes (14 V8.x classes declared on Starknet mainnet between 2026-04-28 and 2026-05-11, ~283 STRK cumulative declare cost):
docs/class-hashes.md. - SDK integration spec (per-kind gas costs, envelope builders, paymaster routing):
docs/v8-3-sdk-integration.md. - Audit closeouts that shaped V8.x: the 2026-04-20 Codex/Cairo audit by Omar Espejel (12 findings; 3, H-2 / M-1 / I-1, traced to the absence of this standard), our audit response letter where the SNIP proposal first appeared, and the 2026-05-10 V8.2 to V8.3 review.
Relationship to existing forum threads
The forum may surface three earlier threads when this one is posted. None of them duplicate this proposal; the relationships are worth naming up front.
- Sign in with Starknet technical proposal is a SIWE-style sign-in protocol that delegates signature verification to each account’s
is_valid_signature. It works for STARK accounts today and is silently broken for non-STARK accounts because they have no standard verification surface. This SNIP is the missing piece that lets Sign-in-with-Starknet authenticate a Phantom holder, a MetaMask user, or a passkey user. The two proposals stack rather than compete. - Support Web3 wallets in StarkNet (December 2021) was the right intuition five years too early. Letting EVM wallets sign for Starknet accounts required cryptographic primitives that didn’t exist on Starknet at the time. Garaga v1.0.1 closed that gap in 2025 to 2026; this SNIP is the standard that 2021 thread was reaching for.
- [SNIP] Off-chain signatures (Ă la EIP712) is SNIP-12 typed-data hashing. This SNIP builds on it directly: the
message_hashthat verifier classes consume is the SNIP-12 hash of the SNIP-9 V2OutsideExecutionstruct. SNIP-12 is the hash format, SNIP-9 V2 is the execution path, the Session Keys SNIP (#163) is the authorization layer, this SNIP is the authentication layer.
What the SNIP defines
- An
ISignerCairo trait with three methods that every verifier class implements.verify(message_hash, pubkey, signature) -> bool. Stateless library_call’d signature check. Pubkey is passed as a parameter on every call because verifier classes hold no storage of their own.kind() -> felt252. Returns the canonical kind tag this verifier implements.validate_pubkey(pubkey) -> bool. Called at every owner-registration site before the new owner record commits to storage, so an off-curve or malformed pubkey can never poison the multi-owner or threshold flows.
- A canonical kind tag registry. Tier 1 (primitives):
STARK,SECP256K1,ED25519,P256,RSA_2048,BLS12_381. Tier 2 (envelope variants):WEBAUTHN_P256,EIP191_SECP256K1,EIP712_SECP256K1,DKIM_RSA,JWT_RS256,JWT_ES256,JWT_ES256_APPLE_SUB. Tier 3 reserves slots for ZK envelopes (ZK_JWT,ZK_EMAIL,ZK_TLS,ZK_TOTP). Tier 3 is not yet specified, but the names are claimed to prevent squatting. - Three signature envelope shapes routed by a version tag: single-owner V2 (
[V2_SNIP12, owner_id, kind_tag, …payload]), threshold V2 with mixed kinds across owners ([V2_THRESHOLD, n, env_1_len, …, env_n_len, …]), and the 4-element session-key envelope from #163. Verifier classes only ever see the kind-specific payload. The account strips the outer routing. - A
library_call_syscalldispatch architecture. The account storesverifier_classes: Map<felt252, ClassHash>and resolves each kind on demand viaISignerLibraryDispatcher. One audited account class can serve N curves. Adding a new curve declares one new verifier class and registers it via the same timelocked governance path that gates owner changes (48h, unanimous owners in the reference impl). No account redeploy, no fresh address derivation, no fresh audit of the orchestration logic. - Address-salt binding. Deployment salt is
poseidon(primary_kind, owner_commitment), so the same raw key bytes registered under two different kinds yield two distinct Starknet addresses. This closes the cross-kind impersonation surface as a SNIP design choice.
Why now
Three forces line up in 2026, and they don’t all line up again next year.
- The cryptographic primitives are now production-ready and cheap. Garaga v1.0.1 ships native Cairo verification for Ed25519, secp256k1, P-256, and BLS12-381 with msm hints. Reference impl gas costs from
docs/v8-3-sdk-integration.md: STARK ~12M l2_gas, secp256k1 ~15M, EIP-191 ~18M, EIP-712 ~19M, Ed25519 ~28M, WebAuthn ~46M, JWT-ES256 ~58M, JWT-ES256-sub ~59M, BLS12-381 ~80M. Every one of those is in the range a paymaster can absorb today. None of these were viable two years ago. - #163 is merged and waiting for an authentication companion. Session keys standardize the authorization layer. They presuppose an owner key signs the session, and today “owner key” means STARK, with every non-STARK wallet rolling its own custom envelope. Without a signer SNIP, every new wallet kind is another integration cliff for the paymaster operators that sponsor session-key flows.
- The April 2026 Codex audit of the Shhh wallet (V7) identified the missing standard as the root cause of three independent findings (H-2 SRC-5 interface ID mismatch, M-1 caller sentinel ambiguity, I-1 custom calls-hash collision). All three traced back to the same gap. There is no standard way for an account to declare which curve its owner uses. The Shhh wallet ended up shipping a custom SRC-5 interface ID and a custom byte envelope because the only alternative was to claim SNIP-9 V2 support while failing to be discoverable as such. This SNIP is the standard those three findings’ correct fix needs.
Current state, every team forks for their curve
| Team / project | Primary signer | Fork surface |
|---|---|---|
| Ready | STARK plus STARK guardian | Owner, guardian, escape flow, STARK-only |
| Braavos | STARK plus hardware signer | Hardware via external library |
| Cartridge Controller | WebAuthn P-256 | Custom SNIP-12 envelope, passkey-specific |
| Clave | WebAuthn P-256 | Passkey-specific |
| Chipi Pay (reference impl) | STARK plus session keys | Session-key component, ref impl of the Session Keys SNIP |
| Shhh Wallet | Ed25519 (Phantom) | Custom Ed25519 account; audited 2026-04-20 |
| OZ Cairo Contracts | STARK ECDSA | Reference-only, no multi-curve |
Each is correct for its niche. None can verify a signature produced by another. A Phantom holder cannot use Ready’s guardian recovery. A Cartridge passkey cannot sign a Chipi session-key invocation. A MetaMask user signing into a Starknet dApp today either gets a STARK wallet popup or a chain switch, never their EVM key.
The SNIP isn’t a proposal that any of these teams adopt the reference account class. It’s a proposal that they expose their existing curve support through a shared trait and envelope so the layer above them (paymasters, SDKs, dApps) can stop hardcoding per-wallet integrations.
What this lets users do
User stories the reference impl already supports end-to-end (snforge-proven; one STARK OE proven on mainnet as of 2026-05-10):
- Solana / Phantom user. Signs in with their existing Ed25519 key, gets a counterfactual Starknet address, bridges USDC via CCTP V2 (Solana to Starknet, live since December 2025), interacts with any Starknet dApp. No seed phrase, no new wallet install, no chain-switch popup. If they lose their Phantom seed, V8 guardian recovery (7-day window, additive, single-owner cancel) lets them rotate the on-chain owner to a fresh device. Funds that crossed to Starknet survive a seed loss, which is a UX guarantee Solana itself cannot offer.
- EVM / MetaMask user. Signs with
personal_sign(EIP-191) oreth_signTypedData_v4(EIP-712). The MetaMask popup reads identically to L1, no Snap, no extension install, no chain switch UI. The EIP-712 verifier binds the typed-data domain to{name: "Shhh", version: "1", chainId, salt: account_address}so the popup shows a human-readable “Sign Typed Data v4” with named fields, not a hex blob. - Consumer passkey user. Face ID or Touch ID is the only signature ever produced. WebAuthn P-256 with
webauthn.getclientData type binding, base64url challenge anchoring, andsha256(authData || sha256(clientData))matches the WebAuthn Level 2 spec verbatim. Same flow as Cartridge or Clave today, but the account class is shared, so a paymaster that sponsors one sponsors all of them. - OAuth / Sign in with Apple user. The Apple-issued JWT itself is the on-chain signature. The verifier checks the ES256 signature, parses the JWS payload, re-encodes payload bytes to base64url to reproduce the canonical signing input, validates the nonce claim (43 base64url bytes of the SNIP-12 hash), binds the issuer to hardcoded
https://appleid.apple.com, and for multi-tenant deployments checksposeidon(payload[sub_offset..sub_offset+sub_len]) == stored_sub_hashso one Apple signing key safely serves many users across many accounts without cross-tenant impersonation.
Reach: who can sign with these primitives
Every Tier 1 and Tier 2 kind in the registry maps to a signing primitive that already runs on a device the user owns. The total addressable reach, in round numbers, with honesty about which verifier classes are live on mainnet today vs. reserved as future work:
- Passkeys (Face ID, Touch ID, Windows Hello, Android passkeys, hardware security keys) via
WEBAUTHN_P256: ~4-5B devices. Verifier class live. - Solana / Phantom via
ED25519: ~10M MAU. Verifier class live. - EVM wallets (MetaMask, Rainbow, Trust, Coinbase Wallet, Rabby, WalletConnect, Ledger, Trezor) via
SECP256K1,EIP191_SECP256K1,EIP712_SECP256K1: ~100M MetaMask installs alone. Three verifier classes live, covering raw,personal_sign, andeth_signTypedData_v4. - Sign in with Apple via
JWT_ES256andJWT_ES256_APPLE_SUB: ~1B Apple IDs. Both verifier classes live; the sub-bound variant lets one Apple signing key safely serve many users on different accounts. - Google OAuth via
JWT_RS256(~3B accounts) and email identity viaDKIM_RSA(~4-5B Gmail / Outlook / iCloud / workplace SSO accounts): kind tags reserved in Tier 2; verifier classes are future work pending the RSA-2048 Garaga circuit. - Hardware-backed PKI via raw
P256orRSA_2048(PIV smart cards, eIDAS eIDs, YubiKey, corporate signing infrastructure): P-256 verifier class live; RSA-2048 reserved. - Validator and DAO BLS keys via
BLS12_381(drand-compatible min-sig): every Ethereum and Cosmos validator and threshold-sig network. Verifier class live; in-browser signing depends on Garaga PR #519 (linked in Acknowledgments). - Starknet-native via
STARK: status quo preserved. Every existing STARK wallet, Ledger Starknet app, OZ reference account, and relayer signs with the same verifier class.
Each is a one-line integration path once the verifier class is declared. Today every kind is a fork; this SNIP collapses that surface to one dispatcher.
Retention angle. Existing crypto self-custody wallets ship with 0% recovery support: lose the seed phrase, lose the funds. The reference impl ships a 7-day guardian recovery window plus multi-device weighted threshold, so a user who loses one device does not lose the account. The recovery path is curve-agnostic, which means the consumer-grade “lost phone equals rotate the key, keep the funds” UX guarantee becomes available to Phantom, MetaMask, passkey, and Sign-in-with-Apple users for the first time on any chain.
Distribution: one dispatcher, every wallet kind
The same trait gives dApps and paymasters a single integration point.
- dApp SDKs write one dispatcher, not six. A dApp that integrates Starknet via this SNIP gets every wallet kind for free. Paymaster operators (AVNU, Chipi Pay) sponsor every kind without per-wallet integration. The Chipi handover doc is what this actually looks like for one operator: pin 11 class hashes, route by
verifier_classes[kind_tag], done. - Session keys (#163) and pluggable signers compose. The 4-element session envelope and the kind-tagged owner envelope are length-routed (
signature.len() == 4selects session, otherwise version-tag dispatch) and can’t collide. An account implementing both surfaces both layers cleanly. - Verifier classes are independently auditable. Each one is a Sierra binary that implements exactly one curve. The audit scope of
Ed25519Verifieris “does Garaga’sis_valid_eddsa_signatureget fed the right inputs and is the envelope fully consumed”, nothing more. The orchestration code that decides whether to call a verifier lives in the account class and is audited once. - Verifier classes are governance-rotatable. A vulnerability in a single curve’s verifier is fixed by declaring a patched verifier class and proposing the rotation through the same 48h timelocked unanimous-owner path that gates owner changes. Existing accounts pick up the fix on their next signature without redeploying. No fork, no migration, no fresh address.
Reference implementation
haycarlitos/shhh-wallet-cairo, branch v8-robust, submission commit ace887e (V8.3 plus the 2026-05-11 SNIP-readiness review closeout).
Verification evidence:
- 14 V8.x classes declared on Starknet mainnet across four declare batches (2026-04-28 to 2026-05-11). Active for new deploys: V8.3
ShhhAccount(0x03bc5392…87d9) plus 10 V8.2 verifier classes. V8.0 / V8.1 / V8.2ShhhAccountremain declared for legacy recognition and are deprecated. snforge test: 242 passed, 0 failed. Includes negative regression tests for the M-1 / M-2 / M-3 audit findings, executed against three deliberately-misbehavingEvilVerifiertest helpers (reentrant, return-true, panic).- Mutation testing: 10 of 10 mutants killed.
- Fuzz testing: 1,792 random sweeps across authorization, timelock, and bounds.
- One end-to-end mainnet OE confirmed against the V8.1 class on 2026-05-10. The dispatcher path is byte-for-byte identical V8.1 to V8.3; only validate_pubkey and the inside_verifier flag wrapping changed in V8.3, and neither is exercised by a no-additional-owners STARK flow.
The reference impl ships one example, not a canonical account everyone is asked to adopt. The point is to make the standard reviewable against a complete implementation rather than a paper spec.
What is intentionally NOT in this SNIP
To save reviewers time:
- No external audit yet. Phase 13 (Omar round 2, or Zellic / Nethermind / OpenZeppelin Security Services) is scheduled after this discussion thread settles. Internal self-reviews and the 2026-04-20 Codex audit on V7 only.
- No ZK kinds.
ZK_JWT,ZK_EMAIL,ZK_TLS,ZK_TOTPare reserved names in Tier 3 but specified by follow-up SNIPs, not this one. The verifier classes for those don’t exist yet. - No
DKIM_RSAverifier class despiteDKIM_RSAbeing in Tier 2. Email-based recovery is a documented use case; the class itself is future work. - No
JWT_RS256(Google) verifier class. Same reason: Tier 2 slot is reserved, RSA-2048 primitive will land when the underlying Garaga circuit ships. - BLS12-381 is server-side-signing only for now. The on-chain verifier class is live and audited, but browser-side signing depends on the upstream Garaga PR linked below. Phantom, MetaMask, passkey, and Apple paths sign client-side today and do not depend on that PR landing.
- No
is_valid_signatureintegration. SNIP-6 stays unchanged.verifyis recommended as the implementation ofis_valid_signaturefor non-STARK curves but the SRC-6 surface is left alone.
Feedback welcome
Specific questions the discussion would benefit from input on.
- Tier 2 envelope variants, especially
JWT_ES256_APPLE_SUB. The 5-felt pubkey (4 P-256 coords plusposeidon(sub)) is the multi-tenant binding that lets one Apple signing key serve many users on different accounts. Is the encoding right? Is the JSON anchor check on thesubclaim ("sub":"preamble plus closing") sufficient, or should the SNIP require a full JSON parse? ShouldJWT_RS256(Google OAuth) land in Tier 2 before its Garaga circuit ships, or stay reserved? - Library_call dispatch vs. component embedding (@OpenZeppelin). Part F is non-normative. Implementations MAY embed verifiers as Cairo components instead of dispatching to separate classes. Should the SNIP take a stance? What is the OpenZeppelin Cairo Contracts team’s preferred direction for the OZ reference account? Component embedding is closer to #163’s pattern; library_call dispatch makes one account class serve N curves and makes verifier classes governance-rotatable. There are real tradeoffs both ways.
- Verifier-class governance attack surface (Security Considerations §7 and §8). The reference impl gates
add_verifier_classbehind 48h timelock plus unanimous owners and raisesinside_verifieraround every library_call site. Is the 48h-unanimous gate strong enough for a class that runs in the account’s storage context? Should there be a public veto period during the timelock window? Is the symmetricinside_verifierwrap on bothverifyandvalidate_pubkey(V8.3’s M-1 fix) the right defense-in-depth, or is there a cleaner reentrancy model? - Kind tag stability. Should
'ED25519_V2'be reserved now for future Garaga changes, or should the SNIP commit to one tag per curve forever and force schema changes through a new kind? The reference impl handles incompatible pubkey schema rotation by requiring a fresh kind tag rather than in-place class replacement. Is that the right policy at the spec level? - Integration timeline, minimum slice for early adopters. What is the smallest useful adoption step for an existing AA team? Just
ISIGNER_IDSRC-5 registration so paymasters can discover support? A single verifier class deploy? A full account class swap? Concrete preferences from Ready, Braavos, Cartridge, Clave, and the OZ Cairo Contracts maintainers on which slice they’d ship first would shape the SNIP’s “Backwards Compatibility” section.
Especially interested in input from wallet teams (Ready, Braavos, Cartridge, Clave), paymaster operators and infra providers (AVNU, Chipi Pay, Cavos, Privy), the OpenZeppelin Cairo Contracts maintainers, and anyone who hit the same standards-drift wall the Shhh V7 audit surfaced. This is the canonical discussion anchor for the entire SNIP lifecycle (Draft, Review, Last Call, Final), so the same thread will be reused across revisions.
Acknowledgments
- Chipi Pay and @OmarEspejel. Session Keys SNIP #163, which established the modular-account pattern this SNIP extends. The two SNIPs share authors deliberately so the layering stays consistent.
- Henri Lieutaud (@l-henri, Head of Developer Relations at the Starknet Foundation). Ran the Nethermind AuditAgent scan on the Shhh V7 commit range on 2026-04-13, a week before Omar’s human review. The scan surfaced three structural findings (unrestricted
__execute__, non-atomic multicall, dead upgrade component) that triggered the V8 rewrite and made several of the audit-converged-root-causes that motivated this SNIP show up early. Per the Nethermind AuditAgent license, the credit is to Henri for running and triaging the scan as an external SNF reviewer, not a claim that the code is “audited by Nethermind.” - Omar Espejel. 2026-04-20 Codex/Cairo audit on Shhh V7 (12 findings). Three of those findings converged on “there is no standard way to declare which curve an owner uses,” which is what this SNIP proposes. Our response letter is where the SNIP proposal first appeared in writing.
- Garaga team (Keep Starknet Strange). Ed25519, secp256k1, P-256, and BLS12-381 verification primitives that make curve-agnostic signer verification practical on Starknet today. Upstream contribution back: Garaga PR #519 adds a
bls_calldata_builderto the Garaga npm package so BLS12-381 signatures can be assembled in-browser. Until it merges, the BLS path is server-side-signing only (Python fixture script atscripts/py/gen_bls_fixture.py); once merged and released to npm, browser flows can sign BLS messages natively. Detailed scope and verified path mapping atdocs/upstream-garaga-bls-pr.md. The other nine kinds (Phantom, MetaMask raw, EIP-191, EIP-712, P-256 raw, WebAuthn, JWT-ES256 single, JWT-ES256 sub, STARK) sign client-side today and do not depend on this PR landing.
Carlos (@haycarlitos)