Skip to content

Security: igor53627/tlos

docs/SECURITY.md

TLOS Security Model

Overview

TLOS is a compute-and-compare (point-function) obfuscator using a five-layer security model with configurable PoW throttling:

  1. Topology layer - structural mixing (heuristic)
  2. LWE layer - control function hiding via standard LWE with Gaussian noise (σ=25, n=384, ~2^112 PQ)
  3. Wire binding layer - full-rank linear hash for inter-gate consistency (algebraic binding)
  4. Planted LWE puzzle - one-time q^128 floor for recovering planted solution from (A,b) without guessing (8.6M gas)
  5. 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.


Combined Security Model

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³).

Why Structural Attacks Fail

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

Input-Dependent Key Derivation (White-Box Defense)

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.


TLOS vs Simple Keccak Commitment

When to Use Keccak

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

When to Use TLOS

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

Attack Cost Comparison

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.


Function Class: Point Functions Only

TLOS implements compute-and-compare semantics:

P(x) = payload    if x == secret
       garbage    otherwise

Why Not Complex Predicates?

The key derivation s = H(input) means:

  • Only input == secret produces 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 Purpose

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: Planted LWE Puzzle

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.

Parameters (WeakLWEPuzzleV7 - Production)

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)

Dictionary Attack Benchmarks (A100 GPU)

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)

Why Increasing Parameters Doesn't Help

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.

How It Works (V5 Design)

  1. 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
  2. Solver (Bob, who knows secret) computes plantedSecret = H("planted", secret) directly
  3. 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
  4. 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.

Integration with TLOS

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...
}

Post-Quantum Profile

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

Construction

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 circuitSeed and gateIdx
  • x is the wire state (64 bits)
  • Output is 64 × u16 packed into 1024 bits (4 × uint256)

Why Full-Rank 64×64?

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).

Per-Batch Updates

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.

Wire Binding PRG Optimization

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×.

Batch Wire Binding Tradeoff

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)

Hybrid Security Analysis

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.

Security Level

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.


Attack Model

What Wire Binding Prevents

  1. Mix-and-match attacks: Adversary cannot combine outputs from different evaluation paths
  2. Gate substitution: Each gate's output is bound to all previous computations
  3. Parallel evaluation attacks: Binding chain prevents independent gate evaluation

What Wire Binding Does NOT Prevent

  1. Black-box evaluation: Anyone can evaluate with a valid secret
  2. Secret recovery: Must rely on LWE hardness for CF hiding
  3. Side-channel attacks: Not addressed at the cryptographic level

Gas / Practicality Note

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

References

There aren’t any published security advisories