Architecture
The wallet consists of a browser extension that is the signing authority, and wallet.quantum.systems which connects to the extension via EIP-6963 when present. The extension holds the encrypted vault and performs all signing; the web app acts as a dApp interface to it.
Key storage and lifetimes
Two layers of storage, with explicit lifetime rules for every kind of key material.
┌─────────────────────────────────────────────────┐
│ Persistent storage (disk) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Vault (AES-256-GCM encrypted) │ │
│ │ { encryptedSeed, salt, iv, iterations, │ │
│ │ kdf: "pbkdf2", version: 1, authMethod } │ │
│ └─────────────────────────────────────────────┘ │
│ Never contains plaintext key material. │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Ephemeral session storage (memory-only) │
│ ┌─────────────────────────────────────────────┐ │
│ │ VMK (Vault Master Key) │ │
│ │ Derived from password via PBKDF2. │ │
│ │ Used to decrypt seed on-demand for signing.│ │
│ └─────────────────────────────────────────────┘ │
│ Cleared when browser closes. │
└─────────────────────────────────────────────────┘
Unlock flow:
- User enters password → PBKDF2-HMAC-SHA256 (100,000 iterations) → VMK.
- VMK stored in session storage (cleared on browser close).
- The wallet is now "unlocked" — the VMK is available for signing.
Signing flow (per-transaction):
- Read VMK from session → decrypt seed from vault → derive ML-DSA-44 key.
- Sign the UserOp hash.
- Immediately zero the seed bytes and private key bytes.
- Return the signature only.
Auto-lock:
- Configurable timer (1 / 5 / 15 / 30 minutes of inactivity).
- On trigger: clear VMK from session storage, set vault state to locked, emit
accountsChanged([])to connected dApps.
Key material lifetime rules:
- Encrypted seed: persisted in storage (always encrypted).
- VMK: in session storage only while unlocked.
- Decrypted seed: in memory only during the signing operation (milliseconds).
- ML-DSA private key bytes: in memory only during the signing operation (milliseconds).
- After signing: explicit zeroing of key bytes. Do not rely on garbage collection.
Bundler and contracts
- Production bundler: Pimlico (hosted). Supports Arbitrum Sepolia and mainnet, EntryPoint v0.7, and has been tested with 3,309-byte composite signatures.
- Contracts: Safe7579,
PQValidatorModule,VerifierGateway. Deployed via CREATE2 with deterministic salts so addresses match across chains.
See Deployed addresses for current deployments.
Chain rollout criteria
A chain is enabled only when all six criteria are met:
| # | Criterion | Verification |
|---|---|---|
| 1 | ML-DSA-65 verifier deployed | Source verification on block explorer |
| 2 | Gas validated (ML-DSA-65 verification < 30M on target chain) | Fork test against target chain |
| 3 | Safe infra + PQValidatorModule deployed at deterministic CREATE2 addresses | eth_getCode at expected addresses |
| 4 | Bundler accepts UserOps with 3,309-byte composite signatures | Submit test UserOp |
| 5 | Testnet E2E validated (deploy account → send ETH → ERC-20 → co-signer) | Automated test suite |
| 6 | Block explorer available for transaction receipts and UserOp traces | Manual verification |
Optional but recommended:
- P256 precompile check — if RIP-7212 is native, use it for passkey co-signing; otherwise fall back to Solidity P256 verification.
- Paymaster available — improves UX, not required.
A chain is added to the registry only after all six criteria pass and engineering and product sign off.
Related
- Overview and supported chains — feature status and supported chains
- Deployed addresses — current contract deployments per chain
- PQ Smart Account → ERC-7579 validator module — the PQValidatorModule this wallet uses