Independent Pi/OpenRouter review: x-ai/grok-4.6
Source: a10323dede4413fbf295916b8ad12e3dbad7514e. Status: completed.
Independent review of timelock-proxy v2 (a10323dede4413fbf295916b8ad12e3dbad7514e)
This is a source review only. I did not execute tests, build the image, talk to NSM/RandomX, or reproduce PCR0. Tests and comments are treated as intent, not evidence.
Verdict on the two questions
| Property | Rating |
|---|---|
| A. Recover/infer request/response contents before the intended delay | SUPPORTED CONDITIONALLY for cryptographic plaintext (inner/upstream TLS + epoch AEAD). Not implied by “RandomX has no shortcut” alone for a one-week wall clock. Metadata/size/destination inference is real and in-scope, but is not a demonstrated plaintext break. |
| B. Early epoch keys / seeds / intermediates / decryption capability, or forcing an already-worked puzzle, without breaking RandomX | SUPPORTED CONDITIONALLY for the lifecycle/composition that is actually in the v2 path. I did not find a protocol path that hands the parent epoch_key, seeds[1..], or wrap outputs, or that accepts a host-supplied puzzle. Faster hardware and shared solving of the same published chain are not blocked by the RandomX assumption. |
I would not rely on this for sensitive content whose premature disclosure is unacceptable. Under the stated assumptions it is a calibrated public time-lock with a measured enclave, not a week-bound VDF, and several confidentiality-adjacent claims (per-request delay, host-invisible destinations, durable capture, post-release provenance) are explicitly weaker than “contents stay secret for ~7 days”).
What the v2 path actually does
Reachable production binary is attested-relay-enclave → crates/enclave/src/v2_main.rs, not legacy tlproxy-enclave. Records are not drand/tlock (crates/common/src/lib.rs seal()). They are XChaCha20-Poly1305 under an epoch key wrapped by seven RandomX segments (crates/timelock/src/lib.rs).
Compiled work parameters (config/relay-v2.toml):
iterations = 43768124(~one Graviton5 full-RandomX day per segment at the claimed 506.5755 h/s)epoch_seconds = 86400require_graviton5 = true
Delay of contents from operator/intermediaries is therefore: inner TLS + upstream TLS while live; after that, whoever holds puzzle.json must evaluate seven dependent RandomX chains. Generation evaluates those seven chains in parallel; solving cannot.
A. Contents before the intended delay
Strongest code-grounded reasons a parent/network adversary does not get plaintext
-
Inner TLS terminates in the enclave, with attestation bound to the actual peer SPKI (
v2_main.rsattestation_for_epoch,tls.rsspki_der, Pythonverify.py_bound_policyrequiringdoc["public_key"] == peer SPKIand a fresh 32-byte nonce). Outer GET/relay?...&payload=is stop-and-wait ciphertext (crates/host/src/http_relay.rs); the host comments that it does not interpret inner bytes. -
Upstream HTTP is TLS’d inside the enclave (
v2_proxy.rsconnect_upstream/net.rsconnect_tls) after enclave DoH to pinned resolver IPs (dns.rs). Parent CONNECT sees an IP and TLS records, not URL path or body. -
The audit record is sealed before the client response is built, and publish ACK is required (
v2_proxy.rs~104–119). Failure →502 relay operation failed, not a plaintext body on the inner channel via that path. Ciphertext is XChaCha20-Poly1305 with AADrelay-encrypted-record-v1 || version || epoch || sequence || puzzle_id(encrypt_recordincrates/timelock/src/lib.rs). -
Diagnostics are static (
v2_diagnostics.rs;v2_main.rslog_infradiscards the formatted string). Host logs are also static (crates/host/src/main.rslog). I did not find a path that prints URL, body, or keys. -
--devcannot silently become production confidentiality. Same PCR0 (runtime flag), but NSM attestation is skipped andmodeis"dev"(attest.rsnew,v2_main.rsattestation JSON). A verifier that requires Nitro +graviton5_verified+ measurediterationsrejects it. A PCR0-only client would not; the Python client is not PCR0-only.
Why “RandomX is strong” does not imply a one-week confidentiality interval
The owner’s RandomX assumption is only: no cryptanalytic shortcut around a specified dependent chain. The intended delay is calibration against one Graviton5 core, encoded as 7 × 43,768,124 hashes (calibrate_workers uses the fastest worker to size a day; config/relay-v2.toml comments that this is “not a hardware speed bound”).
In-scope solvers may use faster CPUs. That does not break RandomX. Wall-clock recovery on day 2–4 is compatible with the primitive assumption and with the threat model’s own “faster hardware can shorten recovery.” Treating “I found no protocol leak” as “one week of confidentiality” would be exactly the error the prompt forbids.
Publication-relative vs per-request:
- Puzzle is public at activation (
v2_epoch.rsactivate). - All records in that epoch share one key.
- Last admitted request of a 24h epoch has ~6 Graviton5-core-days of remaining work, not 7.
- That is the designed delay, not a bug — but it is weaker than a per-request week.
Metadata / inference (do not hide this inside “metadata exceptions”)
v2 boots the network as direct, not Mullvad (v2_main.rs Net::new(transport.clone(), false, Vec::new())). The parent therefore sees every upstream IP. Without ECH, it also sees SNI (net.rs fallback on ECH failure). Response sizes and timings are visible on vsock and on outer /relay payload lengths (MAX_OUTPUT = 32KiB chunks).
That is not AEAD plaintext. It is in-scope inference of destination and, for some sites, of which response was fetched. The objective asked for CONTENTS; I am not rating A as VIOLATED on this alone. It does mean “intermediaries learn nothing about the request” is false.
Conditional guarantees (A)
If Nitro isolation holds, if the client actually runs the Nitro/SPKI/nonce/policy checks (not a fetch-only client), if RandomX+AEAD+HKDF composition is correct, if the adversary’s serial hash rate is close to the Graviton5 calibration, then I did not find a source-level way for parent/Cloudflare/other clients to read URL path or bodies until that chain is evaluated.
That is not a proof of impossibility. Unsafe RandomX FFI, unreviewed vendored C++, kernel/library copies of secrets, and side channels remain open.
B. Early keys, intermediates, or a pre-worked puzzle
Construction (this is the actual sequential bottleneck)
generate_with_rng (crates/timelock/src/lib.rs ~301–398):
- NSM/OS fill of
dataset_key, seven seeds,epoch_key, seven wrap nonces before dataset init; any fill failure aborts; test intent is 16 fills, no OS fallback. - Seven threads each run
iterationsdependent hashes from an independent seed (parallel generation). y_iAEAD-wrapsseed_{i+1}(last wrap isepoch_key) under HKDF-SHA256(ChaCha20-Poly1305), AAD binding epoch/segment/iterations/dataset_key/key_commitment.- Only
seed_1is put in the manifest.GeneratedPuzzleretainsmanifest+epoch_keyonly.
solve (~458–498) is strictly serial: hash segment → unwrap next seed → … → key_commitment check. Checkpoints are digest-integrity only; a forged skip fails AEAD or the commitment. Extra JSON fields are denied.
Generation parallelism does not give solvers free segments, unless those y_i / later seeds leave the enclave. I did not find a send path for them.
Shared work: one puzzle per epoch, by design. Colluding solvers share checkpoints; they do not reduce the dependent hash count on equivalent hardware. They also decrypt every record in that epoch once the key exists.
Lifecycle checks that actually sit on the v2 path
| Concern | What the code does | Residual |
|---|---|---|
| Publish before key is used | activate starts monotonic+NSM clock before puzzle.json is written (v2_epoch.rs 79–88), then ensure!(active.accepts(now)) (98). Delayed ACK cannot rebase an already-disclosed puzzle onto a fresh 24h window. Dev test test_delayed_publication_ack_never_rebases_expired_puzzle_as_fresh_epoch encodes that intent; not executed. |
Host can still ship puzzle.json to solvers at vsock-receipt, seconds before ACK. That is publication, not pre-publication. |
| Next puzzle while current is live | Next generate_with_rng may run concurrently in enclave RAM; publication waits on NSM publication_not_before (v2_epoch.rs 132–140). Active slot is cleared before activate (143). |
Relies on NSM time and on secrets not leaving RAM. Parent pause vs monotonic is discussed below. |
| Host-injected / old puzzle | Enclave never reads a puzzle from the host. Signing key is NSM-random at boot (v2_main.rs 169–171). Restart ⇒ new signer ⇒ new puzzles. |
Host can mix artifacts in the public directory (availability/provenance), not force the running enclave’s epoch.key. |
| Expiration / leases | Watchdog drops the global Arc on monotonic lifetime (v2_epoch.rs 40–52) even if NSM/generation hangs. Admission uses uncached NSM time (trusted_time_ms_uncached, v2_proxy.rs 81–84). Sequence checked_add (next_sequence) refuses wrap. Request lease is the cloned Arc until seal+publish (~30s fetch + 30s publish). |
Pause: if monotonic freezes while NSM advances, accepts() still fails on NSM expiry; key may linger in RAM but not on the host. AWS is trusted for NSM; lingering RAM is not parent-readable under that assumption. |
| Entropy / RNG | Production fill_random is NSM-only; failure zeroizes (attest.rs). seed_os_rng reseeds /dev/random twice or aborts. Record nonces still use OsRng in encrypt_record (timelock lib.rs 103–104), i.e. kernel RNG after that seed, not NSM. |
Conditional on the 4.14 reseed ioctl doing what the comments claim. I did not run it. Weak nonces would be integrity/identity of records more than early epoch keys (24-byte XChaCha nonce). |
| Light vs Full | Production RandomXMode::Full (v2_epoch.rs 120). Light is --dev only. Same hash test-vector is claimed for both; Full is the speed path. Changing iterations changes measured config/PCR0. |
Parent cannot drop iterations without changing PCR0 or running --dev (rejected by a real verifier). |
| Hardware gate | MIDR Neoverse V3, NSM PCR4 = SHA384(0x00×48 || instance-id), EC2 DescribeInstances for c9g.4xlarge + enclaves enabled (hardware.rs). Fail-closed in non-dev. |
Binds generation machine to the calibration class; does not bind solvers. |
I did not find: replay of host ACKs that causes key reuse; checkpoint load inside the enclave; publication of wrapped plaintext; reuse of an already-solved dataset_key/seed_1 pair across boots.
What would count as a B violation and is not shown
- Host obtains
epoch_keyorseeds[1..]before the serial work. - Enclave encrypts live traffic under a puzzle that has already been public for ~a full solve interval.
- A chosen URL/body feeds RandomX or wrap keys (it does not; puzzle generation is independent of
v2_proxyinputs).
Not a B violation in this code, but also not prevented by the RandomX assumption: a faster core finishes the same chain in much less than seven calendar days; after seed_1 is public the operator has the same starting position as everyone else, with first access measured in seconds.
Conditional guarantees (B)
If AWS/Nitro isolation holds, if the RandomX FFI computes the intended dependent chain (vendored C++ was not in this snapshot beyond randomx.h), if AEAD/HKDF wrap is correctly bound, then the lifecycle I traced does not give early equivalent decryption capability via generation parallelism, host storage lies, delayed ACKs, restarts, leases, or chosen inputs.
That is still not a proof. The unsafe binding (crates/timelock/src/randomx.rs: flags | 128 | 16, cache init without RANDOMX_FLAG_V2, unsafe impl Sync for Dataset) is a review target. A wrong-flag VM that still returned the light-mode test vector would be a silent delay collapse; I cannot confirm it from this snapshot.
Concrete issues (code-grounded)
1. One-week claim is not implied by the stated cryptographic assumption
- Attacker: in-scope external solver / colluding operator CPUs.
- Capability: evaluate the published dependent chain faster than Graviton5 full-mode ~506 h/s; no RandomX break required.
- Trace: public
puzzle.jsonatactivate→solveserial loop overiterations=43768124× 7. - Impact: plaintext of all epoch records when the chain finishes, possibly days before “one week.”
- Repro sketch: time
relay-timelock calibrate --mode fullon a faster core; scale43768124/rate*7. Not executed. - Class: limitation of the security objective / calibration, not a missed
if. Must not be sold as a hardware-independent interval.
2. Delay is publication-relative; late-epoch requests are ~1 day short
- Trace:
epoch_seconds = 86400; oneepoch_keyfor all sequences; puzzle public at T0. - Impact: a GET admitted at T0+23h is intended to become public when the T0 puzzle is solved (~6d later at calibration speed).
- Class: matches the owner’s “distinguish publication-relative vs per-request” note. It violates any reading of the objective as per-request seven days.
3. v2 parent sees destinations (and often SNI)
- Trace:
v2_main.rs:156relay_required=false;net.rsECH retry-without-ECH; no WireGuard start on this path. - Attacker: parent/EC2 owner.
- Impact: not body plaintext; full hostname/IP of the intended destination, i.e. a large fraction of request identity.
- Repro: inspect CONNECT targets on vsock 8000 during a
/f/https/<host>/...GET. Not executed.
4. Record nonces/keys vs NSM: mixed entropy story
- Puzzle secrets: NSM
fill_random(generate_with_rng). - Record XChaCha nonces:
OsRng(encrypt_record). - Impact: if kernel RNG were not actually NSM-seeded, record identity/integrity could weaken; epoch_key still NSM. Boot attempts to fail closed (
seed_os_rng). I did not verify the 4.14RNDRESEEDCRNGbehavior.
5. Post-release provenance is not a confidentiality property, but the code invites confusion
Records are not service-signed. After epoch_key is public, anyone can encrypt_record with a new XChaCha nonce (lib.rs 92–124). Puzzle signature does not authenticate a particular ciphertext. Host index is explicitly unauthenticated (artifacts.rs comments). This does not give early decrypt; it means “this ciphertext is what the enclave sealed” is not proved by AEAD+puzzle alone.
6. Host ACK is not durability; capture is not complete
publish (v2_epoch.rs 57–74) treats OK\n as success. A malicious parent can ACK and delete, or kill the enclave after upstream TLS and before seal (v2_proxy.rs fetch then publish). That is the documented availability gap. It does not by itself decrypt contents.
7. Generation longer than the serving epoch
Config comments ~25.14h generation vs 24h epoch. Source: parallel generate then publication_not_before wait; if generate overruns, watchdog already cleared the key (expiration_watchdog) and admission returns 503 (v2_proxy.rs 82–84). Confidentiality fail-closed; availability gaps. Full-duration production generate/rollover is unverified in this review (and in the labeled docs).
8. FFI / missing RandomX source
Snapshot includes vendor/randomx/src/randomx.h and a SHA256SUMS check in crates/timelock/build.rs, not the C++ bodies. I cannot independently confirm the dependent chain, FLAG_V2 interaction with cache init, or JIT/SECURE behavior on aarch64 enclave kernels. Vm::hash is raw randomx_calculate_hash. Memory-safety defects here are in-scope even with PCR0.
9. Live Python get() does not re-bind the puzzle artifact
client.py get() forwards /f/https/... after one verify(). It does not check x-attested-relay-puzzle against the attested signing key. Confidentiality then rests on PCR0 code doing the right encrypt. Archive verify_bundle does bind puzzle parameters to attested policy — that is offline recovery, not the live GET.
Tests not executed
All of them, including:
crates/timelockroundtrip / entropy-fail-closed / tamper testsv2_epochwatchdog / admission testsv2_mainattestation-capacity testtests/test_v2_e2e.py(disconnect capture, delayed ACK, trailers, boot diagnostic hygiene)- RandomX upstream vectors (including ignored full-dataset test)
- hardware SigV4 / PCR4 / MIDR unit tests
Green tests in-tree are not used as proof.
Missing source that limits the conclusion
- Vendored RandomX C/C++ and
SHA256SUMScontents - Legacy
crates/enclave/src/main.rs(should be unreachable fromattested-relay-enclave; not fully shown) crates/clisourcesconfig/enclave.toml(legacy common embed; v2 measuresrelay-v2.tomlinstead)- Actual EIF/PCR build logs (docs at another commit are claims, not runtime)
If RandomX sources differ from upstream aaafe713… while still matching the in-tree checksums, PCR0 would still attest the backdoored hasher. That is a build-identity issue, not something PCR0 solves.
Would I use this for sensitive content?
No, if the content must remain unknown to the operator/solvers for a real week, or forever.
Under the explicit assumptions (AWS/Nitro honest, client verifier honest, RandomX has no shortcut, calibration ≈ attacker serial rate), it is a coherent design: parallel generate, serial solve, NSM entropy, clock-before-puzzle, monotonic key drop, inner TLS+SPKI attestation, seal-before-return. Those are the strongest reasons early protocol decrypt looks blocked.
The same assumptions do not give a week against a faster core, do not hide destinations from the parent, do not make host storage real, and do not prove FFI/memory/side-channel correctness. For data that is acceptable to publish in ~6–7 Graviton5-core-days and whose destination may be known to the operator, this source is consistent with that weaker goal; it is not a substitute for it.