Whitepaper · DARK-CB-1 · draft
A design for encrypted balances and encrypted transfer amounts with ordinary self-custody, no pool, and a proof system a stranger can check without trusting us. This draft precedes the mainnet deployment; addresses and audit status are filled in at launch.
00 · Abstract
Dark is a wallet for USDG on Robinhood Chain in which each account's balance, and the amount of each payment between Dark accounts, is stored on chain as ciphertext. The account itself is ordinary: one address, one owner, one key. There is no shared pool, no shuffling of coins between users, and no party — Dark included — that can decrypt a balance or move funds on a user's behalf.
Deposits into and withdrawals out of the vault are public token transfers. Only the interior — how much each account holds, and how much moves between them — is confidential.
01 · Account model
Balances are encrypted with twisted ElGamal on the Grumpkin curve. Each account holds an available ciphertext and a pending ciphertext. Incoming payments land in pending; only the owner can fold pending into available, and only with a proof. This split is what stops a sender from interfering with a recipient's next transaction.
An account registers once by publishing its public key together with a proof that it knows the matching secret. From then on anyone can pay it, and nobody — including the sender — can learn the recipient's balance from the payment.
02 · Operations
| Operation | Amount | Proof | Pausable |
|---|---|---|---|
| Register | — | knowledge of the secret | no |
| Deposit | public | none | yes |
| Transfer | encrypted | range + balance + correctness | yes |
| Apply pending | — | none (owner only) | never |
| Withdraw | public | balance sufficiency | never |
Withdraw and apply pending can never be paused.
The pause exists so the team can stop new money coming in if something is wrong. It cannot stop money going out. This is enforced by the contract, not by policy.
03 · Proofs
A transfer proof shows that the sender knows their secret, that the amount is within the permitted range, that they hold at least that much, and that the ciphertexts written for sender and recipient encrypt the same amount under the right keys. Every public input except the new ciphertext is read by the contract from its own storage, the registry, or configuration — never supplied by the caller.
Each event carries the account's post-state ciphertexts, so any account's balance at any block can be rebuilt from the public log by anyone. The owner also keeps a sealed copy of their own balance on chain, so a wallet can recover it without replaying history.
Measured on Robinhood Chain testnet
04 · Selective disclosure
A disclosure is a signed document asserting one fact about one account at one block — an exact balance, or a range — with a proof. It is sealed under a fresh key and stored as ciphertext; the key travels in the link's URL fragment, which browsers never send to a server.
The verifier rebuilds the document's binding hash from its fields, requires an EIP-712 signature by the account, reads the ciphertext from the chain itself, and checks the proof against it. A document whose fields have been edited after signing fails. A balance whose only contributions are public deposits is reported as public_balance rather than "verified", because proving knowledge of a number anyone could compute proves nothing.
Revocation cannot unsee.
Revoking a link stops it resolving. Anyone who already opened it may have saved the proof, and a saved proof stays valid. The product says this in the same words wherever revocation appears.
05 · Trust box
| Party | Can | Cannot |
|---|---|---|
| Dark (the team) | pause deposits and transfers; tighten caps; see request metadata; serve the web app's code | move or freeze funds; block withdrawals; read a balance; change verifiers without a 48-hour timelock |
| The vault owner (timelock) | change caps and the guardian, after 48 hours in public | renounce ownership; bypass the delay |
| Robinhood Chain operator | order transactions; upgrade the chain | read encrypted balances |
| USDG issuer | freeze USDG, including the vault's | read or move encrypted balances |
| The proving setup | — | a public, reproducible reference string, bundled and hash-pinned |
| An auditor | read the code | decrypt anything: there is no auditor key |
On the web, Dark serves the wallet code on every load.
A compromised deployment could serve code that captures keys. The mobile apps and the standalone exit tool do not depend on Dark's servers for their code, and the exit tool depends on them for nothing.
06 · Limits and caps
The v1 vault carries immutable ceilings: at most 250,000 USDG in the vault, 2,500 per transfer, 2,500 per deposit, and 10,000 net inflow per account. Anything beyond those needs a new, audited vault. The operating caps sit at or below these and can be tightened without delay by the guardian, but only widened through the timelock.
The team's monitoring checks the vault every minute: solvency against the token contract, bookkeeping against the public log, the caps, the owner and guardian, the verifier bytecode against pinned hashes, timelock activity, and outflow rate. A check that cannot be evaluated is treated as failing.
07 · Exit
Custody claims are cheap. Dark ships a separate, permissively licensed tool that withdraws from the vault using a recovery phrase and any blockchain endpoint, and a static page that does the same in a browser with the prover and its parameters bundled. Neither contacts a Dark server. This is run as a drill with every Dark hostname blocked, and documented in the docs.
08 · Status
Mainnet contract addresses and verifier hashes, the audit firm and report, and final beta caps. Each is filled in on the day it becomes true, not before. The normative specification, with every wire format pinned, is DARK-CB-1.