GRAIL
Privacy

Private wallets

How a Grail private wallet is created, stored, restored and rotated, and why it no longer comes from a wallet signature.

Creating a wallet

A private wallet is a Railgun wallet with a 0zk address. Grail creates it the way Railgun's wallet guide describes, from a BIP-39 mnemonic with 16 bytes of entropy:

Mnemonic.fromEntropy(crypto.getRandomValues(new Uint8Array(16))).phrase

The phrase comes from the browser's random number generator. It has no link to the connected wallet, and nothing about it reaches a server. The 0zk address is derived locally and appears within seconds, before the engine has started.

Storing it

WhatWhereProtection
Recovery phrase, at restThe browser's local storage, per connected walletAES-256-GCM, with a key from your password through PBKDF2-SHA256 at 600,000 iterations
Unlocked secretsThe tab's session storage, so a reload needs no passwordCleared when the tab closes or you lock the wallet
Railgun wallet dataThe browser's IndexedDBEncrypted by the SDK with a key derived from the phrase
On Grail's serversNothingThere are no keys and no mapping between 0x and 0zk addresses

You must save the phrase and confirm two of its words before your first deposit. The phrase is a standard mnemonic with the SDK's default derivation, so it is not tied to Grail. We have not yet tested importing it into other Railgun wallets.

Restoring it

Entering a phrase on a new browser creates a new encrypted copy under a new password. A restored wallet does not know when it was created, so it scans all of Railgun's history for notes it can decrypt. On mainnet this took 776 seconds with the tree download and 372 seconds with the tree already present. The balance reads Searching until the scan ends. A wallet created on Grail stores its creation block and scans only from there.

Rotating keys

If a phrase or a device may be exposed, the old keys need to stop mattering. Railgun addresses cannot change their keys, so rotation means moving to a new wallet:

  1. 1
    The browser creates a new random phrase, and the user saves it.
  2. 2
    It rescans, then sweeps every spendable balance to the new 0zk in one private transfer through the broadcaster. Private transfers carry no Railgun shield or unshield fee.
  3. 3
    The new vault keeps the old phrase, still encrypted, because the old wallet has to submit the Proof of Innocence for the sweep before the funds become spendable in the new one.
  4. 4
    The browser keeps proving until they are, including after a reload or at the next unlock.

The sweep is available only when no funds are in standby. A private transfer does not show which wallets are involved. Earlier versions switched wallets immediately, which left moved funds pending forever, because nothing was left to submit the old wallet's proof.

Why not a wallet signature

Our first prototype derived the mnemonic from a wallet signature over a fixed, versioned message. It was convenient: the same wallet recreated the same 0zk on any device, and rotation was a matter of changing a number in the message.

It also meant that one signature was the whole private wallet. Any site that got a user to sign that message, for example through phishing, could derive the same keys and take the funds, and a signature request looks harmless. When we described this design, a Railgun contributor told us plainly that a signature-based approach is not a good one.

They were right, so we removed it before mainnet. Grail now has no signature step at all. The connected wallet plays no part in the private keys, and a phishing page gains nothing from a signature. The trade-off is that users have to back up a phrase, which is the normal cost of holding their own keys. The current design is our own implementation and has not been reviewed by Railgun contributors.

Grail is an independent project built on Railgun's public contracts and SDK. Railgun contributors do not run, review or endorse Grail, and they did not write this documentation. Their own documentation is at docs.railgun.org.