Proofs
Show one reader exactly what they need, and nothing else. Every proof names its reader and expires.
- Kinds
- Payment, funds, volume
- Reader
- One NEAR account
- Checked
- Browser and pool contract
What a proof is
A zero-knowledge proof about your notes, made on your device. It shows one statement, such as “I paid at least this much of this token”, without showing the notes behind it. Hushd binds each proof to a context: the reader's NEAR account, an expiry, the network and the pool. The context is one of the proof's public inputs, so changing the reader or the expiry after proving makes it fail.
The four kinds
| In the app | Circuit | What it shows |
|---|---|---|
| Exact payment | receipt | You made a payment, or a desk deal leg, of exactly this amount of a token |
| Paid at least | receipt | The same payment reached at least an amount you choose; the exact amount stays hidden |
| Funds held | funds | You hold at least a threshold of a token, in one to four unspent notes |
| OTC volume | volume | You delivered exactly this total of a token across one to sixteen desk deal legs, and how many |
One named reader
The reader is a NEAR account, such as auditor.testnet. A proof can be copied, like any file, and anyone holding it can check it: the reader is who it was made for, not a lock. What it cannot do is pass for someone else's: the reader is bound into the proof, the pool refuses it for any other reader, and the checker at /verify asks who you are and flags a proof made for another account rather than quietly accepting it.
Expiry
You choose how long a proof is valid: one day, seven or thirty in the app, up to ninety over MCP. After that, every checker refuses it, the pool included, which reads the expiry against block time. A funds proof also stops holding as soon as you spend a note behind it: it shows what you hold now, not what you once held.
Checking a proof
A proof is shared as one string that starts with hushdproof1, usually as a link: https://hushd.net/verify#p=hushdproof1…. Everything after the # stays in the browser. The checker then:
- checks the proof was made for this network and this pool,
- rebuilds the context from the reader and expiry, and compares it with the proof's,
- refuses it if it has expired, or names a different reader than you,
- checks the Groth16 proof against the circuit's verifying key, in your browser, and
- asks the pool contract's
verify_disclosureview to check it again against the chain, with the proof's network, reader and expiry.
The pool checks what only the chain knows. For every proof, that its context binds exactly that network, this pool, that reader and that expiry, and that the expiry has not passed on chain. For a payment, that the note you paid with was really spent into the note the proof names, as the payment and not as change. For funds, that the root is one the pool has had and no note behind the proof has been spent since. For volume, that every leg was a desk settlement, and none is counted twice. Any problem is listed in plain words.
What the proofs do not say: a payment proof shows a payment was made, not to whom unless you reveal the payee (paying yourself is a payment too), and a volume proof counts your desk settlements, including any made with a maker you run yourself.
What a proof does not show
- Your other notes, your balance, or your NEAR account.
- A funds proof shows the threshold, never the balance above it.
- A payment proof shows who was paid only if you choose to show the payee's receive key.
One thing a reader does learn: for a payment that settled a desk deal, and for every leg of a volume proof, the pool reports which deal it was. Deal records are public, so the reader can see when those deals happened and how they ended.
Making one
- In the app: Prove. Pick what to show, the reader and the expiry, then share the link.
- From an agent: the
hushd_proveMCP tool, withwhatset topayment,payment_at_least,fundsorvolume. See agents. - In code:
proveReceipt,proveFundsandproveVolume, thenencodeProof. Check one withverifyProof. See the SDK.
import { encodeProof, proveFunds, TESTNET } from "@hushd/sdk";
// At least 1,000 USDC, for auditor.testnet only, valid for 7 days.
const proof = await proveFunds(artifacts, identity, {
net: TESTNET,
reader: "auditor.testnet",
expiresAtMs: Date.now() + 7 * 86_400_000,
}, wallet.merkleTree, wallet.spendable("usdc.hushd.testnet").slice(0, 4), 1_000_000_000n);
const link = `https://hushd.net/verify#p=${encodeProof(proof)}`;