0% 0% 0% 0%

Concepts

Index ▾
  1. 01. Concepts
  2. 02. Contracts
  3. 03. pool
  4. 04. policy
  5. 05. viewkeys
  6. 06. verifier
  7. 07. Circuits
  8. 08. Features
  9. 09. Shielded send
  10. 10. Deposit & withdraw
  11. 11. Consolidate & split
  12. 12. Shielded swap & pay
  13. 13. Escrow
  14. 14. Payment channels
  15. 15. Payroll & subscriptions
  16. 16. Auditor disclosure
  17. 17. ASP compliance
  18. 18. Cloud runtimes

Concepts

ozky is a UTXO shielded pool for stablecoins on Stellar/Soroban — not account balances. This page covers the zero-knowledge primitives it is built on and where each one shows up in the app. It is testnet-first and unaudited.

Notes — the unit of value

Your balance is a set of private notes, never a public number. A note is private data held by its owner — value, asset_tag, owner_pk, a random blinding, the epoch, and a per-note seed rho. On-chain a note exists only as its commitment.

Where in the app: the Wallet page shows your spendable balance by scanning and summing the notes only you can decrypt.

Commitments & the Merkle tree

The on-chain leaf is a Poseidon commitment to the note:

commitment = Poseidon(value, asset_tag, owner_pk, blinding, epoch, rho)

Commitments are appended to an append-only Merkle tree (depth 20). The root is the public anchor for “all value that exists in the pool.”

Where in the app: every action that creates value — Deposit, change from a Send, a Swap output — appends a new commitment.

Nullifiers — spending without linking

To spend a note you publish a nullifier and prove in zero-knowledge that the note is in the tree:

nullifier = Poseidon(rho, owner_sk)

It is deterministic (double-spends are caught), unlinkable to the commitment, and only the owner can produce it — there is no “pull”. So spends can’t be linked to deposits.

Where in the app: Send, Withdraw, Swap, and Consolidate spend notes and publish nullifiers; the Transactions page reads your local history, not the chain graph.

Proving — Noir / UltraHonk

Proofs are written as Noir circuits and proved with UltraHonk, fully client-side in the native Rust core, off the UI thread. The chain only ever sees a proof and its public inputs — never your keys or amounts.

Where in the app: proving runs in the ozky-prover sidecar; the UI just shows progress.

View tags & scanning

Incoming notes are found by trial-decryption accelerated with cheap view tags, so you scan the chain for your notes without decrypting everything. Scanning runs in the Rust core.

Where in the app: receiving a payment “just appears” in Wallet once the scanner detects it.

Keys — separate from your Stellar key

Everything derives from one 12-word BIP39 phrase, but the in-circuit owner_sk (BN254-native) is never the Ed25519 Stellar key — they are kept separate by design. A BIP32-style view-key tree derives scoped, revocable read-only keys per account / asset / epoch.

Where in the app: Settings manages the phrase; the Auditor page hands out scoped view keys. See Auditor disclosure.

Compliance — approved-set membership

Every transfer proves in-circuit that its funds trace to a depositor in an Association Set Provider (ASP) approved set — without revealing which one. Shielded funds are provably clean while the graph stays private.

Where in the app: surfaced as a status on Send/Withdraw; see ASP compliance.

Epochs

Note encryption and view-key scoping are partitioned by epoch (LEDGER_PER_EPOCH = 110_000) — deterministic ledger-sequence windows, no oracle or wall-clock. Epochs bound disclosure windows and keep scanning bounded.

The edges

Privacy is strongest inside the pool. Deposits, withdrawals, and bridges are public Stellar legs — ozky labels them and offers a denomination/timing policy to reduce correlation, framed as speed vs. privacy. (Swaps are now a fully in-pool shielded AMM — no public DEX edge.) A pre-funded relayer pays XLM fees so you never touch a public XLM account.

Next: Contracts — the on-chain layer that verifies all of this.