Bankhaus

Protocol

Who paid is public. How much never is.

Bankhaus keeps balances as ElGamal ciphertexts on the alt_bn128 curve and lets the chain add them without opening them. Everything a bank needs besides secrecy, names, mandates, receipts, lives next to the ciphertext as plain contract state, so the circuits stay small and the rest stays legible.

Layer one

ConfidentialToken

A wrapper around USDG. Each account holds one ciphertext (c1, c2). Deposits add amount·G; transfers add two encrypted deltas; withdrawals subtract against a proof. Point arithmetic runs on the 0x06 and 0x07 precompiles Robinhood Chain inherits from Arbitrum Nitro, so a transfer costs about 146k gas.

Layer two

The house around it

Handles, mandates, vaults, request links and disclosure receipts. None of it is encrypted and none of it needs to be: a handle, a limit and a timestamp say nothing about a figure. Putting them inside the token would mean more circuits to prove and more ways to fail.

A transfer, step by step

  1. 01

    Register a view key

    Your wallet signs one fixed message. The app hashes the signature into a scalar, derives the ElGamal public key and records it on chain with register(pkX, pkY). The contract refuses anything that is not a curve point, because a bad key would make every later credit unreadable forever.

  2. 02

    Deposit

    USDG moves into the wrapper and amount·G is folded into your ciphertext with zero randomness. The deposit is public, the ERC-20 transfer already said the figure, and it becomes private the moment the first encrypted transfer in or out re-randomises the ciphertext.

  3. 03

    Transfer

    Your browser builds two deltas: −amount encrypted to your key, +amount encrypted to the recipient's, with a proof that both carry one non-negative figure and that you stay solvent. The contract adds c1 to c1 and c2 to c2 on each side. No amount in calldata, storage or logs.

  4. 04

    Read

    Subtract sk·c1 from c2 and you hold amount·G. Recovering the integer is a bounded search: a baby-step table of 2¹⁴ points built once per session with one batched inversion, then giant steps. Cents keep the bound at 2²⁸.

  5. 05

    Withdraw

    A proof that the hidden balance covers the figure, then amount·G leaves c2 and the ERC-20 leaves the pool. The amount is public here; the token transfer would reveal it anyway.

  6. 06

    Disclose

    Hand one reader the figure and the randomness of one transfer; they re-encrypt and compare. File a receipt so the chain remembers you answered, without saying to whom.

The arithmetic

pk  = sk · G
Enc(m, r) = (c1, c2) = (r · G,  m · G + r · pk)
Dec(c1, c2) = c2 − sk · c1 = m · G          →  m by bounded search

transfer:  me  += Enc_me(−a)       you += Enc_you(+a)
deposit:   c2  += a · G            (r = 0, public until re-randomised)
withdraw:  c2  −= a · G            against a proof of solvency
unit:      1 = 0.01 USDG           (ConfidentialToken.unit = 10 000)

A million dollars is 10⁸ units, about 2²⁷. The client recovers it with a 2¹⁴-entry baby-step table and at most 2¹⁴ giant steps: a few hundred milliseconds cold, about thirty with the last known balance as a hint.

Trust model

  • Custody

    None. Assets stay in your wallet; agent vaults are the only pooled balances, and only so a mandate is enforced by code.

  • Admin

    Pause, verifier swap, fee routing, role handoff. Cannot move funds, mint or decrypt.

  • Compliance

    Sets a tier on a record. Cannot stop a transfer or read a figure.

  • Pause

    Halts value movement, keeps identity and mandates editable, so you can still revoke an agent mid-incident.

  • Verifier

    Today the bring-up stub. Replaceable with setVerifier without touching a balance.

Contracts

Solidity 0.8.24, Paris, via-IR. Fifty-four checks on the compiled bytecode in an in-process EVM, with the same ElGamal the browser runs.

  • ProtocolAuthorityAdmin and compliance roles, the pause, fee routing. Never holds funds. Two-step handoff of the admin role, so a typo cannot orphan the protocol.
  • AccountRegistryOne record per wallet, a unique lowercase .bankhaus handle, a KYC tier only the compliance role can set and nobody can act on from here.
  • ConfidentialTokenThe USDG wrapper: register, deposit, confidentialTransfer, withdraw. unit = 10 000 (one cent). Its own freeze, a replaceable verifier, isFullyBacked() for monitors.
  • AgentControllerAgents, mandates, vaults, the approval queue, signer rotation, revocation. The x402 invoice id rides into every AgentPaymentExecuted.
  • RequestLedgerRequest-to-pay links. A confidential request carries a commitment in place of the figure; the payer fulfils with amount and blinding.
  • DisclosureLogfile(txReference, viewerHash, commitment): the fact and the time of a letter, never the reader or the payload.
  • FeeScheduleOne rate for every account: min(amount × bps / 10 000, cap). Off until the authority wires it.
Addresses are posted here on deployment.chain 4663 · USDG 0x5fc5…d168

What is not proven yet

The transfer and withdrawal verifier on this build accepts every proof, exactly as the reference deployment it descends from does during bring-up. The ciphertext arithmetic, the keys, the mandates and the receipts are real and tested; the zero-knowledge circuit that pins both deltas to one non-negative figure is the open item on the roadmap. Until it is in, the chain checks that a transfer is well-formed, not that it is honest.