Documentation
MarblePrivacy is a workspace for your wallet, your data, and what you choose to reveal. It has four tools and one rule: it describes what it does, and nothing more. This page says what each tool does, what it stores, where requests go, and where each protection ends.
Overview
| Tool | What it does | Where the data comes from |
|---|---|---|
| Wallet Inspector | Reads a public address, a transaction, a call you are about to sign, or an approval. Read-only except for revoking an approval you choose to revoke. | Live data: JSON-RPC through this site’s relay, plus an explorer index for history and tokens |
| Notes Vault | Private notes, optionally about an address or a transaction, encrypted with a passphrase. | Local only: this browser’s IndexedDB |
| Screen Privacy | Conceals balances, addresses, hashes and amounts in the interface. | Local only: a preference in this browser |
| Stealth Addresses | ERC-5564 on Robinhood Chain: derive keys, register, send to and receive at one-time addresses. | Live data: the Announcer and Registry contracts on chain 4663 |
- No account and no server-side record of you. The server hosts the pages and relays read-only lookups.
- The inspector, the vault and Screen Privacy work without a wallet. A wallet is needed only to sign: revoking an approval, or the stealth-address flows.
- MarblePrivacy never asks for a seed phrase or a private key.
- Nothing here is audited. A clean build and a passing test suite are not a security audit.
Privacy explanation
Every piece of information in this product lives in one of four places. Which one decides who can see it.
| Place | What is there | Who can see it |
|---|---|---|
| Public on-chain | Addresses, balances, transfers with their amounts and times, token approvals, contract calls, stealth announcements and registrations. | Everyone, permanently. Nothing in MarblePrivacy changes that. |
| Stored in this browser | The vault (ciphertext, plus its salt, IVs and sizes), preferences, the local activity log, a cache of public stealth announcements, the wallet connector’s last state. | Whoever has this browser profile. Notes are unreadable without the passphrase. |
| Sent to providers | What you look up: an address, a transaction hash, calldata, a contract address, a 4-byte selector. A stealth withdrawal you signed. | This site’s server, then the RPC provider, the explorer index, Sourcify or the selector database. They see the query and the server’s address, not yours — unless you set your own RPC, which then sees you directly. |
| Hidden only on screen | Whatever Screen Privacy conceals. | Not the person looking at your screen. Everyone listed above sees what they saw before. |
What this does not make private
- Connecting a wallet does not make its activity private. Neither does disconnecting it.
- An ordinary transaction sent from here is as public as one sent from anywhere.
- A balance hidden by Screen Privacy is still on the chain for anyone to read.
- A note about an address is your private remark; the address and everything it did stay public.
- Disconnecting a wallet does not revoke token approvals. They stay in force until a revoke transaction confirms.
What is never claimed
Anonymity, untraceability, zero-knowledge protection, mixing, private execution or private trading. None of them is implemented, so none of them is offered. The single on-chain privacy mechanism is stealth addresses, and its guarantee is narrow: see below.
Hosting
A hosting provider keeps ordinary access logs (IP address, time, path) like any web host. Address lookups for history and tokens travel in the request body, not in the path. The application adds no identifiers, sets no cookies and loads no analytics or third-party scripts.
Wallet Inspector
Address
- Balance, sent-transaction count (the nonce) and code are read at one block with
eth_getBalance,eth_getTransactionCountandeth_getCode. The block number and time are shown with the result. - An account is described as having no code, as a contract, or as delegating to code under EIP-7702 when its code is the 23-byte designator
0xef0100…. - Amounts are handled as integers from the smallest unit. The short form truncates (it never rounds up) and marks the cut; the exact value and the wei figure are shown next to it.
- Mixed-case input must pass the EIP-55 checksum; a mismatch is reported as a probable typo. Names are not resolved.
Recent activity and token balances
Plain JSON-RPC cannot list an account’s history or the tokens it holds, so these come from an explorer index when one answers: the newest 25 transactions the address sent or received, and its ERC-20 balances. This is partial by construction — internal transfers, token movements inside other contracts’ transactions and older history are not listed — and the panel names its source and the time of the answer. When no index answers, the panel says which one was asked and why it failed; nothing is shown in its place.
- Token names and symbols are whatever the token contract says. Unsolicited and impersonating tokens are common; a listing is not an endorsement. No prices are shown.
- A single token can always be checked directly on the chain: paste its contract address and the balance is read from the contract itself.
Transaction, proposed call, allowances
- Transaction: status, block, parties, value, fee, the decoded call, approvals it granted and the events it emitted.
- Proposed call: destination, value and calldata are decoded; gas is estimated and the call simulated with
eth_callfrom the sender you give. Nothing is signed or broadcast. - Identification order: verified ABI from Sourcify → standard interfaces (ERC-20/721/1155, WETH9, Permit2, Multicall3, ERC-4337 EntryPoint, Universal Router) → signature-database candidates, marked unverified → unknown, shown raw.
- Allowances: reads the connected wallet’s ERC-20 allowance, operator approval or Permit2 allowance for a spender, and builds exactly one kind of transaction: the revoke. It is shown decoded, with a gas estimate, a fee ceiling and a simulation, before the wallet is asked to sign.
- Contract code, a Sourcify match or a successful simulation is a fact, never a safety verdict.
Notes Vault
Key hierarchy
passphrase ──PBKDF2-HMAC-SHA-256 (600,000 iterations, 128-bit random salt)──▶ KEK
KEK ──AES-256-GCM (96-bit random IV, AAD = vault header)──▶ wraps the 256-bit vault key
vault key ──AES-256-GCM (fresh 96-bit random IV per save, AAD = record id)──▶ one note per record- The work factor is the OWASP Password Storage Cheat Sheet figure for PBKDF2-HMAC-SHA256. Salt length follows NIST SP 800-132, IV length NIST SP 800-38D. The passphrase is normalised with Unicode NFKC before derivation.
- Every random value comes from
crypto.getRandomValues. A new IV is generated for every encryption, including every edit of the same note. - A note’s title, text, linked address or transaction hash, network and timestamps are one JSON document, sealed as one ciphertext. The record id is bound as additional authenticated data, so a ciphertext cannot be moved to another record.
- The vault key is a non-extractable
CryptoKey. The passphrase, the derived key and the plaintext are never written to storage and never placed in a request. - Locking drops the key and the decrypted notes from memory; an unsaved draft is sealed first. The vault also locks itself after the idle time set in the Privacy Panel.
- Changing the passphrase re-wraps the vault key under a new salt; note records are untouched.
What you must know
- A forgotten passphrase cannot be recovered. MarblePrivacy never sees it, so nobody can reset it.
- Clearing browser data removes the vault. An exported backup is the only copy that survives.
- Encryption protects the stored notes. It does not protect an unlocked vault from a compromised device, a malicious browser extension or a script running in the page.
- Record count, each ciphertext’s size (plaintext + 16 bytes), the KDF parameters and the salt are visible to anyone with the browser profile.
Limits: 160 characters per title, 60,000 characters per note (256 KB sealed), plain text only. Note text is always rendered as text, never as markup.
Backup format
An export is a JSON document (*.marblevault.json) holding exactly what IndexedDB holds, base64-encoded. It can be exported while the vault is locked, because it is ciphertext. Only the passphrase opens it.
{
"format": "marbleprivacy-vault-backup",
"version": 1,
"exportedAt": "ISO-8601",
"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)" }
},
"notes": [
{ "id": "uuid", "order": 1,
"sealed": { "iv": "base64", "data": "base64 — AES-GCM(JSON{title,body,ref,tag,createdAt,updatedAt})" },
"ciphertextBytes": 345 }
]
}wrappedKeyAAD:marbleprivacy:v1:vault-key:<vaultId>:<version>:<kdf>:<hash>:<iterations>:<cipher>:<keyLength>:<ivLength>:<tagLength>notes[].sealedAAD:marbleprivacy:v1:note:<id>
An imported file is treated as untrusted. Before any key derivation: format id and version, vault id shape, KDF name and hash, iterations within 100,000–5,000,000, salt 16–64 bytes, cipher parameters, IV lengths, ciphertext sizes, duplicate ids, at most 5,000 notes and 96 MB. Then the passphrase must unwrap the key and every note must authenticate and decode — with each field type-checked and length-capped — before the current vault is replaced, in one transaction.
Screen Privacy
- One switch, in the sidebar and in the Privacy Panel. When it is on, balances, addresses, transaction hashes and amounts are not drawn — they are not in the page at all — until you press the reveal control next to one. Typed addresses, hashes and amounts show as dots.
- Turning it on hides again everything you had revealed one by one. Explorer links and copy buttons are concealed with the value they belong to.
- In a signing dialog the transaction details are concealed like everything else, and the sign button stays disabled until you reveal them. Nothing is signed unread.
- It is screen concealment for shared screens, recordings and screenshots. It changes nothing on any chain and nothing a provider receives.
Stealth addresses (ERC-5564)
The one on-chain privacy mechanism here. The ERC-5564 Announcer (0x55649E01B5Df198D18D95b5cc5051630cfD45564) and the ERC-6538 Registry (0x6538E6bf4B0eBd30A8Ea093027Ac2422ce5d6538) have code on Robinhood 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 chain 4663.
| It hides | It does not hide |
|---|---|
| Who received a payment: each one goes to a fresh address derived from the recipient’s published keys, and that two payments went to the same person. | The sender, the amount, the time, the one-time address and the announcement. A withdrawal to an address already known to be yours links the payment to you. |
- Keys: one
personal_signover a fixed, chain-bound message → keccak256 → two secp256k1 scalars (spending, viewing). Signing again reproduces them; an encrypted copy can be saved as a note in the vault. Anyone holding that signature can find and spend the funds. - Register (optional):
registerKeys(1, metaAddress)on the registry — a public transaction from your wallet. - Send (ETH only): transaction 1 pays the one-time address, transaction 2 calls
announce. Both are shown decoded with gas, a fee ceiling and a simulation before signing; rejection, submission, confirmation, replacement and failure are separate, visible states. - Receive: the Announcer’s logs are read in 5,000,000-block windows through the relay and cached locally (they are public data); matching happens in memory.
- Withdraw: the transaction is signed in the browser with the derived stealth key and forwarded unchanged by
/api/broadcast, which only checks that the bytes are a signed transaction for the stated chain. - ERC-20 payments are not offered: spending them would need gas from a wallet that links the address, and no relayer is configured.
- The reference contracts’ audit status is not asserted, and this application is not audited.
Shielded pools and private trading
Status: integration required (checked 2026-10-02). No verified shielded-pool or private-execution protocol exists on chain 4663: Privacy Pools (0xbow), RAILGUN, Robinhood Chain ecosystem listings, Privacy Hood (self-described zk pool), VeiledHood (self-described shielded vault) were checked against their own documentation. The Stealth Addresses page probes the candidate addresses live (eth_getCode plus Sourcify) and lists the 6 things a legitimate integration needs. No pool deposit, withdrawal or private-swap control exists in this build, and no custody contract of our own.
Networks and providers
| Network | Chain id | RPC endpoints the relay tries | Index |
|---|---|---|---|
| Robinhood Chain | 4663 | rpc.mainnet.chain.robinhood.com, robinhood-rpc.publicnode.com, robinhood.drpc.org | robinhoodchain.blockscout.com (Blockscout API v2); api.etherscan.io (API v2, needs a key) |
| Robinhood Chain Testnet | 46630 | rpc.testnet.chain.robinhood.com | explorer.testnet.chain.robinhood.com (Blockscout API v2) |
| Ethereum | 1 | ethereum-rpc.publicnode.com, eth.drpc.org, cloudflare-eth.com | eth.blockscout.com (Blockscout API v2); api.etherscan.io (API v2, needs a key) |
| Arbitrum One | 42161 | arbitrum-one-rpc.publicnode.com, arb1.arbitrum.io, arbitrum.drpc.org | arbitrum.blockscout.com (Blockscout API v2); api.etherscan.io (API v2, needs a key) |
- The browser talks to this site only.
/api/rpcis a read-only JSON-RPC relay with a method allow-list and upstreams fixed by configuration;/api/indexerqueries the explorer index;/api/abiasks Sourcify;/api/selectorasks the OpenChain signature database;/api/broadcastforwards an already-signed stealth withdrawal. - History and token balances come from the networks’ Blockscout explorers, key-free. An address an explorer has not served recently can take around twenty seconds. If an explorer refuses the deployment’s requests, the panel says so; an Etherscan API v2 key (
ETHERSCAN_API_KEY) adds a second source for transaction history. - Setting your own RPC URL in the Privacy Panel bypasses the relay for that network. That provider then sees your IP address and every query.
- A wallet extension makes its own requests to its own provider; that is outside this site’s control.
- The Privacy Panel checks every provider live and shows which ones answered.
Local data
| Data | Where | Form |
|---|---|---|
| Vault header | IndexedDB | KDF parameters and salt in clear; the vault key encrypted |
| Notes | IndexedDB | AES-256-GCM ciphertext only |
| Activity log | IndexedDB | Event kinds, counts, times, inspected transaction hashes and shortened addresses in clear; note titles sealed under the vault key |
| Stealth announcement cache | IndexedDB | Public on-chain data, copied |
| Preferences | localStorage | Clear (nothing secret): auto-lock, motion, network, Screen Privacy, RPC overrides |
| Wallet connector state | localStorage (wagmi) | Which connector was used last |
The Privacy Panel lists what is actually stored, with sizes, and “Clear all local data” removes all of it after a typed confirmation. It cannot remove anything from a blockchain.
$MARBLE
$MARBLE is the token of MarblePrivacy. Its contract address on Robinhood Chain (chain 4663) is 0x1e9B57B1986142ddAf11E0a116D4B4A83D34eB2F (Blockscout). The token section of the landing page shows what that contract itself answers (name, symbol, decimals, total supply), read through the relay.
The address is published in one place by a script that first reads the contract on-chain, so the site never shows an address nobody checked. Every tool here works without the token, and holding it changes nothing about what a public chain shows. Official account: X @MarblePrivacy.
Setup and deployment
pnpm install
pnpm dev # http://localhost:21700
pnpm test # vitest: vault crypto and format, inspector parsing, decoder, stealth vectors
pnpm test:fork # Anvil fork of Robinhood Chain: the stealth flow against the real bytecode
pnpm typecheck && pnpm lint && pnpm build
pnpm smoke # routes, headers, relay allow-list, lookups — against a running server
pnpm e2e:browser # headless Chrome: vault, inspector, screen privacy, wallet, mobile, reduced motionEnvironment variables are optional and server-side only: ROBINHOOD_RPC_URL, ROBINHOOD_TESTNET_RPC_URL, ETHEREUM_RPC_URL, ARBITRUM_RPC_URL (private RPC endpoints tried before the public ones), ETHERSCAN_API_KEY (address history where the key-free index is unavailable) and 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. The Content-Security-Policy is nonce-based and set per request, so every page renders dynamically. The project is a standard Next.js application and deploys to Vercel as is.
Limits
- Not audited. Treat it as unaudited software.
- A compromised browser, extension or device defeats client-side encryption. JavaScript cannot guarantee that memory which held plaintext is erased.
- History and token balances are as complete as the explorer index that answered, and no more.
- Simulation reflects the state the endpoint served; the outcome can differ once a transaction is included.
- Signature-database names are community-submitted and may be wrong or ambiguous.
- Injected wallets only. WalletConnect is not included.
- Stealth addresses hide the recipient and nothing else; shielded pools and private trading are not available.