Confidential prize-linked savings
Save. Never lose. Nobody sees.
Your deposit earns a share of the pooled yield as a prize. Your balance, your odds and whether you won stay encrypted end to end, and the draw itself stays publicly verifiable.
32 stakes per shard. 5 levels. 4,476,000 HCU.
A diagram of the walk, over 5 levels. On chain the resolved leaf is encrypted and no client can locate it. The live register is on the draw screen.
The constraint
A linear draw dies at thirty depositors.
FHEVM caps the longest chain of dependent operations in a transaction at 5,000,000 HCU. Encrypting balances and scanning them puts every depositor in that one chain, so it stops working almost immediately. Sortis descends a tree instead, which puts one short chain per level in the budget rather than one per person. Both curves describe a complete draw, not a fragment of one. The Sortis curve is swept until the transaction reverts; the linear one is its per-depositor cost multiplied out, because a scan that reverts cannot be measured past the point it stops fitting.
The two failures are not the same failure. A linear scan is one dependent chain, so at thirty depositors it is finished and no amount of splitting helps. A Sortis shard holds 32 and then a second shard opens. The dashed continuation is what one oversized shard would cost, and it is drawn only to show why the ceiling is where it is.
The Sortis curve is measured by test/Calibration.t.ts against the FHEVM mock coprocessor at commit 3f79c22, sweeping drawLot, the whole settling transaction, until it reverts rather than assuming where it will. test/HCU.t.ts sweeps the walk on its own and reports a higher ceiling; a walk is not a draw, and the number that sets capacity is the cost of the transaction that has to land. The linear curve is derived, not swept: it is 162,000 HCU per depositor multiplied out, because a scan that cannot fit cannot be measured past the point it reverts. Two real draws on Sepolia at the deployed height 5 each reported 4,476,000 HCU against the mock’s 4,476,000, so the two agree exactly. Depth is the budget that binds, not global work, so this ceiling cannot be raised by splitting a draw across transactions. Run npm test to reproduce both tables.
The sequence
How a draw works.
Four steps, in this order. The gap between the second and the third is the security argument, not an implementation detail.
- 01
Commit
Money enters as confidential cUSDT. Weight accrues from how much sat in the pool and for how long, so a deposit made a moment before a draw carries nothing.
commit() 920,000 HCU - 02
The root is snapshotted
Opening a draw publishes the register root and the block. No randomness exists yet, anywhere, so the operator has to commit to the tree before learning anything about who it favours.
openDraw() no randomness yet - 03
The lot is drawn
In a later block, the chain itself produces the lot with FHE.randEuint64. No oracle and no VRF. If the register moved since the snapshot, the root handle changed and the draw is void.
drawLot() one block later - 04
One slot resolves
The walk descends the shard, one encrypted comparison per level, halving what is left each time. It lands on a leaf and the index stays encrypted. Only the drawn address can decrypt what it holds.
32 stakes, 4,476,000 HCU
The threat model
What stays private, and what does not.
The right-hand column is deliberate. Public verifiability is the point of the design, and a draw nobody can check is not worth having.
The limitation
The wrap leak, addressed.
Money arrives as public USDT. If wrapping it into confidential cUSDT happened in the same transaction as the deposit, the amount would be readable one call before it became private, and the encryption would buy nothing.
The queue is deployed and the app does not use it yet. Committing goes straight to the pool, which is the legible path for anyone trying this and the less private one: your wrap and your stake are one transaction apart and the amount is readable in between.
What the queue buys, once deposits route through it, is a one-to-many link instead of a one-to-one: an epoch closes, the whole queue is wrapped and credited together, and a public sender maps to any stake settled in that window rather than to one. Four hours on Sepolia, longer on mainnet. The limits below apply to that design and are the reason it is described here rather than claimed as a privacy guarantee.
Batching raises the cost of linkage. It does not eliminate it. Anything stronger needs equal denominations and a real mixer, which is a different protocol.