Security and Privacy
The proving keys of the C4 and C5 instances were generated with public TEST entropy, so anyone could in principle rebuild the setup secrets and forge proofs. Neither the contracts, the circuits nor the wallet library have been independently audited. Do not use them for funds of value.
What the pool hides
- The owner of every note.
- The amounts of assignments inside the pool.
- Which note a transaction spends. A nullifier cannot be linked to its commitment without the owner's key.
- The recipients of an assignment.
- The link between the receiving addresses of one wallet, since each address has independent keys.
What stays public
| Item | What an observer sees |
|---|---|
| Deposits | The amount and the funding coin, with its address and history |
| Withdrawals | The amount and the destination address. A withdrawal always takes a whole note |
| Fees | The sponsor coin and its address. The change returns to the same script, so every operation paid from that address is linked |
| Form | The verification key reveals the operation and, for assignments, how many notes were created. A T1 shows that a whole note went to one recipient without change |
| Reserve | The total XNA in the pool after every transaction |
| Timing | The block and the order of every operation |
| Network metadata | The RPC node sees the client's IP address, the coins it checks and the transactions it publishes. The server of the proving files sees which form's files are downloaded, and when |
Linking risks
Zero-knowledge proofs hide the private ledger, not the public facts around it. With few users, amounts and timing alone can link operations. If the only activity is deposit 1,000 → assign 400 to Bob → Bob withdraws 400, an observer sees 1,000 XNA enter, an assignment, and 400 XNA leave to Bob's address, and can guess the relationship.
Ways to reduce linking, none of which guarantees anonymity:
- Pay fees from coins that are not linked to the deposit or withdrawal addresses, and do not reuse one sponsor address for unrelated operations.
- Avoid withdrawing exactly the amount that was deposited, and avoid unique amounts.
- Leave time between a deposit and later operations.
- Use your own node, so that no third-party RPC operator sees your queries.
Keeping funds in the pool hides who owns them now, but the original deposit stays visible. A small testnet pool has a small anonymity set even when the cryptography is correct.
Trust assumptions
| Component | What the wallet checks | What it trusts |
|---|---|---|
| Node (RPC) | The genesis, that every transition matches the pinned contract and that every block used is still in the active chain | That the node validates consensus, including the proofs of past transactions, and does not hide confirmed transactions. A lying node can show a stale or incomplete view, and transactions built on it fail at publication |
| Manifest | Leaves, control blocks, verification keys, guard, domain and context. The commitment must match an independent pin | That the pinned contract actually implements safe custody |
| Proving files | Size and SHA-256 of every file | Nothing else: a wrong file is rejected. The server can still refuse to serve them |
| snarkjs | Every proof is verified locally before use | The injected module runs inside the worker and sees the private witness |
| Application code | Nothing | The worker keeps secrets away from page code, but it cannot stop malicious code in the same application, such as a compromised dependency |
| Randomness | Fails if crypto.getRandomValues is missing | The platform's secure random generator for rho, ephemeral keys and nonces |
Where secrets live
| Data | Lives in | Leaves the worker |
|---|---|---|
| Account key, spend secrets, view seeds, nullifier keys | Worker | Never |
| Note plaintexts and circuit witnesses | Worker | Only inside the encrypted checkpoint |
| Scan checkpoint | Worker and page | Yes, encrypted and authenticated |
| Balance, own commitments and amounts, receiving addresses | Worker and page | Yes |
| Proof and unsigned transaction | Worker and page | Yes. They become public anyway |
The page learns the balance and which commitments belong to the wallet. That link is private and must never be sent to analytics or a server. RPC requests never contain wallet words, passphrases, spend secrets, note plaintexts or witnesses. Wiping keys in JavaScript is best effort: the engine may keep copies that cannot be erased.
Not implemented yet
- Selective disclosure. There are no receipts or viewing capabilities that prove where a payment came from. Exporting spend secrets is not a substitute, and a withdrawal alone does not prove a particular source.
- Note merging. A transaction consumes one note.
- Asset pools in wallets. Ordinary assets were tested at node level. Restricted assets, qualifiers and DePIN permissions keep their own consensus rules, and a privacy proof does not override them.
- Forced exit. The current eight-leaf contract has no separate exit or congestion protocol.
Before funds of value
At least these parts need independent review:
- note ownership and nullifier constraints in the circuits;
- bounded amounts and conservation of the reserve;
- the HPKE record construction and data availability;
- the TXHASH anchor and the Script introspection that checks it;
- the commitments from verification keys to contract leaves;
- wallet secret storage, exact amount parsing and recovery after reorganizations;
- a trusted setup ceremony with independent contributions to replace the TEST proving keys.
Replacing verification keys changes the contract leaves and the pool commitment. That is a new deployment, with new pins and a migration plan, not a silent parameter update.