Proof of age, from one txid.
Every shielded Ironwood spend names an earlier snapshot of the shielded pool, and consensus checks that its coins are inside it. Vintage lets a sender pick a snapshot from before a dated event, and lets anyone check it in about a second. No keys, no blocklist, no new cryptography.
One hack makes every shielded deposit suspect.
Exit rails (exchanges, bridges, swap services) see a shielded deposit and can't know whether it carries stolen coins. The only answers on offer today cost the user their privacy: trace the coins to their origin, find someone to vouch, or hand over a viewing key that shows their whole history. The forum author volunteered addresses, transaction hashes and swap history, and the funds were still held. Most honest holders hold coins that are simply older than the hack. Vintage lets them prove exactly that, and nothing more.
The chain already dates every shielded spend.
Real mainnet data: deposit da9017e0…e3a7, anchored at block 3,525,696, checked against the event at block 3,525,709. Gold marks what the chain proved.
After every block, the shielded pool has a root: one 32-byte fingerprint of every shielded coin that exists at that moment.
Every Ironwood spend names one of those roots, its anchor, and proves its coins are in it. Consensus rejects any anchor that isn't a real earlier root.
If the anchor matches none of the roots from the event to the tip, it must be from before the event. So every coin it spends was already there.
No blocklist. No new cryptography. A rule Zcash already enforces on every spend.
Three commands.
Check what you can prove. vintage status lists, for each event, how much of your shielded ZEC is older than it.
Send with an older anchor. vintage send --before <event> builds the deposit on a snapshot from just before the event. Your wallet can only spend coins that are in that snapshot, so newer coins are refused, not hidden. Always run it with --dry-run first.
Share the txid. The exchange, or anyone, checks it in the verifier or with vintage verify.
The cost: an old anchor is visible on chain, so the deposit shows you chose to prove its age. That's fine where the recipient already knows you, such as an exchange. Wallets should never do it by default.
$ vintage status --wallet ~/wallet --sync $ vintage send --wallet ~/wallet -i key.txt \ --to <deposit address> --value 300000 \ --before bitget-2026-09 --dry-run $ vintage verify <txid> --before bitget-2026-09
One request before you hold a deposit.
Vintage gives an exit rail a fast, privacy-preserving way to clear honest shielded deposits after an incident: a consensus-backed answer in about a second, with no keys exchanged, no lists to maintain, and nothing for you to store.
What to do with each answer
| Answer | What it means | What to do |
|---|---|---|
| PASS | Every shielded coin the deposit spends existed before the event, so none of it can be the event's coins. | Skip review related to that event. Keep your usual checks for everything else. |
| FAIL | The anchor is a pool state from the event or later. Nearly every ordinary deposit looks like this. | No information. Apply your normal process. Never treat it as a flag. |
| NOT_PROVABLE | The deposit spends transparent, Sapling or Orchard coins. Vintage only reads Ironwood. | No information. Apply your normal process. |
| provisional: true | The deposit isn't mined yet; until it is, the same txid could be mined with a different anchor. | Act only after confirmation, which you wait for anyway. |
The request
$ curl 'https://api.usevintage.site/verify/da9017e0fca0d8bd87def8e3faa7956067f2bd2234cfee91ca7ad4e67095e3a7?before=vintage-demo-2026-10'
const res = await fetch(
`https://api.usevintage.site/verify/${txid}?before=bitget-2026-09`);
const v = await res.json();
if (res.ok && v.verdict === "PASS" && !v.provisional) {
skipEventReview(deposit); // the coins predate the event
} else {
normalProcess(deposit); // FAIL is not a flag
}
import requests
r = requests.get(f"https://api.usevintage.site/verify/{txid}",
params={"before": "bitget-2026-09"}, timeout=30)
v = r.json()
if r.ok and v["verdict"] == "PASS" and not v["provisional"]:
skip_event_review(deposit) # the coins predate the event
else:
normal_process(deposit) # FAIL is not a flag
{
"verdict": "PASS",
"summary": "These coins existed before block 3525709 (…).",
"provisional": false,
"mined_height": 3527813,
"before": { "height": 3525709, "event_type": "custom",
"requested": "vintage-demo-2026-10" },
"anchor": "855ccc99…81d98502",
"evidence": { "distinct_roots": 1303, "blocks_covered": 2105 },
"elapsed_secs": 0.056
}Why rails use it
Run it yourself
For production, run vintage-api next to your own Zcash node: one binary, about 16 MB of memory, any core count. Then nobody, including us, learns which deposits you check, and the answer depends only on your node. Setup, flags and the full response reference are in the integration guide and API reference.
Make it opt-in, never a gate: a pass is something an honest user offers to get through faster, not a new requirement for every depositor.
Coins received after the event.
Payer side: nothing revealed
Ask the person paying you for a Vintage payment: vintage request makes a ZIP-321 payment link whose memo says VINTAGE before=<block>. Their payment then proves its own coins predate the event.
Receipt account: one viewing key, optional
Receive into a fresh single-use account and deposit from it. The rail checks the link with that account's viewing key: it sees the funding payment, the amounts, the receipt address and the deposit. Nothing from your other accounts, and the key cannot spend. Spend at least two receipt notes in the deposit, so the wallet adds no dummy input.
What it proves, and what it doesn't.
- Every Ironwood coin the deposit spends existed before the event block.
- It holds by consensus. Nobody can fake an older anchor.
- It covers every later event at once.
- Date coins received after the event, without the payer side or a receipt account.
- Cover transparent, Sapling or Orchard inputs.
- Say who sent the coins or how much.
- Hide that you chose to prove it: an old anchor is visible on chain.
- Clear coins of earlier events: check against each date you care about.
The full analysis is in the threat model.
Questions.
Can a hacker fake a pass?
No. An anchor must be a real earlier root, and the spend proof must show every coin is in it. Coins that arrived after the event aren't in any earlier root, so no valid transaction can spend them under it. Our wallet test shows it: asked to include a coin from after the event, it refused to build the transaction.
Does Vintage keep a list of bad coins or bad people?
No. The registry only labels block heights ("Bitget hack, block 3,501,240"), and anyone can check against any height of their own. Nothing is ever marked bad.
What does the verifier learn about me?
The txid you check, while it answers. It keeps no logs; the only record is an anonymous daily count of checks. To involve nobody, run vintage verify yourself.
Why does a normal deposit fail?
Wallets anchor to a snapshot a few blocks old: in 119 recent mainnet transactions we checked, 1 to 11 blocks. That is after any event worth checking, so ordinary deposits say nothing either way. That is why a fail is never a flag.
Has it run on mainnet?
Yes. On 23 Oct 2026 a Vintage payment was the deposit into a NEAR Intents swap and passed; a receipt-account deposit passed the link check; both swaps delivered USDC on Base. Every transaction is in the simulation and the repository.