Skip to content

Privacy in Applications

This page takes the primitives from Privacy and asks what they buy an application:

  • A worked example — a privacy stablecoin — built only from primitives in this repository.
  • The recurring design patterns: an issuer-centric account model, hidden holder-to-holder transfers, and auditable supply.
  • View keys for selective disclosure.
  • Other patterns the primitives support: bearer assets, HTLCs, atomic swaps, and escrow.
  • A direct answer to the question every issuer asks: is money in and out private?

A stablecoin is an interesting test case because it has two conflicting requirements:

  1. Holders want transactional privacy — balances and transfers should not be public.
  2. The issuer must be able to prove the total supply and control mint/burn.

Ootle’s primitives resolve the conflict by separating the two: transfers are private; supply events are not.

OperationPrivacy
Mint (issue new supply)Revealed — the amount is public so the total supply can be trusted.
Redeem / burnRevealed (or value-proven) — the burned amount is accounted for against supply.
Holder ↔ holder transferConfidential or stealth — amounts, and optionally owners, hidden.

This is not an accident of the implementation; it falls directly out of how supply tracking works. A stealth resource tracks its total supply in its resource substate, incremented on mint and decremented on burn. Because individual UTXO amounts are hidden, the engine cannot derive supply by summing balances — so the value entering and leaving supply must be revealed at the moment it crosses that boundary. Inside the boundary, everything stays hidden.

Where privacy lives in a stablecoinTwo revealed boundaries, one private interior. The interior is where the privacy is.Mintamount revealed, sosupply can beincremented on chainHolder ↔ holderUTXOUTXOUTXOamounts hidden — and, for stealth, owners tooBurnamount revealed orvalue-proven, so supplycan be decrementedThe supply boundary: value must be legible here, because total supply is tracked on the resource.Inside it, the engine verifies a balance proof and range proofs and never learns an amount.

Minting a stealth resource produces a revealed amount returned as a Bucket — the engine needs the cleartext value to update total supply. The issued funds then become private by converting the bucket into stealth UTXOs:

// Mint is revealed: total supply must be updated in the clear.
let bucket = ResourceBuilder::stealth()
.with_token_symbol("USDX")
.mintable(rule!(...), LOCKED)
.initial_supply(1_000_000u64);
// Convert the minted supply into stealth UTXOs; any revealed remainder comes back as a bucket.
let revealed_bucket = bucket.stealth_transfer(mint_statement);

Burning is the mirror image: to decrement supply, the engine must know the value being removed. If supply tracking is enabled, burning a stealth UTXO requires a CommitmentValueProof that reveals the value being burnt while revealing nothing else. Constructing that proof requires either the commitment’s mask (the UTXO owner) or the secret view key — so burns are effectively gated on the owner or the auditor:

// Burning confidential commitments requires one CommitmentValueProof per commitment.
bucket.burn_with_value_proofs(proofs);

This is where the privacy lives. A transfer between holders is a stealth transfer: the sender spends a UTXO, the engine verifies the statement-level balance proof (∑in=∑out\sum \text{in} = \sum \text{out}), and range proofs ensure no value is invented. Neither the amounts nor the two owners are readable on-chain. See Stealth Transfers.

The issuer can be given a view key on the resource:

ResourceBuilder::stealth()
.with_view_key(auditor_public_key)
.initial_supply(1_000_000u64);

With a view key, every output carries a ViewableBalanceProof, letting the holder of the view private key recover each amount via the ElGamal relation V=E−pRV = E - pR. The auditor can then reconcile the sum of outputs against the tracked total supply — full audit, no spending power, and nothing revealed to the public. This is the compliance story: view is separable from control.


The stablecoin is one instance of a small set of patterns that recur across private applications.

Put privileged operations (mint, burn, and freeze) behind access rules on the resource and its administrative component, and make ordinary transfers key-only. The issuer holds the mint authority; holders hold spend keys. Because the authority is expressed as an AccessRule, it can be a single key, a multisig, or a component badge — the pattern scales from one operator to a governed issuer. See Authorization and Access.

A confidential or stealth resource can opt into auditor visibility by carrying a view key. The key is fixed when the resource is created and cannot currently be rotated. This turns “private” into “private to the public, transparent to whoever you designate” — the configuration most regulated issuers actually want, and one the primitive supports natively rather than bolted on.

A stealth UTXO is a bearer instrument: whoever can satisfy its SpendAuthorization controls the value, with no registry naming an owner. A bearer token is simply a resource whose outputs are key-path-only stealth UTXOs.

Two conditions compose into a hashed timelock contract:

let claim = SpendCondition::all([
AtomicCondition::Builtin(BuiltinPredicate::HashLock { hash, alg: HashAlg::Sha256 }),
AtomicCondition::Builtin(BuiltinPredicate::BeforeEpoch(deadline)),
]);
let refund = SpendCondition::all([
AtomicCondition::Builtin(BuiltinPredicate::AfterEpoch(deadline)),
AtomicCondition::AccessRule(rule!(public_key(funder_pk))),
]);

The claim branch requires the preimage and a deadline; the refund branch returns funds to the funder after the deadline. Because the hashlock uses the undomained digest, the same secret can unlock a leg on an external chain — the basis for a cross-chain atomic swap. The machinery is in Stealth Transfers; a runnable example lives at crates/wallet/ootle-rs/examples/stealth_spend_conditions.rs.

Because a leaf is a conjunction of conditions, escrow is just a multi-condition spend: release to buyer OR refund to seller after a timeout become two leaves of a condition tree, while the funds stay hidden throughout. Covenants go further — an OutputPreservesCondition leaf forces value to remain under the same condition as it moves, so an escrow can constrain what the recipient does with the funds rather than merely who may spend them.


The question an issuer actually asks is sharper than “does it use cryptography”:

I deployed a stablecoin. Where is its privacy? Are deposits and withdrawals private?

The answer depends entirely on the resource type and the path funds take, and it is worth being precise:

  • Confidential resource — hides the amount. A vault’s balance is a commitment; an observer sees a transfer happened but not how much.
  • Stealth resource — hides the amount and the counterparties. Funds are UTXOs with one-time addresses, so neither value nor ownership is readable.

Outbound (mint / deposit into circulation) is typically public at the boundary: minting produces a revealed amount so total supply can be updated. The privacy begins after the mint, once the revealed bucket is converted into stealth UTXOs.

Inbound (redeem / burn) is where you choose: with supply tracking, the burnt value is revealed to the engine via a CommitmentValueProof; with tracking disabled, it stays hidden.

Transfers between holders are the private part — hidden amounts, and for stealth, hidden owners.

It helps to see the leak surfaces side by side, because they are not equally avoidable:

SurfaceWhat leaksAvoidable?
FeesThe fee amount, in the clearNo — every transaction pays a public fee. This is the floor.
Mint / depositThe issued amountNo, if supply is tracked — revealing it is the whole point of the boundary.
Redeem / burnThe burnt amount, via a CommitmentValueProofYes — .disable_total_supply_tracking() hides it, at the cost of an on-chain supply figure.
Public-component callsThe caller’s identity, via the signatureYes — keep the private path off any component that checks caller identity.
Transaction graphWhich inputs were spent and which outputs createdPartly — spend stealth inputs only, and avoid round-number correlations.

And the caveats that apply in every case:

  • Fees are revealed. Every transaction pays a public fee, which is a real privacy floor.
  • Touching a public component exposes you. If a private transfer calls a public contract that checks the caller’s identity, the transaction is signed and thus linked to a key — the outputs may still hide their recipients, but the initiator is known.
  • Timing and value correlation leak. A revealed mint and a stealth transfer of the same magnitude in the same block can be correlated by an observer.

  • A privacy stablecoin splits the problem: supply events are revealed, holder transfers are private.
  • Mint produces a revealed amount (to update supply); burn reveals or proves a value; transfers stay confidential or stealth.
  • View keys give selective disclosure — audit without spending power.
  • The same primitives yield bearer assets, HTLCs, atomic swaps, and escrow via condition trees.
  • “Is it private?” resolves to: Confidential hides amounts; Stealth hides owners too — but fees, public-component calls, and revealed boundary amounts always leak.

Next: the concrete resource types and built-in templates you can build these on — Templates and Assets.