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
Name, logo, description, link. Skip this for a fast anonymous etch — the ticker is the only on-chain identity either way.
Two permanent choices, locked in at etch — read both before continuing.
(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.
bc1q…/tb1q… address.The tacit asset to send. Source amounts get floor-truncated to this asset's decimals.
Etherscan token-holder CSV exports (HolderAddress,Balance,…) work as-is. Add as many as you want — same-address balances are summed across all sources.
Drop the zero/dead address, CEX hot wallets, and your own team wallets so they don't get a share. One address per line.
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.
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.
fulfilled[] ledger; import below to restore on another device.
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.
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).
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.
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.
Every transfer hides how much moved. Only sender and recipient can read the amount.
Pedersen commitments · aggregated Bulletproofs · kernel signatures
Receive at one-time addresses with no visible link to your published identity.
Blinded-pubkey commits — same curve math as BIP-341 / silent payments
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
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 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
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
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
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
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.
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:
Three privacy layers, each usable on its own:
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.The fixed-denomination Bitcoin mixer (T_DEPOSIT / T_WITHDRAW) is legacy; the confidential pool is the privacy surface for any amount.
What remains public:
T_SWAP_BATCH and pool OP_SWAP_BLIND batches; live pool swaps settle as OP_SWAP_ROUTE, whose amounts show in the reserve change.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.
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
mint_limit; reveals (amount, blinding) so the cap is auditable
Marketplace (atomic offers + orderbook)
Legacy: mixer and claim pools
0x2B is the bridge burn)
0x2CT_DCLAIMpermissionless claim from a drop; reveals (per_claim, blinding) for audit
Native AMM (SPEC §3.5)
LP-staking farms (SPEC §3.5)
Legacy: wrapper attestations and cBTC.zk slots
Cross-chain and cBTC (SPEC §3.6–3.7)
EthCallOutbox
Legacy: early tETH bridge
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.
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.
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.
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.
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:
vin[0].witness[1]; decode as tacit envelope.bpRangeAggBatchVerify, with random per-proof scalars αi, βi ensuring soundness preservation.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.asset_id; the aggregated rangeproof must verify; the kernel sig must verify under (ΣC_out − ΣC_in).xonly() over the kernel msg.E' = ΣC_out + burned_amount·H − ΣC_in. N=0 is allowed (full burn, no change output, no rangeproof).markAll propagates the verdict to every sibling output of a failed envelope.(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".The confidential pool carries the note onto Ethereum and back, and lets it do more than move:
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.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.
See SPEC §2.8 for the canonical walkthrough.
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.
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.
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:
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.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.
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.
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:
excess = Σr_out − Σr_in and signs kernel_msg with priv = excess (BIP-340 Schnorr).E' = ΣC_out − ΣC_in from on-chain commitments. If amounts balance, E' = excess·G and the sig verifies under E'.xonly().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.
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.
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.
sha256 760829334a…ce933afAMM 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.
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.
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.
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.