Security
Architecture and trust assumptions
What the protocol enforces, what it relies on, and what it does not protect against.
Deployment
Sable is deployed on Ethereum Sepolia. Every contract is verifiable on the block explorer.
Deployed Thu, 03 Sep 2026 13:12:50 GMT
Zama Protocol contracts
Sable does not implement encryption itself. It builds on the Zama Protocol’s coprocessor, key management service and access control list, all deployed by Zama on Ethereum Sepolia.
Decides which address may decrypt which ciphertext.
Runs homomorphic operations requested by contracts.
Verifies key-management signatures for decryption.
Checks the zero-knowledge proof attached to every encrypted input.
Relayer: https://relayer.testnet.zama.org
Contract architecture
The vault, round machine and prize engine are written as separate source files but compile into a single deployed contract. That is a deliberate departure from the usual one-concern-one-contract instinct, and the reason is specific to FHE.
Ciphertexts do not cross contract boundaries for free. Every handle passed between contracts needs an explicit transient permission grant on the way out and a check on the way back, on every call. Splitting the prize engine from the vault would mean granting it transient access to every participant’s balance and mode on every settlement batch — a strictly larger attack surface, more homomorphic work, and considerably more to audit, in exchange for a tidier diagram.
The contracts that genuinely are separate are the ones with separate trust boundaries: the ERC-7984 confidential asset, and the yield adapter.
Access control over ciphertext
The critical detail in any FHEVM application is that permissions attach to handles, not to storage slots. Every homomorphic operation produces a new handle, so permissions must be re-granted after every mutation — a missed grant does not fail immediately, it fails on the account’s next interaction.
Sable routes every persistent ciphertext write through one helper:
function _persist(euint64 value, address account) internal {
FHE.allowThis(value); // the contract can keep computing on it
FHE.allow(value, account); // only the owner can decrypt it
}Administrators are never granted permission over another account’s handle, which is what makes “operators cannot read your balance” a structural property rather than a policy.
Enforced invariants
Each of these is covered by tests in the repository:
- A saver's principal can never become another saver's prize. The prize pool is fed exclusively from a select over the encrypted mode applied to accrued yield.
- Only yield attributed to Lucky participation reaches the prize pool.
- A plaintext mode is never revealed by contract state, calldata shape, event log or gas cost.
- A plaintext balance is never publicly revealed. Only aggregates are made publicly decryptable.
- Settlement never distributes more than the pool holds — enforced by validation at configuration time and floor division at every step.
- A completed round cannot execute again. Every state transition is guarded and invalid ones revert.
- A reward cannot be credited twice; claiming clears the handle atomically.
- Unauthorised accounts cannot decrypt another account's values through any application flow.
Overflow, and why it needs care here
Encrypted arithmetic does not behave like ordinary Solidity. There are no overflow checks on ciphertext: an euint64 that exceeds its range wraps silently, with no revert and no on-chain symptom. A corrupted balance would simply exist.
Sable prevents this structurally rather than hoping for it:
- A per-account balance ceiling is enforced homomorphically at deposit, by clamping the request against remaining headroom before any tokens move.
- The yield index has a hard ceiling, so balance × index-delta cannot approach 2^63.
- Round duration is capped, bounding balance × elapsed-minutes in the weight calculation.
- Ticket allocation is capped per participant so the sum can never exceed the ticket domain.
Batching and denial of service
Homomorphic operations are metered. The protocol enforces a ceiling of 20,000,000 compute units per transaction and 5,000,000 of sequential depth — exceeding either reverts.
Settling one participant against the full fourteen-point tier ladder measures at roughly 7.56 million units, so at most two accounts fit in a single transaction. Every phase of a round is therefore cursor-based and resumable, sized from measurements rather than assumptions. An oversized batch reverts without advancing the cursor, so the round is simply retried at a smaller size.
Trust assumptions
- Zama's coprocessor and key management service perform decryption correctly and do not leak plaintexts. Sable inherits this assumption wholesale.
- The relayer is trusted for availability, not for confidentiality — it never sees plaintext values.
- Administrators can configure rounds and pause deposits. They cannot read, choose or seize anything.
- The contracts are not upgradeable. For a protocol custodying savings with a small immutable rule set, a proxy would add an admin capability more dangerous than the bugs it could fix.
Sable has not undergone an external security audit. It is a testnet protocol with a test asset that carries no value. Treat it as demonstration software.
Reporting an issue
If you find a vulnerability, please report it privately rather than opening a public issue. The technical documentation covers the FHE-specific security review in more detail, including the event and calldata confidentiality audit.
