All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
- End-to-end vitest tests for session-action enforcement (
tests-sdk/tests/12-actions.test.ts, 9 cases):programWhitelistallow + reject (3021),programBlacklistallow + reject (3022),solMaxPerTxallow at-cap + reject over-cap (3023),solLimitlifetime budget exhaustion (3024), and combined-rules enforcement. Runs against a livesolana-test-validatorwith the foundation binary loaded and uses@lazorkit/sdk-legacy'sActionsbuilder to dogfood the full encode → on-chain enforce path. docs/audit/artifacts for an Accretion delta-audit follow-up:DELTA_BRIEF.mdsummarises the changes from the previous audited baseline by phase with explicit audit asks;program-src.diffis the full unified diff ofprogram/;program-src.diff.statis a per-file changed-line summary;upstream-parity.txtreports byte-identity vs the already-auditedlazorkit-protocolper file (13/19 changed files identical).- Local git tags
audit-baseline-2026-02-accretion(previous Accretion-audited state, commitd1eaaeb) andaudit-pending-v1(the current consolidated state ready for delta review). - Session action permissions: 8 immutable permission rules attachable at session creation —
SolLimit,SolRecurringLimit,SolMaxPerTx,TokenLimit,TokenRecurringLimit,TokenMaxPerTx,ProgramWhitelist,ProgramBlacklist. Action discriminators (1, 2, 3, 4, 5, 6, 10, 11) and the 11-byte header layout matchlazorkit-protocolso the unified SDK can encode actions identically for both builds. SessionAccountis now variable-size: a session can carry a trailing action buffer (max 16 actions, ≤ 2048 bytes) validated at creation time.CreateSessioninstruction data accepts the new[actions_len: u16][actions: N]extension after the legacy 40-byte args; old 40-byte clients continue to work via the legacy parser branch.- Pre-CPI action enforcement at
Executetime: program whitelist/blacklist checks against each CPI target. - Post-CPI action enforcement: SOL/token spending caps with saturating arithmetic; recurring-window resets aligned to slot boundaries; per-execute SOL outflow tracked across all CPIs for
SolMaxPerTx. - Vault-invariant defenses against
System::Assign/SetAuthority/Approveescapes: vault owner + data-length snapshotted pre-CPI and verified unchanged post-CPI; vault-owned token accounts on listed mints have their owner / delegate / close_authority fields snapshotted and verified. - Anti-CPI guard for session-authenticated
Execute: stack-height must be 1 (rejects wrapper programs chaining throughExecute). - Error codes 3020–3029 (action validation + enforcement) and 3030–3032 (
SessionVaultOwnerChanged,SessionVaultDataLenChanged,SessionTokenAuthorityChanged). - Dual-cluster Cargo features (
mainnet,devnet): the embedded program ID is chosen at compile time via a feature flag with acompile_error!if neither / both is set. Themainnetfeature embedsLazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi(same slot aslazorkit-protocol) for the foundation deployment;devnetkeepsFLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao. security.txtblock embedded viasolana-security-txtmacro: links to SECURITY.md, contact email, source repo, source revision (fromGITHUB_SHA), and the Accretion audit PDF.- Zero-copy
CompactInstructionRefparser (parse_compact_instructions_ref_with_len) used by the Execute hot path — no per-instructionVec<u8>allocations for account-index bytes or instruction data. - Cherry-pick guardrails:
scripts/fee-paths.txtdeclares forbidden fee-surface paths and symbols,scripts/check-no-fee.shverifies the working tree (used by CI),scripts/strip-fee.shauto-removes fee files post-cherry-pick. - CI workflow
check-no-feeruns the verifier on every PR. - CI workflow
sbf-cluster-checkbuilds both mainnet and devnet SBF binaries, verifies their hashes differ, and asserts that an unflaggedcargo build-sbffails with the expectedcompile_error!. scripts/build-all.sh <devnet|mainnet>now drives a feature-flagged build + IDL regen + SDK regen in one step. The previousscripts/sync-program-id.shis removed (program ID is now a compile-time feature, not a sed target).solana-security-txtanddefault-envdependencies,[workspace.metadata.cli]pinning Solana CLI 3.0.4 for verified builds.- Unified SDK API with discriminated union signer types (
ed25519(),secp256r1(),session()helper constructors) CreateWalletOwnerunion type: singlecreateWallet()method for both Ed25519 and Secp256r1AdminSignerunion type for admin operations (addAuthority, removeAuthority, transferOwnership, createSession)ExecuteSignerunion type for execute operations (includes session keys)DeferredPayloadinterface for clean authorize() -> executeDeferredFromPayload() flow- Security test suite: 19 new tests across 3 files (permissions, session execution, attack vectors)
- Permission boundary tests: role enforcement for spender/admin/owner (error 3002 verification)
- Session execution tests: session key execute, transferSol via session, wrong key rejection, expiry enforcement
- Security edge case tests: counter increment verification, self-reentrancy prevention (error 3013), cross-wallet authority isolation, accounts hash binding (recipient swap detection)
- High-level
transferSol()method: transfer SOL with just payer, wallet, signer, recipient, and amount - High-level
execute()method: execute arbitrary TransactionInstructions without manual compact encoding - Auto-derivation of authority PDAs from signer.credentialIdHash (authorityPda now optional in Secp256r1 methods)
- Deferred Execution: 2-transaction flow for large payloads exceeding the ~574-byte limit of a single Secp256r1 Execute tx
- Authorize instruction (disc=6): TX1 signs over instruction/account hashes, creates DeferredExec PDA
- ExecuteDeferred instruction (disc=7): TX2 verifies hashes and executes via CPI with vault signing
- ReclaimDeferred instruction (disc=8): closes expired DeferredExec accounts, refunds rent to original payer
- DeferredExecAccount (176 bytes): stores instruction/account hashes, wallet, authority, payer, expiry
- RevokeSession instruction (disc=9): Owner/Admin can close session accounts early, refunding rent to specified destination
- Error code 3019 (InvalidSessionAccount) for invalid session PDA during revocation
- Devnet smoke test (
tests-sdk/tests/devnet-smoke.ts): exercises all 9 instructions across Ed25519/Secp256r1/Session auth types and Owner/Admin/Spender roles, reporting CU/TX size/rent - Deferred execution benchmarks (CU + tx size measurements for TX1/TX2)
- Error codes 3014-3018 for deferred execution (expired, hash mismatch, invalid expiry, unauthorized reclaim)
- SDK builders:
createAuthorizeIx,createExecuteDeferredIx,createReclaimDeferredIx - SDK helpers:
findDeferredExecPda,computeInstructionsHash - LazorKitClient methods:
authorize,executeDeferredFromPayload,reclaimDeferred - Odometer counter replay protection for Secp256r1 (monotonic u32 per authority)
- program_id included in challenge hash (cross-program replay prevention)
- rpId stored on authority account at creation (saves ~14 bytes per transaction)
- TypeScript SDK: standardised on
@lazorkit/sdk-legacy(lives in siblinglazorkit-protocolrepo); the in-treesdk/solita-clienthas been removed - Integration + security test suite (
tests-sdk/) with 56 tests across 11 files - Benchmark script for CU and transaction size measurements
- CompactInstructions accounts hash for anti-reordering protection
- Session expiry validation (future check + 30-day max duration)
- Self-removal and owner removal protection in RemoveAuthority
- AuthDataParser minimum 37-byte validation
- Signature offset validation in precompile introspection
- Dynamic message length check in precompile verification
- Instruction index restricted to 0xFFFF only (reject index 0)
- Comprehensive open-source documentation (Costs, Architecture, SDK API)
- SECURITY.md, CONTRIBUTING.md, CHANGELOG.md
- Secp256r1 auth payload format: replaces the older
typeAndFlagsbyte atauth_payload[13]with full rawclientDataJSONembedded in the payload. The on-chain auth verifier now parses the JSON directly rather than reconstructing it fromtypeAndFlags + rpId. Aligns withlazorkit-protocolbyte-for-byte and is required for binary-swap compatibility at the shared mainnet slot. - Secp256r1 authority on-chain layout: replaces the previously stored variable-length raw
rpIdwith a precomputed 32-byterpIdHash(SHA-256 digest computed at registration). New layout:header(48) + cred_hash(32) + pubkey(33) + rpIdHash(32) = 145 bytes. Saves onesol_sha256syscall perExecute. Existing wallets created on the upstream commercial binary remain readable after binary swap. - Shank IDL declarations on the
ProgramIxenum (account metadata:writablemodifiers, account positions, descriptions) resynced withlazorkit-protocol. Five fee-related variants (disc 10–14:InitializeProtocol,UpdateProtocol,RegisterPayer,WithdrawTreasury,InitializeTreasuryShard) stripped —program-v2keeps disc 0–9 only. Runtime not affected (@lazorkit/sdk-legacyuses hand-written builders rather than the generated IDL). - SDK API: unified all methods via discriminated unions (breaking: removed
createWalletEd25519,createWalletSecp256r1,addAuthoritySecp256r1,removeAuthoritySecp256r1,executeEd25519,executeSecp256r1,executeSession,createSessionSecp256r1,transferOwnershipSecp256r1,authorizeSecp256r1) - SDK API: all methods now return
{ instructions: TransactionInstruction[]; ...extraPdas }consistently - SDK API:
createSessionnow takessessionKey: PublicKeyinstead ofUint8Array - SDK architecture: split monolithic wrapper.ts into client.ts, types.ts, signing.ts, compact.ts
- Secp256r1 replay protection: primary mechanism changed from WebAuthn hardware counter to program-controlled odometer
- Auth payload layout: added 4-byte counter field at offset 8 (all subsequent fields shifted)
- Challenge hash: 5 elements -> 7 elements (added counter + program_id)
- AuthorityAccountHeader: added
counter(u32) andversion(u8) fields - Secp256r1 pubkey storage: verified as 33-byte compressed format
- Authenticator trait: added
program_idparameter - Counter write timing: moved to after full signature verification
- Slot freshness: replaced SlotHashes sysvar with
Clock::get()(removes 1 account from transaction) - Counter size: u64 -> u32 (4 billion operations per authority is sufficient)
- Execute Secp256r1 transaction size: 708 -> 658 bytes (50 bytes saved)
- Execute Secp256r1 accounts: 8 -> 7 (SlotHashes sysvar removed)
- Cost documentation: updated all CU numbers from local validator to devnet-measured actuals, expanded CU table to all 9 instructions across all auth types and roles
- Shank IDL: fixed 4 instructions missing
rent_sysvaraccounts, added 3 missing deferred execution instructions (Authorize, ExecuteDeferred, ReclaimDeferred)
tests-sdkintegration tests now passPROGRAM_IDexplicitly to theLazorKitClientconstructor.@lazorkit/sdk-legacy's URL-based program-ID inference defaulted localhost to the commercial devnet ID (4h3X…); against a local validator loading the foundation binary at the keypair's pubkey this caused all txs to fail with "Attempt to load a program that does not exist".tests/common.tsnow resolvesPROGRAM_IDfrom (1)PROGRAM_IDenv override, (2) the keypair file attarget/deploy/lazorkit_program-keypair.json, or (3) the foundation devnet fallbackFLb7….tests-sdk/tests/08-deferred.test.tsbuilds theAuthorizesigned_payloadasinstructions_hash || accounts_hash || expiry_offset (u16 LE)to match what the on-chain verifier hashes. The test code was missing the 2-byte expiry buffer at all 6 sign sites, causing all 7 deferred tests to fail withInvalidMessageHash(3005). After the fix, all 65 vitest E2E tests pass against a live validator.- Authorize signed payload now includes
expiry_offset(66 bytes total), preventing relayers from modifying the expiry window sol_assert_bytes_eqnow uses thelenparameter instead ofleft.len()(latent OOB read on-chain)reclaim_deferreduseschecked_addfor lamports (consistent withexecute_deferredandmanage_authority)PublicKey.defaultcollision withSystemProgram.programIdin SDK execute methods: both are 32 zero bytes, causingbuildCompactLayoutto map SystemProgram to the sysvar slot (index 4) instead of adding it as a remaining account. Replaced withSYSVAR_INSTRUCTIONS_PUBKEY.- Synced passkey lockout: WebAuthn hardware counter=0 no longer causes rejection
- 17/17 audit issues resolved (Accretion audit)
- Initial release with Ed25519 and Secp256r1 authentication
- Role-Based Access Control (Owner, Admin, Spender)
- Ephemeral session keys with slot-based expiry
- CompactInstructions for Execute
- SlotHashes nonce for signature freshness (replaced by Clock::get() in v2)
- Zero-copy serialization via pinocchio