Operations
Tacit's V1 opcode set is 19 envelope opcodes at code points 0x21, 0x23–0x32, 0x37, and 0x38 (the 0x22 slot is reserved). This page covers the eleven confidential-token core and mixer opcodes that the present analysis is scoped to. The remaining eight — the Automated Market Maker (AMM) stack (T_LP_ADD, T_LP_REMOVE, T_SWAP_BATCH, T_SWAP_VAR, T_PROTOCOL_FEE_CLAIM, T_INTENT_ATTEST), the variable-amount atomic intent (T_AXFER_VAR), and the wrapper attestation (T_WRAPPER_ATTEST) — are out of scope and noted at the end.
Every operation follows the same physical structure (commit + reveal Bitcoin transactions, envelope in vin[0].witness[1] of the reveal tx, BIP-341 Nothing-Up-My-Sleeve (NUMS) internal pubkey on the spent Pay-to-Taproot (P2TR) output). The Protocol page covers that framing.
V1 opcode catalog (in scope for this analysis)
| OP | NAME | SHORT | CONSUMES | PRODUCES |
|---|---|---|---|---|
0x21 |
CETCH | Issue a new confidential asset with hidden initial supply | A user's BTC for fees | Supply UTXO at vout[0] (hidden amount) |
0x23 |
CXFER | Confidential transfer; splits into 1, 2, 4, or 8 outputs | Asset UTXOs of one asset_id |
New UTXOs (hidden amounts), aggregated proof |
0x24 |
T_MINT | Issuer-signed additional supply on a mintable CETCH | Issuer's BTC for fees | New supply UTXO at vout[0] (hidden amount) |
0x25 |
T_BURN | Any holder destroys part or all of their balance | Asset UTXOs | Optional change UTXO(s); public burned_amount |
0x26 |
T_AXFER | Atomic Over-The-Counter (OTC): CXFER plus auxiliary BTC inputs in the same tx | Asset UTXOs + aux BTC | Tacit UTXO at recipient + BTC payment + change |
0x27 |
T_PETCH | Permissionless-mint deployment record (fair launch) | Deployer's BTC for fees | No supply UTXO — declares (ticker, cap, mint_limit, height window) |
0x28 |
T_PMINT | Permissionless mint event against a T_PETCH parent | Caller's BTC for fees | New supply UTXO at vout[0] (revealed amount = mint_limit) |
0x29 |
T_DEPOSIT | Lock fixed-denomination UTXO into a shielded pool | Asset UTXO matching pool denomination | No tacit UTXO — appends Poseidon leaf to pool tree |
0x2A |
T_WITHDRAW | Anonymous mint from a shielded pool | (Nothing on-chain; one ZK note off-chain) | Fresh tacit UTXO of pool's denomination at vout[0] |
0x2B |
T_DROP | Lock existing supply into a public-claim pool | Asset UTXOs summing to cap_amount |
No tacit UTXO — declares (per_claim, cap, merkle_root, expiry) |
0x2C |
T_DCLAIM | Permissionless claim event against a T_DROP parent | Claimant's BTC for fees | New asset UTXO of per_claim at vout[0] |
The remaining V1 opcodes are listed at Out of scope below.
CETCH — initial issuance (0x21)
The opening move for every confidential asset. CETCH declares a ticker, decimals, optional image URI, and a Pedersen-committed initial supply. It also fixes the mint authority — set to a x-only Schnorr pubkey for mintable assets, set to all-zero (0x00…00) for fixed-supply assets. Mint authority is permanent: there is no protocol-level mechanism to rotate or transfer it short of a hard fork.
Envelope payload shape (from SPEC §5.1):
T_CETCH(1)
|| ticker_len(1) u8, 1..16
|| ticker(ticker_len) UTF-8
|| decimals(1) u8, 0..8
|| commitment(33) Pedersen C = supply·H + r·G (compressed)
|| amount_ct(8) u64 LE supply XOR HMAC-keystream
|| rp_len(2) u16 LE rangeproof length
|| rangeproof(rp_len) aggregated bulletproof, m=1, n=64
|| mint_authority(32) x-only Schnorr pubkey, OR all-zero (=non-mintable)
|| img_len(2) u16 LE, 0..256
|| image_uri(img_len) UTF-8 (typically "ipfs://bafk…")
| FACET | DETAIL |
|---|---|
| Who can call | Anyone with BTC for fees |
| Signature | None on the envelope itself — asset_id self-binds the asset to the carrying tx |
| Output | Supply UTXO at vout[0] (the etcher's address by convention; bytecode does not enforce) |
| Asset ID derivation | asset_id = SHA256(reveal_txid_BE \|\| 0_LE) |
| Ticker uniqueness | None — two CETCHes can both use "USDC". Wallets must display asset_id alongside ticker |
| Supply attestation | dApp publishes (supply, blinding) opening to IPFS by default. Issuers opting out get a centralized-stablecoin trust model |
The supply commitment hides the exact issuance amount. Without an attestation, observers know only that supply is some integer in [0, 2⁶⁴). The dApp's default-on attestation flow pins (supply, blinding) to IPFS as part of the asset metadata; anyone verifies that pedersenCommit(supply, blinding) == on-chain commitment from chain alone, with no worker trust and no issuer trust beyond the one-time honest publish. SPEC §7.3 spells out the attestation flow in full.
CXFER — confidential transfer (0x23)
The workhorse opcode. CXFER consumes one or more asset UTXOs and produces 1, 2, 4, or 8 new ones, with amounts hidden via Pedersen commitments + an aggregated bulletproof, and supply conservation enforced by a Mimblewimble-style Kernel Signature.
Payload:
T_CXFER(1)
|| asset_id(32)
|| kernel_sig(64) Schnorr sig over kernel_msg (§5.2)
|| N(1) number of outputs, ∈ {1, 2, 4, 8}
|| (commitment(33) || amount_ct(8)) × N
|| rp_len(2)
|| rangeproof(rp_len) aggregated bulletproof, m = N, n = 64
| FACET | DETAIL |
|---|---|
| Who can call | Holder of the spent asset UTXOs (signs the kernel sig with excess = Σ r_out − Σ r_in) |
| Output count | Exactly 1, 2, 4, or 8 — no other values |
| Asset-id consistency | Every input must descend from a CETCH or T_MINT for the same asset_id; validator rejects mixed-asset inputs |
| Privacy property | Amounts hidden; sender + recipient Bitcoin addresses visible (this is the address-graph leak Tacit does not close) |
| Recovery | Recipient decrypts amount via ECDH-derived keystream (tacit-amount-v1); sender decrypts change via self-keystream (tacit-amount-self-v1) |
The kernel signature is the cryptographic core. See the Mechanisms page for the full Discrete Log Problem (DLP)-on-NUMS argument.
A CXFER with N = 2 (recipient + change — the common case) is roughly 1 KB of envelope payload and ~510 vBytes after the SegWit witness discount.
T_MINT — additional supply (0x24)
Only valid against a CETCH whose mint_authority is non-zero. The issuer signs mint_msg under the mint authority key; the new supply commitment carries its own range proof; vout[0] becomes the new supply UTXO.
T_MINT(1)
|| asset_id(32) must equal SHA256(etch_txid_BE || 0_LE)
|| etch_txid(32) reference to the original CETCH reveal tx
|| commitment(33) Pedersen C = mint_amount·H + r_m·G
|| amount_ct(8)
|| rp_len(2)
|| rangeproof(rp_len) aggregated bulletproof, m = 1
|| issuer_sig(64) Schnorr sig under mint_authority
| FACET | DETAIL |
|---|---|
| Who can call | Holder of the mint_authority privkey from the parent CETCH |
| Anchor binding | mint_msg includes commit_anchor = commit_tx.vin[0].outpoint — prevents an attacker rewrapping the on-chain mint payload into their own commit/reveal pair |
| Output | New supply UTXO at vout[0], can be CXFER'd or T_BURN'd like any other holding |
| Mintability check | Validator rejects T_MINT against a CETCH with mint_authority = 0x00…00 |
The anchor binding deserves a callout: without it, the entire mint payload (asset_id, commitment, amount_ct, rangeproof, issuer_sig) is observable on-chain. Without the anchor, an attacker could rewrap it into their own commit/reveal and plant a validator-accepted supply UTXO at their own address — and with the SPEC §8 attestation pattern leaking (amount, blinding), that planted UTXO would be spendable, doubling the auditable supply. The anchor closes this exact attack.
T_BURN — destroy supply (0x25)
Any holder of asset UTXOs can burn part or all of their balance. The burned amount is public — that is the whole point: observers must be able to audit supply reduction without trusting the burner. Burn is permissionless; the kernel signature on the balance equation Σ C_in = burned_amount · H + Σ C_out is the authentication.
T_BURN(1)
|| asset_id(32)
|| burned_amount(8) u64 LE — public
|| kernel_sig(64) Schnorr sig under E' = burn·H + Σ_out − Σ_in
|| N(1) ∈ {0, 1, 2, 4, 8}; N = 0 ⇒ burn-everything
|| (commitment(33) || amount_ct(8)) × N
|| rp_len(2) omitted if N = 0
|| rangeproof(rp_len) omitted if N = 0
| FACET | DETAIL |
|---|---|
| Who can call | Any holder of asset UTXOs (signs the kernel sig with excess = Σ r_change − Σ r_in) |
| Public field | burned_amount — observers audit supply reduction directly |
N = 0 case |
Burn-everything; no change outputs, no rangeproof needed |
| Soundness | Same DLP-on-NUMS argument as CXFER (the kernel sig requires H component to be zero, which requires the burn term to balance) |
T_AXFER — atomic OTC settlement (0x26)
A CXFER with auxiliary BTC inputs and BTC payment output in the same Bitcoin transaction. The whole settlement closes in one block; the maker cannot redirect the buyer's payment; the buyer cannot get the tokens without paying. The verified sample tx on the Overview page (b76c90de...150f49 (tacitscan · mempool)) is a T_AXFER.
Structural difference from CXFER: vin[1..1+asset_input_count] carries the asset inputs (validated as Tacit ancestors); vin[1+asset_input_count..] carries the buyer's BTC P2WPKH inputs (not validated as Tacit ancestors). The kernel sig only covers the asset side.
| FACET | DETAIL |
|---|---|
| Who can call | Settlement coordinator with all necessary signatures (maker's asset-side, buyer's BTC-side) |
| Atomicity guarantee | Single Bitcoin tx — both sides settle or neither does |
| Coordination | Off-chain PSBT-style flow (SPEC §5.7.3); browse-and-take atomic intents in SPEC §5.7.6 |
T_PETCH — fair-launch deployment (0x27)
The supply-side trade-off opposite of CETCH: T_PETCH commits to a publicly auditable cap with no supply minted at deploy time. Anyone (including the deployer) can later broadcast T_PMINT to mint a fixed mint_limit tranche, with cumulative supply publicly auditable against the cap.
T_PETCH declares:
ticker(1–16 UTF-8 bytes, not unique)decimals(0–8)cap_amount(u64 — total lifetime supply ceiling; must be divisible bymint_limit)mint_limit(u64 — fixed amount per T_PMINT)mint_start_height(u32 — 0 ⇒ deploy block + 1)mint_end_height(u32 — 0 ⇒ no expiry)image_uri(optional, ≤ 256 B)
T_PETCH produces no UTXO at any vout. Soundness invariants: cap_amount % mint_limit == 0 and well-defined height window. Like CETCH, no signature is required — the asset_id self-binds.
The trade vs. CETCH: supply-side confidentiality is traded for permissionless issuance and on-chain auditable cap.
T_PMINT — permissionless mint event (0x28)
The companion to T_PETCH. Anyone may broadcast a T_PMINT against a T_PETCH ancestor. Mints exactly mint_limit tokens; reveals (amount, blinding) so any chain reader can audit cumulative supply against the cap via canonical chain order.
| FACET | DETAIL |
|---|---|
| Who can call | Anyone with BTC for fees |
| Required identity proof | None — the eligibility is whether the cap has been exhausted and whether we are inside the height window |
| Amount | Always petch.mint_limit (validator enforced; not hidden) |
| Soundness invariants | 1) amount == petch.mint_limit, 2) confirmed_height ∈ [effective_start, effective_end], 3) cumulative valid T_PMINTs against this asset_id × mint_limit + amount ≤ cap_amount, 4) Pedersen opening verifies |
| Replay tolerance | Replay of a published T_PMINT envelope into a fresh commit/reveal pair is allowed but cost-symmetric with honest minting: it consumes a cap slot at full Bitcoin-fee cost and produces a UTXO at the rewrapper's output script, not the original |
The "valid T_PMINT" count is taken at confirmation depth ≥ 3 to provide reorg safety. The canonically-earlier ordering is by (confirmed_height, tx_index, txid).
T_DEPOSIT — into the shielded pool (0x29)
Locks a fixed-denomination Tacit UTXO into a per-(asset_id, denomination) mixer pool. Appends a Poseidon leaf commitment Poseidon₃(secret, ν, denomination) to the pool's Merkle tree. The deposit itself is publicly attributable; unlinkability comes at withdraw time.
| FACET | DETAIL |
|---|---|
| Who can call | Holder of an asset UTXO whose committed amount exactly equals the pool's denomination |
| What it consumes | One Tacit asset UTXO (via BIP-340 Schnorr kernel signature) |
| What it produces | No tacit UTXO. The envelope's leaf is appended to the pool tree at depth ≥ 3 (reorg-safety gate) |
| Pool creation | The first T_DEPOSIT against a (asset_id, denomination) pair with denomination = 0 sentinel is POOL_INIT — registers pool metadata and binds the Groth16 vk |
The deposit transaction is not anonymous. An observer knows: who deposited (the input owner), into which pool (the asset_id + denomination), and at which block (the confirmation). What they do not know is which subsequent T_WITHDRAW redeems this specific deposit.
T_WITHDRAW — out of the shielded pool (0x2A)
The unlinkability operation. Produces a fresh Tacit UTXO of the pool's denomination at vout[0], gated on a Groth16 proof of unspent-leaf membership. Anyone holding the mixer note (secret, ν) for a deposited leaf can withdraw to any recipient address.
The validator's reject path:
- Pool registered for
(asset_id, denomination)☑ - Claimed
merkle_rootin last 32 canonical roots of this pool ☑ nullifier_hashNOT in this pool's spent-nullifier set ☑bind_hashrecomputes correctly over the public-input tuple ☑- Groth16 proof verifies under
pool.vk☑ - External Pedersen check:
recipient_commitment == denomination · H + r_leaf · G☑
Any one failure rejects the envelope. The full Mixer page covers the circuit and ceremony.
T_DROP — public-claim pool (0x2B)
Locks existing supply into a public-claim pool, optionally gated by a Merkle list of eligible Ethereum addresses (think: airdrop to a snapshot of historical token holders). The supply does not leave the system — it shifts from depositor to pool accounting, ready for permissionless claim.
T_DROP declares:
(per_claim, cap_amount, merkle_root, expiry_height)— all envelope plaintext, fixed at drop-creation timenetwork(which Ethereum-style network the merkle gate's eth_addresses live on)
| FACET | DETAIL |
|---|---|
| Who can call | Holder of asset UTXOs summing to cap_amount |
| What it consumes | Asset UTXOs that sum (committed) to cap_amount — kernel sig on Σ C_in − cap_amount · H == excess · G |
| What it produces | No tacit UTXO of the asset. Pool accounting registered in indexer state |
| Eligibility | If merkle_root ≠ 0, claimants must produce (eth_sig + merkle proof) binding their eth_address to their tacit pubkey |
| Cost model | Each claimant pays their own Bitcoin tx fee. Drop creator only pays for the T_DROP tx |
Supply-preserving: tokens shift from depositor's wallet to pool accounting to claimant with no destruction and no re-minting. The kernel sig argument on the balance equation is the same as T_DEPOSIT.
T_DCLAIM — claim from a drop (0x2C)
Permissionless claim event against a T_DROP parent. Mints exactly per_claim tokens from the drop pool to vout[0]; reveals (per_claim, blinding) so chain readers can audit cumulative claims against the cap.
| FACET | DETAIL |
|---|---|
| Who can call | If merkle_root = 0: anyone with BTC for fees. If merkle_root ≠ 0: must produce a valid eligibility witness |
| Amount | Always drop.per_claim (validator enforced; not hidden) |
| Eligibility witness | When required: (recipient_pub, leaf_index, eth_address, eth_sig, merkle_proof). The eth_sig recovers eth_address from the EIP-191 hash of the canonical claim msg over the tacit pubkey |
| Soundness invariants | 1) amount == drop.per_claim, 2) confirmed_height ≤ drop.expiry_height, 3) cumulative valid T_DCLAIMs × per_claim + amount ≤ cap_amount, 4) eligibility witness verifies (if gated), 5) (drop_id, leaf_index) not already claimed |
| Replay tolerance | Replay of a published T_DCLAIM envelope into a fresh commit/reveal pair is allowed but cost-symmetric with honest claiming |
The eligibility witness is what lets a Tacit drop snapshot Ethereum-side balances (e.g., "everyone who held > 0 of token X at block N gets per_claim tokens"). The Ethereum signature ties the Ethereum address to the Tacit recipient pubkey, preventing a hostile party from claiming on someone else's behalf.
Out of scope for V1 analysis
Eight opcodes (grouped into three deferred surfaces — AMM, variable-amount atomic intent, wrapper attestation) exist but are not covered in this V1 analysis. Each surface is large enough to warrant a dedicated deep-dive of its own.
| OP | NAME | WHY OUT OF SCOPE |
|---|---|---|
0x2D |
T_LP_ADD | Part of the confidential AMM stack (4,590 lines of separate spec in AMM.md). Phase 2 trusted setup pending. |
0x2E |
T_LP_REMOVE | Confidential AMM — proportional withdrawal of pool reserves. |
0x2F |
T_SWAP_BATCH | Confidential AMM — batched clearing-price settlement with Groth16 proof. |
0x30 |
T_INTENT_ATTEST | Scope-generic preconfirmation channel attestation used by AMM and orderbook surfaces. |
0x31 |
T_PROTOCOL_FEE_CLAIM | AMM protocol-fee crystallization. |
0x32 |
T_SWAP_VAR | Per-trade variable-amount AMM swap (no Groth16 — sigma cross-curve binding instead). |
0x37 |
T_AXFER_VAR | Variable-amount atomic settlement variant of T_AXFER. |
0x38 |
T_WRAPPER_ATTEST | Optional on-chain wrapper-issuer attestation (cBTC, cBTC-ZK, etc.). |
The confidential AMM is the largest of these. Its design uses BabyJubJub as a second curve for in-circuit arithmetic, sigma cross-curve binding to tie BabyJubJub and secp256k1 commitments, and a Groth16 batch proof that asserts the constant-product invariant + clearing arithmetic over N intents. The complexity is at least an order of magnitude above the confidential-token core covered here.
References
- Tacit protocol specification — SPEC.md (z0r0z/tacit, MIT) — opcode definitions and envelope payload shapes (§5)
- Tacit AMM specification — AMM.md (out of scope for V1)
- Tacit mixer specification — MIXER.md
- Bitcoin Improvement Proposal 340 (Schnorr signatures) — BIP-340
- Bitcoin Improvement Proposal 341 (Taproot output script type, P2TR) — BIP-341
- Ethereum Improvement Proposal 191 (signed data standard, used by T_DCLAIM eligibility) — EIP-191
- DNZN — Protocol, Mechanisms, Mixer, Risks