Skip to content

tannerryan/roughtime

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

roughtime

Go Reference License

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.

Build

Go 1.26 or newer is required.

make build

This produces roughtime, roughtime-client, roughtime-debug, roughtime-bench, and roughtime-stamp. Run any command with -h for its complete flag list.

Server

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

Use -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

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

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

Commands

roughtime-client

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 -all

roughtime-debug

Probe 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=

roughtime-bench

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 -verify

roughtime-stamp

Create 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.proof

Go API

The 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 ROUGHTIM frame is additional.
  • RequestOptions.LegacyPacketSize produces the historical 1024/8192-byte total packet size required by some deployed peers; the high-level client enables it for interoperability.
  • RequestOptions.OmitTYPE produces the untyped drafts 12/13 request.
  • VerifyOptions.RequireTYPE requires the draft-14+ typed exchange. The default verifier accepts both forms.
  • ReplyOptions.Draft14NodeFirst selects 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.
  • NewCertificateWithVersions binds the signed VERS list 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.

Development

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, race

The vendor tree is excluded from source edits and is checked for reproducibility with make verify-vendor.

License

Copyright (c) 2026 Tanner Ryan. All rights reserved. Use of this source code is governed by the BSD 2-Clause License.

About

Roughtime in Go: Google-Roughtime, IETF drafts 01-19, experimental post-quantum ML-DSA-44, and a production-ready server with automatic certificate refresh.

Topics

Resources

License

Contributing

Security policy

Stars

8 stars

Watchers

1 watching

Forks

Contributors

Languages