External signatures, V2 signer types, session keys, and sync execution#30
Open
0xLeo-sqds wants to merge 2 commits intofeat/implement-account-utilizationfrom
Open
External signatures, V2 signer types, session keys, and sync execution#300xLeo-sqds wants to merge 2 commits intofeat/implement-account-utilizationfrom
0xLeo-sqds wants to merge 2 commits intofeat/implement-account-utilizationfrom
Conversation
…nd sync execution - V2 signer system: P256Webauthn, P256Native, Secp256k1, Ed25519External with precompile introspection and syscall verification paths - Unified consensus trait supporting both Settings and Policy accounts - Session keys: delegated signing for external signers with expiration - Synchronous transaction execution with mixed native + external signers - Per-signer nonce replay protection and WebAuthn counter validation - SDK: V2 instruction/transaction/rpc wrappers, custom serializers - Full V1/V2 parameterized test suite
…sion key collisions - Reject P256 compressed pubkeys without 0x02/0x03 prefix - Reject all-zero secp256k1 and Ed25519 public keys - Prevent assigning a session key already used by another signer
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds external (non-Solana-native) signer support to the smart account program. External signers — P256/WebAuthn passkeys, secp256k1/Ethereum keys, and Ed25519 keys held off-chain — can now participate in consensus alongside native Solana signers. Includes session key delegation, synchronous mixed-signer execution, and per-signer nonce replay protection.
59 program files changed, 145 SDK files, 94 test files.
V2 Signer System
Five signer variants, all stored in the unified
SmartAccountSignerenum:NativeAccountInfo.is_signerP256WebauthnP256NativeSecp256k1secp256k1_recoversyscallEd25519ExternalSmartAccountSignerWrapperhandles V1↔V2 format migration with custom packed serialization. V1 format storesVec<LegacySmartAccountSigner>; V2 uses a tagged entry format with per-entry headers. Adding an external signer to a V1 wrapper requires explicit migration viaforce_v2().Cross-P256 duplicate detection:
P256WebauthnandP256Nativesharing the same compressed pubkey are treated as duplicates and rejected at registration.Signer registration validation
Compressed P256 pubkeys must have
0x02or0x03prefix. Secp256k1 and Ed25519 pubkeys reject all-zero keys.rp_id_hashfor WebAuthn signers is derived on-chain from the providedrp_id— never trusted from user input.eth_addressfor secp256k1 is derived on-chain viakeccak256(uncompressed_pubkey)[12..32].Precompile Introspection
Single precompile instruction constraint: one precompile instruction per transaction, always at instruction index 0. Multiple signatures of the same type are packed in that single instruction (
num_signatures > 1).Two verification paths per signer type:
Precompile path — loads the precompile instruction at index 0 via
load_instruction_at_checked, parses signature offsets, extracts pubkey/message/signature, and verifies pubkey matches the stored signer. The Solana runtime has already verified the cryptographic signature before the program executes.Syscall path — for Ed25519 and Secp256k1 when no precompile instruction is present. Ed25519 uses
sol_curve_group_op/sol_curve_validate_pointsyscalls (~40-50k CU). Secp256k1 usessecp256k1_recoversyscall (~25k CU). P256 signers always require the precompile path —PrecompileRequirederror if attempted via syscall.Batch verification in sync path:
verify_precompile_signersloads the instruction once and verifies all signers by direct index — signature at positionicorresponds tosigners[i]. Enforces exact match betweennum_signaturesin the precompile data and the number of signers being verified.Nonce Replay Protection
Every external signer stores a
u64nonce, incremented by 1 after each successful verification. The nonce is appended to the message hash before signing:checked_add(1)prevents overflow — returnsNonceExhaustedafteru64::MAX. WebAuthn signers additionally track a monotonically-increasingu32counter from the authenticator hardware, persisted after each verification.Message Domain Separation
Each operation uses a unique prefix to prevent cross-operation signature replay:
"squads-sync""proposal_vote_v2""proposal_create_v2""transaction_execute_v2""create_session_key_v2""tx_buffer_create_v2"/"tx_buffer_extend_v2"All messages include account-specific keys (consensus account, proposal, etc.) preventing cross-account replay.
WebAuthn Verification
The precompile signs
authenticatorData || clientDataHash. The program:len - 32to extractauth_dataandclient_data_hashrpIdHashagainst stored value, checks user presence flagsign_counter > stored_counter)clientDataJSONfrom compactClientDataJsonReconstructionParams(3 bytes: type/flags byte + port u16)client_data_hashClientDataJsonReconstructionParamssupports:webauthn.create/webauthn.gettypes, cross-origin flag, HTTP/HTTPS, optional port, and Google Android extra field. RP ID bytes are validated for JSON-safe ASCII at reconstruction time.Session Keys
External signers can delegate to a native Solana keypair via
create_session_key. The session key holder signs Solana transactions normally (is_signer = true), but votes/actions are attributed to the parent external signer's canonical key.u64Unix timestamp, max 3 months (SESSION_KEY_EXPIRATION_LIMIT)key()or another signer's session keyrevoke_session_key(requires external signer proof), or session key holder self-revokesclassify_signerresolves the canonical key: native signers return their key directly, session keys return the parent external signer's key, external signers return theirkey_id. This prevents double-counting when someone signs with both a session key and the parent.Synchronous Execution with Mixed Signers
remaining_accountslayout for sync transactions:Verification proceeds in phases:
is_signer = true, break on firstfalsetake_while(is_precompile)splits into precompile batch and syscall slicesverified_keys.len() >= threshold, aggregate permissions cover all bits,vote_permission_count >= thresholdThe signed message for sync transactions includes
hash(payload), binding external signatures to the exact instructions being executed.Unified Consensus Trait
Consensustrait abstracts overSettingsandPolicyaccounts. Both implement the same signer verification, threshold enforcement, and stale transaction protection.ConsensusAccountis an Anchor interface account that dispatches to either implementation.verify_signerhandles all three signer types (native, session key, external) through a single code path. Returns the canonical key for vote recording and permission checks.ExtraVerificationData
Typed enum carried in instruction data, one entry per external signer:
Precompile entries must precede syscall entries in the array (enforced by
take_while(is_precompile)+all(is_syscall)validation).SDK
Three-layer V2 SDK:
instructions/→transactions/→rpc/. All V2 instruction wrappers overrideisSignermetadata for the signer account (generated code hasisSigner: falsebecause on-chain accounts changed fromSigner<'info>toAccountInfo<'info>for external signer support).Custom
SmartAccountSignerWrapperserializer handles the packed V2 format.SmallVec<u8, T>support for instruction-level types vsVec<T>(4-byte Borsh prefix) for stored state.Test Suite
Parameterized V1/V2 test suite via
SIGNER_FORMATenv var.createSignerObject()helper produces the correct format. Test runners:index.ts(full),index-v1-only.ts,index-v2-only.ts.Dedicated V2 test suites:
externalSignerSyscall.ts— Ed25519/Secp256k1 syscall verification, P256 rejectionexternalSignerPrecompile.ts— Ed25519/Secp256k1/P256Webauthn precompile verificationexternalSignerTypes.ts— P256Native signer typesessionKeys.ts— creation, revocation, collision detection, per-type precompile testsmixedSignerSync.ts— 1 native + 2 P256 precompile + 1 secp256k1 syscall + 1 ed25519 syscall in one txexternalSignerSecurity.ts— replay, wrong key, wrong message, permission, threshold, duplicate detectionexternalSignerNoncePersistence.ts— nonce across sequential operations, stale nonce rejection, cross-proposal nonce, payload mismatch507 passing, 3 expected failures (all
increment_account_indexmax index).