This page leads with what leaks, because that is the half a reader cannot check by looking at the app.
Public, and deliberately so
- The pot. The prize is a uint64 in the clear. Encrypting it would remove the public verifiability the design exists to provide, and it describes the pool rather than any position in it.
- The register roots, the block they were committed at, and the total weight the lot was reduced against. The total is published because reducing into a uniform range needs a plaintext bound, and it is verified against the KMS with FHE.checkSignatures rather than trusted.
- That an address interacted with the pool, when, and in which direction. commit pulls and release pushes, and those are different external calls.
- Which leaf index moved. _update writes a visible path of storage slots, so leaf ownership is on chain whatever the frontend does. The register screen shows this rather than pretending otherwise.
- How many leaves are in use, and the shard's capacity.
Encrypted, and never decrypted by the protocol
- Your deposit amount, encrypted in your browser before it is sent.
- Your balance and your weight line, both intercept and slope, at every node of both trees. euint64 handles, decryptable by you alone through EIP-712 user decryption, in your browser.
- The lot, produced by FHE.randEuint64.
- Whether you won. The resolved leaf is an encrypted index and no ACL grant is ever issued on it, so no client can learn it, the winner's own included.
- What you were paid. A losing claim transfers an encrypted zero and costs the same gas, the same sequential depth and the same global HCU as a winning one.
What an observer can still infer
That an address holds a position, roughly when it was opened and last changed, and therefore an upper bound on how long it has been accruing weight. Not its size, not its weight, and not whether it won. Hiding the existence of a position as well would mean hiding the storage writes, which is a different protocol than this one.
Why an over-withdrawal does not revert
Reverting on an encrypted comparison publishes the comparison. A release(X) that reverts proves the caller's balance is below X, and one that succeeds proves it is at or above X. An attacker who can distinguish the two binary-searches any balance in about 64 transactions, and the encryption has bought nothing.
So SortisPool.release uses FHESafeMath.tryDecrease, which returns an encrypted success flag and leaves the balance untouched on failure. A refused release transfers zero, moves the weight line by zero, emits the same event, and matches an honoured one on gas, depth and global HCU. The app says so before you send, because otherwise you watch a successful transaction change none of your numbers and conclude it is broken.
tryDecrease and not trySub: both return a flag, but trySub returns zero on failure, which would wipe a stake the first time somebody fat-fingered a release.
The wrap leak, stated plainly
Money arrives as public USDT. Wrapping it in the same transaction as the deposit would make the amount readable one call before it became private, so deposits can queue and settle in epoch batches instead.
Batching raises the cost of linkage; it does not eliminate it. A depositor alone in an epoch has no anonymity set, amounts are not mixed so sizes can be matched back, and each settlement emits an event naming the stake owner.
What verification cannot do
The descent cannot be replayed by a client. Every node in the register is a ciphertext nobody holds a grant on, and publishing per-node decryptions so a verifier could re-run the comparisons would hand everyone the register, which is the one thing the protocol is built to withhold.
So verification checks the public chain of facts: that the root was committed before the lot existed, that the roots did not move in between, that the denominator carries a KMS signature, and that the draw settled. It does not re-derive the winner, and the Verify screen names that limit rather than implying more than it checks.