(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 §5.8 / §5.9.
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 (SPEC §5.12.1).
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).
Type how much to buy or sell, click Swap. Trades settle atomically on Bitcoin — asset and sats change hands together or not at all. Zero fee — you pay only Bitcoin's miner fees (shown in the swap preview before you sign).
No MEV. No reordering window, no sequencer, no front-running — Bitcoin's PoW handles it.
If your swap can't fully fill at your limit price — including when nothing matches at all — the unfilled amount rests as a passive order for 24h. Either way, you get a tradeable position from one click.
You keep custody throughout. Balances stay private on-chain.
Confidential token meta-protocol on Bitcoin. Amounts are hidden by default via Pedersen commitments + bulletproof rangeproofs + Mimblewimble-style kernel signatures. Full address-graph privacy is available through the built-in shielded mixer pool (Poseidon Merkle tree + Groth16 zk-SNARK withdrawals) — so tacit covers both amount-hiding and unlinkability, not just one or the other. The protocol also includes atomic single-tx OTC settlement, permissionless fair-launch mints with publicly-auditable caps, and on-chain claim pools (airdrops). Single asset per envelope.
Like every Bitcoin token meta-protocol (Runes, BRC-20, Ordinals, Taproot Assets, RGB, Counterparty), tacit's token rules are not enforced by Bitcoin nodes. Bitcoin validates the underlying tx structure and Taproot signatures; an external indexer validates the token-level rules (commitments, rangeproofs, kernel signatures, asset_id consistency, pool nullifier sets).
Architecturally tacit is an indexer-validated meta-protocol — same family as Runes, BRC-20, and Ordinals. All data needed to validate any UTXO lives on Bitcoin's chain. An indexer with chain-only access can recursively walk every UTXO back to its etch root (CETCH or T_PETCH) and reach the same verdict as any other indexer running the same rules. No consensus change, no federation, no out-of-band proof exchange.
This is distinct from proof-distribution protocols (RGB, Taproot Assets) where the recipient must receive validity proofs out-of-band; an indexer with chain-only access cannot reconstruct token state. It's also distinct from fully-validated chains (Liquid, Mimblewimble) where consensus enforces the rules natively. Tacit's contribution is bringing CT-style amount-hiding into the indexer-validated meta-protocol family — the only thing that ever travels out-of-band is the recipient's own opening (cleartext amount + blinding) via share-link, which lets them see their balance but is not required for any indexer to validate the UTXO.
v1 envelope version is 0x01; live opcodes span 0x21–0x38 (reserved range 0x21–0x4F for future extensions). Every op is a 2-tx commit/reveal pair carrying the envelope inside tx.vin[0].witness[1] of the reveal.
mint_limit; reveals (amount, blinding) so the cap is auditable from chain
0x29T_DEPOSITlock a fixed-denomination UTXO into a shielded pool (Poseidon leaf)
0x2AT_WITHDRAWGroth16-gated mint from a pool; recipient unlinkable to any specific deposit
0x2BT_DROPlock existing supply into a public claim pool with optional Merkle eligibility gate
0x2CT_DCLAIMpermissionless claim from a drop; reveals (per_claim, blinding) for audit
0x37T_AXFER_VARvariable-amount atomic settlement — one signed offer up to X, any taker fills any chunk in [min, X] (§5.7.6.1 / §5.7.9)
0x38T_WRAPPER_ATTESTwrapper convention attestation — issuer binds a tacit asset to an external collateral claim with coverage check (§5.19)
Wire-format examples below cover the core four (CETCH / CXFER / T_MINT / T_BURN). The remaining opcodes follow the same envelope shape with their own payload layouts and validator rules — see SPEC.md §5.7–§5.19 for the byte-exact specifications.
Marketplace primitives live in §5.7 and route through T_AXFER / T_AXFER_VAR settlement — no separate opcodes needed: atomic intents (§5.7.6 — seller publishes one signed offer, any taker claims it within 5 min and settles atomically on Bitcoin); preauth sales (§5.7.8 — seller pre-signs everything, then goes offline; any buyer settles whenever); batched preauth-take (§5.7.8.1 — buyer sweeps N preauths in ONE reveal tx, ~70% Bitcoin-fee reduction vs N separate settlements; pure flow-level optimization leveraging position-independent SIGHASH_SINGLE_ACP); variable-amount intents (§5.7.6.1 — partial-fillable seller offers); and bid intents (§5.7.7 — partial-fillable buyer offers, off-chain coordination, on-chain settlement via T_AXFER/T_AXFER_VAR). All marketplace flows share the same kernel-sig + rangeproof crypto as base CXFER; the difference is the coordination layer, not the on-chain primitive.
Commitments: C = a·H + r·G, additively homomorphic. H is a NUMS generator (no known discrete log w.r.t. G) derived deterministically by hash-to-curve from the seed "tacit-generator-H-v1". The protocol carries C on chain; (a, r) stay private.
Rangeproofs: aggregated Bulletproofs (Bünz et al. 2017) at n=64 bits, meaning each committed value is proven to lie in [0, 2⁶⁴). A single proof can cover m ∈ {1, 2, 4, 8} commitments simultaneously via the inner-product argument, with 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. ~250 ms to prove and ~150 ms to verify in-browser per proof; the indexer batches multiple proofs into a single multi-scalar multiplication for sub-linear amortized cost.
64-bit range bounds any single Pedersen commitment to 2⁶⁴ − 1 ≈ 1.84 × 10¹⁹ base units (~184 billion display units at the maximum 8 decimals; ~18 quintillion at 0 decimals). Wallets still spread holdings across multiple UTXOs (1–8 per CXFER) for change tracking, but the per-UTXO cap is not a practical constraint.
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 ≤256 bytes — it does not enforce a URI scheme. This leaves room for future schemes (ar://, ipns://, etc.) without a wire-format change. 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 on purpose — every wallet that views the asset would fetch from the issuer-controlled host, which is an IP-correlation beacon. Forcing IPFS routes traffic through the configured gateway, one fixed origin that the CSP locks down 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 at an attacker's address.
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.
Recipient blinding: r_recip = HMAC-SHA256(ECDH(sender_priv, recipient_pub), "tacit-blind-v1" ‖ anchor ‖ vout_LE), where anchor = first_asset_input_txid_BE ‖ first_asset_input_vout_LE. The anchor's per-tx entropy prevents cross-tx commitment correlation: without it, two transfers between the same parties at the same vout would produce identical blindings, and an observer could compute (C₁ − C₂) = (a₁ − a₂)·H to learn the difference of amounts.
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 (typically 1 for a 2-output CXFER, but parameterized so multi-output CXFERs with N=4 or N=8 can place change at higher vouts). Deterministic from the wallet privkey so it's recoverable from 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.
This means share-links are optional notifications, not required for recovery. A freshly-installed wallet with only its privkey can auto-discover its full balance from chain alone — incoming CXFER transfers (via ECDH), its own CXFER change (via self-derived), and its own CETCH supply (via etch-anchor self-derived).
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".Amounts: hidden by default in every CETCH, T_MINT, CXFER, T_AXFER, and BURN-change commitment (32-byte commitment + 8-byte HMAC-encrypted u64 on chain).
Address graph: publicly linkable on the base layer, but the mixer pool (T_DEPOSIT / T_WITHDRAW, §5.10–§5.11) cuts the link. A holder deposits a fixed-denomination UTXO into a Poseidon Merkle tree, then later withdraws under a Groth16 proof of unspent leaf membership. The withdraw recipient is unlinkable to any specific deposit; the anonymity set is the count of currently-unspent leaves at withdraw time. So end-to-end: amount-hiding is on by default and full address-graph privacy is opt-in via the pool — a stricter posture than Liquid CT and approaching the privacy scope of Mimblewimble or shielded Zcash for the assets that route through it.
What remains public, even after pooling:
tx.vin[1].witness[1] (P2WPKH witness) on non-pool transfers; recipient needs it for ECDH blinding recovery.Recursive validation is O(chain depth) on a cold cache, depth-bounded at 200 hops. With aggregated bulletproofs and batched verification, the practical cost is dominated by BIP-340 Schnorr verification (kernel sigs + mint sigs) across the ancestry, not rangeproofs (~150 ms verify per BP, batched into one multi-exp across the full walk). Memoization keeps subsequent scans O(new UTXOs) within the same session. For a fresh wallet on mobile, deep ancestries (≥50 hops) will still be slow; production deployments benefit from a persistent validation cache or a shared indexer service. Cryptographic correctness holds without either; the gain is purely UX.
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 — embedded in the asset's IPFS metadata blob (content-addressed) and pinned to the discovery worker as a cache. Anyone verifies C == supply·H + r·G against the on-chain commitment, no issuer trust required. Issuers explicitly opt out of attestation only if they have a reason to keep the total confidential; the dApp surfaces that as a deliberate uncheck.
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. Standard signature-gated mint pattern: holders trust the mint-authority key not to be abused, and compromise of that private key means uncapped inflation. The dApp auto-attests every mint by default so supply remains publicly auditable. Fixed-supply CETCHes (mint_authority all-zero) cannot be expanded — for those, etch-time attestation gives provably and permanently public supply.
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.
Mixer pools (T_DEPOSIT / T_WITHDRAW). Each pool runs its own per-circuit Groth16 MPC ceremony at init time; soundness rests on ≥ 1 honest contributor in that ceremony. Privacy (zero-knowledge) is unconditional and does not depend on the ceremony. Pools pin ceremony transcripts to IPFS by content hash; the canonical mixer-circuit ceremony for v1 (Phase 2, beacon-finalized 2026-05-11, 2,227 contributors) is hardcoded in the dApp. Consumers should verify contributor diversity before depositing into any pool not bound to the canonical bundle.
After issuance. No participant can inflate (kernel sig blocks unbalanced CXFER / T_AXFER / T_BURN; T_PMINT caps are chain-auditable), 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.