Protocol
Your account

The platform wallet

The Solana keypair Umbra derives from your account signature, what it signs, how it is bound to your main wallet, and why that binding is public.

Umbra's clients, not the protocol

The pool requires one thing here: register must be signed by the Solana wallet whose address the identity commitment holds in slot 4. Deriving that wallet from your account signature, and binding it to your main wallet in the user registry, is what Umbra's clients do. The pool's register does not read the user registry, and another client could use any wallet it likes.

Your platform wallet is an ordinary Solana keypair that the Umbra SDK derives from your account signature and holds on your device. It is the address your identity commitment is bound to, so it signs where the protocol needs proof that you control that address, and your main wallet is publicly bound to it.

How it is derived

The account context derived from your signature becomes 32 bytes of BIP-39 entropy through HKDF-SHA512 in RFC 9180's labelled form, under a domain that names the chain, the deployment, the application com.umbraprivacy.nimbus-solana, the purpose platform-wallet and a generation. Those bytes are a 24-word phrase, and the phrase's BIP-39 seed feeds a SLIP-0010 ed25519 derivation along m/44'/501'/0'/0', Solana's usual four-level hardened path. The SDK derives the seed once per session; it can derive further accounts by changing the third level, and account 0 is the default.

These are the standard steps, so the phrase restores the same addresses in any wallet that follows BIP-39 and SLIP-0010, solana-keygen included. The SDK does not show the phrase. The domain is part of the derivation, so the same signature gives a different wallet on another chain or deployment and in any other application, and importing your main wallet's phrase elsewhere does not reproduce this address. See the account signature.

Your identity is derived after the platform wallet and binds its public key: the keys behind your identity commitment are derived for this one address, and a commitment that names any other is refused before anything is proved.

What it signs

Two wallets sign for you, and they sign different things. The table compares, for each instruction that involves you, what your main wallet does and what your platform wallet does.

InstructionMain walletPlatform wallet
bind_platform_wallet (user registry)Signs as the ownerSigns as the platform wallet
register (shielded pool)Signs and pays the feeSigns as the bound wallet
A deposit (process_3x3x2x2 with an inflow)Funds the platform walletSigns as the depositor; tokens leave its token account
A relayed transfer or withdrawalNothingNothing

At registration, the pool splits the bound wallet's address into two halves and feeds them to the registration proof, which checks that they match slot 4 of your identity commitment. The signature is what turns a claim to that address into proof of holding it. An Ed25519 check inside the circuit would cost more than the rest of the circuit, so the protocol uses the Solana signature instead.

In a deposit, the pool requires the depositor to sign and moves the tokens from the depositor's token account. The depositor's address is public in the transaction. In the SDK's default single-transaction deposit, the main wallet transfers the tokens to the platform wallet and pays the Solana fee in the same transaction, so both land or neither does. The older two-transaction form funds the platform wallet first and has the platform wallet pay the deposit's fee.

Inside the transaction proof, slot 4 is opened again, privately, so the circuit can recognise a note owned by your own platform wallet's address as yours when it applies the self-transfer fee rate. See the fee model.

The binding

The user registry program has one instruction, bind_platform_wallet. It links a main wallet to the platform wallet that acts for it. It takes three accounts: the owner (your main wallet), the platform wallet and the owner's binding address; the reference section at the bottom of this page lists them with the role the program checks for each.

It checks, in order, that:

  1. exactly three accounts are passed and the payload is empty;
  2. it is listed directly by the transaction, not reached through another program (a program holding the owner's signature could otherwise rebind the owner unseen);
  3. both the owner and the platform wallet signed, so neither can be named without its consent;
  4. the two are different addresses;
  5. the third account is the owner's binding address, a program-derived address seeded by the owner.

It stores nothing, creates nothing and charges no rent. The successful instruction is the binding. Anyone can list an owner's binds by asking an RPC node for the signatures that touched the owner's binding address, and the owner's latest successful bind is the current one. Binding again replaces the earlier binding without an error. The errors each failed check returns are in the reference section.

The shielded pool's register does not read the user registry. The SDK sends both instructions in one transaction the first time, and refuses to register if the registry already binds your main wallet to a different platform wallet: the pool would bind your identity to one address while the public record named another. Repairing that takes a new bind signed by both wallets, and the SDK does not do it automatically.

What the binding makes public

The binding is how anyone can pay you at your main wallet address (see receiving payments). It also means these links are public and permanent:

  • main wallet to platform wallet, through the bind;
  • platform wallet to identity commitment, through the registration it signed;
  • platform wallet to every deposit it made, as the depositor.

A chain observer can therefore attribute every deposit to your main wallet. What happens to the value after it enters the pool is not visible. See what is private and what is visible.

Reference

Last updated

On this page