Strengthening the “lie-detecting” infrastructure of internet time
Time is the invisible foundation of internet security. Everything, from TLS certificates to log integrity, digital signatures, blockchains, and build reproducibility, depends on knowing (roughly) what time it is. Yet the web’s time infrastructure is surprisingly fragile: client clocks regularly drift by hours or even days, NTP responses are unauthenticated, and centralized time authorities create single points of failure.
This fragility also makes it difficult to improve security. Ideally, we would issue certificates with short lifetimes to circumscribe the effect of a leaked key. For example, Let’s Encrypt uses 90-day certificates; shorter would be better. However, absent reliable, verifiable time synchronization, shorter expirations simply produce more failures.
Most synchronization uses the Network Time Protocol (NTP). NTP can provide millisecond-level accuracy, but such precision comes at a cost: the protocol is stateful, operationally complex, and vulnerable to amplification attacks. In fact, misconfigured NTP servers have fueled several major DDoS events, including (at the time) the largest attack Cloudflare had ever seen. And for cryptographic security purposes, millisecond precision is unnecessary. Being within a minute of the true time is sufficient for every certificate validation application that we are aware of.
RoughTime offers a different approach. RoughTime is a decentralized protocol that authenticates responses with digital signatures. While it lacks the millisecond precision of NTP – it provides the “rough” time – its responses are verifiable, and the protocol provides a mechanism to detect equivocation and to provably report malicious behavior. It operates statelessly over UDP, cannot be used for traffic amplification, and sidesteps a key bootstrapping problem in secure NTP: if your clock is too far off to verify HTTPS certificates, how do you safely reach an HTTPS server to correct it?
At Sturdy Statistics, we believe RoughTime merits far broader adoption. But the RoughTime ecosystem is only useful if multiple independent organizations participate. Its verification model requires diversification: more operators, across more networks, running more implementations, using different libraries and toolchains.
That’s why we built and open-sourced a new implementation—written from scratch, in Clojure. (Currently Draft 14 of the IETF protocol.)
This post explains our motivation, the architectural and operational decisions behind our design, and why multiple independent servers and multiple independent implementations are essential to RoughTime’s “lie-detecting” model.
RoughTime in a Nutshell
RoughTime is built on a very simple idea: a server signs a statement that includes a client-chosen random value (a nonce) and a timestamp. Because the nonce did not exist before the client generated it, any valid signature proves the server produced the response after receiving the request.
The essential process looks like this:
- A client retrieves the server’s address and public key out of band. (Ours is on our website; Cloudflare maintains an ecosystem file with a list of other participating servers.)
- A client generates a 64 byte nonce and sends it to the RoughTime server. Verification relies upon the nonce being unpredictable and unique.
- The RoughTime server responds with a) the client’s nonce, b) a time range indicating the server’s current time, and c) a signature of them both.
Using the server’s public key, the client checks that the signature is valid. This proves two things:
- Freshness: The response must have been generated after the request, because it includes the client’s unpredictable nonce.
- Authenticity: Only the server holding the matching private key could have produced the signature.
The client now possesses a cryptographically verifiable timestamp from the server, independent of any assumptions about network latency, trusted middlemen, or synchronized clocks.
There’s a little more to the protocol: for example, servers should delegate signatures to limit exposure of its private key (similar to X.509), the server may batch responses using a Merkle tree, and the IETF version of the protocol introduced version specification. But none of these details change the fundamental idea behind RoughTime.
Decentralized Verification via “Happens-Before” Ordering
If desired, the client can derive a new nonce from a previous response. For example, you might use a hash:
next_nonce = SHA512(response_bytes)[0:64]This new nonce can be sent to another RoughTime server, providing a chain of responses and replies with guaranteed happened-before ordering.
This allows the client to construct a sequence like:
- Generate nonce
A→ send to Server A → receive responseA′ - Derive nonce
B = SHA512(A′)[:64]→ send to Server B → receive responseB′ - Derive nonce
C = SHA512(B′)[:64]→ send to Server C → receive responseC′ - And so on…
Each step is cryptographically tied to the previous one. This produces a tamper-evident, cross-server chain with a guaranteed happens-before relationship: A’ happened before B’, B’ happened before C’, etc.
If server B reports an incorrect or inconsistent time, its response will fall outside the time ranges reported by the other servers in the chain. For example:
| Server | reported time range |
|---|---|
| Server A | 12:05:47 ± 10s |
| Server B | 11:49:04 ± 5s |
| Server C | 12:05:41 ± 3s |
Here, the responses from servers A and C are consistent with one another: C should come after A, and their ranges overlap once you account for uncertainty. Server B, however, is clearly incompatible with the ordering—its claimed time cannot fit between A′ and C′. If this happens, the client can:
- Produce the signed responses proving that
A′precedesB′precedesC′, - Demonstrate that server B’s claimed time is incompatible with that ordering, and
- Publish this as a cryptographic proof of misbehavior.
Crucially, any third party can verify this proof without trusting the client and without recourse to a centralized authority. This is the core “lie-detecting” property of the RoughTime protocol: if a server equivocates, it creates evidence against itself.
This is also why the ecosystem needs multiple independent organizations hosting servers. Today, the canonical ecosystem lists only three servers supporting the current IETF protocol and (at the time of writing) one appears to be malfunctioning. This is insufficient for establishing a meaningful chain of trust. We want to help the ecosystem grow, and we hope others will follow.
Anti-Amplification and Secure Design
RoughTime requests are required to be at least 1024 bytes, and the request is required to be larger than the response; smaller requests are simply dropped by the server. (In response to a 1024 byte request, our server responds with a 420 byte response.) This prevents RoughTime servers from acting as DDoS amplifiers, and the minimum request size naturally rate-limits clients.
Using UDP also sidesteps the bootstrapping paradox faced by secure NTP-over-TLS approaches. A client with a severely skewed clock may be unable to perform TLS handshakes, because certificate validation itself depends on accurate time. RoughTime requires no TLS, no HTTP, and no session establishment of any kind, allowing clients to retrieve a verifiable timestamp even when their local clock is wildly incorrect.
Finally, UDP keeps the surface area for security holes intentionally small. One request produces one response; there is no retransmission logic to implement, no state machine, and no long-lived connection. This simplicity reduces the attack surface, improves auditability, and makes independent implementations easier to write, verify, and deploy.
Why Clojure?
We wrote our RoughTime server from scratch in Clojure because we think the language (and the JVM platform beneath it) aligns naturally with the security and auditability goals of the protocol. Though RoughTime is a small protocol, it is essential to implement it correctly. Clojure gives us a clear, predictable, and expressive implementation, without sacrificing performance or operational stability.
Pure Functions and Immutable Data
Clojure’s core abstractions are immutable and persistent. Representing the RoughTime components as immutable values makes the code easier to audit and the data transformations easier to reason about. Immutable data also reduces the risk of side-channel and state-machine bugs, because state cannot drift or mutate unexpectedly.
The entire request/response pipeline is a pure function of its inputs (the client’s nonce and the server’s system time), which makes the implementation straightforward to test and verify.
Operational Reliability with a Mature Cryptographic Platform
In our experience, the JVM excels at running continuously under unpredictable network traffic. It also deploys seamlessly on every OS we plan to use.
The JVM provides mature, well-vetted cryptographic libraries. RoughTime itself is simple, but it relies on secure random-number generation, hashing, and Ed25519 signatures. The Java implementations of these primitives are high-quality, widely deployed, and hardware-accelerated. We wanted a server that could run for years with minimal surprises, and the JVM gives us that confidence.
core.async for Concurrent Request Handling
RoughTime servers may receive bursts of UDP packets, and signatures are CPU-bound operations. Clojure’s core.async concurrency model lets us:
- queue requests without blocking the socket loop,
- batch signatures efficiently,
- isolate the signer thread pool from network I/O,
- and express the async system as a clear, composable graph.
The result is a server that remains stable even under high load, with predictable scheduling and minimal locking.
A Codebase That Invites Audit
One of our goals was to produce an implementation that can be read and understood by its users. Clojure’s data-centric design makes that possible. RoughTime packets are trees of bytes with signatures attached; Clojure’s map objects let the code mirror that structure directly, without boilerplate or added complexity. The code is a sequence of pure transformations that can be inspected line by line.
Explore the Code
If you’re interested in RoughTime (and especially if you’d like to help strengthen the ecosystem) we invite you to explore the implementation and try running a server yourself.
Our website includes a live demonstration showing example requests to, and responses from, multiple servers across the ecosystem. It’s a useful way to see how the protocol works in practice.
You can find our full source code here:
- Core protocol implementation: https://github.com/Sturdy-Statistics/roughtime-protocol
- Command-line client: https://github.com/Sturdy-Statistics/roughtime-client
- UDP server: https://github.com/Sturdy-Statistics/roughtime-server
If you’d like to run your own server, the server README includes detailed instructions for deploying on EC2. We provide a pre-built Makefile which encodes the steps, and a full deployment should only take a few minutes.
The RoughTime ecosystem is still small enough that each new server measurably improves its trustworthiness. If you want to make a direct, practical contribution to internet security, this is one of the simplest and most impactful ways to do it. We’d love to have you join the network.