Your Zcash history has tells.
Shielded transactions hide who paid whom and how much. How you enter and leave the shielded pool can still link your addresses together. Tells reads your wallet's history through its viewing key, on your own machine, and shows what an outside observer can connect. Run it before a withdrawal and it tells you a safer way to make it.
- 31.5%of the coins sent into Zcash's shielded pool could be paired with a later exit by amount alone, in the first study of the chain (Quesnelle, 2017).
- 96%of those round trips left the pool within two hours of entering it.
- 0 bytesof your viewing key leave your machine. The checkup runs locally, in the browser or the command line.
Checkup
Each arrow is a public crossing: gold arrows enter the shielded pool, the others leave it. Arcs are links an observer can draw between them.
Before you withdraw
Enter the withdrawal you are about to make from the wallet above. Tells checks it against the history and proposes safer ways to make it, each one re-checked.
Try the amount that just went into the pool on the sample wallet, and the same amount a week later.
Check your own wallet
A unified full viewing key can see your history but cannot spend. Tells never asks for a seed phrase, and nothing is uploaded.
Export the viewing key
In your wallet, find the unified full viewing key (starts with uview1) and the wallet's birthday height. Keep the key private: it reveals your history.
Scan locally
With Node 22 or newer, run:
npx github:luoy16002-svg/tells scan \ --ufvk uview1... --birthday 2726400 \ --out history.json
It syncs a view-only wallet with zcash-devtool: yours if it is installed, otherwise a checksummed build made by this project's public workflow.
Open the result here
The terminal prints the checkup. Drop history.json below to see the timeline and plan withdrawals. It is read by this page and never leaves your browser.
Drop history.json here, or .
How it works
Only three things cross the pool boundary in public: an amount, a block time and a transparent address. Every rule below uses nothing else, so each finding is something anyone reading the chain could also find.
Round trip
An exit within a fee of an earlier entry pairs the two. Severity rises with speed (under two hours is critical) and with how unusual the amount is.
Sum match
Splitting the entry does not help when the exit equals two or three recent entries added together.
Quick exit
Leaving the pool within a day of entering narrows the crowd even when the amounts differ.
Fingerprint amounts
Amounts with four or more decimals are close to unique. Round amounts, and round amounts minus a fee, are shared by many users.
Address reuse
One transparent address on several crossings ties them together, whatever happened inside the pool.
Pool migrations and transparent payments
Moves between Sapling, Orchard and Ironwood publish their amount. Transparent-to-transparent payments publish everything.
Crowd size
A link only points at you if few others did the same. The scan reads the compact blocks between each entry and exit and counts look-alike exits; more look-alikes lower the severity.
Round-trip and timing rules follow J. Quesnelle, On the linkability of Zcash transactions (2017) and G. Kappos, H. Yousaf, M. Maller, S. Meiklejohn, An Empirical Analysis of Anonymity in Zcash (USENIX Security 2018).
For wallet builders
The rules are a dependency-free TypeScript package. A wallet can call it with the history it already holds, right before it builds a transaction, and warn the user while there is still time to change the amount or wait.
The same core powers this page and the command line, and runs in a browser, Node or a mobile JavaScript engine.
import { preflight } from '@tells/core';
const check = preflight(history, {
amount: 54_311_000, // zats to a t-address
time: Date.now() / 1000,
address: recipient,
});
if (check.verdict === 'risky') {
showWarning(check.findings[0].title, check.alternatives);
}