Documentation
MarvelousPrivacy is a privacy workspace: a browser-local encrypted file vault, a read-only transaction inspector, a clearly bounded private-pool integration for Robinhood Chain, and a local activity record. This page explains what each part does, what it stores, and where its limits are. The README in the repository carries setup details and test results.
Overview
- No account, no server-side state about you. The server hosts the pages and relays read-only RPC and verification lookups.
- The vault works offline-first after the first load. It never needs a wallet.
- Nothing here is audited. A clean build is not a security claim.
Encrypted vault
Key hierarchy
passphrase ──PBKDF2-HMAC-SHA-256 (600,000 iterations, 128-bit salt)──▶ KEK
KEK ──AES-256-GCM (96-bit IV, AAD = header)──▶ wraps the 256-bit vault key
vault key ──AES-256-GCM (fresh 96-bit IV per record, AAD = record id + purpose)──▶ file bytes, file metadata- The work factor follows the OWASP Password Storage Cheat Sheet recommendation for PBKDF2-HMAC-SHA256 (600,000 iterations). Salt length follows NIST SP 800-132; IV length follows NIST SP 800-38D.
- Every random value (salt, IVs, vault key, record ids) comes from
crypto.getRandomValues. A new IV is generated for every encryption; IVs are never reused under a key. - File contents and file metadata (name, MIME type, size, timestamps) are encrypted in separate records with separate IVs. The additional authenticated data binds each ciphertext to its record id and purpose, so records cannot be swapped.
- The vault key is imported as a non-extractable
CryptoKey. The raw bytes exist in JavaScript only during generation or unwrap and are overwritten immediately afterwards. - The passphrase and the raw key are never written to storage. Locking drops the key and decrypted metadata from memory. JavaScript cannot guarantee forensic erasure of memory that held plaintext.
- Changing the passphrase re-wraps the vault key under a new KEK with a fresh salt; file records are untouched.
What stays observable
Anyone with access to the browser profile can see that a vault exists, how many records it holds, the size of each ciphertext, the KDF parameters and the salt. Ciphertext length equals plaintext length plus a 16-byte tag, so file sizes are inferable. Timestamps are inside the encrypted metadata, but IndexedDB itself may record modification times.
Storage
Records live in IndexedDB (database marvelousprivacy, stores vault, files, activity). Clearing site data removes them. Settings offers persistent storage where the browser supports it, which protects against automatic eviction but not against deliberate clearing.
Backup format
An export is a JSON document (*.mpvault.json). It contains exactly what IndexedDB contains, base64-encoded. It is readable only with the passphrase.
{
"format": "marvelousprivacy-vault-backup",
"version": 1,
"exportedAt": "2026-10-02T12:00:00.000Z",
"vault": {
"vaultId": "uuid",
"createdAt": "ISO-8601",
"kdf": { "name": "PBKDF2", "hash": "SHA-256", "iterations": 600000, "salt": "base64 (16 bytes)" },
"cipher": { "name": "AES-GCM", "keyLength": 256, "ivLength": 96, "tagLength": 128 },
"wrappedKey": { "iv": "base64 (12 bytes)", "data": "base64 (32 + 16 bytes)" }
},
"files": [
{ "id": "uuid", "order": 1,
"meta": { "iv": "base64", "data": "base64 — AES-GCM(JSON{name,type,size,lastModified,addedAt})" },
"content": { "iv": "base64", "data": "base64 — AES-GCM(file bytes)" },
"ciphertextBytes": 12345 }
]
}Authenticated data
wrappedKey: AAD =marvelousprivacy:v1:vault-key:<vaultId>:<version>:<kdf name>:<hash>:<iterations>:<cipher>:<keyLength>:<ivLength>:<tagLength>files[].content: AAD =marvelousprivacy:v1:file:<id>files[].meta: AAD =marvelousprivacy:v1:meta:<id>
Import validation
Before any key derivation: format id and version, vault id shape, KDF name/hash, iteration count within 100,000–5,000,000, salt 16–64 bytes, cipher parameters, IV lengths, ciphertext sizes (≤ 25 MB + tag per file, metadata ≤ 16 KB), duplicate ids, file count ≤ 10,000 and total size ≤ 400 MB. Then the passphrase must unwrap the key and every file's metadata record must authenticate. Only then is the current vault replaced, in one transaction.
Transaction review
- Existing transaction:
eth_getTransactionByHash, receipt, block timestamp,eth_getCodeat the destination, decoded calldata and logs, ERC-20 metadata for involved tokens. - Proposed transaction: destination, value and calldata; decoded call; approval analysis;
eth_estimateGasandeth_callfrom the sender you give (or the connected wallet). Nothing is signed or broadcast. - Identification order: verified ABI from Sourcify (exact or runtime match) → standard interfaces (ERC-20/721/1155, WETH9, Permit2, Multicall3, ERC-4337 EntryPoint, Universal Router) → OpenChain selector candidates, marked unverified → unknown, shown raw.
- “Unlimited” means an allowance equal to 2²⁵⁶−1 (or 2¹⁶⁰−1 for Permit2). An allowance larger than the token's total supply is flagged as effectively unlimited.
- A contract having code, a Sourcify match, or a successful simulation is never presented as a safety verdict.
Supported networks: Robinhood Chain (4663), Robinhood Chain Testnet (46630), Ethereum (1), Arbitrum One (42161).
Stealth addresses (ERC-5564)
The supported privacy tool on Robinhood Chain. The ERC-5564 Announcer (0x55649E01B5Df198D18D95b5cc5051630cfD45564) and ERC-6538 Registry (0x6538E6bf4B0eBd30A8Ea093027Ac2422ce5d6538) singletons have code on chain 4663 since block 8,283,577; the Announcer bytecode is byte-identical to the Ethereum and Arbitrum deployments, and both contracts are a Sourcify match on 4663 (submitted 2026-10-02 with the mainnet standard-JSON input, compiler 0.8.23).
- Keys: one
personal_signover a fixed, chain-bound message → keccak256 → two secp256k1 scalars (spending, viewing). Re-signing reproduces them; an encrypted copy can be saved in the vault. - Register:
registerKeys(1, metaAddress)on the registry, from your wallet, after review. - Send (ETH only): ephemeral key → ECDH with the recipient's viewing key → one-time address; transaction 1 sends the ETH, transaction 2 calls
announce(1, address, ephemeralPubKey, metadata). ERC-20 is deliberately not offered: spending from the stealth address would need gas from a wallet that links it. - Scan:
eth_getLogson the Announcer in 5,000,000-block windows through the relay, cached in IndexedDB (public data); view-tag prefilter, then full derivation in memory. - Sweep: the stealth private key is
(spending + keccak256(sharedSecret)) mod n; the transaction is signed in the browser and forwarded by/api/broadcast. - What observers see: the sender's wallet paying a fresh address, the announcement, the amount and timing. The recipient's identity is hidden until they move funds to an address linked to them.
- Tests: unit vectors (
tests/stealth.test.ts) and an Anvil fork of chain 4663 running register → pay + announce → scan → match → sweep against the real bytecode (pnpm test:fork).
Private pools
Status: integration not configured (checked 2026-10-02). The survey covered Privacy Pools (0xbow), RAILGUN, Robinhood Chain ecosystem listings, Privacy Hood (self-described zk pool), VeiledHood (self-described shielded vault). None publishes a verified, audited pool deployment on Robinhood Chain (4663) or its testnet (46630): Privacy Pools and RAILGUN are not deployed here; Privacy Hood is in a testnet phase without published contracts; VeiledHood's contracts are unverified and its withdrawals need an operator signature (custodial ledger). The pools page probes these addresses live (eth_getCode + Sourcify) instead of showing a placeholder form, and src/lib/pools/registry.ts lists the six dependencies a legitimate integration needs. No pool deposit or withdrawal control exists; no custody contract of our own exists.
Activity and settings
- Three scopes: Local (vault operations, counts only in clear; names sealed under the vault key), Public on-chain (hashes you inspected), Connection (wallet/network events with a shortened address).
- Clearing history deletes this device's log only. Blockchain records cannot be cleared by anyone.
- Settings: auto-lock timeout, backup export/import, storage usage and persistence, wallet disconnect, default network, per-network RPC override, motion, and “clear all local data” with typed confirmation.
Limitations
- No streaming encryption: a file is read into memory and encrypted in one call. The per-file limit is 25 MB.
- Backups are JSON with base64 fields, roughly 1.35× the ciphertext size, built in memory.
- A compromised browser, extension or device defeats client-side encryption; the vault protects data at rest and in transit, not against malware on the machine.
- RPC providers and Sourcify see the addresses, hashes and calldata you look up. The relay hides your IP from them; a custom RPC does not.
- Selector-database names are community-submitted and may be wrong or ambiguous.
- Simulation reflects the latest state the endpoint served; inclusion can differ.
Integration sources
| Item | Source |
|---|---|
| Robinhood Chain ids, RPC, explorer | docs.robinhood.com/chain/connecting; public endpoints from chainlist (publicnode, dRPC) |
| Verified ABIs | Sourcify API v2 — sourcify.dev/server/v2/contract/{chainId}/{address}; chains 1, 42161, 4663, 46630 listed as supported |
| Selector candidates | OpenChain signature database — api.openchain.xyz |
| Standard interfaces | EIP-20, EIP-721, EIP-1155, EIP-2612, WETH9, Uniswap Permit2, Multicall3, ERC-4337 EntryPoint v0.6/0.7/0.8, Uniswap Universal Router |
| Privacy Pools (0xbow) | https://docs.privacypools.com/deployments — Ethereum Mainnet |
| RAILGUN | https://docs.railgun.org — Ethereum, Polygon, BNB Chain, Arbitrum One, Sepolia |
| Robinhood Chain ecosystem listings | https://docs.robinhood.com/chain — No privacy protocol listed for chain 4663 / 46630 |
| Privacy Hood (self-described zk pool) | https://www.privacyhood.org/whitepaper — Testnet phase per its whitepaper; no contract addresses, audit or source published |
| VeiledHood (self-described shielded vault) | https://www.veiledhood.com/ — Contracts on 4663 but unverified; off-chain ledger with operator-signed withdrawals (custodial); own FAQ: testnet, not audited |
| PBKDF2 work factor | OWASP Password Storage Cheat Sheet (PBKDF2-HMAC-SHA256: 600,000) |
Setup and deployment
pnpm install
pnpm dev # http://localhost:21500
pnpm test # vitest: crypto, format, engine, decoder
pnpm typecheck && pnpm lint && pnpm build
pnpm smoke # route + header checks against a running serverEnvironment variables are optional and server-side only: ROBINHOOD_RPC_URL, ROBINHOOD_TESTNET_RPC_URL, ETHEREUM_RPC_URL, ARBITRUM_RPC_URL (private RPCs tried before the public ones), RESOLVE_OVERRIDE (DNS pins for developer machines). NEXT_PUBLIC_SITE_URL is the only public variable and only feeds metadata. There are no secrets in the browser bundle, no analytics and no remote scripts; the Content-Security-Policy is nonce-based and set per request.