TLOS is a compute-and-compare (point-function) obfuscator using a five-layer security model with configurable PoW throttling:
- Topology layer - structural mixing (heuristic)
- LWE layer - control function hiding via standard LWE with Gaussian noise (σ=25, n=384, ~2^112 PQ)
- Wire binding layer - full-rank linear hash for inter-gate consistency (algebraic binding)
- Planted LWE puzzle - one-time q^128 floor for recovering planted solution from (A,b) without guessing (8.6M gas)
- Hash-PoW (default; configurable) - hashcash-style work bound to commit-time randomness for on-chain throttling
What TLOS is: Secret verification with a one-time puzzle floor (not a per-guess bound for low-entropy inputs), plus optional multi-bit payloads and configurable online throttling.
What TLOS is NOT: General circuit obfuscation or complex predicates on public data.
Security relies on multiple layers working together:
| Layer | Hides | From |
|---|---|---|
| Topology | μ (control function bits) | Structural analysis |
| LWE | s (decryption key) | Cryptanalytic attacks |
Critical insight: The ~2^112 estimate assumes μ cannot be predicted from circuit structure. If an attacker predicts μ for n=384 gates via structural analysis, they can recover s via Gaussian elimination in O(n³).
TLOS circuits compute point functions: C(x) = 1 iff x = secret
- No recognizable gadgets: There are no adders, comparators, or hash rounds
- Reversible gates ≠ standard logic: Each gate computes
state[a] ^= cf(state[c1], state[c2]) - Random structure: Topology layer uses non-pow2 distances, uniform wire usage
The decryption key s = H(input || puzzleSolutionHash) is derived at evaluation time.
Why tracing doesn't help:
Attacker traces with wrong input x':
s(x') = H(x') // Wrong key
diff = b - <a, s(x')>
= <a, s_correct - s(x')> + e + μ*(q/2) // s_correct ≠ s(x')
= random value // Reveals nothing about μ
Traces are visible but useless without the correct input.
The EVM is a white-box environment - we do NOT rely on hiding intermediate values. We rely on those values being garbage without the correct input key.
bytes32 commitment = keccak256(secret);Use keccak when:
- Secret is random 256-bit (brute-force is 2^256)
- No multi-bit payload needed beyond TRUE/FALSE
- Gas efficiency is critical
Use TLOS when:
- Low-entropy secrets: passwords, phrases, small numeric ranges
- Multi-bit payloads: GPS coords, wallet seeds, URLs fused with verification
- No salt UX: human must remember only the phrase
- Infrequent checks: vault unlock, puzzle solve, rare admin action
| Secret Type | Keccak Attack | TLOS Attack (offline) |
|---|---|---|
| Random 256-bit | 2^256 hashes | min(2^256, 2^112) |
| Password "hunter2" | Milliseconds | Milliseconds (GPU) |
| Range 0-100K | 0.1 seconds | 17 ms (GPU) |
| 4-word phrase | Seconds | Seconds (GPU) |
Critical limitation: Layer 4 provides only ~0.17 µs per-guess overhead on GPU. For low-entropy secrets, dictionary attacks complete in seconds to minutes. The "q^128 floor" only applies to recovering the planted solution s from (A,b) without guessing the secret. PoW throttles on-chain attempts only; it does not affect offline enumeration.
TLOS implements compute-and-compare semantics:
P(x) = payload if x == secret
garbage otherwise
The key derivation s = H(input) means:
- Only
input == secretproduces the correct decryption key - For any other input, control functions decrypt to garbage
- The circuit cannot evaluate correctly on multiple distinct inputs
Cannot implement: price < threshold where threshold is hidden but arbitrary prices work.
Can implement: "Is your input equal to my hidden secret?" with optional payload reveal.
Wire binding (inspired by SEH from Ma-Dai-Shi 2025) provides inter-gate wire binding to prevent mix-and-match attacks. Note: Our implementation uses a full-rank linear map providing algebraic binding, not cryptographic hiding.
Layer 4 adds a one-time q^128 brute-force floor for recovering the planted solution s directly from public (A, b) without guessing the secret.
Critical limitation: For dictionary attacks on low-entropy secrets, the attacker enumerates candidates, derives s = H("planted" || secret), and verifies offline. This bypasses the puzzle entirely.
| Parameter | Value |
|---|---|
| Secret dimension n | 128 |
| Samples m | 192 |
| Modulus q | 2039 |
| Secret distribution | Uniform mod q |
| Error range | {-2,-1,0,1,2} |
| Threshold | 800 |
| Search space | q^128 (brute-force; lattice estimator ~2^58) |
| Verification gas | 8.6M (14% of 60M block) |
| Metric | Rate | Per-guess cost |
|---|---|---|
| GPU keccak (estimated) | 222M/sec | 0.005 µs |
| GPU matrix verification | 5.8M/sec | 0.17 µs |
| Effective attack rate | 5.8M/sec | 0.17 µs |
| Dictionary Size | Time (1 GPU) | Time (100 GPUs) |
|---|---|---|
| 2^20 (1M passwords) | 181 ms | 2 ms |
| 2^30 (1B entries) | 3.1 min | 2 sec |
| 2^40 (1T entries) | 2.2 days | 32 min |
Attack cost formula:
AttackCost = |Dictionary| × 0.17µs (GPU)
AttackCost = |Dictionary| × 75µs (CPU)
GPU benchmarks show larger matrices are faster due to better parallelization:
| Config | Est. Gas | GPU Rate | 2^20 time |
|---|---|---|---|
| m=192, n=128 (current) | 8.6M | 3.5M/sec | 300ms |
| m=384, n=128 | 17.2M | 29.4M/sec | 36ms |
| m=768, n=128 | 34.4M | 15.3M/sec | 68ms |
The current m=192 is near a local minimum for attacker throughput.
- At deployment, Alice:
- Computes
plantedSecret = H("planted", secret)→ uniform mod-q (u16, q=2039) - Generates puzzle matrix A from random
puzzleSeed - Computes
b = A * plantedSecret + e(small noise e) - Stores
(puzzleSeed, b)in contract - NOT plantedSecret
- Computes
- Solver (Bob, who knows secret) computes
plantedSecret = H("planted", secret)directly - Attacker has two options:
- Solve LWE directly: Given (A, b), find s ∈ Z_q^128 → q^128 brute-force
- Dictionary attack: Enumerate candidate secrets, derive s, verify → |Dictionary| × 0.17µs
- Puzzle solution hash is combined with input to derive TLOS key:
s_tlos = H(input || H(puzzle_solution))
Key insight: The puzzle is ONE per contract. Recovering the planted solution from (A,b) without knowing the secret costs q^128 work. But for low-entropy secrets, dictionary attacks bypass this entirely.
function revealWithPuzzle(bytes32 input, bytes32[8] calldata puzzleSolution, uint64 powNonce) external {
// PoW (default; disabled if difficulty == 0)
bytes32 sHashCandidate = keccak256(abi.encodePacked(puzzleSolution));
require(_verifyPow(input, sHashCandidate, commitRandao, powNonce), "Invalid PoW");
// Verify puzzle solution
(bool puzzleValid, bytes32 sHash, ) = _verifyPuzzle(puzzleSolution);
require(puzzleValid, "Invalid puzzle solution");
// TLOS secret derived from BOTH input AND puzzle solution
(bool circuitValid, ) = _evaluate(input, sHash);
require(circuitValid, "Invalid circuit output");
// Claim reward...
}TLOS uses standard LWE with Gaussian noise (σ=25) for control function hiding:
| Property | Value |
|---|---|
| LWE dimension | n=384 |
| Modulus | q=65521 |
| Gaussian noise | σ=25 |
| Matrix size | 64×64 (full-rank, trivial kernel) |
| Security basis | Standard LWE hardness |
| Post-quantum security | ~2^112 (lattice estimator) |
| Gas cost | 3,697,739-17,176,416 for 64-640 gates (checkWithPuzzle) |
| Role | Primary CF hiding layer |
The wire binding uses a full-rank 64×64 matrix-vector product modulo q:
H(x) = A * x mod q
Where:
- A is a 64×64 matrix derived from
circuitSeedandgateIdx - x is the wire state (64 bits)
- Output is 64 × u16 packed into 1024 bits (4 × uint256)
An earlier design used an 8×64 matrix, which has a 56-dimensional nullspace—many inputs could produce the same output. A reviewer correctly noted this undermines binding. With a full-rank 64×64 matrix, the kernel is trivial (only the zero vector), so any two distinct 64-bit inputs produce distinct outputs—this provides deterministic binding, not collision resistance in the hash-function sense (the map is publicly invertible).
Wire binding is updated after every 128 gates:
uint256 combined = acc[0] ^ acc[1] ^ acc[2] ^ acc[3] ^ wires;
acc = _wireBindingHash(combined, batchEnd);This provides inter-gate binding while maintaining gas efficiency.
The _wireBindingHash function uses a PRG optimization: instead of calling keccak once per matrix coefficient (4096 calls per update), we derive 16 coefficients from each keccak output (320 calls per update). This reduces overhead by ~13×.
Design choice: Wire binding at 128-gate batch granularity, not per-gate.
Security implications:
- Per-gate binding would bind every individual gate transition (closest to Ma-Dai-Shi ideal)
- With 128-gate batches, we bind the aggregate effect of each batch
- An attacker can only "mix-and-match" within a batch without being caught
- Across batch boundaries, all execution is bound by the chain
Gas cost:
- Full-rank 64×64 wire binding with n=384 LWE costs 3,697,739-17,176,416 gas for 64-640 gates (6-28% of 60M block)
- 5 binding updates for 640 gates
- PRG optimization: 320 keccak calls per update (vs 4096 naive)
The five-layer security model:
| Component | Security Basis | Post-Quantum | Level |
|---|---|---|---|
| CF hiding (Layer 2) | Standard LWE with Gaussian noise (n=384, σ=25) | Yes | ~2^112 |
| Wire binding (Layer 3) | Full-rank linear binding | Yes | Algebraic binding |
| Planted puzzle (Layer 4) | Uniform mod-q LWE (n=128) | Yes | q^128 (planted recovery only) |
| Hash-PoW (default) | Hashcash bound to commit randomness | N/A | Protocol cost |
| Unlock mechanism | Hash preimage | Conjectured* | ~256-bit |
*Assumes conservative Grover-style reduction for the hash component.
The overall system security is determined by the weakest component:
- q^128 one-time floor from Layer 4 puzzle for recovering planted solution from (A,b) without guessing (estimator ~2^58 for current params)
- ~2^112 post-quantum from LWE layer (with n=384, σ=25 parameters)
- Configurable PoW for per-commit throttling in online settings
Critical: Low-entropy secrets are vulnerable to offline dictionary attacks regardless of puzzle parameters. Layer 4 provides only ~0.17 µs per-guess overhead on GPU. For high-entropy inputs (256-bit), LWE layer provides ~2^112 security.
- Mix-and-match attacks: Adversary cannot combine outputs from different evaluation paths
- Gate substitution: Each gate's output is bound to all previous computations
- Parallel evaluation attacks: Binding chain prevents independent gate evaluation
- Black-box evaluation: Anyone can evaluate with a valid secret
- Secret recovery: Must rely on LWE hardness for CF hiding
- Side-channel attacks: Not addressed at the cryptographic level
TLOS with n=384 and full-rank 64×64 wire binding costs 3,697,739-17,176,416 gas for 64-640 gates (6-28% of a 60M Ethereum L1 block). Totals below are checkWithPuzzle() (includes puzzle verification cost; see Layer 4 parameters). This is based on Tenderly benchmarks.
| Config | Gates | Total Gas | % of 60M Block |
|---|---|---|---|
| 64 gates | 64 | 3,697,739 | 6% |
| 128 gates | 128 | 4,933,392 | 8% |
| 256 gates | 256 | 8,011,653 | 13% |
| 640 gates | 640 | 17,176,416 | 28% |
Deployment costs (Tenderly, includes circuit data + puzzle + contract):
| Component | 256 gates | 640 gates |
|---|---|---|
| SSTORE2 circuit data | 662,099 | 1,575,259 |
SSTORE2 puzzle b |
136,092 | 136,092 |
| TLOS contract deploy | 1,707,897 | 1,710,198 |
| Total deploy | 2,506,088 | 3,421,549 |
Optimizations applied:
- Wire binding PRG: 16 coefficients per keccak (320 calls vs 4096)
- Single mod at end of LWE inner product
- Batch size 128 (5 binding updates for 640 gates)
- Layer 4 puzzle: n=128, m=192, q=2039 (current hardened parameters)
This is practical for:
- Password-gated vaults and treasure hunts
- Low-frequency, high-value operations
- L1 deployment without requiring L2
- Ma-Dai-Shi 2025: Indistinguishability Obfuscation from Lattices
- TLO: Topology-Lattice Obfuscation (archived)
- Hubacek-Wichs 2015: Somewhere Extractable Hash foundations