HESTIAdocs

How it works

The moving parts — pool, notes, proofs, association sets, relayer — and the life of a private transfer end to end.


Hestia is a shielded UTXO (note) pool. Value lives in notes, not account balances. A note is a private record of "this much of this token belongs to this owner." The chain only ever stores a commitment — a hash of the note — never the note itself.

The three operations

text
        shield                 send                  unshield
  ┌──────────────┐      ┌────────────────┐     ┌────────────────┐
  │ public ERC20 │ ───► │ private balance │ ──► │ clean public   │
  │   or ETH     │      │  (notes in pool)│     │   address      │
  └──────────────┘      └────────────────┘     └────────────────┘
   gross amount is        amounts & links        amount & dest are
   public; ownership      fully hidden           public; unlinkable
   is hidden                                      to the deposit
  • Shield moves a public balance into the pool. A new commitment is appended to the tree. The deposit amount is visible (it has to leave a public account), but who owns the resulting note is not.
  • Send spends one note and creates two: one for the recipient, one for your change. A zero-knowledge proof shows the inputs and outputs balance — without revealing any amount.
  • Unshield spends a note and pays part of it out to a public address. The withdrawal amount and destination are public, but they cannot be linked back to the original deposit.

The pieces

PieceRolePackage
NotePrivate { value, token, owner, label, randomness } record@hestia/common
Commitment treeOn-chain Poseidon Merkle tree of note commitments (depth 32)contracts + @hestia/common
Nullifier setOn-chain set of spent-note markers; prevents double-spendscontracts
CircuitGroth16 join-split that proves a spend is valid@hestia/circuits
Association setMerkle set of approved deposit labels (compliance)registry + @hestia/sdk
IndexerRebuilds tree, nullifiers, and ASP roots from chain events@hestia/route
RelayerSubmits your proof so the recipient address has no gas history@hestia/route
SDKTies it together: keys, discovery, proving, submission@hestia/sdk

Life of a private transfer

When an agent calls hestia.send({ token, amount, to }):

  1. Sync. The SDK pulls the latest pool events through the indexer and reconstructs the commitment tree and nullifier set locally.
  2. Discover. It trial-decrypts the encrypted note blobs with the agent's viewing key to find the notes it owns, and selects one note that covers amount + fee.
  3. Build outputs. It creates two output notes — value to the recipient's public spending key, the change back to itself — and encrypts each to the right viewing key.
  4. Prove. Client-side, it generates a Groth16 proof that: the input note is in the tree, its nullifier is correct, inputs = outputs (+ withdrawal + fee), and the note's lineage label is in the approved association set.
  5. Submit. It sends the proof, the input's nullifier, the two output commitments, and the two ciphertexts on-chain (optionally via a relayer that pays the gas).
  6. Settle. The pool verifies the proof, records the nullifier as spent, and appends the two new commitments. The recipient discovers their note on their next sync.

Nothing in steps 3–6 reveals an amount, and nothing links the spent note to the new ones.

Where to go next