Skip to content

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 by mint_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:

  1. Pool registered for (asset_id, denomination)
  2. Claimed merkle_root in last 32 canonical roots of this pool ☑
  3. nullifier_hash NOT in this pool's spent-nullifier set ☑
  4. bind_hash recomputes correctly over the public-input tuple ☑
  5. Groth16 proof verifies under pool.vk
  6. 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 time
  • network (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