Confidential money
across bitcoin & ethereum
one note · private amounts · privkey-only recovery
●
Secret Sats EVM pool · trusted setup

The EVM side of Secret Sats: private ETH and other EVM assets on Ethereum, Base and Robinhood Chain, proved on your own device, alongside the Tacit V1 confidential pool. Every contribution mixes fresh randomness into the pool's proving key, and the key is sound as long as one contributor kept theirs secret. Open to everyone, in the browser, a few minutes per run, and you can contribute more than once.

Ceremony page · Lite paper · Tacit V1

Loading ceremony state…
Latest participants
tip — loading…
Private Bitcoin wallet silent payments
— sats
Receive Bitcoin privately, hold Tacit assets on Bitcoin, and publish one universal Tacit address for the shielded pool.
Receive identity BTC + TAC
Public key
—
Bitcoin
Tacit
—
universal address
▸ Manage wallet
Tacit needs its own signing key in this browser — confidential transactions use primitives existing Bitcoin wallets don't expose. This is the key that controls your tacit assets; export and back it up before holding value, since clearing browser storage loses it.
▸ Advanced: chain indexer
The dapp rotates across mempool.space + blockstream.info by default; both are public Esplora endpoints with per-IP rate limits. To avoid 429s on heavy use, point at your own Esplora (Start9, Umbrel, Citadel, or a self-hosted node) — your URL takes priority and the public endpoints become fallback only.
Custom Esplora URL (for the active network)
market preview browse all ↗
etch · confidential supply
Mint a new asset with a hidden total supply — observers see a cryptographic commitment, not the number.
1 Ticker, decimals & supply
2 Metadata · optional

Name, logo, description, link. Skip this for a fast anonymous etch — the ticker is the only on-chain identity either way.

Add name, logo, description, link
Off-chain in the metadata blob; ticker stays the on-chain identity. Renderers display "Name (TICKER)" when both are set.
3 Supply & mint policy

Two permanent choices, locked in at etch — read both before continuing.

▸ Why is the supply hidden? · what gets pinned
The supply is committed to the chain via a 33-byte cryptographic commitment plus a ~700-byte zero-knowledge proof that the value is a valid 64-bit integer — only you know the actual number; observers see only the proof. The blinding factor that hides the supply is derived from your wallet's privkey, so a fresh device with just your key recovers the etched supply from chain data alone — no localStorage backup needed. Max supply per asset is ~1.84 × 10¹⁹ base units, which covers any realistic decimals setting. Etch is a 2-tx flow (commit + reveal) and broadcasts in seconds. Optional image / metadata is bundled into the envelope and propagates to recipients alongside the supply commitment.
4 Review & etch
etch · public mint
Deploy a token with a fixed cap and per-mint amount. You receive zero tokens at deploy — anyone can mint until the cap fills.
1 Ticker, decimals, cap & per-mint
⚠ Cap and per-mint amount are permanent; per-mint must divide the cap evenly. Minting opens at the next confirmed block after this deploy lands (or at the start height below, if set).
2 Image & mint window · optional
Restrict minting to a block-height window
Restrict T_PMINTs to a Bitcoin block-height window. Leave both blank (or 0) for open-ended minting until the cap fills — that's the default. Both fields are permanent and locked at deploy; can't be changed after broadcast.
Validation: start must be ≥ deploy block + 1, end must be > start, both must fit in u32. If start = 0 the dapp uses deploy block + 1; if end = 0 minting stays open until the cap fills. SPEC §3.4.
▸ How is this different from a confidential etch?
Mints publicly reveal (amount, blinding), so the cap is auditable from chain alone — supply is fully observable, unlike CETCH where supply is committed but hidden. The deploy is a 2-tx flow (commit + reveal). Once the cap fills, no more T_PMINTs are accepted. SPEC §3.4.
3 Deploy
send
Confidential transfer of one asset — observers always see valid math, never the amount.
more ▾ Optionally hide the recipient too by pasting their shielded address (tcs1…) instead of a pubkey; the on-chain marker becomes a per-tx unique address. Recipients auto-discover their balance on their next scan. The recipient's key needs to sit in a tacit-aware wallet — a general Bitcoin wallet (UniSat, Sparrow, Xverse, etc.) has no concept of this protocol and, if it ever spends the exact UTXO carrying this balance, would treat it as a few hundred sats of dust and destroy the token value riding on it. Any wallet can add tacit awareness by following the public protocol (SPEC.md §3) — none do today, so only send to a key someone will hold in this dapp or another tacit-aware wallet.
Shielded addresses apply to tacit token transfers only — plain BTC sends use a regular bc1q…/tb1q… address.
Choose a specific lot to spend (advanced)
When set, the amount field is locked to the chosen lot's exact value (whole lot is spent, change returns to you). Useful for cancelling a stale listing's lot without touching your full balance.
Irreversible once broadcast.
drops · run an airdrop
Airdrop a tacit token to holders of any ERC-20 — recipients tip ~3,500 sats to claim.
how it works Drop in an Etherscan holder CSV, generate a treasury, launch; you don't pay per-batch fees.
1 Pick the token you're airdropping

The tacit asset to send. Source amounts get floor-truncated to this asset's decimals.

2 Add ETH holder snapshots

Etherscan token-holder CSV exports (HolderAddress,Balance,…) work as-is. Add as many as you want — same-address balances are summed across all sources.

3 Exclude addresses · optional

Drop the zero/dead address, CEX hot wallets, and your own team wallets so they don't get a share. One address per line.

Show exclusion list
4 Make a fresh treasury wallet

This hot key signs every payout. Generate a dedicated one so a leak doesn't expose your main wallet — back up the privkey now, you only see it once.

5 Launch your drop

One click builds the merkle snapshot, pins it to IPFS, and saves a drop card below. After launch: fund the treasury, switch to it, publish to discovery, enable auto-fulfil.

run steps individually
What happens after I launch?
  1. Your drop card appears below — while main wallet is still active, click Fund: TAC → to send payout supply to the treasury pubkey, and Fund: sats → to send bootstrap sats to its bech32 address.
  2. Switch active wallet to the treasury (button on the generated key panel above).
  3. From the treasury, click Publish to discovery on the drop card. ⚠ Publishing before switching routes recipient tips to the wrong pubkey.
  4. Click Fulfil claims → ⚙ Auto-fulfil → Enable. Walk away — the dapp pulls the queue and broadcasts batches automatically.

Your drops

Saved in this browser. Export JSON on each row to back up the local fulfilled[] ledger; import below to restore on another device.
No drops saved yet.
Advanced · on-chain claim pool (T_DROP)
Alternative to the recipient-tipped flow above: lock the payout supply into a public on-chain pool. Recipients self-claim and pay their own ~$5-10 Bitcoin tx fee. Issuer pays once (the pool-creation tx) instead of one Bitcoin tx per batch of 7. The cap is enforced on chain. Optional eligibility gate (paste a snapshot's merkle root) restricts claims to specific ETH addresses; leaving it empty opens the pool first-come-first-served.
Trade-off: each T_DCLAIM is publicly attributable (claimer address + leaf_index on chain). For a CETCH-rooted asset, T_DROP does not change asset_id or migrate holders — it's a one-tx pool that spends some of your existing supply.
Cap MUST be evenly divisible by per-claim. max_claims = cap ÷ per_claim. For merkle-gated drops, size cap to the count of eligible leaves × per-claim. For open FCFS drops, set a non-zero expiry so unclaimed remainder is recoverable by you via Reclaim once expiry passes.
Merkle-gated: only addresses in the snapshot can claim. Open FCFS (empty merkle root): anyone can claim, first-come-first-serve. Open FCFS requires an expiry block so unclaimed tokens can be reclaimed by you afterward.

Active on-chain pools

Pools created via T_DROP on this network. Updated when the worker indexer scans (every ~5 min) or via Refresh.
No on-chain pools loaded · click Refresh.
Trust model · self-host the worker
The shared worker is a convenience layer with no trust authority over your drop. Snapshots live on IPFS (content-addressed, verifiable from chain + CID alone). Drop records are local to your browser (back them up via Export JSON; restore via Import). The worker's claim queue is dumb storage — it can withhold tuples but cannot forge them, and recipients can always hand-deliver tuples instead of submitting through the queue. Setting WORKER_BASE = '' disables it; the dApp falls back to manual flows. To run your own worker for a campaign, see worker/README.md (deploys to a free Cloudflare Workers account in ~5 minutes).
claim · airdrop recipient
Connect your Ethereum wallet to see airdrops you're eligible for, then pick one to start a guided claim. No gas, no Ethereum transaction.
Compatible wallets
Most Ethereum wallets work: MetaMask, Rainbow, Rabby, Coinbase Wallet, Trust, Frame. Smart-contract wallets (Ambire, Safe, Argent) work too — those need to be on the chain that holds the contract; regular wallets work regardless of current chain.

Available drops

Live drops on ….
no drops loaded yet · click Refresh
Other ways to claim
Have a private drop or a direct share link from the issuer? Use the options below.
▸ Self-claim on-chain (you pay your own Bitcoin tx fee)
For drops where the issuer locked tokens into a public claim pool on Bitcoin. Anyone in the snapshot can self-claim first-come-first-served until the cap runs out. For drops with an eligibility list, you'll also need to sign with your Ethereum wallet (use the discovery list above to do that first). Cost: roughly $5-10 in Bitcoin tx fees per claim, paid by you instead of the issuer.
no on-chain drops loaded yet · click Refresh
▸ Have a merkle root + CID? Manual entry
…or paste / upload the snapshot JSON directly (fallback if IPFS gateways are unreachable)
Useful when corporate proxies block IPFS gateways or for offline review. The dapp still recomputes the merkle root locally and refuses any snapshot whose rows don't match the declared root.
holdings
▸ Got a share-link or atomic offer from someone?
Most incoming transfers are auto-discovered — you don't need either button. Use these only when a counterparty hands you a paste-in payload (share-link URL or an atomic-offer JSON).
activity · local log
Local-only history of broadcasts you've signed on this device (etch / transfer / mint / burn / received). Cleared if you wipe browser storage; the chain is the source of truth.
Loading…
TAC airdrop Ethereum
Loading…
Bitcoin order book real sats · atomic on Bitcoin L1
Buy and sell Bitcoin-native assets against real sats. Fills settle atomically on Bitcoin, with no custodian and no pool wrapper.
▸how it works non-custodial · Bitcoin-native · zero protocol fee

Trade. Type an amount, click Swap. Every fill is a single Bitcoin transaction — your asset and the counterparty's sats change hands in the same tx or neither moves. No bridges, no custodians, no settlement layer.

Fees. Zero protocol fee. You pay only Bitcoin miner fees, shown in the preview before you sign.

Signed terms. Orders are pre-signed by the maker against a specific outpoint, so the only valid fill is the one that spends it at the signed price. There is no sequencer or relayer in between.

Limit + rest. If your swap doesn't fully fill at your limit price — including when nothing matches — the unfilled remainder rests as a passive order for 24h. One click either fills, posts a tradeable order, or both.

Amounts. The asset side of a fill is a Pedersen commitment on-chain. The BTC payment is an ordinary public output, asset ids are public, and a listed offer shows its size and price.

Custody. Your signing key never leaves this browser. Tacit can't move your funds — only your wallet can.

Browse live Bitcoin-native assets, public mints, supply attestations, and market entry points on the active network.

Markets →
assets · confidential supply — public mints · fair launch —
public mints · fair launch
Anyone can mint a fixed tranche until the cap fills. Supply is publicly auditable from chain alone.
assets
Confidential tokens on Bitcoin. Tickers and metadata are public; balances stay hidden by default. Some issuers publish an IPFS attestation that reveals total supply — filter by "Supply proven" below. Click an asset_id chip to copy.
mixer · shielded pools
Bitcoin-native private send for any tacit asset that stays on Bitcoin. Deposit a fixed denomination into a shared pool, wait, then withdraw to a fresh address — a zero-knowledge proof breaks the on-chain link between the two halves. The prover and verifier run live in your browser against a separate trusted-setup ceremony.
For amount-flexible privacy plus swap, borrow, and cross-chain flows, use the confidential pool, which works with any amount. This fixed-denomination mixer is legacy.
pools
Initialized pools are listed below. Each row shows total deposits, total withdrawals, and the current privacy set — how many other deposits yours would hide among if you withdrew now. Empty list = no pools have been initialized on this network yet.
deposit
Send the pool's exact denomination from your wallet into the pool. Your browser generates a private deposit record — back it up before broadcasting; without it, the deposit can never be withdrawn.
Backup all deposit records. Stored in localStorage per network — wiping the browser's storage = losing the secret pair = funds permanently un-withdrawable. Download a JSON of every deposit you've made on this network, then re-import on a fresh device.
withdraw
Withdraw a deposit to a fresh address. Pick a saved deposit, paste a deposit record JSON, or import a share-link. The privacy set size — how many other deposits yours hides among — is shown beneath each pool.
wrap sats → cBTC.zk slot
Advanced Bitcoin-only cBTC.zk slot helper. Lock sats into a self-custody slot and save a slot record for redeem or rotate. This is separate from V1 fungible cBTC/tacBTC, which mints from SP1-reflected Bitcoin locks in the shielded pool. No federation or co-signer; lost records lock the backing sats permanently (like losing a Bitcoin key), so back them up.
Slot records on this browser. Each row is one wrapped slot: the secret material needed to redeem the backing sats. Stored in localStorage per network — back up before clearing browser data.
No slots wrapped yet on this network.
send slot to recipient
Rotate a live slot to a recipient. New secrets are encrypted to their viewing pubkey; their dapp auto-detects incoming slots on next load. Leave recipient empty to refresh your own slot's keys (useful if the secrets may have been exposed).
Your incoming viewing pubkey (give this to senders so they can send slots to you; derived deterministically from your wallet privkey via HKDF):
—
redeem slot → sats
Redeem a live cBTC.zk slot to recover the backing sats. This Bitcoin-only helper proves ownership of the saved slot record and spends the slot UTXO; V1 fungible cBTC/tacBTC redemption uses the shielded-pool path.
trusted setup
The mixer's zero-knowledge circuit was bootstrapped by a public multi-party ceremony. As long as one participant across the chain was honest, the setup is sound. The ceremony is finalized; every pool binds to the canonical verifying key.
Verifying key: Ceremony transcript:
initialize a new pool
Anyone can open a pool for any asset and any deposit size. The first confirmed pool for a given asset + amount wins. The verifying key and ceremony transcript are pre-pinned — pick an asset, type a denomination, broadcast.
Enter the deposit size in whole tokens. Pick an asset first to see the resolved on-chain u64.
▸ Canonical trusted setup & CIDs
Both CIDs are locked to the canonical Phase 2 trusted setup — 2,227 community contributions, Bitcoin-block beacon, finalized chain. Every pool for this circuit shares the same verifying key. Audit bundle on IPFS ↗
Shielded Pool Cross-chain
Unlock your wallet to view your confidential account and move value across chains.
Shielded Send Ethereum
🔒
Unlock your wallet to send
Same wallet as the Bitcoin lane — one identity, both chains. Your keys stay in this browser.
Shielded Swap Ethereum
Swap, shielded. Trade one note for another against the confidential AMM — your note balances stay private, but a swap settled on its own moves the pool's public reserves by exactly your trade, so its input and output amounts are visible on-chain. It clears on the Ethereum lane and settles gasless. Instant and note-to-note: you end up holding a shielded note, not real coins. To trade for real sats on Bitcoin, use the order book.
🔒
Unlock your wallet to swap
Same wallet as the Bitcoin lane — one identity, both chains. Your keys stay in this browser.
Confidential OTC note-for-note · fixed terms · settles as a shielded note
Trade note-for-note, privately. A confidential OTC swaps two shielded notes between a specific pair of counterparties atomically — no order book, no price curve, fixed agreed terms. Same From→To shape as a Swap, but the price is what you both agree, not a pool. Instant and note-to-note: you end up holding a shielded note, cleared on the Ethereum lane. For real sats, use the order book.
🔒
Unlock your wallet to trade OTC
Same wallet as the Bitcoin lane — one identity, both chains. Your keys stay in this browser.
cUSD & cBTC the bitcoin-backed dollar & conservation-backed BTC
Two steps: get cBTC, then borrow cUSD against it. cBTC is minted 1:1 from a Bitcoin lock only your key can spend; tacBTC is its ERC-20 form. Minting needs an ETH bond too — about 1.5× the lock's value, staked as wstETH and refundable — which deters spending the lock outside a valid exit; it does not back the peg. cUSD is the bitcoin-backed dollar: lock cBTC as collateral and mint a cUSD note. A position's amounts are public so it can be priced, but its owner is not linked to it. Both are ordinary shielded notes that transfer, trade and exit like anything else in the pool.
🔒
Unlock your wallet to borrow
Same wallet as the Bitcoin lane — one identity, both chains. Your keys stay in this browser.
Earn Ethereum provide liquidity · farm TAC
Earn TAC, shielded. Provide liquidity to a confidential pool and farm TAC rewards. Your LP shares sit in a shielded note; the liquidity you add shows in the pool's public reserves. Bond and harvest amounts are also public in the farm's own events — what stays private is that your notes aren't linked to your identity, not the amounts. Start from TAC you claimed, a note bridged from Bitcoin, or raw ETH; one click adds liquidity and bonds the shares into the farm in a single settle.
🔒
Unlock your wallet to provide liquidity and farm
Same wallet as the Bitcoin lane — one identity, both chains. Your keys stay in this browser.
Govern TAC proposals · private & public voting
Loading governance…
New Ethereum Asset Ethereum canonical factory
Unlock a wallet to deploy a tacit-compatible asset on Ethereum.
documentation

Confidential money across Bitcoin and Ethereum — one shielded note you can send, swap, borrow against, and bridge, settled in ordinary on-chain transactions.

Tacit is a Bitcoin metaprotocol: Bitcoin orders and stores the data, and open-source indexers enforce the token rules by reading the chain. The same note extends into a confidential pool on Ethereum, where it transfers, trades on an AMM, borrows and lends, and bridges back — amounts hidden and your key as your backup throughout.

The Ethereum contracts are live on mainnet at Etherscan-verified addresses. The ConfidentialPool, ConfidentialRouter, asset factory and SP1 guests are immutable: no owner, no proxy, no pause. The CollateralEngine and FarmManager are governed by a 2-of-4 multisig within fixed bounds. Addresses are in DEPLOYMENTS.md.

Shielded amounts default

Every transfer hides how much moved. Only sender and recipient can read the amount.

Pedersen commitments · aggregated Bulletproofs · kernel signatures

Shielded addresses opt-in

Receive at one-time addresses with no visible link to your published identity.

Blinded-pubkey commits — same curve math as BIP-341 / silent payments

Unlinkable pool spends

Inside the confidential pool, a spend does not reveal which note it consumes. Only deposits into and withdrawals out of the pool are public.

Nullifiers · SP1 proof of note-tree membership

Native AMM

Swap and provide liquidity on Bitcoin and in the confidential pool. Balances stay in hidden notes; pool reserves are public, so a swap's size shows in the reserve change.

Constant-product curve · public reserves

cBTC from Bitcoin locks

cBTC is minted 1:1 from SP1-reflected Bitcoin locks, with no price oracle in the peg. tacBTC is its ERC-20 form. A slashable wstETH escrow (1.5× the lock today) makes spending a lock unprofitable.

SP1 Bitcoin reflection · slashable wstETH escrow

Atomic marketplace

Orderbook and atomic-offer trades settle asset-for-sats atomically in one Bitcoin transaction: a fill completes in full or not at all.

Pre-signed offers · one Bitcoin transaction per fill

Cross-chain note btc ⇄ eth

Wrap from Bitcoin or Ethereum into the same shielded note, and bridge value back across. No multisig or attestor set signs a bridge message.

SP1 zero-knowledge reflection · each burn pays once

cUSD loans

Borrow cUSD against cBTC collateral. A CDP position's amounts are public so it can be priced, but its owner is not linked to it.

Unlinkable CDP positions · 150% mint / 130% liquidation

Gasless by relay opt-in

A relayer can settle any op for you and take its fee inside the proof — you never need the chain's gas token to move.

Fee bound in the conservation kernel · relayer can't redirect or pad it

Fair-launch mints, airdrops and LP farms use the same notes. The privacy layers compose: keep the defaults for amount privacy, or add shielded addresses and pool round trips for unlinkability.

Validation model

Bitcoin validates the transactions; indexers validate the tokens. Tacit's rules are not enforced by Bitcoin nodes: an indexer reads the chain and checks the cryptography on top — commitments, range proofs, kernel signatures and zero-knowledge proofs.

Everything an indexer needs is on the chain. Any UTXO can be walked back to its origin and re-verified step by step, so every indexer running the spec reaches the same verdict, with no off-chain proof files and no consensus change.

That also means your key is your backup: a fresh wallet holding only its private key rebuilds its balance from chain data alone. For how SP1 proofs, range proofs and the two Groth16 ceremonies fit together, see SPEC §2.8.

The sections below go one layer deeper each, from privacy down to the cryptography and the security work behind it:

Privacy scope

Three privacy layers, each usable on its own:

  • Shielded amount (default) — every CETCH, T_MINT, CXFER, T_AXFER and burn-change output is a 33-byte Pedersen commitment plus an 8-byte encrypted amount. Pool notes use the same commitment.
  • Shielded address (opt-in) — the sender pays to commit = recipient_pubkey + b·G, with b derived from ECDH between sender and recipient and a per-transaction anchor. Each receipt lands at a one-time address with no visible link to the recipient's published key. Same curve math as BIP-340/341/352.
  • Confidential pool — a pool spend does not reveal which note it consumes, and amounts stay hidden on both chains. Only entry and exit are public.

The fixed-denomination Bitcoin mixer (T_DEPOSIT / T_WITHDRAW) is legacy; the confidential pool is the privacy surface for any amount.

What remains public:

  • Asset id on Bitcoin transfers.
  • Bitcoin addresses and the transaction graph, including the sender pubkey the recipient needs for ECDH recovery.
  • The pool boundary — deposits into and withdrawals out of the confidential pool.
  • AMM reserves and burn amounts. Trade sizes are hidden only in Bitcoin T_SWAP_BATCH and pool OP_SWAP_BLIND batches; live pool swaps settle as OP_SWAP_ROUTE, whose amounts show in the reserve change.
  • The prover's witness. Whoever proves a pool batch sees its inputs. To keep them private, prove locally.
Blinding delivery + amount recovery

Recipient blinding: r_recip = HMAC-SHA256(SHA-256(ECDH_x), "tacit-blind-v1" ‖ anchor ‖ vout_LE) mod n, where anchor = first_asset_input_txid_BE ‖ first_asset_input_vout_LE. The per-transaction anchor gives every transfer between the same two parties fresh blindings, so commitments cannot be compared across transactions.

Sender's change blinding: r_change = HMAC-SHA256(sender_priv, "tacit-change-v1" ‖ anchor ‖ vout_LE), where vout_LE is the change output's index in the reveal tx. It is deterministic from the wallet key, so it is recoverable from the chain alone.

Etcher's supply blinding: r_supply = HMAC-SHA256(etcher_priv, "tacit-etch-v1" ‖ etch_anchor), where etch_anchor = first input outpoint of the commit tx. The anchor predates the envelope (a pre-existing UTXO), breaking the cycle that would otherwise arise from anchoring on the reveal txid. Scanners read it via reveal_tx.vin[0] → fetch commit tx → commit_tx.vin[0].

Each commitment also carries an encrypted amount (8 bytes, u64 LE XOR'd with an HMAC-keystream). For CXFER outputs, the keystream uses ECDH-derived keying for recipients (so recipient can decrypt with their priv + sender pub) and self-derived keying for change. For CETCH supply, the keystream is self-derived from etcher_priv + etch_anchor — only the etcher can decrypt; observers see opaque bytes. After decryption, the wallet verifies C == amount·H + r·G; tampering with the ciphertext makes verification fail.

For self-derived roles (CETCH supply, MINT amount, CXFER change), blinding and keystream are derived under distinct HMAC domain tags (tacit-etch-v1 vs tacit-etch-amount-v1; tacit-mint-blind-v1 vs tacit-mint-amount-v1) so the 8-byte keystream output cannot leak any structure of the 32-byte blinding scalar.

So share-links are optional notifications, not required for recovery. A fresh wallet with only its key finds its balance from the chain: incoming transfers via ECDH, and its own change, etches and mints via self-derived keys.

Operations

Every Bitcoin-side action is one of the operations below, carried inside an ordinary Bitcoin transaction.

The envelope version is 0x01, and every op is a commit/reveal pair carrying the envelope in tx.vin[0].witness[1] of the reveal. SPEC.md §3.9 is the canonical opcode map, with assigned, legacy and reserved bytes.

Core asset lifecycle

0x21CETCHissue new asset; hidden initial supply, optionally mintable 0x22CXFER_BPPBulletproofs+ transfer (~14% smaller rangeproof); same wire shape as CXFER 0x23CXFERconfidential transfer; split into 1, 2, 4, or 8 outputs 0x24T_MINTissuer-signed additional supply on a mintable CETCH 0x25T_BURNany holder destroys supply; public burned_amount 0x27T_PETCHpermissionless-mint deployment: cap + per-mint amount + height window; deployer gets zero tokens 0x28T_PMINTanyone mints exactly mint_limit; reveals (amount, blinding) so the cap is auditable

Marketplace (atomic offers + orderbook)

0x26T_AXFERCXFER variant with aux BTC inputs — single-tx atomic offers, preauth sales and batched takes 0x37T_AXFER_VARlegacy variable-amount atomic settlement; builders off, not reflected 0x5BT_PREAUTH_BIDbuyer-offline bid, exact fill — buyer signs once, any seller completes it; wallet builders gated 0x5CT_PREAUTH_BID_VARbuyer-offline bid, partial fills from a pre-signed grid; residual returns to the buyer; wallet builders gated 0x3CT_AXFER_BPPBulletproofs+ variant of T_AXFER; same wire shape, smaller rangeproof 0x3DT_AXFER_VAR_BPPlegacy Bulletproofs+ variant of T_AXFER_VAR

Legacy: mixer and claim pools

0x29T_DEPOSITlock a fixed-denomination UTXO into a mixer pool (Poseidon leaf) 0x2AT_WITHDRAWGroth16-gated mint from a pool; recipient unlinkable to any specific deposit 0x2BT_DROPlock existing supply into a public claim pool with optional Merkle eligibility gate (a 161-byte 0x2B is the bridge burn) 0x2CT_DCLAIMpermissionless claim from a drop; reveals (per_claim, blinding) for audit

Native AMM (SPEC §3.5)

0x2DT_LP_ADDadd liquidity to a confidential AMM pool; variant=1 sentinel doubles as POOL_INIT 0x2ET_LP_REMOVEburn LP shares for proportional withdrawal of pool reserves 0x2FT_SWAP_BATCHbatched uniform-price settlement; N intents at one P_clear via Groth16 over hidden per-trader amounts 0x30T_INTENT_ATTESTreserved name; no validator 0x31T_PROTOCOL_FEE_CLAIMmint a pool's accrued protocol-fee skim to its configured recipient 0x32T_SWAP_VARper-trade variable-amount swap; cleartext amounts against curve, no Groth16, no ceremony coupling 0x33T_SWAP_ROUTEatomic multi-hop AMM routing (2..4 pools in one Bitcoin tx); reuses T_SWAP_VAR cryptography

LP-staking farms (SPEC §3.5)

0x34T_FARM_INITlauncher-funded LP-staking reward farm; virtual treasury 0x35T_LP_BONDbond lp_asset_id shares against a farm; per-bond worker-indexed record 0x36T_LP_UNBONDsettle bond: mint fresh LP shares + reward UTXO 0x3BT_LP_HARVESTclaim accrued reward without unbonding 0x3ET_FARM_REFUNDlauncher reclaims unspent treasury after the farm end plus a grace period

Legacy: wrapper attestations and cBTC.zk slots

0x38T_WRAPPER_ATTESTwrapper attestation — issuer-signed reserves and supply for a wrapper asset 0x43T_SLOT_MINTcBTC.zk: mint a self-custody BTC slot (fungible cBTC uses T_CBTC_LOCK instead) 0x44T_SLOT_BURNcBTC.zk: redeem BTC from a live self-custody slot 0x45T_SLOT_ROTATEcBTC.zk: atomic key rotation / transfer of a slot 0x46T_SLOT_SPLITcBTC.zk: atomic 1→N slot split, ΣD_new = D_old 0x47T_SLOT_MERGEcBTC.zk: atomic N→1 slot merge

Cross-chain and cBTC (SPEC §3.6–3.7)

0x39T_CXFER_BOUNDtransfer whose outputs become notes bound to one pool deployment, spendable in its fast lane 0x2Bbridge burn161 bytes; burns a note for the pool deployment named in it, which mints it once 0x65T_CROSSOUT_MINTre-mints on Bitcoin a note crossed out of the pool 0x66T_CBTC_LOCKregisters a self-custody BTC output for cBTC minting on Ethereum 0x67T_CBTC_REDEEMunlocks a lock and burns exactly its value in cBTC 0x68T_BTC_CALLvalue-free, Bitcoin-signed authorization of an Ethereum call 0x69T_ETH_CALLdelivers a message from Ethereum's EthCallOutbox

Legacy: early tETH bridge

0x60–0x64tETH bridge opskept in the validator for recovery; new flows use the confidential pool

Wire-format examples below cover the core four (CETCH / CXFER / T_MINT / T_BURN). The other opcodes use the same envelope with their own payload layouts and rules — see SPEC.md §3.

Marketplace flows settle through T_AXFER and the preauth bids, with no separate opcodes: atomic intents (a seller publishes one signed offer and any taker settles it atomically on Bitcoin), preauth sales (the seller pre-signs, then goes offline), and batched takes (a buyer sweeps several preauths in one reveal tx, using position-independent SIGHASH_SINGLE|ANYONECANPAY). They share the kernel and range-proof checks of a plain CXFER; only the coordination differs.

Wire format (Taproot envelope)

Rangeproofs and aggregated payloads don't fit in 80-byte OP_RETURN, so payload moves into a Taproot script-path leaf. Each operation is 2 transactions: a commit tx that creates a P2TR output committed to the envelope's leaf hash (internal pubkey = BIP-341 NUMS, so script-path is the only spend), and a reveal tx that spends the P2TR via script-path, exposing the envelope script in the witness. Indexers scan tx.vin[0].witness[1] for envelopes.

Envelope leaf script

32B<signing pubkey x-only>wallet pubkey allowed to sign reveal acOP_CHECKSIGverifies witness signature 00 63OP_FALSE OP_IFenter unreachable data block 05"TACIT"protocol magic 01versionenvelope version (0x01) ..payload chunks (≤520B each)opcode + body + rangeproof 68OP_ENDIFend

CETCH payload (opcode 0x21) — initial issuance

21T_CETCHconfidential etch Lticker length1 byte (1–16) ..tickerUTF-8 ddecimals1 byte (0–8) 33Bcommitment Ca·H + r·G 8Bamount_ctu64 LE supply XOR keystream — etcher-only, recoverable from chain 2Brp_lenu16 LE; rangeproof byte count (688 for m=1) ..rangeproofaggregated bulletproof, m=1, n=64 32Bmint_authorityx-only pubkey or all-zero (non-mintable) 2Bimage_lenu16 LE (0–256) ..image_uriopaque UTF-8; image OR metadata-blob CID; renderers MUST validate scheme

The wire format treats image_uri as opaque UTF-8 bytes. The decoder accepts any valid UTF-8 up to 256 bytes and does not enforce a URI scheme. Renderers MUST validate before display: tacit's UI accepts only ipfs://CID and bare CIDs (Qm…/bafy…), and rejects http:, https:, javascript:, data: and other schemes. Direct https:// images are rejected so viewing an asset never contacts an issuer-controlled host; IPFS images load through the gateways the CSP allows at img-src.

Asset_id = sha256(reveal_txid_BE ‖ 0_LE) (32 bytes). Supply commitment lives at vout 0 of the reveal tx. If mint_authority is all-zero, the asset is fixed-supply (BTC-style); otherwise the named pubkey can issue more via T_MINT.

CXFER payload (opcode 0x23) — confidential transfer

23T_CXFERconfidential transfer 32Basset_idsingle asset 64Bkernel_sigBIP-340 Schnorr; proves balance closes Noutput count1, 2, 4, or 8 (must be a power of 2 for aggregation) 33BC_icommitment for vout i 8Bamount_ct_iu64 LE plaintext XOR keystream — recoverable from chain 2Brp_lenu16 LE; aggregated rangeproof byte count ..rangeproofsingle bulletproof covering all N commitments

MINT payload (opcode 0x24) — issue more supply (mintable assets only)

24T_MINTsigned by mint_authority 32Basset_idmust equal sha256(etch_txid ‖ 0) 32Betch_txidparent CETCH; validator confirms it's mintable 33Bcommitmenta·H + r·G for the new supply 8Bamount_ctissuer-self keystream; only the authority can decrypt 2Brp_lenu16 LE ..rangeproofm=1 bulletproof on the new commitment 64Bissuer_sigBIP-340 over sha256("tacit-mint-v1" ‖ asset_id ‖ commit_anchor(36B) ‖ commitment ‖ amount_ct), under mint_authority

commit_anchor = commit_tx.vin[0].txid_BE ‖ commit_tx.vin[0].vout_LE (the same anchor the issuer uses for the mint blinding/keystream). Binding it into mint_msg ties the issuer's signature to a specific commit/reveal pair so a witnessed mint envelope cannot be replayed into a different commit/reveal pair.

BURN payload (opcode 0x25) — destroy supply, public amount

25T_BURNany holder; emits a public burned_amount 32Basset_idsingle asset 8Bburned_amountu64 LE plaintext — public, auditable 64Bkernel_sigBIP-340; proves Σ C_in = burned·H + Σ C_out Noutput count0, 1, 2, 4, or 8 (N=0 = full burn, no change) 33BC_ichange commitment for vout i 8Bamount_ct_iself keystream for the burn-tx's change output 2Brp_lenu16 LE (0 if N=0) ..rangeproofaggregated bulletproof on N change commitments
Indexer rules (recursive validation)

This is how a wallet, or any independent indexer, works out what is valid and what each balance is. Anyone can re-run these steps and reach the same answer:

  1. For each wallet UTXO, fetch parent tx; read vin[0].witness[1]; decode as tacit envelope.
  2. Recursively validate the outpoint: walk the ancestry back to its CETCH root, validating each step. Each outpoint validates once and is memoized, so cost is O(N) over an ancestry of N steps. There is no depth cap.
  3. Aggregated rangeproof verification. A single bulletproof per envelope covers all m commitments (m ∈ {1,2,4,8}). The walker can additionally batch proofs across the entire ancestry into one multi-scalar multiplication via bpRangeAggBatchVerify, with random per-proof scalars αi, βi ensuring soundness preservation.
  4. For CETCH leaves (T_CETCH): only vout 0 is the supply commitment; no kernel; the rangeproof bounds the initial supply.
  5. For MINT envelopes (T_MINT): vout 0 holds the new supply commitment. Validator confirms asset_id = sha256(etch_txid ‖ 0), recursively validates the CETCH ancestor, requires mint_authority ≠ 0, and verifies the issuer's BIP-340 sig under the authority's x-only pubkey. mint_msg binds commit_anchor (the commit-tx's first input outpoint), preventing replay of the envelope into a different commit/reveal pair.
  6. For CXFER nodes (T_CXFER): every input outpoint must itself recursively validate; every input's parent (CETCH/MINT/CXFER/BURN) must declare the same asset_id; the aggregated rangeproof must verify; the kernel sig must verify under (ΣC_out − ΣC_in).xonly() over the kernel msg.
  7. For BURN nodes (T_BURN): same as CXFER but the verifier reconstructs E' = ΣC_out + burned_amount·H − ΣC_in. N=0 is allowed (full burn, no change output, no rangeproof).
  8. Any failure anywhere in the ancestry → the UTXO is flagged as inflation attempt and excluded from balance. markAll propagates the verdict to every sibling output of a failed envelope.
  9. Resolve (amount, blinding) for the commitment: try local opening first, then trial-decrypt the on-chain amount_ct (ECDH against sender pubkey for incoming, self-derived for own change/etch/mint). Verify C == a·H + r·G. If known or recovered → balance += a; if neither path works → "ghost".
DeFi & cross-chain

The confidential pool carries the note onto Ethereum and back, and lets it do more than move:

  • One note, two chains. Wrap ETH or any token through the ConfidentialRouter, or bring value over from Bitcoin — both land as the same shielded note. From there it transfers, trades, or borrows, and can exit on either side.
  • cUSD CDP. Borrow cUSD against cBTC. A position's collateral and debt are public so the CollateralEngine can price them, but the position cannot be linked to its owner. Minting needs 150% collateralization and liquidation is possible below 130%; a stability fee and savings rate are built in and dormant.
  • cBTC. Mint cBTC 1:1 against an SP1-reflected Bitcoin lock; supply is bounded by reflected locks, not a price feed. tacBTC is its ERC-20 form. Each mint needs a wstETH escrow (1.5× the lock value today), slashable if the lock is spent without a redeem; it deters, it does not back the peg.
  • LP farms. Bond LP shares to earn rewards. The FarmManager escrows the reward asset, so a farm never pays beyond what it holds.
  • Reflection bridge. Bitcoin ⇄ Ethereum value moves by SP1 zero-knowledge reflection: a burn on one side is proven on the other and minted once, after Bitcoin blocks are 24 confirmations deep. No multisig or attestor set signs a bridge message.
  • One unified address. Publish a single tacit1… address; senders don't need to know whether you're receiving on Bitcoin or in the confidential pool — it resolves to the right endpoint, and a send can wrap straight into a shielded note for the recipient.

Every one of these settles through the same conservation kernel as a plain transfer, so value-in equals value-out per asset, and any op can be relayed gaslessly with the fee bound inside the proof. See SPEC §6 for the cross-chain path.

Cryptography

See SPEC §2.8 for the canonical walkthrough.

On-chain commitments — Pedersen on secp256k1

Commitments: C = a·H + r·G, additively homomorphic. H is a NUMS generator (no known discrete log w.r.t. G) derived deterministically by hash-to-curve from the seed "tacit-generator-H-v1". The protocol carries C on chain; (a, r) stay private.

Rangeproofs — aggregated Bulletproofs + Bulletproofs+

Bünz et al. 2017, at n=64 bits — each committed value proven to lie in [0, 2⁶⁴). A single proof covers m ∈ {1, 2, 4, 8} commitments simultaneously via the inner-product argument, witness size O(log(n·m)). On secp256k1: 688 B at m=1, 754 B at m=2, 820 B at m=4, 886 B at m=8. The client batches the proofs along an ancestry into a single multi-scalar multiplication. A 64-bit range bounds one commitment to about 184 billion units at 8 decimals.

Bulletproofs+ (Chung et al. 2022) is the default for new transfers (T_CXFER_BPP) and every pool output: about 14% smaller at the same security level, with the same wire shape as the base opcode. Classic Bulletproofs stay valid, and the verifier picks the scheme by proof length.

Proof systems — SP1, range proofs, two small ceremonies

The confidential pool and reflection run on SP1 proofs, Bulletproofs+ range proofs and Schnorr kernels. None of these needs a Tacit ceremony (SP1 proofs are wrapped in Groth16 using SP1's own setup). Two Groth16 circuit sets over BN254, with Phase 1 from the Polygon Hermez ceremony, cover narrower jobs:

  • Swap batch — the AMM circuits (amm_swap_batch 171K constraints at N≤16 traders, plus amm_lp_add and amm_lp_remove; pot18 Phase 1). BabyJubJub Pedersen openings, range checks and a uniform clearing price over hidden per-trader amounts. The amm_swap_batch key is compiled into the settle guest (OP_SWAP_BLIND) and the reflection guest (T_SWAP_BATCH). OP_SWAP_BLIND is enabled in the guest but not yet emitted by the relayer; its batcher sees the amounts, while the SP1 prover and the chain do not.
  • Legacy mixer — withdraw.circom (Poseidon-Merkle leaf membership + nullifier reveal, pot14 Phase 1) for Bitcoin T_DEPOSIT / T_WITHDRAW only.

Chain commitments live on secp256k1; in-circuit work lives on BabyJubJub (the embedded curve over BN254 Fr). A 169-byte Camenisch–Stadler sigma proof binds the two, out-of-circuit, with no trusted setup, so no secp256k1 arithmetic runs inside the circuit.

Shielded-address primitive — blinded-pubkey commits

Opt-in: commit = recipient_pubkey + b·G, where b = HMAC(SHA-256(ECDH_x), domain ‖ network ‖ tx_anchor) mod n. The output pays to the commit, and the recipient spends it with sk + b mod n. Each receipt sits at a one-time address with no visible link to the recipient's published key. Same curve math as BIP-340 / BIP-341 / BIP-352; no ceremony. It composes with shielded amounts, and a plain pubkey recipient stays valid.

How attacks are blocked

Negative amounts (mod N). A "−1000" is just N − 1000 as a scalar; bulletproofs reject any value outside [0, 2⁶⁴). The proof's inner-product argument cannot be satisfied for a value that doesn't fit in 64 bits, so the verifier rejects.

Unbalanced amounts (CXFER). Even with rangeproofs, a sender holding 100 USDV could try to construct outputs (30 to recipient, 200 to themselves) — both with valid rangeproofs — and mint 130 from nothing. Kernel signatures block this:

  • Sender computes excess = Σr_out − Σr_in and signs kernel_msg with priv = excess (BIP-340 Schnorr).
  • Verifier reconstructs E' = ΣC_out − ΣC_in from on-chain commitments. If amounts balance, E' = excess·G and the sig verifies under E'.xonly().
  • If amounts don't balance, E' = δ·H + excess·G with δ ≠ 0. Producing a valid sig would require knowing the discrete log of H w.r.t. G, which is hard since H is NUMS.
  • kernel_msg = sha256("tacit-kernel-v1" ‖ asset_id ‖ N_in ‖ inputs ‖ N_out ‖ outputs ‖ burned_amount_LE), binding the sig to all relevant fields and preventing replay across txs.

Unbalanced amounts (BURN). Same kernel construction with burned_amount made explicit: the verifier checks E' = ΣC_out + burned·H − ΣC_in = excess·G. The public burned_amount field is bound into kernel_msg so claiming a smaller burn than was actually destroyed shifts the message and breaks the sig.

Mint forgery. Only the holder of mint_authority's private key can issue valid T_MINT envelopes for an asset. The validator fetches the parent CETCH, confirms the asset is mintable (mint_authority ≠ zero), and verifies the issuer Schnorr sig under that x-only key. The signed message binds the commit_anchor (commit-tx's first input outpoint) so an observer cannot rewrap the on-chain envelope payload into their own commit/reveal pair. The mint itself is rangeproof-bounded to [0, 2⁶⁴) per envelope; the authority can mint any number of times.

Cross-asset confusion. A sender could declare a CXFER as USDV but spend GOLDC inputs. The indexer fetches each input's parent envelope, reads its declared asset_id (across all four opcodes — CETCH, MINT, CXFER, BURN — via a unified parent-resolver), and rejects the CXFER if any input's asset_id differs from the current claim.

Validation cost

Verifying a coin costs work proportional to its history: recursive validation is O(ancestry size) on a cold cache. Range proofs along the walk are batched into one multi-scalar multiplication, and memoization keeps later scans in the same session to O(new UTXOs). Deep ancestries are slow on a fresh device; a slow check affects speed only, never correctness.

Ceremony artifacts

Tacit's Groth16 circuits use two trusted-setup ceremonies, and every artifact is published on IPFS: the Powers-of-Tau lineage, each circuit's final zkey, the pinned verifying keys, and the Bitcoin-block beacon that seals each ceremony. Anyone can re-derive a pinned verifying key from its bundle. Soundness needs only one honest contributor; zero-knowledge never depends on the ceremony. CEREMONY.md lists every CID and hash.

Mixer ceremony — withdraw.circom

Phase 1 from the Polygon Hermez pot14 Powers of Tau; Phase 2 with 2,227 contributors, sealed by a beacon over Bitcoin block 948,824. Used by the legacy Bitcoin mixer, and available to any denominated anonymity pool built on Tacit.

~11,938withdrawPoseidon-Merkle leaf membership + nullifier reveal (mixer spend)
Pinned VK
sha256 760829334a…ce933af
Ceremony bundle
ipfs://bafybeidq2…ltxy2u (ptau · zkey · vkey · transcript)

AMM ceremony — three circuits

Three independent Phase 2 chains under one shared pot18 Phase 1 (5,018 contributions for amm_swap_batch, 13,668 for amm_lp_add, 13,703 for amm_lp_remove), all sealed by a single beacon over Bitcoin block 951,267. Pool reserves are public; the circuits hide per-trader amounts. The production amm_swap_batch zkey is bafybeieb5haf…xefwqm.

~5,153amm_lp_addconfidential liquidity add (hidden LP-share blinding) ~10,369amm_lp_removeproportional withdrawal with confidential receipts ~171,162amm_swap_batchbatched uniform-price clearing over hidden per-trader amounts (N ≤ 16)
Pinned VK bundle
ipfs://bafkreibjpe…iomp4
Ceremony bundle
ipfs://bafybeiheww…niuam (per-circuit zkeys · vkeys · transcripts)

The confidential pool, cBTC and the reflection bridge otherwise run on SP1 proofs, Bulletproofs+ and Schnorr kernels, which need no Tacit ceremony. The one exception is swap batches: the amm_swap_batch key above is compiled into both SP1 guests, for OP_SWAP_BLIND and T_SWAP_BATCH. See SPEC §2.8 for the full circuit walkthrough.

Audits & reviews

Independent, adversarial reviews of the immutable contracts, the SP1 guests and the relayed cross-chain ops, before and after deployment. The deployed bytecode was checked byte for byte against source. No open fund-impacting findings: every finding is fixed or dispositioned in its report.

Full review history: AUDITS.md. The v1 release's agentic audits: Fable 5.1 lock checkpoint (the review that gated the deploy), an external automated review, and the Pashov solidity-auditor v4 round (24-agent, post-deploy, extended to the zkVM guest) — run after a week of live mainnet use.

Trust model

What each surface relies on, beyond Bitcoin and Ethereum consensus and the soundness of SP1 and Groth16:

Confidential pool. The pool, router, asset factory and SP1 guests are immutable. The CollateralEngine and FarmManager are governed by a 2-of-4 ops multisig within fixed bounds, with a delay and notice before changes that work against borrowers or farmers. cUSD relies on the CollateralEngine's price feed. Ethereum → Bitcoin reflection relies on the sp1-helios sync committee. Relayers, the hosted API and IPFS gateways cannot change validity; anyone can prove locally and call settle.

Issuer at CETCH. The initial supply commitment is hidden, so no kernel-sig constraint applies — the issuer chooses any value in [0, 2⁶⁴). The dApp publishes the (supply, blinding) opening by default in the asset's IPFS metadata, so anyone can check C == supply·H + r·G against the on-chain commitment. An issuer can opt out to keep the total confidential.

Mint authority for mintable assets. If mint_authority ≠ 0 in the CETCH envelope, that x-only pubkey can issue additional supply via T_MINT envelopes — bounded only by 2⁶⁴ per envelope, unlimited in count. Holders trust whoever holds that key. The dApp attests every mint by default so supply stays auditable. Fixed-supply CETCHes (mint_authority all-zero), such as TAC, can never be expanded.

T_PETCH (permissionless mints). No issuer trust at all. The deploy declares cap_amount, mint_limit, and an optional height window; deployer receives zero tokens. Each T_PMINT reveals (amount, blinding) on chain, so cumulative supply against the cap is auditable from chain alone — no attestation channel needed.

Ceremony circuits. Soundness of the legacy mixer circuit and the three AMM circuits rests on at least one honest contributor per Phase 2 chain; zero-knowledge does not depend on the ceremony. The amm_swap_batch key is the only ceremony key the confidential pool uses, for OP_SWAP_BLIND. On Bitcoin, AMM reserves are public numbers the indexer tracks and no UTXO holds pool funds; Groth16 binds hidden per-trader amounts in T_SWAP_BATCH to public batch deltas.

cBTC / tacBTC. Minted from SP1-reflected Bitcoin locks plus a CollateralEngine wstETH escrow (1.5× the lock today). Total cBTC cannot exceed reflected locked sats; the price feed only sizes the escrow. Custody is economic: the locker holds the key, and the escrow makes spending the lock unprofitable.

After issuance. No participant can inflate (kernel sig blocks unbalanced CXFER / T_AXFER / T_BURN; T_PMINT caps are chain-auditable; AMM circuits bind hidden amounts to public deltas), burn covertly (BURN's burned_amount is public and bound into the kernel msg), or substitute assets (asset_id consistency checked across every input's parent envelope). Recursive client-side validation guarantees this independent of any indexer's honesty.

Pedersen commits via @noble/secp256k1 projective ops. NUMS H from deterministic hash-to-curve. BIP-340 Schnorr + BIP-341 Taproot inlined. Aggregated bulletproofs (Bünz et al. 2017) + Mimblewimble-style kernel sigs in pure JS, with Pippenger MSM for batched verification. Groth16 prover + verifier via snarkjs (vendored); Poseidon-Merkle (legacy mixer) and BabyJubJub Pedersen (AMM swap batch) inside the circuits; SP1 proves pool settlement and reflects Bitcoin state into the confidential pool; Camenisch–Stadler sigma cross-curve binding between secp256k1 and BabyJubJub at 169 bytes, no trusted setup.