[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81139":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":13,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":17,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":15,"starSnapshotCount":15,"syncStatus":16,"lastSyncTime":27,"discoverSource":28},81139,"tacit","z0r0z\u002Ftacit","z0r0z","smart contracts on bitcoin",null,"JavaScript",34,9,1,7,0,2,3,46.2,"MIT License",false,"main",true,[],"2026-06-12 04:01:32","\u003Cp align=\"center\">\n  \u003Cimg src=\".\u002Ftacit.svg\" alt=\"tacit\" width=\"120\">\n\u003C\u002Fp>\n\n# tacit\n\nA meta-protocol on Bitcoin that scales the Runes\u002FOrdinals pattern past\nplain tokens — confidential value, anonymous spend, native AMM,\ntrustless wrapped BTC, trustless wrapped ETH, all enforced by indexers\nanyone can run and reach the same verdict from chain alone. No\nfederation, no sidechain, no smart-contract runtime. Cryptographic\nprivacy and Groth16 circuits do the work a VM would do elsewhere.\n\n> **Status:** signet + mainnet. Sign in with Xverse \u002F UniSat \u002F\n> Leather, import a privkey, or — on signet — let the dApp generate\n> one and grab faucet sats.\n>\n> **Live demo:** [tacit.finance](https:\u002F\u002Ftacit.finance)\n>\n> **Protocol specs:**\n> [`SPEC.md`](.\u002FSPEC.md) — canonical wire-format authority ·\n> [`AMM.md`](.\u002FAMM.md) — confidential AMM architecture ·\n> [`MIXER.md`](.\u002FMIXER.md) — shielded-pool architecture ·\n> [`BRIDGE.md`](.\u002FBRIDGE.md) — tETH trustless ETH-Bitcoin bridge ·\n> [`spec\u002FCIRCUITS.md`](.\u002Fspec\u002FCIRCUITS.md) — how the ZK stack composes ·\n> [`spec\u002FGLOSSARY.md`](.\u002Fspec\u002FGLOSSARY.md) — terms that overlap across surfaces ·\n> [`spec\u002Famendments\u002F`](.\u002Fspec\u002Famendments\u002F) — cBTC.zk, cBTC.tac, farms, tETH, orderbook, governance.\n\n---\n\n## What it is\n\n**Tacit is what Runes becomes when you push the indexer-validated\nmeta-protocol pattern past plain tokens.** Same trust model — token\nrules aren't enforced by Bitcoin nodes, they're enforced by\nindexers that anyone can run and reach the same verdict from chain\ndata alone. Tacit applies that pattern to a much wider surface:\n\n- **Confidential value.** Every on-chain commitment is a Pedersen\n  point with an aggregated bulletproof rangeproof and a\n  Mimblewimble-style kernel signature. Supply conservation holds\n  without ever revealing individual amounts.\n- **Anonymous spend.** A Tornado-style shielded pool (Groth16 + a\n  Poseidon-Merkle tree + nullifiers) lets any holder deposit a\n  fixed-denomination UTXO and withdraw to a fresh address with no\n  on-chain edge linking the two.\n- **Native AMM.** A uniform-clearing-price block-batched AMM\n  between any two tacit assets, with confidential per-trader\n  amounts and mixer-composable LP shares. The pool reserves are\n  public numbers the indexer tracks; no UTXO holds any pool's\n  funds.\n- **Trustless wrapped BTC.** `cBTC.zk` locks real BTC at a\n  Taproot output whose spending key is derived from a mixer\n  leaf's secret — one note, two locks, no federation and no\n  co-signer.\n- **Fungible wrapped BTC.** `cBTC.tac` composes a cBTC.zk anchor\n  with an LP-share lien on the canonical (TAC, tETH) pool, so\n  amount-granular wrapped BTC is itself a standard tacit asset:\n  CXFER it, swap it, LP it, mix it. Trustless on the anchor side,\n  over-collateralized by a (TAC, tETH) LP on the fungibility side.\n- **Trustless wrapped ETH.** `tETH` deposits ETH on Ethereum into\n  a Poseidon-Merkle mixer contract, mints composable tETH on\n  Bitcoin via Groth16 proof, and withdraws back to Ethereum via\n  SP1-verified burn — no federation, no attestor, no multisig.\n  Every mint is client-side verified: re-verify the Groth16 proof\n  + check `ethRoot` against the Ethereum contract via `eth_call`.\n  Notes and shielded UTXOs are deterministically derived from\n  privkey alone. See [`BRIDGE.md`](.\u002FBRIDGE.md).\n- **Native marketplace.** Atomic OTC settlement of a confidential\n  token against a BTC payment in one Bitcoin tx (`T_AXFER`), plus\n  variable-amount partial fills (`T_AXFER_VAR`) and buyer-offline\n  preauthorized bids (`T_PREAUTH_BID_VAR`) for walk-away flow.\n- **Airdrops, fair-launches, drops.** `T_PETCH` \u002F `T_PMINT` for\n  permissionless-mint assets with publicly auditable caps;\n  `T_DROP` \u002F `T_DCLAIM` for ETH-gated public-claim pools;\n  batched confidential CXFER airdrops for issuer-side\n  distributions.\n\n**The architectural move.** Indexer-validated meta-protocols\n(Runes, Ordinals, BRC-20) trade real market value purely on\nindexer agreement over chain state. Tacit leverages that same\nconsensus-of-indexers into a **collateral substrate**: TAC's\nmarket-validated value (the same kind of value a Rune carries)\nbecomes the bond that makes wrapped BTC trustless without\nfederation. Cryptographic primitives (Pedersen, Groth16,\nBabyJubJub-Pedersen, sigma binding) handle the parts that must\nbe cryptographic — custody of real BTC, amount confidentiality,\nanonymous spend. The result is smart-contract-shaped properties\n— AMM trading, collateralized wrapping, batched settlement —\ndelivered without a VM, without a sidechain, without leaving\nBitcoin L1.\n\nSee [`spec\u002FCIRCUITS.md`](.\u002Fspec\u002FCIRCUITS.md) for how the two\nGroth16 circuit families (`withdraw.circom` for anonymous spend\n+ AMM circuits for amount-confidentiality) compose across these\nsurfaces.\n\nWhat tacit doesn't do:\n\n- Hide the address graph (sender\u002Frecipient Bitcoin addresses are\n  visible — same as every Bitcoin-substrate protocol).\n- Hide the asset ID (which token is moving is public).\n- Run general-purpose code (no Turing-complete VM — the protocol\n  grows by adding opcodes and circuits, not by executing user\n  scripts).\n- Eliminate issuer trust for confidential-supply assets unless\n  the issuer publishes `(supply, blinding)`. The dApp publishes\n  by default; opt-out is explicit.\n\n---\n\n## How tacit compares\n\nBitcoin's protocol surface sorts roughly along these axes: **where\nvalidity is enforced**, **whether amounts are exposed**, **whether\nthe protocol does more than tokens**, and **whether wrapped BTC is\ntrustless**.\n\n| | Substrate | Validity | Amounts | AMM | Trustless wBTC | Federation |\n|---|---|---|---|---|---|---|\n| Ordinals \u002F BRC-20 | Bitcoin | Indexer | Public | — | — | None |\n| Runes | Bitcoin | Indexer | Public | — | — | None |\n| RGB | Bitcoin (anchor) | Off-chain client-side proofs | Hidden | — | — | None |\n| Taproot Assets | Bitcoin (anchor) | Off-chain client-side proofs | Partial | — | — | None |\n| Liquid CT + Liquid AMM | **Federated sidechain** | Sidechain consensus | Hidden | Yes | — | **15-of-N** |\n| Citrea \u002F Botanix \u002F rollups | Bitcoin (rollup) | Rollup operator \u002F fraud proofs | Varies | Yes | Varies | Operator set |\n| **tacit** | **Bitcoin** | **Indexer** | **Hidden** | **Native** | **Yes (cBTC.zk)** | **None** |\n\nWhat tacit does that nothing else does in one stack:\n\n- **Confidential fungibles on Bitcoin proper.** Liquid CT uses the\n  same Pedersen + Bulletproof primitives but lives on a federated\n  sidechain. Tacit is just Bitcoin: every CXFER is a Bitcoin tx,\n  every UTXO is a Bitcoin UTXO, no bridge.\n- **Native AMM on Bitcoin L1.** Uniform-clearing-price block-batched\n  AMM with confidential per-trader amounts and mixer-composable LP\n  shares. No L2, no rollup, no smart-contract runtime — the\n  \"contract\" is a Groth16 circuit + indexer rules fixed at pool\n  init. AMMs on Bitcoin sidechains (Liquid SideSwap) inherit\n  federation trust; AMMs on Bitcoin rollups (Citrea, Botanix)\n  inherit operator-set \u002F fraud-proof trust. Tacit has neither.\n- **Trustless wrapped BTC.** `cBTC.zk` locks real BTC at a Taproot\n  output `K_btc = r_leaf · G_secp256k1` derived from a mixer note's\n  own secret. No federation, no co-signer, no oracle. WBTC,\n  tBTC, RBTC: all federated or threshold-bonded. cBTC.zk is\n  cryptographic.\n- **Fungible wrapped BTC, also without federation.** `cBTC.tac`\n  layers an LP-share lien on top of a cBTC.zk anchor with TAC\n  over-collateral. Trust model: TAC stays valuable enough\n  relative to BTC. Same shape as DAI's ETH-collateralization risk;\n  not the same shape as wBTC's BitGo + auditors.\n- **No off-chain proof exchange.** RGB and Taproot Assets push\n  validity off-chain — the recipient must receive a proof chain\n  from the sender, and losing it loses the balance. Tacit keeps\n  everything on-chain; a wallet recovers full state from privkey\n  + chain alone, even years later, with no surviving relationship\n  to the sender.\n- **Trustless wrapped ETH on Bitcoin L1.** `tETH` deposits ETH into\n  an Ethereum mixer contract, mints composable tETH on tacit via\n  Groth16, and withdraws via SP1-verified burn. No federation, no\n  attestor set. Client-side Ethereum root verification on every mint.\n- **OTC settlement in one Bitcoin tx (`T_AXFER`).** A confidential\n  token transfer and the BTC payment that pays for it close in\n  the same tx, atomically. `T_AXFER_VAR` adds partial fills;\n  `T_PREAUTH_BID_VAR` adds buyer-offline walk-away bids.\n  Ordinals atomic listings are the closest precedent, but they're\n  public-amount; tacit gets the same atomicity over hidden balances.\n\nScope boundaries:\n\n- **On-chain inscriptions (file bytes in Bitcoin witnesses).**\n  Ordinals embeds the file directly into the witness; tacit\n  carries only an `imageUri` on-chain and pins media to IPFS.\n- **Lightning-native assets.** Taproot Assets is built into the\n  LN stack; tacit is on-chain only.\n- **Asset-graph privacy.** `asset_id` is visible in every\n  envelope. Surjection proofs (on the roadmap) would hide this.\n- **Address-graph privacy.** Same as every Bitcoin-substrate\n  protocol. No CoinJoin; BIP-352 silent-payments\n  composition is on the roadmap.\n\n---\n\n## Architecture in one screen\n\nThe protocol grows by applying a small number of primitives across\nmany surfaces. Two Groth16 circuit families and a uniform\nout-of-circuit toolkit do all the cryptographic work; the indexer\ndoes the accounting; Bitcoin holds the data.\n\n```\n                  Bitcoin L1 (substrate)\n                          │\n       ┌──────────────────┴───────────────────┐\n       │      indexer-validated rules         │\n       │   (same trust model as Runes; any    │\n       │    party reaches the same verdict    │\n       │    from chain data alone)            │\n       └──────────────────┬───────────────────┘\n                          │\n       ┌──────────────────┴───────────────────┐\n       │  out-of-circuit cryptographic stack  │\n       │   secp256k1 Pedersen · bulletproofs  │\n       │   BIP-340 Schnorr · 169-byte sigma   │\n       │   cross-curve binding (secp ↔ BJJ)   │\n       └─────┬─────────────────────────┬──────┘\n             │                         │\n       ┌─────┴─────┐             ┌─────┴─────┐\n       │ withdraw  │             │    AMM    │\n       │ .circom   │             │ circuits  │\n       │           │             │           │\n       │ Poseidon  │             │ BabyJubJub│\n       │ leaf +    │             │ Pedersen +│\n       │ Merkle +  │             │ range +   │\n       │ nullifier │             │ in-circuit│\n       │           │             │ AMM logic │\n       └─────┬─────┘             └─────┬─────┘\n   anonymous-spend                 amount-confidentiality\n       │                                 │\n   ┌───┴────┐                    ┌───────┴────────┐\n   │ mixer  │                    │ T_LP_ADD\u002FREMOVE│\n   │ pool   │                    │ T_SWAP_BATCH   │\n   │        │                    │ T_SWAP_VAR (*) │\n   │ cBTC.zk│                    │ T_SWAP_ROUTE   │\n   │ slot   │                    │ T_FARM_*       │\n   │ ops    │                    │                │\n   └────┬───┘                    └───────┬────────┘\n        │                                │\n        └────────────┬───────────────────┘\n                     │\n              ┌──────┴──────┐\n              │  cBTC.tac   │  composes both families:\n              │             │  cBTC.zk slot (cryptographic anchor)\n              │             │  + AMM LP-share lien (indexer-enforced\n              │             │  collateral, (TAC,tETH)-LP over-collateralized)\n              └─────────────┘\n\n(*) T_SWAP_VAR uses no Groth16 — Pedersen + bulletproof + kernel\n    sig only. The \"two trader paths\" model picks circuits where\n    confidentiality is load-bearing and skips them where amounts\n    can be public.\n```\n\nThe diagram and full primitive-by-primitive walkthrough live at\n[`spec\u002FCIRCUITS.md`](.\u002Fspec\u002FCIRCUITS.md). The single-image version\nis [`tacit-circuits.svg`](.\u002Ftacit-circuits.svg).\n\n**Why this stack matters.** Indexer-validated meta-protocols like\nRunes already prove at scale that consensus-of-indexers can\nunderwrite real market value. Tacit takes the same consensus model\nand **leverages indexer-validated value into the collateral\nsubstrate for native-BTC wrapping** — TAC's market price (the same\nkind of value a Rune carries) becomes the bond that makes cBTC.tac\ntrustless without a federation. Cryptographic primitives handle the\nparts that must be cryptographic (real BTC custody at L1, amount\nconfidentiality, anonymous spend). Circuits handle privacy where\nit's load-bearing, and stay out of the way where it isn't. The\nresult is smart-contract-shaped properties — AMM trading,\ncollateralized wrapping, batched settlement — delivered without a\nVM, without a sidechain, without leaving Bitcoin L1.\n\n---\n\n## FAQ\n\n**What is tacit?** Confidential tokens, a native AMM, trustless wrapped\nBTC, a trustless ETH-Bitcoin bridge, and atomic marketplace settlement —\nall on Bitcoin L1, no federation, no off-chain proofs, no third-party\ntrust. Issue a token with public or hidden initial supply (the dApp\npublishes the supply opening to IPFS by default), or deploy a\nfair-launch asset (`T_PETCH`) where supply is minted permissionlessly\nagainst a publicly auditable cap. Send privately, swap on the AMM, LP,\nfarm, settle OTC atomically in a single Bitcoin tx, or route through the\nmixer pool for full unlinkability. Your privkey plus the Bitcoin chain\nare enough to recover your full balance years later.\n\n**Why tacit and not Runes \u002F Liquid \u002F RGB?** Runes and BRC-20 publish\namounts in cleartext — anyone with a block explorer sees your balance.\nLiquid CT hides amounts (and asset IDs too) but runs on a federated\nsidechain with ~15 KYC'd functionaries; that's not Bitcoin and it's\nnot trustless. RGB and Taproot Assets keep the substrate clean but\npush validation off-chain — the recipient has to receive and store a\nproof chain from the sender, and losing it loses the asset. Tacit layers\nprivacy on Bitcoin L1 — shielded amounts by default, opt-in shielded\naddresses for per-tx unique recipient markers, opt-in mixer pool for\nfull unlinkability — and recovers from privkey + chain alone, the way\nBitcoin itself works. See \"How tacit compares\" above for the longer\nbreakdown.\n\n**How do I see my tacit balances?** In the dApp here, or in any\nthird-party tacit indexer that implements [SPEC.md](.\u002FSPEC.md). Tacit\nenvelopes ride in Bitcoin Taproot witnesses; the dApp decodes them\nclient-side, walks ancestry to each CETCH\u002FMINT, and decrypts amounts via\nECDH — all from chain data alone, no server in the trust path. External\nwallets like Xverse \u002F UniSat \u002F Leather connect for *funding* the tacit\nwallet from your existing BTC; the tacit privkey itself stays in this\nbrowser, encrypted and separate from your external wallet's seed.\n\n**How much privacy do I get, and can I dial it?** Privacy is layered so\neach user picks the level that fits the use case rather than forcing one\nposture on everyone. Three orthogonal axes, composable:\n\n1. **Shielded amount** — default on every transfer. Pedersen + bulletproofs\n   hide the amount in every CETCH, T_MINT, CXFER, T_AXFER, and BURN-change\n   commitment. BabyJubJub Pedersen + Groth16 inside `T_SWAP_BATCH` hide\n   per-trader amounts during AMM settlement.\n2. **Shielded address** — opt-in per receipt, live in production for CXFER.\n   BIP-341-style blinded-pubkey commit `commit = recipient_pubkey +\n   blinding·G` with `blinding = HMAC(ECDH(sender_priv, recipient_pub) ||\n   domain || tx_anchor)`. On-chain recipient marker is a per-tx unique\n   P2WPKH address with no apparent link to the recipient's published\n   identity. Same crypto as BIP-340 \u002F BIP-341 \u002F BIP-352 silent payments;\n   no new ceremony. Same scheme as Liquid CT's confidential addresses but\n   on Bitcoin L1.\n3. **Mixer pool** — opt-in per UTXO, live in production. T_DEPOSIT locks\n   a fixed-denomination UTXO into a Poseidon Merkle tree; T_WITHDRAW\n   proves unspent-leaf membership via Groth16 + nullifier without\n   revealing which leaf. Breaks the on-chain link between deposit and\n   withdrawal entirely. The same circuit underpins cBTC.zk slot\n   semantics so trustless wrapped BTC inherits the primitive.\n\nPractical postures: a **merchant** keeps public transfers + shielded\nbalances (default — clean accounting + amount privacy on every line item).\nA **privacy-conscious user** publishes a `tcs1…` shielded address for\nreceipts and routes outbound payments through the mixer pool (full\nunlinkability at every endpoint). Asset-id privacy via asset surjection\nproofs is on the roadmap; until then asset_id is public on chain.\n\n**Am I locked into your platform?** No. The protocol spec is open (MIT,\n[SPEC.md](.\u002FSPEC.md) is authoritative); any indexer in any language\nreaches the same verdict from chain alone — re-implement it, audit it,\npin a copy of the dApp by IPFS CID. Asset metadata (images, descriptions,\nsupply openings) is pinned to IPFS by content hash, so the on-chain\nreference is to the content (CID), not to anyone's server: anyone can\nre-pin to a different IPFS service, any IPFS gateway can resolve. Both\n`WORKER_BASE` and `IPFS_GATEWAY` are top-of-file constants in\n`dapp\u002Ftacit.js`; setting `WORKER_BASE = ''` disables every worker\nendpoint and the protocol still works for transfers, validation, and\nrecovery.\n\nOrdinals takes the opposite tradeoff — content lives directly on-chain\nin Taproot witnesses, no off-chain dependency at all but each inscription\ncarries the full file as Bitcoin fees (impractical beyond small images).\nRunes encodes token state in OP_RETURN runestones but carries no image \u002F\nmetadata convention in the protocol, so wallets and explorers usually\nfetch \"what does this rune look like\" from a marketplace's centralized\nAPI.\n\n**Can I recover my balance from just my privkey?** Yes, for every\non-chain envelope. The dApp scans chain data, walks ancestry back to\neach CETCH\u002FMINT, decrypts amounts via ECDH, and reconstructs the wallet —\nno share-link or sync server required. The one exception is atomic-intent\nrecipient UTXOs (SPEC §5.7.6 — listed with a uniform-random blinding so\nbrowse-and-take can publish a cleartext amount without leaking via\nbaby-step-giant-step), which fall back to local cache or the worker's\n24-hour fulfilment record.\n\n**How do I trust the announced supply when amounts are hidden?** The\ndApp ships supply attestation **on by default**. At etch time it pins\nthe `(supply, blinding)` opening into IPFS as part of the asset\nmetadata; anyone fetches the blob and verifies\n`pedersenCommit(supply, blinding) == on-chain commitment` from chain\nalone — no worker trust, no issuer trust beyond the one-time honest\npublish. Issuers who want a centralized-stablecoin-style \"trust me about\nthe supply\" model can opt out explicitly. SPEC §7.3 spells out the\nattestation flow; for non-mintable assets attested at etch, supply is\nprovably and permanently public.\n\n**What's the cost per transfer?** ~10 KB witness per CXFER (m=2\naggregation), about 2,500–3,000 vBytes after the SegWit discount. At\n10 sat\u002FvB on mainnet that's ~25–30k sats per transfer; at low-fee\nperiods correspondingly less. Bulletproofs+ (`T_CXFER_BPP`) shaves ~14%\noff the rangeproof. The witness carries the cryptographic proof that\namounts balance without revealing them — one aggregated bulletproof\nfor all outputs plus a kernel signature.\n\n**Is the indexer trust-bearing? What if I don't trust the worker?**\nThe dApp's *indexer code* is the trust target — re-host it, pin it by\nIPFS CID; two browsers running the same code reach the same verdict from\nchain alone. The worker is a convenience cache, not part of the\ntrust-bearing protocol: it cannot make an invalid envelope appear valid\n(clients re-verify rangeproofs, kernel sigs, mint sigs, and Pedersen\nopenings client-side). You can run your own in ~5 minutes on a free\nCloudflare account, or set `WORKER_BASE = ''` in `dapp\u002Ftacit.js` to\ndisable it entirely. SPEC §8 covers this.\n\n**Is the code open?** Yes — MIT licensed, this repo is the canonical\nsource. The protocol spec ([SPEC.md](.\u002FSPEC.md)) is the authoritative\nreference for indexer implementations; a re-implementation in any\nlanguage reaches the same verdict from chain alone.\n\n---\n\n## User stories\n\n**Alice mints a token.** Alice opens the dApp and signs in with Xverse —\ntacit derives a per-wallet identity in this browser and Alice clicks\n\"Top up tacit\" to send a few thousand sats over from Xverse for tx fees.\nShe fills in ticker = `ALICE`, supply = 1000, decimals = 2, optionally\nuploads an image or marks the asset Mintable, and clicks Etch. Two\ntransactions go on chain (commit + reveal). On chain, anyone can see a\nnew ALICE token exists and that its supply is some integer in `[0, 2⁶⁴)` —\nbut only Alice knows the supply is 1000. (On signet, Alice can skip the\nwallet connect: the dApp generates a key and the faucet button funds it.)\n\n**Alice sends 50 ALICE to Bob.** Alice pastes Bob's pubkey (Bob copied it\nfrom his Wallet tab). The dApp builds a CXFER tx: input = Alice's supply\nUTXO (commits to 1000), outputs = recipient (commits to 50, blinded with an\nECDH key only Alice and Bob can derive) + change (commits to 950, blinded\nwith Alice's own key). One aggregated bulletproof covers both output range\nproofs (~754 B total). A kernel signature proves inputs − outputs balance\nto zero. On chain: ALICE moved, neither amount visible.\n\n**Bob recovers on a fresh device.** Bob enters his privkey on a clean\ninstall. The dApp scans signet\u002Fmainnet for outputs paying his pubkey, walks\neach one back through CXFER history to its CETCH ancestor, verifies every\nrangeproof and kernel sig locally (no trust in any server), derives the ECDH\nkey with the sender, and decrypts the amount. **No share-link or sync server\nrequired.** Privkey alone reconstructs the wallet.\n\n**Carol browses what tokens exist.** Carol clicks Discover. The dApp hits\nthe Worker's `\u002Fassets` and `\u002Fpetch-assets` endpoints, which have been\nchain-scanning every 5 minutes for CETCH, T_CXFER, T_MINT, T_BURN,\nT_AXFER, T_PETCH, and T_PMINT envelopes. **Every Discover card is\nclient-validated before render**: the dApp walks back to the on-chain\nCETCH envelope, verifies the rangeproof, decodes the canonical ticker \u002F\ndecimals \u002F commitment, and checks attestation against the `tacit_attest`\nfield in the IPFS metadata blob (no worker trust — content-addressed). If\nthe worker tries to spoof a ticker, Carol sees a ⚠ MISMATCH badge. She\ncan't see anyone's balances, but for any attested asset she sees the\nsupply (✓ verified opening, IPFS) along with attested mint history and\npublic burn totals.\n\n**Alice optionally pings Bob with a share-link.** After broadcasting, the\ndApp emits a URL ending in `#recv=…` containing the opening (amount +\nblinding). Alice DMs it to Bob; clicking it imports the opening directly,\nskipping a chain scan. **This is purely UX — Bob's recovery story above\nworks regardless.** Share-links notify, they don't authorize.\n\n**Bob lists 5,000 ALICE for sale on the public market.** From Holdings, Bob\nclicks \"List a UTXO for sale\" → 250,000 sats, 7-day expiry. The listing\npublishes the UTXO's `(amount, blinding)` opening + price + signed offer to\nthe worker. Anyone can browse it on the Market tab and click Verify, which\nre-runs Pedersen + sig + liveness checks client-side. A taker pays Bob's\naddress out-of-band and Bob delivers via CXFER — OTC, trust required.\n\n**Carol lists 1,000 ALICE with hidden total balance.** Carol holds a\ntreasury of ALICE across many UTXOs and doesn't want to dox the total.\n\"List (hidden balance)\" → bulletproof showing `balance ≥ 1000` is published\nalongside the offer. The Market tab shows it with a green ≥ badge. Same\nOTC settlement, but Carol's exact balance stays confidential.\n\n**Dave atomically swaps 10,000 GOLDC for 500K sats with Erin.** They\nexchange pubkeys via Telegram. Dave clicks \"Atomic (targeted)\" on Holdings,\ngenerates a partial reveal targeted at Erin's pubkey, copies the JSON.\nErin clicks \"Take atomic offer,\" pastes the JSON, and the dApp appends her\nBTC funding signed `SIGHASH_ALL`. **One Bitcoin tx settles both sides.**\nNeither could grief the other — the maker's `SIGHASH_SINGLE_ACP` sigs bind\nthe BTC payment, the taker's `SIGHASH_ALL` sig binds the whole tx.\n\n**Frank publishes an open atomic intent for anyone to claim.** \"Atomic\nintent (open)\" on Holdings → 100 USDA for 50K sats, 1-day expiry. The\nintent appears on the Market tab with a purple ⚡ badge. Helen clicks\nClaim, locks for 5 minutes. Frank sees the claim on the Market tab and\nclicks \"Fulfil claim\" — the dApp generates a partial reveal targeted at\nHelen's pubkey and posts it. Helen clicks Take, broadcasts. **Discoverable\n+ trustless atomic OTC**, no out-of-band coordination.\n\n**Jack swaps TAC for USDA on the AMM.** Jack opens the Pool tab, picks the\nTAC\u002FUSDA pool, enters 500 TAC. The dApp builds a `T_SWAP_VAR` envelope —\ncleartext amounts against the constant-product curve, no Groth16 needed\nfor the per-trade path. One commit-reveal pair, ~10 seconds on signet.\nJack's USDA UTXO appears in Holdings. Pool reserves update for every\nindexer watching the chain.\n\n**Kim publishes a walk-away bid for GOLDC.** Kim wants to buy 5,000 GOLDC\nat 100 sats\u002Funit but doesn't want to keep the dApp open. She clicks\n\"Preauth bid\" on Market, sets her price and range, and signs once. The\nbid goes live on the Market tab. Any seller can fill — partially or\nfully — by spinning up an atomic intent targeted at Kim's pubkey. Kim's\nresidual returns to her automatically if the fill is partial\n(`T_PREAUTH_BID_VAR`). Kim can be offline the entire time.\n\n**Greta airdrops 50,000 GRETA to ETH holders of an old token.** Greta has\nan Etherscan CSV (320 addresses + balances). On the Drops tab she selects\nGRETA, uploads the CSV (optional blacklist), clicks Build merged snapshot\n— the dApp normalizes amounts, sorts by address, and computes the merkle\nroot. She pins the snapshot JSON to IPFS and shares the `(merkle_root, CID)`\npair via her usual channels. As claims arrive in the worker queue, she\npulls them in batches and the dApp broadcasts batched CXFERs (up to 7\nconfidential recipients per tx, all signed in one go from her treasury key).\n\n**Ivy claims her share.** Ivy opens the Claim tab, pastes the merkle root\n+ IPFS CID, clicks Load snapshot. The dApp fetches the JSON, recomputes\nthe merkle root locally, and shows Ivy her row. She connects MetaMask and\nsigns a canonical claim binding her tacit pubkey to the drop (off-chain\nsignature — no Eth tx, no gas). The resulting `(leaf_index, tacit_pubkey,\neth_sig)` tuple goes to the worker queue (or directly to Greta). When\nGreta fulfils, the confidential GRETA UTXO appears in Ivy's Holdings via\nthe same ECDH recovery path as any other CXFER.\n\n---\n\n## Repository layout\n\n```\ntacit\u002F\n├── dapp\u002F                  # THE dApp — pin this directory to IPFS\n│   ├── index.html         # markup, meta-CSP, script tags\n│   ├── tacit.js           # core: Pedersen, bulletproofs, kernel sigs,\n│   │                      #  BIP-340\u002F341, envelope encode\u002Fdecode,\n│   │                      #  recursive validator, wallet, UI, marketplace\n│   ├── bulletproofs.js    # bulletproof rangeproof prover\u002Fverifier\n│   ├── bulletproofs-plus.js # Bulletproofs+ (~14% smaller witnesses)\n│   ├── amm-envelope.js    # AMM envelope builders (LP_ADD, LP_REMOVE, SWAP_BATCH…)\n│   ├── amm-bjj.js         # BabyJubJub curve ops for in-circuit AMM math\n│   ├── amm-kernel.js      # AMM kernel signature computation\n│   ├── amm-sigma.js       # sigma cross-curve proofs (secp ↔ BJJ)\n│   ├── amm-asset.js       # LP-share asset derivation\n│   ├── amm-min-liq.js     # minimum liquidity tracking\n│   ├── amm-receipt.js     # AMM receipt recovery\n│   ├── amm-farm-ui.js     # yield farm UI\n│   ├── amm-farm-actions.js # farm action builders (FARM_INIT, LP_BOND, LP_HARVEST)\n│   ├── prf-wallet.js      # WebAuthn PRF key derivation\n│   ├── preboot.js         # pre-initialization (localStorage, session setup)\n│   ├── sw.js              # service worker\n│   ├── tacit.svg          # logo \u002F favicon\n│   ├── _headers           # CF Pages HTTP headers (frame-ancestors, XCTO, Referrer)\n│   └── vendor\u002F\n│       └── tacit-deps.min.js   # bundled @noble\u002Fsecp256k1 + @noble\u002Fhashes\n│                                #  + @scure\u002Fbase + sats-connect\n├── contracts\u002F             # Solidity bridge contracts (tETH: trustless ETH ↔ Bitcoin)\n│   ├── src\u002F               # TacitBridgeMixer.sol, Groth16Verifier.sol, SP1 verifier\n│   ├── test\u002F              # Forge tests\n│   └── script\u002F            # deployment scripts\n├── worker\u002F                # optional Cloudflare Worker (faucet, asset registry, IPFS pin)\n│   ├── src\u002Findex.js\n│   ├── wrangler.toml\n│   └── README.md\n├── fulfiller\u002F             # auto-fulfilment service for atomic intents\n│   └── auto-fulfil.mjs\n├── verify-service\u002F        # remote Groth16 proof verification server\n│   ├── server.mjs\n│   └── Dockerfile\n├── tests\u002F                 # offline test harness (100+ test files)\n├── spec\u002F                  # protocol specs + amendments\n│   ├── CIRCUITS.md        # how the ZK stack composes\n│   ├── GLOSSARY.md        # cross-surface term definitions\n│   ├── amendments\u002F        # 25 amendments (shipped + drafted)\n│   ├── amm\u002F               # AMM wire formats, ceremony, failure modes\n│   └── design\u002F            # design docs (channel UX, consensus, stealth)\n├── build\u002F                 # esbuild bundler for vendor deps (dev-time only)\n├── whitepaper\u002F            # technical whitepaper (WHITEPAPER.md + .tex + .pdf)\n├── discord\u002F               # protocol monitoring bot\n├── airdrop\u002F               # CSV-based airdrop tooling\n├── ops\u002F                   # operational runbooks\n├── scripts\u002F               # utility scripts (relay, SP1 proofs)\n├── SPEC.md                # canonical protocol specification\n├── AMM.md                 # confidential AMM architecture\n├── MIXER.md               # shielded-pool architecture\n├── BRIDGE.md              # tETH trustless ETH-Bitcoin bridge\n├── AMENDMENTS.md          # amendment index + status\n├── README.md              # you are here\n└── LICENSE\n```\n\n`dapp\u002F` loads `index.html` (markup + meta-CSP), `tacit.js` (core protocol +\nwallet + UI, ESM module), and `vendor\u002Ftacit-deps.min.js` (noble + scure +\nsats-connect, bundled — imported from `tacit.js`). The AMM, farm, and\nBulletproofs+ modules are separate ESM files imported by `tacit.js`. The\nmeta-CSP locks `script-src 'self' 'wasm-unsafe-eval'` (no `'unsafe-inline'`,\nno `'unsafe-eval'`, no third-party origins). `'wasm-unsafe-eval'` permits\n`WebAssembly.instantiate()` (snarkjs Groth16 prover\u002Fverifier) without\nreopening the broader eval() surface. Pinning `dapp\u002F` yields one CID\ncovering every byte of trust-bearing code. `connect-src` reaches only\n`mempool.space`, `blockstream.info` (divergence watchdog), the worker, and\nthe IPFS gateway. `img-src` is `'self' data: https:\u002F\u002Fcontent.wrappr.wtf` —\ndirect `https:\u002F\u002F` images in CETCH envelopes are rejected to avoid\nIP-correlation beacons.\n\n`contracts\u002F` holds the tETH bridge — Solidity contracts\n(`TacitBridgeMixer.sol`, `Groth16Verifier.sol`, `SP1PoolRootVerifier.sol`)\nfor trustless ETH ↔ Bitcoin wrapping. See [`BRIDGE.md`](.\u002FBRIDGE.md).\n\n`build\u002F` is dev-time only. Run `cd build && npm install && npm run build`\nwhen you bump deps or want fresh SRI hashes. Editing `dapp\u002Findex.html`\nor `dapp\u002Ftacit.js` directly does not require a build — both are served\nas-is, and the runtime KAT catches any drift between the bundle and what\ntacit expects.\n\nThe `worker\u002F` directory holds an optional Cloudflare Worker (image pinning\nto IPFS, signet faucet, asset directory, pool\u002Ffarm state).\n**The Worker holds no trust-bearing logic.** Setting `WORKER_BASE = ''`\nat the top of `dapp\u002Ftacit.js` disables it entirely; the protocol still\nworks.\n\n`fulfiller\u002F` provides an auto-fulfilment service for atomic intents —\npolls the worker for pending claims and settles them without manual\nintervention. `verify-service\u002F` exposes a remote Groth16 verifier\n(Docker-ready).\n\n---\n\n## How the protocol works (one screen)\n\n```\nETCH (one-time, mints a new asset)\n─────────────────────────────────\n commit-tx → P2TR output committed to envelope\n reveal-tx → spends P2TR via script-path, exposes envelope in witness:\n\n     CETCH || ticker || decimals || C(33B) || amount_ct(8B)\n            || rangeproof(~688B m=1 bulletproof, n=64)\n            || mint_authority(32B, all-zero = non-mintable)\n            || image_uri(≤256B)\n\n   C = supply·H + r·G        (Pedersen commitment to supply)\n   amount_ct = supply ⊕ HMAC(etcher_priv, \"tacit-etch-amount-v1\" ‖ anchor)\n   r          = HMAC(etcher_priv, \"tacit-etch-v1\" ‖ anchor)\n\n   anchor = first input outpoint of commit-tx (so the etcher can recover\n            the supply opening from chain + privkey alone)\n\n   asset_id = sha256(reveal_txid ‖ vout=0)\n\n\nTRANSFER\n────────\n commit-tx → P2TR output committed to envelope\n reveal-tx → spends commit-tx + asset UTXO(s); envelope:\n\n     CXFER || asset_id || kernel_sig(64B) || N\n           || (C_i, amount_ct_i)*N\n           || aggregated_rangeproof    (one bulletproof for all N outputs)\n\n   N ∈ {1, 2, 4, 8} (power of 2 for aggregation)\n   r_recipient = HMAC(ECDH(sender_priv, recipient_pub), \"tacit-blind-v1\" ‖ anchor ‖ vout)\n   r_change    = HMAC(sender_priv,                       \"tacit-change-v1\" ‖ anchor ‖ vout)\n   amount_ct   = amount ⊕ keystream  (ECDH-derived for recipient, self-derived for change)\n\n   excess = (Σr_out − Σr_in) mod N\n   E'     = ΣC_out − ΣC_in\n   kernel_sig verifies under E'.xonly() — proves Σa_out = Σa_in without revealing amounts\n\n\nMINT (mintable assets only)\n───────────────────────────\n Same commit-reveal pattern, envelope:\n\n     T_MINT || asset_id || etch_txid || C(33B) || amount_ct(8B)\n            || rangeproof(~688B) || issuer_sig(64B)\n\n   issuer_sig = BIP-340 over sha256(\"tacit-mint-v1\" ‖ asset_id ‖ commit_anchor ‖ C ‖ amount_ct)\n                under mint_authority's privkey\n   (commit_anchor binding prevents envelope replay into a different commit\u002Freveal pair)\n\n\nBURN\n────\n Same commit-reveal pattern, envelope:\n\n     T_BURN || asset_id || burned_amount(8B, public) || kernel_sig(64B) || N\n           || (C_i, amount_ct_i)*N        # change outputs (N=0 = full burn)\n           || aggregated_rangeproof       # omitted if N=0\n\n   E' = ΣC_out + burned_amount·H − ΣC_in\n   kernel_sig verifies under E'.xonly()\n\n\nT_AXFER (atomic OTC settlement — CXFER variant for marketplace use)\n───────────────────────────────────────────────────────────────────\n Same shape as CXFER except the maker explicitly declares how many of\n vin[1..] are tacit asset inputs; the rest are aux BTC inputs the taker\n funds in the same Bitcoin tx:\n\n     T_AXFER || asset_id || asset_input_count(1B) || kernel_sig(64B) || N\n            || (C_i, amount_ct_i)*N\n            || aggregated_rangeproof\n\n   maker signs vin[0] (envelope) + vin[1..1+asset_input_count] with\n   SIGHASH_SINGLE | ANYONECANPAY → taker can append BTC inputs\u002Foutputs\n   without invalidating maker's sigs.\n   taker's BTC funding inputs are SIGHASH_ALL → pin the whole tx.\n   Both sides settle in one Bitcoin tx; neither can grief the other.\n\n\nT_PETCH (permissionless-mint deployment record — fair-launch issuance)\n──────────────────────────────────────────────────────────────────────\n Same commit-reveal pattern. Produces NO supply UTXO — deployer gets zero\n tokens; the only way to hold supply is to broadcast T_PMINT later.\n\n     T_PETCH || ticker || decimals\n             || cap_amount(8B) || mint_limit(8B)\n             || mint_start_height(4B) || mint_end_height(4B)\n             || image_uri(≤256B)\n\n   asset_id = sha256(reveal_txid ‖ vout=0)            (same as CETCH)\n   cap_amount % mint_limit == 0  (cap reachable; rejected if not)\n   mint_start_height ≥ etch_height + 1  (deployer can't mint in own block)\n\n\nT_PMINT (permissionless mint event — anyone may broadcast)\n──────────────────────────────────────────────────────────\n Mints exactly mint_limit tokens against a T_PETCH ancestor.\n\n     T_PMINT || asset_id || etch_txid\n             || C(33B) || amount(8B, public) || blinding(32B, public)\n\n   amount == petch.mint_limit       (validator rejects otherwise)\n   confirmed_height ∈ [start, end]  (height-window check)\n   credited only at depth ≥ 3       (Bitcoin reorg safety; SPEC §5.9)\n   cap enforced from canonically-ordered chain history:\n     prior_count × mint_limit + amount ≤ cap_amount\n\n   No signature. (amount, blinding) are public — any wallet with the\n   privkey for vout[0]'s output script recovers the UTXO from chain\n   alone, no derivation needed. The first CXFER from such a UTXO\n   re-blinds it back into confidential transfer mode.\n\n\nT_DEPOSIT \u002F T_WITHDRAW (shielded mixer pool)\n────────────────────────────────────────────\n Tornado-style anonymity pool over any tacit asset at a fixed denomination.\n\n POOL_INIT (T_DEPOSIT with denomination = 0 sentinel):\n     T_DEPOSIT || asset_id || denomination=0(8B)\n               || pool_denom(8B) || vk_cid_len(1) || vk_cid\n               || ceremony_cid_len(1) || ceremony_cid || init_sig(64B)\n\n DEPOSIT (T_DEPOSIT with denomination ≠ 0):\n     T_DEPOSIT || asset_id || denomination(8B)\n               || leaf_commitment(32B) || kernel_sig(64B)\n\n   leaf_commitment = Poseidon(secret, nullifier_preimage, denomination)\n   The (secret, nullifier_preimage) pair is held by the depositor —\n   without it, the deposit cannot be withdrawn. Worker appends each\n   deposit's leaf to a per-pool merkle tree in canonical order.\n\n WITHDRAW (T_WITHDRAW):\n     T_WITHDRAW || asset_id || denomination(8B)\n                || merkle_root(32B) || nullifier_hash(32B)\n                || recipient_commitment(33B)\n                || r_leaf(32B)        (public Pedersen blinding scalar)\n                || bind_hash(32B)     SHA256-tagged commit to the tuple\n                || proof_len(2B) || proof(Groth16, ~256B)\n\n   Public inputs to the Groth16 verifier:\n     [merkle_root, nullifier_hash, denomination, r_leaf, bind_hash]\n   Witness (private): secret, nullifier_preimage, leaf merkle path.\n\n   r_leaf is published in cleartext — same posture as T_PMINT's\n   (amount, blinding). Privacy comes from Groth16 zero-knowledge over\n   the leaf-membership statement, not from hiding r_leaf. Recovery is\n   then trivial for both self-withdraw and tornado-flow-to-other\n   (recipient reads r_leaf from chain).\n\n   Validator extras beyond the proof:\n     - bind_hash recompute matches (binds proof to the specific tuple)\n     - external secp256k1 check: recipient_commitment == denom·H + r_leaf·G\n       (closes the inflate-amount attack — circuit forces r_leaf =\n        Poseidon(secret, ν), validator forces the Pedersen equation)\n     - merkle_root in the last 32 canonical roots of this pool\n     - nullifier_hash NOT in this pool's spent set\n\n Anonymity set = currently-unspent leaves at withdraw time. Wait for\n the pool to fill before withdrawing.\n\n v1 status: wire format, worker indexing, browser-side Groth16 prover +\n verifier, Phase 2 ceremony coordinator, and the public ceremony run\n (2,227 contributions + Bitcoin-block beacon) all shipped. Canonical\n bundle CID is hardcoded in the dapp. SPEC §5.10–§5.11 + §3.6–§3.8.\n\n\nVALIDATION (recursive, browser-side)\n────────────────────────────────────\n For each wallet UTXO:\n   1. Decode envelope at parent_tx.vin[0].witness[1]\n   2. If CETCH:    verify rangeproof; record metadata\n   3. If T_MINT:   recursively validate the CETCH ancestor;\n                   verify issuer_sig (with commit_anchor binding); verify rangeproof\n   4. If CXFER\u002FT_AXFER\u002FBURN:\n       a. recursively validate every asset input outpoint\n          (T_AXFER: only vin[1..1+asset_input_count]; aux BTC inputs are skipped)\n       b. verify aggregated rangeproof for outputs (skip if BURN with N=0)\n       c. verify asset_id consistency across all asset-input parents\n       d. verify kernel_sig under (ΣC_out + burned·H − ΣC_in).xonly()\n   5. If T_PETCH:  not a UTXO — return false. Metadata recorded by indexer.\n   6. If T_PMINT:  resolve T_PETCH parent metadata; verify amount == mint_limit,\n                   confirmed_height ∈ window, depth ≥ 3, cap not exceeded,\n                   pedersenCommit(amount, blinding) == commitment\n   7. If T_DEPOSIT:  not a UTXO — vout[0] is BTC change, not tacit. Worker\n                     records the leaf for pool merkle-tree state. Recovery\n                     walks do not recurse through T_DEPOSIT envelopes.\n   8. If T_WITHDRAW: verify merkle_root is in the pool's last 32 canonical\n                     roots, nullifier_hash is unseen, bind_hash recomputes,\n                     `recipient_commitment == denom·H + r_leaf·G` (external\n                     secp256k1 Pedersen check), and the Groth16 proof verifies\n                     under the pool's vk over [merkle_root, nullifier_hash,\n                     denomination, r_leaf, bind_hash].\n   9. Resolve own (amount, blinding) via local cache OR trial-decrypt amount_ct\n      (T_PMINT and T_WITHDRAW: amount + blinding are in the envelope\n       cleartext — no decrypt; just verify the Pedersen equation)\n\n Memoized; O(N) over chain depth N. Optimistically batches all rangeproofs\n across the walk into a single multi-scalar multiplication.\n\n\nRECOVERY\n────────\n Privkey + chain → full wallet state. No share-link required, no localStorage\n backup required. The wallet trial-decrypts every commitment it owns:\n   - As recipient (ECDH against sender pubkey at vin[1].witness[1])\n   - As own change      (self-derived keystream)\n   - As own etched supply (self-derived from commit input outpoint anchor)\n   - As own minted supply (same anchor pattern, different domain string)\n   - As T_PMINT-minted supply (own or other) — (amount, blinding) are in the\n     envelope cleartext; no derivation. Match P2WPKH(hash160(my_pub)) to\n     vout[0] to claim ownership; verify pedersenCommit(amount, blinding) ==\n     commitment to reject tampered envelopes.\n   - As mixer-pool withdrawal (T_WITHDRAW) — `denomination` and `r_leaf`\n     are both in the envelope cleartext (same posture as T_PMINT). Verify\n     `pedersenCommit(denomination, r_leaf) == on_chain_commitment`. Works\n     identically whether withdrawer === recipient or not — the public\n     `r_leaf` makes share-links unnecessary for tornado-flow-to-other.\n\n (One exception: atomic-intent recipient UTXOs use a uniform-random blinding\n  delivered ECDH-encrypted to the claimant at fulfilment time — recovery\n  falls back to local cache or re-fetching the encrypted fulfilment from\n  the worker within its 24h TTL. SPEC §5.7.6.)\n```\n\nFor more detail, open the dApp and read the **About** tab — the on-page\ndocs spell out the wire format, attack vectors, blinding delivery, and\ntrust model.\n\n---\n\n## Running the dApp\n\nThe dApp is a single HTML file plus its vendored bundle.\n\n### Locally (fastest path)\n\n```sh\n# any static file server works\ncd tacit\u002Fdapp\npython3 -m http.server 8000\n# open http:\u002F\u002Flocalhost:8000\u002F  (serves dapp\u002Findex.html)\n```\n\nCORS is allowlisted for `http:\u002F\u002Flocalhost:8000`, `:3000`, `:127.0.0.1:8000`,\nand `null` (`file:\u002F\u002F`) in the deployed Worker, so local dev hits the live\nendpoints out of the box.\n\n### Hosted\n\nPin the `dapp\u002F` directory to IPFS, or drop it on Cloudflare Pages, GitHub\nPages, Vercel, or any static host. There are no env vars or build flags —\nthe Worker URL and IPFS gateway are set at the top of the script:\n\n```js\nconst WORKER_BASE  = 'https:\u002F\u002Ftacit-pin.rosscampbell9.workers.dev';\nconst IPFS_GATEWAY = 'https:\u002F\u002Fcontent.wrappr.wtf\u002Fipfs\u002F';\n```\n\nOnce you know your hosted origin, narrow `ALLOWED_ORIGINS` in\n`worker\u002Fwrangler.toml` to it, then `wrangler deploy`.\n\n### Refreshing the vendor bundle\n\nRun only when bumping crypto dep versions or wanting fresh SRI hashes.\n\n```sh\ncd tacit\u002Fbuild\nnpm install\nnpm run build\n# prints SHA-384 of dapp\u002Fvendor\u002Ftacit-deps.min.js, dapp\u002Ftacit.js, and dapp\u002Findex.html\n```\n\nSee `build\u002FREADME.md` for details.\n\n---\n\n## Using the dApp\n\n1. **Sign in \u002F set up a wallet.** Three paths, pick whichever fits:\n   - **Connect an external wallet** (Xverse \u002F UniSat \u002F Leather) — tacit derives\n     a per-wallet identity stored in this browser and binds it to that wallet's\n     address. Reconnecting from this browser restores it.\n   - **Import a privkey** — paste the 64-hex from any source.\n   - **Auto-generated** — the dApp creates a key on first load (handy for\n     signet demos with the faucet).\n\n   Whichever path you pick, the in-page privkey is **encrypted at rest in\n   localStorage** — AES-GCM with a passphrase-derived key (PBKDF2-SHA256,\n   600k iterations per OWASP 2023). On every load the dApp prompts for the\n   passphrase to unlock. Forgetting the passphrase = losing the wallet, so\n   **export the privkey separately** via Wallet → Export key — that's the\n   real recovery path. Mainnet operations gate every value-creating action\n   behind a \"have you exported the key?\" acknowledgement.\n2. **Get sats.** On signet, click ⚡ Demo drip — single round trip, no\n   captcha. (If the faucet is empty, the Manual faucet button opens public\n   signet faucets.) On mainnet, click **Top up tacit** in the connect panel\n   for one-click funding from your external wallet, or send sats to the\n   tacit address shown on the Wallet tab from any Bitcoin wallet.\n3. **Etch.** Pick a ticker, supply, decimals (0–8). Optionally upload an\n   image and add description \u002F external URL — the dApp pins a JSON\n   metadata blob to IPFS and stores its CID in the envelope. Mark\n   **Mintable** if the etcher pubkey should be allowed to issue more supply\n   later (permanent decision; loss of the in-page privkey freezes supply\n   forever). Click Preview, then Etch & broadcast. Two transactions go out\n   (commit + reveal); the second confirms in ~10 minutes on signet.\n4. **Transfer.** Pick an asset, paste recipient's pubkey (they read it off\n   their own Wallet tab), enter an amount. Click Preview, then Transfer &\n   broadcast. The recipient auto-discovers the balance on next scan via\n   the on-chain encrypted-amount field.\n5. **Mint.** Mintable asset only — issues additional supply, signed by the\n   mint_authority key (the etcher's in-page privkey). Re-uses the\n   commit-reveal flow.\n6. **Fair-launch (T_PETCH \u002F T_PMINT).** Deploy an asset whose supply is\n   issued permissionlessly in fixed tranches, with a publicly auditable cap.\n   On the **Etch · public mint** panel, set ticker, decimals, `cap_amount`,\n   `mint_limit` (`mint_limit` must divide `cap_amount` so the cap is reachable), and an optional\n   height window. Click Deploy public-mint asset — the deploy tx creates\n   **zero tokens**; you (or anyone) mint a tranche later by clicking **Mint**\n   on the asset's card in **Discover → Fair launch**. Cumulative supply,\n   mints remaining, and per-mint status (pending \u002F credited \u002F revoked) are\n   visible to everyone. Mints credit at Bitcoin confirmation depth ≥ 3.\n7. **Burn.** Any holder. Destroys part or all of their balance with a\n   public `burned_amount` so observers can audit supply reduction.\n8. **Holdings.** Lists your assets with images, descriptions, balances.\n   ↻ Rescan UTXOs forces re-validation. **Per-asset card actions** include:\n   - **Send privately \u002F Burn \u002F Mint more** (core CXFER \u002F BURN \u002F MINT flows).\n   - **Reveal supply \u002F Reveal mints** — etcher \u002F mint-authority publish their\n     openings to the worker so anyone in Discover sees ✓ verified supply.\n   - **Publish balance** — pin the per-UTXO `(amount, blinding)` openings to\n     the worker; signed by your wallet so a CXFER counterparty who learned\n     the opening can't dox you. Permanent — once on the worker, it's there.\n   - **Prove ≥ threshold** — generate a bulletproof showing balance ≥ X\n     without revealing the exact amount (range disclosure, SPEC §5.6).\n   - **List a UTXO for sale** — public OTC listing with the per-UTXO\n     opening + price + expiry + maker payment address.\n   - **List (hidden balance)** — same shape but the proof is \"`balance ≥ K`\"\n     instead of full opening; other UTXOs stay confidential.\n   - **Atomic (targeted)** — partial-reveal-as-JSON for a specific known\n     recipient pubkey. Settles atomically when the taker pastes + finalizes.\n   - **Atomic intent (open)** — publish a generic atomic offer on the\n     Market tab for any taker to claim and atomically settle.\n9. **Discover.** Lists every asset on the active network in two sections:\n   the main confidential-supply list (CETCH-rooted, supply hidden behind a\n   Pedersen commitment plus issuer attestation), and a separate **fair\n   launch · public-mint assets** panel (T_PETCH-rooted, permissionless-mint\n   with public cumulative supply and per-mint status). Filter by ticker \u002F\n   asset_id; pills (`mintable` \u002F `attested` \u002F `recent` \u002F `has mints` \u002F\n   `has burns` \u002F `has transfers`) narrow the main list. Each card surfaces\n   verified supply, mint history, burn totals, and — for fair-launch\n   assets — cap progress and a Mint button.\n10. **Market.** Aggregate marketplace across all assets. Listing kinds:\n   - 🟢 **opening** — exact-amount listing (OTC settlement)\n   - 🟢 **≥ range** — range-disclosed listing (OTC settlement, exact balance hidden)\n   - 🟣 **⚡ atomic intent** — browse-and-take with single-Bitcoin-tx settlement\n   - 🔵 **bid intent** — buyer publishes \"I'd buy N at P\"; sellers fill by\n     spinning up an atomic intent targeted at the bidder\n   - 🟠 **preauth bid** — buyer-offline walk-away bid (`T_PREAUTH_BID_VAR`);\n     any seller can fill partially or fully while the buyer is offline\n   Filters: ticker \u002F asset_id, kind, price min\u002Fmax, sort by recency or\n   price. Take + Verify buttons run full client-side validation (sigs,\n   ownership, Pedersen for openings \u002F bulletproof for ranges, UTXO\n   liveness) before any commitment. Atomic intent tiles surface the\n   relevant button based on your role: Claim if untaken, Fulfil if it's\n   yours and a claim is pending, Take when fulfilment is ready.\n11. **Drops** *(issuer side)*. Batched 1:N confidential CXFER airdrops.\n    Upload one or more snapshot CSVs (`eth_address,amount` — Etherscan\n    holder exports work as-is), optionally blacklist addresses, Build\n    merged snapshot to compute the merkle commitment, Pin to IPFS, Save\n    the drop record. Then publish `(merkle_root, IPFS CID)` to recipients\n    however you like (Twitter, Discord, blog). As claims arrive in the\n    worker queue, Pull queued → Verify batch → Broadcast: up to 7\n    recipients per CXFER, each verified for merkle inclusion + ETH-sig\n    recovery to the listed address before broadcast. Drop records\n    (including the local fulfilled-leaves ledger) live in localStorage;\n    use Export JSON to back up. A Cross-check vs chain button walks the\n    local ledger and verifies each fulfilled leaf actually confirmed\n    on-chain.\n12. **Mixer** *(production — Phase 2 finalized 2026-05-11)*.\n    Tornado-style shielded pool over any tacit asset at a fixed\n    denomination. Deposit a UTXO of the pool's exact denomination; the\n    dApp generates a `(secret, nullifier_preimage)` pair and emits a\n    Poseidon leaf commitment — back up the deposit record before\n    broadcasting, without it the deposit cannot be withdrawn. Wait for\n    the pool's anonymity set to grow, then withdraw to a fresh pubkey:\n    the dApp generates a Groth16 proof of unspent-leaf membership and\n    re-verifies it client-side, the worker rejects duplicate nullifiers,\n    and the resulting UTXO is unlinkable to any specific deposit. Pool\n    initialization is permissionless — declare a new `(asset_id,\n    denomination)` pair with a verifying-key CID; the canonical\n    Phase 2 ceremony bundle is hardcoded in the dapp at IPFS\n    `bafybeidq2ahzte4sfiqjsmhqta62ufenpppzpch5ppry55tzxzlvltxy2u`\n    (2,227 community contributions + Bitcoin-block beacon, finalized\n    chain). SPEC §5.10–§5.11; full status + caveats in\n    [MIXER.md](.\u002FMIXER.md).\n13. **Pool** *(AMM — ceremony pending)*. Uniform-clearing-price\n    block-batched AMM between any two tacit assets. The Pool tab surfaces\n    pool initialization, LP add\u002Fremove, and per-trade variable-amount\n    swaps (`T_SWAP_VAR`). Per-trader amounts are confidential\n    (BabyJubJub Pedersen + Groth16 inside `T_SWAP_BATCH`); pool reserves\n    are public numbers the indexer tracks. The AMM Phase 2 ceremony is\n    per-pool and not yet finalized — the dApp uses placeholder proofs\n    until the ceremony CID is populated. Wire format and architecture:\n    [`AMM.md`](.\u002FAMM.md); amendments in [`spec\u002Famendments\u002F`](.\u002Fspec\u002Famendments\u002F).\n14. **Farms** *(yield farming)*. LP-staking yield farms over AMM pool\n    shares. `T_FARM_INIT` deploys a farm with a reward schedule;\n    `T_LP_BOND` \u002F `T_LP_HARVEST` stake LP shares and claim rewards.\n    SPEC-AMM-FARM-AMENDMENT.\n15. **Claim** *(recipient side)*. Paste the drop's merkle root + IPFS CID\n    (from the issuer) → Load snapshot. The dApp fetches the JSON, refuses\n    any blob whose rows don't match the root, and shows your row. Connect\n    MetaMask (or any EIP-1193 provider) — the connection is purely for\n    signing a canonical claim message; no Eth tx, no gas, the current\n    chain doesn't matter. The signed tuple `(leaf_index, tacit_pubkey,\n    eth_sig)` goes to the worker queue or straight to the issuer. When\n    they fulfil, the confidential UTXO lands in your Holdings via the\n    standard ECDH recovery path.\n\n### Recovery sanity check\n\nOpen the dApp in a fresh incognito window. Import your privkey via the\nImport key button. ↻ Rescan UTXOs. Your full balance — across received\ntransfers, your own etches, your own mints, and your change — should\nreappear from chain data alone.\n\n---\n\n## Trust model\n\n| What you trust                            | For what                                                                              | Mitigation if compromised                                                                  |\n| ----------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| Bitcoin (signet \u002F mainnet)                | Tx ordering, no double-spends, witness data integrity                                  | None — it's the bottom layer                                                              |\n| `mempool.space` API (primary) + `blockstream.info` (watchdog) | Returning real chain data                            | A 5-min divergence watchdog cross-checks tip heights between the two endpoints; ≥3-block disagreement surfaces a top-of-page banner so a single-endpoint outage or tampering is visible. Swap either for any Esplora-compatible API by editing `NETWORKS` in `dapp\u002Ftacit.js`. |\n| The dApp source (`dapp\u002Findex.html` + `dapp\u002Ftacit.js`) you loaded | Implementing the validation rules correctly                                            | Re-host, audit; pin by IPFS CID — the runtime KAT in `runStartupKAT()` is independent defense |\n| `dapp\u002Fvendor\u002Ftacit-deps.min.js` (vendored) | Crypto code matching what was published                                                | Bundle is pinned alongside `dapp\u002Findex.html` and `dapp\u002Ftacit.js` under one IPFS CID; rebuild and re-pin if upstream npm packages change |\n| The asset's etcher                        | *Confidential-supply assets only:* the supply they announced. *(Mintable assets:)* their use of the mint_authority key. | The dApp publishes `(supply, blinding)` to IPFS-embedded metadata by default; for attested assets supply is cryptographically verifiable from chain + IPFS alone. The \"centralized-stablecoin\" trust model only applies when the issuer explicitly opts out of attestation. Mintable assets retain mint-key trust regardless. |\n| The in-page tacit privkey                 | Signing every tacit op (P2WPKH spend, taproot script-path, kernel sig, mint authority) — whichever path put a key in the page (auto, imported, or locally bound to an external wallet address) | **AES-GCM encrypted at rest** with a passphrase-derived key (PBKDF2-SHA256, 600k iterations); unlocked per session. Defends against localStorage exfiltration (malicious extensions, stolen unlocked devices). Export the raw privkey separately via Wallet → Export key — that's the recovery path if the passphrase is lost. |\n\nThe Worker is a **convenience cache**, not a trust target. Setting\n`WORKER_BASE = ''` disables it; the protocol still works (no auto-faucet,\nno image upload, no asset directory, but every existing token still\nvalidates and transfers correctly).\n\n---\n\n## Privacy scope\n\nTacit hides **amounts**. It does not hide:\n\n- Address graph (sender\u002Frecipient bitcoin addresses are visible).\n- Asset ID (the 32-byte asset_id is in every CXFER \u002F T_AXFER \u002F T_MINT \u002F T_BURN \u002F T_PMINT envelope).\n- Sender pubkey (visible at `tx.vin[1].witness[1]` — the recipient needs it\n  for ECDH blinding recovery).\n- Tx graph (inputs and outputs are linkable like any UTXO chain).\n- Burn amounts (T_BURN's `burned_amount` is public for auditability).\n\nSame privacy scope as Liquid CT without surjection proofs: amount-hiding\non every transfer, with opt-in mixer for full unlinkability. Surjection\nproofs for asset_id hiding are on the roadmap.\n\n**The mixer pool (§5.10–§5.11)** layers an opt-in unlinkability primitive\non top: a holder who deposits a fixed-denomination UTXO into a pool and\nlater withdraws to a fresh pubkey breaks the *amount-to-address-to-amount*\nlink inside that pool. Pool participation itself is still public —\nobservers see *that* an address deposited or withdrew, just not which\ndeposit corresponds to which withdrawal. Phase 2 trusted setup finalized\n2026-05-11 with 2,227 community contributions and a Bitcoin-block beacon.\n\n---\n\n## Design tradeoffs + roadmap\n\n- **Witness size.** ~10 KB per CXFER (m=2), about 2,500–3,000 vBytes\n  after the SegWit discount. The witness carries an aggregated bulletproof\n  + kernel signature — the cryptographic cost of hiding amounts.\n  Bulletproofs+ (`T_CXFER_BPP` \u002F `T_AXFER_BPP`) shaves ~14%.\n- **Recursive validation is O(chain depth) on cold cache.** Memoized within\n  a session. A persistent validator cache is a production add for deep\n  chains.\n- **localStorage is the wallet.** Whichever path placed the privkey in the\n  page (auto, imported, or locally bound to an external wallet address),\n  `localStorage` is what persists it. Mainnet UX gates every value-creating\n  op behind an explicit key-export acknowledgement; hardware-wallet\n  integration for the protocol's signing paths (kernel sig, taproot\n  script-path, HMAC-blinding) is the proper long-term mitigation but not\n  yet shipped.\n- **Per-network wallet identities.** Signet and mainnet use independent\n  localStorage keys (`tacit-wallet-v1:signet`, `tacit-wallet-v1:mainnet`,\n  plus `:by:\u003CextAddr>` variants when bound to a connected wallet). A\n  signet test compromise does NOT blast-radius into mainnet, but it also\n  means flipping networks shows a fresh empty wallet by default — the\n  dApp surfaces a one-time toast on the first cross-network flip\n  explaining this. Use `Import key` on the destination network to carry a\n  single identity across both.\n- **Lost mint key = permanent fixed supply.** No recovery mechanism for the\n  mint authority. Mintable etches force a key-export step before broadcast.\n- **Issuer trust** (*for confidential-supply assets only*). Pedersen hides\n  the supply, so unless the issuer publishes `(supply, blinding)` there's\n  nothing to verify the announcement against. The dApp ships with the\n  \"Publish supply opening\" checkbox on by default — the opening is embedded\n  into the asset's IPFS metadata blob (content-addressed, worker-\n  independent) and also POSTed to the worker's `\u002Fattest` cache for fast\n  Discover paint. Issuers who want the centralized-stablecoin trust model\n  uncheck the box explicitly. Mints are auto-attested by default too (per-\n  asset opt-out via `localStorage`). For attested non-mintable assets,\n  total supply is provably and permanently public.\n- **Single-asset transfers only.** No multi-asset CXFER (e.g.,\n  USDA ↔ GOLDC swap in one envelope). The wire format would need a new\n  opcode (e.g., `T_CXFER_MULTI`) with per-asset kernel sigs sharing one\n  aggregated bulletproof. Follow-up territory.\n- **Atomic intent fulfilment window.** Makers fulfil within a 5-min claim\n  window after a taker locks. The `fulfiller\u002F` directory provides an\n  auto-fulfilment service for market-maker automation.\n- **Abandoned commits aren't auto-reclaimed.** If an atomic intent\n  expires unclaimed, the commit P2TR sits unspent on chain. The maker can\n  reclaim manually by spending it via the script-path with the envelope as\n  the leaf — the dApp doesn't yet expose a one-click button for this. Cost:\n  the commit tx fee (~$0.10–1 mainnet).\n- **T_PMINT reorg sensitivity.** Cap correctness for fair-launch\n  (T_PETCH-rooted) assets requires complete, canonically-ordered T_PMINT\n  history, so the indexer only credits a mint at confirmation depth ≥ 3. Wallets\n  surface \"pending\" T_PMINT UTXOs as non-spendable until the depth\n  threshold crosses. A reorg below depth 3 may revoke a previously-\n  credited T_PMINT under new canonical ordering — indexers re-run the\n  cap check on reorg. CETCH+T_MINT assets are unaffected (credit there\n  depends only on the issuer's signature, not aggregate chain state).\n  SPEC §5.9 + §10.\n- **Reference-indexer KV.list cap.** The reference worker uses a single\n  un-paginated `KV.list({ limit: 1000 })` in three places: per-asset\n  `loadCanonicalPmints`, `\u002Fpools` aggregate counts, and\n  `\u002Fpools\u002F:asset_id\u002F:denom` leaf + nullifier lists. Assets accruing more\n  than 1000 T_PMINTs will under-count `cumulative_minted`; pools with\n  more than 1000 deposits or withdrawals will return truncated state to\n  clients consuming the worker view. Practical for now; larger schedules\n  need pagination patches. The cap is operational, not cryptographic —\n  the dapp's local `scanPools` reconstructs from chain regardless, so a\n  worker truncation degrades freshness\u002FUX, not soundness.\n- **Mixer pool — production, Phase 2 finalized.** The shielded-pool wire\n  format (`T_DEPOSIT` \u002F `T_WITHDRAW`, SPEC §5.10–§5.11), worker indexing\n  (`\u002Fpools`, canonical leaf order, nullifier set, reorg-safety depth\n  gate), browser-side Groth16 prover + verifier (snarkjs vendored at\n  `dapp\u002Fvendor\u002Ftacit-mixer.min.js`), Phase 2 ceremony coordinator (init\n  \u002F contribute \u002F finalize), client-side `verifyFromInit` walk, and\n  indexer rejection-path determinism are all shipped (108 mixer tests\n  across 7 files). Phase 1 ptau is the verified Polygon Hermez ceremony\n  output, dual-hash-checked at build. Phase 2 was run publicly via the\n  coordinator: 2,227 community contributions + Bitcoin-block-948824\n  beacon (10 MiMC iterations), finalized 2026-05-11. The canonical\n  bundle is pinned to IPFS at\n  `bafybeidq2ahzte4sfiqjsmhqta62ufenpppzpch5ppry55tzxzlvltxy2u` and\n  hardcoded in the dapp as the trust anchor every pool init binds to.\n  Full status + bundle contents: [MIXER.md](.\u002FMIXER.md).\n- **Lost mixer note = permanent inaccessibility of the deposit.**\n  `T_WITHDRAW` requires the depositor's `(secret, ν)` pair, generated\n  by CSPRNG at deposit time and not derivable from chain alone. The\n  dApp gates first deposits behind a note-export step and offers\n  deposit-record export\u002Fimport; deterministic `(secret, ν)` derivation\n  from privkey is a future UX improvement. Same out-of-band-backup\n  posture as Tornado \u002F Privacy Pools.\n\n---\n\n## Future directions\n\nBeyond the current dApp surface, the protocol opens onto several further\nextensions. Wrapped BTC landed as the cBTC.zk \u002F cBTC.tac amendments;\ntETH landed as the trustless ETH-Bitcoin bridge\n([`BRIDGE.md`](.\u002FBRIDGE.md), [`contracts\u002F`](.\u002Fcontracts\u002F)). See\n[`spec\u002Famendments\u002F`](.\u002Fspec\u002Famendments\u002F) for the normative specs.\nWhat follows is what's still in flight.\n\n### Receiver privacy — shipped for tacit tokens (CXFER)\n\nTacit's **shielded address** primitive (SPEC-BLINDED-PUBKEY amendment, class-2)\nshipped for CXFER in production. Recipients publish a `tcs1…` \u002F `tcsts1…` \u002F\n`tcsrt1…` bech32m address that opaquely encodes their pubkey. Senders who\npaste that handle into the recipient field produce per-tx-unique on-chain\nmarkers — `P2WPKH(hash160(commit))` where `commit = recipient_pubkey + b·G`\nand `b = HMAC(ECDH(sender_priv, recipient_pub) || domain || tx_anchor || vout)`.\nNo recurring on-chain address links one payment to the next; observers see\na fresh recipient address on every receipt.\n\n```\nrecipient publishes: tcs1\u003Cbech32m(recipient_pubkey)>     (one static handle)\nsender derives:      shared = ECDH(sender_priv, recipient_pub)\n                     b      = HMAC(sha256(shared.x), domain || network || tx_anchor || vout)\n                     commit = recipient_pubkey + b·G\n                     CXFER vout[0] → P2WPKH(hash160(commit))\nrecipient scans:     trial-derive commit for each eligible tx output;\n                     match → tweaked_sk = recipient_priv + b mod n\n```\n\nThe ECDH shared secret is reused for both pubkey-blinding (new) and the\nexisting amount-keystream (so no extra ECDH cost on the sender). Cryptography\nis BIP-340 \u002F BIP-341 family; no new ceremony. Recipient-side discovery uses\nthe worker's per-asset `xferseen` index — bounded scan, not the every-block\nwalk BIP-352 requires.\n\n**Receiver privacy for plain BTC sats — BIP-352 Silent Payments (deferred).**\n\nBIP-352 solves the same problem for plain bitcoin (not just tacit tokens).\nA separate ceremony from t","Tacit 是一个基于比特币的元协议，它扩展了Runes\u002FOrdinals模式，超越了简单的代币应用。其核心功能包括保密价值、匿名支出、原生自动做市商（AMM）、无需信任的BTC和ETH封装等，这些都通过任何人都可以运行的索引器来验证，并且仅依赖于链上数据达成共识。技术特点上，Tacit利用了加密隐私技术和Groth16电路来实现原本需要虚拟机完成的功能，而无需联邦制、侧链或智能合约运行环境。适用于追求更高隐私保护及去中心化金融服务的应用场景，如希望在不牺牲安全性前提下进行资产转移、交易或者参与流动性挖矿等活动的用户。","2026-06-11 04:03:40","CREATED_QUERY"]