How Enygma Public works
Enygma Public keeps shielded balances as notes in vault contracts on the Rayls Public Chain. The wallet, in the user's browser, holds the keys, finds the account's notes and proves each payment with a zero-knowledge proof. A relayer submits the transactions and charges their gas in RLS.
Keys
A passkey gives the wallet a 32-byte secret each time the user approves (the WebAuthn PRF extension). The wallet derives the account's keys from it, in the browser:
| Key | What it does |
|---|---|
| Spend key | Owns the account's notes: only the spend key can spend them. The wallet holds it only while it makes a proof. |
| Nullifier key | Works out which of the account's notes are spent, without being able to spend them. |
| View key | An ML-KEM-768 key pair. Payments to the account are encrypted to its public part, so the account, and the auditor, which holds a copy, can read them. |
| EVM key | A secp256k1 key. Its address holds the account's public tokens and signs its requests. |
Every transaction asks for the passkey again, and the wallet locks after 15 minutes without use.
An account can sign up with an existing EVM wallet instead. A signature from that wallet gives the secret, and the wallet's own address holds the public tokens. It is a separate account from any passkey account.
Registering
Registering puts the account's public spend key, public view key and username in the registry contract, so that others can pay it. With them goes a copy of the view key, encrypted to the deployment's auditor key with ML-KEM-768 and AES-256-GCM. An address registers once.
Notes and vaults
A shielded balance is a set of notes. A note is a commitment, a Poseidon hash of the owner's public spend key, a salt, the amount and the token, stored as a leaf in a Merkle tree in a vault contract. Its amount and token are encrypted to the owner's view key: ML-KEM-768 gives a shared secret, from which the note's salt and an AES-256-GCM key are derived. Only the owner and the auditor can open it.
Spending a note publishes its nullifier, a value derived from the note and the owner's nullifier key. The vault refuses a nullifier it has already seen, so a note can't be spent twice, and nobody else can tell which note a nullifier belongs to.
The Rayls Public Chain testnet has a vault for USDr, one for RLS, and a shared vault that takes any ERC-20 token in one Merkle tree. In the shared vault, a payment doesn't show which token it moved.
A payment, step by step
- Pick the recipient. The sender picks a registered account by username or address, and the wallet reads its public keys from the registry. An address that hasn't registered can't receive shielded payments.
- Build the notes. The wallet picks one or two of the sender's notes that cover the amount, then builds the recipient's note and a change note for the sender, each encrypted to its owner's view key.
- Prove it. The wallet makes a Groth16 proof in the browser, with the circuits compiled to WebAssembly. The proof shows that the notes are the sender's, that they are in the vault, that they are unspent, and that the amounts add up with nothing created. It takes a few seconds, and only the 256-byte proof goes onchain.
- Pay the gas from shielded RLS. The wallet proves a second, smaller payment of the gas from the sender's shielded RLS to the relayer. For an RLS payment, the relayer's fee can be a note in the same proof instead.
- Submit. The relayer checks its fee, then submits the payment and the fee payment in one transaction (through Multicall3), from its own account, so neither goes through alone.
- Settle. The vault checks the proof, records the nullifiers and adds the new commitments to its tree. The recipient's wallet finds the payment by trying its view key on new notes, without a refresh.
Shielding and unshielding
Shielding moves public tokens into a vault. The wallet builds a note for the account's own view key, and the account signs a transfer of the tokens that commits to it: an EIP-3009 authorization for USDr, an EIP-2612 permit for RLS, or an approval for a token with neither. The relayer submits it through the gas forwarder.
Unshielding spends notes with a proof and pays the tokens to any address. Shielding and unshielding are public: see Who sees what.
Gas and fees
The relayer pays the chain's gas for every Enygma Public transaction and charges the account in RLS:
- Requests from the account's address, such as registering, shielding and unshielding, go through the gas forwarder, an ERC-2771 contract that takes the fee in RLS by permit. The account can pay from its shielded RLS instead, and the relayer then sends the request and that shielded payment together.
- Shielded payments are submitted from the relayer's own account, and always pay from shielded RLS.
Each gas fee includes a 15% commission to the Rayls Foundation, and shielding or unshielding USDr also costs 0.25% of the amount. The wallet shows every fee before you confirm. The contracts take no fee themselves: the relayer collects the fees, and pays the Foundation's share once each transaction is mined.
Invites
An invite starts a new account privately. The inviter makes a shielded payment to a one-time account and shares a link that holds its key. The person who opens the link claims the payment and registers: the relayer submits both, and the claim pays for both, so the newcomer's address never holds a public token. Invites are in RLS by default, which gives the newcomer gas for their own payments. The auditor reads every invite and who claimed it.
Cryptography
| Part | Choice |
|---|---|
| Zero-knowledge proofs | Groth16 over the BN254 curve, with circuits written in gnark |
| Commitments, nullifiers and Merkle trees | Poseidon hash |
| Note and view-key encryption | ML-KEM-768 (NIST FIPS 203), with HKDF-SHA256 and AES-256-GCM |
| Account keys | Derived from the passkey's PRF output with HKDF-SHA256 |
The notes and the escrowed view keys are quantum-private: ML-KEM-768 is designed to resist future quantum computers. The zero-knowledge proofs, passkeys and signatures are not post-quantum.
Groth16 needs a trusted setup. The testnet's proving and verifying keys come from a single-party setup, which is suitable for testing only. A production deployment needs a multi-party ceremony, whose keys are safe as long as any one contributor destroyed their secret.
Updated 1 day ago
