Veylor: A New Zcash Wallet with a Shielded Atomic Bridge

Today I am excited to announce Veylor, a new Zcash wallet which allows you to self custody shielded USDC. It uses USDC shielded in Railgun on Ethereum (0zkUSDC) and a trustless, atomic bridge to swap to and from shielded ZEC. Both the wallet and the bridge are open source and run on mainnet today in a capped beta.

So how does this it work?

The Bridge

The bridge works similarly to an HTLC, where it uses Ethereum’s programmability to escrow the USDC, while the ZEC is held under a spending key that is split between two parties.

The contract on Ethereum implements the Pallas elliptic curve and takes advantage of two properties:

  1. Given a public point (E = e·G) you cannot recover the secret (e) used to generate it.
  2. The public key of the sum of two secrets is the sum of their public keys: E + Z is the public key of e + z. So both sides can derive the joint ZEC address from E and Z, without anybody holding its full spending key.

The flow:

  1. The user and the bridge agree on a quote and exchange the public keys of their secrets, each with a proof that they know the secret. The user also sends the bridge the Railgun note the 0zkUSDC must be paid to.
  2. The bridge then locks the USDC in the escrow contract under both public keys, two deadlines (t0 and t1) and the user’s payout note.
  3. The wallet independently verifies the escrow on Ethereum, waits 3 blocks, and then sends the ZEC to a fresh shielded address whose spending key is e + z. Both sides derive this address from E + Z.
  4. Once the ZEC has 2 confirmations, the bridge marks the swap as ready in the contract. This gives up its right to cancel and marks the point where the ZEC and USDC must change hands.
  5. The user then claims on the contract with their secret z. The contract computes z·G on chain and checks it against the Z it stored. If they match, it releases the USDC.
  6. The claim reveals z on chain, and the bridge adds it to e and claims the ZEC.

The contract operates on a windows basis, so at every moment only one party can act:

  • Until t0: the bridge can cancel a swap that is not ready (the user never paid).
  • From ready, or from t0 if the bridge is unresponsive, the user can claim until t1.
  • From t1, for 24 hours, only the bridge can refund.
  • After that, only the user can claim.

This configuration is a tradeoff with UX but is necessary to avoid race conditions. The current time windows are 1 hour (the minimum the contract enforces from open to t0 and from t0 to t1) and 24 hours (the bridge’s refund window).

Going from ZEC to 0zkUSDC takes 1 authorization if you have auto claim on. Going from 0zkUSDC to ZEC takes 2 authorizations.

The Wallet

The wallet is a fork of Vizor and adds:

  • A Railgun wallet derived from your Zcash seed through BIP-85.
  • The UI for bridging. Each swap is signed with a throwaway Ethereum key derived from the seed. It only signs the swap’s authorizations, and is designed so that two swaps can’t be linked by address.
  • A Receive page where you can receive USDC or 0zkUSDC. USDC arrives at a rotating deposit address derived from the seed and is auto shielded into 0zkUSDC.
  • Send to another 0zk address, and Withdraw to a public Ethereum address. A relayer sends every Ethereum transaction the wallet makes and takes its fee.
  • A toggle for auto-claim (on by default) so 0zkUSDC can be claimed into the wallet without a separate authorization.

Some configuration worth discussing

  • Confirmations: the bridge marks a swap ready after 2 Zcash confirmations. On the Ethereum side, the wallet waits 3 Ethereum blocks after the open before sending ZEC, and the bridge waits 12 before acting on an escrow.
  • Window times: the contract enforces a minimum of 1 hour from open to t0 and from t0 to t1, and a 24-hour refund window for the bridge from t1. The bridge currently opens with t0 90 minutes out and t1 150 minutes out, and cancels a swap that isn’t paid within 15 minutes. For 0zkUSDC to ZEC: 15 minutes to fund the escrow, t0 at 2 hours and t1 at 3 hours.
  • I am considering adding optional notifications for swaps so the user doesn’t accidentally get their transaction refunded because they didn’t authorize in time.

Disclaimer

  1. This is a public beta. I fully intend to continue building this project and to secure an audit, but until that happens, only bridge funds you are okay losing. Swaps are capped at ~50 USDC.
  2. Only the Zcash side is fully shielded. On Ethereum the funds are unshielded and sit in a public escrow contract for the duration of the swap, so the amount and timing of each swap are public.
  3. The bridge holds the joint address’s viewing key, but at no point can the bridge steal your funds.
  4. The protocol assumes the relayer is a separate entity from the bridge, so a case could be made that this is not CROPS aligned yet. I am researching how to take the relayer out of the trust model, possibly with Ethereum’s frame transactions (EIP-8141), but those only arrive with the Hegotá upgrade. Another option would be to add a toggle to disable the relayer and allow the user to fund the wallet with ETH for gas (UX tradeoff).
  5. I am actively researching ways to make the bridge service decentralized and intend to write a separate post about this, but as of this moment the bridge service is a centralized service.

I am actively looking for feedback, and fully understand that Veylor may not be its final form. I look forward to hearing what the community has to say. Note that swaps take about 3 minutes and a brand new deposit on railgun takes 1hr as part of their Private Proofs of Innocence (PPOI) system.

Links

Isn’t this just the XMR-ETH swap design? Similar to how DLEQ-based swaps work? Why use an expensive (you said open() is 370k) gas thing?

Overall, very nice job!

1 Like

(post deleted by author)

Thanks! Yes, it’s the same family as the XMR<>ETH atomic swap. The difference is that the contract checks the revealed share on Pallas directly instead of secp256k1, so there’s no cross curve proof to verify.

Open is 107k gas on mainnet and does no curve math. The Pallas check runs once, at claim: ~165k of a ~1.23M-gas swap, most of which is the Railgun shield. With today’s price thats about 2 cents.