Tier 2 unlinkable Starknet account — contracts + wallet client + sponsored deploy/execute#24
Open
Akashneelesh wants to merge 4 commits into
Open
Conversation
Introduce the Tier 2 unlinkable-account scheme: the on-chain factory and
account contracts no longer reference the user's MetaMask address. Instead,
a client-derived secp256k1 "session key" plays the role of account owner.
Anything observable on chain — calldata, events, storage, signatures — is
keyed off the session address, which has no a-priori relationship to the
MetaMask wallet that produced it.
Cairo changes:
- account_factory: rename eth_address → session_eth_address through the
IAccountFactory trait, impl, and tests; drop eth_address from the
AccountDeployed event so indexers can't trivially link account ↔ owner;
bump PRIMER_CLASS_HASH to the scarb 2.16.1 release-profile output.
- eth_712_account: rename initialize param to owner_eth_address; add
Tier-2-oriented doc comments; storage var name unchanged for layout
stability across upgrades.
- New packages:
counter/ — minimal demo target the Tier 2 account will exercise.
helpers/ — privacy-pool helper contracts:
- EarnDeployHelper: privacy_invoke(session_eth, signature, note_id)
forwards to factory.deploy_account, pool-caller-validated. Lets the
Starknet privacy pool deploy a Tier 2 account inside a private,
proof-validated transaction.
- EarnInvokeHelper: privacy_invoke(target_account, outside_execution,
signature, note_id) forwards to ISRC9_V2.execute_from_outside_v2,
so any sponsored private invoke can land on the new account
without an MM signature.
- testing_utils: MockOutsideExecutionTarget for the invoke-helper tests.
- Build config: casm = true on every starknet-contract target so deploy
scripts can hash the casm; release profile produces deterministic class
hashes used by the Tier 2 wallet client.
- Workspace: drop strategy_implementation (incompatible with the new
param shape; replaced by counter for the demo); add counter + helpers.
Tier 2 alone does NOT make an account unlinkable — it removes the
on-chain references to MetaMask, but funding, gas-payer, and relayer
metadata can still re-link. That side is handled by the wallet client +
sponsored-private flow added in subsequent commits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lows
The TypeScript side of the Tier 2 stack. Lives in tier2_wallet/, a
self-contained npm package built on @noble/curves + starknet.js v10 +
the Starknet privacy SDK (path-dep to ../../privacy/starknet-privacy/sdk).
Wallet primitives (src/):
- derive.ts: MetaMask personal_sign(bootstrap) → keccak → secp256k1
session keypair. Deterministic and re-derivable from MM
alone, no second backup.
- address.ts: computeAccountAddress() mirrors Cairo eth_address_to_account
so the deterministic Tier 2 account address is known
client-side before any on-chain action.
- sign.ts: signOwnership for factory.deploy_account, plus a hand-rolled
EIP-712 OutsideExecution signer that mirrors eth_712_utils.
cairo byte-for-byte. Cross-checked against the existing
eth_712_account/scripts/generate_test_signatures.py
(4 byte-identical parity tests live in tests/sign.test.ts).
- mm-signer.ts: MmSigner interface with FixedKeyMmSigner (tests) and
BrowserMmSigner (window.ethereum) implementations.
- constants.ts: typehashes, primer class hashes (test + prod), bootstrap
message, masks.
End-to-end sponsored flows (src/):
- paymaster.ts: AVNU paymaster JSON-RPC client (vendored / adapted
from paymaster/examples/private-sponsored-web).
Sponsored-private apply_action mode only — fees
paid from inside the privacy pool, never from the
MetaMask user's STRK.
- sponsored-deploy.ts: privateSponsoredDeployTier2Account — Alice's pool
notes pay AVNU gas; EarnDeployHelper.privacy_invoke
forwards (session_eth, signature) into the factory.
MetaMask never signs a Starknet tx.
- sponsored-execute.ts: privateSponsoredExecuteOnTier2Account — same
envelope but invoking EarnInvokeHelper, which
relays an EIP-712-signed OutsideExecution into the
deployed account. Includes counterIncrementCall +
readCounterCount helpers.
Browser demo (demo/):
- index.html / style.css — 6 step walkthrough.
- demo.ts — Connect MM → derive session → compute
Starknet addr → sign ownership → sponsored
deploy → call Counter.increment via Tier 2.
- esbuild bundle, python3 -m http.server for serving.
Deploy infrastructure (scripts/):
- deploy-contracts.ts: declares Primer, StarknetEth712Account,
AccountFactory, EarnDeployHelper, Counter; deploys
AccountFactory + EarnDeployHelper instances.
Idempotent declares; persists addresses to
sepolia-deployments.json and .env.
- deploy-extras.ts: declares EarnInvokeHelper; deploys a Counter
instance + EarnInvokeHelper instance for the demo.
Tests: 20 passing (3 files). The 4 EIP-712 parity tests are the
load-bearing ones: if they regress, signatures will fail
is_valid_signature on chain.
Secrets handling:
- .env / demo/config.ts are gitignored (.env.example is the template).
- Alice's key being shipped to the browser is documented in the demo
config as throwaway-only; production must lift this into a backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Output of `npm run deploy:contracts` + `npm run deploy:extras` on Starknet Sepolia (custom node at 34.133.167.123). Captures every class hash + singleton address so subsequent runs of the demo / wallet client can reuse them without redeploying. If anyone redeploys, the scripts will rewrite this file in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
End-to-end implementation of a Tier 2 unlinkable Starknet account, where the on-chain identity is a fresh secp256k1 "session key" derived from a one-time MetaMask
personal_sign, not the user's MetaMask wallet itself. Combined with the Starknet privacy pool + AVNU paymaster'ssponsored_privatemode, a Tier 2 account can be deployed and operated without any MetaMask-correlatable footprint on chain.Architecture
After deploy, the same envelope shape (
InvokeExternal → EarnInvokeHelper → account.execute_from_outside_v2) drives the deployed account through any subsequent call — demonstrated end-to-end againstCounter.increment().What's in the 3 commits
eth_addressfrom events, dropstrategy_implementation, newcounter+helperspackages withEarnDeployHelper+EarnInvokeHelper, bumpedPRIMER_CLASS_HASHfor scarb 2.16.1,casm = trueeverywhere.tier2_wallet/TS client + browser demo + sponsored flows — session-key derivation, Starknet address computation, EIP-712 signing (4 byte-identical parity tests against the existing Python reference), AVNU paymaster client, sponsored-private deploy/execute flow functions, full browser demo, Sepolia deploy scripts. 20 vitest passing.tier2_wallet/sepolia-deployments.jsonfor reproducibility.Honest privacy assessment
Tier 2 closes every cryptographic link between MetaMask and the Starknet account:
session_eth_address, not MM addr.It does not eliminate every linkability channel:
This is the strongest practical unlinkability available on Starknet today, equivalent in spirit to "Tornado Cash-grade privacy" — but only as strong as the anonymity set and operational hygiene around it.
Test plan
scarb buildclean across all 7 workspace packages (dropsstrategy_implementation, addscounter,helpers).cd tier2_wallet && npm install && npm test— 20/20 green.npm run deploy:contractsagainst a funded Sepolia admin — declares 5 classes, deploysAccountFactory+EarnDeployHelper.npm run deploy:extras— declaresEarnInvokeHelper, deploys it + aCounterinstance.npm run demo— openhttp://localhost:8088, run Steps 1-6 against MetaMask. Step 5 deploys the Tier 2 account via Alice; Step 6 increments the counter; both should leave no MM-correlatable trace.Notes for reviewers
account_factory/src/utils.cairo::PRIMER_CLASS_HASHwas bumped from the original0x00123e…to0x03edae21…. This is because scarb 2.16.1 produces a different hash than the previously-pinned scarb version. No prod deployment of the old hash existed when this changed.eth_addressineth_712_accountwas kept under that name for storage-layout stability across upgrades — only the parameter / interface name was changed toowner_eth_address.EarnDeployHelper.privacy_invokeincludes anote_id: felt252last arg — required by the privacy-pool helper protocol (it's serialized whether or not the helper uses it).🤖 Generated with Claude Code
This change is