Architecture overview
The PQ Smart Account stack adds quantum-resistant authorization to existing EVM smart accounts. No protocol changes, no new chains, no hard forks. Drop in an ERC-7579 validator module, point it at a Stylus verifier on Arbitrum, and selected operations now require an ML-DSA-65 signature instead of an ECDSA one.
Status: Proof of concept. End-to-end validated on a local Nitro devnode. Not audited. Not deployed on mainnet. See Production limitations.
Three layers
flowchart TD
subgraph OffChain["Off-Chain Tools"]
CLI["pq-keygen / pq-sign / pq-verify<br/>(Rust CLI)"]
Snap["MetaMask Snap<br/>(pq-snap)"]
Demo["WalletConnect Demo<br/>(demo/)"]
end
Bundler["Alto Bundler :4337"]
subgraph Arbitrum["Arbitrum (On-Chain)"]
EP["EntryPoint v0.7"]
Kernel["Kernel v3 Smart Account"]
subgraph Validation["Validation Pipeline"]
PQ["PQValidatorModule<br/>(Solidity · ERC-7579)<br/>Stores 1,952-byte public keys"]
Stylus["MLDSAVerifier<br/>(Stylus · Rust/WASM)<br/>ML-DSA-65 verify()"]
end
EP -->|"handleOps()"| Kernel
Kernel -->|"IValidator.validateUserOp"| PQ
PQ -->|"verify(pubKey, msgHash, sig)"| Stylus
Stylus -->|"bool"| PQ
PQ -->|"0 = success / 1 = failure"| Kernel
end
CLI -->|"eth_sendUserOperation"| Bundler
Snap -->|"eth_sendUserOperation"| Bundler
Demo -->|"eth_sendUserOperation"| Bundler
Bundler -->|"handleOps()"| EP
- Off-chain tools — ML-DSA key generation, signing, and UserOp submission. Available as a Rust CLI (
pq-keygen,pq-sign,pq-verify), a MetaMask Snap, and a WalletConnect dapp. - Solidity validator module (
PQValidatorModule) — ERC-7579 compliant. Stores per-account public keys, routes verification calls. - Stylus verifier (
MLDSAVerifier) — ML-DSA-65 signature verification in Rust/WASM at ~374K gas — 10–100× lower cost than pure EVM.
End-to-end transaction flow
Step │ Actor │ Action
──────┼────────────────────┼─────────────────────────────────────────
1 │ dApp │ Constructs UserOp calldata (swap, transfer, vote)
2 │ dApp / Wallet │ Fills UserOp:
│ │ sender: smart account address
│ │ nonce: PQ validator nonce
│ │ verificationGasLimit ≥ 2M
3 │ EntryPoint (query) │ getUserOpHash(userOp) → userOpHash
4 │ User (off-chain) │ Signs userOpHash with ML-DSA private key
│ │ → 3,309-byte signature
5 │ Bundler │ Receives userOp via eth_sendUserOperation
│ │ Simulates validation → accepts to mempool
6 │ Bundler │ Calls EntryPoint.handleOps([userOp], beneficiary)
7 │ EntryPoint │ Calls account.validateUserOp(userOp, userOpHash)
8 │ Kernel │ Reads nonce → identifies PQ validator (non-root)
│ │ Checks selector whitelist
│ │ Delegates to PQValidatorModule.validateUserOp()
9 │ PQ Validator │ Loads stored 1,952-byte public key
│ │ Calls Stylus: verify(pk, userOpHash, sig)
10 │ Stylus Verifier │ ML-DSA-65 verify (FIPS 204) → bool (~374K gas)
11 │ EntryPoint │ If valid → executes UserOp calldata
The dApp target contract sees a normal msg.sender — it does not need to know the transaction was PQ-verified.
What a dApp integration looks like
From a dApp's perspective, PQ-secured transactions are transparent. The dApp constructs its intent as normal calldata; only the signing step and UserOp nonce encoding change:
// dApp constructs a normal interaction
const callData = kernel.interface.encodeFunctionData("execute", [
targetContract, // e.g. Uniswap router
value, // ETH value
swapCalldata, // e.g. swapExactTokensForTokens(...)
]);
// UserOp uses PQ nonce + ML-DSA signature instead of ECDSA
const userOp = {
sender: kernelAccountAddress,
nonce: pqValidatorNonce, // ← encodes PQ validator
callData: callData, // ← standard dApp intent
signature: mlDsaSignature, // ← 3,309 bytes (vs 65 for ECDSA)
verificationGasLimit: 2_000_000 // ← higher for ML-DSA
};
Design decisions
- Arbitrum Stylus — native Rust/WASM execution. ML-DSA verification at ~374K gas versus millions in pure EVM. Today this is the only viable platform for on-chain lattice crypto. See Stylus maturity assessment.
- ML-DSA-65 (FIPS 204) — NIST Level 3, best balance of security and signature size for on-chain verification. See ML-DSA library comparison.
- ERC-7579 module pattern — plug-and-play with existing accounts (Kernel, Safe, Rhinestone). Users keep their existing key for low-stakes operations and upgrade specific selectors to PQ.
- Kernel v3 — ERC-7579 native, full E2E validated on local devnode.
- Alto bundler — open-source ERC-4337 bundler that works on Nitro devnode with
--chain-type arbitrum --safe-mode false. - Stateless Stylus verifier — public keys live in Solidity and are passed as calldata. Avoids cross-runtime storage issues and keeps the Stylus contract a pure function.
Related
- Stylus verifier — the cryptographic primitive
- ERC-7579 validator module — on-chain integration surface
- Local dev guide — bringing up the full stack locally