crates/timelock/README.md

Native RandomX timelock

relay-timelock links RandomX v2.0.1 based on aaafe71322df6602c21a5c72937ac284724ae561, with the native hardening changes in vendor/randomx/PATCHED.md. The build checks the patched sources against vendor/randomx/SHA256SUMS; original digests are retained in UPSTREAM-SHA256SUMS. CMake and a C++ compiler are required. This protocol explicitly selects algorithm V2 (RANDOMX_FLAG_V2), including the v2.0.1 ARM/RISC-V correctness fix. It uses runtime CPU flags, JIT where supported, and secure JIT memory protection. No alternate hash or simulated delay exists.

generate(epoch, iterations, signing_key, mode) returns GeneratedPuzzle with a signed public manifest and a zeroizing secret epoch key. The standalone API uses bounded threads. The Linux enclave uses generate_with_process_progress: a fresh exec starts a single-threaded supervisor, initializes one dataset/cache, then forks bounded workers sharing its read-only pages. Each worker has a private VM and CPU affinity. Fork never runs inside the multithreaded serving process. The exec closes inherited descriptors and clears the environment. Only dataset key and segment seeds cross anonymous pipes; signing and epoch keys stay in the serving process. Fixed-size events carry aggregate progress or final segment outputs inside the enclave. Failed workers abort generation; parent death kills workers. The supervisor and workers exit before a puzzle can be published.

Production uses 96 independent groups with 4,843,750 iterations each, totaling 465,000,000 hashes, on 24 workers. Recovery remains serial across all 96 groups; only the first seed is public. Existing seven- and 84-group manifests remain supported. Generation runs at nice 19 so foreground enclave work takes precedence. Nitro CPU reservations remain exclusive to the enclave. Process isolation and smaller ARM JIT permission updates improve throughput without changing the hash, work count, signed format, W^X protection, or memory wiping. The native API allocates roughly 2080 MiB of dataset plus 256 MiB of cache in full mode, with a small scratchpad per VM. Light mode omits the dataset and recomputes its items; it produces the same hash at lower speed.

Every iteration hashes the fixed byte string relay-timelock-v1, followed by epoch (u64 big-endian), segment (u32 big-endian, zero-based), iteration (u64 big-endian, zero-based), and the previous 32-byte output. The independently random first seed is the initial output; only that seed is published. Every segment output feeds HKDF-SHA256 with protocol salt and a context containing epoch, segment, iterations, dataset key, and epoch-key commitment. ChaCha20- Poly1305 wraps the next random seed (or last epoch key), authenticating that same context. Each wrap has an independent random 96-bit nonce.

Manifests transport bounded, strict JSON with lowercase fixed-size hexadecimal fields. Ed25519 signs a canonical binary representation:

  1. relay-timelock-signed-manifest-v1 and one NUL byte;
  2. version u32, epoch u64 (big-endian);
  3. literal 2.0.1, literal 40-character commit, literal v2;
  4. dataset key 32 bytes, iterations u64, segments u32;
  5. first seed 32 bytes, one 60-byte wrapper per segment (7 or 84 legacy, 96 current);
  6. epoch key SHA-256 commitment, 32 bytes.

The manifest ID is SHA-256(canonical puzzle bytes || 32-byte signer || 64-byte signature). This differs from the content address of its JSON transport file. The required verifier key comes from independent attestation verification.

solve authenticates the manifest before memory allocation. Checkpoints contain manifest ID, segment, iteration and output, with a domain-separated SHA-256 digest. The digest detects accidental corruption; it does not authenticate a hostile local writer. Final segment AEAD and key commitment prevent an invalid checkpoint from returning a false key, while a hostile writer can waste solver computation. The CLI appends and syncs checkpoints to a journal. Restarting the same command resumes the newest parseable complete entry; an incomplete write cannot erase earlier entries. Output keys and generated manifests are created exclusively.

Encrypted audit records derive a separate per-puzzle key using HKDF-SHA256 and XChaCha20-Poly1305 with random 192-bit nonces. Associated data binds version, epoch, sequence and canonical manifest ID. Both plaintext and ciphertext have explicit size limits. Record replay policy belongs to the archive consumer.

cargo test -p relay-timelock -- --test-threads=1
cargo test -p relay-timelock full_mode_upstream_v2_vector -- --ignored
cargo run --release -p relay-timelock -- calibrate --mode full --workers 1 --samples 10000
cargo run --release -p relay-timelock -- calibrate --mode full --workers 7 --samples 10000

The first calibration measures serial solver speed; the second measures seven generator workers sharing one dataset. Use the fastest sustained reference solver speed to set iterations, then check that the slowest generator worker plus dataset initialization finishes within the activation interval. Short calibration samples do not establish a one-day segment or seven-day delay. RandomX is not a formally established VDF and no software can establish a hardware-independent wall-clock lower bound here.