UTT Protocol Overview
UTT (UnTraceable Transactions) is a cryptographic protocol for decentralized ecash with accountable privacy. Alin Tomescu began UTT research at VMware Research in 2018. During 2019-2020, Alin completed his cryptography PhD at MIT, advised by Srini Devadas, while UTT work continued at VMware. The project was rebooted in 2021, and the paper, published in April 2022, was written by Alin Tomescu, Adithya Bhat, Benny Applebaum, Ittai Abraham, Guy Gueta, Benny Pinkas, and Avishay Yanai.
In February 2022, Aptos launched with a founding team that included Alin Tomescu, Josh Lind, and others from Meta/Diem. The research was presented at Stanford Blockchain Conference in 2023, at Yale, and at a16z directly. It formed the basis of a testing pilot with the Bank of Israel for CBDC privacy research and was integrated into VMware's Concord BFT system before Alin joined Aptos Labs.
UTT is the academic protocol. Invisible Assets is Alin Tomescu and Josh Lind's implementation of UTT on Aptos. It is the next layer after AIP-143 (Confidential Assets). On March 21, 2026, Alin published the UTT blog post. Three days later, on March 24, 2026, the Confidential Assets AMA took place, where Alin hinted at "invisible assets" as the next layer.
The Fundamental Distinction From Confidential Assets
AIP-143 (Confidential Assets) is account-based: every user has a ConfidentialAssetStore resource attached to their address. The math operates on the balance in that resource. The address is always visible because the resource lives at an address.
UTT is UTXO-based: there are no per-user balances anywhere onchain. All state lives in two append-only structures:
- 02A Merkle tree of coin commitments. Sealed envelopes, each containing a public key and value
- 04A nullifier set. Spent markers for coins that have been consumed
No address is associated with any coin. The only link between a coin and its owner exists in the owner's secret key sk, which never appears onchain. This is why UTT hides the trade graph: there is nothing onchain that links the sender and receiver of a transaction.
Coin Structure
Every UTT coin is a Pedersen commitment:
C = Commit(pid, sn, v)
Where:
- ›
pid. The owner's pseudonymous ID (derived from their public key, not their address) - ›
sn. A secret serial number, known only to the coin owner - ›
v. The coin's value in the base currency
The commitment hides all three values. Onchain, a coin looks like a random 32-byte group element. There is no way to determine the owner, the denomination, or the serial number from the commitment alone.
When a coin is minted, the mint authority signs (pid, sn, v) with a Pointcheval-Sanders (PS) signature σ. The owner holds (pid, sn, v, σ) privately. The signed tuple is what proves the coin was legitimately issued. It cannot be fabricated without access to the threshold mint keys.
How It Works
UTT works like digital cash. Every coin is a sealed envelope: a cryptographic commitment that locks a public key and a value inside, invisible to everyone except the owner. You can't open the envelope without the secret key. You can't tell which envelope belongs to whom. You can't even tell how much is inside.
When you want to spend a coin, you don't open the envelope. Instead, you produce a unique fingerprint called a nullifier that proves you own the coin and marks it as spent. The nullifier is deterministic: the same coin always produces the same fingerprint, so you can't spend it twice. But the fingerprint reveals nothing about which envelope it came from, who you are, or how much you spent.
The system maintains two append-only databases: a tree of sealed envelopes (every coin ever created) and a set of spent fingerprints (every coin ever consumed). Neither database is ever reduced, only added to. The tree grows when coins are minted. The fingerprint set grows when coins are spent. That's the entire state model.
A ZK proof ties it all together. When you submit a transaction, you include a cryptographic proof that says: "I own a real coin in this tree, I computed the fingerprint correctly, and the math balances." The onchain contract verifies the proof in constant time, checks the fingerprint isn't already spent, and appends the new coins. It never learns who you are, which coin you spent, or how much you moved.
The Nullifier: How Double-spend Prevention Works Without Revealing Identity
A nullifier is a deterministic fingerprint that only the coin's owner can compute. It's derived from two secrets: the coin's serial number sn (hidden inside the sealed envelope) and the owner's spending key sk (never published anywhere).
nl = prf(sn. Sk)
In practice this uses the Dodis-Yampolskiy PRF over BN254: nl = H(g^(sn / sk)). On Aptos, the PRF is Poseidon hash over BN254 field elements, which is ZK-friendly (220 constraints vs 25,000 for SHA256).
Double-spend prevention: The same (sk, sn) always produces the same nl. If you try to spend a coin twice, you'd publish the same fingerprint twice. The contract rejects the second attempt instantly because nl is already in the nullifier set. No scanning, no graph analysis. Just a single O(1) table lookup.
Privacy preservation: A nullifier looks like a random 32-byte value to anyone without sk. There is no computable link between a nullifier and its source coin commitment. An observer watching the chain sees spent fingerprints appearing but cannot connect any of them back to any sealed envelope in the tree.
The Merkle Tree: Append-only, Constant Storage
All coin commitments live in a single append-only Merkle tree. New coins get appended as leaves. Nothing is ever removed.
The tree uses a frontier representation: instead of storing every leaf, it stores only the rightmost node at each level. For a tree of depth 32 (supporting 4 billion coins), that's 32 nodes = 1,024 bytes of storage. Constant forever, regardless of how many coins exist.
Historical roots stay valid. Every time a new leaf is appended, the root changes. But all prior roots are cached. A coin created when the root was r_7 can be spent when the tree has grown to r_1000 by proving membership against the old root r_7. This prevents timing attacks: an observer can't narrow down when you received a coin by watching when you spend it, because your proof could reference any historical root.
Nullifiers in Table: Each nullifier is an independent storage slot in an Aptos Table. Checking whether a coin is spent is always O(1), never requires loading the full set.
The ZK Proof: Public Inputs vs. Private Witness
When you spend a coin, you produce a single Groth16 proof (~256 bytes) that simultaneously proves five things. The contract verifies this proof in constant time. Everything below happens inside the proof circuit and is never revealed to anyone.
The 5 core constraints (from Alin's Stanford Blockchain Conference'23 presentation):
- 02
sk_iis the secret key ofpk_i(I own input coin i) - 04
sk_jis the secret key ofpk_j(I own input coin j) - 06
nl_i = prf(i. Sk_i)(nullifier for coin i is correctly computed) - 08
nl_j = prf(j. Sk_j)(nullifier for coin j is correctly computed) - 10
v_i + v_j = v_A + v_B + v_gas(value conservation: no money created)
The full production circuit has ~14 constraints including PS signature verification inside ZK (proving the coin was legitimately minted without revealing the signature), output commitment integrity, range proofs via DeKART (preventing negative values that could create money), anonymous credential verification, and budget UTXO enforcement.
What the contract sees (public inputs only): nullifiers, output commitments, the Merkle root, and the gas amount. That's it. The contract has no idea who spent what, which coins were consumed, or what the amounts were. It just verifies that one compact proof is mathematically valid, checks the nullifiers aren't already spent, and updates the tree.
What the contract never sees: secret keys, serial numbers, values, mint signatures, Merkle paths, budget balances, pseudonymous IDs. All of that lives inside the ZK proof as private witness data.
The Anonymity Budget: How Compliance is Enforced by Math, Not Policy
Every anonymous payment system has a compliance problem. Tornado Cash gave everyone unlimited anonymity with no exceptions. Regulators killed it. UTT's insight: most people don't need unlimited anonymity. They need normal financial privacy, the kind you already have with a bank account where your neighbor can't see your balance. The anonymity budget delivers that without becoming a money laundering tool.
How it works: When you register with UTT, you get a special coin alongside your regular payment coins. This coin isn't money. It's a meter. It starts at a cap , such as $10,000 for the month, and ticks down with every private transaction you make.
The meter coin lives in the same Merkle tree as payment coins. It's a real Pedersen commitment with its own nullifier, following the exact same cryptographic rules. There's no separate "budget system" that could be exploited differently.
Why it's a 3-in-3-out transaction: In UTT, there's no such thing as updating a number. You can't edit a coin's value. The only operation that exists is: destroy a coin, create a new one. So if your budget needs to go from $10,000 to $8,000, the system destroys the old $10,000 budget coin (publishes its nullifier) and mints a new $8,000 coin in its place:
- ›Inputs: payment coin i + payment coin j + old budget coin ($10,000)
- ›Outputs: new coin for recipient A + new coin for recipient B + new budget coin ($8,000)
The ZK circuit enforces: new_budget = old_budget - amount_sent >= 0. If you try to overdraw, the proof is mathematically invalid and the transaction is rejected before it touches the chain.
Why the budget can't be cheated: The budget coin has its own nullifier prf(index. Sk). The moment you spend it, that nullifier is permanently recorded. You can't reuse the old $10,000 coin after it's been consumed. You can't forge a higher balance without the threshold mint authority's secret key. You can't skip the budget deduction because it's a hard constraint inside the ZK circuit.
What happens at zero: When the meter hits zero, your next transaction either goes public (addresses and amounts visible) or gets submitted to threshold auditors (k-of-n key holders decrypt together, no single party can surveil). There is no third option.
Why this satisfies regulators: A money launderer trying to move $100M anonymously at $10,000/month would need 833 years. Normal traders never get close to the cap. The math separates legitimate financial privacy from large-scale illicit flows without anyone making a judgment call. This is why the Bank of Israel piloted UTT for digital shekel research. Compliance isn't a promise. It's built into the cryptography.
Cap configuration on Whop:
- ›Default retail cap: $10,000/month
- ›Whop can propose governance to raise or lower the cap
- ›Above-cap transactions automatically route to public execution or threshold audit
The IBE (Identity-Based Encryption) Mechanism
UTT uses IBE to enable sending to an identity (a phone number, an email address, a .whop name) rather than a wallet address.
How it works:
- 02A distributed IBE master key authority (committee of k-of-n nodes) holds the system's IBE master secret
- 04Any identity , such as
max.whop, has a corresponding IBE public key derived from the master key and the identity string - 06When Alice sends to
max.whop, she encrypts a note containing the new coin's(sn, v)tomax.whop's IBE public key - 08The note is posted onchain alongside the new coin commitment
- 10Max scans all new notes, attempts to decrypt each one with his IBE private key
- 12If decryption succeeds, the coin is his. He now knows
snand can compute the nullifier when spending
What this enables: Privacy-preserving payments to human-readable identities. Alice can send to max.whop without knowing Max's wallet address. Max doesn't need to share his wallet address with Alice. Neither address appears in the transaction data.
Shared IBE infrastructure with ACE: ACE (the access control system) also uses IBE for threshold decryption key derivation. The committee infrastructure that holds IBE key shares for UTT can serve ACE as well. A unified committee serving both protocols reduces infrastructure overhead.
Deployment on Aptos
Deployment Tier
UTT deploys as a Tier 3 Move module. A standard smart contract at a user-controlled address, not a consensus-layer change. This is the same tier as any DeFi protocol on Aptos.
Why Tier 3 is possible: every cryptographic primitive UTT needs is already in the Move VM from AIP-143 deployment:
- ›Groth16 BN254 verifier: native function (already live)
- ›BLS12-381 pairings: native function (already live, used for PS signatures)
- ›Ristretto255: native function (already live, used for Pedersen commitments)
- ›Table data structure: native (used for nullifier set
O(1)lookups)
What requires a governance vote:
- ›Setting the Groth16 verifying key (VK) for the UTT circuit. Done once at deployment, then immutable
- ›Setting the IBE master key parameters
- ›Configuring the Pointcheval-Sanders mint verification key
These are parameter settings, not code changes. The governance vote is a coordination mechanism, not a protocol upgrade.
The Global State Bottleneck
All UTT transactions write to a single global resource (UTTState) at @aptos_framework. This resource contains:
- ›The append-only Merkle tree frontier
- ›The nullifier set (append-only Table)
- ›The current root
- ›The budget configuration
Block-STM must serialize writes to this resource. Multiple UTT transactions in the same block queue behind each other for the UTTState write.
Estimated throughput ceiling: ~200-500 UTT transactions per second, depending on write complexity. This is acceptable for a privacy layer at Whop's initial scale. The VMware Concord BFT deployment benchmarked UTT at ~1,000 anonymous payments per second on different infrastructure. The Aptos ceiling is lower due to Block-STM serialization but sufficient for launch.
Move v2 Implementation
module aptos_framework::utt {
use aptos_std::table::{Self, Table};
use aptos_std::ristretto255::CompressedRistretto;
use aptos_std::groth16_bn254::{Self, PreparedVerifyingKey};
use aptos_std::bls12381;
struct UTTState has key {
leaves: vector<CompressedRistretto>, // append-only Merkle tree
tree_size: u64,
current_root: vector<u8>,
historical_roots: Table<vector<u8>, bool>, // any prior root valid
nullifiers: Table<vector<u8>, bool>, // append-only nullifier set
budget_map: Table<vector<u8>, u64>, // budget coin tracking
budget_cap_per_period: u64,
groth16_vk: PreparedVerifyingKey, // set once at init
mint_vk: PSVerifyingKey, // threshold PS key
}
}
The anon_transfer entry function takes:
- ›
nullifiers: vector<vector<u8>>. one per input coin being spent - ›
output_commitments: vector<CompressedRistretto>. new sealed envelopes - ›
encrypted_notes: vector<vector<u8>>. IBE-encrypted recipient notes - ›
gas_value: u64. gas paid publicly (the gas coin in the circuit) - ›
root: vector<u8>. Merkle root the proof was generated against - ›
proof: vector<u8>. The Groth16 proof
The contract: checks the root is historical, checks nullifiers aren't spent, verifies the Groth16 proof, marks nullifiers as spent, appends new commitments, updates the root, emits the event (which recipients scan for IBE decryption).
The Unlinkability Property. Formal Statement
An outside observer sees:
- ›A Merkle tree of random-looking group elements (coin commitments)
- ›A nullifier set of random-looking group elements (spent markers)
The observer cannot:
- ›Link any nullifier to any coin commitment (requires knowing
snandsk) - ›Determine the sender of any transaction (no address in the transaction data)
- ›Determine the recipient of any transaction (IBE-encrypted notes are ciphertext)
- ›Determine the amount of any transaction (values are inside Pedersen commitments)
- ›Link transactions to each other (no shared state between transactions beyond the global tree)
The only information a transaction reveals:
- ›That a transaction occurred (the Merkle tree grows)
- ›That certain coins were spent (new nullifiers appear)
- ›The gas amount (always public. Required for Aptos transaction validation)
- ›A timestamp (approximate. Block height)
Even the timestamp reveals little: because historical roots are valid, there's no way to determine when a coin was received from when it was spent.
Authors and Institutional History
| Author | Affiliation |
|---|---|
| Alin Tomescu | Head of Cryptography, Aptos Labs |
| Ittai Abraham | a16z crypto (former Founding Engineer and Cryptography Researcher at Aptos Labs) |
| Benny Pinkas | Bar-Ilan University |
| Adithya Bhat | VMware Research |
| Guy Gueta | VMware Research |
| Avishay Yanai | VMware Research / Microsoft |
Published April 2022. Presented at Stanford Blockchain Conference 2023, Yale, and a16z.
Deployments:
- ›Bank of Israel. Testing pilot for digital shekel (CBDC) privacy research
- ›VMware Concord BFT. Production integration for enterprise blockchain privacy
Status on Aptos: Unreleased as of April 2026. Alin's March 21 blog post signals it's on the roadmap. One governance vote away from deployment.
