Skip to content
rhmask

Documentation

Everything, stated plainly

How the product works, what it protects, and exactly where the protection stops. If a claim is not backed by a check you can run, it is not in here.

[Chain 4663] rh-mask/rhmaskUpdated with every release

Quick start

There is nothing to install and nothing to sign up for. Open the app, generate a pair of keys, and you have a meta-address you can hand out for the rest of the project’s life.

  1. 01Open the app. Go to the Ghost Receive tab and press Generate keys. Two private keys are created in your browser.
  2. 02Back them up. Download the JSON backup, or set a passphrase so the keys are encrypted at rest on this device.
  3. 03Share the meta-address. Copy the text or show the QR. It is safe to publish; it cannot spend anything.
  4. 04Get paid. Every payer derives a fresh one-time address from it. Claim their receipt QR, then sweep.
The whole app runs without a single secret. Endpoints that need a server key answer 503 naming the exact variable, and the interface shows that message instead of a blank state.

Keys and meta-address

RhMask implements ERC-5564 stealth addresses on secp256k1. You hold two private keys. The spending key moves funds. The viewing key only recognises which payments are yours, so it can be handed to an accountant without giving up control.

Your meta-address is the two matching public keys joined together:

st:eth:0x <33-byte spending pubkey> <33-byte viewing pubkey>
              └─ 132 hex characters in total

A sender turns that into a one-time address nobody can link back to you:

ephemeral r,  R = r·G
S        = r · K_view              (sender)   ==  k_view · R   (you)
h        = keccak256(compress(S)),  view tag = h[0]
P_stealth = K_spend + h·G
address   = keccak256(uncompress(P_stealth)[1:])[12:]

The view tag is one byte. Scanning checks it first and only does the full elliptic-curve work on a match, which is what keeps a scan fast once the announcer contract exists.

KeyCan doCannot do
Spending keyMove funds from any stealth addressNothing is out of reach; guard it
Viewing keyFind which payments belong to youSpend anything
Meta-addressReceive paymentsSpend, or reveal your balance
Losing the spending key loses every unswept stealth balance. There is no recovery, no server copy, and no support channel that can restore it. Back it up before you receive anything real.

Private Pay

A private payment is three scans and no server. Every step happens in a browser, and the receipt QR carries the announcement until the on-chain announcer contract ships.

StepWhoWhat happens
RequestRecipientBuilds a payment request from the meta-address, plus optional token, amount and memo. Shown as a link and a QR.
SendPayerScans it, derives a one-time address, sends from any wallet on Robinhood Chain, receives a receipt QR.
ClaimRecipientScans the receipt. The viewing key verifies it, the spending key unlocks it, balances load.
SweepRecipientMoves the funds anywhere, signed in the page with the derived key.

Both formats are plain URLs, so a phone camera opens the right screen with no app install:

Payment request
https://rhmask.org/pay?to=st:eth:0x…&token=NVDA&amount=1.5&memo=invoice%2042

Receipt / announcement
https://rhmask.org/app?claim=1&addr=0x…&eph=0x…&tag=87&tx=0x…#receive

addr is the one-time address, eph the compressed ephemeral public key, and tag the view tag. Those three fields map one-to-one onto the ERC-5564 announcement event, so the scanner will replace the QR without a format change.

Send the receipt. Without the ephemeral key the recipient cannot find the payment, even though the funds are already theirs on chain.

Mask Swap

Swaps are filled through an intent router rather than a public order book, so the order never sits in a mempool as a visible trade. Pick an asset from 189 across 35 chains, enter a human amount, and the quote prints the venue and the flat fee before anything is sent.

The receiving address can be a fresh stealth address derived from your own meta-address in one click, so a swap and a private receive become a single action.

Live deposit addresses need a router partner key on the server. Until it is configured the quote endpoint answers 503 and the card shows dry quotes, clearly labelled.

Security model

Five properties hold for every release, and each one is checked rather than asserted.

  • Keys never leave the device. Generation, derivation and recognition are pure client-side code. No fetch sits in the key path.
  • The server holds nothing. No custody, no private keys, no session. Secrets are server-only env vars used to reach third-party routers.
  • Encrypted at rest. Optional passphrase wraps the keys with AES-256-GCM, key derived by PBKDF2-SHA256 over 310,000 rounds via WebCrypto.
  • Fail closed. A missing key produces a named 503, never a silent fallback that might leak or mislead.
  • Verified against mainnet. A read-only script simulates real transfers from real holders before each release. It signs nothing.

What is not hidden matters just as much: on-chain settlement is public, the venue filling a swap sees the deposit and the receiving address, and your IP reaches whichever RPC you use unless you run your own node.

Found a problem? Report it privately by email rather than in a public issue. Details are in SECURITY.md.

API reference

Every route is a Next.js handler in the same deployment. Reads are cached where it is safe, and nothing that touches a key is exposed.

MethodPathNotes
GET/api/healthLiveness and chain id
GET/api/chainChain params plus a live block number
GET/api/tokensStock token registry on Robinhood Chain
GET/api/router-tokensAssets the intent router can fill, cached 10 minutes
POST/api/quotePrivate fill quote; 503 when the router key is absent
GET/api/order/:depositSettlement status for a routed order
GET/api/vaultVault totals, basket and proof ledger
POST/api/rpcJSON-RPC pass-through, allow-listed read methods plus raw send

The pass-through exists because some networks hijack the chain’s RPC domain at the DNS level. Routing through the app’s own origin keeps balance reads and sweeps working where the direct endpoint is filtered. Batches are capped and log ranges bounded so the public RPC is not abused through us.

curl -X POST https://rhmask.org/api/rpc \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'

Browser extension

A stealth address only protects you if you use it at the moment someone asks for an address. The extension moves that choice to exactly that moment: a chip appears beside any address field, one click pastes a fresh one-time address, and the receipt is kept locally.

It shares the key format with the app, so one backup and one passphrase work in both. It has no backend and requests no host permissions: the content script runs only on the tab where you clicked the toolbar icon.

git clone https://github.com/rh-mask/rhmask.git
cd rhmask && npm install
npm run build:extension       # bundles into extension/dist
# chrome://extensions → Developer mode → Load unpacked → extension/dist

Run it yourself

The repository is public and the app builds with no secrets at all.

git clone https://github.com/rh-mask/rhmask.git
cd rhmask
npm install
npm run hooks:install     # pre-push gate, once per clone
npm run dev               # http://localhost:3000

Four checks back the claims on this page, and the same set runs in CI:

CommandWhat it proves
npm run check:stealthDerive, recognise, recover. A stranger cannot claim your payment.
npm run check:paymentRequest and receipt formats round-trip; malformed input is refused.
npm run check:keycryptoSeal and open keys; a wrong passphrase and a tampered blob both fail.
npm run check:onchainRead-only mainnet verification of tokens, stealth receive and vault feasibility.

FAQ

Is this a mixer?

No. Nothing is pooled and nothing is co-mingled. Each payment simply lands on its own fresh address that only you control.

Do I need to connect a wallet?

Only to send. Generating keys, showing a request QR and claiming a receipt all work with no wallet at all.

What if I clear my browser data?

Unswept balances become unreachable unless you have the backup file or the two private keys. Back them up first.

Can I use it on my phone?

Yes. The request and receipt QRs are designed for a phone camera, and every screen is laid out for a small viewport.

Why do I need ETH to sweep?

A fresh stealth address pays its own gas. The sponsored sweep on the roadmap removes that step.

Is the vault live?

No. It is marked planned everywhere it appears, and it stays that way until a contract is deployed and audited.