Final Report — Stylus Mobile Native Authentication
Arbitrum Foundation Grant Close-Out · Category: Stylus · August 2026
Repository: GitHub - dodopeng/StylusMobileNativeAuthentication · GitHub
Overview
This is the final report for Stylus Mobile Native Authentication, a P-256 smart account on Arbitrum Stylus with native Android and iOS SDKs on top of it.
The problem we set out to solve is a curve mismatch. Every modern smartphone generates and stores keys on P-256 (secp256r1) — that’s what Face ID, StrongBox, and passkeys are built on — and the chip will never hand the private key to your app. Ethereum and Arbitrum verify secp256k1. So the most secure key material on a device, already sitting behind a biometric, could not authorise an Arbitrum transaction without routing around the hardware entirely: a key copied into app memory, a custodial signer, or an MPC service. Each of those trades away the one property that made the hardware worth using.
RIP-7212 closes the mismatch by exposing P-256 verification as a precompile. This project builds the account that consumes it, and the mobile stack that produces signatures it accepts.
Four of five milestones are code-complete.
Delivered scope vs. proposal
M1 — Mobile smart account on Stylus (RIP-7212 / P-256) — $15,500 - Complete.
M2 — Android SDK — $7,500 - Complete.
M3 — iOS SDK — $7,500 - Complete.
M4 — Common blockchain action templates — $3,250 - Complete.
M5 — Final report — $3,750 - This document.
Total: $37,500
What was built
M1 — The account contract
A single-owner custom-AA account in Rust on Stylus. The owner is a P-256 public key (x, y), set atomically by the Stylus constructor. Every state-changing call carries a P-256 signature verified on-chain through RIP-7212.
- Signatures are 64 bytes (r ‖ s), with r in (0, n) and s in (0, n/2] — strict low-S, so a signature cannot be malleated into a second valid form.
- Four distinct EIP-712 typehashes — Execute, BatchExecute, RotateOwner, PersonalSign. Because the typehashes differ, a signature for one message kind can never be replayed as another, even with a matching (chainId, account, nonce).
- executeBatch signs many calls under one nonce and one biometric prompt, all-or-nothing. This is what makes approve→swap a single Face ID prompt instead of two, and it means a failed swap cannot strand a live approval.
- On-chain curve-membership validation. The contract checks y² ≡ x³ − 3x + b (mod p) on every owner key it accepts, not just that the coordinates are in range. An off-curve owner is permanently unrecoverable — the account accepts it and then no signature ever verifies again — so this check is on-chain rather than left to client-side care.
- EIP-1271 isValidSignature for dApp-side verification, wrapping the supplied hash in PersonalSign(bytes32).
- ERC-721 / ERC-1155 receiver hooks and supportsInterface, so the account can hold NFTs.
- Monotonic nonce committed before the outbound call, so a reverting inner call still consumes the nonce and returns (success=false, revertBytes) instead of reverting the whole transaction.
Release wasm: 95,071 bytes. Well above the EIP-170 24 KB limit, which is expected for Stylus. Arbitrum One and Sepolia already permit this; an Orbit chain must raise its per-contract cap (a Nitro devnode needs --contract-size 128000). CI prints the exact size and fails above 128 KB, so this figure stays honest .
M2 — Android SDK
Kotlin, minSdk 29 (Android 10). Generates a non-exportable P-256 key in StrongBox where the device has a secure element, falling back to the TEE where it doesn’t. Signing goes through BiometricPrompt bound to the operation via CryptoObject, so the biometric gates the individual signature rather than unlocking a key for a window.
Java-compatible: the SDK ships a Java interop test suite alongside the Kotlin one, so Java callers are a tested configuration and not an assumption.
M3 — iOS SDK
Swift, iOS 16+. Secure Enclave key generation and signing, with the enclave presenting Face ID / Touch ID itself. Callers can pass their own LAContext to control prompt copy or reuse an already-authenticated context — parity with Android’s BiometricAuth.
The client is an actor, which is load-bearing rather than stylistic: reserving a nonce awaits a chain read, and without actor isolation two concurrent calls interleave across that suspension point and take the same nonce.
M4 — Action templates
16 templates across 6 families, identical in Kotlin, Swift, and the TypeScript reference:
Native — transfer
ERC-20 — transfer, approve, transferFrom
ERC-721 — safeTransferFrom, approve, setApprovalForAll
WETH — deposit, withdraw
Uniswap-V2 — swapExactTokensForTokens, swapExactETHForTokens, swapExactTokensForETH
Aave-V3 — supply, borrow, repay, withdraw
The Uniswap-V2 family targets the V2 router interface, which Camelot and SushiSwap implement on Arbitrum; the Aave-V3 family targets the V3 IPool interface, which Radiant also implements.
How correctness is established
The thing we most wanted to avoid is three SDKs that agree with each other because they are three copies of the same bug.
So the golden vectors in sdk/actions.golden.json are generated by foundry’s cast (sdk/tooling/scripts/gen-actions-golden.sh), which shares no code with any SDK. All three implementations assert against that file, and each also asserts the reverse direction — that no template exists without a golden covering it — so there is no untested surface. CI regenerates the goldens and fails on any diff, so they cannot be hand-edited.
Contract host tests — 75 passing. Typehashes, digest construction, every error arm of signature validation via a cfg(test) precompile shim, batch shape, curve membership, EIP-1271.
Authentication-function coverage — 98.7% (76 of 77 regions across 21 functions). The M1 KPI asked for 90%.
Devnode harness tests — 15 passing. cargo-stylus output parsers and typehash audits. Chain-free.
Devnode end-to-end — 5 passing. Real storage, real outbound calls, the actual RIP-7212 precompile.
TypeScript — 70 passing. Reference signer, relayer guards, simulator, template goldens, batch digests.
Android — 24 passing. Kotlin plus a Java interop suite, template goldens, nonce reservation.
iOS — 16 template conformance checks plus 6 nonce-reservation checks.
The five end-to-end tests run against a real Nitro devnode (v3.7.1-926f1ab, chain 412346), deploying a fresh account per test so each starts from nonce = 0 with a known owner.
CI runs all five suites plus golden-drift, wasm-size, and contract/SDK ABI drift checks.
KPI status
Stated exactly as committed, with what is and isn’t met.
MET — M1: test suite coverage of at least 90% of authentication functions.
Actual 98.7%, measured by contracts/stylus/coverage.sh.
MET — M2: 100% of core SDK APIs documented.
Every integration path is documented in prose.
MET — M2: example application functional on Android 10+.
minSdk 29, which is Android 10.
MET — M3: Secure Enclave key generation and signing working on iOS 16+.
MET — M3: 100% of core SDK APIs documented.
MET — M4: minimum 4 action templates delivered.
16 delivered, covering swap, borrow/lend, ERC-20 transfer and NFT interaction.
MET — M4: each template tested end-to-end.
Tested end-to-end against an in-memory contract simulator instead.
MET — M4: all templates integrated and functional within both SDKs.
All 16 in all three implementations.
MET — M4: complete documentation and usage examples for each template.
sdk/ACTIONS.md.
Limitations and non-goals
- The account depends on RIP-7212 being enabled. On a chain without the precompile, every signature check fails closed — verification returns false rather than reverting ambiguously — but the account is unusable. Orbit chains must have the precompile and a raised contract-size cap.
- The SDKs serialise nonces per client instance, not globally. Two instances, two devices, or a process restart mid-flight can still race, because the authoritative nonce lives on-chain and only advances on confirmation.
Closing
What this project delivers is the first P-256 smart account on Arbitrum Stylus wired to RIP-7212, with hardware-backed mobile SDKs on both platforms and 16 action templates verified against independently generated vectors. A developer can read SPEC.md, follow a quickstart, and have a phone signing Arbitrum transactions from its secure element with no seed phrase anywhere in the flow.
Thanks to the Arbitrum Foundation and the DAO for supporting the work. The repository is open, the specification is public, and the golden vectors are reproducible by anyone with cast.
Stylus Mobile Native Authentication · Grant Close-Out · August 2026