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.
The lot descends 5 levels. Every slot stays encrypted.
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 are a complete draw, not a fragment of one.
Measured by test/HCU.t.ts against the FHEVM mock coprocessor at commit c234b6a, sweeping register sizes until a draw reverts rather than assuming where it will. A real draw on Sepolia at height 2 reported 2,199,000 HCU against the mock’s 2,199,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 every number here.
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.
Deposits queue instead. At the end of each epoch the whole queue is wrapped and credited together, so the on-chain link between a public sender and a confidential stake is one-to-many across everyone who queued in that window. Four hours on Sepolia, longer on mainnet.
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.