A Go implementation of Google-Roughtime and IETF Roughtime drafts 01–19. The repository contains a high-level client package, the low-level protocol package, a high-throughput server, and client, debug, benchmark, and document-stamping commands.
Drafts 12–19 share wire version 0x8000000c. Drafts 14–19 add the TYPE
exchange without changing that value, so clients must support both typed and
untyped peers.
Try the public server:
go run ./cmd/roughtime-client -addr time.txryan.com:2002 \
-pubkey iBVjxg/1j7y1+kQUTBYdTabxCppesU/07D4PMDJk2WA=ML-DSA-44 support is an experimental, non-IETF extension. It uses the FIPS 204 context parameter and TCP framing because its replies exceed the UDP amplification budget. It is not interoperable with standard Roughtime implementations.
Go 1.26 or newer is required.
make buildThis produces roughtime, roughtime-client, roughtime-debug,
roughtime-bench, and roughtime-stamp. Run any command with -h for
its complete flag list.
The server accepts IETF Ed25519 requests over UDP and TCP and Google-Roughtime
over UDP. The experimental ML-DSA-44 suite uses TCP only. The Linux UDP path
uses one SO_REUSEPORT socket per GOMAXPROCS worker and
batched I/O; other Unix systems use a portable socket loop. Windows is not
supported.
Generate a root key and start the server:
roughtime -keygen /path/to/root.key
roughtime -root-key-file /path/to/root.keyUse -pq-keygen and -pq-root-key-file for ML-DSA-44, or configure both
root files. Seed-file permissions are no broader than 0600; existing files
are never overwritten. Online delegation certificates refresh automatically.
With -offline-delegation, root files are read only during startup and the
server stops when the delegation leaves its validity window.
By default the server greases 1% of responses to exercise client error paths;
set -grease-rate 0 when deterministic replies are required.
UDP and TCP share -port (default 2002). On multihomed hosts,
-listen-address binds both listeners to the advertised local address so UDP
replies retain that source. -metrics-addr enables
unauthenticated Prometheus /metrics and /healthz endpoints; bind it to
loopback unless an external access-control layer protects it.
docker build -t roughtime .
mkdir -p keys
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/keys:/keys" \
roughtime -keygen /keys/root.key
docker run --read-only --user "$(id -u):$(id -g)" \
--cap-drop ALL --security-opt no-new-privileges \
-p 2002:2002/udp -p 2002:2002/tcp -v "$PWD/keys:/keys:ro" \
roughtime -root-key-file /keys/root.keyThe example uses the host user so the generated private key remains readable.
Without --user, the runtime image uses UID 65532 and mounted files must be
readable by that UID.
Query one server with -addr and -pubkey, or an ecosystem with
-servers. Multi-server ecosystem queries form a causal chain unless
-chain=false is set. The default samples up to five endpoint-domain groups;
-all disables sampling and queries every transport-compatible entry. That
grouping is a diversity heuristic, not authenticated operator identity or
Sybil resistance.
go run ./cmd/roughtime-client -servers ecosystem.json -allProbe supported versions and inspect authenticated response structure and timing data. Draft 12 is tried in typed and untyped forms.
go run ./cmd/roughtime-debug -addr time.txryan.com:2002 \
-pubkey iBVjxg/1j7y1+kQUTBYdTabxCppesU/07D4PMDJk2WA=A closed-loop load generator intended for servers you control. -verify
checks each signature and Merkle proof; without it, results measure transport
only and may count malformed replies.
go run ./cmd/roughtime-bench -addr 127.0.0.1:2002 -pubkey <key> \
-workers 64 -duration 10s -verifyCreate or verify a document timestamp receipt. New receipts select three compatible endpoint-domain groups and query them twice in the same order. The document is hashed before querying and again immediately before the proof is durably persisted. Verification checks the document, the full causal chain, and the trusted ecosystem. Existing one-pass proofs remain readable and are reported as legacy receipts.
go run ./cmd/roughtime-stamp -doc README.md -servers ecosystem.json \
-out README.md.proof
go run ./cmd/roughtime-stamp -mode verify -doc README.md \
-servers ecosystem.json -in README.md.proofThe top-level package provides Client.Query, concurrent QueryAll, causal
QueryChain, document-bound QueryChainWithNonce, consensus helpers, proof
serialization and offline verification, and ecosystem parsing.
pk, err := roughtime.DecodePublicKey(encodedKey)
if err != nil {
return err
}
server := roughtime.Server{
Name: "example",
PublicKey: pk,
Addresses: []roughtime.Address{{
Transport: "udp",
Address: "example.com:2002",
}},
}
response, err := new(roughtime.Client).Query(ctx, server)The protocol package exposes request parsing/building, reply creation and verification, transports, version negotiation, chaining, and malfeasance reports.
Compatibility details for low-level callers:
- IETF request builders use a 1024-byte body; ML-DSA-44 uses 8192 bytes. The
12-byte
ROUGHTIMframe is additional. RequestOptions.LegacyPacketSizeproduces the historical 1024/8192-byte total packet size required by some deployed peers; the high-level client enables it for interoperability.RequestOptions.OmitTYPEproduces the untyped drafts 12/13 request.VerifyOptions.RequireTYPErequires the draft-14+ typed exchange. The default verifier accepts both forms.ReplyOptions.Draft14NodeFirstselects the node-first Merkle convention from drafts 14–15. The default builder remains hash-first for backward compatibility; verification accepts both node-first and draft-16+ hash-first proofs.NewCertificateWithVersionsbinds the signedVERSlist to a server's actual advertised versions.
ComputeSRV(rootPublicKey) returns the drafts 10+ server binding. Verifiers
check negotiated versions, signed VERS, SRV, delegation validity,
signatures, nonces, timestamps, and Merkle proofs.
make deps # install development tools once
make test # tests
make test-race # race detector
make fuzz # retained parser/verifier fuzzers; FUZZ_TIME defaults to 30s
make lint # go vet and staticcheck
make vuln # reachable-vulnerability scan
make check # dependency, vendor, format, lint, security, build, raceThe vendor tree is excluded from source edits and is checked for reproducibility
with make verify-vendor.
Copyright (c) 2026 Tanner Ryan. All rights reserved. Use of this source code is governed by the BSD 2-Clause License.